Asp .Net Core 系列:Asp .Net Core 集成 Panda.DynamicWebApi

文章目录

    • 简介
    • Asp .Net Core 集成 Panda.DynamicWebApi
    • 配置
    • 原理
      • 什么是POCO Controller?
      • POCO控制器原理
      • ControllerFeatureProvider
      • 实现自定义判断规则
      • IApplicationModelConvention
      • Panda.DynamicWebApi中的实现
        • ConfigureApiExplorer()
        • ConfigureSelector()
        • ConfigureParameters()

简介

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 的子类来提供自己的识别规则,以满足特定的需求。

https://github.com/dotnet/aspnetcore/blob/main/src/Mvc/Mvc.Core/src/Controllers/ControllerFeatureProvider.cs

实现自定义判断规则

通过上面的剖析,我们就知道要实现自定义的控制器判断规则,只需要重写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 方法中通过调用 AddApplicationPartApplyApplicationPartManager 方法来注册。

https://learn.microsoft.com/zh-cn/dotnet/api/microsoft.aspnetcore.mvc.applicationmodels.iapplicationmodelconvention?view=aspnetcore-8.0

下面是一个简单的 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() });}}}}}

image

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/673721.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

S7-1200PLC通讯问题总结

文章目录 一、硬件1.串口通信RS232RS485RS422 2.网口通信 二、协议1.串口通信协议2.网口通信协议 三、程序编写1.S7通信PUTGET 2.开放式以太网通信 一、硬件 可分为PLC与PLC通信&#xff0c;PLC与上位机通信&#xff0c;PLC与变频器通信&#xff0c;PLC与仪器仪表通信&#xf…

兼容ARM 32位架构的edgeConnector产品为用户提供新的部署选项

Softing工业将ARM 32位兼容性集成到了edgeConnector产品中&#xff0c;以满足用户对ARM处理器的边缘设备日益增长的使用需求。 &#xff08;兼容ARM 32位架构的edgeConnector产品扩展了其应用部署范围&#xff09; 用户对采用ARM处理器的紧凑型边缘设备的需求正在大幅增长&…

使用 Docker 镜像预热提升容器启动效率详解

概要 在容器化部署中,Docker 镜像的加载速度直接影响到服务的启动时间和扩展效率。本文将深入探讨 Docker 镜像预热的概念、必要性以及实现方法。通过详细的操作示例和实践建议,读者将了解如何有效地实现镜像预热,以加快容器启动速度,提高服务的响应能力。 Docker 镜像预热…

使用influxdb+Grafana+nmon2influxdb+nmon实时监控vps性能

Grafana可以用来实时查看linux系统的各种性能数据。 1、安装环境&#xff1a; centos 7influxdb1.7.6grafana-4.6.3-1nmon2influxdb-2.1.7nmon-16m 2、安装influxdb&#xff1a; 下载rpm包&#xff1a; influxdb官网&#xff1a;https://docs.influxdata.com/influxdb/v2.0…

【Git版本控制 01】基本操作

目录 一、初始配置 二、添加文件 三、查看日志 四、修改文件 五、版本回退 六、撤销修改 七、删除文件 一、初始配置 Git版本控制器&#xff1a;记录每次的修改以及版本迭代的一个管理系统。 # 初始化本地仓库&#xff1a;git init(base) [rootlocalhost gitcode]# gi…

NLP_语言模型的雏形 N-Gram 模型

文章目录 N-Gram 模型1.将给定的文本分割成连续的N个词的组合(N-Gram)2.统计每个N-Gram在文本中出现的次数&#xff0c;也就是词频3.为了得到一个词在给定上下文中出现的概率&#xff0c;我们可以利用条件概率公式计算。具体来讲&#xff0c;就是计算给定前N-1个词时&#xff0…

笔记---dp---数字三角形模型

所谓数字三角形模型&#xff0c;即是从数字三角形这一题衍生出来的 题目为经典题目&#xff0c;不再赘述&#xff0c;此笔记根据AcWing算法提高课来进行对数字三角形模型衍生例题的记录 题目关系如下&#xff08;见AcWing里的AcSaber&#xff09;&#xff1a; AcWing.1015.摘…

微信小程序(三十八)滚动容器

注释很详细&#xff0c;直接上代码 上一篇 新增内容&#xff1a; 1.滚动触底事件 2.下拉刷新事件 源码&#xff1a; index.wxml <view class"Area"> <!-- scroll-y 垂直滚动refresher-enabled 允许刷新bindrefresherrefresh 绑定刷新作用函数bindscrollto…

单片机无线发射的原理剖析

