概念

ASP.NET Core MVC 会定义一个 应用程序模型,用于表示 MVC 应用的各个组件。 读取并处理此模型,以修改 MVC 元素的行为方式。 默认情况下,MVC 遵循特定的约定来确定哪些类被视为控制器、这些类上的哪些方法是操作以及参数和路由的行为方式。 自定义此行为以满足应用程序的需求,方法是创建自定义约定,并将其作为全局或特性应用。

模型(Model)和程序提供器(Provider)

对象模型(Model)

ASP.NET Core MVC 应用程序模型包括用于描述 MVC 应用程序的抽象接口和具体实现类。 此模型是 MVC 根据默认约定发现应用的控制器、操作、操作参数、路由和筛选器的结果。 通过使用应用程序模型,修改应用以遵循默认 MVC 行为中的不同约定。 参数、名称、路由和筛选器都用作操作和控制器的配置数据。
ASP.NET Core MVC 应用程序模型具有以下结构:

  • ApplicationModel
    • 控制器 (ControllerModel)
      • 属性 (PropertyModel)
    • 操作 (ActionModel)
    • 参数 (ParameterModel)

程序提供器(IApplicationModelProvider)

  • IApplicationModelProvider
    • 默认提供器 (DefaultApplicationModelProvider)
    • 权限相关提供器 (AuthorizationApplicationModelProvider)
    • 跨域相关提供器 (CorsApplicationModelProvider)

DefaultApplicationModelProvider 建立了由 ASP.NET Core MVC 使用的许多默认行为。 其职责包括:

  • 将全局筛选器添加到上下文
  • 将控制器添加到上下文
  • 将公共控制器方法作为操作添加
  • 将操作方法参数添加到上下文
  • 应用路由和其他属性

AuthorizationApplicationModelProvider 负责应用与 AuthorizeFilter 和 AllowAnonymousFilter 属性关联的行为。

CorsApplicationModelProvider 与和关联的实现 IEnableCorsAttribute 行为 IDisableCorsAttribute 。

约定(Convention)

应用程序模型定义了约定抽象,通过约定抽象来自定义模型行为比重写整个模型或提供程序更简单。 建议使用这些抽象方法修改应用的行为。 约定提供一种方法来编写动态应用自定义项的代码。 尽管 筛选器 提供了修改框架行为的方法,但自定义允许控制整个应用程序的工作方式。

可用约定如下:

  • IApplicationModelConvention
  • IControllerModelConvention
  • IActionModelConvention
  • IParameterModelConvention

工作原理

ASP.NET Core MVC 使用接口定义的提供程序模式加载应用程序模型 IApplicationModelProvider 。 此部分介绍此提供程序的工作原理的一些内部实现细节。 使用提供程序模式是一种高级主题,主要用于框架使用。 大多数应用程序应使用约定,而不是提供程序模式。

IApplicationModelProvider接口有两个方法OnProvidersExecuting和OnProvidersExecuted以及属性Order。

接口的每个实现OnProvidersExecuting方法基于其属性Order以升序调用。然后,按照相反的顺序调用OnProvidersExecuted方法。

首先(Order=-1000):

  • DefaultApplicationModelProvider

然后(Order=-990):

  • AuthorizationApplicationModelProvider
  • CorsApplicationModelProvider

最后(Order=-900):

  • ApiBehaviorApplicationModelProvider 执行约定,

ApplicationModelFactory对象内有IApplicationModelProvider数组和IApplicationModelConvention集合。统一构建ApplicationModel、ControllerModel、ActionModel、ParameterModel等对象

用法

修改ApplicationModel

以下约定用于向应用程序模型添加属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
using Microsoft.AspNetCore.Mvc.ApplicationModels;

namespace AppModelSample.Conventions
{
public class ApplicationDescription : IApplicationModelConvention
{
private readonly string _description;

public ApplicationDescription(string description)
{
_description = description;
}

public void Apply(ApplicationModel application)
{
application.Properties["description"] = _description;
}
}
}

当在中添加 MVC 时,应用程序模型约定作为选项应用 Startup.ConfigureServices :

1
2
3
4
5
6
7
8
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options =>
{
options.Conventions.Add(new ApplicationDescription("My Application Description"));
options.Conventions.Add(new NamespaceRoutingConvention());
});
}

修改 ControllerModel 说明

控制器模型还可以包含自定义属性。 自定义属性将覆盖在应用程序模型中指定的同名的现有属性。 以下约定属性可在控制器级别添加说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
using System;
using Microsoft.AspNetCore.Mvc.ApplicationModels;

namespace AppModelSample.Conventions
{
public class ControllerDescriptionAttribute : Attribute, IControllerModelConvention
{
private readonly string _description;

public ControllerDescriptionAttribute(string description)
{
_description = description;
}

public void Apply(ControllerModel controllerModel)
{
controllerModel.Properties["description"] = _description;
}
}
}

此约定作为控制器上的属性应用:

1
2
3
4
5
6
7
8
[ControllerDescription("Controller Description")]
public class DescriptionAttributesController : Controller
{
public string Index()
{
return "Description: " + ControllerContext.ActionDescriptor.Properties["description"];
}
}

修改 ActionModel 说明

可以将单独的特性约定应用于单个操作,重写已应用于应用程序或控制器级别的行为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
using System;
using Microsoft.AspNetCore.Mvc.ApplicationModels;

namespace AppModelSample.Conventions
{
public class ActionDescriptionAttribute : Attribute, IActionModelConvention
{
private readonly string _description;

public ActionDescriptionAttribute(string description)
{
_description = description;
}

public void Apply(ActionModel actionModel)
{
actionModel.Properties["description"] = _description;
}
}
}

修改 ParameterModel

可将以下约定应用于操作参数,以修改其 BindingInfo。 以下约定要求参数是路由参数。 其他可能的绑定源(如查询字符串值)将被忽略:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using System;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.ModelBinding;

namespace AppModelSample.Conventions
{
public class MustBeInRouteParameterModelConvention : Attribute, IParameterModelConvention
{
public void Apply(ParameterModel model)
{
if (model.BindingInfo == null)
{
model.BindingInfo = new BindingInfo();
}
model.BindingInfo.BindingSource = BindingSource.Path;
}
}
}

该属性可应用于任何操作参数:

1
2
3
4
5
6
7
8
9
public class ParameterModelController : Controller
{
// Will bind: /ParameterModel/GetById/123
// WON'T bind: /ParameterModel/GetById?id=123
public string GetById([MustBeInRouteParameterModelConvention]int id)
{
return $"Bound to id: {id}";
}
}

若要将约定应用于所有操作参数,请将添加 MustBeInRouteParameterModelConvention 到 MvcOptions 中的 Startup.ConfigureServices :

1
options.Conventions.Add(new MustBeInRouteParameterModelConvention());

修改 ActionModel 名称

以下约定可修改 ActionModel,以更新其应用到的操作的 名称。 新名称以参数形式提供给该属性。 路由使用此新名称,因此它会影响用于访问此操作方法的路由:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
using System;
using Microsoft.AspNetCore.Mvc.ApplicationModels;

namespace AppModelSample.Conventions
{
public class CustomActionNameAttribute : Attribute, IActionModelConvention
{
private readonly string _actionName;

public CustomActionNameAttribute(string actionName)
{
_actionName = actionName;
}

public void Apply(ActionModel actionModel)
{
// this name will be used by routing
actionModel.ActionName = _actionName;
}
}
}

此属性应用于 HomeController 中的操作方法:

1
2
3
4
5
6
// Route: /Home/MyCoolAction
[CustomActionName("MyCoolAction")]
public string SomeName()
{
return ControllerContext.ActionDescriptor.ActionName;
}

即使方法名称为 SomeName,该属性也会覆盖 MVC 使用该方法名称的约定,并将操作名称替换为 MyCoolAction。 因此,用于访问此操作的路由为 /Home/MyCoolAction。官方提供了类似功能的特性 ActionNameAttribute 。

自定义路由约定

使用 IApplicationModelConvention 自定义路由的工作方式。 例如,以下约定将控制器的命名空间合并到它们的路由中,并将命名空间中的替换为 . / 路由:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using System.Linq;

namespace AppModelSample.Conventions
{
public class NamespaceRoutingConvention : IApplicationModelConvention
{
public void Apply(ApplicationModel application)
{
foreach (var controller in application.Controllers)
{
var hasAttributeRouteModels = controller.Selectors
.Any(selector => selector.AttributeRouteModel != null);

if (!hasAttributeRouteModels
&& controller.ControllerName.Contains("Namespace")) // affect one controller in this sample
{
// Replace the . in the namespace with a / to create the attribute route
// Ex: MySite.Admin namespace will correspond to MySite/Admin attribute route
// Then attach [controller], [action] and optional {id?} token.
// [Controller] and [action] is replaced with the controller and action
// name to generate the final template
controller.Selectors[0].AttributeRouteModel = new AttributeRouteModel()
{
Template = controller.ControllerType.Namespace.Replace('.', '/') + "/[controller]/[action]/{id?}"
};
}
}

// You can continue to put attribute route templates for the controller actions depending on the way you want them to behave
}
}
}

此约定作为选项添加到 Startup.ConfigureServices :

1
2
3
4
5
6
7
8
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options =>
{
options.Conventions.Add(new ApplicationDescription("My Application Description"));
options.Conventions.Add(new NamespaceRoutingConvention());
});
}

下面的示例将约定应用于不使用属性路由的路由,其中控制器的 Namespace 名称如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
using Microsoft.AspNetCore.Mvc;

namespace AppModelSample.Controllers
{
public class NamespaceRoutingController : Controller
{
// using NamespaceRoutingConvention
// route: /AppModelSample/Controllers/NamespaceRouting/Index
public string Index()
{
return "This demonstrates namespace routing.";
}
}
}