我们已经准备好了,你呢?

2021我们与您携手共赢,为您的企业形象保驾护航!

前言:

远距组织工作早已两个月了,前段时间也称得上较为闲,每晚中午下班打个卡,加速修修当日要做的组织工作后就欢乐北窝去了。以后在用 ABP 架构(旧版本)的时候就觉得数据服务层写出来吗爽,为什么同时实现了个 IApplicationService 的空USB就可以变为 Web API,可是的是以后一直无暇去研究这几块的基本原理及其同时实现,Seiches里也找不出有关同时实现基本原理的该文(旧版本 ABP 的倒是有,但是 asp.net core 难以参照)。前段时间闲出来,就看了呵呵 abp vnext 的源代码,因此也参照了呵呵KroneMaster 如是说的 Panda.DynamicWebApi。我自己也单纯同时实现了两遍静态 Web API,不由得感慨 asp.net core 结构设计之绝妙。

abp vnext:https://abp.io

Panda.DynamicWebApi:https://github.com/pdafx/Panda.DynamicWebApi

这里先非常感谢这两个库的有关相关人员,没有他们的组织工作,责任编辑也出现没法。另外在此新闻稿,责任编辑以图探求其同时实现基本原理并同时实现两个固定式版,若耶莱贝切勿用作生产环境。

节录:

具体来说先建立他们的软件系统如下表所示:

即使静态 Web API 这一机能是与业务毫无关系的,而且为了F83E43Se,他们应该把这一机能的同时实现写到两个原则上的C#之中。左图中 Demo 工程项目是 asp.net core 3.1 版的 Web API 工程项目,用作模拟他们的固定式静态 Web API,而 SimpleDynamicWebAPI 的 .net standard 2.0 工程项目则是他们的固定式静态 Web API 工程项目。

要同时实现静态 Web API,具体来说要做的第二件事情就是要有两个准则,来认定两个类呢静态 Web API。在 abp vnext 之中,主要提供三种形式,两个是同时实现 IRemoteService USB(前述合作开发过程中一般都是同时实现 IApplicationService USB),另一种形式记号 RemoteServiceAttribute。而在 Panda.DynamicWebApi 中,则是同时实现 IDynamicWebApi USB因此记号 DynamicWebApi。即使责任编辑是要同时实现固定式版,因此只选空USB形式。在 SimpleDynamicWebAPI 工程项目中建立如下表所示空USB:

namespace SimpleDynamicWebAPI { public interface IApplicationService { } }

接下去,他们有了 IApplicationService USB,他们也晓得同时实现了那个USB的类是要成为静态 Web API 的,但那个是他们所晓得的准则,asp.net core 架构它是不晓得的,他们需要把那个准则告诉它。

这几块 abp vnext 有点儿繁杂,他们参照 Panda.DynamicWebAPI 的同时实现:

https://github.com/pdafx/Panda.DynamicWebApi/blob/master/src/Panda.DynamicWebApi/DynamicWebApiServiceExtensions.cs#L46

https://github.com/pdafx/Panda.DynamicWebApi/blob/master/src/Panda.DynamicWebApi/DynamicWebApiControllerFeatureProvider.cs

下面图中

DynamicWebApiControllerFeatureProvider 的 IsController 方法很明显了。查阅 msdn:

粗俗点翻译过来就是判断两个类呢控制器。

接下去开始依样画葫芦。具体来说一点 ControllerFeatureProvider 类是属于 asp.net core 的,理论上是位于

Microsoft.AspNetCore.Mvc.Core 那个 nuget 包的,但是那个包的 3.x 版并没有发布在 nuget 上。如果他们的 SimpleDynamicWebAPI 引用 2.x 版的,而 Demo 工程项目又是 3.x 版的,则很可能会引起冲突。保险起见,他们 修改 SimpleDynamicWebAPI 为两个 asp.net core 的C#。反正那个库本来也不可能会被其它类型诸如 WPF 的工程项目引用。

