net core RESTful Api笔记⑧

数据的塑形:

允许api的消费者选择返回的资源的字段

/api.companies?fields=id,name

针对资源的字段,而不是其他更低层次的对象的字段。

创建自定义方法IEnumerableExtensions:针对集合的,这里使用反射+扩展方法,就可以对这种问题处理了

using Microsoft.AspNetCore.Mvc.Filters;
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;

namespace Rountine.API.Helpers
{
    public  static class IEnumerableExtensions
    {
        public static IEnumerable<ExpandoObject> ShapeData<TSource>(this IEnumerable<TSource> sources, string fields) 
        {
            if (sources == null) {
                throw new ArgumentException(nameof(sources));
            }
            var expandoObject = new List<ExpandoObject>(sources.Count());

            var propertyInfoList = new List<PropertyInfo>();
            if (string.IsNullOrEmpty(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}没找到:{typeof(TSource)}");
                    }
                    propertyInfoList.Add(propertyInfo);
                }
            }
            foreach (TSource obj in sources) 
            {
                var shapedObj = new ExpandoObject();
                foreach (var propertyInfo in propertyInfoList) 
                {
                    var propertyValue = propertyInfo.GetValue(obj);
                    ((IDictionary<string, object>)shapedObj).Add(propertyInfo.Name,propertyValue);
                }
                expandoObject.Add(shapedObj);
            }
            return expandoObject;
        }
    }
}

修改controller:

        [HttpGet(Name =nameof(GetCompanies))]
        public async Task<IActionResult> GetCompanies([FromQuery] CompanyDtoParameters parameters)
        {
            var companies = await _companyRepository.GetCompaniesAsync(parameters);

            var previousPageLink = companies.HasPrevious ? CreateCompaniesResourceUri(parameters, ResourceUriType.PreviousPage) : null;
            var nextPageLink = companies.HasNext ? CreateCompaniesResourceUri(parameters, ResourceUriType.NextPage) : null;

            var paginationMetadate = new
            {
                  totalCount= companies.ToTalCount,
                  pageSize=companies.PageSize,
                  currentPage=companies.CurrentPage,
                  totalPages=companies.ToTalPages,
                  previousPageLink,
                  nextPageLink
            };
            Response.Headers.Add("X-Pagination", JsonSerializer.Serialize(paginationMetadate, new JsonSerializerOptions
            {
                Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
            }) ) ;
            var companyDto = _mapper.Map<IEnumerable<CompanyDto>>(companies);
            
            return Ok(companyDto.ShapeData(parameters.Fields));
        }

postman:

 针对对象ObjectExtensions:

using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;

namespace Rountine.API.Helpers
{
    public static class ObjectExtensions
    {
        public static ExpandoObject shapeData<TSource>(this TSource source, string fields) 
        {
            if (source == null) 
            {
                throw new ArgumentNullException(nameof(source));
            }
            var expandoObject = new ExpandoObject();
            if (string.IsNullOrEmpty(fields))
            {
                var propertyInfos = typeof(TSource).GetProperties(BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
                foreach (var propertyInfo in propertyInfos)
                {
                    var propertyValue = propertyInfo.GetValue(source);
                    ((IDictionary<string, object>)expandoObject).Add(propertyInfo.Name, propertyValue);
                }
            }
            else 
            {
                var fieldsAfterSplit = fields.Split(",");
                foreach (var field in fieldsAfterSplit) 
                {
                    var propertyName = field.Trim();
                    var propertyInfo = typeof(TSource).GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase);
                    if (propertyInfo == null) 
                    {
                        throw new Exception();
                    }
                    var propertyValue = propertyInfo.GetValue(source);
                    ((IDictionary<string, object>)expandoObject).Add(propertyInfo.Name, propertyValue);
                }
            }
            return expandoObject;
        }
    }
}

hatos

Hypermedia as the engine of application state

hateoas是rest风格架构中最复杂的约束,也是构建成熟rest服务核心,它的重要性是打破客户端和服务器之间严格的契约,使得客户端能智能自适应,

