ASP.NET Core 用の GraphQL Server を使うことで、 ASP.NET Core プロジェクトに GraphQL API を実装するのが楽になりそうだった。
ただ、ASP.NET Core MVC でエンドポイントを自作する必要はなくなるけど、resolver は依然書く必要がある。 ミューテーションはともかくクエリなら、GraphQL.EntityFramework を使うことで、さらに実装を楽にできるかも。
というわけで試してみた。
using GraphQL; using GraphQL.EntityFramework; using GraphQL.Server; using GraphQL.Server.Ui.Playground; using GraphQL.Types; using Microsoft.AspNetCore; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using System.Linq; namespace HelloGraphQL { //============================================ // 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(); } } } //============================== // GraphQL で使うクラスを定義 //============================== // Player に対応する GraphQL の型 public class PlayerType : EfObjectGraphType<Player> { public PlayerType(IEfGraphQLService graphQLService) : base(graphQLService) { Field(x => x.Id); Field(x => x.Name); Field(x => x.Position); } } // クエリを定義 public class HelloGraphQLQuery : EfObjectGraphType { public HelloGraphQLQuery(IEfGraphQLService graphQLService) : base(graphQLService) { AddQueryConnectionField<PlayerType, Player>( name: "players", resolve: context => { var dbContext = (ApplicationDbContext)context.UserContext; return dbContext.Players.AsQueryable(); }); } } // ミューテーションを定義 // ミューテーションでは GraphQL.EntityFramework のサポートは無いので、 // GraphQL.NET で実装したときのまま。 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>(); } } //============================================================== // 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"))); // GraphQL Server 用 services.AddGraphQL() .AddUserContextBuilder(httpContext => { // UserContext として ApplicationDbContext を // resolver に渡す。 return httpContext.RequestServices .GetService<ApplicationDbContext>(); }); //============================ // GraphQL.EntityFramework 用 //============================ // コネクション用の型(ConnectionType<T>, EdgeType<T>, PageInfoType) // を DI コンテナに登録する EfGraphQLConventions.RegisterConnectionTypesInContainer(services); // GraphQL.EntityFramework が使う型(EfGraphQLServer とか)を登録する。 // DbContext は EfGraphQLServer に渡されるが、 // EfGraphQLServer が必要なのは DbContext.Model だけなので、 // DbContext のインスタンスは即 Dispose してしまっていい。 using (var dbContext = services.BuildServiceProvider().GetService<ApplicationDbContext>()) { EfGraphQLConventions.RegisterInContainer(services, dbContext); } // 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) { // GraphQL Server を使う app.UseGraphQL<ISchema>("/graphql"); // GraphQL Playground を使う app.UseGraphQLPlayground(new GraphQLPlaygroundOptions { Path = "/ui/playground", }); // テスト用データベースが無ければ作る 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>(); } }
GraphQL Playground でクエリを実行してみる。 フィルタ用の where やソート用の order といった引数を GraphQL.EntityFramework が生成してくれている。
外部に公開する API なら導入をちょっと躊躇するけど、 内部で使う API ならこれでいいかもしれないな。 個人開発なので、できればサクッと実装を済ませたいし。