《ASP.NET Core 與 RESTful API 開發實戰》-- (第7章)-- 讀書筆記(下)

第 7 章 高級主題

7.4 HATEOAS

全稱 Hypermedia AS The Engine Of Application State,即超媒體做爲應用程序狀態引擎。它做爲 REST 統一界面約束中的一個子約束,是 REST 架構中最重要、最複雜,也是構建成熟 REST 服務的核心服務器

Richardson 成熟度模型是根據 REST 約束對 API 成熟度進行衡量的一種方法,該成熟模型使用3個因素來決定服務的成熟度,即 URI、HTTP 方法和 HATEOAS。一個 API 應用程序越多地採用這些特性,就越成熟。根據上述3個因素,RESTful API 應用的成熟度分爲3級:數據結構

  • 第 1 級:資源
  • 第 2 級:HTTP 動詞
  • 第 3 級:超文本驅動,即 HATEOAS

HATEOAS 使 API 在其響應消息中不只提供資源,還提供 URL。這些 URL 可以告訴客戶端如何使用 API,它們由服務器根據應用程序當前的狀態動態生成,而客戶端在獲得響應後,經過這些 URL 就可以知道服務器提供哪些操做,並使用這些連接與服務器進行交互架構

7.5 GraphQL

全稱 Graph Query Language,做爲查詢語言,最主要的特色是可以根據客戶端準確地得到它所須要的數據app

做爲 API 查詢語言,GraphQL 提供了一種以聲明的方式從服務器上獲取數據的方法async

{
    authors{
        name,
        email
    }
}

執行後的結果以下:ide

{
    "data":{
        "authors":{
            "name":"Author 1",
            "email":"author1@xxx.com"
        },
        ...
    }
}

儘管 GraphQL 可以與 REST 實現一樣的目的,但它們各自的實現方式以及特色有較大的差別,主要體如今:ui

  • (1)端點:對 REST 而言,每個 URL 至關於一個資源,而 GraphQL 經過一個端點能夠返回用戶所須要的任何數據
  • (2)請求方式:REST 充分使用 HTTP 動詞來訪問不一樣的端點,而 GraphQL 全部請求都是向服務器相同端點發送相似 JSON 格式的信息
  • (3)資源表現形式:REST 獲得的資源是事先定義好的固定的數據結構,而 GraphQL 可以根據客戶端的請求靈活地返回所須要的形式
  • (4)版本:GraphQL 是在客戶端來定義資源的表現形式,所以服務端數據結構變化不影響客戶端的使用,即便服務器發生更改,也是向後兼容

GraphQL 僅使用一個端點便可執行並響應全部 Graph 查詢請求,所以它徹底能夠與 Library.API 項目中現有的 REST 端點共存,彌補 RESTful API 的不足this

添加nugetspa

Install-Package GraphQL

GraphQL 中有一個很是重要的概念--Schema,它定義了 GraphQL 服務提供什麼樣的數據結構,執行查詢時,必須指定一個 Schemacode

添加兩個類 AuthorType 和 BookType

namespace Library.API.GraphQLSchema
{
    public class AuthorType : ObjectGraphType<Author>
    {
        public AuthorType(IRepositoryWrapper repositoryWrapper)
        {
            Field(x => x.Id, type: typeof(IdGraphType));
            Field(x => x.Name);
            Field(x => x.BirthData);
            Field(x => x.BirthPlace);
            Field(x => x.Email);
            Field<ListGraphType<BookType>>("books", resolve: context => repositoryWrapper.Book.GetBooksAsync(context.Source.Id).Result);
        }
    }
}

namespace Library.API.GraphQLSchema
{
    public class BookType : ObjectGraphType<Book>
    {
        public BookType()
        {
            Field(x => x.Id, type: typeof(IdGraphType));
            Field(x => x.Title);
            Field(x => x.Description);
            Field(x => x.Pages);
        }
    }
}

接下來建立查詢類 LibraryQuery

