又再一次向 GraphQL 挑戰,什麼是 GraphQL,可以快速閱讀一下這篇文章 2018 GraphQL 漸進式導入的架構,ASP.NET Core 又該如何設定呢? 這裡先簡單的記錄一下,關於更細節的設定,就待以後再來研究了
測試環境
- ASP.NET Core 2.x 版
- 套件 GraphQL
- 前端使用 Angular 來呼叫 GraphQL
後端
套件安裝
- 
安裝 GraphQL for .NET
 | 1
 | dotnet add package GraphQL
 |  
 
基本設定
- 建立 Schema:GraphQL 解析的進入點,定義Query與Mutation
| 12
 3
 4
 5
 6
 7
 
 | public class CaptionSchema: Schema{
 public CaptionSchema(Func<Type, GraphType> resolveType) : base(resolveType)
 {
 Query = (CaptionQuery)resolveType(typeof(CaptionQuery));
 }
 }
 
 | 
- 建立 ObjectGraphType,設定可以查詢的欄位及資料撈取的方式
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 
 | public class CaptionQuery : ObjectGraphType{
 public CaptionQuery(ICaptionRepository captionRepository)
 {
 
 Field<ListGraphType<CaptionType>>(
 "captions",
 arguments: new QueryArguments(
 new QueryArgument<NonNullGraphType<IntGraphType>> {Name = "id", Description = "Category id"}
 ),
 resolve: context =>{ ... }
 }
 }
 
 | 
- 定義 CaptionType,回傳的資料型別
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 
 | public class CaptionType: ObjectGraphType<Caption>{
 public CaptionType(StreamDbContext dbContext)
 {
 Field(x => x.Id).Description("Caption Id");
 Field(x => x.Uid).Description("User Unique ID");
 
 
 Field<ListGraphType<ProductType>>(
 "products",
 resolve: context => productRepository.GetProductsWithByCategoryIdAsync(context.Source.Id).Result.ToList()
 );
 }
 }
 
 | 
- 註冊到 Startup.cs
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 
 | public void ConfigureServices(IServiceCollection services){
 ...
 services.AddMvc();
 
 
 services.AddScoped<CaptionQuery>();
 services.AddTransient<CaptionType>();
 services.AddTransient<ICaptionRepository, CaptionRepository>();
 services.AddScoped<IDocumentExecuter, DocumentExecuter>();
 var sp = services.BuildServiceProvider();
 services.AddScoped<ISchema>(_ => new CaptionSchema(type => (GraphType)sp.GetService(type)) { Query = sp.GetService<CaptionQuery>() });
 
 }
 
 | 
- 為 GraphQL 建立 API EndPoint
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 
 | [Route("graphql")]public class GraphQLController : ControllerBase
 {
 private readonly IDocumentExecuter _documentExecuter;
 private readonly ISchema _schema;
 
 public GraphQLController(IDocumentExecuter documentExecuter, ISchema schema)
 {
 _documentExecuter = documentExecuter;
 _schema = schema;
 }
 
 [HttpPost]
 public async Task<IActionResult> Post([FromBody]GraphQLQuery query)
 {
 if (query == null) { throw new ArgumentNullException(nameof(query)); }
 
 var executionOptions = new ExecutionOptions { Schema = _schema, Query = query.Query, UserContext = HttpContext.User };
 
 try
 {
 var result = await _documentExecuter.ExecuteAsync(executionOptions).ConfigureAwait(false);
 
 if (result.Errors?.Count > 0)
 {
 return BadRequest(result);
 }
 return Ok(result);
 }
 catch (Exception ex)
 {
 return BadRequest(ex);
 }
 }
 }
 
 | 
- 建立 GraphQLQuery.cs
| 12
 3
 4
 5
 6
 7
 8
 9
 
 | using Newtonsoft.Json.Linq;...
 public class GraphQLQuery
 {
 public string OperationName { get; set; }
 public string NamedQuery { get; set; }
 public string Query { get; set; }
 public JObject Variables { get; set; }
 }
 
 | 
	* 有些文章的 Variables 會用 string 型別,但這個在使用 Angular 呼叫時,會出現問題,必須改成 JObject 才不會有問題
到這邊為止,可以算大致上完成 GraphQL 在 asp.net core 後端的設定,以上的範例在 resolve 裡的程式碼實作,可以直接使用 EF 來讀取,或是透過 Repository 的方式來存取都可以
前端
套件安裝
| 1
 | npm install apollo-angular apollo-angular-link-http apollo-link apollo-client apollo-cache-inmemory graphql-tag graphql --save
 | 
或是
| 1
 | yarn add apollo-angular apollo-angular-link-http apollo-link apollo-client apollo-cache-inmemory graphql-tag graphql
 | 
Angular 設定
在 app.modules.ts 新增以下設定
- 
import  ApolloModule和HttpLinkModule
 
- 
在 constructor的地方設定apollo
 | 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 
 | import { ApolloModule, Apollo } from 'apollo-angular';import { HttpLinkModule, HttpLink } from 'apollo-angular-link-http';
 import { InMemoryCache } from 'apollo-cache-inmemory';
 ...
 export class AppModule {
 constructor(apollo: Apollo, httpLink: HttpLink) {
 apollo.create({
 link: httpLink.create({ uri: '[URL]' }),
 cache: new InMemoryCache()
 });
 }
 }
 
 |  
 
Angular 發出第一次 query
先在 component 寫第一次的 GraphQL query
- 
constructor注入apollo服務
 
- 
建立查詢語法 | 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 
 | this.apollo.query<any[]>({
 query: gql`
 query {
 captions {
 id
 label
 colorClass
 }
 }
 `
 })
 .subscribe(value => console.log(value));
 
 |  
 
由於 apollo建立出來的 query 指令是 Observable 型別, 後續的做法就跟 HttpClient 的 get 等相同了
 
- 
當執行這一段理論上就可以從後端撈取所設定的資料集了 
結論
剛開始在碰 GraphQL 時,最麻煩的是摸索設定的階段,一旦設定完成後,後續模組的設定,難度上就還好了。只是還是得規劃一下,希望用怎樣的資料結構讓前端做查詢,畢竟後端沒有設定的欄位,前端是沒有辦法做查詢的,如果前端查詢到後端沒有設定到的欄位時,就會出現錯誤訊息
當然就目前這階段,我並沒有辦法說 GraphQL 的好壞或是適用情境,但至少先完成環境的設置。之後要進一步的研究就比較容易了
參考資料