在Asp.net MVC中,一个request过来后,mvc framework是怎么处理的:
一个请求过来,经过Route系统的处理后, 它会找出适合request的controller和action的名称。注意,这个时候仅仅是找到了它的名称,那它怎么去创建对应的Controller,并调用action呢?
1. Controller Factory:
所有的Controller Factory都要实现IControllerFactory接口:
{
IController CreateController(RequestContext requestContext, string controllerName);
SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, string controllerName);
void ReleaseController(IController controller);
}
才能被注入到MVC Framework中。 标黄的部分就是上文问题的答案主体了。好,我们先自己实现一个:
{
public IController CreateController(RequestContext requestContext, string controllerName)
{
Type targetType = null;
switch (controllerName)
{
case "Home":
//requestContext.RouteData.Values["controller"] = "First";
requestContext.RouteData.Values["controller"] = "Home";
targetType = typeof(FirstController);
break;
case "First":
requestContext.RouteData.Values["controller"] = "First";
targetType = typeof(FirstController);
break;
case "Second":
requestContext.RouteData.Values["controller"] = "Second";
targetType = typeof(FirstController);
break;
}
return targetType == null ? null : Activator.CreateInstance(targetType) as IController;
}
public SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, string controllerName)
{
return SessionStateBehavior.Default;
}
public void ReleaseController(IController controller)
{
var disposeableController = controller as IDisposable;
if (disposeableController != null)
{
disposeableController.Dispose();
}
}
}
在CreateController方法中,根据名称选择目标controller类型,然后通过Activator反射构造出一个controller实例对象。关于GetControllerSessionBehavior 方法,是返回session的状态设置,后边有更详细描述。 ReleaseController方法通过转换为IDisposable,巧妙释放controller资源。 另外,可以看到,在CreateController方法中,你甚至可以篡改所返回的controller对象(被注释那部分)。
构造完了,就是注入使用了。在Application_Start方法中,加入:
再看ControllerBuilder的定义:
{
public ControllerBuilder();
public static ControllerBuilder Current { get; }
public HashSet<string> DefaultNamespaces { get; }
public IControllerFactory GetControllerFactory();
public void SetControllerFactory(IControllerFactory controllerFactory);
public void SetControllerFactory(Type controllerFactoryType);
}
你就知道,一个MVC项目中只能同时启用一个Controller Factory了。所以,通常情况下,不建议自己实现它。另外,基于ControllerBuilder的定义,可以定义全局优先的Route namespace:
ControllerBuilder.Current.DefaultNamespaces.Add("MyProject.*");
和http://www.cnblogs.com/Langzi127/archive/2012/10/20/2732725.html 中描述的那些方法不一样的是,上述2个namespace对所有route配置都取作用。不过,相同的一点就是,如果两者同时匹配到指定route,都会报异常。
现在研究一下DefaultControllerFactory的定义:
{
public DefaultControllerFactory();
public DefaultControllerFactory(IControllerActivator controllerActivator);
#region IControllerFactory Members
public virtual IController CreateController(RequestContext requestContext, string controllerName);
public virtual void ReleaseController(IController controller);
SessionStateBehavior IControllerFactory.GetControllerSessionBehavior(RequestContext requestContext, string controllerName);
#endregion
protected internal virtual IController GetControllerInstance(RequestContext requestContext, Type controllerType);
protected internal virtual SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, Type controllerType);
protected internal virtual Type GetControllerType(RequestContext requestContext, string controllerName);
}
可以看到,它还可以通过注入IControllerActivator对象来达到自己的目的。而IControllerActivator的定义为:
{
IController Create(RequestContext requestContext, Type controllerType);
}
所以,实际上它才是controller的创建者。我们先来自己实现一下它:
{
public IController Create(RequestContext requestContext, Type controllerType)
{
if(controllerType == typeof(FirstController))
{
controllerType = typeof (SecondController);
requestContext.RouteData.Values["controller"] = "Second";
}
return DependencyResolver.Current.GetService(controllerType) as IController;
}
}
然后简单定义一个ControllerFactory,并使用上述ControllerActivator:
{
public CustomControllerFactory2(IControllerActivator controllerActivator) : base(controllerActivator)
{
}
}
最后注册启用:
2. Action Invoker
通过ControllerActivator,已经可以创建出目标controller对象实例了。那么通过controller对象和action名称(仅仅是一个字符串而已),怎么来调用action方法?看IActionInvoker的定义:
{
bool InvokeAction(ControllerContext controllerContext, string actionName);
}
对细节还是不很清楚。还是自己先实现一个吧:
{
public bool InvokeAction(ControllerContext controllerContext, string actionName)
{
if(actionName == "Index")
{
controllerContext.HttpContext.Response.Write("This is output from the Index action.");
return true;
}
return false; //http code: 404
}
}
这里当然太简单直接了,如果action名称为Index,直接Response Write了,否则就404. 使用方式:
{
public CustomActionInvokerController()
{
this.ActionInvoker = new CustomActionInvoker();
}
public ActionResult Index()
{
return View();
}
}
在MVC中,它是通过ControllerActionInvoker类来做这个事情,之后再详细研究它。
注意:ActionInvoker调用action时,不仅仅是要核对方法的签名,还要核对它头上的ActionMethodSelector(及其子类)特性,如:ActionName 、 HttpPost、NonAction等。
关于ActionName,可以用于别名中需要包含非法字符,如:
public ActionResult UserRegister()
调用的时候,通过User-Register即可。也可用于实现REST:
{
[HttpGet]
[ActionName("Staff")]
public ActionResult StaffGet(int id)
{
// logic to get staff
return Content("got staff");
}
[HttpPost]
[ActionName("Staff")]
public ActionResult StaffModify(int id, StaffMember person)
{
// ... logic to modify or create data item
return Content("modifed the staff");
}
[HttpDelete]
[ActionName("Staff")]
public ActionResult StaffDelete(int id)
{
// ... logic to delete data item
return Content("deleted the staff");
}
}
调用方式,如:
@Html.HttpMethodOverride(HttpVerbs.Delete)
<input type="submit" value="Delete" />
}
标黄部分是为了fix html form不支持delete。
除了上述ActionMethodSelector特性,还可以自己创建它的拓展类:
{
public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo)
{
return controllerContext.HttpContext.Request.IsLocal;
}
}
使用方式:
public ActionResult About()
{
return View();
}
这样就只有本地用户可以看到about action了。
3. Performance Improvement
基于上述的过程,要做性能优化,这里列举2个方法:
1) 尽量少使用Session,我的意思是将Controller的Session状态设置为ReadOnly或者Disabled;
2) 使用异步action。
当然还有其他一些办法,但这里仅列举这2种。假如我们要实现一个Data的action,并且异步:
{
public void DataAsync()
{
AsyncManager.OutstandingOperations.Increment();
Task.Factory.StartNew(() =>
{
//Thread.Sleep(2000);
//AsyncManager.Parameters["result"] = "Finally done the work.";
//AsyncManager.OutstandingOperations.Decrement();
WebRequest req = WebRequest.Create("http://www.asp.net");
req.BeginGetResponse((IAsyncResult ias) =>
{
WebResponse resp = req.EndGetResponse(ias);
string content = new StreamReader(resp.GetResponseStream()).ReadToEnd();
AsyncManager.Parameters["result"] = content;
AsyncManager.OutstandingOperations.Decrement();
}, null);
});
//http://baike.baidu.com/view/1256215.htm
}
public ActionResult DataCompleted(string result)
{
//return Content(result);
return Content(result, "text/html");
}
}
可以看到action方法一拆为二,分为:DataAsync、DataCompleted。而它们之间是通过AsyncManager来连接和传值的。 关于异步的实现是利用windows内核的IOCP,具体见http://baike.baidu.com/view/1256215.htm 。
源码download