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

public class Program { public static void Main() { Host.CreateDefaultBuilder() .ConfigureWebHostDefaults(web => web .ConfigureServices(svcs => svcs .AddSingleton<ICompiler, Compiler>() .AddSingleton<DynamicActionProvider>() .AddSingleton<DynamicChangeTokenProvider>() .AddSingleton<IActionDescriptorProvider>(provider => provider.GetRequiredService<DynamicActionProvider>()) .AddSingleton<IActionDescriptorChangeProvider>(provider => provider.GetRequiredService<DynamicChangeTokenProvider>()) .AddRouting().AddControllersWithViews()) .Configure(app => app .UseRouting() .UseEndpoints(endpoints => endpoints.MapControllerRoute( name: default, pattern: "{controller}/{action}" )))) .Build() .Run(); } }

然后我们定义了如下这个HomeController。针对GET请求的Index方法会将上图所示的视图呈现出来。当我们点击“Register”按钮之后,提交的源代码会通过针对POST请求的Index方法进行处理。如下面的代码片段所示,在将将提交的源代码作为参数调用了DynamicActionProvider对象的 AddControllers方法之后,我们调用了DynamicChangeTokenProvider对象的 NotifyChanges方法。

public class HomeController : Controller { [HttpGet("https://www.jb51.net/")] public IActionResult Index() => View(); [HttpPost("https://www.jb51.net/")] public IActionResult Index( string source, [FromServices]DynamicActionProvider actionProvider, [FromServices] DynamicChangeTokenProvider tokenProvider) { try { actionProvider.AddControllers(source); tokenProvider.NotifyChanges(); return Content("OK"); } catch (Exception ex) { return Content(ex.Message); } } }

如下所示的是View的定义。

<html> <body> <form method="post"> <textarea cols="50" rows="10">Define your controller here...</textarea> <br/> <button type="submit">Register</button> </form> </body> </html>

六、换一种实现方式

接下来我们提供一种更加简单的解决方案。通过上面的介绍我们知道,用来描述Action方法的ActionDescriptor列表是由一组IActionDescriptorProvider对象提供的,对于针对Controller的MVC编程模型(另一种是针对Razor Page的编程模型)来说,对应的实现类型为ControllerActionDescriptorProvider。

当ControllerActionDescriptorProvider在提供对应ActionDescriptor对象之前,会从作为当前应用组成部分(ApplicationPart)的程序集中解析出所有Controller类型。如果我们能够让动态提供给源代码编程生成的程序集成为其合法的组成部分,那么我们面对的问题自然就能迎刃而解。添加应用组成部分其实很简单,我们只需要按照如下的方式调用ApplicationPartManager对象的Add方法就可以了。为了让MVC框架感知到提供的ActionDescriptor列表已经发生改变,我们还是需要调用DynamicChangeTokenProvider对象的NotifyChanges方法。

public class HomeController : Controller { [HttpGet("https://www.jb51.net/")] public IActionResult Index() => View(); [HttpPost("https://www.jb51.net/")] public IActionResult Index(string source, [FromServices] ApplicationPartManager manager, [FromServices] ICompiler compiler, [FromServices] DynamicChangeTokenProvider tokenProvider) { try { manager.ApplicationParts.Add(new AssemblyPart(compiler.Compile(source, Assembly.Load(new AssemblyName("System.Runtime")), typeof(object).Assembly, typeof(ControllerBase).Assembly, typeof(Controller).Assembly))); tokenProvider.NotifyChanges(); return Content("OK"); } catch (Exception ex) { return Content(ex.Message); } } }

由于我们不在需要自定义的DynamicActionProvider,自然也就不需要对应的服务注册了。

public class Program { public static void Main() { Host.CreateDefaultBuilder() .ConfigureWebHostDefaults(web => web .ConfigureServices(svcs => svcs .AddSingleton<ICompiler, Compiler>() .AddSingleton<DynamicChangeTokenProvider>() .AddSingleton<IActionDescriptorChangeProvider>(provider => provider.GetRequiredService<DynamicChangeTokenProvider>()) .AddRouting().AddControllersWithViews()) .Configure(app => app .UseRouting() .UseEndpoints(endpoints => endpoints.MapControllerRoute( name: default, pattern: "{controller}/{action}" )))) .Build() .Run(); } }

七、这其实不是一个小问题

有人可能觉得上面我们所做的好像只是一些“奇淫巧计”,其实不然,这里涉及到MVC应用一个重大的主题,我个人将它称为“动态模块化”。对于一个面向Controller的MVC应用来说,Controller类型是应用基本的组成单元,所以其应用模型(通过上面提到的ApplicationModel对象表示)呈现出这样的结构:Application->Controller->Action。如果一个MVC应用需要拆分为多个独立的模块,意味着需要将Controller类型分别定义在不同的程序集中。为了让这些程序集成为应用的一个有效组成部分,程序集需要封装成ApplicationPart对象并利用ApplicationPartManager进行注册。针对应用组成部分的注册不是静态的(在应用启动的时候进行),而是动态的(在运行的任意时刻都可以进行)。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wjfsps.html