学习ASP.NET + MVC(三)

来源:互联网 发布:九九乘法表的c语言代码 编辑:程序博客网 时间:2024/05/22 00:10

本文示例源代码或素材下载

  一.摘要

  一个Url请求经过了Routing处理后会调用Controller的Action方法. 中间的过程是怎样的? Action方法中返回ActionResult对象后,如何到达View的? 本文将讲解Controller的基本用法, 深入分析Controller的运行机制, 并且提供了创建所有类型Action的代码. 值得学习ASP.NET MVC时参考.

  二.承上启下

  在上一篇文章中, 我已经学会了如何使用Routing获取Controller和Action, 随后的程序会调用Controller中的Action方法.

  每个Action方法都要返回一个ActionResult对象. 一个Action会将数据传递给View,如图:

学习ASP.NET MVC(三) Controller/Action 深入解析与应用实例

  三.Controller与Action的作用

  1.职责

  Controller负责将获取Model数据并将Model传递给View对象.通知View对象显示.

  2.ASP.NET MVC中的Controller和Action

  在ASP.NET MVC中, 一个Controller可以包含多个Action. 每一个Action都是一个方法, 返回一个ActionResult实例.

  ActionResult类包括ExecuteResult方法, 当ActionResult对象返回后会执行此方法.

  下面分层次的总结Controller 处理流程:

  1. 页面处理流程

  发送请求 –> UrlRoutingModule捕获请求 –> MvcRouteHandler.GetHttpHandler() –> MvcHandler.ProcessRequest()

  2.MvcHandler.ProcessRequest() 处理流程:

  使用工厂方法获取具体的Controller –> Controller.Execute() –> 释放Controller对象

  3.Controller.Execute() 处理流程

  获取Action –> 调用Action方法获取返回的ActionResult –> 调用ActionResult.ExecuteResult() 方法

  4.ActionResult.ExecuteResult() 处理流程

  获取IView对象-> 根据IView对象中的页面路径获取Page类-> 调用IView.RenderView() 方法(内部调用Page.RenderView方法)

  通过对MVC源代码的分析,我们了解到Controller对象的职责是传递数据,获取View对象(实现了IView接口的类),通知View对象显示.

  View对象的作用是显示.虽然显示的方法RenderView()是由Controller调用的,但是Controller仅仅是一个"指挥官"的作用, 具体的显示逻辑仍然在View对象中.

  需要注意IView接口与具体的ViewPage之间的联系.在Controller和View之间还存在着IView对象.对于ASP.NET程序提供了WebFormView对象实现了IView接口.WebFormView负责根据虚拟目录获取具体的Page类,然后调用 Page.RenderView().

  四.ActionResult解析

  通过上面的流程,我们知道了ActionResult对象在整个流程中的作用.ActionResult是一个抽象类, 在Action中返回的都是其派生类.下面是我整理的RC2版本中提供的ActionResult派生类:

 

类名抽象类父类功能ContentResult  根据内容的类型和编码,数据内容.EmptyResult  空方法.FileResultabstract 写入文件内容,具体的写入方式在派生类中.FileContentResult FileResult通过 文件byte[] 写入文件.FilePathResult FileResult通过 文件路径 写入文件.FileStreamResult FileResult通过 文件Stream 写入文件.HttpUnauthorizedResult  抛出401错误JavaScriptResult  返回javascript文件JsonResult  返回Json格式的数据RedirectResult  使用Response.Redirect重定向页面RedirectToRouteResult  根据Route规则重定向页面ViewResultBaseabstract 调用IView.Render()PartialViewResult ViewResultBase调用父类ViewResultBase 的ExecuteResult方法.

  重写了父类的FindView方法.

  寻找用户控件.ascx文件

 

ViewResult ViewResultBase调用父类ViewResultBase 的ExecuteResult方法.

  重写了父类的FindView方法.

  寻找页面.aspx文件

 

  目前ASP.NET MVC还没有提供官方的ActionResult列表.上面的列表是我在源代码中分析得出的.有些解释的可能不够清楚,请谅解.

  下面我将列举各个ActionResult的实例.

  五.实例应用

  1.添加Controller

  安装了ASP.NET MVC后, 在项目上点击右键会找到添加Controller项:

学习ASP.NET MVC(三) Controller/Action 深入解析与应用实例

  2.添加Action

  下面这个类提供了返回各种类型的ActionResult的Action实例:

