ASP.NET Core MVC應用模型的構建[3]: Controller的收集

從編程的角度來看,一個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屬性中包含了我們標註的三個特性。

clip_image002

圖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屬性值。

clip_image004

圖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模型

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章