HAL(Hypertext Application Language,超文本应用语言)是一种RESTful API的数据格式风格,为RESTful API的设计提供了接口规范,同时也降低了客户端与服务端接口的耦合度。很多当今流行的RESTful API开发框架,包括Spring REST,也都默认支持HAL规范,当RESTful API被调用后,服务端就会返回ContentType为application/hal+json的JSON内容,例如:

{
"_links": {
"self": {
"href": "http://example.com/api/book/hal-cookbook"
}
},
"_embedded": {
"author": {
"_links":
"self": {
"href": "http://author-example.com"
}
},
"id": "shahadat",
"name": "Shahadat Hossain Khan"
}
},
"id": "hal-cookbook",
"name": "HAL Cookbook"
}

相对于仅返回一个id和一个name的结果而言,这样的JSON Response包含了更为丰富的信息,比如:当前请求的API地址、HAL Cookbook这本书的作者信息,以及访问作者信息的超文本链接。那么客户端在获取到这个服务端的响应后,就能很方便地将这些信息绑定在界面上,而无需通过多个API的调用来查找关联信息。另一方面,这种JSON Response中包含的超文本链接也可以是动态的,比如分页导航链接,这样客户端实现分页功能将变得非常方便。本文着重介绍在ASP.NET Core Web API中,如何为自己设计的RESTful API增加对HAL的支持。

Apworks框架