而rest本身进化更新跟冗余

有助于进化和自我描述

超媒体提如何驱动

支持hateos两种办法

静态类型方案:需要基类(包含link)和包装类,也就是返回的资源里面都含有link,通过集成同一个基类实现。

动态类型方案:需要使用匿名类型或者ExpandoObject等,对于单个资源可以使用ExpandoObject,而对于集合使用匿名类。

单个资源的hateos支持:

 LinkDto:

using Microsoft.EntityFrameworkCore.Storage.ValueConversion.Internal;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Rountine.API.Models
{
    public class LinkDto
    {
        public string  Href { get; }
        public string Rel { get;  }
        public string Method { get;  }
        public LinkDto(string href,string rel,string method)
        {
            Href = href;
            Rel = rel;
            Method = method;
        }
    }
}

CompainesController:

        [HttpGet("{companyId}", Name = nameof(GetCompany))]
        public async Task<IActionResult> GetCompany(Guid companyId, string field)
        {
            var company = await _companyRepository.GetCompanyAsync(companyId);
            if (company == null)
            {
                return NotFound();
            }
            var companyDto = _mapper.Map<CompanyDto>(company);

            var links = CreatLinkForCompany(companyId, field);
            var linkedResoruce = companyDto.shapeData(field) as IDictionary<string, object>;
            linkedResoruce.Add("links",links);
            return Ok(linkedResoruce);
        }
     private IEnumerable<LinkDto> CreatLinkForCompany(Guid companyId, String field)
        {
            var links = new List<LinkDto>();

            if (string.IsNullOrEmpty(field))
            {
                links.Add(new LinkDto(Url.Link(nameof(GetCompany), new { companyId }), "self", "GET"));
            }
            else 
            {
                links.Add(new LinkDto(Url.Link(nameof(GetCompany), new { companyId, field }), "self", "GET"));
            }
            
            return links;
        }

postman:

 集合资源的hateos支持:

    [HttpGet(Name = nameof(GetCompanies))]
        public async Task<IActionResult> GetCompanies([FromQuery] CompanyDtoParameters parameters)
        {
            var companies = await _companyRepository.GetCompaniesAsync(parameters);

            var previousPageLink = companies.HasPrevious ? CreateCompaniesResourceUri(parameters, ResourceUriType.PreviousPage) : null;
            var nextPageLink = companies.HasNext ? CreateCompaniesResourceUri(parameters, ResourceUriType.NextPage) : null;

            var paginationMetadate = new
            {
                totalCount = companies.ToTalCount,
                pageSize = companies.PageSize,
                currentPage = companies.CurrentPage,
                totalPages = companies.ToTalPages,
                previousPageLink,
                nextPageLink
            };
            Response.Headers.Add("X-Pagination", JsonSerializer.Serialize(paginationMetadate, new JsonSerializerOptions
            {
                Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
            }));

            var companyDto = _mapper.Map<IEnumerable<CompanyDto>>(companies);
            var shape=companyDto.ShapeData(parameters.Fields);

            var links = createLinksForCompany(parameters);

            var shapedCompaniesWithLinks = shape.Select(c => {
                var companyDict = c as IDictionary<string, Object>;
                var companyLinks = CreatLinkForCompany((Guid)companyDict["Id"],null);
                companyDict.Add("links",companyLinks);
                return companyDict;
            });

            var linkedCollectionResource = new
            {
                value = shapedCompaniesWithLinks,
                links
            };
            return Ok(linkedCollectionResource);
        }
      private IEnumerable<LinkDto> createLinksForCompany(CompanyDtoParameters parameters) 
        {
            var links = new List<LinkDto>();
            links.Add(new LinkDto(CreateCompaniesResourceUri(parameters, ResourceUriType.Currentpage), "self", "GET"));

            return links;
        }

响应:

原文地址:https://www.cnblogs.com/liuyang95/p/13252073.html