P3 改用一堆继承自 ActionResult 的对象来完成视图显示,这是该版本的最大变化。
1. ViewResult
当 我们在 Action Method 中调用 View 生成 ViewResult 的时候,应该注意两个细节,那就是生成默认的 ViewData 字典和 WebFormViewEngine 视图显示引擎。上一节的分析中我们已经知道 ActionFilter.OnActionExecuting 在 Action Method 之前执行,那么我们完全可以定义一个自己的视图引擎,比如用 NVelocity 来代替 WebForm。(可参考《ASP.NET MVC Preview 2 – 流程分析 (3) 》和《ASP.NET MVC Preview 2 – NVelocityViewEngine 》)
{
public ViewDataDictionary ViewData
{
get
{
if (_viewData == null)
{
_viewData = new ViewDataDictionary();
}
return _viewData;
}
}
public IViewEngine ViewEngine
{
get
{
if (_viewEngine == null)
{
_viewEngine = new WebFormViewEngine();
}
return _viewEngine;
}
set
{
// … 省略部分代码 …
_viewEngine = value;
}
}
protected internal virtual ViewResult View(string viewName, string masterName, object model)
{
if (model != null)
{
ViewData.Model = model;
}
return new ViewResult()
{
ViewName = viewName,
MasterName = masterName,
ViewData = ViewData,
ViewEngine = ViewEngine,
TempData = TempData
};
}
}
在 Controller.InvokeAction() 的最后会间接通过 InvokeActionResult() 来调用 ViewResult.ExecuteResult,从而实现对视图引擎的触发 (可参考上一章)。
{
public override void ExecuteResult(ControllerContext context)
{
// … 省略部分代码 …
string viewName = (!String.IsNullOrEmpty(ViewName)) ?
ViewName : context.RouteData.GetRequiredString("action");
ViewContext viewContext = new ViewContext(context, viewName, MasterName, ViewData, TempData);
ViewEngine.RenderView(viewContext);
}
}
创建视图上下文,并调用 WebFormViewEngine.RenderView()。
{
protected virtual void RenderView(ViewContext viewContext)
{
// … 省略部分代码 …
string viewPath = ViewLocator.GetViewLocation(viewContext, viewContext.ViewName);
// … 省略部分代码 …
object viewInstance = BuildManager.CreateInstanceFromVirtualPath(viewPath, typeof(object));
// … 省略部分代码 …
ViewPage viewPage = viewInstance as ViewPage;
if (viewPage != null)
{
if (!String.IsNullOrEmpty(viewContext.MasterName))
{
string masterLocation = ViewLocator.GetMasterLocation(viewContext, viewContext.MasterName);
// … 省略部分代码 …
// We don't set the page's MasterPageFile directly since it will get
// overwritten by a statically-defined value. In ViewPage we wait until
// the PreInit phase until we set the new value.
viewPage.MasterLocation = masterLocation;
}
viewPage.ViewData = viewContext.ViewData;
viewPage.RenderView(viewContext);
}
else
{
ViewUserControl viewUserControl = viewInstance as ViewUserControl;
if (viewUserControl != null)
{
// … 省略部分代码 …
viewUserControl.ViewData = viewContext.ViewData;
viewUserControl.RenderView(viewContext);
}
else
{
// … 省略部分代码 …
}
}
}
}
这部分的变化并不大,有关细节可参考 《ASP.NET MVC Preview 2 – 流程分析 (3)》,这里就不在啰嗦了。
2. RedirectResult / RedirectToRouteResult
其实很简单,重建 RouteValueDictionary,然后在 RedirectToRouteResult.ExecuteResult() 中使用 HttpContext.Response.Redirect() 进行跳转。
{
protected internal virtual RedirectToRouteResult RedirectToAction(actionName, controllerName, values)
{
// … 省略部分代码 …
RouteValueDictionary newDict = (values != null) ?
new RouteValueDictionary(values) : new RouteValueDictionary();
newDict["action"] = actionName;
if (!String.IsNullOrEmpty(controllerName))
{
newDict["controller"] = controllerName;
}
return new RedirectToRouteResult(newDict);
}
protected internal virtual RedirectToRouteResult RedirectToRoute(routeName, values)
{
RouteValueDictionary newDict = (values != null) ?
new RouteValueDictionary(values) : new RouteValueDictionary();
return new RedirectToRouteResult(routeName, newDict);
}
}
public class RedirectToRouteResult : ActionResult
{
public RedirectToRouteResult(string routeName, RouteValueDictionary values)
{
RouteName = routeName ?? String.Empty;
Values = values ?? new RouteValueDictionary();
}
public override void ExecuteResult(ControllerContext context)
{
// … 省略部分代码 …
VirtualPathData vpd = Routes.GetVirtualPath(context, RouteName, Values);
// … 省略部分代码 …
string target = vpd.VirtualPath;
context.HttpContext.Response.Redirect(target);
}
}
尽管这种跳转会导致 Controller 实例被重新生成,但我们依然可以使用 TempData 传递相关的数据给下一个 Action。(有关 TempData 传递数据的原理,可阅读《ASP.NET MVC Preview 2 – RedirectToAction》)
下面是一个跳转的演示
{
public ActionResult Index()
{
TempData["s"] = "Haha…";
//return RedirectToRoute(new { controller = "Test", action = "Test" });
return RedirectToAction("About");
}
public ActionResult About()
{
ViewData["Title"] = TempData["s"].ToString();
return View();
}
}
public class TestController : Controller
{
public ActionResult Test()
{
return Content(TempData["s"].ToString());
}
}
在使用 Controller.RedirectToRoute 时有个参数叫 routeName,其实它就是 Global.asax.cs 中调用 MapRoute 时的定义。
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = "" } // Parameter defaults
);
3. JsonResult
有了这个,写 Ajax 操作就简单多了。
{
protected internal virtual JsonResult Json(object data, string contentType, Encoding contentEncoding)
{
return new JsonResult
{
Data = data,
ContentType = contentType,
ContentEncoding = contentEncoding
};
}
}
public class JsonResult : ActionResult
{
public override void ExecuteResult(ControllerContext context)
{
// … 省略部分代码 …
HttpResponseBase response = context.HttpContext.Response;
if (!String.IsNullOrEmpty(ContentType))
{
response.ContentType = ContentType;
}
else
{
response.ContentType = "application/json";
}
if (ContentEncoding != null)
{
response.ContentEncoding = ContentEncoding;
}
if (Data != null)
{
JavaScriptSerializer serializer = new JavaScriptSerializer();
response.Write(serializer.Serialize(Data));
}
}
}
写个例子看看。
{
public ActionResult Index()
{
return Json(new { Name = "Rose", Sex = "Male", Age = 31 });
}
}
看看返回的 Http 结果。
Server: ASP.NET Development Server/9.0.0.0
Date: Sat, 14 Jun 2008 11:24:46 GMT
X-AspNet-Version: 2.0.50727
Cache-Control: private
Content-Type: application/json; charset=utf-8
Content-Length: 37
Connection: Close
{"Name":"Rose","Sex":"Male","Age":31}
4. ContentResult
这个纯粹就是代码缩写了,将 "Response.Write(…)" 简写成 "Content(…)"。
{
public override void ExecuteResult(ControllerContext context)
{
// … 省略部分代码 …
HttpResponseBase response = context.HttpContext.Response;
if (!String.IsNullOrEmpty(ContentType))
{
response.ContentType = ContentType;
}
if (ContentEncoding != null)
{
response.ContentEncoding = ContentEncoding;
}
if (Content != null)
{
response.Write(Content);
}
}
}
5. EmptyResult
这个最好,没啥可说,没啥可做。
{
public override void ExecuteResult(ControllerContext context)
{
}
}
唯一要关注的是,ControllerActionInvoker.InvokeActionMethod 会将 Action return null 转为为 EmptyResult。
{
protected virtual ActionResult InvokeActionMethod(methodInfo, parameters)
{
// … 省略部分代码 …
object returnValue = methodInfo.Invoke(controller, parametersArray);
if (returnValue == null)
{
return new EmptyResult();
}
// … 省略部分代码 …
}
}