在ASP.NET Core Web API中为RESTful服务增加对HAL的支持,是通过Apworks框架以及HAL框架来完成的。这两个框架都是我自己设计开发的开源框架,前者基于Apache 2.0开源,后者基于MIT开源,因此完全可以用于商业系统开发。HAL项目为超文本应用语言(Hypertext Application Language)提供了基本的数据模型和处理逻辑,它对JSON的支持基于大名鼎鼎的Newtonsoft.Json,因此性能方面是可以保证的。简便快捷的流畅接口(Fluent Interface API)编程方式,使得构建一个完整合理的HAL对象模型变得非常容易。HAL项目的设计使用了一些对象结构化模式,有兴趣的朋友可以到Github项目主页(https://github.com/daxnet/hal)了解一下。

至于Apworks框架,它的主要功能并不是仅仅为了向ASP.NET Core Web API提供HAL的支持,它更重要的是一套基于.NET Core的微服务快速开发框架,不仅提供了面向领域驱动(DDD)的基本构造元素(聚合、实体、仓储、工厂等),而且整合了消息队列、消息派发及订阅、消息处理、查询服务、事件存储等微服务架构功能模块,并基于MongoDB、Entity Framework、RabbitMQ、PostgreSQL、SQL Server等基础服务作出了实现。目前整个框架还在开发和完善阶段,读者有兴趣也可以上Apworks Examples案例项目查看Apworks框架的案例代码,案例代码也在同步更新之中。等所有的案例代码开发完成后,我会对Apworks框架发布一个相对稳定的版本。

值得一提的是,Apworks框架中的Apworks.Integration.AspNetCore Package提供了对ASP.NET Core Web API的开发扩展,对HAL的支持也是该Package的一个部分。OK,Apworks框架和HAL框架暂时介绍这些,它们不是本文重点。接下来,让我们看看,如何快速地在ASP.NET Core Web API中实现HAL的支持。

最简单的案例

事实上,在《在ASP.NET Core中使用Apworks开发数据服务:对HAL的支持》一文中,我已经介绍了如何在Apworks快速搭建数据服务的同时,提供HAL的JSON数据格式。当时的案例是需要满足数据服务开发模式的,比如需要注入仓储实例,并且Controller需要默认提供GET、POST、PUT、DELETE的操作。这对于仅需要实现某些特定功能的通用Web API Controller而言,又显得太重了。所以,我们还是从最简单的案例开始吧。

  1. 打开Visual Studio 2017,新建一个ASP.NET Core Web API,.NET框架建议选择.NET Core/ASP.NET Core 2.0,项目模板选择Web API,此时会生成一个默认的ASP.NET Core 2.0 Web API应用程序。可以暂且不启用Docker支持
  2. 直接启动这个ASP.NET Core Web API应用程序,将默认在浏览器中输出value1和value2两个值:

  3. 根据《在ASP.NET Core中使用Apworks快速开发数据服务》一文中的介绍,将Apworks和Apworks.Integration.AspNetCore两个Package添加到项目中。注意,版本必须大于等于0.1.138(目前是开发版本)

  4. 添加成功后,ASP.NET Core Web API应用程序的NuGet依赖项类似如下:

  5. 双击打开Startup.cs文件,在ConfigureServices方法中,加入以下代码:
    public void ConfigureServices(IServiceCollection services)
    {
    services.AddMvc(); var halBuildConfiguration = new HalBuildConfiguration(new HalBuildConfigurationItem[]
    {
    new HalBuildConfigurationItem("Values.Get", context =>
    new ResourceBuilder().WithState(null)
    .AddSelfLink()
    .WithLinkItem(context.HttpContext.Request.GetDisplayUrl())
    .AddEmbedded("values")
    .Resource(new ResourceBuilder().WithState(context.State)))
    }); services.AddApworks()
    .WithHalSupport(halBuildConfiguration)
    .Configure();
    }
  6. 双击打开ValuesController.cs文件,在Get()方法上,加上SupportsHal特性:
    [HttpGet]
    [SupportsHal]
    public IEnumerable<string> Get()
    {
    return new string[] { "value1", "value2" };
    }
  7. 再次运行,可以看到,返回结果已经是HAL的JSON格式,Content-Type为application/hal+json:

上面是最简单的案例,我们就只用了几行代码,花了10分钟不到,就把ASP.NET Core Web API的默认项目打造成了支持HAL JSON格式的RESTful API项目。大概介绍几个要点:

  1. 首先需要创建一个HalBuildConfiguration对象,这个对象可以容纳一个或者多个HalBuildConfigurationItem对象,每个HalBuildConfigurationItem对象包含了“需要对哪个Controller的哪个Action的返回值进行HAL格式处理”以及“如何处理API的返回结果并包装成HAL JSON数据格式”这两方面的信息
  2. 构造HalBuildConfigurationItem的第一个参数,用以指定API Controller下的Action的方法签名,以便能够确定HAL处理的API。大致规则如下,但事实上要更加灵活:
    1. *.Get:表示匹配所有Controller下,参数个数为0的Get方法
    2. Values.Get:表示匹配Values Controller下,参数个数为0的Get方法
    3. Values.Get(int,string):表示匹配Values Controller下,参数个数为2,类型分别为int和string的Get方法
  3. 构造HalBuildConfigurationItem的第二个参数,用以指定HAL JSON的产生方式。关于HAL ResourceBuilder的相关详细文档,请参考https://github.com/daxnet/hal
  4. 在相应的Controller Action上使用SupportsHal特性,标注当前方法需要产生HAL JSON的输出。SupportsHalAttribute还可以直接使用在Controller级别,用以标注当前Controller下,所有的Action都能够支持HAL JSON的输出

过程非常简单,而且非常灵活,开发人员可以完全自定义所产生的HAL JSON的格式(比如在这个例子中,我们向RESTful API的结果输出了请求的URL路径(也就是上图中_links.self节点))。接下来,让我们看一个稍微复杂一点的应用场景,看看如何通过HAL JSON的RESTful API快速实现服务端分页。

服务端分页案例介绍

分页,是Web Service开发中不可避免的常见问题。来自服务端的数据量往往很大,即使可以通过查询条件来过滤掉很大一部分数据,用户数据库中的数据量仍然是无法估计的。采用服务端分页,不仅可以减少网络上的数据传输,提高服务响应度,而且还可以降低客户端数据处理的压力,为用户提供较好的使用体验。Apworks.Integration.AspNetCore程序包通过向ASP.NET Core Web API提供HAL的支持,开发人员可以非常方便地实现专业化的服务端分页功能。接下来,就让我们一起看一个简单的案例:将26个英文字母进行分页并根据用户的输入条件返回相应页的数据。

我们仍旧使用上面的Web API项目:

  1. 将ValuesController中的Get()方法改成如下代码:

    [HttpGet]
    [SupportsHal]
    public IActionResult Get(int page = 1, int size = 5)
    {
    var values = new[] { "a", "b", "c", "d", "e",
    "f", "g", "h", "i", "j",
    "k", "l", "m", "n", "o",
    "p","q", "r","s","t",
    "u","v","w","x","y",
    "z" };
    var skip = (page - 1) * size;
    var take = size;
    var records = values.Length;
    var pages = (records + size - 1) / size;
    return Ok(new PagedResult(values.Skip(skip).Take(take), page, size, records, pages));
    }
  2. 修改Startup.cs文件,将ConfigureServices方法改为如下代码,注意HalBuildConfigurationItem的第一个参数,已经变成Values.Get(int, int),表示匹配ValuesController中含有两个int类型参数的Get方法:
    public void ConfigureServices(IServiceCollection services)
    {
    services.AddMvc(); services.AddApworks()
    .WithHalSupport(new PagedResultHalBuildConfiguration("Values.Get(int, int)"))
    .Configure();
    }
  3. Ctrl+F5直接运行Web API应用程序,在浏览器中输出如下:

    可以看到,英文字母已经通过分页形式输出,在_links节点下包含了self、first、last、next的链接,与此同时,服务端还返回了当前页码、每页数、总记录条数以及总页数的信息。

  4. 在浏览器中,输入http://localhost:52566/api/values?page=2,产生的结果如下:

    此时已经显示了第二页的信息,并且分页的链接已经相应地更新了。如果你的浏览器安装了JSON格式数据查看插件,那么很有可能JSON中的超文本链接已经可以直接点击了,于是,你就可以在浏览器中直接点击这些链接来实现各页面的跳转,非常方便。

由此可见,基于Apworks在ASP.NET Core Web API中为RESTful服务增加对HAL的支持还是非常方便的。在上面分页的案例中,分页查询是通过在URL中增加名为page的Query String来实现了,page这个Query String就对应Get方法中的第一个page参数。那么,如果我希望使用其它的Query String来作为分页的页码参数,又该如何做呢?

自定义分页参数

自定义分页参数方法非常简单,只需要在参数定义前加上PageNumberAttribute就行了。比如下面的Get方法:

[HttpGet]
[SupportsHal]
public IActionResult Get([PageNumber] int p = 1, int size = 5)
{
var values = new[] { "a", "b", "c", "d", "e",
"f", "g", "h", "i", "j",
"k", "l", "m", "n", "o",
"p","q", "r","s","t",
"u","v","w","x","y",
"z" };
var skip = (p - 1) * size;
var take = size;
var records = values.Length;
var pages = (records + size - 1) / size;
return Ok(new PagedResult(values.Skip(skip).Take(take), p, size, records, pages));
}

我们通过PageNumberAttribute来指定参数p为分页参数,那么,在访问特定页的数据时,就可以使用http://localhost:52566/api/values?p=2这样的方式:

客户端范例

既然我们已经有了一个服务端分页的RESTful API,我们不妨快速搭建一个客户端App,来试用一下这个支持HAL JSON格式的服务端分页API是否好用。为此,我建立了一个客户端项目,开发采用Angular 4和TypeScript,主要代码如下:

// LettersResponse.ts
export class LettersResponse {
public first: string;
public prev: string;
public next: string;
public last: string;
public values: string[];
} // my-letters-service.service.ts
@Injectable()
export class MyLettersServiceService { constructor(private http: Http) { } getLetters(url: string): Promise<LettersResponse> {
return this.http.get(url)
.toPromise()
.then(response => {
const json = response.json();
return {
first: json._links.first ? json._links.first.href : null,
last: json._links.last ? json._links.last.href : null,
prev: json._links.prev ? json._links.prev.href : null,
next: json._links.next ? json._links.next.href : null,
values: json._embedded.values
};
});
}
} // app.component.ts
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
providers: [MyLettersServiceService]
})
export class AppComponent implements OnInit { response: LettersResponse; constructor(private service: MyLettersServiceService) {
} ngOnInit(): void {
this.service.getLetters(environment.serviceUrl)
.then(res => this.response = res);
} onLinkClicked(url: string): void {
console.log(url);
this.service.getLetters(url)
.then(res => this.response = res);
}
} // app.component.html
<div *ngIf="response">
<ul>
<li *ngFor="let c of response.values">
<h2>{{c}}</h2>
</li>
</ul>
<button (click)="onLinkClicked(response.first)" [disabled]="!response.first">First Page</button>
<button (click)="onLinkClicked(response.prev)" [disabled]="!response.prev">Previous Page</button>
<button (click)="onLinkClicked(response.next)" [disabled]="!response.next">Next Page</button>
<button (click)="onLinkClicked(response.last)" [disabled]="!response.last">Last Page</button>
</div>

