iis服务器助手广告
返回顶部
首页 > 资讯 > 后端开发 > ASP.NET >.Net动态生成controller遇到的坑
  • 243
分享到

.Net动态生成controller遇到的坑

2024-04-02 19:04:59 243人浏览 安东尼
摘要

目录前言动态新建Type使用Emit撸IL使用ApplicationPartManager注册controller最后贴一下代码一些动态生成controller的问题 前言 最近在写

一些动态生成controller的问题

前言

最近在写包, 一开始封装了仓储Repository用于操作数据库, 然后为了快速开发一些业务简单的接口, 通过QueryController , ModifyController , CrudController 提供默认实现, 在添加接口的时候只需要新建一个 Controller, 然后继承

public class TestController : QueryRepController<int?, TestEntity, TestEntityGet>
{
    public TestController(IQueryRepository<int?, TestEntity> repository) : base(repository)
    {
    }
}

即可实现简单的增删改查功能

看到 TestController 这单薄的实现, 我突然有个想法

"既然这个controller写得这么简单, 为什么我不能尝试靠代码去生成呢!?!"

虽然这个功能不一定有什么用, 但我还是开始了踩坑

动态新建Type

经过简单的思考, 我认为第一步应该是创建 Type

尝试的方案一

最开始尝试注册一堆 typeof(QueryRepController<int?, TestEntity, TestEntityGet>), 然后动态创建路由

但我搞了半天也没发现asp.net里面有相关的功能, 也不能确定这样生成的 Type 是正常的, 感觉这里面能让我栽进去的坑有很多

虽然可以自己重新实现一套路由......后面还得搞日志, 拦截器什么的 ?!?

我废那劲干嘛, 于是放弃

尝试的方案二

之前就听说C#有 Source Generator, 可以在编译时直接生成代码

还听说 AutoMapper 就用了这种技术(也不知道是真是假)

然后决定研究一下......

一个周末的时间让我了解到, 这东西好像没多少人用啊, 相关资料少得可怜, 网上逛了两天, 除了说这东西很有用, 很香, 没找着多少对我有用的资料, 也可能是我太菜了不会用

虽然最后生成了一个可以正常使用的 Controller, 但是与我的预期有极大的差距

我期望的使用方式类似下面这种

services.AddQueryRepController<int?, TestEntity, TestEntityGet>("Test");

在使用的时候可以主动通过注册的方式添加 Controller, 然后可以自由更改路由(比如把Test改为WTF)

搞了两天感觉方向不对, 虽然 Source Generator 确实挺有意思的, 也有可以发挥的场景, 但至少不太符合我这时的需要

尝试的方案三

从 Source Generator 中抽身后, 我又开始大海捞针式地寻找方案

然后在 万能的stackoverflow 上找到了可能的方案

使用Emit撸IL

说实话在这之前我从来没有听说过 dotnet 中的 Emit, 平时使用的反射也只是 GetValue SetValue 这样的, 这鬼东西真是让我大开眼界。

经过一番"艰苦"奋战后, 磕磕绊绊憋出了类似下面的代码

public static IServiceCollection AddQueryRepController<TKEy, T, GetT>(this IServiceCollection services, string route)
where T : class, IBaseEntity<TKey> where GetT : IBaseGet<T>
{
    // 建一个 Assembly
    AssemblyBuilder Ass = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("NewController"), AssemblyBuilderAccess.Run);
    ModuleBuilder MB = Ass.DefineDynamicModule("NewController");
    // 起个好听的名字
    var typeName = $"{route}Controller";
    // 使用QueryRepController<TKey, T, GetT>整一个builder
    var typeBuilder = MB.DefineType(typeName, TypeAttributes.Class | TypeAttributes.Public, typeof(QueryRepController<TKey, T, GetT>), null);
    // 添加一个构造函数,
    var ctor = typeBuilder.DefineConstructor(MethodAttributes.Public, CallinGConventions.Standard | CallingConventions.HasThis, new[] { typeof(IQueryRepository<TKey, T>) });
    // 给这个构造函数编IL
    var ilGenerator = ctor.GetILGenerator();
    // 通过ILSpy反编译,然后抄il
    ilGenerator.Emit(OpCodes.Ldarg, 0);
    ilGenerator.Emit(OpCodes.Ldarg, 1);
    ilGenerator.Emit(OpCodes.Call, typeof(QueryRepController<TKey, T, GetT>).GetConstructors()[0]);
    ilGenerator.Emit(OpCodes.Nop);
    ilGenerator.Emit(OpCodes.Ret);
    // 创建这个新的 type
    var type = typeBuilder.CreateType();
    // 根据自己的情况注册到容器中
    services.AddTransient(typeof(IQueryController<TKey, T, GetT>), type);
    return services;
}

