日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > asp.net >内容正文

asp.net

使用静态基类方案让 ASP.NET Core 实现遵循 HATEOAS Restful Web API

發(fā)布時間:2023/12/4 asp.net 22 豆豆
生活随笔 收集整理的這篇文章主要介紹了 使用静态基类方案让 ASP.NET Core 实现遵循 HATEOAS Restful Web API 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

Hypermedia As The Engine Of Application State (HATEOAS)

HATEOAS(Hypermedia as the engine of application state)是 REST 架構(gòu)風(fēng)格中最復(fù)雜的約束,也是構(gòu)建成熟 REST 服務(wù)的核心。它的重要性在于打破了客戶端和服務(wù)器之間嚴(yán)格的契約,使得客戶端可以更加智能和自適應(yīng),而 REST 服務(wù)本身的演化和更新也變得更加容易。

HATEOAS的優(yōu)點(diǎn)有:

具有可進(jìn)化性并且能自我描述

超媒體(Hypermedia, 例如超鏈接)驅(qū)動如何消費(fèi)和使用API, 它告訴客戶端如何使用API, 如何與API交互, 例如: 如何刪除資源, 更新資源, 創(chuàng)建資源, 如何訪問下一頁資源等等.?

例如下面就是一個不使用HATEOAS的響應(yīng)例子:

{ ? ?"id" : 1, ? ?"body" : "My first blog post", ? ?"postdate" : "2015-05-30T21:41:12.650Z"}

如果不使用HATEOAS的話, 可能會有這些問題:

  • 客戶端更多的需要了解API內(nèi)在邏輯

  • 如果API發(fā)生了一點(diǎn)變化(添加了額外的規(guī)則, 改變規(guī)則)都會破壞API的消費(fèi)者.

  • API無法獨(dú)立于消費(fèi)它的應(yīng)用進(jìn)行進(jìn)化.

如果使用HATEOAS:

{

? ? "id" : 1,

? ? "body" : "My first blog post",

? ? "postdate" : "2015-05-30T21:41:12.650Z",

? ? "links" : [

? ? ? ? {

? ? ? ? ? ? "rel" : "self",

? ? ? ? ? ? "href" : http://blog.example.com/posts/{id},

? ? ? ? ? ? "method" : "GET"

? ? ? ? },

     {

      ? "rel": "update-blog",

       "href": http://blog.example.com/posts/{id},

      ? "method" "PUT"

? ? ? ? }

? ? ? ? ....

? ? ]?

}

這個response里面包含了若干link, 第一個link包含著獲取當(dāng)前響應(yīng)的鏈接, 第二個link則告訴客戶端如何去更新該post.?

Roy Fielding的一句名言: "如果在部署的時候客戶端把它們的控件都嵌入到了設(shè)計(jì)中, 那么它們就無法獲得可進(jìn)化性, 控件必須可以實(shí)時的被發(fā)現(xiàn). 這就是超媒體能做到的." ????

比如說針對上面的例子, 我可以在不改變響應(yīng)主體結(jié)果的情況下添加另外一個刪除的功能(link), 客戶端通過響應(yīng)里的links就會發(fā)現(xiàn)這個刪除功能, 但是對其他部分都沒有影響.

所以說HTTP協(xié)議還是很支持HATEOAS的:

如果你仔細(xì)想一下, 這就是我們平時瀏覽網(wǎng)頁的方式. 瀏覽網(wǎng)站的時候, 我們并不關(guān)心網(wǎng)頁里面的超鏈接地址是否變化了, 只要知道超鏈接是干什么就可以.

我們可以點(diǎn)擊超鏈接進(jìn)行跳轉(zhuǎn), 也可以提交表單, 這就是超媒體驅(qū)動應(yīng)用程序(瀏覽器)狀態(tài)的例子.

如果服務(wù)器決定改變超鏈接的地址, 客戶端程序(瀏覽器)并不會因?yàn)檫@個改變而發(fā)生故障, 這就瀏覽器使用超媒體響應(yīng)來告訴我們下一步該怎么做.

那么怎么展示這些link呢??