public class DemoController : Controller
  {

    /// <summary>
    /// http://localhost:1847/Demo/ContentResultDemo
    /// </summary>
    /// <returns></returns>
    public ActionResult ContentResultDemo()
    {
      string contentString = "ContextResultDemo!";
      return Content(contentString);
    }

    /// <summary>
    /// http://localhost:1847/Demo/EmptyResultDemo
    /// </summary>
    /// <returns></returns>
    public ActionResult EmptyResultDemo()
    {
      return new EmptyResult();
    }

    /// <summary>
    /// http://localhost:1847/Demo/FileContentResultDemo
    /// </summary>
    /// <returns></returns>
    public ActionResult FileContentResultDemo()
    {
      FileStream fs = new FileStream(Server.MapPath(@"/resource/Images/1.gif"), FileMode.Open, FileAccess.Read);
      byte[] buffer = new byte[Convert.ToInt32(fs.Length)];
      fs.Read(buffer, 0, Convert.ToInt32(fs.Length) );
      return File(buffer, @"image/gif");
    }

    /// <summary>
    /// http://localhost:1847/Demo/FilePathResultDemo
    /// </summary>
    /// <returns></returns>
    public ActionResult FilePathResultDemo()
    {
      //可以将一个jpg格式的图像输出为gif格式
      return File(Server.MapPath(@"/resource/Images/2.jpg"), @"image/gif");
    }

    /// <summary>
    /// http://localhost:1847/Demo/FileStreamResultDemo
    /// </summary>
    /// <returns></returns>
    public ActionResult FileStreamResultDemo()
    {      
      FileStream fs = new FileStream(Server.MapPath(@"/resource/Images/1.gif"), FileMode.Open, FileAccess.Read);
      return File(fs, @"image/gif");
    }

    /// <summary>
    /// http://localhost:1847/Demo/HttpUnauthorizedResultDemo
    /// </summary>
    /// <returns></returns>
    public ActionResult HttpUnauthorizedResultDemo()
    {
      return new HttpUnauthorizedResult();
    }

    /// <summary>
    /// http://localhost:1847/Demo/JavaScriptResultDemo
    /// </summary>
    /// <returns></returns>
    public ActionResult JavaScriptResultDemo()
    {
      return JavaScript(@"alert(""Test JavaScriptResultDemo!"")");
    }

    /// <summary>
    /// http://localhost:1847/Demo/JsonResultDemo
    /// </summary>
    /// <returns></returns>
    public ActionResult JsonResultDemo()
    {
      var tempObj = new { Controller = "DemoController", Action = "JsonResultDemo" };
      return Json(tempObj);
    }

    /// <summary>
    /// http://localhost:1847/Demo/RedirectResultDemo
    /// </summary>
    /// <returns></returns>
    public ActionResult RedirectResultDemo()
    {
      return Redirect(@"http://localhost:1847/Demo/ContentResultDemo");
    }

    /// <summary>
    /// http://localhost:1847/Demo/RedirectToRouteResultDemo
    /// </summary>
    /// <returns></returns>
    public ActionResult RedirectToRouteResultDemo()
    {
      return RedirectToAction(@"FileStreamResultDemo");
    }

    /// <summary>
    /// http://localhost:1847/Demo/PartialViewResultDemo
    /// </summary>
    /// <returns></returns>
    public ActionResult PartialViewResultDemo()
    {
      return PartialView();
    }

    /// <summary>
    /// http://localhost:1847/Demo/RedirectToRouteResultDemo
    /// </summary>
    /// <returns></returns>
    public ActionResult ViewResultDemo()
    {
      //如果没有传入View名称, 默认寻找与Action名称相同的View页面.
      return View();
    }

  }

 

  在文章最后提供有完整实例代码下载.

  六.Controller 深入分析

  在研究Controller/Action的流程过程中, 发现了ASP.NET MVC一些问题.

  1.Routing组件与MVC框架的结合

  Routing组件和ASP.NET MVC并不是一个项目, 在ASP.NET MVC中仅仅是使用了Routing组件, 在源代码中是通过dll的方式引用的.Routing组件已经包含在.net framework 3.5 sp1中了.而ASP.NET MVC还未出正式版.

  那么ASP.NET MVC是如何应用Routing组件的呢?

  Routing组件获取了 Url中的数据后, 会将数据保存在一个 RouteData 对象中.并将请求传递给一个实现了IRouteHandler接口的对象. 在Asp.net MVC中提供的MvcRouteHandler类实现了此接口, Routing 将请求传递给MvcRouteHandler的GetHttpHandler方法.下面是源代码:

  IRouteHandler接口:

  public interface IRouteHandler
  {
    IHttpHandler GetHttpHandler(RequestContext requestContext);
  }

 

  MvcRouteHandler类:

  public class MvcRouteHandler : IRouteHandler {
    protected virtual IHttpHandler GetHttpHandler(RequestContext requestContext) {
      return new MvcHandler(requestContext);
    }

    #region IRouteHandler Members
    IHttpHandler IRouteHandler.GetHttpHandler(RequestContext requestContext) {
      return GetHttpHandler(requestContext);
    }
    #endregion
  }

 

  曾经我认为IRouteHandler是多余的, 用IHttpHandler就够了. 现在知道了为何要定义这个接口. 主要是为了传递RouteData对象.GetHttpHandler方法需要一个RequestContext 对象.RequestContext 是 System.Web.Routing程序集中的类, 里面除了处理请求需要的HttpContextBase对象,还包括了一个RouteData对象.

  RequestContext类:

  public class RequestContext
  {
    public RequestContext(HttpContextBase httpContext, RouteData routeData);
    public HttpContextBase HttpContext { get; }
    public RouteData RouteData { get; }
  }

  Routing组件在Web.Config中注册了一个HttpModule: System.Web.Routing.UrlRoutingModule, 而不是HttpHandler:

<add  name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule,  System.Web.Routing, Version=3.5.0.0, Culture=neutral,  PublicKeyToken=31BF3856AD364E35"/>

 

  可惜看不到这个类的源代码. 所有请求最后都是要传递给IHttpHandler对象处理, 主要的工作是编译页面, 所以我猜测这个Module将请求截获后通过IRouteHandler接口对象获取一个HttpHandler, 然后将处理移交给获取到的HttpHandler.

  ASP.NET MVC 中实现了IHttpHandler接口的类是MvcHandler, MvcRouteHandler.GetHttpHandler方法就是返回一个MvcHandler对象. MvcHandler类的构造函数需要传入一个RequestContext对象. 实现的IHttpHandler接口方法处理过程中都需要依赖这个对象.

  但是微软在这里的处理有一些不足. MvcHandler虽然实现了IHttpHandler接口但是不能被当作IHttpHandler接口使用. 因为IHttpHandler中没有定义RequestContext属性, 如果一个MvcHandler对象此属性没有赋值则会出错, 也没有将默认的无参数构造函数设置为private, 所以理论上可以很随意的实例化一个MvcHandler而不为其RequestContext属性赋值.

  IRouteHandler想实现的语意是: 返回一个具有RequestContext属性的IHttpHandler对象.

  但是最后的实现结果是: 提供"返回IHttpHandler对象"的方法,  此方法接收RequestContext对象参数.

  还需要注意ControllerContext类. 在Controller的处理过程中使用此对象作为保存上下文数据的容器.下面是这几个类的包含关系:

学习ASP.NET MVC(三) Controller/Action 深入解析与应用实例

  可以看到在ControllerContext中包含了RequestContext对象,但是又将RequestContext对象中的两个属性提取到自己的类中.如果仅仅是为了使用方便而这么做, 个人认为不是一个好的设计.数据对象的存储职责也应该明确,使用ControllerContext.RequestContext.RouteData 的方式更容易被人理解.

  PS:这种方式类似于方法内联.对于属性JIT为了效率会帮助我们做内联.而仅仅是为了使用方便.

  2.IView 与 View对象的关系

  所以从系统的角度上看, 实现了IView接口的对象才是View.

  但是从实现效果上看, 具体的aspx或者ascx页面才是View.

  当第一次看到IView接口时我认为它应该是"View角色"需要实现的接口. 但是结果并不是这样.

  在我们的系统中View对象应该是aspx或者ascx文件. 而且并不是所有的ActionResult都需要找到aspx或者ascx文件, 事实上只有PartialViewResult 和 ViewResult 才会去寻找View对象.其他的ActionResult要么是返回文件, 要么是跳转等等.

  那么两者的关系到底是怎样的? 其实其中的过程需要牵扯到这几个接口和类:

  IViewEngine, ViewEngineResult, ViewEngineCollection

  ViewEngine是View引擎, ViewEngineCollection是一个引擎集合,里面保存了各种寻找View的引擎.但是在目前的源代码中只有 WebFormViewEngine : VirtualPathProviderViewEngine : IViewEngine

  这一系列WebForm使用的引擎.引擎的作用有两个:

  1.寻找Page/用户控件的路径

  2.根据路径创建IView对象.也就是根据页面的物理文件创建IView接口对象.

  而且目前实现了IView接口的对象也只有一个:

  WebFormView

  WebFormViewEngine 根据页面路径, 将一个页面地址转化为一个WebFormView对象,也就是一个IView接口对象.

  至此IView接口和Page页面类仍然没有任何关系, IView对象只是保存了页面的物理路径.

  接着在IView的Render事件中,根据物理路径创建了一个页面的object实例,注意看这一段代码:

      object viewInstance = BuildManager.CreateInstanceFromVirtualPath(ViewPath, typeof(object));
      if (viewInstance == null) {
        throw new InvalidOperationException(
          String.Format(
            CultureInfo.CurrentUICulture,
            MvcResources.WebFormViewEngine_ViewCouldNotBeCreated,
            ViewPath));
      }

      ViewPage viewPage = viewInstance as ViewPage;
      if (viewPage != null) {
        RenderViewPage(viewContext, viewPage);
        return;
      }

      ViewUserControl viewUserControl = viewInstance as ViewUserControl;
      if (viewUserControl != null) {
        RenderViewUserControl(viewContext, viewUserControl);
        return;
      }

 

  viewInstance 就是通过物理路径创建的页面对象.但是他的类型是object, 而且程序尝试将其分别转化为ViewPage对象和ViewUserControl对象.

  我想很多人都看到了这里的设计不足.现在我们只能"约定": 所有的MVC中的页面对象都必须继承自ViewPage或者ViewUserControl类, 否则程序就会出错.产生这种不足的原因就是IView接口和ViewPage没有任何的耦合性, 完全是硬编码进去的.

  为什么不让页面直接实现IView接口? 然后尝试将页面转化为IView接口对象, 而不是ViewPage, 这样才是好的设计. 其实微软知道什么是好的设计, 我猜测他们遇到的困难是Page对象和IView接口的冲突. 因为两者都需要Render. 如果在IView中定义自己的Render名称, 那就意味着ASP.NET MVC开发小组要自己处理页面的显示逻辑, 而现在ASP.NET WebForm模式下面的页面显示引擎又不能复用, 重新开发自己的一套显示引擎成本又太大, 才出此下策.

  以上只是猜测.这种设计的缺陷虽然可以接受, 但是真的是让我好几天陷入了看不懂代码的痛苦之中.还好, 现在可以解脱了.

  七.如何在MVC项目中使用MVC源代码项目

  另外在为了跟踪实现过程, 我将ASP.NET MVC的源代码项目添加到了实例项目中, 其中有一些需要注意的地方:

  1. 将实例项目中的System.Web.Mvc引用删除, 改成项目引用.

  2. 需要在Web.Config中注释掉程序集引用:

    <compilation debug="true">
      <assemblies>
        <add assembly="System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
        <add assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
        <add assembly="System.Web.Abstractions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
        <add assembly="System.Web.Routing, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
        <!-- <add assembly="System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>-->
        <add assembly="System.Data.DataSetExtensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
        <add assembly="System.Xml.Linq, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
        <add assembly="System.Data.Linq, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
      </assemblies>
    </compilation>

 

  注释掉的程序集存在于GAC中, 但是我们现在不希望使用GAC中的程序集, 而是引用项目.

  3. 将View目录下的Web.Config中的所有System.Web.Mvc相关的 PublicKeyToken 都修改为 null:

  <pages
    validateRequest="false"
    pageParserFilterType="System.Web.Mvc.ViewTypeParserFilter,  System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
    pageBaseType="System.Web.Mvc.ViewPage, System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
    userControlBaseType="System.Web.Mvc.ViewUserControl, System.Web.Mvc,  Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
   <controls>
    <add assembly="System.Web.Mvc, Version=1.0.0.0, Culture=neutral,  PublicKeyToken=null" namespace="System.Web.Mvc" tagPrefix="mvc" />
   </controls>
  </pages>

  八.总结

  首先很抱歉在本系列文章开篇时承诺的每日一篇仅仅坚持了2天.具体原因就不解释了.这篇文章的出炉历时半个月, 并且经历了ASP.NET MVC版本从RC到RC2的演变. 在查看MVC源代码上花费了大量的时间, 希望付出的努力能够为大家研究学习ASP.NET MVC带来帮助. 我也会把这一系列的文章写完, 关于ASP.NET MVC还有太多的地方没有学习.