昨天有个朋友在微信上问我一个问题:他希望通过动态脚本的形式实现对ASP.NET Core MVC应用的扩展,比如在程序运行过程中上传一段C#脚本将其中定义的Controller类型注册到应用中,问我是否有好解决方案。这是一个挺有意思的问题,我们可以通过两种方案实现了这个需求。

01

实现效果

我们先来看看实现的效果。如下所示的是一个MVC应用的主页,我们可以在文本框中通过编写C#代码定义一个有效的Controller类型,然后点击“Register”按钮,定义的Controller类型将自动注册到MVC应用中

由于我们采用了针对模板为“{controller}/{action}”的约定路由,所以我们采用路径“/foo/bar”就可以访问上图中定义在FooController中的Action方法Bar,下图证实了这一点。

02

动态编译源代码

要实现如上所示的“针对Controller类型的动态注册”,首先需要解决的是针对提供源代码的动态编译问题,我们知道这个可以利用Roslyn来解决。具体来说,我们定义了如下这个ICompiler接口,它的Compile方法将会对参数sourceCode提供的源代码进行编译。该方法返回源代码动态编译生成的程序集,它的第二个参数代表引用的程序集。

public interface ICompiler
{Assembly Compile(string text, params Assembly[] referencedAssemblies);
}

如下所示的Compiler类型是对ICompiler接口的默认实现。

public class Compiler : ICompiler
{public Assembly Compile(string text, params Assembly[] referencedAssemblies){var references = referencedAssemblies.Select(it => MetadataReference.CreateFromFile(it.Location));var options = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary);var assemblyName = "_" + Guid.NewGuid().ToString("D");var syntaxTrees = new SyntaxTree[] { CSharpSyntaxTree.ParseText(text) };var compilation = CSharpCompilation.Create(assemblyName, syntaxTrees, references, options);using var stream = new MemoryStream();var compilationResult = compilation.Emit(stream);if (compilationResult.Success){stream.Seek(0, SeekOrigin.Begin);return Assembly.Load(stream.ToArray());}throw new InvalidOperationException("Compilation error");}
}

03

DynamicActionProvider

解决了针对提供源代码的动态编译问题之后,我们可以获得需要注册的Controller类型,那么如何将它注册MVC应用上呢?要回答这个问题,我们得对MVC框架的执行原理有一个大致的了解:ASP.NET Core通过一个由服务器和若干中间件构成的管道来处理请求,MVC框架建立在通过EndpointRoutingMiddleware和EndpointMiddleare这两个中间件构成的终结点路由系统上。此路由系统维护着一组路由终结点,该终结点体现为一个路由模式(Route Pattern)与对应处理器(通过RequestDelegate委托表示)之间的映射。

由于针对MVC应用的请求总是指向某一个Action,所以MVC框架提供的路由整合机制体现在为每一个Action创建一个或者多个终结点(同一个Action方法可以注册多个路由)。针对Action方法的路由终结点是根据描述Action方法的ActionDescriptor对象构建而成的。至于ActionDescriptor对象,则是通过注册的一组IActionDescriptorProvider对象来提供的,那么我们的问题就迎刃而解:通过注册自定义的IActionDescriptorProvider从动态定义的Controller类型中解析出合法的Action方法,并创建对应的ActionDescriptor对象即可。

那么ActionDescriptor如何创建呢?我们能想到简单的方式是调用如下这个Build方法。针对该方法的调用存在两个问题:第一,ControllerActionDescriptorBuilder是一个内部(internal)类型,我们指定以反射的方式调用这个方法,第二,这个方法接受一个类型为ApplicationModel的参数。

internal static class ControllerActionDescriptorBuilder
{public static IList<ControllerActionDescriptor> Build(ApplicationModel application);
}

ApplicationModel类型涉及到一个很大的主题:MVC应用模型,目前我们现在只关注如何创建这个对象。表示MVC应用模型的ApplicationModel对象是通过对应的工厂ApplicationModelFactory创建的。这个工厂会自动注册到MVC应用的依赖注入框架中,但是这依然是一个内部(内部)类型,所以还得反射。

internal class ApplicationModelFactory
{public ApplicationModel CreateApplicationModel(IEnumerable<TypeInfo> controllerTypes);
}

我们定义了如下这个DynamicActionProvider类型实现了IActionDescriptorProvider接口。针对提供的源代码向ActionDescriptor列表的转换体现在AddControllers方法中:它利用ICompiler对象编译源代码,并在生成的程序集中解析出有效的Controller类型,然后利用ApplicationModelFactory创建出代表应用模型的ApplicationModel对象,后者作为参数调用ControllerActionDescriptorBuilder的静态方法Build创建出描述所有Action方法的ActionDescriptor对象。