在命令行使用NgCLI启动客户端程序,然后访问http://localhost:4200,得到的效果如下:

总结

本文介绍了如何使用Apworks对于ASP.NET Core的扩展,来实现为RESTful API提供HAL支持的功能。文章使用了两个案例,展示了ASP.NET Core Web API对HAL的支持是非常简单方便的,而且自定义功能很强大,第二个服务端分页案例更是引入了Angular和TypeScript,通过实现一个简单的客户端页面来更详细地展示具有HAL特性的RESTful API的便捷之处。正如上文所说,对HAL的支持仅仅是Apworks框架中对ASP.NET Core进行扩展的一个部分,Apworks框架更多地专注于为微服务的快速开发提供解决方案。在今后的文章中,我会更多地介绍Apworks对微服务支持的相关内容。

在ASP.NET Core Web API中为RESTful服务增加对HAL的支持的更多相关文章

  1. [译]ASP.NET Core Web API 中使用Oracle数据库和Dapper看这篇就够了

    [译]ASP.NET Core Web API 中使用Oracle数据库和Dapper看这篇就够了 本文首发自:博客园 文章地址: https://www.cnblogs.com/yilezhu/p/ ...

  2. C#实现多级子目录Zip压缩解压实例 NET4.6下的UTC时间转换 [译]ASP.NET Core Web API 中使用Oracle数据库和Dapper看这篇就够了 asp.Net Core免费开源分布式异常日志收集框架Exceptionless安装配置以及简单使用图文教程 asp.net core异步进行新增操作并且需要判断某些字段是否重复的三种解决方案 .NET Core开发日志

    C#实现多级子目录Zip压缩解压实例 参考 https://blog.csdn.net/lki_suidongdong/article/details/20942977 重点: 实现多级子目录的压缩, ...

  3. ASP.NET Core Web API中使用Swagger

    本节导航 Swagger介绍 在ASP.NET CORE 中的使用swagger   在软件开发中,管理和测试API是一件重要而富有挑战性的工作.在我之前的文章<研发团队,请管好你的API文档& ...

  4. 如何在ASP.NET Core Web API中使用Mini Profiler

    原文如何在ASP.NET Core Web API中使用Mini Profiler 由Anuraj发表于2019年11月25日星期一阅读时间:1分钟 ASPNETCoreMiniProfiler 这篇 ...

  5. ASP.NET Core Web API中带有刷新令牌的JWT身份验证流程

    ASP.NET Core Web API中带有刷新令牌的JWT身份验证流程 翻译自:地址 在今年年初,我整理了有关将JWT身份验证与ASP.NET Core Web API和Angular一起使用的详 ...

  6. 在 ASP.NET Core Web API中使用 Polly 构建弹性容错的微服务

    在 ASP.NET Core Web API中使用 Polly 构建弹性容错的微服务 https://procodeguide.com/programming/polly-in-aspnet-core ...

  7. 翻译一篇英文文章,主要是给自己看的——在ASP.NET Core Web Api中如何刷新token

    原文地址 :https://www.blinkingcaret.com/2018/05/30/refresh-tokens-in-asp-net-core-web-api/ 先申明,本人英语太菜,每次 ...

  8. ASP.NET Core Web API中实现全局异常捕获与处理

    处理全局异常 HANDLING ERRORS GLOBALLY 在上面的示例中,我们的 action 内部有一个 try-catch 代码块.这一点很重要,我们需要在我们的 action 方法体中处理 ...

  9. ASP.NET Core Web API中Startup的使用技巧

    Startup类和服务配置   STARTUP CLASS AND THE SERVICE CONFIGURATION 在 Startup 类中,有两个方法:ConfigureServices 是用于 ...