修改

SimpleDynamicWebAPI.csproj 如下表所示: <Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> <TargetFramework>netcoreapp3.1</TargetFramework> <OutputType>Library</OutputType> </PropertyGroup> </Project>

接下去建立

ApplicationServiceControllerFeatureProvider 类,并修改代码如下表所示:
using Microsoft.AspNetCore.Mvc.Controllers; using System.Reflection; namespace SimpleDynamicWebAPI { public class ApplicationServiceControllerFeatureProvider : ControllerFeatureProvider { protected override bool IsController(TypeInfo typeInfo) { if (typeof(IApplicationService).IsAssignableFrom(typeInfo)) { if (!typeInfo.IsInterface && !typeInfo.IsAbstract && !typeInfo.IsGenericType && typeInfo.IsPublic) { return true; } } return false; } } }

具体来说先要判断呢同时实现了 IApplicationService USB,那个是他们一开始所定下的准则。

接下去,1、如果两个USB即使它同时实现了 IApplicationService,但它仍然不能是两个控制器,那是即使USB是难以实例化的;2、抽象类同理,也是即使难以实例化;3、泛型类也不允许,即使需要确切的类型才能实例化;4、public 代表着公开,可被外界访问,如果两个类不是 public 的,那么就不应该成为两个静态 Web API 控制器。

接下去就是要把那个

ApplicationServiceControllerFeatureProvider 加入到 asp.net core 架构中。

建立

SimpleDynamicWebApiExtensions 扩展类,修改代码如下表所示:
using Microsoft.Extensions.DependencyInjection; using System; namespace SimpleDynamicWebAPI { public static class SimpleDynamicWebApiExtensions { public static IMvcBuilder AddDynamicWebApi(this IMvcBuilder builder) { if (builder == null) { throw new ArgumentNullException(nameof(builder)); } builder.ConfigureApplicationPartManager(applicationPartManager => { applicationPartManager.FeatureProviders.Add(new ApplicationServiceControllerFeatureProvider()); }); return builder; } public static IMvcCoreBuilder AddDynamicWebApi(this IMvcCoreBuilder builder) { if (builder == null) { throw new ArgumentNullException(nameof(builder)); } builder.ConfigureApplicationPartManager(applicationPartManager => { applicationPartManager.FeatureProviders.Add(new ApplicationServiceControllerFeatureProvider()); }); return builder; } } }

即使

ConfigureApplicationPartManager 扩展方法分别在 IMvcBuilder 和 IMvcCoreBuilder 上都有,所以他们也只能写两遍。当然参照 abp vnext 或 Panda.DynamicWebApi 从 services 中获取 ApplicationPartManager 对象实例也是可行的。

接下去回到 Demo 工程项目,在 AddControllers 后面加上 AddDynamicWebApi:

public void ConfigureServices(IServiceCollection services) { services.AddControllers().AddDynamicWebApi(); }

现在他们早已完成第一步了,同时实现了 IApplicationService USB的类将被视作控制器处理。但仅仅这样并不足够,假设有多个类同时同时实现 IApplicationService USB,那应该如何映射呢,如果没错的话,那个时候你应该会想到是——路由。他们还需要做的组织工作就是把这些控制器与路由配置出来。

abp vnext 这块为了在配置过程中获取 services 而延迟加载导致包了一层,有点儿繁杂。这里参照 Panda.DynamicWebApi

https://github.com/pdafx/Panda.DynamicWebApi/blob/master/src/Panda.DynamicWebApi/DynamicWebApiServiceExtensions.cs#L51

注释告诉了他们这里是配置控制器的路由,非常感谢作者大大。

继续画葫芦,建立

ApplicationServiceConvention 类并同时实现

IApplicationModelConvention USB:
using Microsoft.AspNetCore.Mvc.ApplicationModels; using System; namespace SimpleDynamicWebAPI { public class ApplicationServiceConvention : IApplicationModelConvention { public void Apply(ApplicationModel application) { throw new NotImplementedException(); } } }

