HAL(Hypertext Application Language,超文本應用語言)是一種RESTful API的數據格式風格,爲RESTful API的設計提供了接口規範,同時也下降了客戶端與服務端接口的耦合度。不少當今流行的RESTful API開發框架,包括Spring REST,也都默認支持HAL規範,當RESTful API被調用後,服務端就會返回ContentType爲application/hal+json的JSON內容,例如:css
{ "_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的支持。html
在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)瞭解一下。git
至於Apworks框架,它的主要功能並非僅僅爲了向ASP.NET Core Web API提供HAL的支持,它更重要的是一套基於.NET Core的微服務快速開發框架,不只提供了面向領域驅動(DDD)的基本構造元素(聚合、實體、倉儲、工廠等),並且整合了消息隊列、消息派發及訂閱、消息處理、查詢服務、事件存儲等微服務架構功能模塊,並基於MongoDB、Entity Framework、RabbitMQ、PostgreSQL、SQL Server等基礎服務做出了實現。目前整個框架還在開發和完善階段,讀者有興趣也能夠上Apworks Examples案例項目查看Apworks框架的案例代碼,案例代碼也在同步更新之中。等全部的案例代碼開發完成後,我會對Apworks框架發佈一個相對穩定的版本。github
值得一提的是,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而言,又顯得過重了。因此,咱們仍是從最簡單的案例開始吧。編程
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(); }
[HttpGet] [SupportsHal] public IEnumerable<string> Get() { return new string[] { "value1", "value2" }; }
上面是最簡單的案例,咱們就只用了幾行代碼,花了10分鐘不到,就把ASP.NET Core Web API的默認項目打形成了支持HAL JSON格式的RESTful API項目。大概介紹幾個要點:json
過程很是簡單,並且很是靈活,開發人員能夠徹底自定義所產生的HAL JSON的格式(好比在這個例子中,咱們向RESTful API的結果輸出了請求的URL路徑(也就是上圖中_links.self節點))。接下來,讓咱們看一個稍微複雜一點的應用場景,看看如何經過HAL JSON的RESTful API快速實現服務端分頁。api
分頁,是Web Service開發中不可避免的常見問題。來自服務端的數據量每每很大,即便能夠經過查詢條件來過濾掉很大一部分數據,用戶數據庫中的數據量仍然是沒法估計的。採用服務端分頁,不只能夠減小網絡上的數據傳輸,提升服務響應度,並且還能夠下降客戶端數據處理的壓力,爲用戶提供較好的使用體驗。Apworks.Integration.AspNetCore程序包經過向ASP.NET Core Web API提供HAL的支持,開發人員能夠很是方便地實現專業化的服務端分頁功能。接下來,就讓咱們一塊兒看一個簡單的案例:將26個英文字母進行分頁並根據用戶的輸入條件返回相應頁的數據。瀏覽器
咱們仍舊使用上面的Web API項目:網絡
[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)); }
public void ConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddApworks() .WithHalSupport(new PagedResultHalBuildConfiguration("Values.Get(int, int)")) .Configure(); }
因而可知,基於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對微服務支持的相關內容。