夜鹰教程网-程序员的加油站
 当前位置:文章中心 >> vs2022_vs2019_vs2017_vs2014_vs2012
ASP.NET MVC开发实践教程Controller/Action 深入解析与应用实例】
夜鹰教程网 来源:www.yyjcw.com 日期:2016-11-13 15:45:41
Controller/Action 深入解析与应用实例 

一.摘要

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

二.承上启下

在上一篇文章中, 我已经学会了如何使用Routing获取Controller和Action, 随后的程序会调用Controller中
的Action方法.每个Action方法都要返回一个ActionResult对象. 一个Action会将数据传递给View,如图:

 

三.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中返回的都是其派生类.下面是我整理的ASP.NET MVC 1.0 版本中提供的ActionResult派生类:

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

 

2.添加Action

六.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是如何应用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的处理过程中使用此对象作为保存上下文数据的容器.
下面是这几个类的包含关系:



可以看到在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对象只是保存了页面的物理路径.

 

我想很多人都看到了这里的设计不足.现在我们只能"约定": 所有的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直到最近的1.0版本的演变. 在查看MVC源代码上花费了大量的时间,
希望付出的努力能够为大家研究学习ASP.NET MVC带来帮助.

实例源代码下载地址:

http://files.cnblogs.com/zhangziqiu/Asp.net-MVC-3-Demo.rar

作者:张子秋

下面这个类提供了返回各种类型的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();
        }

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

复制链接 网友评论 收藏本文 关闭此页
上一条: ASP.NET MVC开发实践教程【View/M…  下一条: ASP.NET MVC开发实践教程【Routing的使…
夜鹰教程网成立于2008年,目前已经运营了将近 13 年,发布了大量关于 html5/css3/C#/asp.net/java/python/nodejs/mongodb/sql server/android/javascript/mysql/mvc/easyui/vue/echarts原创教程。 我们一直都在坚持的是:认证负责、一丝不苟、以工匠的精神来打磨每一套教程,让读者感受到作者的用心。我们默默投入的时间,确保每一套教程都是一件作品,而不是呆板的文字和视频! 目前我们推出在线辅导班试运营,模式为一对一辅导,教学工具为QQ。我们的辅导学科包括 java 、android原生开发、webapp开发、商城开发、C#和asp.net开发,winform和物联网开发、web前端开发,但不仅限于此。 普通班针对的是国内学员,例如想打好基础的大学生、想转行的有志青年、想深入学习的程序员、想开发软件的初学者或者业余爱好者等。 就业办针对即将毕业上岗的大四学生,或者打算转行的初级开发工程师。 留学生班针对的是在欧美、加拿大、澳洲、日本、韩国、新加坡等地留学的中国学子,目的是让大家熟练地掌握编程技能,按时完成老师布置的作业,并能顺利地通过考试。 详细咨询QQ:1416759661   夜鹰教程网  基于角色的权限管理系统(c-s/b-s)。
  夜鹰教程网  基于nodejs的聊天室开发视频教程
  夜鹰教程网  Git分布式版本管理视频教程
  夜鹰教程网  MVC+EasyUI视频教程
  夜鹰教程网  在线考试系统视频教程
  夜鹰教程网  MongoDB视频教程。
  夜鹰教程网 Canvas视频教程
  夜鹰教程网 报表开发视频教程
  热点推荐
一个关于天气预报的WebService【C…
VS2010最大的新特点是并行编程的进…
TextBox控件:asp.net中如何为密码…
Web服务调用实例:实现天气预报的…
ASP.NET程序员面试试题(130道题)
ASP.NET教程:调用WebService的源码…
网站开发全程设计
据说这套.net面试题很多网络公司都…
考考你:C#常见题型及部分答案
原创:.net读取数据库sql2000
伪静态URL重写配置
配置web.config代码asp.net3.5个性…
使用线程池提高性能 Socket网络编…
ASP.NET(C#)GridView表头的增加…
如何找到正确的学习方向【.NET版】…
  最近更新
C#修改注册表demo
一个获取内容中的图片地址的方法
ASP.NET 4.0尚未在 Web 服务器上注…
四大作用域:application,session…
ConfigurationManager不存在的解决…
vs2012_vs2013_vs2015没有Web Dep…
vs2015禁用解决方案中单击打开文件…
微软为Visual Studio 2015新增安卓…
C#如何实现搜索引擎网络爬虫程序
C#中正则表达式的用法
用C#抓取需要登录的页面数据
VS2015新功能
VS2015安装图解教程
vs2015新功能介绍
vs2015安装图解

关于我们 | 网站建设 | 技术辅导 | 常见问题 | 联系我们 | 友情链接

夜鹰教程网 版权所有 www.yyjcw.com All rights reserved 备案号:蜀ICP备08011740号3