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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > asp.net >内容正文

asp.net

使用 dynamic 类型让 ASP.NET Core 实现 HATEOAS 结构的 RESTful API

發布時間:2023/12/4 asp.net 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 使用 dynamic 类型让 ASP.NET Core 实现 HATEOAS 结构的 RESTful API 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

上一篇寫的是使用靜態基類方法的實現步驟:?

使用dynamic (ExpandoObject)的好處就是可以動態組建返回類型, 之前使用的是ViewModel, 如果想返回結果的話, 肯定需要把ViewModel所有的屬性都返回, 如果屬性比較多, 就有可能造成性能和靈活性等問題. 而使用ExpandoObject(dynamic)就可以解決這個問題.

返回一個對象

返回一個dynamic類型的對象, 需要把所需要的屬性從ViewModel抽取出來并轉化成dynamic對象, 這里所需要的屬性通常是從參數傳進來的, 例如針對下面的CustomerViewModel類, 參數可能是這樣的: "Name, Company":

using System;

using SalesApi.Core.Abstractions.DomainModels;


namespace SalesApi.ViewModels

{

? ? public class CustomerViewModel: EntityBase

? ? {

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

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

? ? ? ? public DateTimeOffset EstablishmentTime { get; set; }

? ? }

}

還需要一個Extension Method可以把對象按照需要的屬性轉化成dynamic類型:

using System;

using System.Collections.Generic;

using System.Dynamic;

using System.Reflection;


namespace SalesApi.Shared.Helpers

