作者:Steve Smith
ASP.NET Core MVC 定義代表MVC應用程式元件 的應用程式模型 。 查看並操作此模型以修改 MVC 元素的行為。 根據預設,MVC 會遵循某些慣例來判斷哪些類別會被視為控制器、這些類別上的哪些方法是動作,以及參數和路由的行為。 藉由建立自定義慣例並將其套用為屬性,自定義此行為以符合應用程式的需求。
模型和提供者 (IApplicationModelProvider)
ASP.NET Core MVC 應用程式模型同時包含描述MVC應用程式的抽象介面和具體實作類別。 此模型是MVC根據預設慣例探索應用程式控制器、動作、動作參數、路由和篩選的結果。 藉由使用應用程式模型,修改應用程式以遵循預設MVC行為的不同慣例。 參數、名稱、路由和篩選全都做為動作和控制器的設定數據。
ASP.NET Core MVC 應用程式模型具有下列結構:
- ApplicationModel
- 控制器 (ControllerModel)
- Actions (ActionModel)
- 參數 (ParameterModel)
- Actions (ActionModel)
- 控制器 (ControllerModel)
模型的每個層級都可以存取通用 Properties 集合,而較低層級可以存取和覆寫階層中較高層級所設定的屬性值。 當動作被建立時,屬性會被儲存至 ActionDescriptor.Properties。 然後,處理要求時,可以透過 ActionContext.ActionDescriptor存取加入或修改慣例的任何屬性。 使用屬性是依據每個動作設定篩選、模型系結器和其他應用程式模型層面的絕佳方式。
備註
在ActionDescriptor.Properties應用程式啟動之後,集合在寫入操作時不具備執行緒安全性。 慣例是安全地將數據新增至此集合的最佳方式。
ASP.NET Core MVC 會使用 介面所 IApplicationModelProvider 定義的提供者模式載入應用程式模型。 本節涵蓋此提供者運作方式的一些內部實作詳細數據。 使用供應者模式是一個進階議題,主要是用於架構。 大部分的應用程式都應該使用慣例,而不是提供者模式。
IApplicationModelProvider 介面的各個實作會相互「封裝」,其中每個實作會根據其 OnProvidersExecuting 屬性以遞增順序呼叫 Order。 然後將以反向順序呼叫 OnProvidersExecuted 方法。 架構會定義數個提供者:
第一個 (Order=-1000):
DefaultApplicationModelProvider
然後 (Order=-990):
AuthorizationApplicationModelProviderCorsApplicationModelProvider
備註
具有相同 Order 值的兩個提供者的呼叫順序是未定義的,因此不應該依賴。
備註
IApplicationModelProvider 是架構作者要擴充的進階概念。 一般而言,應用程式應該使用慣例,而架構應該使用提供者。 主要區別在於提供者一律會在慣例之前執行。
建立 DefaultApplicationModelProvider ASP.NET Core MVC 所使用的許多預設行為。 其責任包括:
- 將全域篩選器新增至環境
- 將控制器新增至內容
- 將公用控制器方法新增為動作
- 將動作方法參數新增至內容
- 套用路由設定和其他屬性
某些內建行為是由 DefaultApplicationModelProvider 實現的。 此提供者負責建構 ControllerModel,而後者接著會參考 ActionModel、 PropertyModel和 ParameterModel 實例。 類別 DefaultApplicationModelProvider 是未來可能會變更的內部架構實作詳細數據。
AuthorizationApplicationModelProvider負責套用與 AuthorizeFilter 和 AllowAnonymousFilter 屬性相關聯的行為。 如需詳細資訊,請參閱 ASP.NET Core 中的簡單授權。
CorsApplicationModelProvider 實作與 IEnableCorsAttribute 和 IDisableCorsAttribute 相關聯的行為。 如需詳細資訊,請參閱 在 ASP.NET Core 中啟用跨原始來源要求 (CORS)。
本節所述的架構內部提供者信息無法透過 .NET API 瀏覽器取得。 不過,您可以在 dotnet/aspnetcore GitHub 存放庫中的 ASP.NET Core 參考來源中檢查提供者。 使用 GitHub 搜尋依名稱尋找提供者,並使用 [切換分支/標籤 ] 下拉式清單選取來源版本。
慣例
應用程式模型會定義一些慣例的抽象概念,提供一種比覆寫整個模型或提供者更簡單的方式來自訂模型的行為。 這些抽象概念是修改應用程式行為的建議方式。 慣例提供一種方式來撰寫動態套用自定義的程序代碼。 雖然 篩選 條件提供修改架構行為的方法,但自定義可控制整個應用程式的運作方式。
下列慣例可供使用:
- IApplicationModelConvention
- IControllerModelConvention
- IActionModelConvention
- IParameterModelConvention
將慣例新增至MVC選項或實作屬性,並將其套用至控制器、動作或動作參數,以套用慣例(類似於 篩選條件)。不同於篩選,只有在應用程式啟動時才會執行慣例,而不是作為每個要求的一部分。
備註
如需頁面路由和應用程式模型提供者慣例的相關信息 Razor ,請參閱 Razor ASP.NET Core 中的頁面路由和應用程式慣例。
修改 ApplicationModel
下列慣例是用來將屬性新增至應用程式模型:
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;
}
}
}
在 中 Startup.ConfigureServices新增 MVC 時,應用程式模型慣例會套用為選項:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options =>
{
options.Conventions.Add(new ApplicationDescription("My Application Description"));
options.Conventions.Add(new NamespaceRoutingConvention());
});
}
屬性可從控制器動作內的集合存取 ActionDescriptor.Properties :
public class AppModelController : Controller
{
public string Description()
{
return "Description: " + ControllerContext.ActionDescriptor.Properties["description"];
}
}
修改ControllerModel的描述
控制器模型也可以包含自定義屬性。 自訂屬性會取代應用程式模型中指定的具有相同名稱的現有屬性。 下列慣例屬性會在控制器層級新增描述:
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;
}
}
}
此慣例會套用為控制器上的屬性:
[ControllerDescription("Controller Description")]
public class DescriptionAttributesController : Controller
{
public string Index()
{
return "Description: " + ControllerContext.ActionDescriptor.Properties["description"];
}
修改ActionModel的描述
不同的屬性慣例可以套用至個別動作,覆蓋已在應用程式或控制器層級套用的行為:
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;
}
}
}
將此套用至控制器中的動作,以示範其如何覆蓋控制器層級慣例:
[ControllerDescription("Controller Description")]
public class DescriptionAttributesController : Controller
{
public string Index()
{
return "Description: " + ControllerContext.ActionDescriptor.Properties["description"];
}
[ActionDescription("Action Description")]
public string UseActionDescriptionAttribute()
{
return "Description: " + ControllerContext.ActionDescriptor.Properties["description"];
}
}
修改 ParameterModel
下列慣例可以套用至動作參數,以修改其 BindingInfo。 下列慣例要求參數為路由參數。 其他潛在的系結來源,例如查詢字串值,會被忽略:
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;
}
}
}
屬性可以套用至任何動作參數:
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:
options.Conventions.Add(new MustBeInRouteParameterModelConvention());
修改ActionModel名稱
下列慣例會修改 ActionModel ,以更新其所套用的動作 名稱。 新名稱會以 屬性的參數的形式提供。 路由會使用這個新名稱,因此會影響用來觸達此動作方法的路由:
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 中的動作方法。
// Route: /Home/MyCoolAction
[CustomActionName("MyCoolAction")]
public string SomeName()
{
return ControllerContext.ActionDescriptor.ActionName;
}
即使方法名稱是 SomeName,屬性仍會覆寫使用方法名稱的MVC慣例,並將動作名稱取代為 MyCoolAction。 因此,用來連線此動作的路由為 /Home/MyCoolAction。
備註
本節中的這個範例基本上與內建ActionNameAttribute的使用相同。
自定義路由慣例
使用IApplicationModelConvention來自訂義路由運作的方式。 例如,下列慣例會將控制器的命名空間納入其路由中,並在路由中將命名空間的 . 替換為 /:
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中增加此協定作為選項:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options =>
{
options.Conventions.Add(new ApplicationDescription("My Application Description"));
options.Conventions.Add(new NamespaceRoutingConvention());
});
}
小提示
使用下列方法將慣例新增至中間件MvcOptions。
{CONVENTION} 佔位符是新增的慣例:
services.Configure<MvcOptions>(c => c.Conventions.Add({CONVENTION}));
下列範例會將慣例套用至未使用屬性路由且控制器名稱包含 Namespace 的路由:
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.";
}
}
}
在WebApiCompatShim中的應用程式模型使用方式
ASP.NET Core MVC 會使用與 ASP.NET Web API 2 不同的一組慣例。 使用自定義慣例,您可以修改 ASP.NET Core MVC 應用程式的行為,以與 Web API 應用程式的行為一致。 Microsoft特別針對此目的提供 WebApiCompatShim NuGet 套件 。
備註
如需從 ASP.NET Web API 移轉的詳細資訊,請參閱 從 ASP.NET Web API 遷移至 ASP.NET Core。
若要使用 Web API 相容性適配器:
- 將
Microsoft.AspNetCore.Mvc.WebApiCompatShim套件新增至專案。 - 在 AddWebApiConventions 中呼叫
Startup.ConfigureServices,將慣例新增至 MVC:
services.AddMvc().AddWebApiConventions();
填充碼所提供的慣例只會套用至已套用特定屬性的應用程式部分。 下列四個屬性用於控制哪些控制器應該由樁的慣例進行修改:
- UseWebApiActionConventionsAttribute
- UseWebApiOverloadingAttribute
- UseWebApiParameterConventionsAttribute
- UseWebApiRoutesAttribute
動作慣例
UseWebApiActionConventionsAttribute 是用來根據 HTTP 方法的名稱將 HTTP 方法對應至動作(例如, Get 會對應至 HttpGet)。 它只適用於不使用屬性路由的動作。
重載
UseWebApiOverloadingAttribute 是用來套用 WebApiOverloadingApplicationModelConvention 慣例。 此慣例會將 新增 OverloadActionConstraint 至動作選取程式,其會將候選動作限製為要求滿足所有非選擇性參數的動作。
參數慣例
UseWebApiParameterConventionsAttribute 是用來套用 WebApiParameterConventionsApplicationModelConvention 動作慣例。 此慣例會指定作為動作參數的簡單型別預設會從 URI 系結,而複雜型別則會從要求主體系結。
路線
UseWebApiRoutesAttribute 控制是否應用 WebApiApplicationModelConvention 控制器慣例。 啟用時,會使用此慣例將 區域 支援新增至路線,並指出控制器位於 api 區域中。
除了一組慣例之外,相容性套件還包含一個 System.Web.Http.ApiController 基類,取代 Web API 所提供的基類。 這可讓您為 Web API 撰寫的 Web API 控制器在 ASP.NET Core MVC 上運行並從 Web API 的 ApiController 繼承而正常運作。
UseWebApi*先前所列的所有屬性都會套用至基底控制器類別。
ApiController 會公開與 Web API 中的屬性、方法和結果類型相容的功能。
使用 ApiExplorer 記錄應用程式
應用程式模型會在 ApiExplorerModel 每個層級公開一個屬性,可用來瀏覽應用程式的結構。 這可用來 使用 Swagger 之類的工具來產生 Web API 的說明頁面。 屬性 ApiExplorer 會使用 IsVisible 屬性來指定應用程式模型中應該被公開的部分。 使用慣例來設定此設定:
using Microsoft.AspNetCore.Mvc.ApplicationModels;
namespace AppModelSample.Conventions
{
public class EnableApiExplorerApplicationConvention : IApplicationModelConvention
{
public void Apply(ApplicationModel application)
{
application.ApiExplorer.IsVisible = true;
}
}
}
使用此方法(如有必要,以及其他慣例),應用程式內的任何層級會啟用或停用 API 可見度。