GraphQL in ASP.NET Core
一、GraphQL简介
1.什么是GraphQL
GraphQL是一种用于API的查询语言;它也是一个用来执行查询的服务器端运行时,通过您为您的数据定义的类型系统查进行查询的。
GraphQL与数据库、存储技术和开发语言、框架无关。
GraphQL的服务也不限定使用的传输技术,但是通常都是通过HTTP(S)来传输。
查询是可以嵌套的,一次请求,获得多个类型的数据,无需继续钻取数据;客服端不会收到多余的数据。
每当对GraphQL服务器进行查询的时候,都会使用类型系统进行验证;在Schema里定义类型,如下:
GraphQL通常被称作是 声明式数据获取语言。
2.设计原则
层次结构性;
以产品为中心;
强类型;
客户端定制查询;
内省;
3.GraphQL历史
源自Facebook 2012年Facebook开始开发 2015年开源
4.对比Rest
GraphQL指定查询,精确获取;Rest过渡获取。
Rest获取不充分;GraphQL可以嵌套查询,一次性获取结果。
通常,可以使用GraphQL管理Rest端点
5.谁在使用GraphQL
二、图论(Graph Theory)
图论就是研究图的,图可以用来表示一组关联的对象;也可以把图看成是一个包含数据点和连接的对象。例如:人际关系图、家谱、公交线路图。
1.无向图
2.有向图
3.树
三、查询和修改
1、工具GitHub GraplQL API使用
访问地址:https://developer.github.com/v4/explorer/
首先需要登录,登录之后才可以使用。
登录后简单使用
2、查询
GraphQL与SQL对比:SQL查询数据库,GraphQL查询API;SQL的数据存在数据表里,GraphQL的数据可以存在任何地方;SQL使用Select查询数据,GraphQL使用Query;SQL使用Insert、Update、Delete来修改数据,GraphQL使用Mutation修改数据;
•GraphQL的请求
GraphQL查询的内容通过HTTP POST 的body发送给GraphQL端点。查询就是从API获取数据,通过字段来请求查询的数据,这些字段和查询结果的JSON响应的字段对应。
成功查询的JSON结果里包含一个data字段,不成功的查询里包含一个errors字段,里面有具体的错误信息json响应结果可同时包含data和errors字段。
1) Query是GraphQL的一个类型,叫做根类型
2)片段Fragments:可以复用的选择集
3)联合类型Union Type:如果你想返回不止一种类型
4)接口interface
3、修改(mutation)
四、Schema和Types
1.Schema是什么
GraphQL会改变你进行设计的过程。使用Rest的时候,可以把你的API看做是一组Rest端点;而在GraphQL里你把你的API看作一组类型,为你暴露API定义的这组数据类型就叫做Schema。
2.设计Schema
Schema First,使前后端团队在数据类型上保持一致;
GraphQL使用SDL(Schema Definition Language)语言来定义Schema,GraphQL的Schema就是定义可用类型的文本文档,它被客户端和服务器端用来验证GraphQL请求。
3.类型定义
叹号表示不能为null
4.一对一连接
图论里,两个对象之间的连接叫做边,而由一个对象接连到另外一个对象的连接就是一对一连接。
5.一对多连接
要尽量保持GraphQL服务的无向性,也就是说可以从图的任何一个顶点开始遍历。
6.多对多连接
需要在双方的类型里都添加LIst字段,一个多对多连接由两个一对多连接组成,创建多对多连接是,有时需要保存关系本身,这时就需要通过类型,比如用户和角色。
五、使用ASP.Net Core 构建GraphQL
我的项目结构如下:
首先我们定义Models,例如:
public class Movie { public int id { get; set; } public string Name { get; set; } public DateTime ReleaseDate { get; set; } public string Company { get; set; } public int ActorId { get; set; } public MovieRating MovieRating { get; set; } } public class Actor { public int id { get; set; } public string name { get; set; } } [Flags] public enum MovieRating { Untrate=0, G=1, PG=2, PG13=3, R=4, NC17=5 }
再定义接口和现实
public interface IMovieService { Task<Movie> GetByIdAsync(int id); Task<IEnumerable<Movie>> GetAsync(); Task<Movie> CreateAsync(Movie movie); } public class MovieService : IMovieService { private readonly IList<Movie> _movies; public MovieService() { _movies = new List<Movie> { new Movie { id=1, Name="ZYY1", ActorId=1, Company="kindstar", MovieRating=MovieRating.PG, ReleaseDate=new DateTime(2020,1,2) } , new Movie { id=1, Name="ZYY2", ActorId=2, Company="kindstar", MovieRating=MovieRating.PG13, ReleaseDate=new DateTime(2020,1,3) } , new Movie { id=1, Name="ZYY3", ActorId=3, Company="kindstar", MovieRating=MovieRating.G, ReleaseDate=new DateTime(2020,1,4) }, new Movie { id=1, Name="ZYY4", ActorId=4, Company="kindstar", MovieRating=MovieRating.PG, ReleaseDate=new DateTime(2020,1,5) }, new Movie { id=1, Name="ZYY5", ActorId=5, Company="kindstar", MovieRating=MovieRating.Untrate, ReleaseDate=new DateTime(2020,1,2) } }; } public Task<Movie> CreateAsync(Movie movie) { _movies.Add(movie); return Task.FromResult(movie); } public Task<IEnumerable<Movie>> GetAsync() { return Task.FromResult(_movies.AsEnumerable()); } public Task<Movie> GetByIdAsync(int id) { var movie = Task.FromResult(_movies.SingleOrDefault(x => x.id == id)); if(movie==null) { throw new AggregateException(String.Format("ID {0}不正确",id)); } return movie; } }
之后,我们要去Schema里面定义Type、Schema和Mutation,例子如下:
public class MovieType:ObjectGraphType<Movie> { public MovieType(IActorService actorService) { Name = "Movie"; Description = ""; Field(x=>x.id); Field(x => x.Name); Field(x => x.Company); Field(x => x.ReleaseDate); Field(x => x.ActorId); Field<ActorType>("Actor",resolve: context=> actorService.GetByIdAsync(context.Source.ActorId)); //Field(x => x.MovieRating); Field<MovieRatingEnum>("movieRatings", resolve: context => context.Source.MovieRating); Field<StringGraphType>("customString", resolve: context => "1234"); } }
public class MoviesQuery: ObjectGraphType { public MoviesQuery(IMovieService movieSevice) { Name = "Query"; Field<ListGraphType<MovieType>>("movies",resolve:context=> movieSevice.GetAsync()); } }
public class MoviesMutation : ObjectGraphType { public MoviesMutation(IMovieService movieService) { Name = "Mutation"; FieldAsync<MovieType>( "createMovie", arguments: new QueryArguments(new QueryArgument<NonNullGraphType<MovieInputType>> { Name = "movie" }), resolve:async context => { var movieinput = context.GetArgument<MovieInput>("movie"); var movies = await movieService.GetAsync(); var maxid = movies.Select(x=>x.id).Max(); var movie = new Movie { id = ++maxid, Name = movieinput.Name, Company = movieinput.Company, ActorId=movieinput.ActorId, MovieRating=movieinput.MovieRating, ReleaseDate=movieinput.ReleaseDate }; var result = await movieService.CreateAsync(movie); return result; }); } }
在项目中,我们需要引用如下包:
dotnet add package GraphQL
dotnet add package GraphQL.Server.Transports.WebSockets
dotnet add package GraphQL.Server.Transports.AspNetCore
现在去Startup中注入:
public void ConfigureServices(IServiceCollection services) { services.AddSingleton<IMovieService, MovieService>(); services.AddSingleton<IActorService, ActorService>(); services.AddSingleton<MovieType>(); services.AddSingleton<ActorType>(); services.AddSingleton<MovieRatingEnum>(); services.AddSingleton<MoviesQuery>(); services.AddSingleton<MoviesSchema>(); services.AddSingleton<MovieInputType>(); services.AddSingleton<MoviesMutation>(); services.AddSingleton<IDependencyResolver>(s => new FuncDependencyResolver(s.GetRequiredService)); services.AddGraphQL(options => { options.EnableMetrics = true; options.ExposeExceptions = true; }) .AddWebSockets() .AddDataLoader(); services.Configure<KestrelServerOptions>(options => { options.AllowSynchronousIO = true; }); }
注意,在.Net Core3.0中要加入红色字体部分,否则会报错:Synchronous operations are disallowed. Call FlushAsync or set AllowSynchronousIO to true instead
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseWebSockets(); app.UseGraphQLWebSockets<MoviesSchema>("/graphql"); app.UseGraphQL<MoviesSchema>("/graphql"); // use graphql-playground middleware at default url /ui/playground app.UseGraphQLPlayground(new GraphQLPlaygroundOptions()); }
红色部分是加入访问的工具,有4个可以选择,如下:
// use graphiQL middleware at default url /graphiql app.UseGraphiQLServer(new GraphiQLOptions()); // use graphql-playground middleware at default url /ui/playground app.UseGraphQLPlayground(new GraphQLPlaygroundOptions()); // use altair middleware at default url /ui/altair app.UseGraphQLAltair(new GraphQLAltairOptions()); // use voyager middleware at default url /ui/voyager app.UseGraphQLVoyager(new GraphQLVoyagerOptions());
运行后测试如下:
参考地址:https://github.com/graphql-dotnet/server