[MVC]ASP.NET MVC Preview 3 流程分析 - 3.View

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 abstract class Controller : IActionFilter, IController, IDisposable
{
  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 class ViewResult : ActionResult
{
  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()。

public class WebFormViewEngine : IViewEngine
{
  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() 进行跳转。

public abstract class Controller : IActionFilter, IController, IDisposable
{
  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 class HomeController : Controller
{
  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 时的定义。

routes.MapRoute(
  "Default",  // Route name
  "{controller}/{action}/{id}",  // URL with parameters
  new { controller = "Home", action = "Index", id = "" } // Parameter defaults
);

3. JsonResult
有了这个,写 Ajax 操作就简单多了。

public class HomeController : Controller
{
  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 class HomeController : Controller
{
  public ActionResult Index()
  {
    return Json(new { Name = "Rose", Sex = "Male", Age = 31 });
  }
}

看看返回的 Http 结果。

HTTP/1.1 200 OK
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(…)"。 [lol]

public class ContentResult : ActionResult
{
  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 class EmptyResult : ActionResult
{
  public override void ExecuteResult(ControllerContext context)
  {
  }
}

唯一要关注的是,ControllerActionInvoker.InvokeActionMethod 会将 Action return null 转为为 EmptyResult。

public class ControllerActionInvoker
{
  protected virtual ActionResult InvokeActionMethod(methodInfo, parameters)
  {
    // … 省略部分代码 …
    object returnValue = methodInfo.Invoke(controller, parametersArray);
    if (returnValue == null)
    {
      return new EmptyResult();
    }
    
    // … 省略部分代码 …
  }
}
赞(0) 打赏
分享到: 更多 (0)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