JSON和XML并沒有如何展示link的概念. 但是HTML卻知道, anchor元素:?

<a href="uri" rel="type" type="media type">

href包含了URI

rel則描述了link如何和資源的關(guān)系

type是可選的, 它表示了媒體的類型

為了支持HATEOAS, 這些形式就很有用了:

{

? ? ...

? ? "links" : [

? ? ? ? {

? ? ? ? ? ? "rel" : "self",

? ? ? ? ? ? "href" : http://blog.example.com/posts/{id},

? ? ? ? ? ? "method" : "GET"

? ? ? ? }

? ? ? ? ....

? ? ]?

}

method: 定義了需要使用的方法

rel: 表明了動作的類型

href: 包含了執(zhí)行這個動作所包含的URI.

?

為了讓ASP.NET Core Web API 支持HATEOAS, 得需要自己手動編寫代碼實(shí)現(xiàn). 有兩種辦法:

靜態(tài)類型方案: 需要基類(包含link)和包裝類, 也就是返回的資源的ViewModel里面都含有l(wèi)ink, 通過繼承于同一個基類來實(shí)現(xiàn).

動態(tài)類型方案: 需要使用例如匿名類或ExpandoObject等, 對于單個資源可以使用ExpandoObject, 而對于集合類資源則使用匿名類.

這一篇文章介紹如何實(shí)施第一種方案 -- 靜態(tài)類型方案

首先需要準(zhǔn)備一個asp.net core 2.0 web api的項(xiàng)目. 項(xiàng)目搭建的過程就不介紹了, 我的很多文章里都有介紹.

下面開始建立Domain Model -- Vehicle.cs:

using SalesApi.Core.Abstractions.DomainModels;


namespace SalesApi.Core.DomainModels

{

? ? public class Vehicle: EntityBase

? ? {

? ? ? ? public string Model { get; set; }

? ? ? ? public string Owner { get; set; }

? ? }

}

這里的父類EntityBase是我的項(xiàng)目特有的, 您可能不需要.

然后為這個類添加約束(數(shù)據(jù)庫映射的字段長度, 必填等等) VehicleConfiguration.cs:

using Microsoft.EntityFrameworkCore.Metadata.Builders;

using SalesApi.Core.Abstractions.DomainModels;


namespace SalesApi.Core.DomainModels

{

? ? public class VehicleConfiguration : EntityBaseConfiguration<Vehicle>

? ? {

? ? ? ? public override void ConfigureDerived(EntityTypeBuilder<Vehicle> b)

? ? ? ? {

? ? ? ? ? ? b.Property(x => x.Model).IsRequired().HasMaxLength(50);

? ? ? ? ? ? b.Property(x => x.Owner).IsRequired().HasMaxLength(50);

? ? ? ? }

? ? }

}

然后把Vehicle添加到SalesContext.cs:

using Microsoft.EntityFrameworkCore;

using SalesApi.Core.Abstractions.Data;

using SalesApi.Core.DomainModels;


namespace SalesApi.Core.Contexts

{

? ? public class SalesContext : DbContextBase

? ? {

? ? ? ? public SalesContext(DbContextOptions<SalesContext> options)

? ? ? ? ? ? : base(options)

? ? ? ? {

? ? ? ? }


? ? ? ? protected override void OnModelCreating(ModelBuilder modelBuilder)

? ? ? ? {

? ? ? ? ? ? base.OnModelCreating(modelBuilder);

? ? ? ? ? ? modelBuilder.ApplyConfiguration(new ProductConfiguration());

? ? ? ? ? ? modelBuilder.ApplyConfiguration(new VehicleConfiguration());

? ? ? ? ? ? modelBuilder.ApplyConfiguration(new CustomerConfiguration());

? ? ? ? }


? ? ? ? public DbSet<Product> Products { get; set; }

? ? ? ? public DbSet<Vehicle> Vehicles { get; set; }

? ? ? ? public DbSet<Customer> Customers { get; set; }

? ? }

}

建立IVehicleRepository.cs:

using SalesApi.Core.Abstractions.Data;

using SalesApi.Core.DomainModels;