public class DynamicActionProvider : IActionDescriptorProvider
{private readonly List<ControllerActionDescriptor> _actions;private readonly Func<string, IEnumerable<ControllerActionDescriptor>> _creator;public DynamicActionProvider(IServiceProvider serviceProvider, ICompiler compiler){_actions = new List<ControllerActionDescriptor>();_creator = CreateActionDescrptors;IEnumerable<ControllerActionDescriptor> CreateActionDescrptors(string sourceCode){var assembly = compiler.Compile(sourceCode, Assembly.Load(new AssemblyName("System.Runtime")),typeof(object).Assembly,typeof(ControllerBase).Assembly,typeof(Controller).Assembly);var controllerTypes = assembly.GetTypes().Where(it => IsController(it));var applicationModel = CreateApplicationModel(controllerTypes);assembly = Assembly.Load(new AssemblyName("Microsoft.AspNetCore.Mvc.Core"));var typeName = "Microsoft.AspNetCore.Mvc.ApplicationModels.ControllerActionDescriptorBuilder";var controllerBuilderType = assembly.GetTypes().Single(it => it.FullName == typeName);var buildMethod = controllerBuilderType.GetMethod("Build", BindingFlags.Static | BindingFlags.Public);return (IEnumerable<ControllerActionDescriptor>)buildMethod.Invoke(null, new object[] { applicationModel });}ApplicationModel CreateApplicationModel(IEnumerable<Type> controllerTypes){var assembly = Assembly.Load(new AssemblyName("Microsoft.AspNetCore.Mvc.Core"));var typeName = "Microsoft.AspNetCore.Mvc.ApplicationModels.ApplicationModelFactory";var factoryType = assembly.GetTypes().Single(it => it.FullName == typeName);var factory = serviceProvider.GetService(factoryType);var method = factoryType.GetMethod("CreateApplicationModel");var typeInfos = controllerTypes.Select(it => it.GetTypeInfo());return (ApplicationModel)method.Invoke(factory, new object[] { typeInfos });}bool IsController(Type typeInfo){if (!typeInfo.IsClass) return false;if (typeInfo.IsAbstract) return false;if (!typeInfo.IsPublic) return false;if (typeInfo.ContainsGenericParameters) return false;if (typeInfo.IsDefined(typeof(NonControllerAttribute))) return false;if (!typeInfo.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase) && !typeInfo.IsDefined(typeof(ControllerAttribute))) return false;return true;}}public int Order => -100;public void OnProvidersExecuted(ActionDescriptorProviderContext context) { }public void OnProvidersExecuting(ActionDescriptorProviderContext context){foreach (var action in _actions){context.Results.Add(action);}}public void AddControllers(string sourceCode) => _actions.AddRange(_creator(sourceCode));
}

http://www.taodudu.cc/news/show-911451.html

相关文章:

  • 【视频回放与课件】Build your AI solution with MLOps
  • 如何在.NET应用程序中分析CPU使用率过高的问题
  • 玩转控件:对Dev的GridControl控件扩展
  • 初识消息队列/RabbitMQ详解
  • 【最强VSCode】之管理MySql数据库
  • [Flags]标识的Enum不能使用Html.GetEnumSelectList方法
  • 你遇到的面试官是「伯乐」吗?
  • 没用过.gitignore还敢自称高级开发?
  • .net core 集成 sentry 进行异常报警
  • .NET Core 3.1 的REST 和gRPC 性能测试
  • 从对我的质疑说起,谈谈Linux下的文件删除
  • 关于 Blazor Server Side 的一些杂项, 感想
  • 重现江湖!大数据高并发——架构师秘籍
  • ASP.NET Core+Quartz.Net实现web定时任务
  • ASP.NET CORE WEBAPI文件下载
  • .NET Core + Kubernetes:快速体验
  • 海底捞涨价,有错吗?
  • .NET Core + Kubernetes:Pod
  • .NET项目升级手记:可为空引用
  • .NET与鲲鹏共展翅,昇腾九万里(一)
  • 十问十答 Ms-PL 许可证
  • 从案例角度解析建模平台动态规则引擎
  • 想基于K8s按需扩展应用程序,可从这几方面入手
  • EntityFramework Core 3.x上下文构造函数可以注入实例呢?
  • IO 模型知多少
  • 让 .NET 轻松构建中间件模式代码
  • 从编码层面对比java和c#
  • 红帽借“订阅”模式成开源一哥,首创者升任总裁
  • C#两大知名Redis客户端连接哨兵集群的姿势
  • dotNET Core 3.X 请求处理管道和中间件的理解

