文件内容 AppServiceAutoConvention.cs 10 KB 9960 字符
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ActionConstraints;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.Options;

namespace PasteApeart
{

    /// <summary>
    /// 配置路由得 把AppService注入到Controller中
    /// </summary>
    public class AppServiceAutoConvention : IApplicationModelConvention
    {
        private readonly ApiRouteOption _config;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="config"></param>
        public AppServiceAutoConvention(IOptions<ApiRouteOption> config)
        {
            _config = config.Value;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="application"></param>
        public void Apply(ApplicationModel application)
        {
            var appServices = application.Controllers
                .Where(c => c.ControllerType.Name.EndsWith(_config.ServiceEnd))
                .ToList();

            foreach (var controller in appServices)
            {
                // 获取控制器名称
                var controllerName = controller.ControllerType.Name;
                if (controllerName.EndsWith(_config.ServiceEnd))
                {
                    controllerName = controllerName[..^10];
                }

                controller.ControllerName = controllerName;

                // 检查 Controller 级别是否已有手动配置的路由
                var hasManualControllerRoute = controller.Selectors.Any(s =>
                    s.AttributeRouteModel != null && s.AttributeRouteModel.Template != null);

                // 如果没有手动配置,才设置默认路由
                if (!hasManualControllerRoute)
                {
                    var route = $"{_config.RootApi}/{ToActionName(controllerName)}";
                    controller.Selectors[0].AttributeRouteModel = new AttributeRouteModel(
                        new RouteAttribute(route));
                }

                // 配置每个 Action
                foreach (var action in controller.Actions)
                {
                    // 检查是否已有手动配置的 HTTP 动词
                    var hasManualHttpMethod = action.Selectors.Any(s =>
                        s.ActionConstraints != null && s.ActionConstraints.Any());

                    // 设置 HTTP 动词(如果没手动配置)
                    if (!hasManualHttpMethod)
                    {
                        var httpMethod = GetHttpMethod(action.ActionName);
                        if (httpMethod != null)
                        {
                            action.Selectors[0].ActionConstraints.Add(new HttpMethodActionConstraint(new[] { httpMethod }));
                        }
                    }

                    // ⭐ 关键:为参数设置绑定源
                    foreach (var parameter in action.Parameters)
                    {
                        // 检查是否已手动配置了绑定源
                        if (parameter.BindingInfo != null && parameter.BindingInfo.BindingSource != null)
                        {
                            continue; // 已有配置,跳过
                        }

                        // 判断参数类型
                        var paramType = parameter.ParameterType;

                        // 如果是复杂类型(非简单类型)且不是从路由来的
                        if (!IsSimpleType(paramType) && !IsRouteParameter(action, parameter.ParameterName))
                        {
                            // 设置 [FromBody] 绑定
                            parameter.BindingInfo = new BindingInfo
                            {
                                BindingSource = BindingSource.Body
                            };
                        }
                        // 如果参数名是 id,且是简单类型,设置为 FromRoute
                        else if (parameter.ParameterName.Equals("id", StringComparison.OrdinalIgnoreCase))
                        {
                            parameter.BindingInfo = new BindingInfo
                            {
                                BindingSource = BindingSource.Path
                            };
                        }
                        // 其他简单类型使用 FromQuery
                        else if (IsSimpleType(paramType))
                        {
                            parameter.BindingInfo = new BindingInfo
                            {
                                BindingSource = BindingSource.Query
                            };
                        }
                    }

                    // 检查 Action 是否已有手动配置的路由
                    var hasManualActionRoute = action.Selectors.Any(s =>
                        s.AttributeRouteModel != null && s.AttributeRouteModel.Template != null);

                    if (hasManualActionRoute)
                    {
                        continue;
                    }

                    // 设置 Action 路由
                    var actionName = action.ActionName.EndsWith("Async") ? action.ActionName.Replace("Async", "") : action.ActionName;
                    var routeTemplate = GetActionRouteTemplate(actionName, action.Parameters);
                    if (!string.IsNullOrEmpty(routeTemplate))
                    {
                        action.Selectors[0].AttributeRouteModel = new AttributeRouteModel(new RouteAttribute(routeTemplate));
                    }
                }
            }
        }


        /// <summary>
        /// 辅助方法:判断是否为简单类型
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        private bool IsSimpleType(Type type)
        {
            return type.IsPrimitive ||
                   type == typeof(string) ||
                   type == typeof(decimal) ||
                   type == typeof(DateTime) ||
                   type == typeof(DateTimeOffset) ||
                   type == typeof(Guid) ||
                   type == typeof(int?) ||
                   type == typeof(long?) ||
                   type == typeof(decimal?) ||
                   type == typeof(DateTime?) ||
                   type == typeof(Guid?) ||
                   type.IsEnum;
        }

        /// <summary>
        /// 辅助方法:判断参数是否从路由获取
        /// </summary>
        /// <param name="action"></param>
        /// <param name="parameterName"></param>
        /// <returns></returns>
        private bool IsRouteParameter(ActionModel action, string parameterName)
        {
            // 检查路由模板中是否包含该参数
            foreach (var selector in action.Selectors)
            {
                if (selector.AttributeRouteModel?.Template?.Contains($"{{{parameterName}}}") == true)
                {
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// 辅助方法:获取 HTTP 方法
        /// </summary>
        /// <param name="actionName"></param>
        /// <returns></returns>
        private string GetHttpMethod(string actionName)
        {
            if (actionName.StartsWith("Get"))
                return "GET";
            if (actionName.StartsWith("Create"))
                return "POST";
            if (actionName.StartsWith("Update"))
                return "PUT";
            if (actionName.StartsWith("Delete"))
                return "DELETE";
            return null;
        }

        /// <summary>
        /// 辅助方法:获取 Action 路由模板
        /// </summary>
        /// <param name="actionName"></param>
        /// <param name="parameters"></param>
        /// <returns></returns>
        private string GetActionRouteTemplate(string actionName, IList<ParameterModel> parameters)
        {
            if (actionName == "Get" && parameters.Any(p => p.ParameterName == "id"))
            {
                return "{id}";
            }

            if (actionName != "Get" && actionName != "Create" &&
                actionName != "Update" && actionName != "Delete")
            {
                var actionRoute = ToActionName(actionName);
                if (parameters.Any(p => p.ParameterName == "id"))
                {
                    actionRoute = $"{{id}}/{actionRoute}";
                }
                return actionRoute;
            }

            return null;
        }


        /// <summary>
        /// 
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        private string ToActionName(string input)
        {
            if (_config.RouteModel == "CamelCase")
            {
                return ToCamelCase(input);
            }
            else
            {
                return ToKebabCase(input);
            }
        }


        /// <summary>
        /// 
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        private string ToKebabCase(string input)
        {
            if (string.IsNullOrEmpty(input)) return "";
            // 去掉常见前缀
            //input = input.Replace("Get", "").Replace("Create", "").Replace("Update", "").Replace("Delete", "");
            if (input.StartsWith("Create"))
            {
                input = input.Replace("Create", "");
            }
            return string.Concat(input.Select((c, i) =>
                i > 0 && char.IsUpper(c) ? "-" + c.ToString() : c.ToString())).ToLower();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        private string ToCamelCase(string input)
        {
            if (string.IsNullOrEmpty(input)) return "";

            //if (input == "UpdateItem") { return "updateItem"; }

            if (input.StartsWith("Create"))
            {
                input = input.Replace("Create", "");
            }

            // 去掉常见前缀
            //input = input.Replace("Get", "").Replace("Create", "").Replace("Update", "").Replace("Delete", "");

            if (string.IsNullOrEmpty(input)) return "";

            // 首字母小写,其余保持不变
            return char.ToLowerInvariant(input[0]) + input.Substring(1);
        }

    }

}
​ ​