基于.Net6简单实现自动发现api

基于.Net6简单实现自动发现api

因为使用过abp,最近又看了下.Net6的源码,所以就想写个简单的例子

源码地址:https://gitee.com/z51313/aspnet-core-exercise

如何发现自定义Controller

实现自动发现api,实际上就是要构造Controller,我们平时创建的Controller一般都是在web项目中的,它们会被框架默认构造,所以我们需要找到源头。即给ApplicationPartManager添加一个IApplicationFeatureProvider,上代码

//编写WebApplication扩展方法,获取ApplicationPartManager并将自定义的IApplicationFeatureProvider添加进去
public static void AddCustomController(this WebApplication app)
        {
            using (var serviceScope = app.Services.CreateScope())
            {
                var services = serviceScope.ServiceProvider;
                var applicationPartManager = services.GetRequiredService<ApplicationPartManager>();
                if (applicationPartManager == null)
                {
                    throw new Exception("未在容器中找到ApplicationPartManager");
                }
                applicationPartManager.FeatureProviders.Add(new CustomControllerFeatureProvider());
            }
        }
public class CustomControllerFeatureProvider : IApplicationFeatureProvider<ControllerFeature>
    {
        public void PopulateFeature(IEnumerable<ApplicationPart> parts, ControllerFeature feature)
        {
            var assembly = Assembly.GetEntryAssembly();
            var assemblies = assembly.GetReferencedAssemblies();
            assemblies.Append<AssemblyName>(assembly.GetName());
            assemblies.ToList().ForEach(assembly =>
            {
                var assTemp = Assembly.Load(assembly);
                assTemp.GetTypes().ToList().ForEach(type =>
                {
                    //如果实现了IRomteService则认为是一个控制器
                    if (!IsController(type))
                    {
                        return;
                    }
                    feature.Controllers.Add(type.GetTypeInfo());
                });
            });
        }
     }

CustomControllerFeatureProvider中,获取web项目的引用集并将实现了IRomteService接口的类添加为Controller;做完之后发现,这里使用引用集的方式也是存在问题的,就是说GetReferencedAssemblies获取的引用集一定web项目是用到的,不然是获取不到的,可能Abp的模块依赖特性就是解决这个问题的,我没有过于纠结,毕竟目标不是这个

现在我们只要调用AddCustomController方法就可以看到我们想被自动发现的api了

构造Controller

我们的自定义Controller被成功添加后,通过跟框架默认的Controller进行对比,就会发现压根不完整,是不可用的,所以我们现在需要将自定义的Controller构造成可用的Controller,不求完整,这里我纠结了很久,最后还是参考了Abp的源码,直接复制过来用了,上代码

public class CustomApplicationModelConvention : IApplicationModelConvention
    {
        public void Apply(ApplicationModel application)
        {
            foreach (var controller in application.Controllers)
            {
                if (controller.ControllerType.IsAssignableTo(typeof(IRomteService)))
                {
                    ConfigureApiExplorer(controller);
                    ConfigureSelector(controller);
                    ConfigureParameters(controller);
                }
            }
        }
        //直接抄abp代码
        private void ConfigureParameters(ControllerModel controller)
        {
            foreach (var action in controller.Actions)
            {
                foreach (var prm in action.Parameters)
                {
                    if (prm.BindingInfo != null)
                    {
                        continue;
                    }

                    if (!TypeHelper.IsPrimitiveExtended(prm.ParameterInfo.ParameterType, includeEnums: true))
                    {
                        if (CanUseFormBodyBinding(action, prm))
                        {
                            prm.BindingInfo = BindingInfo.GetBindingInfo(new[] { new FromBodyAttribute() });
                        }
                    }
                }
            }
        }
        private bool CanUseFormBodyBinding(ActionModel action, ParameterModel parameter)
        {
            //如果参数名为id则默认为query param
            if (parameter.ParameterName == "id")
            {
                return false;
            }

            foreach (var selector in action.Selectors)
            {
                if (selector.ActionConstraints == null)
                {
                    continue;
                }

                foreach (var actionConstraint in selector.ActionConstraints)
                {
                    var httpMethodActionConstraint = actionConstraint as HttpMethodActionConstraint;
                    if (httpMethodActionConstraint == null)
                    {
                        continue;
                    }

                    if (httpMethodActionConstraint.HttpMethods.All(hm => hm.IsIn("GET", "DELETE", "TRACE", "HEAD")))
                    {
                        return false;
                    }
                }
            }
            return true;
        }

        private void ConfigureSelector(ControllerModel controller)
        {
            //先不使用特性,做个最简单的
            var controllerName = controller.ControllerName;
            foreach (var action in controller.Actions)
            {
                action.Selectors.Clear();
                var reqMethod = GetRequestMethod(action);
                action.Selectors.Add(new SelectorModel
                {
                    AttributeRouteModel = new AttributeRouteModel(new RouteAttribute($"/{controllerName}/{action.ActionName}")),
                    ActionConstraints = { new HttpMethodActionConstraint(new[] { reqMethod }) }
                });
            }
        }

        private string GetRequestMethod(ActionModel action)
        {
            var reqM = action.Attributes.OfType<RequestMethodAttribute>().ToList();
            if (reqM.Count == 0)
            {
                return RequestMethods.GET.ToString();
            }
            return reqM.Last()._method.ToString();
        }

        private void ConfigureApiExplorer(ControllerModel controller)
        {
            controller.ApiExplorer.IsVisible = true;
            foreach (var action in controller.Actions)
            {
                ConfigureApiExplorer(action);
            }
        }

        private void ConfigureApiExplorer(ActionModel action)
        {
            action.ApiExplorer.IsVisible = true;
        }
    }

实现了IApplicationModelConvention的类会被注入到容器,Abp中也是实现了这个接口来实现构造的,看过源码就知道,所有Controller被构造后会被放到一个上下文中,并作为参数传给所有实现了IApplicationModelConvention的类的Apply方法

ConfigureApiExplorer方法只是用来设置api可不可见,一般不管也可以

ConfigureSelector方法用于添加选择器,其实也是在给Controller或者Action添加RouteAttribute,我们就可以从这里进行url的配置,这里只是简单的拼接了一下

ConfigureParameters方法用来配置请求参数,这里直接复制abp源码了,最关键的一步就是参数绑定,即绑定asp.net core系统绑定的参数,没有参数绑定的话,其实也是可用的,能接收的参数就只有url后面的查询参数了,也只适用于GET请求方式,所以参数绑定是必要的

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值