Apply 方法的同时实现等下再考虑,先把它注册到 asp.net core 架构,修改

SimpleDynamicWebApiExtensions 扩展类如下表所示:
using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; using System; namespace SimpleDynamicWebAPI { public static class SimpleDynamicWebApiExtensions { public static IMvcBuilder AddDynamicWebApi(this IMvcBuilder builder) { if (builder == null) { throw new ArgumentNullException(nameof(builder)); } builder.ConfigureApplicationPartManager(applicationPartManager => { applicationPartManager.FeatureProviders.Add(new ApplicationServiceControllerFeatureProvider()); }); builder.Services.Configure<MvcOptions>(options => { options.Conventions.Add(new ApplicationServiceConvention()); }); return builder; } public static IMvcCoreBuilder AddDynamicWebApi(this IMvcCoreBuilder builder) { if (builder == null) { throw new ArgumentNullException(nameof(builder)); } builder.ConfigureApplicationPartManager(applicationPartManager => { applicationPartManager.FeatureProviders.Add(new ApplicationServiceControllerFeatureProvider()); }); builder.Services.Configure<MvcOptions>(options => { options.Conventions.Add(new ApplicationServiceConvention()); }); return builder; } } }

对服务容器中的 MvcOptions 进行配置,添加上

ApplicationServiceConvention。ok,接下去回到考虑 Apply 方法同时实现的问题了。

这里参照 abp vnext:

https://github.com/abpframework/abp/blob/master/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpServiceConvention.cs#L33

左图中的 ApplyForControllers 方法的方法体关键部分很好懂,foreach 遍历了所有的控制器,如果控制器同时实现了 IRemoteService USB或者记号了 RemoteServiceAttribute,则调用 ConfigureRemoteService 进一步处理。即使他们的固定式版是只有USB,else 部分的他们就不需要了。

修改

ApplicationServiceConvention 代码如下表所示:
using Microsoft.AspNetCore.Mvc.ApplicationModels; using System; namespace SimpleDynamicWebAPI { public class ApplicationServiceConvention : IApplicationModelConvention { public void Apply(ApplicationModel application) { foreach (var controller in application.Controllers) { if (typeof(IApplicationService).IsAssignableFrom(controller.ControllerType)) { ConfigureApplicationService(controller); } } } private void ConfigureApplicationService(ControllerModel controller) { throw new NotImplementedException(); } } }

那 abp vnext 的 ConfigureRemoteService 方法中又干了什么呢?跳转到 ConfigureRemoteService 的同时实现:

做了三件事情。

1、ConfigureApiExplorer。ApiExplorer,单纯点说就是 API 是否可被发现。举个栗子,加入你写了两个 Web API,工程项目又配置了 swagger,而且你又想 swagger 不显示那个 Web API 的话,那么可以在 Action 上加上:

[ApiExplorerSettings(IgnoreApi = true)]

具体这里就不说了,大家可以自行 google。

2、ConfigureSelector。Selector,选择器,可能不太好理解。但是第三个明显是配置参数,那么第二那个只能是配置路由了,那个方法将会是他们的关键。

3、ConfigureParameters。第二点说了,配置参数。

那么继续修改他们的

ApplicationServiceConvention 类因此同时实现他们的 ConfigureApiExplorer:
using Microsoft.AspNetCore.Mvc.ApplicationModels; using System; namespace SimpleDynamicWebAPI { public class ApplicationServiceConvention : IApplicationModelConvention { public void Apply(ApplicationModel application) { foreach (var controller in application.Controllers) { if (typeof(IApplicationService).IsAssignableFrom(controller.ControllerType)) { ConfigureApplicationService(controller); } } } private void ConfigureApplicationService(ControllerModel controller) { ConfigureApiExplorer(controller); ConfigureSelector(controller); ConfigureParameters(controller); } private 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; } } } private void ConfigureSelector(ControllerModel controller) { throw new NotImplementedException(); } private void ConfigureParameters(ControllerModel controller) { throw new NotImplementedException(); } } }

ConfigureApiExplorer 这块,IsVisible 只要没有值,就无脑设为 true 好了。

接下去 ConfigureSelector 看 abp vnext 的同时实现:

https://github.com/abpframework/abp/blob/master/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpServiceConvention.cs#L170

具体来说第一行 RemoveEmptySelectors 这是两个关键点。虽然他们的静态 Web API 控制器一开始并没有配置路由,但前述上 asp.net core 架构会为此生成一些空白信息。abp vnext 在这里就抹除掉了这些空白信息。而 Panda.DynamicWebApi 虽然没有这样干,但是后面的判断逻辑就相对繁杂了一些(大大别打我)。

实抄现袭他们的 RemoveEmptySelectors:

private 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); } } }

使用倒序删除小技巧,就不需要担心下标越界的问题了。

if 第一行明显可以看出判断路由信息是否存在,第二行判断的 Action 的约束,而约束则是指 HttpGet、HttpPost 这种约束,第三行判断了端点元数据信息,例如记号了什么 Attribute 之类的。假如这些都没有,那么这条 selector 就可以断定为空白信息了。

接下去回到 abp vnext 代码截图的 181 行:

https://github.com/abpframework/abp/blob/master/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpServiceConvention.cs#L181

假如移除过空白信息后仍然有路由的话,则后续不进行处理。

接下去的 foreach 就开始处理 Action 了。先完善他们的代码,再开始处理 Action 的路由:

using Microsoft.AspNetCore.Mvc.ApplicationModels; using System; using System.Collections.Generic; using System.Linq; namespace SimpleDynamicWebAPI { public class ApplicationServiceConvention : IApplicationModelConvention { public void Apply(ApplicationModel application) { foreach (var controller in application.Controllers) { if (typeof(IApplicationService).IsAssignableFrom(controller.ControllerType)) { ConfigureApplicationService(controller); } } } private void ConfigureApplicationService(ControllerModel controller) { ConfigureApiExplorer(controller); ConfigureSelector(controller); ConfigureParameters(controller); } private 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; } } } private void ConfigureSelector(ControllerModel controller) { RemoveEmptySelectors(controller.Selectors); if (controller.Selectors.Any(temp => temp.AttributeRouteModel != null)) { return; } foreach (var action in controller.Actions) { ConfigureSelector(action); } } private void ConfigureSelector(ActionModel action) { throw new NotImplementedException(); } private void ConfigureParameters(ControllerModel controller) { throw new NotImplementedException(); } private 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); } } } } }

开始处理 Action 的路由,参照 abp vnext 的 194 行到 212 行:

第一行仍然是移除空白信息。

关键在最后的判断,假如没有 selector 的话,加上就是了。但是如果早已有了呢?那就修改呗。举个栗子,假如他们同时实现 IApplicationService USB的类的两个方法记号了 HttpGet,那么那个 Action 是有约束的,但是它却是没有路由的。这几行无论是 abp vnext 还是 Panda.DynamicWebApi 都是一样的。

初步同时实现添加 selector 方法,这里我叫它

AddApplicationServiceSelector:
private void AddApplicationServiceSelector(ActionModel action) { var selector = new SelectorModel(); selector.AttributeRouteModel = new AttributeRouteModel(new RouteAttribute(CalculateRouteTemplate(action))); selector.ActionConstraints.Add(new HttpMethodActionConstraint(new[] { GetHttpMethod(action) })); action.Selectors.Add(selector); } private string CalculateRouteTemplate(ActionModel action) { throw new NotImplementedException(); } private string GetHttpMethod(ActionModel action) { throw new NotImplementedException(); }

接下去他们需要添加路由因此配置约束。

要计算路由,他们先举个栗子(嗯,第三颗栗子了)。假设他们有两个叫 BookController 的 API 控制器,有两个叫 Save 的 Action,那么它的路由一般就是:

