从接口摘要可以看到,这个接口允许自定义 ControllerModel 对象,而如何自定义内容正是通过 Apply 方法来实现,这个方法提供了当前 ControllerModel 对象的实例,我们可以在它身上获取到的东西实在太多了,看看它包含些什么:

有了这些,我们可以做很多很灵活的操作,例如通过设置 ControllerName 字段强制更改控制器的名称让程序中写死的控制器名失效,也可以通过 Filters 字段动态更新它的过滤器集合,通过 RouteValues 来更改路由规则等等。
说到这里,很多人会觉得这玩意儿和自定义过滤器看起来差不多,最开始我也这么认为,但经过实际代码调试我发现它的生命周期要比过滤器早的多,或者说根本无法比较, 这个家伙只需要在应用启动时执行一次并不用随着每次请求而执行 。也就是说,它的执行时间比激活控制器还要早,那时候根本没有过滤器什么事儿,它的调用是发生在 app.UseEndpoints() 。
回到最开始的需求。基于上面的介绍,我们可以自定义如下的约定:
public class ApiControllerAuthorizeConvention : IControllerModelConvention
{
public void Apply(ControllerModel controller)
{
if (controller.Filters.Any(x => x is ApiControllerAttribute) && !controller.Filters.Any(x => x is AccessControlFilter))
{
controller.Filters.Add(new AccessControlAttribute());
}
}
}
上面的主要思路就是通过判断控制器本身的过滤器集合是否包含 ApiControllerAttribute 来识别是否API Controller,如果是API Controller并且没有标记过 AccessControlAttribute 的话就新建一个实例加入进去。
那么如何把这个约定注册到应用中呢?在Microsoft.AspNetCore.Mvc.MvcOptions中提供了 Conventions 属性:
//
// 摘要:
// Gets a list of Microsoft.AspNetCore.Mvc.ApplicationModels.IApplicationModelConvention
// instances that will be applied to the Microsoft.AspNetCore.Mvc.ApplicationModels.ApplicationModel
// when discovering actions.
public IList<IApplicationModelConvention> Conventions { get; }
通过操作它就能把自定义约定注入进去:
services.AddMvc(options =>
{
options.Conventions.Add(new ApiControllerAuthorizeConvention());
})
细心的人会发现,Conventions是一个 IApplicationModelConvention 类型的集合,而我们自定义的Convention是一个 IControllerModelConvention ,正常来说应该会报错才对?原因是Asp.Net Core的DI框架帮我们提供了一系列扩展方法来简化Convention的添加不用自己再去转换:








