MVC Action和ActionResult

本文深入探讨了ASP.NET MVC框架中的ActionResult概念及其多种派生类的使用方法,包括ContentResult、JsonResult等,并介绍了如何通过这些类实现如RSS Feed生成、JSON响应、文件下载等功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在深入研究调用过程的细节前,先有一个总体的认识是很有帮助的。InvokeAction方法大致是按照这样的顺序进行的:

image

 

查找action:MVC内部查找action的方法似乎有点复杂,涉及到一个ActionDescriptor的东西,但是原理上是通过反射,在以后的文章中会有所涉及。

验证和过滤:众所周知的IActionFilterIAuthorizationFilter在这部分生效,它们在真正执行action之前,事实上对于IResultFilterIExceptionFilter这样的过滤器是在action执行之后执行的,图中对于这个没有画出。

执行action:真正进入用户代码执行,通过反射调用,调用之前还涉及到复杂的参数提供和绑定,在以后的文章中会涉及。

执行结果:ActionResult在这部起到了关键的作用,ActionResult有多个派生,其中最为常见的就是ViewResult。ActionResult是前面步骤执行的最终“果实”,通过执行ActionResult的ExecuteResult抽象方法,一个HttpRespose被正确的构造好,准备传回客户端。

 

从ActionResult开始说起

就像上一篇讲到的,我们可以在ControllerExecute方法中直接对HttpContext.Response操作,绕过action;即便我们走了action这一路,仍然可以在action中像下面这样直接操作Response:

1
2
3
4
5
6
7
8
9
10
11
public  class  SimpleController : Controller
{
     public  void  MyActionMethod()
     {
         Response.Write( "I'll never stop using the <blink>blink</blink> tag" );
         // ... or ...
         Response.Redirect( "/Some/Other/Url" );
         // ... or ...
         Response.TransmitFile( @"c:\files\somefile.zip" );
     }
}

然而这种方式难以维护,而且难以单元测试,于是MVC框架建议action返回ActionResult,并由框架调用ActionResultExecuteResult方法,这类似于设计模式中的command模式。你会看到这种设计模式在这里的运用实在是十分精辟的。

ActionResult是一个十足的抽象类,抽象到不能再抽象了,它定义了唯一的ExecuteResult方法,参数为一个ControllerContext,其中封装了包括HttpContext在内的许多对象,也是重写这个方法唯一的上下文信息:

1
2
3
4
5
6
7
8
9
namespace  System.Web.Mvc {
 
     public  abstract  class  ActionResult {
 
         public  abstract  void  ExecuteResult(ControllerContext context);
 
     }
 
}

MVC内置了很多实用的ActionResult,如下图:

image

我们可以看个简单的实现类RedirectResult是如何实现ExecuteResult的。在这里我发现了我曾经遇到过的一个异常的原因:Child actions are not allowed to perform redirect actions,意思是在子action不允许重定向。

1
2
3
4
5
6
7
8
9
10
11
12
public  override  void  ExecuteResult(ControllerContext context) {
     if  (context == null ) {
         throw  new  ArgumentNullException( "context" );
     }
     if  (context.IsChildAction) {
         throw  new  InvalidOperationException(MvcResources.RedirectAction_CannotRedirectInChildAction);
     }
 
     string  destinationUrl = UrlHelper.GenerateContentUrl(Url, context.HttpContext);
     context.Controller.TempData.Keep();
     context.HttpContext.Response.Redirect(destinationUrl, false  /* endResponse */ );
}

在这段代码中ExecuteResult的实现相对简单的多,事实上像ViewResult的实现就复杂很多,关于ViewResult将在视图中涉及到。

在Controller中有很多辅助方法便于我们在action中返回需要的ActionResult,下面列出:

Content():返回ContentResult

