從編程的角度來看,一個MVC應用是由一系列Controller類型構建而成的,所以對於一個代表應用模型的ApplicationModel對象來說,它的核心就是Controllers屬性返回的一組ControllerModel對象,每個ControllerModel對象是應用模型針對Controller類型的描述。
一、ControllerModel
二、 實例演示:Controller模型的構建
三、實例演示:定製Controller模型
一、ControllerModel
描述Controller類型的ControllerModel具有如下定義。該類型的Application屬性返回作爲當前應用模型的ApplicationModel對象。它的Actions屬性返回的ActionModel是對所有定義在當前Controller類型中的Action方法的描述。描述Controller類型屬性的PropertyModel對象則存放在ControllerProperties屬性中,由於PropertyModel和描述Action方法參數的ParameterModel對象承載的都是服務於模型綁定的元數據,所以我們會將這兩個類型的介紹放在一起。ControllerModel類型的Selectors屬性返回的一組SelectorModel對象是對應用在Controller級別上的Action選擇器的描述,我們會在後續內容中對SelectorModel對象進行單獨介紹。
public class ControllerModel : ICommonModel, IFilterModel, IApiExplorerModel { public ApplicationModel Application { get; set; } public IList<ActionModel> Actions { get; } public IList<PropertyModel> ControllerProperties { get; } public IList<SelectorModel> Selectors { get; } public IDictionary<object, object> Properties { get; } public IList<IFilterMetadata> Filters { get; } public ApiExplorerModel ApiExplorer { get; set; } public TypeInfo ControllerType { get; } public IReadOnlyList<object> Attributes { get; } public string ControllerName { get; set; } public string DisplayName { get; } public IDictionary<string, string> RouteValues { get; } MemberInfo ICommonModel.MemberInfo { get; } string ICommonModel.Name { get; } }
ControllerModel類型同時實現了ICommonModel、IFilterModel和IApiExplorerModel接口。默認註冊的DefaultApplicationModelProvider會對ControllerModel對象做如下的設置:ControllerType和MemberInfo屬性會設置爲當前Controller的類型,該類型名稱去除“Controller”後綴的字符串會作爲Name和ControllerName的屬性值。通過標註的特性註冊到Controller類型上的過濾器會被提取出來,對應的元數據會添加到Filters屬性中。ApiExplorer屬性返回的ApiExplorerModel對象由標註在Controller類型上實現了IApiDescriptionGroupNameProvider和IApiDescriptionVisibilityProvider接口的特性構建而成。
DefaultApplicationModelProvider還會將標註到Controller類型上的所有特性提取出來,並將它們添加到Attributes屬性中。如果特性類型實現了IRouteTemplateProvider接口,它們將專門用來構建特性路由信息或者路由約束,所以它們會從此列表中移除。DisplayName屬性返回的顯示名稱通過對類型名稱作相應格式化生成。DefaultApplicationModelProvider還會提取標註在Controller類型上實現了IRouteValueProvider接口的特性,並利用對應的設置來填充RouteValues屬性返回的路由參數。目前唯一實現了該接口的是如下這個用來設置Area名稱的AreaAttribute特性,設置的路由參數名稱爲“area”。
public interface IRouteValueProvider { string RouteKey { get; } string RouteValue { get; } } [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple=true, Inherited=true)] public abstract class RouteValueAttribute : Attribute, IRouteValueProvider { public string RouteKey { get; } public string RouteValue { get; } protected RouteValueAttribute(string routeKey, string routeValue); } [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple=false, Inherited=true)] public class AreaAttribute : RouteValueAttribute { public AreaAttribute(string areaName) : base("area", areaName) {} }
二、 實例演示:Controller模型的構建
我們照例通過一個簡單的實例來演示應用模型中用以描述Controller的元數據採用的默認構建規則。我們在前面演示程序中定義如下這個測試Controller類型FoobarController。如代碼片段所示,FoobarController類型上標註了三個特性,分別是用來指定Area的AreaAttribute、過濾器特性FoobarAttribute和設置ApiExplorer的ApiExplorerSettingsAttribute。FoobarController類型中定義了兩個屬性(A和B)和兩個Action方法(Foo和Bar)。
[Area("test")] [Foobar] [ApiExplorerSettings(GroupName = "test")] public class FoobarController { public string A { get; set; } public string B { get; set; } [HttpGet("foo")] public void Foo() => throw new NotImplementedException(); [HttpGet("bar")] public void Bar() => throw new NotImplementedException(); }
爲了在頁面上呈現描述FoobarController類型的ControllerModel對象的相關信息,我們對定義在HomeControllere中的Action方法Index作了相應的修改。如下面的代碼片段所示,我們利用在方法中注入的ApplicationModelProducer對象根據FoobarController類型創建一個ApplicationModel對象,並將包含在該對象中用來描述FoobarController的ControllerModel作爲Model呈現成默認的View中。
public class HomeController: Controller { [HttpGet("/")] public IActionResult Index([FromServices]ApplicationModelProducer producer) { var applicationModel = producer.Create(typeof(FoobarController)); return View(applicationModel.Controllers.Single()); } }
我們最後對Action方法Index對應的View文件作相應的修改。如下面的代碼片段所示,這是一個Model類型爲ControllerModel的強類型View,,它將ControllerModel承載的元數據呈現在一個表格中。
@using Microsoft.AspNetCore.Mvc.ApplicationModels; @model ControllerModel @{ var commonModel = (ICommonModel)Model; var actions = Model.Actions; var filters = Model.Filters; var properties = Model.ControllerProperties; var attributes = Model.Attributes; var routeValues = Model.RouteValues.ToArray(); } <html> <head> <title>Controller</title> </head> <body> <table border="1" cellpadding="0" cellspacing="0"> <tr><td>ControllerType </td><td>@Model.ControllerType.Name</td></tr> <tr><td>ControllerName </td><td>@Model.ControllerName</td></tr> <tr><td>Name </td><td>@commonModel.Name</td></tr> <tr><td>DisplayName </td><td>@Model.DisplayName</td></tr> <tr><td rowspan="@actions.Count">Actions</td><td>@actions[0].ActionName</td></tr> @for (int index = 1; index < actions.Count; index++) { <tr><td>@actions[index].ActionName</td></tr> } <tr> <td rowspan="@filters.Count">Filters</td><td>@filters[0].GetType().Name</td> </tr> @for (int index = 1; index < filters.Count; index++) { <tr><td>@filters[index].GetType().Name</td></tr> } <tr> <td rowspan="@properties.Count">ControllerProperties</td> <td>@properties[0].PropertyName</td> </tr> @for (int index = 1; index < properties.Count; index++) { <tr><td>@properties[index].PropertyName</td></tr> } <tr> <td rowspan="@attributes.Count">Attributes</td> <td>@attributes[0].GetType().Name</td> </tr> @for (int index = 1; index < attributes.Count; index++) { <tr><td>@attributes[index].GetType().Name</td></tr> } <tr> <td rowspan="@routeValues.Length">RouteVlues</td> <td>@routeValues[0].Key = @routeValues[0].Value</td> </tr> @for (int index = 1; index < routeValues.Length; index++) { <tr><td>@routeValues[index].Key = @routeValues[index].Value</td></tr> } <tr> <td rowspan="2">ApiExplorer</td> <td>IsVisible = @Model.ApiExplorer.IsVisible </td> </tr> <tr><td>GroupName = @Model.ApiExplorer.GroupName </td></tr> </table> </body> </html>
改動後的演示程序啓動後,我們利用瀏覽器訪問應用的主頁,可以得到如圖1所示的輸出結果。正如上面我們所說的,去除“Controller”字符後綴的類型名稱成爲了ControllerModel對象的Name和ControllerName的屬性值(“Foobar”)。兩個屬性(A和B)和Action方法(Foo和Bar)轉換成相應的PropertyModel和ActionModel對象並分別添加到ControllerModel對象的ControllerProperties和Actions屬性中。通過標註特性註冊的過濾器(FoobarAttribute)被添加到ControllerModel對象的Filters屬性中。通過標註的AreaAttribute設置的Area名稱最終轉移到ControllerModel對象對象RouteValues屬性中。ControllerModel對象的ApiExplorer屬性返回的ApiExplorerModel對象很明顯是通過標註在類型上的ApiExplorerSettingsAttribute特性創建的,而它的Attributes屬性中包含了我們標註的三個特性。
圖1Controller模型默認的構建規則
三、實例演示:定製Controller模型
通過前面介紹的針對應用模型的總體設計,我們知道針對Controller模型的定製可以通過自定義的IControllerModelConvention實現類型來實現,我們接下來利用這種方式來改變Controller默認的命名規則。我們在上面演示的程序中定義瞭如下這個ControllerNameAttribute特性,該特性類型實現了IControllerModelConvention接口,在實現的Apply方法中,我們將構造函數中設置的Controller名稱應用到提供的ControllerModel對象上。我們將該特性標註到FoobarController類型上並將名稱設置爲“Baz”。
[AttributeUsage(AttributeTargets.Class)] public class ControllerNameAttribute : Attribute, IControllerModelConvention { public string ControllerName { get; } public ControllerNameAttribute(string name) => ControllerName = name; public void Apply(ControllerModel controller) => controller.ControllerName = ControllerName; } [Area("test")] [Foobar] [ApiExplorerSettings(GroupName = "test")] [ControllerName("Baz")] public class FoobarController { … }
改動後的演示程序啓動後,我們利用瀏覽器訪問應用的主頁,可以得到如圖2所示的輸出結果。我們從圖中可以看出,對於最終生成的用來描述FoobarController類型的ControllerModel對象來說,它的ControllerName屬性被設置成我們指定的名稱“Baz”,它的Name屬性(ControllerModel類型針對ICommonModel接口的實現)返回的就是ControllerName屬性值。
圖2 自定義IControllerModelConvention實現類型定製Controller模型
ASP.NET Core MVC應用模型的構建[1]: 應用的藍圖
ASP.NET Core MVC應用模型的構建[2]: 應用模型
ASP.NET Core MVC應用模型的構建[3]: Controller模型
ASP.NET Core MVC應用模型的構建[4]: Action模型