加入收藏 | 设为首页 | 会员中心 | 我要投稿 东莞站长网 (https://www.0769zz.com/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 站长学院 > Asp教程 > 正文

ASP.NET Core MVC如何实现运行时动态定义Controller类型

发布时间:2020-06-18 11:26:36 所属栏目:Asp教程 来源:站长网
导读:副标题#e# 昨天有个朋友在微信上问我一个问题:他希望通过动态脚本的形式实现对ASP.NET Core MVC应用的扩展,比如在程序运行过程中上传一段C#脚本将其中定义的Controller类型注册到应用中,问我是否有好解决方案。我当时在外边,回复不太方便,所以只给他说
副标题[/!--empirenews.page--]

昨天有个朋友在微信上问我一个问题:他希望通过动态脚本的形式实现对ASP.NET Core MVC应用的扩展,比如在程序运行过程中上传一段C#脚本将其中定义的Controller类型注册到应用中,问我是否有好解决方案。我当时在外边,回复不太方便,所以只给他说了两个接口/类型:IActionDescriptorProvider和ApplicationPartManager。这是一个挺有意思的问题,所以回家后通过两种方案实现了这个需求。源代码从这里下载。

一、实现的效果

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

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

二、动态编译源代码

要实现如上所示的“针对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"); } }

三、自定义IActionDescriptorProvider

解决了针对提供源代码的动态编译问题之后,我们可以获得需要注册的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)); }

四、让应用感知到变化

(编辑:东莞站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

热点阅读