{

? ? public static class ObjectExtensions

? ? {

? ? ? ? public static ExpandoObject ToDynamic<TSource>(this TSource source, string fields = null)

? ? ? ? {

? ? ? ? ? ? if (source == null)

? ? ? ? ? ? {

? ? ? ? ? ? ? ? throw new ArgumentNullException("source");

? ? ? ? ? ? }


? ? ? ? ? ? var dataShapedObject = new ExpandoObject();

? ? ? ? ? ? if (string.IsNullOrWhiteSpace(fields))

? ? ? ? ? ? {

? ? ? ? ? ? ? ? // 所有的 public properties 應該包含在ExpandoObject里?

? ? ? ? ? ? ? ? var propertyInfos = typeof(TSource).GetProperties(BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);

? ? ? ? ? ? ? ? foreach (var propertyInfo in propertyInfos)

? ? ? ? ? ? ? ? {

? ? ? ? ? ? ? ? ? ? // 取得源對象上該property的值

? ? ? ? ? ? ? ? ? ? var propertyValue = propertyInfo.GetValue(source);

? ? ? ? ? ? ? ? ? ? // 為ExpandoObject添加field

? ? ? ? ? ? ? ? ? ? ((IDictionary<string, object>)dataShapedObject).Add(propertyInfo.Name, propertyValue);

? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? return dataShapedObject;

? ? ? ? ? ? }


? ? ? ? ? ? // field是使用 "," 分割的, 這里是進行分割動作.

? ? ? ? ? ? var fieldsAfterSplit = fields.Split(',');

? ? ? ? ? ? foreach (var field in fieldsAfterSplit)

? ? ? ? ? ? {

? ? ? ? ? ? ? ? var propertyName = field.Trim();


? ? ? ? ? ? ? ? // 使用反射來獲取源對象上的property

? ? ? ? ? ? ? ? // 需要包括public和實例屬性, 并忽略大小寫.

? ? ? ? ? ? ? ? var propertyInfo = typeof(TSource).GetProperty(propertyName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);

? ? ? ? ? ? ? ? if (propertyInfo == null)

? ? ? ? ? ? ? ? {

? ? ? ? ? ? ? ? ? ? throw new Exception($"沒有在‘{typeof(TSource)}’上找到‘{propertyName}’這個Property");

? ? ? ? ? ? ? ? }


? ? ? ? ? ? ? ? // 取得源對象property的值

? ? ? ? ? ? ? ? var propertyValue = propertyInfo.GetValue(source);

? ? ? ? ? ? ? ? // 為ExpandoObject添加field

? ? ? ? ? ? ? ? ((IDictionary<string, object>)dataShapedObject).Add(propertyInfo.Name, propertyValue);

? ? ? ? ? ? }


? ? ? ? ? ? return dataShapedObject;

? ? ? ? }

? ? }

}

注意: 這里的邏輯是如果沒有選擇需要的屬性的話, 那么就返回所有合適的屬性.

然后在CustomerController里面:

首先創建為對象添加link的方法:

private IEnumerable<LinkViewModel> CreateLinksForCustomer(int id, string fields = null)

? ? ? ? {

? ? ? ? ? ? var links = new List<LinkViewModel>();

? ? ? ? ? ? if (string.IsNullOrWhiteSpace(fields))

? ? ? ? ? ? {

? ? ? ? ? ? ? ? links.Add(

? ? ? ? ? ? ? ? ? ? new LinkViewModel(_urlHelper.Link("GetCustomer", new { id = id }),

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

? ? ? ? ? ? ? ? ? ? "GET"));

? ? ? ? ? ? }

? ? ? ? ? ? else

? ? ? ? ? ? {

? ? ? ? ? ? ? ? links.Add(

? ? ? ? ? ? ? ? ? ? new LinkViewModel(_urlHelper.Link("GetCustomer", new { id = id, fields = fields }),

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

? ? ? ? ? ? ? ? ? ? "GET"));

? ? ? ? ? ? }


? ? ? ? ? ? links.Add(

? ? ? ? ? ? ? ? new LinkViewModel(_urlHelper.Link("DeleteCustomer", new { id = id? ? ? ? ? ? ? }),

? ? ? ? ? ? ? ? "delete_customer",

? ? ? ? ? ? ? ? "DELETE"));


? ? ? ? ? ? links.Add(

? ? ? ? ? ? ? ? new LinkViewModel(_urlHelper.Link("CreateCustomer", new { id = id }),

? ? ? ? ? ? ? ? "create_customer",

? ? ? ? ? ? ? ? "POST"));


? ? ? ? ? ? return links;

? ? ? ? }


針對返回一個對象, 添加了本身的連接, 添加的連接 以及 刪除的連接.

然后修改Get和Post的Action:

[HttpGet]

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

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

? ? ? ? {

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

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

? ? ? ? ? ? {

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

? ? ? ? ? ? }

? ? ? ? ? ? var customerVm = Mapper.Map<CustomerViewModel>(item);

? ? ? ? ? ? var links = CreateLinksForCustomer(id, fields);

? ? ? ? ? ? var dynamicObject = customerVm.ToDynamic(fields) as IDictionary<string, object>;

? ? ? ? ? ? dynamicObject.Add("links", links);

? ? ? ? ? ? return Ok(dynamicObject);

? ? ? ? }


? ? ? ? [HttpPost(Name = "CreateCustomer")]

? ? ? ? public async Task<IActionResult> Post([FromBody] CustomerViewModel customerVm)

? ? ? ? {

? ? ? ? ? ? if (customerVm == null)

? ? ? ? ? ? {

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

? ? ? ? ? ? }


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

? ? ? ? ? ? {

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

? ? ? ? ? ? }


? ? ? ? ? ? var newItem = Mapper.Map<Customer>(customerVm);

? ? ? ? ? ? _customerRepository.Add(newItem);

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

? ? ? ? ? ? {

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

? ? ? ? ? ? }


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


? ? ? ? ? ? var links = CreateLinksForCustomer(vm.Id);

? ? ? ? ? ? var dynamicObject = vm.ToDynamic() as IDictionary<string, object>;

? ? ? ? ? ? dynamicObject.Add("links", links);


? ? ? ? ? ? return CreatedAtRoute("GetCustomer", new { id = dynamicObject["Id"] }, dynamicObject);

? ? ? ? }

紅色部分是相關的代碼. 創建links之后把vm對象按照需要的屬性轉化成dynamic對象. 然后往這個dynamic對象里面添加links屬性. 最后返回該對象.

下面測試一下.

POST:

結果:

由于POST方法里面沒有選擇任何fields, 所以返回所有的屬性.

下面試一下GET:

?

再試一下GET, 選擇幾個fields:

OK, 效果都如預期.

但是有一個問題, 因為返回的json的Pascal case的(只有dynamic對象返回的是Pascal case, 其他ViewModel現在返回的都是camel case的), 而camel case才是更好的選擇 .

所以在Startup里面可以這樣設置:

services.AddMvc(options =>

? ? ? ? ? ? {

? ? ? ? ? ? ? ? options.ReturnHttpNotAcceptable = true;

? ? ? ? ? ? ? ? // the default formatter is the first one in the list.

? ? ? ? ? ? ? ? options.OutputFormatters.Remove(new XmlDataContractSerializerOutputFormatter());


? ? ? ? ? ? ? ? // set authorization on all controllers or routes

? ? ? ? ? ? ? ? var policy = new AuthorizationPolicyBuilder()

? ? ? ? ? ? ? ? ? ? .RequireAuthenticatedUser()

? ? ? ? ? ? ? ? ? ? .Build();

? ? ? ? ? ? ? ? options.Filters.Add(new AuthorizeFilter(policy));

? ? ? ? ? ? })

? ? ? ? ? ? .AddJsonOptions(options =>

? ? ? ? ? ? {

? ? ? ? ? ? ? ? options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();

? ? ? ? ? ? })

? ? ? ? ? ? .AddFluetValidations();

然后再試試:

OK.

?

返回集合

?首先編寫創建links的方法:

private IEnumerable<LinkViewModel> CreateLinksForCustomers(string fields = null)

? ? ? ? {

? ? ? ? ? ? var links = new List<LinkViewModel>();

? ? ? ? ? ? if (string.IsNullOrWhiteSpace(fields))

? ? ? ? ? ? {

? ? ? ? ? ? ? ? links.Add(

? ? ? ? ? ? ? ? ? ?new LinkViewModel(_urlHelper.Link("GetAllCustomers", new { fields = fields }),

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

? ? ? ? ? ? ? ? ? ?"GET"));

? ? ? ? ? ? }

? ? ? ? ? ? else

? ? ? ? ? ? {

? ? ? ? ? ? ? ? links.Add(

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

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

? ? ? ? ? ? ? ? ? ?"GET"));

? ? ? ? ? ? }

? ? ? ? ? ? return links;

? ? ? ? }

