Asp.Net MVC4 系列--進階篇之Controller(2)

mybwu_com發表於2014-04-08

呼叫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";
}
}


相關文章