由于针对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));
}








