Asp .Net Core 系列:Asp .Net Core 集成 Panda.DynamicWebApi
简介
Panda.DynamicWebApi
是一个动态生成WebApi的组件,生成的API符合Restful风格,受启发于ABP。它可以根据符合条件的类来生成WebApi,由MVC框架直接调用逻辑,无性能问题,完美兼容Swagger来构建API说明文档,与手动编写Controller相比并无区别。
应用场景:DDD架构中的应用逻辑层,可使用本组件来直接生成WebApi,而无需再用Controller来调用。
Asp .Net Core 集成 Panda.DynamicWebApi
(1)新建一个 ASP.NET Core WebApi(或MVC) 项目
(2)通过Nuget安装组件
Install-Package Panda.DynamicWebApi
(3)创建一个类命名为 AppleAppService
,实现 IDynamicWebApi
接口,并加入特性 [DynamicWebApi]
[DynamicWebApi]
public class AppleAppService: IDynamicWebApi
{
private static readonly Dictionary<int,string> Apples=new Dictionary<int, string>()
{
[1]="Big Apple",
[2]="Small Apple"
};
/// <summary>
/// Get An Apple.
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[HttpGet("{id:int}")]
public string Get(int id)
{
if (Apples.ContainsKey(id))
{
return Apples[id];
}
else
{
return "No Apple!";
}
}
/// <summary>
/// Get All Apple.
/// </summary>
/// <returns></returns>
public IEnumerable<string> Get()
{
return Apples.Values;
}
public void Update(UpdateAppleDto dto)
{
if (Apples.ContainsKey(dto.Id))
{
Apples[dto.Id] =dto.Name;
}
}
/// <summary>
/// Delete Apple
/// </summary>
/// <param name="id">Apple Id</param>
[HttpDelete("{id:int}")]
public void Delete(int id)
{
if (Apples.ContainsKey(id))
{
Apples.Remove(id);
}
}
}
(4)在 Startup 中注册 DynamicWebApi
public void ConfigureServices(IServiceCollection services)
{
// 默认配置
services.AddDynamicWebApi();
// 自定义配置
services.AddDynamicWebApi((options) =>
{
// 指定全局默认的 api 前缀
options.DefaultApiPrefix = "apis";
/**
* 清空API结尾,不删除API结尾;
* 若不清空 CreatUserAsync 将变为 CreateUser
*/
options.RemoveActionPostfixes.Clear();
/**
* 自定义 ActionName 处理函数;
*/
options.GetRestFulActionName = (actionName) => actionName;
/**
* 指定程序集 配置 url 前缀为 apis
* 如: http://localhost:8080/apis/User/CreateUser
*/
options.AddAssemblyOptions(this.GetType().Assembly, apiPreFix: "apis");
/**
* 指定程序集 配置所有的api请求方式都为 POST
*/
options.AddAssemblyOptions(this.GetType().Assembly, httpVerb: "POST");
/**
* 指定程序集 配置 url 前缀为 apis, 且所有请求方式都为POST
* 如: http://localhost:8080/apis/User/CreateUser
*/
options.AddAssemblyOptions(this.GetType().Assembly, apiPreFix: "apis", httpVerb: "POST");
});
}
(5)添加 Swagger
builder.Services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo() { Title = "Panda Dynamic WebApi", Version = "v1" });
// TODO:一定要返回true!
options.DocInclusionPredicate((docName, description) => true);
var baseDirectory = System.AppDomain.CurrentDomain.BaseDirectory;
var xmlFile = System.AppDomain.CurrentDomain.FriendlyName + ".xml";
var xmlPath = Path.Combine(baseDirectory, xmlFile);
options.IncludeXmlComments(xmlPath);
});
配置
所有的配置均在对象 DynamicWebApiOptions
中,说明如下:
属性名 | 是否必须 | 说明 |
---|---|---|
DefaultHttpVerb | 否 | 默认值:POST。默认HTTP动词 |
DefaultAreaName | 否 | 默认值:空。Area 路由名称 |
DefaultApiPrefix | 否 | 默认值:api。API路由前缀 |
RemoveControllerPostfixes | 否 | 默认值:AppService/ApplicationService。类名需要移除的后缀 |
RemoveActionPostfixes | 否 | 默认值:Async。方法名需要移除的后缀 |
FormBodyBindingIgnoredTypes | 否 | 默认值:IFormFile。不通过MVC绑定到参数列表的类型。 |
原理
什么是POCO Controller?
POCO Controller是 ASP.NET Core 中的一个特性,虽然在2015年刚发布的时候就有这个特性了,可是大多数开发者都只是按原有的方式去写,而没有用到这个特性。其实,如果利用这个特性进行稍微封装后,用在SOA架构中Service层的场景中是极其便利的。这篇文章主要就是说我最近在学习使用开源AOP库AspectCore写WebApi动态代理客户端的时候,实现为普通类无添加WebApi服务的过程。
POCO控制器就是ASP.NET Core项目中所有带有Controller后缀的类、或者标记了[Controller]特性的类,虽然没有像模版项目中那样继承自Controller类,也会被识别为控制器,拥有跟普通控制器一样的功能,像下面这段代码中,两个类都会被识别成控制器:
public class PocoController
{
public IActionResult Index()
{
return new ContentResult() { Content = “Hello from POCO controller!” };
}
}
[Controller]
public class Poco
{
public IActionResult Index()
{
return new ContentResult() { Content = “Hello from POCO controller!” };
}
}
POCO控制器原理
其实,在ASP.NET Core中,已经不像旧版本的 ASP.NET WebApi 那样,通过ControllerFactory来创建Controller,多亏于ASP.NET Core一脉相承的IoC框架 Microsoft.Extensions.DependencyInjection,ASP.NET Core中的内部实现变得更优雅。其中POCO控制器的核心原理就在IApplicationFeatureProvider<ControllerFeature>这个接口的实现ControllerFeatureProvider。
通过aspnet/Mvc项目的Github源码仓库中查询得知,Mvc里把Controller、ViewComponent、TagHelper、Views等组件定义为特性(Feature),如ControllerFeature,特性里就存放了应用中被识别为相组件的类型的集合,如如ControllerFeature中就存放了所有Controller类型。IApplicationFeatureProvider<ControllerFeature>这个接口是用来给MVC框架提供控制器类型识别的接口,当把这个接口的实现注册到服务配置中,就能为其中识别的类型提供控制器功能。
ControllerFeatureProvider是这个接口的默认实现,其中有一个方法IsController(TypeInfo typeInfo)的功能就是判断某类型是否为控制器的。而接口方法PopulateFeature(IEnumerable<ApplicationPart> parts,ControllerFeature feature)则为把传入的 “Mvc应用部分(ApplicationPart,大概是指Mvc的作用程序集)”中的类型都一一判断,如果是控制器,那么就加入控制器特性对象中。
ControllerFeatureProvider
ControllerFeatureProvider
是 ASP.NET Core MVC 框架中的一个类,它实现了 IApplicationFeatureProvider<ControllerFeature>
接口。这个类的主要作用是提供控制器类型的识别功能。
在 ASP.NET Core MVC 中,控制器是用来处理 HTTP 请求的类。传统的控制器类需要继承自 Controller
基类,但 ASP.NET Core 引入了一个新特性,即 POCO(Plain Old CLR Object)控制器。POCO 控制器允许你创建没有继承自 Controller
基类的类,但仍然可以将其识别为控制器,并赋予其处理 HTTP 请求的能力。
ControllerFeatureProvider
就是负责识别这些 POCO 控制器的类。它实现了 IApplicationFeatureProvider<ControllerFeature>
接口的 PopulateFeature
方法,该方法会在 MVC 框架构建应用模型时被调用。在这个方法中,ControllerFeatureProvider
会扫描应用程序中的类型,并根据一定的规则判断哪些类型应该被识别为控制器。
默认情况下,ControllerFeatureProvider
会将所有带有 "Controller" 后缀的类,或者使用了 [Controller]
特性的类识别为控制器。但你也可以通过自定义 ControllerFeatureProvider
的子类来提供自己的识别规则,以满足特定的需求。
实现自定义判断规则
通过上面的剖析,我们就知道要实现自定义的控制器判断规则,只需要重写ControllerFeature类或者重新实现IApplicationFeatureProvider接口,但是由于PopulateFeature不是虚方法或抽象方法,所以不能被重写,那么只能重新写一个类来实现IApplicationFeatureProvider接口了:
public class MyDynamicControllerFeatureProvider : ControllerFeatureProvider
{
protected override bool IsController(TypeInfo typeInfo)
{
var typeInfo = type.GetTypeInfo();
if (!typeof(IDynamicWebApi).IsAssignableFrom(type) ||
!typeInfo.IsPublic || typeInfo.IsAbstract || typeInfo.IsGenericType)
{
return false;
}
var attr = ReflectionHelper.GetSingleAttributeOrDefaultByFullSearch<DynamicWebApiAttribute>(typeInfo);
if (attr == null)
{
return false;
}
if (ReflectionHelper.GetSingleAttributeOrDefaultByFullSearch<NonDynamicWebApiAttribute>(typeInfo) != null)
{
return false;
}
return true;
}
}
IApplicationModelConvention
IApplicationModelConvention
是ASP.NET Core中的一个接口,它允许开发者在应用模型构建过程中应用自定义约定。ASP.NET Core 的应用模型是描述如何构建 HTTP 请求处理管道的一组组件和服务。
通过实现 IApplicationModelConvention
接口,开发者可以注册中间件、修改路由、添加模型绑定器、配置控制器和服务等。这些约定在应用的启动过程中被应用,通常在 Startup.ConfigureServices
方法中通过调用 AddApplicationPart
和 ApplyApplicationPartManager
方法来注册。
下面是一个简单的 IApplicationModelConvention
实现示例,该示例演示了如何为所有控制器添加一个自定义操作筛选器:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
public class CustomConvention : IApplicationModelConvention
{
public void Apply(ApplicationModel application)
{
foreach (var controller in application.Controllers)
{
// 为每个控制器添加自定义操作筛选器
controller.Filters.Add(new CustomActionFilter());
}
}
}
public class CustomActionFilter : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
// 在操作执行前执行的代码
}
public void OnActionExecuted(ActionExecutedContext context)
{
// 在操作执行后执行的代码
}
}
然后,在 Startup.ConfigureServices
方法中注册这个约定:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddApplicationPart(typeof(Startup).Assembly)
.ApplyApplicationPartManager(manager =>
{
manager.Conventions.Add(new CustomConvention());
});
}
在这个例子中,CustomConvention
被添加到了 ApplicationModel
的约定集合中。每当 ASP.NET Core 构建应用模型时,Apply
方法就会被调用,并且所有的控制器都会被添加 CustomActionFilter
筛选器。
Panda.DynamicWebApi中的实现
ConfigureApiExplorer()
首先,是对ApiExplorer进行配置。通过ApiExplorer,我们可以控制Controller级别和Action级别的Web API的可见性。一般情况下的用法是在Controller或者Action上添加ApiExplorerSettings标记,而在这里,我们只需要给ControllerModel和ActionModel的ApiExplorer属性赋值即可。
private void ConfigureApiExplorer(ControllerModel controller)
{
if (controller.ApiExplorer.GroupName.IsNullOrEmpty())
{
controller.ApiExplorer.GroupName = controller.ControllerName;
}
if (controller.ApiExplorer.IsVisible == null)
{
controller.ApiExplorer.IsVisible = true;
}
foreach (var action in controller.Actions)
{
if (!CheckNoMapMethod(action))
ConfigureApiExplorer(action);
}
}
private void ConfigureApiExplorer(ActionModel action)
{
if (action.ApiExplorer.IsVisible == null)
{
action.ApiExplorer.IsVisible = true;
}
}
ConfigureSelector()
接下来,是对路由进行配置。这部分的核心其实就是根据AreaName、ControllerName、ActionName来生成路由信息,我们会为没有配置过特性路由的Action生成默认的路由,这其实就是MVC里约定大于配置的一种体现啦。在这里会涉及到对ControllerName和ActionName的优化调整,主要体现在两个方面,其一是对类似XXXService、XXXController等这样的后缀进行去除,使其构造出的Api路由更加短小精简;其二是对ActionName里的Get/Save/Update等动词进行替换,使其构造出的Api路由更加符合RESTful风格。
private void ConfigureSelector(ControllerModel controller, DynamicWebApiAttribute controllerAttr)
{
if (controller.Selectors.Any(selector => selector.AttributeRouteModel != null))
{
return;
}
var areaName = string.Empty;
if (controllerAttr != null)
{
areaName = controllerAttr.Module;
}
foreach (var action in controller.Actions)
{
if (!CheckNoMapMethod(action))
ConfigureSelector(areaName, controller.ControllerName, action);
}
}
private void ConfigureSelector(string areaName, string controllerName, ActionModel action)
{
var nonAttr = ReflectionHelper.GetSingleAttributeOrDefault<NonDynamicWebApiAttribute>(action.ActionMethod);
if (nonAttr != null)
{
return;
}
if (action.Selectors.IsNullOrEmpty() || action.Selectors.Any(a => a.ActionConstraints.IsNullOrEmpty()))
{
if (!CheckNoMapMethod(action))
AddAppServiceSelector(areaName, controllerName, action);
}
else
{
NormalizeSelectorRoutes(areaName, controllerName, action);
}
}
private void AddAppServiceSelector(string areaName, string controllerName, ActionModel action)
{
var verb = GetHttpVerb(action);
action.ActionName = GetRestFulActionName(action.ActionName);
var appServiceSelectorModel = action.Selectors[0];
if (appServiceSelectorModel.AttributeRouteModel == null)
{
appServiceSelectorModel.AttributeRouteModel = CreateActionRouteModel(areaName, controllerName, action);
}
if (!appServiceSelectorModel.ActionConstraints.Any())
{
appServiceSelectorModel.ActionConstraints.Add(new HttpMethodActionConstraint(new[] { verb }));
switch (verb)
{
case "GET":
appServiceSelectorModel.EndpointMetadata.Add(new HttpGetAttribute());
break;
case "POST":
appServiceSelectorModel.EndpointMetadata.Add(new HttpPostAttribute());
break;
case "PUT":
appServiceSelectorModel.EndpointMetadata.Add(new HttpPutAttribute());
break;
case "DELETE":
appServiceSelectorModel.EndpointMetadata.Add(new HttpDeleteAttribute());
break;
default:
throw new Exception($"Unsupported http verb: {verb}.");
}
}
}
ConfigureParameters()
接下来参数绑定相对简单,因为简单类型MVC自己就能完成绑定,所以,我们只需要关注复杂类型的绑定即可,最常见的一种绑定方式是FromBody:
private void ConfigureParameters(ControllerModel controller)
{
foreach (var action in controller.Actions)
{
if (!CheckNoMapMethod(action))
foreach (var para in action.Parameters)
{
if (para.BindingInfo != null)
{
continue;
}
if (!TypeHelper.IsPrimitiveExtendedIncludingNullable(para.ParameterInfo.ParameterType))
{
if (CanUseFormBodyBinding(action, para))
{
para.BindingInfo = BindingInfo.GetBindingInfo(new[] { new FromBodyAttribute() });
}
}
}
}
}
Asp .Net Core 系列:Asp .Net Core 集成 Panda.DynamicWebApi的更多相关文章
- [ASP.NET MVC2 系列] ASP.Net MVC教程之《在15分钟内用ASP.Net MVC创建一个电影数据库应用程序》
[ASP.NET MVC2 系列] [ASP.NET MVC2 系列] ASP.Net MVC教程之<在15分钟内用ASP.Net MVC创建一个电影数据库应用程序> ...
- 跨平台应用集成(在ASP.NET Core MVC 应用程序中集成 Microsoft Graph)
作者:陈希章 发表于 2017年6月25日 谈一谈.NET 的跨平台 终于要写到这一篇了.跨平台的支持可以说是 Office 365 平台在设计伊始就考虑的目标.我在前面的文章已经提到过了,Micro ...
- asp.net core 系列 18 web服务器实现
一. ASP.NET Core Module 在介绍ASP.NET Core Web实现之前,先来了解下ASP.NET Core Module.该模块是插入 IIS 管道的本机 IIS 模块(本机是指 ...
- asp.net core 系列 16 Web主机 IWebHostBuilder
一.概述 在asp.net core中,Host主机负责应用程序启动和生存期管理.host主机包括Web 主机(IWebHostBuilder)和通用主机(IHostBuilder).Web 主机是适 ...
- Asp.net Core 系列之--5.认证、授权与自定义权限的实现
ChuanGoing 2019-11-24 asp.net core系列已经来到了第五篇,通过之前的基础介绍,我们了解了事件订阅/发布的eventbus整个流程,初探dapper ORM实现,并且简单 ...
- 【目录】asp.net core系列篇
随笔分类 - asp.net core系列篇 asp.net core系列 68 Filter管道过滤器 摘要: 一.概述 本篇详细了解一下asp.net core filters,filter叫&q ...
- Taurus.MVC 微服务框架 入门开发教程:项目集成:2、客户端:ASP.NET Core(C#)项目集成:应用中心。
系列目录: 本系列分为项目集成.项目部署.架构演进三个方向,后续会根据情况调整文章目录. 本系列第一篇:Taurus.MVC V3.0.3 微服务开源框架发布:让.NET 架构在大并发的演进过程更简单 ...
- 基于Asp.Net Core Mvc和EntityFramework Core 的实战入门教程系列-1
来个目录吧: 第一章 第二章 第三章 暂时就这么多.后面路线更新吧 本系列文章为翻译加上我个人的使用心得理解,希望帮助热爱学习的程序员. 珍重声明:本系列文章会跟原文有点出入,去掉了罗里吧嗦的文字. ...
- ASP.NET CORE系列【一】搭建ASP.NET CORE项目
为什么要使用 ASP.NET Core? NET Core 刚发布的时候根据介绍就有点心里痒痒,微软的尿性都懂的,新东西bug太多,现在2.0也发布很久了,决定研究一下. ASP.NET Core官方 ...
- 1.1专题介绍「深入浅出ASP.NET Core系列」
大家好,我是IT人张飞洪,专注于.NET平台十年有余. 工作之余喜欢阅读和写作,学习的内容包括数据结构/算法.网络技术.Linux系统原理.数据库技术原理,设计模式.前沿架构.微服务.容器技术等等…… ...
随机推荐
- 全流程机器视觉工程开发(二)PaddleDetection:拉框,然后开始训练模型
前言 我现在在准备做一个全流程的机器视觉的工程,之前做了很多理论相关的工作.大概理解了机器视觉的原理,然后大概了解了一下,我发现现在的库其实已经很发展了,完全不需要用到非常多的理论,只需要知道开发过程 ...
- C#多曲线数据分析
数据如下 统计效果图如下 程序初始化 private void Form1_Load(object sender, EventArgs e) { using (SqlConnection con = ...
- C#通过泛型实现对子窗体的不同操作
private void button1_Click(object sender, EventArgs e) { FormOperate<object>();//调用FormOperate ...
- 43 干货系列从零用Rust编写负载均衡及代理,内网穿透方案完整部署
wmproxy wmproxy已用Rust实现http/https代理, socks5代理, 反向代理, 静态文件服务器,四层TCP/UDP转发,七层负载均衡,内网穿透,后续将实现websocket代 ...
- Jquery - 获取所有子节点 ( 并删除 )
1,获取所有子节点 $(".parent").find('.child') 2,获取所有子节点,通过上层 div 的类名 , 获取上层 div 节点 $(".pare ...
- sql server 数据恢复
1) 备份当前数据库的事务日志:BACKUP LOG [数据库名] TO disk= N'备份文件名' WITH NORECOVERY 2) 恢复一个误删除之前的完全备份:RESTORE DATABA ...
- SQLServer解决deadlock问题的一个场景
SQLServer解决deadlock问题的一个场景 背景 公司产品出现过很多次dead lock 跟研发讨论了很久, 都没有具体的解决思路 但是这边知道了一个SQLServer数据库上面计划100% ...
- [转帖]Kingbase实现Oracle userenv函数功能
目录 1. 问题 2. 文档概述 3. Oracle userenv()函数功能调研 3.1. 函数名称/函数原型 3.2. 函数功能 3.3. 参数介绍 3.3.1. Parameter 3.4. ...
- [转帖]GC日志分析工具——GCViewer案例
原创 石页粑粑 来自zxsk的码农 2020-09-28 06:18 一.GCViewer介绍 业界较为流行分析GC日志的两个工具--GCViewer.GCEasy.GCEasy部分功能还是要收费的, ...
- sed 删除包含某字符的一行 给包含某字符的一行添加 逗号的简单方法
今天处理环境折腾死了 方法: #给包含 configdata 的一行 添加 逗号结尾 find . -name "*.json" |xargs sed -i '/configdat ...