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

2020-06-11 16:00 来源:易采站长站 作者:王冬梅 点击: 评论:

A-A+

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

昨天有个朋友在微信上问我一个问题:他希望通过动态脚本的形式实现对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委托表示)之间的映射。

【易采站长站编辑:秋军】