大家都很少用到这个ActionResult,因为它的基本用法是返回文本,也许下面这段代码可以说服你

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public  ContentResult RSSFeed()
{
     Story[] stories = GetAllStories(); // Fetch them from the database or wherever
  
     // Build the RSS feed document
     string  encoding = Response.ContentEncoding.WebName;
     XDocument rss = new  XDocument( new  XDeclaration( "1.0" , encoding, "yes" ),
         new  XElement( "rss" , new  XAttribute( "version" , "2.0" ),
             new  XElement( "channel" , new  XElement( "title" , "Example RSS 2.0 feed" ),
                 from  story in  stories
                 select  new  XElement( "item" ,
                       new  XElement( "title" , story.Title),
                       new  XElement( "description" , story.Description),
                       new  XElement( "link" , story.Url)
                    )
             )
         )
     );
      return  Content(rss.ToString(), "application/rss+xml" );
}

上面的代码返回了一个RSS。值得注意的是,Content的第二个参数是个contentType(MIME类型,参见www.iana.org/assignments/media-types),如果不指定这个参数将使用text/html的contentType。

事实上我们的action可以返回一个非ActionResult,MVC在执行action的返回结果前,会确保将返回值转换成一个ActionResult,其中一步,就是对非空和非ActionResult的结果转换成string,并包装成ContentResult:

1
2
3
4
5
6
7
8
9
protected  virtual  ActionResult CreateActionResult(ControllerContext controllerContext, ActionDescriptor actionDescriptor, object  actionReturnValue) {
     if  (actionReturnValue == null ) {
         return  new  EmptyResult();
     }
 
     ActionResult actionResult = (actionReturnValue as  ActionResult) ??
         new  ContentResult { Content = Convert.ToString(actionReturnValue, CultureInfo.InvariantCulture) };
     return  actionResult;
}

 

Json():返回JsonResult

Controller的Json方法能返回一个JsonResult,出于安全性的考虑JsonResult只支持POST方式,设置response.ContentType = "application/json";并利用JavaScriptSerializer序列化对象,并返回给客户端。像下面这样使用JsonResult:

1
2
3
4
5
6
7
8
9
10
11
12
class  CityData { public  string  city; public  int  temperature; }
  
[HttpPost]
public  JsonResult WeatherData()
{
     var  citiesArray = new [] {
         new  CityData { city = "London" , temperature = 68 },
         new  CityData { city = "Hong Kong" , temperature = 84 }
     };
  
     return  Json(citiesArray);
}

JavaScript():返回JavaScriptResult

JavaScript方法实例化一个JavaScriptResult,JavaScriptResult只是简单的设置response.ContentType = "application/x-javascript";

 

File():返回二进制数据或文件

FileResult是个抽象类,File方法的多个重载返回不同的FileResult:

FilePathResult:直接将一个文件发送给客户端

1
2
3
4
5
public  FilePathResult DownloadReport()
{
     string  filename = @"c:\files\somefile.pdf" ;
     return  File(filename, "application/pdf" , "AnnualReport.pdf" );
}

FileContentResult:返回byte字节给客户端比如图片

1
2
3
4
5
public  FileContentResult GetImage( int  productId)
{
     var  product = productsRepository.Products.First(x => x.ProductID == productId);
     return  File(product.ImageData, product.ImageMimeType);
}
1
< img  src="<%: Url.Action("GetImage", "Products",  new { Model.ProductID }) %>" />

FileStreamResult:返回流

1
2
3
4
5
6
public  FileStreamResult ProxyExampleDotCom()
{
     WebClient wc = new  WebClient();
     Stream stream = wc.OpenRead( "http://www.example.com/" );
     return  File(stream, "text/html" );
}

 

PartialView()和View():分别返回PartialViewResult和ViewResult

PartialViewResult和ViewResult十分复杂,涉及到视图,将在以后详细讨论。

 

Redirect():返回RedirectResult

产生重定向结果,上面已经展示了RedirectResult的实现了。

 

RedirectToAction(),RedirectToRoute():返回RedirectToRouteResult

RedirectToRouteResult同样是产生跳转的结果,但是它具有“路由表遍历能力”,也就是具有Url outbound的特点,参见深入理解ASP.NET MVC(3)

 

更多关于ActionResult的派生类的细节,可以参看MVC源码文件。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值