🚀前言
本文是《.Net Core进阶编程课程》教程专栏的导航站(点击链接,跳转到专栏主页,欢迎订阅,持续更新…)
专栏介绍:通过源码实例来讲解Asp.Net Core进阶知识点,让大家完全掌握每一个知识点。
专栏适用于人群:Web后端开发人员
————————————————
本课程内容:
1、了解ABP框架的,RESTful风格规则。
2、实现RESTful风格
3、演示RESTful风格
一、ABP的接口RESTful风格规则
RESTful详细内容可以查看之前课程:课程9:RESTful风格API接口。
RESTful是目前比较流行的API风格,ABP框架就是采用RESTful风格。
以下截图,就是ABP官方文档示例。
我们要实现与ABP一样的风格,工作内容有:
1、去掉方法的前缀:Get、Create、Update、Delete等;
2、自动判断参数名是否有Id,把Id作为Url的一部分。比如:/api/app/book/{id};
3、去掉方法的后缀:Async,异步编程是主流,我们异步方法都会用Async为后缀。
二、实现RESTful风格
2.1 类型帮助类
在项目Electric.AutoWebAPICore,添加文件夹:Util,并添加类:TypeHelper
TypeHelper代码如下,用于判断参数中Id的类型,是否为基础类型。比如:int、double、string、guid等。
namespace Electric.AutoWebAPICore.Util
{
/// <summary>
/// 类型帮助类
/// </summary>
public static class TypeHelper
{
/// <summary>
/// 是否为基础类型
/// </summary>
/// <param name="type"></param>
/// <param name="includeNullables"></param>
/// <param name="includeEnums"></param>
/// <returns></returns>
public static bool IsPrimitiveExtended(Type type, bool includeNullables = true, bool includeEnums = false)
{
if (IsPrimitiveExtendedInternal(type, includeEnums))
{
return true;
}
if (includeNullables && IsNullable(type) && type.GenericTypeArguments.Any())
{
return IsPrimitiveExtendedInternal(type.GenericTypeArguments[0], includeEnums);
}
return false;
}
/// <summary>
/// 是否可空
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public static bool IsNullable(Type type)
{
return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
}
/// <summary>
/// 是否为基础类型
/// </summary>
/// <param name="type"></param>
/// <param name="includeEnums"></param>
/// <returns></returns>
private static bool IsPrimitiveExtendedInternal(Type type, bool includeEnums)
{
if (type.IsPrimitive)
{
return true;
}
if (includeEnums && type.IsEnum)
{
return true;
}
return type == typeof(string) ||
type == typeof(decimal) ||
type == typeof(DateTime) ||
type == typeof(DateTimeOffset) ||
type == typeof(TimeSpan) ||
type == typeof(Guid);
}
}
}
2.2 参数Id
判断参数名是否有Id,把Id作为Url的一部分。比如:/api/app/book/{id}。
在类:AutoApplicationModelConvention的GetRouteTemplate,添加如下代码。
代码说明:
1、如果是Id的类型是基础类似,直接Url模板直接拼接:/{id}。
2、如果是Id的类型是对象类型,获取所有字段名称拼接。
要满足的效果如下:
2.3 格式化参数的名称
1、去掉方法的前缀:Get、Create、Update、Delete等;
2、去掉方法的后缀:Async,异步编程是主流,我们异步方法都会用Async为后缀。
修改AutoApplicationModelConvention的核心代码:
常规前缀定义如下:
格式化方法名:去掉前缀、和尾部的Async。
在类:AutoApplicationModelConvention的GetRouteTemplate,添加如下代码,把方法拼接到Url模板。
AutoApplicationModelConvention完整的代码如下:
using Electric.AutoWebAPICore.Util;
using Microsoft.AspNetCore.Mvc.ActionConstraints;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using System.Reflection;
using System.Text;
namespace Electric.AutoWebAPICore
{
/// <summary>
/// 自动控制器应用模型
/// </summary>
public class AutoApplicationModelConvention : IApplicationModelConvention
{
/// <summary>
/// 控制器移除的后缀
/// </summary>
private readonly string _ControllerRemoveSuffix = "AppService";
/// <summary>
/// 常规前缀
/// </summary>
private Dictionary<string, string[]> ConventionalPrefixes = new Dictionary<string, string[]> {
{"GET", new[] {"GetList", "GetAll", "Get"}},
{"PUT", new[] {"Put", "Update"}},
{"DELETE", new[] {"Delete", "Remove"}},
{"POST", new[] {"Create", "Add", "Insert", "Post"}},
{"PATCH", new[] {"Patch"}},
{"OPTIONS", new[] {"Options"}},
{"TRACE", new[] {"Trace"}},
{"HEAD", new[] {"Head"}},
};
public void Apply(ApplicationModel application)
{
//遍历控制器
foreach (var controller in application.Controllers)
{
var type = controller.ControllerType;
//设置控制器名称
if(controller.ControllerName.EndsWith(_ControllerRemoveSuffix))
{
controller.ControllerName = controller.ControllerName[..^_ControllerRemoveSuffix.Length];
}
//控制器继承于IAutoController
if (typeof(IAutoController).IsAssignableFrom(type))
{
//设置API对外可见
ConfigureApiExplorer(controller);
//设置控制器选择器
ConfigureSelector(controller);
}
}
}
/// <summary>
/// 设置API对外可见
/// </summary>
/// <param name="controller"></param>
private static void ConfigureApiExplorer(ControllerModel controller)
{
if (!controller.ApiExplorer.IsVisible.HasValue)
{
controller.ApiExplorer.IsVisible = true;
}
foreach (var action in controller.Actions)
{
if (!action.ApiExplorer.IsVisible.HasValue)
{
action.ApiExplorer.IsVisible = true;
}
}
}
/// <summary>
/// 设置控制器选择器
/// </summary>
/// <param name="controller"></param>
private void ConfigureSelector(ControllerModel controller)
{
//移除空的选择器
RemoveEmptySelectors(controller.Selectors);
if (controller.Selectors.Any(selector => selector.AttributeRouteModel != null))
{
return;
}
//遍历控制器的方法
foreach (var action in controller.Actions)
{
//设置方法的选择器
ConfigureSelector(action);
}
}
/// <summary>
/// 移除空的选择器
/// </summary>
/// <param name="selectors"></param>
private static void RemoveEmptySelectors(IList<SelectorModel> selectors)
{
for (var i = selectors.Count - 1; i >= 0; i--)
{
var selector = selectors[i];
if (selector.AttributeRouteModel == null &&
(selector.ActionConstraints == null || selector.ActionConstraints.Count <= 0) &&
(selector.EndpointMetadata == null || selector.EndpointMetadata.Count <= 0))
{
selectors.Remove(selector);
}
}
}
/// <summary>
/// 设置方法的选择器
/// </summary>
/// <param name="action"></param>
private void ConfigureSelector(ActionModel action)
{
RemoveEmptySelectors(action.Selectors);
if (action.Selectors.Count <= 0)
{
//添加选择器
AddServiceSelector(action);
}
else
{
//格式化选择器
NormalizeSelectorRoutes(action);
}
}
/// <summary>
/// 为方法添加选择器:路由、Http请求方式
/// </summary>
/// <param name="action"></param>
private void AddServiceSelector(ActionModel action)
{
var httpMothod = GetHttpMethod(action);
var template = new Microsoft.AspNetCore.Mvc.RouteAttribute(GetRouteTemplate(action));
var selector = new SelectorModel
{
AttributeRouteModel = new AttributeRouteModel(template)
};
selector.ActionConstraints.Add(new HttpMethodActionConstraint(new[] { httpMothod }));
action.Selectors.Add(selector);
}
/// <summary>
/// 格式化方法选择器:路由、Http请求方式
/// </summary>
/// <param name="action"></param>
private void NormalizeSelectorRoutes(ActionModel action)
{
foreach (var selector in action.Selectors)
{
var httpMothod = GetHttpMethod(action);
if (selector.AttributeRouteModel == null)
{
var template = new Microsoft.AspNetCore.Mvc.RouteAttribute(GetRouteTemplate(action));
selector.AttributeRouteModel = new AttributeRouteModel(template);
}
if (selector.ActionConstraints.OfType<HttpMethodActionConstraint>().FirstOrDefault()?.HttpMethods?.FirstOrDefault() == null)
{
selector.ActionConstraints.Add(new HttpMethodActionConstraint(new[] { httpMothod }));
}
}
}
/// <summary>
/// 获取路由
/// </summary>
/// <param name="action"></param>
/// <returns></returns>
private string GetRouteTemplate(ActionModel action)
{
//路由
var routeTemplate = new StringBuilder();
// 控制器
var controllerName = action.Controller.ControllerName;
if (controllerName.EndsWith(_ControllerRemoveSuffix))
{
controllerName = controllerName[0..^_ControllerRemoveSuffix.Length];
}
routeTemplate.Append($"/{controllerName}");
//id 路由
var idParameterModel = action.Parameters.FirstOrDefault(p => p.ParameterName == "id");
if (idParameterModel != null)
{
if (TypeHelper.IsPrimitiveExtended(idParameterModel.ParameterType, includeEnums: true))
{
routeTemplate.Append("/{id}");
}
else
{
var properties = idParameterModel
.ParameterType
.GetProperties(BindingFlags.Instance | BindingFlags.Public);
foreach (var property in properties)
{
routeTemplate.Append("/{" + property.Name + "}");
}
}
}
// 方法
var actionName = NormalizeUrlActionName(action);
if (!string.IsNullOrEmpty(actionName))
{
routeTemplate.Append($"/{actionName}");
}
return routeTemplate.ToString();
}
/// <summary>
/// 格式方法名
/// </summary>
/// <param name="action"></param>
/// <returns></returns>
private string NormalizeUrlActionName(ActionModel action)
{
var actionName = action.ActionName;
//移除尾部的Async
if (actionName.EndsWith("Async"))
{
actionName = actionName[..^"Async".Length];
}
//移除前缀
foreach (var item in ConventionalPrefixes)
{
var prefixe = item.Value.FirstOrDefault(x => actionName.StartsWith(x));
if (prefixe != null)
{
return actionName[prefixe.Length..];
}
}
return actionName;
}
/// <summary>
/// 获取请求方式
/// </summary>
/// <param name="action"></param>
/// <returns></returns>
private string GetHttpMethod(ActionModel action)
{
var actionName = action.ActionName;
foreach (var item in ConventionalPrefixes)
{
var prefixe = item.Value.FirstOrDefault(x => actionName.StartsWith(x));
if (prefixe != null)
{
return item.Key;
}
}
return "Post";
}
}
}
三、演示接口
3.1 修改演示应用服务
修改项目Electric.Application中的UserAppService。
在UserAppService新增如下方法,包含:获取、删除、更新、新增等模拟API。
3.2 测试效果
运行项目,API的文档如下。
四、最后
【本课程源码下载链接】加我:xgbbwj,领取!
【.Net动态Web API】动态方法RESTful风格就介绍到这边啦!
这个系列预计一天一篇文章,想要学习的可以关注起来!
文章会持续更新,大家有想要了解的功能点或者话题,可以在评论区告诉我!