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

news/2024/5/19 21:44:54 标签: .netcore

文章目录

    • 简介
    • 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.niftyadmin.cn/n/5370450.html

相关文章

【MySQL】_JDBC编程

目录 1. JDBC原理 2. 导入JDBC驱动包 3. 编写JDBC代码实现Insert 3.1 创建并初始化一个数据源 3.2 和数据库服务器建立连接 3.3 构造SQL语句 3.4 执行SQL语句 3.5 释放必要的资源 4. JDBC代码的优化 4.1 从控制台输入 4.2 避免SQL注入的SQL语句 5. 编写JDBC代码实现…

Stable Diffusion教程——使用TensorRT GPU加速提升Stable Diffusion出图速度

概述 Diffusion 模型在生成图像时最大的瓶颈是速度过慢的问题。为了解决这个问题&#xff0c;Stable Diffusion 采用了多种方式来加速图像生成&#xff0c;使得实时图像生成成为可能。最核心的加速是Stable Diffusion 使用了编码器将图像从原始的 3512512 大小转换为更小的 46…

如何在Mac上允许主流浏览器使用弹出式窗口?这里有详细步骤

这篇文章教你如何关闭流行的Mac浏览器上的弹出窗口阻止程序,包括Safari、Chrome和Firefox。它还探讨了你可能希望这样做的原因及其影响。 如何在Mac上允许Safari使用弹出窗口 如果你经常在Mac上使用Safari,你会注意到默认情况下弹出窗口阻止程序是打开的。有时,这并不方便…

python视频播放列表信息库之m3u8使用详解

m3u8库是什么&#xff1f; m3u8是一个用于解析和操作M3U8文件的Python库。M3U8文件&#xff0c;是指使用UTF-8编码格式的M3U文件&#xff0c;它们通常用于播放列表文件&#xff0c;尤其是在HTTP Live Streaming&#xff08;HLS&#xff09;中。简单来说&#xff0c;m3u8库能帮…

【学习笔记】【内核】container_of 的用法

根据结构体变量成员的指针获取整个结构体的指针 #include <stdio.h> …

书生·浦语大模型第二课作业

基础作业&#xff1a; 使用 InternLM-Chat-7B 模型生成 300 字的小故事&#xff08;需截图&#xff09;。熟悉 hugging face 下载功能&#xff0c;使用 huggingface_hub python 包&#xff0c;下载 InternLM-20B 的 config.json 文件到本地&#xff08;需截图下载过程&#xf…

2024年Java架构篇之数据结构与算法面试题

2024年Java实战面试题_java 5 年 面试-CSDN博客 2024年Java架构篇之数据结构与算法面试题 1、判断一个数是否是2的N次方&#xff0c;比如2、4、8、16是6、10不是 1:01、2:10、3:011、4:100、7:0111、8:1000、15:01111、16:10000 if(n&(n-1) 0) 那么n就是2的次方 n > 1 …

Mac上几款好用的MacBook视频播放器

使用Mac电脑时&#xff0c;视频播放器可以说是我们使用频率最高的软件之一了&#xff0c;不管是工作时看视频资料还是在家里看下载好的电影&#xff0c;都需要用到视频播放器&#xff0c;本文中我们就来推荐几款好用的Macbook视频播放器&#xff0c;总有一款适合你&#xff01;…