随机推荐

  1. TargetType Mismatch

    TargetType Mismatch 环境:windowsphone 8,silerlight toolkit, 页面报TargeType Mismatch错误或者 length 0,是因为Syst ...

  2. ASP.NET没有魔法——开篇-用VS创建一个ASP.NET Web程序

    为什么写这一系列文章? 本系列文章基于ASP.NET MVC,在ASP.NET Core已经发布2.0版本,微服务漫天的今天为什么还写ASP.NET?. 答:虽然现在已经有ASP.NET Core并且 ...

  3. 【Windows 10 应用开发】细说文本资源文件(resw)

    最近,小戏骨版<红楼梦>很是火热,老周一口气看完了9集,一直看到 Surface 的风扇呼呼响.林黛玉和薛宝钗这两个角色都演得不怎么样,倒是演史湘云的娃娃演得不错,老周甚是喜欢. 于是,昨 ...

  4. 【转】NAS 黑群晖 配置完成(不含硬盘),NAS能做什么?

    在配黑群晖前,240元入手过一个艾美佳的NAS感受了下,功能倒还合适,就是配置太老,厂家固件也停止更新了,一直不太满意. 后来经常关注NAS1,发现现在X86的NAS也很好自己DIY了,就长草了,向女 ...

  5. iOS在类内部怎么访问实例变量比较好?

    OC在类文件的内部访问实例变量,有直接访问和使用getter/setter方法访问两种方式,它们的区别有: 1.直接访问不经过OC的方法分发(method dispatch),所以访问速度比较快,在这 ...

  6. 调用js中文乱码

    JS文件是GB2312编码的,如果需要js中的中文不出现乱码,在UTF-8编码中调用语句为: <script src="myjs001.js" charset="g ...

  7. OC实现同步访问属性

    有时候,我们在开发过程中需要对属性的访问进行同步操作,这种属性需要做成原子的,用atomic来修饰属性,即可实现这一点. 如果我们想自己实现,可以按照下面方式写代码: SFPerson.h #impo ...

  8. flying-saucer + iText + Freemarker实现pdf的导出, 支持中文、css以及图片

    前言 项目中有个需求,需要将合同内容导出成pdf.上网查阅到了 iText , iText 是一个生成PDF文档的开源Java库,能够动态的从XML或者数据库生成PDF,同时还可以对文档进行加密,权限 ...

  9. ST40 自制 JTAG 适配器

    // 文章首发于 https://zhuanlan.zhihu.com/p/28762429 // 但是不知道为什么搜索引擎还没有收录,便在博客再次发布. 0. 引言 意法半导体生产的 SH4 架构的 ...

  10. Struts2 xml 详解

    <!-- include节点是struts2中组件化的方式 可以将每个功能模块独立到一个xml配置文件中 然后用include节点引用 --> <include file=" ...