【.Net动态Web API】动态方法RESTful风格

🚀前言
本文是《.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风格就介绍到这边啦!

这个系列预计一天一篇文章,想要学习的可以关注起来!

文章会持续更新,大家有想要了解的功能点或者话题,可以在评论区告诉我!

  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

编程乐趣

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值