namespace SalesApi.Core.IRepositories

{

? ? public interface IVehicleRepository: IEntityBaseRepository<Vehicle>

? ? {

? ? ? ??

? ? }

}

這里面的IEntityBaseRepository也是我項(xiàng)目里面的類, 您可以沒有.

然后實(shí)現(xiàn)這個VehicleRepository.cs:

using SalesApi.Core.Abstractions.Data;

using SalesApi.Core.DomainModels;

using SalesApi.Core.IRepositories;


namespace SalesApi.Repositories

{

? ? public class VehicleRepository : EntityBaseRepository<Vehicle>, IVehicleRepository

? ? {

? ? ? ? public VehicleRepository(IUnitOfWork unitOfWork) : base(unitOfWork)

? ? ? ? {

? ? ? ? }

? ? }

}

具體的實(shí)現(xiàn)是在我的泛型父類里面了, 所以這里沒有代碼, 您可能需要實(shí)現(xiàn)一下.

然后是重要的部分:

建立一個LinkViewMode.cs 用其表示超鏈接:

namespace SalesApi.Core.Abstractions.Hateoas

{

? ? public class LinkViewModel

? ? {

? ? ? ? public LinkViewModel(string href, string rel, string method)

? ? ? ? {

? ? ? ? ? ? Href = href;

? ? ? ? ? ? Rel = rel;

? ? ? ? ? ? Method = method;

? ? ? ? }

? ? ? ??

? ? ? ? public string Href { get; set; }

? ? ? ? public string Rel { get; set; }

? ? ? ? public string Method { get; set; }

? ? }

}

里面的三個屬性正好就是超鏈接的三個屬性.

然后建立LinkedResourceBaseViewModel.cs, 它將作為ViewModel的父類:

using System.Collections.Generic;

using SalesApi.Core.Abstractions.DomainModels;


namespace SalesApi.Core.Abstractions.Hateoas

{

? ? public abstract class LinkedResourceBaseViewModel: EntityBase

? ? {

? ? ? ? public List<LinkViewModel> Links { get; set; } = new List<LinkViewModel>();

? ? }

}

這樣一個ViewModel就可以包含多個link了.

然后就可以建立VehicleViewModel了:

using SalesApi.Core.Abstractions.DomainModels;

using SalesApi.Core.Abstractions.Hateoas;


namespace SalesApi.ViewModels

{

? ? public class VehicleViewModel: LinkedResourceBaseViewModel

? ? {

? ? ? ? public string Model { get; set; }

? ? ? ? public string Owner { get; set; }

? ? }

}

注冊Repository:

services.AddScoped<IVehicleRepository, VehicleRepository>();

注冊Model/ViewModel到AutoMapper:

CreateMap<Vehicle, VehicleViewModel>();CreateMap<VehicleViewModel, Vehicle>();

建立VehicleController.cs:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Threading.Tasks;

using Microsoft.AspNetCore.Authorization;

using Microsoft.AspNetCore.JsonPatch;

using Microsoft.AspNetCore.Mvc;

using Microsoft.EntityFrameworkCore;

using SalesApi.Core.Abstractions.Hateoas;

using SalesApi.Core.DomainModels;

using SalesApi.Core.IRepositories;

using SalesApi.Core.Services;

using SalesApi.Shared.Enums;

using SalesApi.ViewModels;

using SalesApi.Web.Controllers.Bases;


namespace SalesApi.Web.Controllers