這個很簡單.

然后需要針對IEnumerable<T>類型創建把ViewModel轉化成dynamic對象的Extension方法:

using System;

using System.Collections.Generic;

using System.Dynamic;

using System.Reflection;


namespace SalesApi.Shared.Helpers

{

? ? public static class IEnumerableExtensions

? ? {

? ? ? ? public static IEnumerable<ExpandoObject> ToDynamicIEnumerable<TSource>(this IEnumerable<TSource> source, string fields)

? ? ? ? {

? ? ? ? ? ? if (source == null)

? ? ? ? ? ? {

? ? ? ? ? ? ? ? throw new ArgumentNullException("source");

? ? ? ? ? ? }


? ? ? ? ? ? var expandoObjectList = new List<ExpandoObject>();

? ? ? ? ? ? var propertyInfoList = new List<PropertyInfo>();

? ? ? ? ? ? if (string.IsNullOrWhiteSpace(fields))

? ? ? ? ? ? {

? ? ? ? ? ? ? ? var propertyInfos = typeof(TSource).GetProperties(BindingFlags.Public | BindingFlags.Instance);

? ? ? ? ? ? ? ? propertyInfoList.AddRange(propertyInfos);

? ? ? ? ? ? }

? ? ? ? ? ? else

? ? ? ? ? ? {

? ? ? ? ? ? ? ? var fieldsAfterSplit = fields.Split(',');

? ? ? ? ? ? ? ? foreach (var field in fieldsAfterSplit)

? ? ? ? ? ? ? ? {

? ? ? ? ? ? ? ? ? ? var propertyName = field.Trim();

? ? ? ? ? ? ? ? ? ? var propertyInfo = typeof(TSource).GetProperty(propertyName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);

? ? ? ? ? ? ? ? ? ? if (propertyInfo == null)

? ? ? ? ? ? ? ? ? ? {

? ? ? ? ? ? ? ? ? ? ? ? throw new Exception($"Property {propertyName} wasn't found on {typeof(TSource)}");

? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? propertyInfoList.Add(propertyInfo);

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }


? ? ? ? ? ? foreach (TSource sourceObject in source)

? ? ? ? ? ? {

? ? ? ? ? ? ? ? var dataShapedObject = new ExpandoObject();

? ? ? ? ? ? ? ? foreach (var propertyInfo in propertyInfoList)

? ? ? ? ? ? ? ? {

? ? ? ? ? ? ? ? ? ? var propertyValue = propertyInfo.GetValue(sourceObject);

? ? ? ? ? ? ? ? ? ? ((IDictionary<string, object>)dataShapedObject).Add(propertyInfo.Name, propertyValue);

? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? expandoObjectList.Add(dataShapedObject);

? ? ? ? ? ? }


? ? ? ? ? ? return expandoObjectList;

? ? ? ? }

? ? }

}

注意: 反射的開銷很大, 注意性能.

然后修改GetAll方法:

[HttpGet(Name = "GetAllCustomers")]

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

? ? ? ? {

? ? ? ? ? ? var items = await _customerRepository.GetAllAsync();

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

? ? ? ? ? ? var dynamicList = results.ToDynamicIEnumerable(fields);

? ? ? ? ? ? var links = CreateLinksForCustomers(fields);

? ? ? ? ? ? var dynamicListWithLinks = dynamicList.Select(customer =>

? ? ? ? ? ? {

? ? ? ? ? ? ? ? var customerDictionary = customer as IDictionary<string, object>;

? ? ? ? ? ? ? ? var customerLinks = CreateLinksForCustomer(

? ? ? ? ? ? ? ? ? ? (int)customerDictionary["Id"], fields);

? ? ? ? ? ? ? ? customerDictionary.Add("links", customerLinks);

? ? ? ? ? ? ? ? return customerDictionary;

? ? ? ? ? ? });

? ? ? ? ? ? var resultWithLink = new {

? ? ? ? ? ? ? ? Value = dynamicListWithLinks,

? ? ? ? ? ? ? ? Links = links

? ? ? ? ? ? };

? ? ? ? ? ? return Ok(resultWithLink);

? ? ? ? }

紅色部分是相關代碼.

測試一下:

不選擇屬性:

選擇部分屬性:

OK.?

HATEOAS這部分就寫到這.

其實 翻頁的邏輯很適合使用HATEOAS結構. 有空我再寫一個翻頁的吧.

原文地址?https://www.cnblogs.com/cgzl/p/8745631.html


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

總結

以上是生活随笔為你收集整理的使用 dynamic 类型让 ASP.NET Core 实现 HATEOAS 结构的 RESTful API的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。