GraphQL in .Net core with Angular7

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 中的应用过程就说到这里,放链接容易审核不过,想要源码的留言,哈哈,仅供参考,欢迎指正。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值