{

? ? [AllowAnonymous]

? ? [Route("api/sales/[controller]")]

? ? public class VehicleController : SalesBaseController<VehicleController>

? ? {

? ? ? ? private readonly IVehicleRepository _vehicleRepository;

? ? ? ? private readonly IUrlHelper _urlHelper;


? ? ? ? public VehicleController(

? ? ? ? ? ? ICoreService<VehicleController> coreService,

? ? ? ? ? ? IVehicleRepository vehicleRepository,

? ? ? ? ? ? IUrlHelper urlHelper) : base(coreService)

? ? ? ? {

? ? ? ? ? ? _vehicleRepository = vehicleRepository;

? ? ? ? ? ? this._urlHelper = urlHelper;

? ? ? ? }


? ? ? ? [HttpGet]

? ? ? ? [Route("{id}", Name = "GetVehicle")]

? ? ? ? public async Task<IActionResult> Get(int id)

? ? ? ? {

? ? ? ? ? ? var item = await _vehicleRepository.GetSingleAsync(id);

? ? ? ? ? ? if (item == null)

? ? ? ? ? ? {

? ? ? ? ? ? ? ? return NotFound();

? ? ? ? ? ? }

? ? ? ? ? ? var vehicleVm = Mapper.Map<VehicleViewModel>(item);

? ? ? ? ? ? return Ok(CreateLinksForVehicle(vehicleVm));

? ? ? ? }


? ? ? ? [HttpPost]

? ? ? ? public async Task<IActionResult> Post([FromBody] VehicleViewModel vehicleVm)

? ? ? ? {

? ? ? ? ? ? if (vehicleVm == null)

? ? ? ? ? ? {

? ? ? ? ? ? ? ? return BadRequest();

? ? ? ? ? ? }


? ? ? ? ? ? if (!ModelState.IsValid)

? ? ? ? ? ? {

? ? ? ? ? ? ? ? return BadRequest(ModelState);

? ? ? ? ? ? }


? ? ? ? ? ? var newItem = Mapper.Map<Vehicle>(vehicleVm);

? ? ? ? ? ? _vehicleRepository.Add(newItem);

? ? ? ? ? ? if (!await UnitOfWork.SaveAsync())

? ? ? ? ? ? {

? ? ? ? ? ? ? ? return StatusCode(500, "保存時出錯");

? ? ? ? ? ? }


? ? ? ? ? ? var vm = Mapper.Map<VehicleViewModel>(newItem);


? ? ? ? ? ? return CreatedAtRoute("GetVehicle", new { id = vm.Id }, CreateLinksForVehicle(vm));

? ? ? ? }


? ? ? ? [HttpPut("{id}", Name = "UpdateVehicle")]

? ? ? ? public async Task<IActionResult> Put(int id, [FromBody] VehicleViewModel vehicleVm)

? ? ? ? {

? ? ? ? ? ? if (vehicleVm == null)

? ? ? ? ? ? {

? ? ? ? ? ? ? ? return BadRequest();

? ? ? ? ? ? }


? ? ? ? ? ? if (!ModelState.IsValid)

? ? ? ? ? ? {

? ? ? ? ? ? ? ? return BadRequest(ModelState);

? ? ? ? ? ? }

? ? ? ? ? ? var dbItem = await _vehicleRepository.GetSingleAsync(id);

? ? ? ? ? ? if (dbItem == null)

? ? ? ? ? ? {

? ? ? ? ? ? ? ? return NotFound();

? ? ? ? ? ? }

? ? ? ? ? ? Mapper.Map(vehicleVm, dbItem);

? ? ? ? ? ? _vehicleRepository.Update(dbItem);

? ? ? ? ? ? if (!await UnitOfWork.SaveAsync())

? ? ? ? ? ? {

? ? ? ? ? ? ? ? return StatusCode(500, "保存時出錯");

? ? ? ? ? ? }


? ? ? ? ? ? return NoContent();

? ? ? ? }


? ? ? ? [HttpPatch("{id}", Name = "PartiallyUpdateVehicle")]

? ? ? ? public async Task<IActionResult> Patch(int id, [FromBody] JsonPatchDocument<VehicleViewModel> patchDoc)

? ? ? ? {

? ? ? ? ? ? if (patchDoc == null)

? ? ? ? ? ? {

? ? ? ? ? ? ? ? return BadRequest();

? ? ? ? ? ? }

? ? ? ? ? ? var dbItem = await _vehicleRepository.GetSingleAsync(id);

? ? ? ? ? ? if (dbItem == null)

? ? ? ? ? ? {

? ? ? ? ? ? ? ? return NotFound();

? ? ? ? ? ? }

? ? ? ? ? ? var toPatchVm = Mapper.Map<VehicleViewModel>(dbItem);

? ? ? ? ? ? patchDoc.ApplyTo(toPatchVm, ModelState);


? ? ? ? ? ? TryValidateModel(toPatchVm);

? ? ? ? ? ? if (!ModelState.IsValid)

? ? ? ? ? ? {

? ? ? ? ? ? ? ? return BadRequest(ModelState);

? ? ? ? ? ? }


? ? ? ? ? ? Mapper.Map(toPatchVm, dbItem);


? ? ? ? ? ? if (!await UnitOfWork.SaveAsync())

? ? ? ? ? ? {

? ? ? ? ? ? ? ? return StatusCode(500, "更新時出錯");

? ? ? ? ? ? }


? ? ? ? ? ? return NoContent();

? ? ? ? }


? ? ? ? [HttpDelete("{id}", Name = "DeleteVehicle")]

? ? ? ? public async Task<IActionResult> Delete(int id)

? ? ? ? {

? ? ? ? ? ? var model = await _vehicleRepository.GetSingleAsync(id);

? ? ? ? ? ? if (model == null)

? ? ? ? ? ? {

? ? ? ? ? ? ? ? return NotFound();

? ? ? ? ? ? }

? ? ? ? ? ? _vehicleRepository.Delete(model);

? ? ? ? ? ? if (!await UnitOfWork.SaveAsync())

? ? ? ? ? ? {

? ? ? ? ? ? ? ? return StatusCode(500, "刪除時出錯");

? ? ? ? ? ? }

? ? ? ? ? ? return NoContent();

? ? ? ? }

private VehicleViewModel CreateLinksForVehicle(VehicleViewModel vehicle)

? ? ? ? {

? ? ? ? ? ? vehicle.Links.Add(

? ? ? ? ? ? ? ? new LinkViewModel(

? ? ? ? ? ? ? ? ? ? href: _urlHelper.Link("GetVehicle", new { id = vehicle.Id }),

? ? ? ? ? ? ? ? ? ? rel: "self",

? ? ? ? ? ? ? ? ? ? method: "GET"));


? ? ? ? ? ? vehicle.Links.Add(

? ? ? ? ? ? ? ? new LinkViewModel(

? ? ? ? ? ? ? ? ? ? href: _urlHelper.Link("UpdateVehicle", new { id = vehicle.Id }),

? ? ? ? ? ? ? ? ? ? rel: "update_vehicle",

? ? ? ? ? ? ? ? ? ? method: "PUT"));


? ? ? ? ? ? vehicle.Links.Add(

? ? ? ? ? ? new LinkViewModel(

? ? ? ? ? ? ? ? href: _urlHelper.Link("PartiallyUpdateVehicle", new { id = vehicle.Id }),

? ? ? ? ? ? ? ? rel: "partially_update_vehicle",

? ? ? ? ? ? ? ? method: "PATCH"));


? ? ? ? ? ? vehicle.Links.Add(

? ? ? ? ? ? new LinkViewModel(

? ? ? ? ? ? ? ? href: _urlHelper.Link("DeleteVehicle", new { id = vehicle.Id }),

? ? ? ? ? ? ? ? rel: "delete_vehicle",

? ? ? ? ? ? ? ? method: "DELETE"));


? ? ? ? ? ? return vehicle;

? ? ? ? }

? ? }

}