api/books/{id}/save

也就是说,一般 API 控制器的路由如下表所示:

api/[controller]s(/{id})?(/[action])?

那么他们大概能写出如下表所示代码:

private string CalculateRouteTemplate(ActionModel action) { var routeTemplate = new StringBuilder(); routeTemplate.Append("api"); // 控制器名称部分 var controllerName = action.Controller.ControllerName; if (controllerName.EndsWith("ApplicationService")) { controllerName = controllerName.Substring(0, controllerName.Length - "ApplicationService".Length); } else if (controllerName.EndsWith("AppService")) { controllerName = controllerName.Substring(0, controllerName.Length - "AppService".Length); } controllerName += "s"; routeTemplate.Append($"/{controllerName}"); // id 部分 if (action.Parameters.Any(temp => temp.ParameterName == "id")) { routeTemplate.Append("/{id}"); } // Action 名称部分 var actionName = action.ActionName; if (actionName.EndsWith("Async")) { actionName = actionName.Substring(0, actionName.Length - "Async".Length); } var trimPrefixes = new[] { "GetAll","GetList","Get", "Post","Create","Add","Insert", "Put","Update", "Delete","Remove", "Patch" }; foreach (var trimPrefix in trimPrefixes) { if (actionName.StartsWith(trimPrefix)) { actionName = actionName.Substring(trimPrefix.Length); break; } } if (!string.IsNullOrEmpty(actionName)) { routeTemplate.Append($"/{actionName}"); } return routeTemplate.ToString(); }

以 api 开头。

控制器部分,如果名字结尾是 ApplicationService 或者 AppService,那就裁掉。因此变为复数。即使这里是固定式版,直接加 s 了是。前述建议使用 Inflector 等之类的库。不然 bus 这种词直接加 s 就太奇怪了。

id 部分没啥好说的。

最后是 Action 部分,假如是 Async 结尾的,裁掉。接下去看开头呢以 Get、Post、Create 等等这些开头,是的话也裁掉,注意要先判断 GetAll 和 GetList 然后再判断 Get。即使最后裁掉后有可能是空字符串,所以还需要判断呵呵再确定是否添加到路由中。

通过 Action 部分的计算,以后他们剩下的 GetHttpMethod 方法也很好写了:

private string GetHttpMethod(ActionModel action) { var actionName = action.ActionName; if (actionName.StartsWith("Get")) { return "GET"; } if (actionName.StartsWith("Put") || actionName.StartsWith("Update")) { return "PUT"; } if (actionName.StartsWith("Delete") || actionName.StartsWith("Remove")) { return "DELETE"; } if (actionName.StartsWith("Patch")) { return "PATCH"; } return "POST"; }

根据 Action 名开头返回 Http 方法就是了,如果什么都匹配不上就假定 POST。

添加 Selector 总算写完了,修改 Selector 还难么?同时实现他们自己的 NormalizeSelectorRoutes 方法:

private void NormalizeSelectorRoutes(ActionModel action) { foreach (var selector in action.Selectors) { if (selector.AttributeRouteModel == null) { selector.AttributeRouteModel = new AttributeRouteModel(new RouteAttribute(CalculateRouteTemplate(action))); } if (selector.ActionConstraints.OfType<HttpMethodActionConstraint>().FirstOrDefault()?.HttpMethods?.FirstOrDefault() == null) { selector.ActionConstraints.Add(new HttpMethodActionConstraint(new[] { GetHttpMethod(action) })); } } }

没有路由就给它补路由,没有约束就给它补约束。

现在他们的

ApplicationServiceConvention 的代码应该如下表所示:
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ActionConstraints; using Microsoft.AspNetCore.Mvc.ApplicationModels; using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace SimpleDynamicWebAPI { public class ApplicationServiceConvention : IApplicationModelConvention { public void Apply(ApplicationModel application) { foreach (var controller in application.Controllers) { if (typeof(IApplicationService).IsAssignableFrom(controller.ControllerType)) { ConfigureApplicationService(controller); } } } private void ConfigureApplicationService(ControllerModel controller) { ConfigureApiExplorer(controller); ConfigureSelector(controller); ConfigureParameters(controller); } private 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; } } } private void ConfigureSelector(ControllerModel controller) { RemoveEmptySelectors(controller.Selectors); if (controller.Selectors.Any(temp => temp.AttributeRouteModel != null)) { return; } foreach (var action in controller.Actions) { ConfigureSelector(action); } } private void ConfigureSelector(ActionModel action) { RemoveEmptySelectors(action.Selectors); if (action.Selectors.Count <= 0) { AddApplicationServiceSelector(action); } else { NormalizeSelectorRoutes(action); } } private void ConfigureParameters(ControllerModel controller) { throw new NotImplementedException(); } private void NormalizeSelectorRoutes(ActionModel action) { foreach (var selector in action.Selectors) { if (selector.AttributeRouteModel == null) { selector.AttributeRouteModel = new AttributeRouteModel(new RouteAttribute(CalculateRouteTemplate(action))); } if (selector.ActionConstraints.OfType<HttpMethodActionConstraint>().FirstOrDefault()?.HttpMethods?.FirstOrDefault() == null) { selector.ActionConstraints.Add(new HttpMethodActionConstraint(new[] { GetHttpMethod(action) })); } } } private void AddApplicationServiceSelector(ActionModel action) { var selector = new SelectorModel(); selector.AttributeRouteModel = new AttributeRouteModel(new RouteAttribute(CalculateRouteTemplate(action))); selector.ActionConstraints.Add(new HttpMethodActionConstraint(new[] { GetHttpMethod(action) })); action.Selectors.Add(selector); } private string CalculateRouteTemplate(ActionModel action) { var routeTemplate = new StringBuilder(); routeTemplate.Append("api"); // 控制器名称部分 var controllerName = action.Controller.ControllerName; if (controllerName.EndsWith("ApplicationService")) { controllerName = controllerName.Substring(0, controllerName.Length - "ApplicationService".Length); } else if (controllerName.EndsWith("AppService")) { controllerName = controllerName.Substring(0, controllerName.Length - "AppService".Length); } controllerName += "s"; routeTemplate.Append($"/{controllerName}"); // id 部分 if (action.Parameters.Any(temp => temp.ParameterName == "id")) { routeTemplate.Append("/{id}"); } // Action 名称部分 var actionName = action.ActionName; if (actionName.EndsWith("Async")) { actionName = actionName.Substring(0, actionName.Length - "Async".Length); } var trimPrefixes = new[] { "GetAll","GetList","Get", "Post","Create","Add","Insert", "Put","Update", "Delete","Remove", "Patch" }; foreach (var trimPrefix in trimPrefixes) { if (actionName.StartsWith(trimPrefix)) { actionName = actionName.Substring(trimPrefix.Length); break; } } if (!string.IsNullOrEmpty(actionName)) { routeTemplate.Append($"/{actionName}"); } return routeTemplate.ToString(); } private string GetHttpMethod(ActionModel action) { var actionName = action.ActionName; if (actionName.StartsWith("Get")) { return "GET"; } if (actionName.StartsWith("Put") || actionName.StartsWith("Update")) { return "PUT"; } if (actionName.StartsWith("Delete") || actionName.StartsWith("Remove")) { return "DELETE"; } if (actionName.StartsWith("Patch")) { return "PATCH"; } return "POST"; } private 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); } } } } }

嗯,他们差不多完成了,就剩最后两个 ConfigureParameters。继续参照 abp vnext(这块 Panda.DynamicWebApi 同时实现也几乎一样了):

https://github.com/abpframework/abp/blob/master/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpServiceConvention.cs#L67

没啥东西,主要是配置控制器的每个 Action 的每个参数的 Binding。

关键就后面两个判断,

TypeHelper.IsPrimitiveExtended 类似于判断呢基础类型。例如 int、long 这些就是基础类型,是不应该加上 FromBody 绑定的,因此 abp vnext 进一步判断像 Nullable<int>、Nullable<long>、DateTime 这些也不应该加 FromBody 绑定。那个阅读 TypeHelper 的源代码还是很好懂的。第二个判断则判断了当前 Http 约束是否能用 FormBody,例如 GET、DELETE 请求是没办法用 FromBody 的。

即使是固定式版,他们可以同时实现如下表所示:

private void ConfigureParameters(ControllerModel controller) { foreach (var action in controller.Actions) { foreach (var parameter in action.Parameters) { if (parameter.BindingInfo != null) { continue; } if (parameter.ParameterType.IsClass && parameter.ParameterType != typeof(string) && parameter.ParameterType != typeof(IFormFile)) { var httpMethods = action.Selectors.SelectMany(temp => temp.ActionConstraints).OfType<HttpMethodActionConstraint>().SelectMany(temp => temp.HttpMethods).ToList(); if (httpMethods.Contains("GET") || httpMethods.Contains("DELETE") || httpMethods.Contains("TRACE") || httpMethods.Contains("HEAD")) { continue; } parameter.BindingInfo = BindingInfo.GetBindingInfo(new[] { new FromBodyAttribute() }); } } } }

当然第两个判断没有 abp vnext 和 Panda.DynamicWebApi 严谨,但 90% 情况足够用了。第二个判断则把 Http 约束通通查出来,如果有 GET、DELETE 等等这些则不能加 FromBody 约束,反之则加上。

模拟:

历经千辛万苦,他们的固定式版静态 Web API 终于完成了。接下去他们可以给 Demo 工程项目添加呵呵测试代码以及配置 swagger 来看呵呵效果。

在 Demo 工程项目中添加测试代码 PersonAppService:

using SimpleDynamicWebAPI; using System.Collections.Generic; using System.Linq; namespace Demo.Application { public class CreateUpdatePersonInput { public string Name { get; set; } } public class PersonDto { public string Name { get; set; } } public class PersonAppService : IApplicationService { public string Create(CreateUpdatePersonInput input) { return $"你造了个名字叫:{input.Name} 的人"; } public string Delete(int id) { return $"你把 Id:{id} 的人干掉了"; } public string Get(int id) { return $"你输入的 Id 是:{id}"; } public List<PersonDto> GetAll() { return "服务器向你扔了一堆人" .ToCharArray() .Select(temp => new PersonDto { Name = temp.ToString() }) .ToList(); } public string Update(int id, CreateUpdatePersonInput input) { return $"你把 Id:{id} 的人的名字改成了 {input.Name}"; } } }

配置 swagger 的该文Seiches里多得是,这里就不贴代码了。

完事后跑出来。

感觉还行。

结语:

他们总算同时实现了两个非常固定式的静态 Web API,也相当于又造了两遍轮子,但在这造轮子的过程中,他们了解到了其同时实现的基本原理,假如以后发现 abp vnext 等架构的静态 Web API 满足没法他们的时候,他们也有一定能力进行修改。最后我再次新闻稿,如果没有把握的话,千万别用作生产环境。abp vnext 这种是经过大量工程项目验证的,即使有 bug,abp vnext 官方也有足够人力去修复。

最后附上 Gayhub 源代码:

https://github.com/h82258652/SimpleDynamicWebAPI

原文来源:博客园

原文地址:

https://www.cnblogs.com/h82258652/p/12454554.html

我们凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设、网站改版、域名注册、主机空间、手机网站建设、网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线: ,我们会详细为你一一解答你心中的疑难。项目经理在线

我们已经准备好了,你呢?

2020我们与您携手共赢,为您的企业形象保驾护航!

在线客服
联系方式

热线电话

上班时间

周一到周五

公司电话

二维码
微信
线