四、让应用感知到变化
DynamicActionProvider 解决了将提供的源代码向对应ActionDescriptor列表的转换,但是MVC默认情况下对提供的ActionDescriptor对象进行了缓存。如果框架能够使用新的ActionDescriptor对象,需要告诉它当前应用提供的ActionDescriptor列表发生了改变,而这可以利用自定义的IActionDescriptorChangeProvider来实现。为此我们定义了如下这个DynamicChangeTokenProvider类型,该类型实现了IActionDescriptorChangeProvider接口,并利用GetChangeToken方法返回IChangeToken对象通知MVC框架当前的ActionDescriptor已经发生改变。从实现实现代码可以看出,当我们调用NotifyChanges方法的时候,状态改变通知会被发出去。
public class DynamicChangeTokenProvider : IActionDescriptorChangeProvider
{
private CancellationTokenSource _source;
private CancellationChangeToken _token;
public DynamicChangeTokenProvider()
{
_source = new CancellationTokenSource();
_token = new CancellationChangeToken(_source.Token);
}
public IChangeToken GetChangeToken() => _token;
public void NotifyChanges()
{
var old = Interlocked.Exchange(ref _source, new CancellationTokenSource());
_token = new CancellationChangeToken(_source.Token);
old.Cancel();
}
}
五、应用构建
到目前为止,核心的两个类型DynamicActionProvider和DynamicChangeTokenProvider已经定义好了,接下来我们按照如下的方式将它们注册到MVC应用的依赖注入框架中。
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("/")]
public IActionResult Index() => View();
[HttpPost("/")]
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);
}
}
}








