以前、ASP.NET Core で GraphQL API のサンプルを実装してみた。
当時必要だったのはクエリだけだったので、ミューテーションは後回しにしていたけど、 とうとうミューテーションが必要になってしまった。 真面目に GraphQL API を実装するなら、ミューテーションの実装は避けられない。
まずはお試しということで、簡単なクエリとミューテーションを持つ GraphQL API を実装してみた。 ASP.NET Core MVC と Entity Framework Core を使っている。
using GraphiQl; using GraphQL; using GraphQL.Types; using Microsoft.AspNetCore; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using System.Linq; using System.Threading.Tasks; namespace HelloGraphQL { //============================================ // 1. Entity Framework Core で使うクラスを定義 //============================================ public class Player { public int Id { get; set; } public string Name { get; set; } public string Position { get; set; } } public class ApplicationDbContext : DbContext { public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { } public DbSet<Player> Players => Set<Player>(); protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Player>(e => { e.Property(x => x.Id).IsRequired(); e.Property(x => x.Name).IsRequired(); e.Property(x => x.Position).IsRequired(); e.HasKey(x => x.Id); }); } // テスト用のデータを登録する public void EnsureSeedData() { if (!Players.Any()) { Players.AddRange( new Player { Name = "メッシ", Position = "FW", }, new Player { Name = "スアレス", Position = "FW", }, new Player { Name = "ベイル", Position = "FW", }, new Player { Name = "モドリッチ", Position = "MF", } ); SaveChanges(); } } } //============================== // 2. GraphQL で使うクラスを定義 //============================== // Player に対応する GraphQL の型 public class PlayerType : ObjectGraphType<Player> { public PlayerType() { Field(x => x.Id); Field(x => x.Name); Field(x => x.Position); } } // クエリを定義 public class HelloGraphQLQuery : ObjectGraphType { public HelloGraphQLQuery() { Field<ListGraphType<PlayerType>>( "players", arguments: new QueryArguments( new QueryArgument<IntGraphType> { Name = "id", }), resolve: context => { var dbContext = (ApplicationDbContext)context.UserContext; var query = dbContext.Players.AsQueryable(); if (context.HasArgument("id")) { var id = context.GetArgument<int>("id"); query = query.Where(b => b.Id == id); } return query.ToList(); }); } } // ミューテーションを定義 public class HelloGraphQLMutation : ObjectGraphType { public HelloGraphQLMutation() { Field<PlayerType>( "createPlayer", arguments: new QueryArguments( new QueryArgument<NonNullGraphType<StringGraphType>>() { Name = "name", }, new QueryArgument<NonNullGraphType<StringGraphType>>() { Name = "position", } ), resolve: context => { var dbContext = (ApplicationDbContext)context.UserContext; var name = context.GetArgument<string>("name"); var position = context.GetArgument<string>("position"); var player = new Player() { Name = name, Position = position, }; dbContext.Players.Add(player); dbContext.SaveChanges(); return player; }); Field<PlayerType>( "updatePlayer", arguments: new QueryArguments( new QueryArgument<NonNullGraphType<IntGraphType>>() { Name = "id", }, new QueryArgument<StringGraphType>() { Name = "name", }, new QueryArgument<StringGraphType>() { Name = "position", } ), resolve: context => { var dbContext = (ApplicationDbContext)context.UserContext; var id = context.GetArgument<int>("id"); var player = dbContext.Players.Find(id); if (player != null) { if (context.HasArgument("name")) { player.Name = context.GetArgument<string>("name"); } if (context.HasArgument("position")) { player.Position = context.GetArgument<string>("position"); } dbContext.Players.Update(player); dbContext.SaveChanges(); return player; } else { return null; } }); Field<PlayerType>( "deletePlayer", arguments: new QueryArguments( new QueryArgument<NonNullGraphType<IntGraphType>>() { Name = "id", } ), resolve: context => { var dbContext = (ApplicationDbContext)context.UserContext; var id = context.GetArgument<int>("id"); var player = dbContext.Players.Find(id); if (player != null) { dbContext.Players.Remove(player); dbContext.SaveChanges(); return player; } else { return null; } }); } } // GraphQL のスキーマを定義 public class HelloGraphQLSchema : Schema { public HelloGraphQLSchema(IDependencyResolver dependencyResolver) : base(dependencyResolver) { Query = dependencyResolver.Resolve<HelloGraphQLQuery>(); Mutation = dependencyResolver.Resolve<HelloGraphQLMutation>(); } } //==================================================== // 3. ASP.NET Core MVC で GraphQL エンドポイントを実装 //==================================================== // クライアントから送信されてきた GraphQL をバインドするモデル public class GraphQLModel { public string OperationName { get; set; } public string Query { get; set; } } [Route("graphql")] [ApiController] public class GraphQLController : ControllerBase { readonly IDocumentExecuter documentExecuter; readonly ISchema schema; readonly ApplicationDbContext dbContext; public GraphQLController( IDocumentExecuter documentExecuter, ISchema schema, ApplicationDbContext dbContext) { this.documentExecuter = documentExecuter; this.schema = schema; this.dbContext = dbContext; } [HttpPost] public async Task<IActionResult> Post([FromBody] GraphQLModel model) { var result = await documentExecuter.ExecuteAsync(new ExecutionOptions { Schema = schema, OperationName = model.OperationName, Query = model.Query, UserContext = dbContext, }); return Ok(result); } } //============================================================== // 4. いつものように ASP.NET Core 用の Startup と Program を作成 //============================================================== public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } public void ConfigureServices(IServiceCollection services) { services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))); services.AddMvc() .SetCompatibilityVersion(CompatibilityVersion.Version_2_2); // GraphQL 用 services.AddSingleton<PlayerType>(); services.AddSingleton<HelloGraphQLQuery>(); services.AddSingleton<HelloGraphQLMutation>(); services.AddSingleton<ISchema, HelloGraphQLSchema>(); services.AddSingleton<IDocumentExecuter, DocumentExecuter>(); services.AddSingleton<IDependencyResolver>(x => new FuncDependencyResolver(x.GetService)); } public void Configure(IApplicationBuilder app, IHostingEnvironment env, ApplicationDbContext dbContext) { app.UseGraphiQl("/graphiql", "/graphql"); app.UseMvc(); // テスト用データベースが無ければ作る dbContext.Database.EnsureCreated(); dbContext.EnsureSeedData(); } } public class Program { public static void Main(string[] args) { CreateWebHostBuilder(args).Build().Run(); } public static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost.CreateDefaultBuilder(args) .UseStartup<Startup>(); } }
デバッグ実行して動作確認。 まずは登録した初期データをクエリで取得してみる。
選手を 1 人追加してみる。 ストーブリーグで注目の日本人選手筆頭、堂安を追加。
ちゃんと追加されたかクエリで確認。
追加されていたので、今度は選手の情報を更新してみる。 メッシは右サイドが主戦場なので、RWG に変更。
更新されたかクエリで確認。
ポジションを RWG に変更できていた。 最後に選手の情報を削除してみる。 ベイルはよく負傷で離脱するので登録削除。
削除されたことをクエリで確認。
削除成功。
単純な CRUD だけど、GraphQL API で実現できた。 クエリとミューテーション、どちらも実装方法は似たようなもの。 今回のサンプル程度でわかる明確な違いは、クエリは resolve 時にデータベースからデータを取得して返すのに対し、 ミューテーションは resolve 時にデータベースに変更を加える、という点くらい。