[ASP.NET Core MVC] 如何实现运行时动态定义Controller类型?相关推荐

  1. SAP UI5 应用开发教程之五十八 - 使用工厂方法在运行时动态创建不同类型的列表行项目控件试读版

    一套适合 SAP UI5 初学者循序渐进的学习教程 教程目录 SAP UI5 本地开发环境的搭建 SAP UI5 应用开发教程之一:Hello World SAP UI5 应用开发教程之二:SAP U ...

  2. 使用asp.net Core MVC codeFirst创建表时出的错误总结

    1 "EntityTypeBuilder"未包含"ToTable"的定义,并且找不到可接受第一个"EntityTypeBuilder"类型参 ...

  3. SAP UI5 应用开发教程之五十八 - 使用工厂方法在运行时动态创建不同类型的列表行项目控件

    本教程在之前的第 17 个步骤,学习了列表控件的使用方法: SAP UI5 应用开发教程之十七 - 聚合绑定在 UI5 复合控件中的使用 如下图所示,列表里每个行项目,都是使用同一种 SAP UI5 ...

  4. 从零开始实现ASP.NET Core MVC的插件式开发(三) - 如何在运行时启用组件

    标题:从零开始实现ASP.NET Core MVC的插件式开发(三) - 如何在运行时启用组件 作者:Lamond Lu 地址:https://www.cnblogs.com/lwqlun/p/112 ...

  5. ASP.NET Core MVC 直接运行报错:对象不支持“addEventListener”属性或方法

    场景:第一次创建了ASP.NET Core MVC项目,我们知道,什么都不动,就可以运行的,出来的是Core2.0的页面,类似于.NetFramework创建MVC一样,就在此时,如果你的默认的调式浏 ...

  6. ASP.NET Core MVC 模型绑定用法及原理

    前言 查询了一下关于 MVC 中的模型绑定,大部分都是关于如何使用的,以及模型绑定过程中的一些用法和概念,很少有关于模型绑定的内部机制实现的文章,本文就来讲解一下在 ASP.NET Core MVC ...

  7. 从零开始实现ASP.NET Core MVC的插件式开发(五) - 插件的删除和升级

    标题:从零开始实现ASP.NET Core MVC的插件式开发(五) - 使用AssemblyLoadContext实现插件的升级和删除 作者:Lamond Lu 地址:https://www.cnb ...

  8. 从零开始实现ASP.NET Core MVC的插件式开发(四) - 插件安装

    标题:从零开始实现ASP.NET Core MVC的插件式开发(四) - 插件安装 作者:Lamond Lu 地址:https://www.cnblogs.com/lwqlun/p/11343141. ...

  9. ASP.NET Core MVC压缩样式、脚本及总是复制文件到输出目录

    前言 在.NET Core之前对于压缩样式文件和脚本我们可能需要借助第三方工具来进行压缩,但在ASP.NET MVC Core中则无需借助第三方工具来完成,本节我们来看看ASP.NET Core MV ...

最新文章

  1. ftp服务器搭建(离线安装vsftpd),配置
  2. 高防御服务器与高防御IP之间的关系
  3. 使用Ant实现打包jar包上传到服务器
  4. Linux Centos7网络属性配置
  5. vs2019新建android生成app,VS2017 VS2019创建离线安装包
  6. 机器学习之线性回归的改进-岭回归
  7. 辽宁师范大学海华学院计算机科学与技术,辽宁师范大学海华学院计算机科学与技术专业综合评价简况表.doc...
  8. 华为p40为何没有搭载鸿蒙系统?
  9. 64 位系统 vs2013 配置 OpenCV-3.1.0
  10. size_t 和int 无符号整型和有符号整型
  11. 双屏鼠标经常跑到副屏_双屏游戏本什么体验?上手ROG冰刃双屏:效率直接拉满...
  12. 在一个数组中找到第k小的数(线性时间选择)
  13. 联想y50更换固态硬盘_联想y50怎么加固态硬盘而不换原来的机器硬盘?
  14. 如何同时使用双网卡进行两个网络上网
  15. MDI Jade6的安装(含ocx控件的安装、PDF索引建立、修改注册表)
  16. 基于单片机的RFID刷卡门禁电路设计(#0206)
  17. 新手如何起步做好微商,微商如何加精准好友?微商如何选产品!如何让别人主动加微信,怎么让别人主动加我微信!
  18. 微型计算机接口课程设计报告,《微机接口技术》课程设计报告(范文).doc
  19. 微信小程序中实现瀑布流
  20. outlook2007 菜单灰色...不能使用的解决方案

热门文章

  1. object-c 入门基础篇
  2. 开发Teams Tabs应用程序
  3. t30智能插座怎么设置_如何设置ConnectSense智能插座
  4. C++--day05
  5. Maven发布工程到私服
  6. 光纤熔接过程详细说明
  7. centos 新建swap区文件
  8. 章鱼黑的第一篇博客~
  9. 3. 视图数据View Data和Balde模版 - Laravel从零开始教程
  10. Codeforces Round #330 (Div. 2) B. Pasha and Phone 容斥定理