zoukankan      html  css  js  c++  java
  • 使用.NET6打造动态API

    ApiLite是基于.NET6直接将Service层生成动态api路由,可以不用添加Controller,支持模块插件化,在项目开发中能够提高工作效率,降低代码量。

    开发环境

    • .NET SDK 6.0.100-rc.2.21505.57
    • VS2022 Preview 7.0

    项目地址

    项目目标

    • 根据Service动态生成api
    • 支持自定义路由模板(通过Route特性定义)
    • 支持模块插件化
    • 支持不同模块,相同Service名称的路由(命名空间需要有3级以上,例如:Com.Mod.XXX)
    • 自动根据方法名称判断请求方式,Get开头的方法名为GET请求,其他为POST请求

    编码约定

    • 模块类库必须包含继承IModule接口的类
    • 需要生成api的Service必须继承IService接口
    • GET请求的方法必须以Get开头

    核心代码

    主要是ApiFeatureProvider和ApiConvention这两个自定义类来动态生成api,ApiFeatureProvider继承ControllerFeatureProvider,覆写IsController方法,判断服务类型是否符合Controller。ApiConvention实现了IApplicationModelConvention接口,实现动态添加Action。下面是主要代码,完整代码请在GitHub上下载。

    static class ServiceExtension
    {
        internal static WebApplicationBuilder AddKApp(this WebApplicationBuilder builder, Action<AppOption>? action = null)
        {
            var option = new AppOption();
            action?.Invoke(option);
            ...
            AddDynamicApi(mvcBuilder, option);//添加动态api
            return builder;
        }
    
        private static void AddDynamicApi(IMvcBuilder builder, AppOption option)
        {
            builder.ConfigureApplicationPartManager(m =>
            {
                m.ApplicationParts.Add(new AssemblyPart(typeof(IService).Assembly));
                foreach (var item in option.Modules)
                {
                    item.Initialize();//初始化模块
                    //将模块添加到ApplicationParts,这样才能发现服务类
                    var assembly = item.GetType().Assembly;
                    m.ApplicationParts.Add(new AssemblyPart(assembly));
                }
                m.FeatureProviders.Add(new ApiFeatureProvider());
            });
    
            builder.Services.Configure<MvcOptions>(o =>
            {
                o.Conventions.Add(new ApiConvention());
            });
        }
    }
    
    //判断服务类型是否为Controller
    class ApiFeatureProvider : ControllerFeatureProvider
    {
        protected override bool IsController(TypeInfo typeInfo)
        {
            if (!typeof(IService).IsAssignableFrom(typeInfo) ||
                !typeInfo.IsPublic ||
                typeInfo.IsAbstract ||
                typeInfo.IsGenericType)
                return false;
    
            return true;
        }
    }
    
    class ApiConvention : IApplicationModelConvention
    {
        public void Apply(ApplicationModel application)
        {
            foreach (var controller in application.Controllers)
            {
                var type = controller.ControllerType;
                if (typeof(IService).IsAssignableFrom(type))
                {
                    ConfigureApiExplorer(controller);
                    ConfigureSelector(controller);
                }
            }
        }
    
        ...
    
        //构造路由模板
        private string GetRouteTemplate(ActionModel action)
        {
            if (action.Attributes != null && action.Attributes.Count > 0)
            {
                foreach (var item in action.Attributes)
                {
                    if (item is RouteAttribute attribute)
                    {
                        return attribute.Path;//返回自定义路由
                    }
                }
            }
    
            var routeTemplate = new StringBuilder();
            //routeTemplate.Append("api");
            var names = action.Controller.ControllerType.Namespace.Split('.');
            if (names.Length > 2)
            {
                //支持不同模块相同类名,添加命名空间模块名作前缀
                routeTemplate.Append(names[^2]);
            }
    
            // Controller
            var controllerName = action.Controller.ControllerName;
            if (controllerName.EndsWith("Service"))
                controllerName = controllerName[0..^7];
    
            routeTemplate.Append($"/{controllerName}");
    
            // Action
            var actionName = action.ActionName;
            if (actionName.EndsWith("Async"))
                actionName = actionName[..^"Async".Length];
    
            if (!string.IsNullOrEmpty(actionName))
                routeTemplate.Append($"/{actionName}");
    
            return routeTemplate.ToString();
        }
    }
    

    使用示例

    KHost.Run(args, o =>
    {
        o.Modules.Add(new TestModule());//添加模块
    });
    
    class TestModule : IModule
    {
        public void Initialize()
        {
        }
    }
    
    public class TestService : IService
    {
        public string GetName(string name)
        {
            return $"Hello {name}";
        }
    
        public string SaveData(string data)
        {
            return $"{DateTime.Now:yyyy-MM-dd HH:mm:ss} {data}";
        }
    
        [Route("api/test")]
        public string GetCustMethod(string id)
        {
            return id;
        }
    }
    
  • 相关阅读:
    第十五篇:在SOUI中消息通讯
    为GDI函数增加透明度处理
    第十四篇:在SOUI中使用定时器
    第十三篇:在SOUI中使用有窗口句柄的子窗口
    第十二篇:SOUI的utilities模块为什么要用DLL编译?
    第十一篇:SOUI系统资源管理
    第十篇:扩展SOUI的控件及绘图对象(ISkinObj)
    第九篇:在SOUI中使用多语言翻译
    第八篇:SOUI中控件事件的响应
    Linked List Cycle
  • 原文地址:https://www.cnblogs.com/known/p/15499542.html
Copyright © 2011-2022 走看看