在Controller里, 查詢方法返回的都是ViewModel, 我們需要為ViewModel生成Links, 所以我建立了CreateLinksForVehicle方法來做這件事.

假設(shè)客戶通過API得到一個Vehicle的時候, 它可能會需要得到修改(整體修改和部分修改)這個Vehicle的鏈接以及刪除這個Vehicle的鏈接. 所以我把這兩個鏈接放進(jìn)去了, 當(dāng)然別忘了還有本身的鏈接也一定要放進(jìn)去, 放在最前邊.

這里我使用了IURLHelper, 它會通過Action的名字來定位Action, 所以我把相應(yīng)Action都賦上了Name屬性.

在ASP.NET Core 2.0里面使用IUrlHelper需要在Startup里面注冊:

? ? ? ? ? ?services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();

? ? ? ? ? ? services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();

? ? ? ? ? ? services.AddScoped<IUrlHelper>(factory =>

? ? ? ? ? ? {

? ? ? ? ? ? ? ? var actionContext = factory.GetService<IActionContextAccessor>()

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? .ActionContext;

? ? ? ? ? ? ? ? return new UrlHelper(actionContext);

? ? ? ? ? ? });

最后, 在調(diào)用Get和Post方法返回的時候使用CreateLinksForVehicle方法對要返回的VehicleViewModel進(jìn)行包裝, 生成links.