目录 一、EV1527编码格式 二、OOK&ASK的简单了解 三、433MHZ 四、单片机的地址ID 五、基于STC15W104单片机实现无线通信 无线发射主要运用到了三个知识点&#xff1a;EV1527格式&#xff1b;OOk&#xff1b;433MHZ。下面我们来分别阐述&#xff1a; EV1527是数据的编…

网神 SecGate 3600 防火墙 route_ispinfo_import_save 文件上传漏洞

免责声明&#xff1a;文章来源互联网收集整理&#xff0c;请勿利用文章内的相关技术从事非法测试&#xff0c;由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;所产生的一切不良后果与文章作者无关。该…

Golang 基础 Go Modules包管理

Golang 基础 Go Modules包管理 在 Go 项目开发中&#xff0c;依赖包管理是一个非常重要的内容&#xff0c;依赖包处理不好&#xff0c;就会导致编译失败&#xff0c;本文将系统介绍下 Go 的依赖包管理工具。 我会首先介绍下 Go 依赖包管理工具的历史&#xff0c;并详细介绍下…

idea2023创建spring项目无法选择Java8

idea2023创建spring项目无法选择Java8 今天下载了新版的idea 2023.3.2&#xff0c;但是在创建springboot项目的时候只能选择Java17和Java21&#xff0c;没法选择其他的版本。 使用下面阿里云的地址替换Server URL中的start.spring.io的地址即可 https://start.aliyun.com/替…

C#调用WechatOCR.exe实现本地OCR文字识别

最近遇到一个需求&#xff1a;有大量的扫描件需要还原为可编辑的文本&#xff0c;很显然需要用到图片OCR识别为文字技术。本来以为这个技术很普遍的&#xff0c;结果用了几个开源库&#xff0c;效果不理想。后来&#xff0c;用了取巧的方法&#xff0c;直接使用了WX的OCR识别模…

Linux大集合

Linux Linux是什么&#xff1f; Linux是一套免费使用和自由传播的类Unix操作系统&#xff0c;是一个基于POSIX和UNIX的多用户、多任务、 支持多线程和多CPU的操作系统。它能运行主要的UNIX工具软件、应用程序和网络协议。它支持32位和 64位硬件。 Linux内核 是一个Linux系统…

可达鸭二月月赛——入门赛第四场T3题解

姓名 王胤皓 AC 记录 题目&#xff1a; 思路 用数组进行操作太难&#xff0c;而这些操作可以再 STL 中的 vector 容器&#xff0c;有 insert 和 erase 函数&#xff0c;所以非常方便。 vector 下标从 0 0 0 开始&#xff0c;所以所有操作都要 − 1 -1 −1。 操作 1 1 1 …

Redis的数据类型Hash使用场景实战

Redis的数据类型Hash使用场景 常见面试题&#xff1a;redis在你们项目中是怎么用的&#xff0c;除了String数据类型还使用什么数据类型&#xff1f; 怎么保证缓存和数据一致性等问题… Hash模型使用场景 知识回顾&#xff1a; redisTemplate.opsForHash() 方法是 Redis 的 …

Spring Boot3整合Redis

⛰️个人主页: 蒾酒 &#x1f525;系列专栏&#xff1a;《spring boot实战》 &#x1f30a;山高路远&#xff0c;行路漫漫&#xff0c;终有归途。 目录 前置条件 1.导依赖 2.配置连接信息以及连接池参数 3.配置序列化方式 4.编写测试 前置条件 已经初始化好一个spr…

MyBatisPlus基础操作之增删改查

目录 一、基本使用 1.1 插入数据 1.2 删除操作 1.3 更新操作 二、条件构造器Wrapper 2.1 常用AbstractWrapper方法 2.1.1 示例一 2.2.2 示例二 2.2.3 示例三 2.2 常用QueryWrapper方法 2.2.1 示例一 2.2.2 示例二 2.2.3 示例三&#xff08;常用&#xff09; 2.3 常…

1978-2022年地级市全要素生产率数据

1978-2022年地级市全要素生产率数据 1、时间&#xff1a;1978-2022年 2、来源&#xff1a;城市统计年鉴以及各省市的统计年鉴 3、指标&#xff1a;省份、地区、年份、OLS、FE、RE、DGMM、SGMM、SFA1、SFA2、SFA3、SFA3D、TFE、非参数法 4、范围&#xff1a;421地区 5、参考…

Deepin基本环境查看(八)【系统安全:房、车、查房、查车】

Deepin基本环境查看&#xff08;八&#xff09;【系统安全&#xff1a;房、车、查房、查车】 - 相关文章目录1、概述2、想象中的... 现实中的...1&#xff09;想象中的我2&#xff09;梦幻中的我3&#xff09;现实中的我 3 要房、要车、还是房车都要1&#xff09;超级计算机2&a…