第 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,作为查询语言,最主要的特点是能够根据客户端准确地获得它所需要的数据

作为 API 查询语言,GraphQL 提供了一种以声明的方式从服务器上获取数据的方法

{
authors{
name,
email
}
}

执行后的结果如下:

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

尽管 GraphQL 能够与 REST 实现同样的目的,但它们各自的实现方式以及特点有较大的差异,主要体现在:

  • (1)端点:对 REST 而言,每一个 URL 相当于一个资源,而 GraphQL 通过一个端点可以返回用户所需要的任何数据
  • (2)请求方式:REST 充分使用 HTTP 动词来访问不同的端点,而 GraphQL 所有请求都是向服务器相同端点发送类似 JSON 格式的信息
  • (3)资源表现形式:REST 得到的资源是事先定义好的固定的数据结构,而 GraphQL 能够根据客户端的请求灵活地返回所需要的形式
  • (4)版本:GraphQL 是在客户端来定义资源的表现形式,因此服务端数据结构变化不影响客户端的使用,即使服务器发生更改,也是向后兼容

GraphQL 仅使用一个端点即可执行并响应所有 Graph 查询请求,因此它完全可以与 Library.API 项目中现有的 REST 端点共存,弥补 RESTful API 的不足

添加nuget

Install-Package GraphQL

GraphQL 中有一个非常重要的概念--Schema,它定义了 GraphQL 服务提供什么样的数据结构,执行查询时,必须指定一个 Schema

添加两个类 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) 。

《ASP.NET Core 与 RESTful API 开发实战》-- (第7章)-- 读书笔记(下)的更多相关文章

  1. 使用ASP.NET Core构建RESTful API的技术指南

    译者荐语:利用周末的时间,本人拜读了长沙.NET技术社区翻译的技术标准<微软RESTFul API指南>,打算按照步骤写一个完整的教程,后来无意中看到了这篇文章,与我要写的主题有不少相似之 ...

  2. 4类Storage方案(AS开发实战第四章学习笔记)

    4.1 共享参数SharedPreferences SharedPreferences按照key-value对的方式把数据保存在配置文件中,该配置文件符合XML规范,文件路径是/data/data/应 ...

  3. 菜单Menu(AS开发实战第四章学习笔记)

    4.5 菜单Menu Android的菜单主要分两种,一种是选项菜单OptionMenu,通过按菜单键或点击事件触发,另一种是上下文菜单ContextMenu,通过长按事件触发.页面的布局文件放在re ...

  4. [Android]《Android艺术开发探索》第一章读书笔记

    1. 典型情况下生命周期分析 (1)一般情况下,当当前Activity从不可见重新变为可见状态时,onRestart方法就会被调用. (2)当用户打开新的Activity或者切换到桌面的时候,回调如下 ...

  5. 温故知新,使用ASP.NET Core创建Web API,永远第一次

    ASP.NET Core简介 ASP.NET Core是一个跨平台的高性能开源框架,用于生成启用云且连接Internet的新式应用. 使用ASP.NET Core,您可以: 生成Web应用和服务.物联 ...

  6. 快读《ASP.NET Core技术内幕与项目实战》WebApi3.1:WebApi最佳实践

    本节内容,涉及到6.1-6.6(P155-182),以WebApi说明为主.主要NuGet包:无 一.创建WebApi的最佳实践,综合了RPC和Restful两种风格的特点 1 //定义Person类 ...

  7. 零基础ASP.NET Core WebAPI团队协作开发

    零基础ASP.NET Core WebAPI团队协作开发 相信大家对“前后端分离”和“微服务”这两个词应该是耳熟能详了.网上也有很多介绍这方面的文章,写的都很好.我这里提这个是因为接下来我要分享的内容 ...

  8. ASP.NET Core WebApi构建API接口服务实战演练

    一.ASP.NET Core WebApi课程介绍 人生苦短,我用.NET Core!提到Api接口,一般会想到以前用到的WebService和WCF服务,这三个技术都是用来创建服务接口,只不过Web ...

  9. 从 0 使用 SpringBoot MyBatis MySQL Redis Elasticsearch打造企业级 RESTful API 项目实战

    大家好!这是一门付费视频课程.新课优惠价 699 元,折合每小时 9 元左右,需要朋友的联系爱学啊客服 QQ:3469271680:我们每课程是明码标价的,因为如果售价为现在的 2 倍,然后打 5 折 ...

  10. Asp.Net Core 5 REST API - Step by Step

    翻译自 Mohamad Lawand 2021年1月19日的文章 <Asp.Net Core 5 Rest API Step by Step> [1] 在本文中,我们将创建一个简单的 As ...

随机推荐

  1. 强烈建议收藏,python库大全

    Python常用库大全及简要说明 本文为大家罗列了Python开发的常用库和各个库的简要说明以及Python开发工具,包管理,环境管理等其它常用资源和Python学习资料.本文只罗列了一部分,完整内容 ...

  2. 【驱动】SPI驱动分析(一)-SPI协议简介

    1. 什么是SPI SPI全拼Serial Peripheral interface(串行外围设备接口),是由Motorola(摩托罗拉)在MC68HCXX系列处理器上定义的,主要应用于EEPROM( ...

  3. DDD领域驱动设计 (C# 整理自“老张的哲学”)

    大话DDD领域驱动设计 概念 Domain Driven Design 领域驱动设计 第一个D(Domain): 领域:指围绕业务为核心而划分的实体模块. 第二个D(Driven): 驱动:这里的驱动 ...

  4. python global函数的使用

    1.在全局变量与局部变量均存在时自定义的函数优先使用局部变量,自定义函数并不能改变全局变量的值. 查看运行结果:  2.在没有局部变量时,使用全局变量,且函数内部不能改变全局变量的值  查看运行结果: ...

  5. http连接池配置及spring boot restTemplate配置http连接池

    本文为博主原创,转载请注明出处: 项目中存在第三方系统之间的服务调用通信,且会进行频繁调用,由于很早之前实现的调用方式为每调用一次外部接口,就需要新建一个HttpClient 对象.由于频繁调用,会存 ...

  6. [转帖]fullgc问题解决:Full GC (Metadata GC Threshold)

    #问题描述 在工作过程中,遇到一个问题:Tomcat在重启或者发布的时候,会有多次的full GC. 笔者使用的版本说明: Tomcat7.0.25 JDK8 首先排查JVM的问题,就要把GC日志打开 ...

  7. [转帖]为什么需要在脚本文件的开头加上#!/ bin / bash?

    本文翻译自:Why do you need to put #!/bin/bash at the beginning of a script file? I have made Bash scripts ...

  8. [转帖]/dev/random 和 /dev/urandom的一点备忘

    https://www.cnblogs.com/ohmygirl/p/random.html 1.  基本介绍 /dev/random和/dev/urandom是Linux系统中提供的随机伪设备,这两 ...

  9. [转帖]MySQL数据类型(decimal的存储大小)

    本来还以为MySQL的数据类型挺简单的,没想到竟然有很多坑,容我仔细道来 MySQL数据类型 整数类型(注意是字节) 浮点型(重点关注decimal) 字符型(注意这是4.x版本的定义,5.x以后已经 ...

  10. [转帖]Nginx性能优化详解

    https://developer.aliyun.com/article/886146?spm=a2c6h.24874632.expert-profile.256.7c46cfe9h5DxWK 感觉文 ...