昨天有个朋友在微信上问我一个问题:他希望通过动态脚本的形式实现对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.mzph.cn/news/310638.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!相关文章
C++ 实现带权有向图的每对顶点之间的最短路径Floyd算法(完整代码)
等额本息,等额本金区别
【视频回放与课件】Build your AI solution with MLOps
C++ 实现分块查找(顺序存储结构)(完整代码)
《五分钟商学院》管理篇学习笔记
如何在.NET应用程序中分析CPU使用率过高的问题
HashMap实现原理
做好技术管理,你必须要跨越的4道槛
图的最小生成树和最短路径算法思路总结(Prim,Kruskal,Dijkstra,Floyd)
玩转控件:对Dev的GridControl控件扩展
二叉排序树(搜索树BST)-详解结点的删除
java调优方法,jvm监控工具
那位五十多岁的创业者给我的启示!
平衡二叉树(AVL树)-详解平衡调整
初识消息队列/RabbitMQ详解
zookeeper理解
那些年,在MSRA实习过的女孩,现在都怎么样了?
Zookeeper理解---ZAB协议
【最强VSCode】之管理MySql数据库
[Flags]标识的Enum不能使用Html.GetEnumSelectList方法
- 计算机网络之---公钥基础设施(PKI)
- 《新闻大厦抢先版》V0.18.105+Dlcs官方学习版
- React 进阶之路:深入详解事件绑定的多样方式与区别,促使更加容易理解
- 大模型WebUI:Gradio全解11——Chatbots:融合大模型的多模态聊天机器人(2)
- 安科瑞 Acrel-1000DP 分布式光伏监控系统在工业厂房分布式光伏发电项目中的应用
- linux上使用cmake编译的方法
- 基于React开发范式的思考:写在Lesx发布之际
- Android 开发使用 Gradle 配置构建库模块的工作方式
- Array 的一些常用 API
- java8-02-Stream-API
- VUE-搜索过滤器
- 从0到1使用VUE-CLI3开发实战(五):模块化VUEX及使用vuetify