以我的水平和能力, 做到这样已经是极限, 靠ILSpy反编译上面的 TestController, 抄了点代码(我抄我自己)

现在可以使用

services.AddQueryRepController<int?, TestEntity, TestEntityGet>("Test")

生成并注册一个 TestController 到容器中, 也可以正常获取实例

但是程序就是无法感知到代码的变化, swagger 中也看不到新加的 Controller

尝试进行请求, 最后也以 404 Not Found 失败告终

于是再次陷入僵局

使用ApplicationPartManager注册controller

之前在逛园子的时候看到 Artech大佬的 文章 , 当时看的时候感觉云里雾里的, 不知所云

也尝试硬着头皮写, 但是没有能够坚持下去, 但我在完成以上步骤并且被卡住后, 再次看了大佬的文章, 豁然开朗!

为了让这些程序集成为应用的一个有效组成部分,程序集需要封装成ApplicationPart对象并利用ApplicationPartManager进行注册

参考大佬的文章, 写了如下的实现

AddControllerChangeProvider

public class AddControllerChangeProvider : IActionDescriptorChangeProvider
{
    public static AddControllerChangeProvider Instance { get; } = new AddControllerChangeProvider();
    public CancellationTokenSource TokenSource { get; private set; }
    public bool HasChanged { get; set; }
    public IChangeToken GetChangeToken()
    {
        TokenSource = new CancellationTokenSource();
        return new CancellationChangeToken(TokenSource.Token);
    }
}

又有一个 HostedService 在注册完成后通过 ApplicationPartManager 更新注册信息

ChangeActionService

public class ChangeActionService : IHostedService
{
    private readonly ApplicationPartManager Part;
    public ChangeActionService(IServiceScopeFactory scope)
    {
        Part = scope.CreateScope().ServiceProvider.GetService<ApplicationPartManager>();
    }
    public async Task StartAsync(CancellationToken cancellationToken)
        Part.ApplicationParts.Add(new AssemblyPart( <可以直接使用之前的AssemblyBuilder> ));
        AddControllerChangeProvider.Instance.HasChanged = true;
        AddControllerChangeProvider.Instance.TokenSource.Cancel();
        await Task.CompletedTask;

    public async Task StopAsync(CancellationToken cancellationToken)
}

之后使用时注册 AddControllerChangeProviderChangeActionService

services.AddSingleton<IActionDescriptorChangeProvider>(AddControllerChangeProvider.Instance);
services.AddHostedService<ChangeActionService>();

程序运行后会启动 ChangeActionService, 读取我之前生成controller时使用的 AssemblyBuilder, 注册生成的新的controller

这时就已经可以在 swagger 中看到创建的 TestController 了, 并且也能正常进行访问

最后贴一下代码

之后经过一系列过度封装, 简单的代码如下(用了很多自己的封装, 看看就好...)

var builder = WEBApplication.CreateBuilder(args);
builder.Services.AddMysql<TestDbContext>("localhost", 3306, "test", "root", "pwd")
// 将 TestDbContext 注册为默认的 DbContext
.ADDDefaultDbContext<TestDbContext>()
.AddControllers();
builder.Services
// 注册一个 TestController
.AddQueryRepController<long?, TestEntity, TestEntityGet>("Test")
// 带注释的 Swagger
.AddSwaggerWithComments();
var app = builder.Build();
app.UseSwagger().UseSwaggerUI();
app.MapControllers();
app.Run();
public class TestDbContext : DbContext
{
    public DbSet<TestEntity> Tests { get; set; }
    public TestDbContext(DbContextOptions<TestDbContext> options) : base(options)
    { }
}
// 对应数据库中的 Test 表
public class TestEntity : BaseEntity<long?>
    public string Code { get; set; }
    public int? Number { get; set; }
    public bool? IsTest { get; set; }
// 对应 TestEntity 的 TestEntityGet, 决定接口的查询规则
public class TestEntityGet : BaseGet<TestEntity>
    public string? Code { get; set; }

虽然没啥卵用, 但是写出这段代码的那一刻, 我自己是爽了, 有没有用已经不重要的

到此这篇关于dotnet动态生成controller的问题的文章就介绍到这了,更多相关dotnet动态生成controller内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: .Net动态生成controller遇到的坑