下面我們可以使用POSTMAN來測試一下效果:

首先添加一筆數(shù)據(jù):

返回結(jié)果:

沒問題, 這就是我想要的效果.

然后看一下GET:

也沒問題.

針對集合類返回結(jié)果

上面的例子都是返回單筆數(shù)據(jù), 如果返回集合類的數(shù)據(jù), 我當(dāng)然可以遍歷集合里的每一個數(shù)據(jù), 然后做CreateLinksForVehicle. 但是這樣就無法添加這個GET集合Action本身的link了. 所以針對集合類結(jié)果需要再做一個父類.

LinkedCollectionResourceWrapperViewModel.cs:

using System.Collections.Generic;


namespace SalesApi.Core.Abstractions.Hateoas

{

? ? public class LinkedCollectionResourceWrapperViewModel<T> : LinkedResourceBaseViewModel

? ? ? ? where T : LinkedResourceBaseViewModel

? ? {

? ? ? ? public LinkedCollectionResourceWrapperViewModel(IEnumerable<T> value)

? ? ? ? {

? ? ? ? ? ? Value = value;

? ? ? ? }


? ? ? ? public IEnumerable<T> Value { get; set; }

? ? }

}

這里, 我把集合數(shù)據(jù)包裝到了這個類的value屬性里.

然后在Controller里面添加另外一個方法:

private LinkedCollectionResourceWrapperViewModel<VehicleViewModel> CreateLinksForVehicle(LinkedCollectionResourceWrapperViewModel<VehicleViewModel> vehiclesWrapper)

? ? ? ? {

? ? ? ? ? ? vehiclesWrapper.Links.Add(

? ? ? ? ? ? ? ? new LinkViewModel(_urlHelper.Link("GetAllVehicles", new { }),

? ? ? ? ? ? ? ? "self",

? ? ? ? ? ? ? ? "GET"

? ? ? ? ? ? ));


? ? ? ? ? ? return vehiclesWrapper;

? ? ? ? }

然后針對集合查詢的ACTION我這樣修改:

? ? ? ? [HttpGet(Name = "GetAllVehicles")]

? ? ? ? public async Task<IActionResult> GetAll()

? ? ? ? {

? ? ? ? ? ? var items = await _vehicleRepository.All.ToListAsync();

? ? ? ? ? ? var results = Mapper.Map<IEnumerable<VehicleViewModel>>(items);

? ? ? ? ? ? results = results.Select(CreateLinksForVehicle);

? ? ? ? ? ? var wrapper = new LinkedCollectionResourceWrapperViewModel<VehicleViewModel>(results);

? ? ? ? ? ? return Ok(CreateLinksForVehicle(wrapper));

? ? ? ? }

這里主要有三項(xiàng)工作:

  • 通過results.Select(x => CreateLinksForVehicle(x)) 對集合的每個元素添加links.

  • 然后把集合用上面剛剛建立的父類進(jìn)行包裝

  • 使用剛剛建立的CrateLinksForVehicle重載方法對這個包裝的集合添加本身的link.

  • 最后看看效果:

    嗯, 沒問題.??

    這是第一種實(shí)現(xiàn)HATEOAS的方案, 另外一種等我稍微研究下再寫.

    原文:https://www.cnblogs.com/cgzl/p/8726805.html


    .NET社區(qū)新聞,深度好文,歡迎訪問公眾號文章匯總 http://www.csharpkit.com

    總結(jié)

    以上是生活随笔為你收集整理的使用静态基类方案让 ASP.NET Core 实现遵循 HATEOAS Restful Web API的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。