namespace Library.API.GraphQLSchema
{
    public class LibraryQuery : ObjectGraphType
    {
        public LibraryQuery(IRepositoryWrapper repositoryWrapper)
        {
            // 返回全部做者的信息
            Field<ListGraphType<AuthorType>>("authors",
                resolve: context => repositoryWrapper.Author.GetAllAsync().Result);

            // 返回指定做者信息
            Field<AuthorType>("author", arguments: new QueryArguments(new QueryArgument<IdGraphType>()
                {
                    Name = "id"
                }),
                resolve: context =>
                {
                    Guid id = Guid.Empty;
                    if (context.Arguments.ContainsKey("id"))
                    {
                        id = new Guid(context.Arguments["id"].ToString() ?? string.Empty);
                    }

                    return repositoryWrapper.Author.GetByIdAsync(id).Result;
                });
        }
    }
}

接下來建立 Schema

namespace Library.API.GraphQLSchema
{
    public class LibrarySchema : Schema
    {
        public LibrarySchema(LibraryQuery query, IDependencyResolver denDependencyResolver)
        {
            Query = query;
            DependencyResolver = denDependencyResolver;
        }
    }
}

當 GraphQL 類型、查詢以及 Schema 都建立完成後,應將它們添加到依賴注入容器中

添加一個擴展方法,並在擴展方法中添加全部類型

namespace Library.API.Extentions
{
    public static class GraphQLExtensions
    {
        public static void AddGraphQLSchemaAndTypes(this IServiceCollection services)
        {
            services.AddSingleton<AuthorType>();
            services.AddSingleton<BookType>();
            services.AddSingleton<LibraryQuery>();
            services.AddSingleton<ISchema, LibrarySchema>();
            // 用於執行 Graph 查詢
            services.AddSingleton<IDocumentExecuter, DocumentExecuter>();
            // 用於獲取指定的依賴
            services.AddSingleton<IDependencyResolver>(provider =>
                new FuncDependencyResolver(provider.GetRequiredService));
        }
    }
}

爲了方便解析客戶端請求中的 GraphQL 查詢內容,添加一個類

namespace Library.API.GraphQLSchema
{
    public class GraphQLRequest
    {
        /// <summary>
        /// 用於接收客戶端請求正文中的 Graph 查詢
        /// </summary>
        public string Query { get; set; }
    }
}

接下來,在項目中添加一個控制器

namespace Library.API.Controllers
{
    [Route("graphql")]
    [ApiController]
    public class GraphQLController : ControllerBase
    {
        public IDocumentExecuter DocumentExecuter { get; set; }
        public ISchema LibrarySchema { get; set; }

        public GraphQLController(ISchema librarySchema, IDocumentExecuter documentExecuter)
        {
            LibrarySchema = librarySchema;
            DocumentExecuter = documentExecuter;
        }

        [HttpPost]
        public async Task<IActionResult> Post([FromBody] GraphQLRequest query)
        {
            var result = await DocumentExecuter.ExecuteAsync(options =>
            {
                options.Schema = LibrarySchema;
                options.Query = query.Query;
            });

            if (result.Errors?.Count > 0)
            {
                return BadRequest(result);
            }

            return Ok(result);
        }
    }
}

運行程序,以 POST 方式請求 URL:http://localhost:5001/graphql

請求內容以下:

{
    "query":
    "query{
        authors{
            id,
            name,
            birthPlace,
            birthDate,
            books{
                title,
                pages
            }
        }
    }"
}

能夠獲得與請求的內容徹底一致的請求結果,代表客戶端能夠根據須要在請求的查詢中定義所須要的信息,經過一次查詢,便可返回全部須要的數據

在 LibraryQuery 類中還添加了對指定 author 的查詢,能夠經過如下請求內容查詢

{
    "query":
    "query{
        authors(id:"86072f62-5ec8-4266-9356-752a8496d56a"){
            id,
            name,
            birthPlace,
            birthDate,
            books{
                title,
                pages
            }
        }
    }"
}

知識共享許可協議

本做品採用知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議進行許可。

歡迎轉載、使用、從新發布,但務必保留文章署名 鄭子銘 (包含連接: http://www.cnblogs.com/MingsonZheng/ ),不得用於商業目的,基於本文修改後的做品務必以相同的許可發佈。

若有任何疑問,請與我聯繫 (MingsonZheng@outlook.com) 。

相關文章
相關標籤/搜索