本文链接: https://www.lsjlt.com/news/143778.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • .Net动态生成controller遇到的坑
    目录前言动态新建Type使用Emit撸IL使用ApplicationPartManager注册controller最后贴一下代码一些动态生成controller的问题 前言 最近在写...
    99+
    2024-04-02
  • mybatisplus自动生成器解析(及遇到的坑)
    目录mybatisplus自动生成器解析1.加入依赖2.写一个类,作为自动生成器的入口3.修改代码3.运行讲解一下mybatisplus代码生成器使用及注意事项1.添加maven依赖...
    99+
    2024-04-02
  • 基于Lombok集成springboot遇到的坑
    目录Lombok集成springboot遇到的坑问题原因springboot引入LombokLombok集成springboot遇到的坑 最近有同事在spring boot中用Lom...
    99+
    2024-04-02
  • idea2020.3.3集成maven及遇到的坑(推荐)
    目录idea2021最新激活码一:配置Maven环境二:idea集成mavenidea2020.3.3配置Maven走过的那些坑 idea2021最新激活码 idea2020.3.3...
    99+
    2024-04-02
  • 使用mybatisPlus生成oracle自增序列遇到的坑如何解决
    今天小编给大家分享一下使用mybatisPlus生成oracle自增序列遇到的坑如何解决的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了...
    99+
    2023-07-05
  • springboot2.6.4集成swagger3.0遇到的坑及解决方法
    Swagger简介 号称:世界上最流行的API框架PestFul API文档在线自动生成器 -->API文档与API定义同步更新可以直接运行,测试API接口 支持多种语言 强调...
    99+
    2024-04-02
  • springboot访问静态资源遇到的坑及解决
    目录访问静态资源遇到的坑及解决直接访问静态资源的问题SpringBoot 默认静态资源访问配置引入shiro 或 security后的拦截过滤访问静态资源遇到的坑及解决 开始是以这...
    99+
    2024-04-02
  • 自动生成代码controller tool的简单使用
    目录介绍controller-tools介绍 在上一篇code-generator简单介绍中重点介绍了如何使用code-generator来自动生成代码,通过自动生成的代码可以帮助我...
    99+
    2024-04-02
  • springboot 多数据源配置不生效遇到的坑及解决
    目录多数据源配置不生效遇到的坑解决方案踩坑SpringBoot配置多数据源,循环引用问题解决办法多数据源配置不生效遇到的坑 ** 同步数据时遇到多个数据源切换的问题,配置了yml...
    99+
    2024-04-02
  • Javascript动态生成的页面信息爬
      最近,笔者在使用Requests模拟浏览器发送Post请求时,发现程序返回的html与浏览器F12观察到的略有不同,经过观察返回的response.text,cookies确认有效,因为我们可以看到返回的登陆信息。然而部分字段的值依然...
    99+
    2023-01-30
    页面 动态 信息
  • java 动态生成SQL的实例讲解
    代码如下: public <T> String updateSqlAndParamList(Vector<String> ve,List<String> paramList,T t,String tabl...
    99+
    2023-05-31
    java 动态生成 sql
  • MySQL生成千万测试数据以及遇到的问题
    目录1、创建基础表结构2、创建内存表3、创建存储过程和函数4、执行存储过程5、遇到的问题5.1、1449错误5.2、1114错误6、同步数据总结1、创建基础表结构 CREATE TA...
    99+
    2022-11-13
    mysql千万数据查询 mysql快速生成测试数据 mysql 千万级数据
  • 怎么使用MyBatis的@SelectProvider动态生成SQL
    ...
    99+
    2024-05-08
    MyBatis
  • 如何优化动态生成的SQL性能
    要优化动态生成的SQL性能,可以考虑以下几点: 使用参数化查询:动态生成的SQL语句中应尽量使用参数化查询,避免直接拼接变量或者...
    99+
    2024-04-29
    SQL
  • Vue动态生成数据字段的实例
    目录动态生成数据字段实例1.父组件定义data里面的数据字段2.子组件接收数据3.因为获取数据是异步操作4.计算属性计算两个变量是否均完成5.子组件完整代码表单动态生成字段 ...
    99+
    2024-04-02
  • Linux系统下动态库的生成方式
    本篇内容介绍了“Linux系统下动态库的生成方式”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!  什么是动态库?  动态库又称动态链接库英文...
    99+
    2023-06-13
  • golang动态生成struct的方法是什么
    在Go语言中,可以使用`reflect`包来动态生成struct。下面是一个示例代码:```gopackage mainimport...
    99+
    2023-08-08
    golang
  • java动态生成word的方法是什么
    在Java中,可以使用Apache POI库来动态生成Word文档。Apache POI是一个用于处理Microsoft文档格式的J...
    99+
    2023-09-16
    java word
  • JavaScript实现生成动态表格和动态效果的方法详解
    今天上午完成了Vue实现一个表格的动态样式,那么JavaScript代码能不能实现同样的效果呢?这样也可以学习一下JavaScript的语法,晚上试了一下,完全可以,效果一模一样。 ...
    99+
    2024-04-02
  • vue中v-model动态生成的示例分析
    这篇文章主要介绍了vue中v-model动态生成的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。vue中v-model动态生成的实例...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作