Asp.Net MVC4 系列--進階篇之Controller(2)
呼叫Action的流程
IIS 接收到一個http請求,進入w3wp程式(如果是webgarden,先找到一個壓力小的的w3wp),找到applicationpool,進入global.asax,進入路由,從controllerfactory找到一個controller,如果我們用了預設的controller,會解析出action名稱和引數呼叫action(也可以手動直接在controller處理掉請求),返回並執行result,如圖:
自定義controller factory
在上一章節,介紹瞭如果customize 一個controller(實現IController介面),現在介紹一個如何customizecontroller factory。
介面:
public interface IControllerFactory {
IController CreateController(RequestContextrequestContext,
string controllerName);
SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext,
string controllerName);
void ReleaseController(IControllercontroller);
}
說明:
CreateController : 接收request context和controller name(不包含”Controller”字尾),返回一個IController物件,用於建立Controller物件
GetControllerSessionBehavior:引數同上,返回一個SessionStateBehavior,控制建立出的controller的sessionstate物件
ReleaseController:
如果controller包含了一些需要手動釋放的資源,可以在這個函式裡統一釋放
程式碼示例:
public class CustControllerFactory : IControllerFactory
{
private string_nsFind;
private string_assembly;
public CustControllerFactory(string namespaceFind, string assemblyName)
{
if(namespaceFind.Last() == '.') namespaceFind =namespaceFind.Remove(namespaceFind.Length - 1);
_nsFind =namespaceFind;
_assembly= assemblyName;
}
public IController CreateController(RequestContext requestContext, string controllerName)
{
var fullName = _nsFind + "." + controllerName + "Controller";
var type =GetType().Assembly.GetTypes().FirstOrDefault(t => t.FullName == fullName);
if (type== null)
{
return new TestController();
}
return (IController)Activator.CreateInstance(_assembly, fullName).Unwrap();
}
public SessionStateBehavior GetControllerSessionBehavior(RequestContextrequestContext, string controllerName)
{
return SessionStateBehavior.Default;
}
public void ReleaseController(IController controller)
{
var disposable = controller as IDisposable;
if(disposable != null)
{
disposable.Dispose();
}
}
}
程式碼說明:factory接收一個名稱空間名稱和assembly名稱,在指定的範圍內檢索controller物件通過反射動態建立物件,如果找不到,返回一個預設的controller。
下一步,在global檔案中註冊factory:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
ControllerBuilder.Current.SetControllerFactory(new
CustControllerFactory("MVCRouteStudy.Controllers","MVCRouteStudy"));
}
可以看到,在最後一行,我註冊了我手動建立的factory。
測試一下這個factory,先訪問一個不存在controller:
訪問一個存在的:
注意:此例只作為一個簡單的實現,甚至沒有捕捉異常。現實應用中,場景要複雜的多,自定義的controller工廠在建立controller物件時,一般不僅僅簡單的建立一個例項,可能會考慮效能維護一個快取,手動維護session的狀態,異常控制,以及一些安全性的考慮等等。
建立工廠時拿到的兩個物件:
HttpContext |
當前Http請求物件 |
RouteData |
和當前請求匹配的那條route資訊 |
Controller重置:
當前請求的controller是可以被覆蓋的,
requestContext.RouteData.Values["controller"] ="Test";
這樣一來,MVC Framework就會去查詢新的View並覆蓋本來的View名稱了。
使用自帶的controllerfactory
如果不自己實現controller factory,預設都會使用MVC 自帶的factory,但是有幾點是需要注意的,為了確保controller會被factory正確找到並例項化:
- 必須public
- 不可以是抽象類
- 不能是泛型類
- 必須以 ”Controller” 為結尾
- 必須實現了IController介面(如果繼承了Controller就不用了,只針對自定義Controller的情況)
優先查詢名稱空間
開啟global檔案,新增:
ControllerBuilder.Current.DefaultNamespaces.Add("MyControllerNamespace");
ControllerBuilder.Current.DefaultNamespaces.Add("MyProject.*");
程式碼目的很顯然,希望MVC framework建立controller時候,新增的這些名稱空間中被優先查詢,注意最後傳遞了一個MyProject.*的SearchPattern,而不是正規表示式。另外,被新增的名稱空間不會因為先後順序導致被先後查詢,他們的優先順序是平行的(就像前面章節介紹的限制Route名稱空間一樣)。
Customize 預設ControllerFactory的行為
Controller Activator
對於一個工廠,最主要的職責就是建立物件,而在MVC Framework中,預設的工廠是支援自己實現建立過程的。
介面:
public interface IControllerActivator {
IController Create(RequestContext requestContext, Type controllerType);
}
引數:
RequestContext 物件:當前http請求的上下文資訊,前面章節說了,這個物件包含的資訊非常豐富,可以做很多事情(客戶端ip,cookie,session,querystring,form, Files, Request ,Response等等)
Controller 型別:和剛才手動建立factory不同,這裡我們直接得到了一個Type,這樣就不用關心也不用反射出型別了,建立物件方便多了。
示例的實現:
public class CustomControllerActivator :IControllerActivator {
public IController Create(RequestContext requestContext,
Type controllerType) {
if (controllerType == typeof(HomeController)) {
controllerType = typeof(TestController);
}
return (IController)DependencyResolver.Current.GetService(controllerType);
}
}
程式碼說明:如果進來的是Home,重寫為Test ,呼叫DependencyResolver建立並返回例項。
在global檔案的Application_Start方法中配置一下:
ControllerBuilder.Current.SetControllerFactory(new
DefaultControllerFactory(new CustomControllerActivator()));
如果還不能滿足需要,繼承DefaultControllerFactory,選擇的重寫以下的函式一個或多個:
public override IController CreateController(RequestContextrequestContext, string controllerName)
{
return base.CreateController(requestContext, controllerName);
}
protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
{
return base.GetControllerInstance(requestContext, controllerType);
}
protected override SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, Type controllerType)
{
return base.GetControllerSessionBehavior(requestContext, controllerType);
}
protected override Type GetControllerType(RequestContext requestContext, string controllerName)
{
return base.GetControllerType(requestContext, controllerName);
}
public override void ReleaseController(IController controller)
{
base.ReleaseController(controller);
}
Customize Action Invoker
我們知道,MVC 通過controller工廠找到並建立一個controller例項之後,下一步就是查詢並呼叫action了,這一步也是支援customize的。
介面:
public interface IActionInvoker {
bool InvokeAction(ControllerContext controllerContext,string actionName);
}
示例實現:
public class CustomActionInvoker : IActionInvoker
{
public bool InvokeAction(ControllerContext controllerContext,
string actionName)
{
if(actionName == "Index")
{
controllerContext.HttpContext.
Response.Write("I'd like over write all the Index ActionBehavior.");
return true;
}
return false;
}
}
程式碼說明:重寫所有名稱為index的action的行為。
在Controller中指向這個actioninvoker:
public TestController()
{
ActionInvoker = new CustomActionInvoker();
}
檢視結果:
發現TestController中的IndexAction的行為已經被重寫了。
使用自帶的ActionInvoker
- 方法必須是public
- 不支援static
- 方法不能和System.Web.Mvc.Controller中的函式重名
- 不支援泛型
Customize Action的名字
可以使用[ActionName] 來指定Action的名稱:
[ActionName("Enumerate")]
public ViewResult List() {
return View("Result",new Result {
ControllerName ="Customer",
ActionName = "List"
});
}
}
注意:一旦改了名字,舊的名字將被覆蓋,意味將不會被找到,如果訪問將返回404。
如果有場景需要同樣的Action名稱出現多次在同一個Controller,例如,支援httppost和httpget的行為想要分在不同的action裡面,那麼可以使用ActionName屬性:
[ActionName("Index2")]
[HttpPost]
public ActionResult Index1()
{
return Content("FromPost");
}
[ActionName("Index")]
[HttpGet]
public ActionResult Index2()
{
return Content("FromGet");
}
阻止一個Action被訪問
[NonAction]
public ActionResult MyAction() {
return View();
}
訪問這個Action,會返回404
Customize Action Selector
可以自定義Selector,來決定這個Action是否被select
抽象類:
[AttributeUsage(AttributeTargets.Method,AllowMultiple = false, Inherited = true)]
public abstract class ActionMethodSelectorAttribute : Attribute {
public abstract bool IsValidForRequest(ControllerContext controllerContext,
MethodInfo methodInfo);
}
說明:
拿到的是ControllerContext和MethodInfo
ControllerContext中包含了http請求的資訊
MethodInfo可以取的方法資訊
示例實現:
public class LocalAttribute :ActionMethodSelectorAttribute {
public override bool IsValidForRequest(ControllerContext controllerContext,
MethodInfo methodInfo) {
return controllerContext.HttpContext.Request.IsLocal;
}
}
程式碼目的很明確,限制這個Action只能被本地的請求訪問。
使用:
[Local]
public ActionResult LocalIndex() {
return Content("From Local ");
}
控制unkownAction
如果本次請求向controller要一個不存在的action,可能希望返回到一個特殊的錯誤頁面或者View,這時可以重寫Controller中的HandleUnkownAction函式:
protected override void HandleUnknownAction(string actionName) {
Response.Write(string.Format("You requested the {0} action", actionName));
}
檢視結果:
Controller效能考慮
1. 考慮設定controller的session為SessionStateLess
如果沒必要開啟Session,可以考慮把一個Session狀態關閉:
[SessionState(SessionStateBehavior.Disabled)]
Public class FastController :Controller {
Public ActionResult Index() {
return View("Result",new Result {
ControllerName = "Fast ",ActionName = "Index"
});
}
}
2. 考慮非同步Controller
場景:和第三方系統互動,並且客戶端不需要等待這個結果就可以進行當前的活動。可以結合使用TPL完成:
public class RemoteDataController : AsyncController{
public async Task<ActionResult> Data(){
string data = await Task<string>.Factory.StartNew(() => {
return new RemoteService().GetRemoteData();
});
return View((object)data);
}
}
RemoteService模擬實現
public class RemoteService {
public string GetRemoteData() {
Thread.Sleep(2000);
return "Hello from the other side of the world";
}
}
相關文章
- Dagger 2 系列(五) -- 進階篇:@Scope 和 @Singleton
- 【webpack 系列】進階篇Web
- React進階篇2React
- Kubernetes的Controller進階(十二)Controller
- vue2進階篇:vue-router之命名路由Vue路由
- Dagger2進階篇(二)
- 測開之函式進階· 第2篇《純函式》函式
- SpringCloud-OAuth2(三):進階篇SpringGCCloudOAuth
- Asp.NetCore之AutoMapper進階篇ASP.NETNetCoreAPP
- 帶你深度解鎖Webpack系列(進階篇)Web
- 正規表示式系列之中級進階篇
- 你所不知道的ASP.NET Core進階系列(三)ASP.NET
- Java多執行緒之進階篇Java執行緒
- Linux ACL 許可權之進階篇Linux
- Java進階篇 設計模式之十四 ----- 總結篇Java設計模式
- Java進階篇——springboot2原始碼探究JavaSpring Boot原始碼
- 【Kubernetes系列】第6篇 Ingress controller - nginx元件介紹ControllerNginx元件
- 【Kubernetes系列】第5篇 Ingress controller - traefik元件介紹Controller元件
- Dagger 2 系列(六) -- 進階篇:Component 依賴、@SubComponent 與多 Component 下的 Scope 使用限制
- TypeScript極速完全進階指南-2中級篇TypeScript
- 高階前端進階系列 - webview前端WebView
- Three.js進階篇之6 - 碰撞檢測JS
- Three.js進階篇之5 - 粒子系統JS
- [一天一個進階系列] - MyBatis基礎篇MyBatis
- asp.net core 系列之ConfigurationASP.NET
- asp.net core 系列之StartupASP.NET
- (三)struts2進階之實現Action
- React進階篇1React
- 測試 ASP.NET Core API ControllerASP.NETAPIController
- Android 進階 ———— Handler系列之建立子執行緒HandlerAndroid執行緒
- 【進階篇】Redis實戰之Jedis使用技巧詳解Redis
- Java進階篇之十五 ----- JDK1.8的Lambda、StreJavaJDK
- 測開之函式進階· 第6篇《閉包》函式
- python入門與進階篇(七)之原生爬蟲Python爬蟲
- JS進階系列 --- 繼承JS繼承
- 《MySQL 進階篇》二十:鎖MySql
- 樹莓派-進階篇樹莓派
- python網路進階篇Python
- 介面測試進階篇