GraphQL in .Net core with Angular7
It’s Graphs All the Way Down
–GraphQL
前言
最近学习了一个新东西,叫做GraphQL,项目主管让我们先了解学习一下,验证一下是否在重构项目时可以应用到。其实大家google一下就知道这是一个什么技术,它是 Facebook 在 2015 年推出的查询语言,现在社区也非常庞大,中文网站上用一句话来解释 就是说这是为你的API而生的查询语言。不得不注意,github在它的第四个版本的API中就选择了使用GraphQL,国外很多网站,例如Facebook、Twitte,cousera,airbnb都已经在使用了。
对比之下,那GraphQL与更早出现的 SOAP 和 REST 来看,GraphQL 其实提供的是一套相对完善的查询语言,而不是类似 REST 的设计规范,妥善使用可以大大提高开发效率。我们知道,Graph是图的意思,图有顶点,有边,可以有向连接,也可以无向连接,GraphQL就是将整个 Web 服务中的全部资源看成一个有连接的图,而不是一个个孤立的资源(区别REST),在访问任何资源时都可以通过资源之间的连接访问其它的资源。
在了解了一些有关于GraphQL的文档之后,我觉得它足以跟REST抗衡,甚至打败REST的点主要有三个。
首先:聚合请求
将多个 HTTP 请求聚合成了一个请求,由根结点连接多个,一次性给了我们一棵大树,让我们用类似SQL的方式去查询,去顺藤摸瓜,一次请求就获取应用所需的所有数据,请求取决于应用,而不是服务器。一次性获取数据,可以提高页面的渲染速度,在移动端网速不理想的情况下也能很快的渲染页面。
其次:灵活多变
当我们需要在请求中减少字段或增加请求字段时,rest要设计大量的接口,但GraphQL不需要,可以定制化我们的请求,大大提高了开发效率。
在者,就是有专门的IDE,像graphiql,替代了swgger,提高了前后端沟通效率
其他的像丰富的类型系统,很强大,还需要继续学习才能体会。
介绍就到这里,实战开始
首先我们创建一个 ASP .NET Core web 项目 GraphQLDemoAPI
接下来为这个项目建一个类库,来存放Model,Service 和最为关键的Schema
这个时候我们安装几个Nuget包
1.GraphQL
2.GraphQL.Server.Transports.AspNetCore
3.GraphQL.Server.Transports.WebSockets
4.GraphQL.Server.Ui.Playground
这些一搜就可以搜到,安装最新稳定版本即可
接下来就是码代码的时刻了,Code First
先创建一个Order的model
using System;
namespace GraphDemo.Orders.Models
{
public class Order
{
//产品单号
public int Id { get; set; }
//产品名称
public string Name { get; set; }
//客户名称
public int UserId { get; set; }
//创建时间
public DateTime CreateDate { get; set; }
//单价
public double Price { get; set; }
//数量
public int Number { get; set; }
//总价
public double TotalAmount { get; set; }
//枚举类型订单评分
public OrderRating OrderRating { get; set; }
}
}
后期我们会用到嵌套,所以我们还需要一个user的model
namespace GraphDemo.Orders.Models
{
public class User
{
public int Id { get; set; }
public string Name { get; set; }
}
}
接下来,去写service的接口,异步的增删改查
using GraphDemo.Orders.Models;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace GraphDemo.Orders.Services
{
public interface IOrderService
{
Task<Order> GetByIdAsync(int id);
Task<IEnumerable<Order>> GetAsync();
Task<Order> CreateAsync(Order order);
Task<Order> DeleteAsync(int id);
Task<Order> UpdateAsync(Order order);
}
}
order的service的具体实现
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using GraphDemo.Orders.Models;
namespace GraphDemo.Orders.Services
{
public class OrderService : IOrderService
{
private readonly IOrderEventService _orderEventService;
private readonly IList<Order> _orders;
public OrderService(IOrderEventService orderEventService)
{
_orderEventService = orderEventService;
#region Orders
_orders = new List<Order>
{
new Order
{
Id =1,
Name = "Gps",
UserId = 1,
CreateDate = new DateTime(2019,3,5),
Price = 200,
Number = 5,
TotalAmount = 1000,
OrderRating = OrderRating.G
},
new Order
{
Id =2,
Name = "喷漆",
UserId = 2,
CreateDate = new DateTime(2019,3,5),
Price = 200,
Number = 5,
TotalAmount = 1000,
OrderRating = OrderRating.G
},
new Order
{
Id =3,
Name = "方向盘",
UserId = 3,
CreateDate = new DateTime(2019,2,5),
Price = 2200,
Number = 10,
TotalAmount = 22000,
OrderRating = OrderRating.G
},
new Order
{
Id =4,
Name = "座椅",
UserId = 4,
CreateDate = new DateTime(2019,3,2),
Price = 5000,
Number = 1,
TotalAmount = 5000,
OrderRating = OrderRating.G
},
new Order
{
Id =5,
Name = "大屏机",
UserId = 5,
CreateDate = new DateTime(2019,3,1),
Price = 1000,
Number = 2,
TotalAmount = 2000,
OrderRating = OrderRating.G
},
};
OrderEventService = orderEventService;
#endregion
}
public IOrderEventService OrderEventService { get; }
public async Task<Order> CreateAsync(Order order)
{
_orders.Add(order);
var orderEvent = new OrderEvent()
{
Name = $"Add Order",
OrderId = order.Id,
TimeStamp = DateTime.Now,
OrderRating = order.OrderRating
};
_orderEventService.AddEvent(orderEvent);
return await Task.FromResult(order);
}
public async Task<Order> DeleteAsync(int id)
{
var order = _orders.SingleOrDefault(x => x.Id == id);
if (order == null)
{
throw new ArgumentException(String.Format("Order ID {0} 不正确", id));
}
else
{
_orders.Remove(order);
}
return await Task.FromResult(order);
}
public Task<IEnumerable<Order>> GetAsync()
{
return Task.FromResult(_orders.AsEnumerable());
}
public Task<Order> GetByIdAsync(int id)
{
var order = _orders.SingleOrDefault(x => x.Id == id);
if(order == null)
{
throw new ArgumentException(String.Format("Order ID {0} 不正确",id));
}
return Task.FromResult(order);
}
public async Task<Order> UpdateAsync(Order order)
{
var oldorder = _orders.SingleOrDefault(x => x.Id == order.Id);
if (oldorder == null)
{
throw new ArgumentException(String.Format("Order ID {0} 不正确", order.Id));
}
else
{
oldorder.Name = order.Name;
oldorder.Number = order.Number;
oldorder.Price = order.Price;
oldorder.TotalAmount = order.TotalAmount;
oldorder.UserId = order.UserId;
oldorder.OrderRating = order.OrderRating;
oldorder.CreateDate = order.CreateDate;
}
return await Task.FromResult(oldorder);
}
}
}
user的类似,不贴代码了。
写完service,重中之中,我们开始写类型系统
创建一个OrderType类并继承ObjectGraphType
枚举类型和其他对象类型作为字段和嵌套查询的要注意类型的声明
这里可以看到,我们直接对user进行了查询,相当于连接了User的query
Filed一般需要指定类型,具体可查官方文档
using GraphDemo.Orders.Models;
using GraphDemo.Orders.Services;
using GraphQL.Types;
namespace GraphDemo.Orders.Schema
{
public class OrderType: ObjectGraphType<Order>
{
public OrderType(IUserService userService)
{
Name = "Order";
Description = "";
Field(x => x.Id);
Field(x => x.Name);
Field(x => x.UserId);
Field(x => x.CreateDate);
Field(x => x.Price);
Field(x => x.Number);
Field(x => x.TotalAmount);
Field<UserType>("user", resolve: context => userService.GetByIdAsync(context.Source.UserId));
Field<OrderRatingEnum>("orderRatings", resolve: context => context.Source.OrderRating);
}
}
}
接下来是Query,相当于是查询的入口和实现,调用service,仔细观察对应的类型,类型不对会导致查询报错
using GraphDemo.Orders.Services;
using GraphQL.Types;
namespace GraphDemo.Orders.Schema
{
public class OrdersQuery: ObjectGraphType
{
public OrdersQuery(IOrderService orderService)
{
Name = "Query";
Field<ListGraphType<OrderType>>("orders", resolve: context => orderService.GetAsync());
}
}
}
最后注入到OrderSchema
using GraphQL;
using System;
using System.Collections.Generic;
using System.Text;
namespace GraphDemo.Orders.Schema
{
public class OrderSchema: GraphQL.Types.Schema
{
public OrderSchema(
IDependencyResolver dependencyResolver,
OrdersQuery ordersQuery,
OrdersMutation ordersMutation,
OrderSubscription orderSubscription)
{
DependencyResolver = dependencyResolver;
Query = ordersQuery;
Mutation = ordersMutation;
Subscription = orderSubscription;
}
}
}
在startup.cs中注入相关实例和服务以及允许跨域请求
using GraphDemo.Orders.Schema;
using GraphDemo.Orders.Services;
using GraphQL;
using GraphQL.Server;
using GraphQL.Server.Ui.Playground;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace GraphQLDemo
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IOrderService, OrderService>();
services.AddSingleton<IUserService, UserService>();
services.AddSingleton<OrderType>();
services.AddSingleton<UserType>();
services.AddSingleton<OrderRatingEnum>();
services.AddSingleton<OrderSchema>();
services.AddSingleton<OrdersQuery>();
services.AddSingleton<OrderInputType>();
services.AddSingleton<OrdersMutation>();
services.AddSingleton<OrderEventType>();
services.AddSingleton<IOrderEventService, OrderEventService>();
services.AddSingleton<OrderSubscription>();
services.AddSingleton<IDependencyResolver>(s => new FuncDependencyResolver(s.GetRequiredService));
services.AddGraphQL(options =>
{
options.EnableMetrics = true;
options.ExposeExceptions = true;
})
.AddWebSockets()
.AddDataLoader();
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddCors(options =>
{
// Policy 名稱 CorsPolicy 是自訂的,可以自己改
options.AddPolicy("jerrygood", policy =>
{
// 設定允許跨域的來源,有多個的話可以用 `,` 隔開
policy.WithOrigins("http://localhost:4200", "http://127.0.0.1")
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials();
});
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseCors("jerrygood");
app.UseWebSockets();
app.UseGraphQLWebSockets<OrderSchema>("/graphql");
app.UseGraphQL<OrderSchema>("/graphql");
app.UseGraphQLPlayground(new GraphQLPlaygroundOptions());
}
}
}
在launchSetitngs.json中配置一下端口号
{
"profiles": {
"GraphQLDemoApi": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "ui/playground",
"applicationUrl": "http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
这里只是拿查询来举例,篇幅原因,mutation不再举例,直接在文章末尾有Demo源码可下载,这里只说思路。接下来看一下运行结果
简单的查询就完成了,可以看到一次请求也查询到了user,可以定制我们需要查询的字段,是不是还蛮不错的。其实最重要的还是对类型系统的学习。
接下来我们去写一下前端代码,联调一下,你会更加感兴趣
首先我们要了解一个东西,就是 Apollo angular,它提供了一些对GraphQL的服务支持
我们只需要简单的ng一下,就可以将Apollo Angular安装到项目中了
ng add apollo-angular
接下来是去在模块中导入,帖一下部分ts代码
import { Component, OnInit } from "@angular/core";
import { Apollo } from "apollo-angular";
import gql from "graphql-tag";
import { Observable } from "rxjs";
import { map } from 'rxjs/operators';
class Order {
constructor(
public id: number = null ,
public Name: string = "",
public UserId: number=null,
public CreateDate: string = "",
public Price: number=null ,
public Number: number=null,
public TotalAmount: number=null ,
public OrderRating: string = ""
) {}
}
@Component({
selector: "app-order",
templateUrl: "./order.component.html",
styleUrls: ["./order.component.css"]
})
export class OrderComponent implements OnInit {
orders: Array<any> = [];
orderModel: Order;
showNew: Boolean = false;
submitType: string = "Save";
selectedRow: number;
orderList: Array<any> = [];
comments: Observable<any>;
constructor(private apollo: Apollo) { }
ngOnInit() {
this.submitType = "Save";
this.displayOrders();
}
displayOrders() {
const getOrders = gql`
{
orders{
id
name
price
number
totalAmount
createDate
user{
id
name
}
}
}
`;
this.apollo
.watchQuery({
query: getOrders,
fetchPolicy: "network-only"
})
.valueChanges.pipe(map((result: any) => result.data.orders))
.subscribe(data => {
this.orders = data;
});
}
最关键的graphql.module.ts中配置接口路由
import { NgModule } from "@angular/core";
import { HttpClientModule } from "@angular/common/http";
// Apollo
import { ApolloModule, Apollo } from "apollo-angular";
import { HttpLinkModule, HttpLink } from "apollo-angular-link-http";
import { InMemoryCache } from "apollo-cache-inmemory";
@NgModule({
exports: [HttpClientModule, ApolloModule, HttpLinkModule]
})
export class GraphQLModule {
constructor(apollo: Apollo, httpLink: HttpLink) {
apollo.create({
link: httpLink.create({ uri: "http://localhost:5000/graphql" }),
cache: new InMemoryCache()
});
}
}
最后让我们看一下运行结果
Graph在.net core 和angular7 中的应用过程就说到这里,放链接容易审核不过,想要源码的留言,哈哈,仅供参考,欢迎指正。