《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) 。

相关文章
相关标签/搜索