[译]Chapter 3 Understanding Controllers

原文:Chapter 3 - Understanding Controllers


ASP.NET MVC的控制器负责控制应用程序执行的流程。当你通过浏览器向ASP.NET MVC 应用程序发出一个请求时,控制器负责返回针对请求的响应。控制器会暴露一个或多个动作。控制器动作可以返回给浏览器不同类型的动作结果,例如,控制器动作可能返回一个视图,可能返回一个文件,或者可以重定向到另一个控制器动作。

在本章中,你会学到如何创建控制器和控制器动作,学到如何返回不同类型的控制器动作结果,你还会学到当一个特定的控制器动作被触发时,如何通过特性来控制它。最后,我们会讨论如何给你的控制器和动作编写单元测试。


Creating a Controller


创建控制器最简单的方式就是在Visual Studio解决方案窗口中,右键点击Controllers文件夹,选择菜单Add, Controller ,显示Add Controller 对话框(参见图1),如果输入ProductController ,你会得到如列表1所示的代码:


*** Begin Warning ***

控制器的名字必须以Controller后缀结束。如果你忘记包含Controller后缀,那么你就不可能调用到这个控制器。

*** End Warning ***


1 – 添加控制器对话框



列表1 – Controllers\ProductController.cs [C#]

using System;  
using System.Collections.Generic;  
using System.Linq;  
using System.Web;  
using System.Web.Mvc;  
using System.Web.Mvc.Ajax;  
#   
namespace MvcApplication1.Controllers  
# {  
#     
public class ProductController : Controller  
#     {  
#         
//  
#         // GET: /Product/  
#   
#         
public ActionResult Index()  
#         {  
#             
return View();  
#         }  
#   
#     }  
# }  

using System;

using System.Collections.Generic;

using System.Linq;

using System.Web;

using System.Web.Mvc;

using System.Web.Mvc.Ajax;

 

namespace MvcApplication1.Controllers

{

    public class ProductController : Controller

    {

        //

        // GET: /Product/

 

        public ActionResult Index()

        {

            return View();

        }

 

    }

}



列表1 – Controllers\ProductController.vb [VB]

Public Class ProductController  
#     
Inherits System.Web.Mvc.Controller  
#   
#     
'  
#     ' GET: /Product/  
#   
#     
Function Index() As ActionResult  
#         
Return View()  
#     
End Function  
#   
End Class  


请注意,控制器只是一个继承自ProductController 的类(Visual Basic或者C#类)。Public Class ProductController

    Inherits System.Web.Mvc.Controller

 

    '

    ' GET: /Product/

 

    Function Index() As ActionResult

        Return View()

    End Function

 

End Class

 

控制器中的任何一个共有方法都会被暴露为一个控制器动作。列表1中的控制器类暴露了一个动作,名为Index()Index()动作时一个默认动作,当动作没有被显示指定时,这个动作就被调用。

*** Begin Warning ***

默认情况下,控制器中包含的公共方法可以被Internet上的任意位置的人调用,所以要小心那些从控制器中暴露出来的公共方法。你可以通过为方法添加NonAction特性的方式来阻止控制器中一个公共方法被调用。

*** End Warning ***

请注意Index()动作返回一个ActionResult,控制动作总是返回一个ActionResult(即使它看起来不像返回一个ActionResult)。ActionResult决定了返回给浏览器的响应内容,Index()控制器返回一个视图,作为ActionResult

一个控制器一般会暴露多个动作。你可以通过向控制器中添加新方法的方式来向控制器添加动作。例如,列表2中经过修改的Product控制器暴露了3个动作,分别是Index()Help()Details()


列表 2 – Controllers\ProductController.cs with additional methods [C#]

using System.Web.Mvc;  
  
namespace MvcApplication1.Controllers  
{  
    
public class ProductController : Controller  
    {  
        
//  
        
// GET: /Product/  
  
        
public ActionResult Index()  
        {  
            
return View();  
        }  
  
        
//  
        
// GET: /Product/Help  
  
        
public ActionResult Help()  
        {  
            
return View();  
        }  
  
        
//  
        
// GET: /Details/1  
  
        
public ActionResult Details(int Id)  
        {  
            
return View();  
        }  
  
  
    }  

using System.Web.Mvc;

 

namespace MvcApplication1.Controllers

{

    public class ProductController : Controller

    {

        //

        // GET: /Product/

 

        public ActionResult Index()

        {

            return View();

        }

 

        //

        // GET: /Product/Help

 

        public ActionResult Help()

        {

            return View();

        }

 

        //

        // GET: /Details/1

 

        public ActionResult Details(int Id)

        {

            return View();

        }

 

 

    }

}


列表2 – Controllers\ProductController.vb with additional methods [VB]

Public Class ProductController  
    
Inherits System.Web.Mvc.Controller  
  
    
'  
    ' GET: /Product/  
  
    
Function Index() As ActionResult  
        
Return View()  
    
End Function  
  
    
'  
    ' GET: /Product/Help  
  
    
Function Help() As ActionResult  
        
Return View()  
    
End Function  
  
    
'  
    ' GET: /Details/1  
  
    
Function Details(ByVal id As IntegerAs ActionResult  
        
Return View()  
    
End Function  
  
  
End Class 

你可以向浏览器地址栏中输入以下内容来调用不同的动作:Public Class ProductController

    Inherits System.Web.Mvc.Controller

 

    '

    ' GET: /Product/

 

    Function Index() As ActionResult

        Return View()

    End Function

 

    '

    ' GET: /Product/Help

 

    Function Help() As ActionResult

        Return View()

    End Function

 

    '

    ' GET: /Details/1

 

    Function Details(ByVal id As Integer) As ActionResult

        Return View()

    End Function

 

 

End Class

 

· /Product/Index – 调用ProductController Index() 动作。

· /Product – 调用ProductController Index()动作。

· /Product/Help – 调用ProductController Help()动作。

· /Product/Details/34 – 调用ProductController Details()动作,并以“34”作为其Id参数。

你可以通过一种如下所示的特定模式来调用控制器动作:

{controller}/{action}/{id}

请注意,当你调用一个控制器时,在URL中并不包括Controller后缀。例如,调用Product控制器的URL形式为/Product/Index,而不是/ProductController/Index

Index()时默认的控制器动作,因此,/Product/Index/Product 都会调用Index()动作。

当调用控制器时,你可以提供一个可选的Id参数。例如Details动作接收一个Id参数,URL/Product/Details/2调用Details动作,并传递2作为Id参数。参数的名称很重要,你必须将其命名为Id

*** Begin Note ***

Global.asax文件中的默认路由定义了调用控制器动作的默认URL模式。如果想改变调用动作的URL模式,你需要改动默认路由。可以参考第9章来了解更多关于创建自定义路由的信息。

*** End Note


Returning Action Results


控制器动作总是返回一个ActionResultASP.NET MVC框架包括以下类型的ActionResult

· ViewResult – 表现为一个ASP.NET MVC视图。

· PartialViewResult – 表现为一个ASP.NET MVC视图的片段(局部视图)。

· RedirectResult – 表现为重定向到另外一个控制器动作或者URL

· ContentResult – 表现为一些未经处理的内容,这些内容被发送给浏览器。

· JsonResult – 表现为一个JavaScript对象符号结果(用于Ajax场景中)。

· FileResult – 表现为一个将要被下载的文件。

· EmptyResult – 表现为一个动作没有结果返回。

· HttpUnauthorizedResult – 表现为一个HTTP未授权的状态代码。

· JavaScriptResult – 表现为一个JavaScript文件。

· RedirectToRouteResult – 表现为重定向到使用路由值的另一个控制器动作或者URL

通常,你不会直接从控制器动作中返回ActionResult,而是调用一个返回ActionResult的控制器方法。例如,如果想返回一个ViewResult,你可以调用控制器的View()方法。

下面是一个可以返回ActionResult的控制器方法列表:

· View() – 返回一个ViewResult

· PartialView() –返回一个PartialViewResult

· RedirectToAction() –返回一个RedirectToRouteResult

· Redirect() –返回一个RedirectResult

· Content() –返回一个ContentResult

· Json() –返回一个JsonResult

· File() –返回一个FileResult

· JavaScript() –返回一个JavaScriptResult

· RedirectToRoute() –返回一个RedirectToRouteResult

在接下来的章节,我们会对这些ActionResult中的一些加以详细讨论。

*** Begin Note ***

在第10章理解视图母页和视图用户控件中,我们会讨论局部视图结果(AKA视图用户控件或者局部视图)。

*** End Note ***


Returning a View Result


ViewResult是控制器动作返回的最普遍的ActionResult。一个ViewResult表现为一个ASP.NET MVC视图。当想返回HTML给浏览器时,你可以选择ViewResult

列表3Customer控制器的代码,它暴露了一个名为Details()的动作,返回一个ViewResult


列表3 – Controllers\CustomerController.cs [C#]

using System.Web.Mvc;  
  
namespace MvcApplication1.Controllers  
{  
    
public class CustomerController : Controller  
    {  
  
        
public ActionResult Details()  
        {  
            
return View();  
        }  
  
    }  
}
 

using System.Web.Mvc;

 

namespace MvcApplication1.Controllers

{

    public class CustomerController : Controller

    {

 

        public ActionResult Details()

        {

            return View();

        }

 

    }

}

列表列表 列表3 – Controllers\CustomerController.vb [VB]

Public Class CustomerController  
    
Inherits System.Web.Mvc.Controller  
  
    
Function Details() As ActionResult  
        
Return View()  
    
End Function  
  
End Class 

Details()方法调用View()方法返回一个ViewResult。当调用View()方法时,你可以通过两种方式来指定视图:隐式和显式。Public Class CustomerController

    Inherits System.Web.Mvc.Controller

 

    Function Details() As ActionResult

        Return View()

    End Function

 

End Class

 

在列表3中,视图的名称是被隐式指定的。ASP.NET MVC框架从动作的名称中判断视图的名称,在这种情况下,动作返回的视图位置如下所示:

\Views\Customer\Details.aspx

ASP.NET MVC 框架按照以下的模式来决定视图的位置:

\Views\{controller}\{action}.aspx

如果你喜欢,可以显示的指定视图的名称。在列表4中,View()方法包括了一个显式的视图名称。

列表4 – Controllers\CustomerController.cs with explicit view [C#] 

using System.Web.Mvc;  
  
namespace MvcApplication1.Controllers  
{  
    
public class CustomerController : Controller  
    {  
  
        
public ActionResult Details()  
        {  
            
return View("Details");  
        }  
  
    }  

 

namespace MvcApplication1.Controllers

{

    public class CustomerController : Controller

    {

 

        public ActionResult Details()

        {

            return View("Details");

        }

 

    }

}

列表ing System.Web.Mvc;4 – Controllers\CustomerController.vb with explicit view [VB]

Public Class CustomerController  
    
Inherits System.Web.Mvc.Controller  
  
    
Function Details() As ActionResult  
        
Return View("Details")  
    
End Function  
  
End Class 

 

列表4中的View()方法返回了同一个视图。然而,它式显式制订了视图的名称,请注意,当提供视图名称时,不要包含.aspx扩展名。Public Class CustomerController

    Inherits System.Web.Mvc.Controller

 

    Function Details() As ActionResult

        Return View("Details")

    End Function

 

End Class

 

*** Begin Tip ***

如果打算为你的ASP.NET MVC应用程序构建单元测试,那么显式指定视图名称就是一个很好的方法,否则,你不能测试带有正确名称的视图是否是从控制器动作返回的。

*** End Tip ***

视图的名字中可以包含相对或者绝对路径,当你指定一个相对路径时,视图的位置是相对于普通位置来计算的。例如,我们从Details()动作来调用视图(“Subfolder/Details”)时,会从以下位置返回一个视图:

\Views\Details\Subfolder\Details.aspx

你也可以为视图指定一个绝对路径。如果你从Details()动作调用视图(“~/Details.aspx”),那么就会从以下位置返回一个视图:

\Details.aspx

请注意,当你使用绝对路径时,必须提供.aspx扩展。

View()方法有很多重载形式,可以接受不同的参数。下面的列表列出了你可以传递给View()方法的一些参数

· viewName – 视图的名称(或者视图的路径)。

· masterName – 视图母版页的名称。

· model – 传递给视图的模型类。

在第10章(理解视图母版页和视图用户控件)中,我们会讨论视图母版页,在下一章(理解视图)中,我们会讨论为视图传递模型。


Returning a Redirect Result


很多情况下,你会不得不从一个控制器动作重定向到另一个控制器动作。你可以使用RedirectToAction()方法返回一个RedirectResult,它可以将用户从一个控制器动作重定向到另外一个。

例如,列表5中的Widget控制器包含一个Details()动作,当Details()动作被调用并且没有为id参数赋值,这时用户被重定向到Index()动作。


列表5 – Controllers\WidgetController.cs [C#]

using System.Web.Mvc;  
  
namespace MvcApplication1.Controllers  
{  
    
public class WidgetController : Controller  
    {  
        
//  
        
// GET: /Widget/  
  
        
public ActionResult Index()  
        {  
            
return View();  
        }  
  
  
        
//  
        
// POST: /Widget/Create  
  
        
public ActionResult Details(int? id)  
        {  
            
if (!id.HasValue)  
                
return RedirectToAction("Index");  
  
            
return View();  
        }  
  
  
    }  

 

 

namespace MvcApplication1.Controllers

{

    public class WidgetController : Controller

    {

        //

        // GET: /Widget/

 

        public ActionResult Index()

        {

            return View();

        }

 

 

        //

        // POST: /Widget/Create

 

        public ActionResult Details(int? id)

        {

            if (!id.HasValue)

                return RedirectToAction("Index");

 

            return View();

        }

 

 

    }

}

列表using System.Web.Mvc;5 – Controllers\WidgetController.cs [VB]

Public Class WidgetController  
    
Inherits System.Web.Mvc.Controller  
  
    
Function Index() As ActionResult  
        
Return View()  
    
End Function  
  
  
    
Function Details(ByVal id As Integer?) As ActionResult  
        
If Not id.HasValue Then  
            
Return RedirectToAction("Index")  
        
End If  
  
        
Return View()  
    
End Function  
  
  
End Class 

Public Class WidgetController

    Inherits System.Web.Mvc.Controller

 

    Function Index() As ActionResult

        Return View()

    End Function

 

 

    Function Details(ByVal id As Integer?) As ActionResult

        If Not id.HasValue Then

            Return RedirectToAction("Index")

        End If

 

        Return View()

    End Function

 

 

End Class

*** Begin Note ***

列表5中的id参数的类型是可空类型。一个可空整型可以是任何整数或者null。你可以通过在类型关键字后加?的方式创建一个可空类型。

*** End Note ***

RedirectToAction()方法有多个重载形式,以下的列表说明了使用RedirectToAction()时所有可能的参数:

· actionName – 控制器动作的名称。

· controllerName – 控制器的名字。

· routeValues – 传递给动作的路由值。

你可以使用controllerName参数来将程序从一个控制器动作重定向到另外一个控制器,当你指定controllerName时,不要包含Controller后缀。例如,使用Product而不是ProductController

[C#]

return RedirectToAction(“Index”, “Product”);

[VB]

Return RedirectToAction(“Index”, “Product”)

当你需要传递id到控制器动作时,提供路由值时特别重要的。例如,想象一下,当你想从其他某一个控制器动作重定向到Details()动作并且为id参数传递一个值,在这种情况下,你可以像以下这种方式来调用RedirectToAction()

[C#]

return RedirectToAction(“Details”, new {id=53});

[VB]

Return RedirectToAction(“Details”, New With {.id=53})

这种调用RedirectToAction()方法的形式将53作为id参数传递给Index()动作。

*** Begin Note ***

RedirectToAction()方法的返回一个302 FoundHTTP状态码给浏览器来执行重定向到新动作的操作。执行浏览器重定向的一个好处是它可以将浏览器的地址栏更新成新的URL地址。

*** End Note ***

Returning a Content Result

列表6Hello控制器暴露出的Say()动作没有返回ActionResult,相反,这个动作返回了一个字符串,如果你调用这个动作,那么字符串就被返回给浏览器(参见图2

图 2 – Results of invoking Say() action



列表6 – Controllers\HelloController.cs [C#]

using System.Web.Mvc;  
  
namespace MvcApplication1.Controllers  
{  
    
public class HelloController : Controller  
    {  
  
        
public string Say()  
        {  
            
return "Hello";  
        }  
  
    }  

 

 

namespace MvcApplication1.Controllers

{

    public class HelloController : Controller

    {

 

        public string Say()

        {

            return "Hello";

        }

 

    }

}

列表using System.Web.Mvc;6 – Controllers\HelloController.vb [VB]

Public Class HelloController  
    
Inherits System.Web.Mvc.Controller  
  
    
Function Say() As String  
        
Return "Hello!"  
    
End Function  
  
End Class
 

动作方法也可以返回日期值、整型值或者任何.NET框架支持的类型值。

    Inherits System.Web.Mvc.Controller

 

    Function Say() As String

        Return "Hello!"

    End Function

 

End Class

 

在这背后,ASP.NET MVC框架将任何不是ActionResult类型的值转换为ActionResult类型。特别的,ASP.NET MVC框架将任何不是ActionResult类型的值转换为ContentResult类型,它通过对值调用ToString()方法 将其包装成ContentResult类型。

如果你愿意,你可以像这样显示的返回一个ContentResult

[C#]

public ActionResult Say()  
{  
  
return Content("Hello!");  
}  


public ActionResult Say()

{

  return Content("Hello!");

}

[VB]

Function Say() As ActionResult

   Return Content("Hello!")

End Function

Function Say() As ActionResult  
   
Return Content("Hello!")  
End Function 

 

Content()方法有多个重载形式,下表列出了你可以传递给这个方法的所有参数形式:

· string – 要提交给浏览器的字符串。

· contentType – 内容的MIME类型(默认是text/html)。

· contentEncoding – 内容的文本编码格式(例如Unicode或者ASCII)。


Returning a JSON Result


JavaScript对象符号(JSON)由Douglas Crockford发明,主要作为XML的一种替代方案,在AJAX应用程序中传递数据。例如,你可以将一个数据库记录的集合转换成JSON形式,将其从服务器端传给浏览器。

*** Begin Note ***

你可以通过访问JSON.org来了解更多JSON相关信息。

*** End Note ***

你可以通过调用Json()方法从动作中返回JSON。例如,列表7所示的控制器返回了一个引用语的集合。


列表7 – Controllers\QuotationController.cs [C#]

using System.Collections.Generic;  
using System.Web.Mvc;  
  
namespace MvcApplication1.Controllers  
{  
    
public class QuotationController : Controller  
    {  
  
        
public ActionResult Index()  
        {  
            
return View();  
        }  
  
  
        
public ActionResult List()  
        {  
            var quotes 
= new List<string>  
            {  
                
"Look before you leap",  
                
"The early bird gets the worm",  
                
"All hat, no cattle"  
            };  
              
            
return Json(quotes);  
        }  
  
    }  
}
 

using System.Web.Mvc;

 

namespace MvcApplication1.Controllers

{

    public class QuotationController : Controller

    {

 

        public ActionResult Index()

        {

            return View();

        }

 

 

        public ActionResult List()

        {

            var quotes = new List<string>

            {

                "Look before you leap",

                "The early bird gets the worm",

                "All hat, no cattle"

            };

           

            return Json(quotes);

        }

 

    }

}

using System.Collections.Generic;列表7 – Controllers\QuotationController.vb [VB]

Public Class QuotationController  
    
Inherits System.Web.Mvc.Controller  
  
    
Function Index() As ActionResult  
        
Return View()  
    
End Function  
  
  
    
Function List() As ActionResult  
        
Dim quotes As New List(Of String)()  
        quotes.Add(
"Look before you leap")  
        quotes.Add(
"The early bird gets the worm")  
        quotes.Add(
"All hat, no cattle")  
  
        
Return Json(quotes)  
    
End Function  
  
  
End Class
 

Public Class QuotationController

    Inherits System.Web.Mvc.Controller

 

    Function Index() As ActionResult

        Return View()

    End Function

 

 

    Function List() As ActionResult

        Dim quotes As New List(Of String)()

        quotes.Add("Look before you leap")

        quotes.Add("The early bird gets the worm")

        quotes.Add("All hat, no cattle")

 

        Return Json(quotes)

    End Function

 

 

End Class

*** Begin Note ***

在这背后,Json()方法使用.NET框架中一个被称为JavaScriptSerializer的类将一个对象序列化为JSON形式。你可以通过注册自定义转换器的方式来控制如何序列化对象。

*** End Note ***

List()动作被调用时,它返回了如下所示的一个JSON形式的引用语集合:

["Look before you leap", "The early bird gets the worm", "All hat, no cattle"]

你可以通过执行一个针对服务器的AjaX请求来调用Index()方法。列表8中的视图捕捉到了引用语集合并且随机的显示它们。

图 3 – Using JSON to retrieve quotations.



列表8 – Views\Quotation\Index.aspx

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %>  
  
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">  
  
  
<script src="http://www.cnblogs.com/Scripts/jquery-1.2.6.js" type="text/javascript"></script>  
  
  
<script type="text/javascript">  
  
      $(getQuote);  
  
      
function getQuote() {  
          $.getJSON(
"Quotation/List", showQuote);  
      }  
        
      
function showQuote(data) {  
          
var index = Math.floor(Math.random() * 3);  
          $(
"#quote").text(data[index]);        
      }  
        
  
</script>    
  
      
  
<id="quote"></p>  
  
  
<button onclick="getQuote()">Get Quote</button>    
  
  
</asp:Content>

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %>

 

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">

 

  <script src="http://www.cnblogs.com/Scripts/jquery-1.2.6.js" type="text/javascript"></script>

 

  <script type="text/javascript">

 

      $(getQuote);

 

      function getQuote() {

          $.getJSON("Quotation/List", showQuote);

      }

     

      function showQuote(data) {

          var index = Math.floor(Math.random() * 3);

          $("#quote").text(data[index]);     

      }

     

  </script> 

 

   

  <p id="quote"></p>

 

  <button onclick="getQuote()">Get Quote</button> 

 

 

</asp:Content>

*** Begin Note ***

列表8中的视图采用jQuery来从服务器返回JSON结果,我们会在第17章(使用jQuery)中详细讨论jQuery

*** End Note ***

Json()方法有一些重载形式,它支持以下的这些参数形式:

· data – 需要进行序列化的内容。

· contentType – 内容的MIME类型(默认时application.json)。

· contentEncoding – 内容的文本编码格式(例如Unicode或者ASCII)。


Returning a File Result


你可以从动作中返回一个文件。例如,你可以返回一个图片文件、一个Microsoft Word文档或者一个Microsoft Excel文件。

列表9所示的控制器中暴露两个动作,分别时Index()Download()Index()显示一个视图,它包含一个指向Download()动作的链接,当点击这个链接时,你会看到一个浏览或者保存文件的对话框(参见图4)。


4 – 下载一个文件



列表9 – Controllers\ContentManagerController.cs [C#]

using System.Web.Mvc;  
  
namespace MvcApplication1.Controllers  
{  
    
public class ContentManagerController : Controller  
    {  
   
        
public ActionResult Index()  
        {  
            
return View();  
        }  
  
  
        
public ActionResult Download()  
        {  
             
            
return File("~/Content/CompanyPlans.docx""application/vnd.openxmlformats-officedocument.wordprocessingml.document""CompanyPlans.docx");  
        }  
  
    }  
}
 

 

namespace MvcApplication1.Controllers

{

    public class ContentManagerController : Controller

    {

 

        public ActionResult Index()

        {

            return View();

        }

 

 

        public ActionResult Download()

        {

          

            return File("~/Content/CompanyPlans.docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "CompanyPlans.docx");

        }

 

    }

}

列表using System.Web.Mvc;9 – Controllers\ContentManagerController.vb [VB]

Public Class ContentManagerController  
    
Inherits System.Web.Mvc.Controller  
  
  
    
Function Index()  
        
Return View()  
    
End Function  
  
    
Function Download() As ActionResult  
        
Return File("~/Content/CompanyPlans.docx""application/vnd.openxmlformats-officedocument.wordprocessingml.document""CompanyPlans.docx")  
    
End Function  
  
End Class 

 

Download()动作返回一个名为CompanyPlans.docxMicrosoft Word文档。请注意,File()方法需要3个参数:文档的路径、文档的内容类型以及文档的名字。Microsoft Word DOCX文档的MIME类型是:Public Class ContentManagerController

    Inherits System.Web.Mvc.Controller

 

 

    Function Index()

        Return View()

    End Function

 

    Function Download() As ActionResult

        Return File("~/Content/CompanyPlans.docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "CompanyPlans.docx")

    End Function

 

End Class

 

application/vnd.openxmlformats-officedocument.wordprocessingml.document

File()方法有多个重载形式,它可以接受以下参数:

· filename – 需要下载的文档的路径。

· contentType – 需要下载的文档的MIME类型。

· fileDownloadName – 文档的名字,它会显示在浏览器对话中。

· fileContents – 如果不提供需要下载的文件的路径,你可以以字节数组的形式提供文件的实际内容。

· fileStream – 如果不提供需要下载的文件的路径,你可以以文件流的形式提供文件的实际内容。

*** Begin Note ***

File()方法使用HTTP头中的Content-Disposition 来设置下载文件的名字。

*** End Note ***


Controlling How Actions are Invoked


ASP.NET MVC框架关于如何调用动作的默认算法非常简单。例如,当你输入/Product/Details时,ProductController控制器中的Details()方法被执行。

然而,事情很快就变得更复杂。当你有多个方法的名字相同时会发生什么情况?在需要传递表单数据或者其他内容时如何调用动作?当做出一个AjaX请求时如何调用一个特定的动作?

在本节中,你会了解到如何使用AcceptVerbsActionName以及ActionMethodSelector特性来调用一个特定的动作。


Using AcceptVerbs


AcceptVerbs特性可以是你能够让一个动作只在执行特定的HTTP操作时才能被调用。例如,你可以使用AcceptVerbs特性来使得一个动作只能在执行HTTP POST操作才能被调用。

列表10中的Employees控制器暴露了两个名为Create()的动作,第一个动作用来显示一个创建新员工的HTML表单,第二个动作向数据库中插入一个新员工。

两个Create()都装饰了AcceptVerbs特性。第一个动作只能在执行HTTP GET操作时才能被调用,第二个动作只能在执行HTTP POST操作时才能被调用。


列表10 – Controllers\EmployeeController.cs [C#]

using System.Web.Mvc;  
using MvcApplication1.Models;  
  
namespace MvcApplication1.Controllers  
{  
    
public class EmployeeController : Controller  
    {  
        
private EmployeeRepository _repository = new EmployeeRepository();  
  
        
// GET: /Employee/  
        public ActionResult Index()  
        {  
            
return View();  
        }   
  
        
// GET: /Employee/Create  
        [AcceptVerbs(HttpVerbs.Get)]  
        
public ActionResult Create()  
        {  
            
return View();  
        }   
  
        
// POST: /Employee/Create  
        [AcceptVerbs(HttpVerbs.Post)]  
        
public ActionResult Create(Employee employeeToCreate)  
        {  
            
try  
            {  
                _repository.InsertEmployee(employeeToCreate);  
                
return RedirectToAction("Index");  
            }  
            
catch  
            {  
                
return View();  
            }  
        }  
  
        
// DELETE: /Employee/Delete/1  
        [AcceptVerbs(HttpVerbs.Delete)]  
        
public ActionResult Delete(int id)  
        {  
            _repository.DeleteEmployee(id);  
            
return Json(true);  
        }  
  
    }  

 

using MvcApplication1.Models;

 

namespace MvcApplication1.Controllers

{

    public class EmployeeController : Controller

    {

        private EmployeeRepository _repository = new EmployeeRepository();

 

        // GET: /Employee/

        public ActionResult Index()

        {

            return View();

        }

 

        // GET: /Employee/Create

        [AcceptVerbs(HttpVerbs.Get)]

        public ActionResult Create()

        {

            return View();

        }

 

        // POST: /Employee/Create

        [AcceptVerbs(HttpVerbs.Post)]

        public ActionResult Create(Employee employeeToCreate)

        {

            try

            {

                _repository.InsertEmployee(employeeToCreate);

                return RedirectToAction("Index");

            }

            catch

            {

                return View();

            }

        }

 

        // DELETE: /Employee/Delete/1

        [AcceptVerbs(HttpVerbs.Delete)]

        public ActionResult Delete(int id)

        {

            _repository.DeleteEmployee(id);

            return Json(true);

        }

 

    }

}

列表using System.Web.Mvc;10 – Controllers\EmployeeController.vb [VB]

Public Class EmployeeController  
    
Inherits System.Web.Mvc.Controller  
  
    
Private _repository As New EmployeeRepository()  
  
    
' GET: /Employee/Create  
    Function Index() As ActionResult  
        
Return View()  
    
End Function  
  
  
    
' GET: /Employee/Create  
    <AcceptVerbs(HttpVerbs.Get)> _  
    
Function Create() As ActionResult  
        
Return View()  
    
End Function  
  
    
' POST: /Employee/Create  
    <AcceptVerbs(HttpVerbs.Post)> _  
    
Function Create(ByVal employeeToCreate As Employee) As ActionResult  
        
Try  
            _repository.InsertEmployee(employeeToCreate)  
            
Return RedirectToAction("Index")  
        
Catch  
            
Return View()  
        
End Try  
    
End Function  
  
    
' DELETE: /Employee/Create  
    <AcceptVerbs(HttpVerbs.Delete)> _  
    
Function Delete(ByVal id As IntegerAs ActionResult  
        _repository.DeleteEmployee(id)  
        
Return Json(True)  
    
End Function  
  
  
End Class  


大部分人都很熟悉HTTP GETHTTP POST,当你任何时候通过在浏览器中输入地址的方式来获取一个网站的网页时,就是在执行HTTP GET 操作;当你提交一个HTML表单并带有”method = post”特性时,就是在执行HTTP POST操作。Public Class EmployeeController

    Inherits System.Web.Mvc.Controller

 

    Private _repository As New EmployeeRepository()

 

    ' GET: /Employee/Create

    Function Index() As ActionResult

        Return View()

    End Function

 

 

    ' GET: /Employee/Create

    <AcceptVerbs(HttpVerbs.Get)> _

    Function Create() As ActionResult

        Return View()

    End Function

 

    ' POST: /Employee/Create

    <AcceptVerbs(HttpVerbs.Post)> _

    Function Create(ByVal employeeToCreate As Employee) As ActionResult

        Try

            _repository.InsertEmployee(employeeToCreate)

            Return RedirectToAction("Index")

        Catch

            Return View()

        End Try

    End Function

 

    ' DELETE: /Employee/Create

    <AcceptVerbs(HttpVerbs.Delete)> _

    Function Delete(ByVal id As Integer) As ActionResult

        _repository.DeleteEmployee(id)

        Return Json(True)

    End Function

 

 

End Class

 

大部分都没有意识到HTTP协议支持很多额外的HTTP操作类型:

· OPTIONS – 返回可用的通信选项的信息。

· GET – 返回请求可以识别出来的所有信息。

· HEAD – 除了不返回消息体外,其他操作和GET一样。

· POST – 公告新的信息或者更新已经存在的信息。

· PUT – 公告新的信息或者更新已经存在的信息。

· DELETE – 删除信息。

· TRACE – 执行消息跟踪。

· CONNECT – 用来做SSL通道。

*** Begin Note **

HTTP操作是作为HTTP1.1标准的一部分存在的,你可以通过以下地址来获取HTTP 1.1标准的信息: http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html.

*** End Note ***

在执行Ajax请求时,你可以选择这些额外的HTTP操作。列表10中的控制器包括Delete()动作,它只能在执行HTTP DELETE操作时被调用,列表11中的视图包含了一个删除链接,它使用Ajax来执行一个HTTP DELETE操作。

列表11 – Views\Employee\Delete.aspx [C#]

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %>  
  
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">  
  
    
<script src="http://www.cnblogs.com/Scripts/MicrosoftAjax.js" type="text/javascript"></script>  
    
<script src="http://www.cnblogs.com/Scripts/MicrosoftMvcAjax.js" type="text/javascript"></script>  
  
    
<h2>Index</h2>  
  
    
<%= Ajax.ActionLink  
        (  
            
"Delete",   // link text   
            
"Delete",   // action name  
            
new {id=39}, // route values  
            
new AjaxOptions {HttpMethod="DELETE", Confirm="Delete Employee?"}  
        ) 
%>  
  
</asp:Content>

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %>

 

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">

 

    <script src="http://www.cnblogs.com/Scripts/MicrosoftAjax.js" type="text/javascript"></script>

    <script src="http://www.cnblogs.com/Scripts/MicrosoftMvcAjax.js" type="text/javascript"></script>

 

    <h2>Index</h2>

 

    <%= Ajax.ActionLink

        (

            "Delete",   // link text

            "Delete",   // action name

            new {id=39}, // route values

            new AjaxOptions {HttpMethod="DELETE", Confirm="Delete Employee?"}

        ) %>

 

</asp:Content>


列表11 – Views\Employee\Delete.aspx [VB]

<%@ Page Title="" Language="VB" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %>  
  
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">  
  
    
<script src="http://www.cnblogs.com/Scripts/MicrosoftAjax.js" type="text/javascript"></script>  
    
<script src="http://www.cnblogs.com/Scripts/MicrosoftMvcAjax.js" type="text/javascript"></script>  
  
    
<h2>Index</h2>  
  
    
<%=Ajax.ActionLink( _  
        
"Delete", _  
        
"Delete", _  
        
New With {.id = 39}, _  
        
New AjaxOptions With {.HttpMethod = "DELETE", .Confirm = "Delete Employee?"} _  
    )
%>  
      
      
    
<%=DateTime.Now%>  
  
</asp:Content>

<%@ Page Title="" Language="VB" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %>

 

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">

 

    <script src="http://www.cnblogs.com/Scripts/MicrosoftAjax.js" type="text/javascript"></script>

    <script src="http://www.cnblogs.com/Scripts/MicrosoftMvcAjax.js" type="text/javascript"></script>

 

    <h2>Index</h2>

 

    <%=Ajax.ActionLink( _

        "Delete", _

        "Delete", _

        New With {.id = 39}, _

        New AjaxOptions With {.HttpMethod = "DELETE", .Confirm = "Delete Employee?"} _

    )%>

   

   

    <%=DateTime.Now%>

 

</asp:Content>

在列表11中,Ajax.ActionLink()方法帮助提交一个链接,用来执行一个HTTP DELETE操作。这个链接删除了Id39的员工,你可以在Firebug中证实这个链接执行了HTTP DELETE操作。


5 – 执行一个HTTP DELETE操作



*** Begin Note ***

Firebug是一个用于调试Ajax应用程序的重要工具,它是Mozilla Firefox的一个扩展,你可以从http://getFirebug.com.上下载到。

*** End Note ***


Using ActionName


ActionName特性使你能够暴露一个和方法名不同的动作。它主要用于以下两种情况:

首先,当控制器有重载方法时,你可以使用ActionName特性来区分这两个方法,换言之,你可以使用ActionName特性来将两个具有相同名字的方法暴露成具有不同名字的动作。

例如,假设你创建了一个Product控制器,它有两个名为Details()的重载方法,第一个方法接受一个id参数,第二个不接受,在这种情况下,你可以使用ActionName特性来区分这两个Details()方法,可以将这两个方法暴露成两个具有不同名字的动作。

其次,当一个控制器包括不同名字的方法,但是你想将这些不同的方法暴露成同一个动作时,ActionName特性也是很有用的。例如,列表12中的控制器暴露了两个动作,名为Edit(),它们接受相同的参数:

列表12 – Controllers\MerchandiseController.cs [C#]

using System.Web.Mvc;  
using MvcApplication1.Models;  
  
namespace MvcApplication1.Controllers  
{  
    
public class MerchandiseController : Controller  
    {  
        
private MerchandiseRepository _repository = new MerchandiseRepository();  
  
        
// GET: /Merchandise/Edit  
        [ActionName("Edit")]  
        [AcceptVerbs(HttpVerbs.Get)]  
        
public ActionResult Edit_GET(Merchandise merchandiseToEdit)  
        {  
            
return View(merchandiseToEdit);  
        }  
  
        
// POST: /Merchandise/Edit  
        [ActionName("Edit")]  
        [AcceptVerbs(HttpVerbs.Post)]  
        
public ActionResult Edit_POST(Merchandise merchandiseToEdit)  
        {  
            
try  
            {  
                _repository.Edit(merchandiseToEdit);  
                
return RedirectToAction("Edit");  
            }  
            
catch  
            {  
                
return View();  
            }  
        }  
    }  

 

列表12 – Controllers\MerchandiseController.vb [VB]

Public Class MerchandiseController  
    
Inherits System.Web.Mvc.Controller  
  
    
Private _repository As New MerchandiseRepository()  
  
    
' GET: /Merchandise/Edit  
    <ActionName("Edit")> _  
    
<AcceptVerbs(HttpVerbs.Get)> _  
    
Function Edit_GET(ByVal merchandiseToEdit As Merchandise) As ActionResult  
        
Return View(merchandiseToEdit)  
    
End Function  
  
    
' POST: /Merchandise/Edit  
    <ActionName("Edit")> _  
    
<AcceptVerbs(HttpVerbs.Post)> _  
    
Function Edit_POST(ByVal merchandiseToEdit As Merchandise) As ActionResult  
        
Try  
            _repository.Edit(merchandiseToEdit)  
            
Return RedirectToAction("Edit")  
        
Catch  
            
Return View()  
        
End Try  
    
End Function  
  
End Class 

你不能在一个类中拥有两个具有相同名字和相同参数的方法,然而,你可以拥有两个具有相同名称和相同参数的动作。Public Class MerchandiseController

    Inherits System.Web.Mvc.Controller

 

    Private _repository As New MerchandiseRepository()

 

    ' GET: /Merchandise/Edit

    <ActionName("Edit")> _

    <AcceptVerbs(HttpVerbs.Get)> _

    Function Edit_GET(ByVal merchandiseToEdit As Merchandise) As ActionResult

        Return View(merchandiseToEdit)

    End Function

 

    ' POST: /Merchandise/Edit

    <ActionName("Edit")> _

    <AcceptVerbs(HttpVerbs.Post)> _

    Function Edit_POST(ByVal merchandiseToEdit As Merchandise) As ActionResult

        Try

            _repository.Edit(merchandiseToEdit)

            Return RedirectToAction("Edit")

        Catch

            Return View()

        End Try

    End Function

 

End Class

 

列表12中的两个Edit()动作时通过AcceptVerbs特性来进行区分的。第一个Edit()动作只能在执行HTTP GET操作时被调用,第二个只能在执行HTTP POST操作时被调用,ActionName特性使得你能够以相同的名字暴露这两个动作。


Using ActionMethodSelector


你可以构建你自己的特性来控制如何调用控制器动作,通过继承抽象类ActionMethodSelectorAttribute ,你可以构建自己的特性。

这是一个非常简单的类,它有一个简单的名为IsValidForRequest ()的方法,这是你必须要实现的,如果这个方法返回false,那么动作方法就不会被调用。

在实现IsValidForRequest ()方法时,你可以使用任何你想用的标准,这些标准可以是时间、随机数字生成器或者当前外面的温度等等。关于如何使用ActionMethod特性,列表13中的AjaxMethod特性时一个更加实用的列子,AjaxMethod特性保证一个方法只有在执行Ajax请求时才被调用。

列表13 – Selectors\AjaxMethodAttribute.cs [C#]

using System.Reflection;  
using System.Web.Mvc;  
  
namespace MvcApplication1.Selectors  
{  
    
public class AjaxMethod : ActionMethodSelectorAttribute  
    {  
        
public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo)  
        {  
            
return controllerContext.HttpContext.Request.IsAjaxRequest();  
        }  
    }  
}
 

using System.Web.Mvc;

 

namespace MvcApplication1.Selectors

{

    public class AjaxMethod : ActionMethodSelectorAttribute

    {

        public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo)

        {

            return controllerContext.HttpContext.Request.IsAjaxRequest();

        }

    }

}

using System.Reflection;列表13 – Selectors\AjaxMethodAttribute.vb [VB]

Imports System.Reflection  
  
Public Class AjaxMethodAttribute  
    
Inherits ActionMethodSelectorAttribute  
  
    
Public Overrides Function IsValidForRequest(ByVal controllerContext As ControllerContext, ByVal methodInfo As MethodInfo) As Boolean  
        
Return controllerContext.HttpContext.Request.IsAjaxRequest  
    
End Function  
  
End Class 

 

列表13中的选择器简单的以IsAjaxRequest()方法的返回值作为选择标准返回。Imports System.Reflection

 

Public Class AjaxMethodAttribute

    Inherits ActionMethodSelectorAttribute

 

    Public Overrides Function IsValidForRequest(ByVal controllerContext As ControllerContext, ByVal methodInfo As MethodInfo) As Boolean

        Return controllerContext.HttpContext.Request.IsAjaxRequest

    End Function

 

End Class

列表14表明如何实用AjaxMethod特性。

列表14 – Controllers\NewsController.cs [C#]

using System;  
using System.Collections.Generic;  
using System.Web.Mvc;  
using MvcApplication1.Selectors;  
  
namespace MvcApplication1.Controllers  
{  
    
public class NewsController : Controller  
    {  
        
private readonly List<string> _news = new List<string>();  
        
private Random _rnd = new Random();  
  
        
public NewsController()  
        {  
            _news.Add(
"Moon explodes!");  
            _news.Add(
"Stock market up 200 percent!");  
            _news.Add(
"Talking robot created!");  
        }  
  
        
public ActionResult Index()  
        {  
            var selectedIndex 
= _rnd.Next(_news.Count);  
            ViewData.Model 
= _news[selectedIndex];  
            
return View();  
        }  
  
  
        [AjaxMethod]  
        [ActionName(
"Index")]  
        
public string Index_AJAX()  
        {  
            var selectedIndex 
= _rnd.Next(_news.Count);  
            
return _news[selectedIndex];  
        }  
  
  
    }  

 

using System.Collections.Generic;

using System.Web.Mvc;

using MvcApplication1.Selectors;

 

namespace MvcApplication1.Controllers

{

    public class NewsController : Controller

    {

        private readonly List<string> _news = new List<string>();

        private Random _rnd = new Random();

 

        public NewsController()

        {

            _news.Add("Moon explodes!");

            _news.Add("Stock market up 200 percent!");

            _news.Add("Talking robot created!");

        }

 

        public ActionResult Index()

        {

            var selectedIndex = _rnd.Next(_news.Count);

            ViewData.Model = _news[selectedIndex];

            return View();

        }

 

 

        [AjaxMethod]

        [ActionName("Index")]

        public string Index_AJAX()

        {

            var selectedIndex = _rnd.Next(_news.Count);

            return _news[selectedIndex];

        }

 

 

    }

}

using System;列表14 – Controllers\NewsController.vb [VB]

Public Class NewsController  
    
Inherits System.Web.Mvc.Controller  
  
    
Private ReadOnly _news As New List(Of String)  
    
Private _rnd As New Random()  
  
    
Sub New()  
        _news.Add(
"Moon explodes!")  
        _news.Add(
"Stock market up 200 percent!")  
        _news.Add(
"Talking robot created!")  
    
End Sub  
  
    
Function Index() As ActionResult  
        
Dim selectedIndex = _rnd.Next(_news.Count)  
        ViewData.Model 
= _news(selectedIndex)  
        
Return View()  
    
End Function  
  
  
    
<AjaxMethod()> _  
    
<ActionName("Index")> _  
    
Function Index_AJAX() As String  
        
Dim selectedIndex = _rnd.Next(_news.Count)  
        
Return _news(selectedIndex)  
    
End Function  
  
  
End Class

列表14中的控制器暴露了两个名为Index()的动作,第一个Index()动作可以被一个普通的浏览器请求调用,第二个动作可以被一个Ajax请求调用。Public Class NewsController

    Inherits System.Web.Mvc.Controller

 

    Private ReadOnly _news As New List(Of String)

    Private _rnd As New Random()

 

    Sub New()

        _news.Add("Moon explodes!")

        _news.Add("Stock market up 200 percent!")

        _news.Add("Talking robot created!")

    End Sub

 

    Function Index() As ActionResult

        Dim selectedIndex = _rnd.Next(_news.Count)

        ViewData.Model = _news(selectedIndex)

        Return View()

    End Function

 

 

    <AjaxMethod()> _

    <ActionName("Index")> _

    Function Index_AJAX() As String

        Dim selectedIndex = _rnd.Next(_news.Count)

        Return _news(selectedIndex)

    End Function

 

 

End Class

 

AjaxMethod特性被应用于第二个Index()动作,如果这个动作不被装饰AjaxMethod特性,那么你会得到一个Ambiguous Match Exception ,因为ASP.NET MVC框架不能确定要调用哪一个动作(参见图6)


6 – An Ambiguous Match Exception


列表15中的视图实用Ajax.ActionLink()方法来提交Get News 链接,链接用于显示新闻。如果你使用上层浏览器——一种支持基本JavaScript的浏览器——这时点击链接来向服务器执行一个Ajax请求,带有AjaxMethod特性的Index()方法被调用,页面在没有回传的情况下被更新。

另一方面,如果你使用下层浏览器——一种不支持基本JavaScript的浏览器——这是点击Get News 链接来执行一个通常的回传操作,页面也会更新一个新项目,但是用户必须忍受糟糕的带有回传的用户体验(参见图7)。


7 – 显示新闻



列表15 – Views\News\Index.aspx [C#]

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %>  
  
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">  
  
    
<script src="http://www.cnblogs.com/Scripts/MicrosoftAjax.js" type="text/javascript"></script>  
    
<script src="http://www.cnblogs.com/Scripts/MicrosoftMvcAjax.js" type="text/javascript"></script>  
  
    
<%=Ajax.ActionLink("Get News""Index"new AjaxOptions {UpdateTargetId = "news"})%>  
  
    
<span id="news"></span>  
  
</asp:Content> 

 

 

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">

 

    <script src="http://www.cnblogs.com/Scripts/MicrosoftAjax.js" type="text/javascript"></script>

    <script src="http://www.cnblogs.com/Scripts/MicrosoftMvcAjax.js" type="text/javascript"></script>

 

    <%=Ajax.ActionLink("Get News", "Index", new AjaxOptions {UpdateTargetId = "news"})%>

 

    <span id="news"></span>

 

</asp:Content>

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %>列表15 – Views\News\Index.aspx [VB]

<%@ Page Title="" Language="VB" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %>  
  
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">  
  
    
<script src="http://www.cnblogs.com/Scripts/MicrosoftAjax.js" type="text/javascript"></script>  
    
<script src="http://www.cnblogs.com/Scripts/MicrosoftMvcAjax.js" type="text/javascript"></script>  
  
    
<%=Ajax.ActionLink("Get News""Index"New AjaxOptions With {.UpdateTargetId = "news"})%>  
  
    
<span id="news"></span>  
  
</asp:Content> 

<%@ Page Title="" Language="VB" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %>

 

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">

 

    <script src="http://www.cnblogs.com/Scripts/MicrosoftAjax.js" type="text/javascript"></script>

    <script src="http://www.cnblogs.com/Scripts/MicrosoftMvcAjax.js" type="text/javascript"></script>

 

    <%=Ajax.ActionLink("Get News", "Index", New AjaxOptions With {.UpdateTargetId = "news"})%>

 

    <span id="news"></span>

 

</asp:Content>


Handling Unknown Actions


控制器包含一个名为HandleUnknownAction ()的特殊方法,当控制器找不到和浏览器请求相匹配的动作时,会自动调用这个方法。例如,如果你请求的URL地址为/Product/DoSomethingCrazy ,而Product控制器中并不包含名为DoSomethingCrazy ()的方法,这时,Product控制器的HandleUnknownAction ()被调用。

默认情况下,这个方法抛出一个404 Resource Not Found 的异常,然而,你可以重写这个方法使其做任何你想做的。例如,列表16中的控制器显示了一个自定义错误信息。


列表16 – Controllers\CatalogController.cs [C#]

using System.Web.Mvc;  
  
namespace MvcApplication1.Controllers  
{  
    
public class CatalogController : Controller  
    {  
         
        
public ActionResult Create()  
        {  
            
return View();  
        }  
  
        
public ActionResult Delete(int id)  
        {  
            
return View();  
        }  
  
  
        
protected override void HandleUnknownAction(string actionName)  
        {  
            ViewData[
"actionName"= actionName;  
            View(
"Unknown").ExecuteResult(this.ControllerContext);  
        }  
  
  
    }  

 

 

namespace MvcApplication1.Controllers

{

    public class CatalogController : Controller

    {

      

        public ActionResult Create()

        {

            return View();

        }

 

        public ActionResult Delete(int id)

        {

            return View();

        }

 

 

        protected override void HandleUnknownAction(string actionName)

        {

            ViewData["actionName"] = actionName;

            View("Unknown").ExecuteResult(this.ControllerContext);

        }

 

 

    }

}

using System.Web.Mvc;列表16 – Controllers\CatalogController.vb [VB]

Public Class CatalogController  
    
Inherits System.Web.Mvc.Controller  
  
  
    
Function Create() As ActionResult  
        
Return View()  
    
End Function  
  
    
Function Delete() As ActionResult  
        
Return View()  
    
End Function  
  
    
Protected Overrides Sub HandleUnknownAction(ByVal actionName As String)  
        ViewData(
"actionName"= actionName  
        View(
"Unknown").ExecuteResult(Me.ControllerContext)  
    
End Sub  
  
  
End Class 

    Inherits System.Web.Mvc.Controller

 

 

    Function Create() As ActionResult

        Return View()

    End Function

 

    Function Delete() As ActionResult

        Return View()

    End Function

 

    Protected Overrides Sub HandleUnknownAction(ByVal actionName As String)

        ViewData("actionName") = actionName

        View("Unknown").ExecuteResult(Me.ControllerContext)

    End Sub

 

 

End Class

如果你请求的URLublic Class CatalogController地址是/Catalog/Create 或者 /Catalog/Delete 那么Catalog控制器会返回Create视图或者Delete视图,如果你请求的URL地址包含未知的动作,例如/Catalog/Wow 或者 /Catalog/Eeeks,那么HandleUnknownAction()方法就不会调用。

在列表16中,HandleUnknownAction ()方法将动作的明泽添加到视图数据中提交给名为Unknown的视图(参见图8)。


8 – 显示Unknown视图




Testing Controllers and Actions


ASP.NET MVC团队努力工作来确保可以很容易对控制器动作进行测试,如果你想测试控制器动作,你只需简单的实例化控制器,然后调用动作方法。

例如,列表17中的控制器暴露了两个动作,名为Index()Details(),如果你调用Details()动作时没有传递id参数,那么你应该将其重定向到Index()动作。


列表17 – Controllers\PersonController.cs [C#]

using System.Web.Mvc;  
  
namespace MvcApplication1.Controllers  
{  
    
public class PersonController : Controller  
    {  
        
public ActionResult Index()  
        {  
            
return View("Index");  
        }  
  
        
public ActionResult Details(int? id)  
        {  
            
if (!id.HasValue)  
                
return RedirectToAction("Index");  
            
return View("Details");  
        }  
  
    }  

 

 

namespace MvcApplication1.Controllers

{

    public class PersonController : Controller

    {

        public ActionResult Index()

        {

            return View("Index");

        }

 

        public ActionResult Details(int? id)

        {

            if (!id.HasValue)

                return RedirectToAction("Index");

            return View("Details");

        }

 

    }

}

using System.Web.Mvc;列表17 – Controllers\PersonController.vb [VB]

Public Class PersonController  
    
Inherits System.Web.Mvc.Controller  
  
  
    
Function Index() As ActionResult  
        
Return View("Index")  
    
End Function  
  
    
Function Details(ByVal id As Integer?) As ActionResult  
        
If Not id.HasValue Then  
            
Return RedirectToAction("Index")  
        
End If  
  
        
Return View("Details")  
    
End Function  
  
End Class 

Public Class PersonController

    Inherits System.Web.Mvc.Controller

 

 

    Function Index() As ActionResult

        Return View("Index")

    End Function

 

    Function Details(ByVal id As Integer?) As ActionResult

        If Not id.HasValue Then

            Return RedirectToAction("Index")

        End If

 

        Return View("Details")

    End Function

 

End Class

*** Begin Warning ***

当动作返回一个视图时,你必须显示的写出视图的名称,否则你不能在单元测试中对视图的名称进行验证。例如,在列表17中,Index()方法返回View(“Index”)而不是View()

*** End Warning ***

列表18中的单元测试说明了如何对Person控制器暴露出来的动作进行测试,第一个单元测试,名为DetailsWithId (),证明了在调用Details()并传递一个值作为id参数时,返回Details视图。

第二个单元测试,名为DetailsWithoutId (),证明了在调用Details()方法并不传入任何参数时,返回一个RedirectToRouteResult


列表18 – Controllers\PersonControllerTest.cs [C#]

using System.Web.Mvc;  
using Microsoft.VisualStudio.TestTools.UnitTesting;  
using MvcApplication1.Controllers;  
  
namespace MvcApplication1.Tests.Controllers  
{  
    [TestClass]  
    
public class PersonControllerTest  
    {  
        [TestMethod]  
        
public void DetailsWithId()  
        {  
            
// Arrange  
            var controller = new PersonController();  
  
            
// Act  
            var result = (ViewResult)controller.Details(33);  
  
            
// Assert  
            Assert.AreEqual("Details", result.ViewName);  
        }  
  
  
        [TestMethod]  
        
public void DetailsWithoutId()  
        {  
            
// Arrange  
            var controller = new PersonController();  
  
            
// Act  
            var result = (RedirectToRouteResult)controller.Details(null);  
  
            
// Assert  
            Assert.AreEqual("Index", result.RouteValues["action"]);  
        }  
    }  
}
 

using Microsoft.VisualStudio.TestTools.UnitTesting;

using MvcApplication1.Controllers;

 

namespace MvcApplication1.Tests.Controllers

{

    [TestClass]

    public class PersonControllerTest

    {

        [TestMethod]

        public void DetailsWithId()

        {

            // Arrange

            var controller = new PersonController();

 

            // Act

            var result = (ViewResult)controller.Details(33);

 

            // Assert

            Assert.AreEqual("Details", result.ViewName);

        }

 

 

        [TestMethod]

        public void DetailsWithoutId()

        {

            // Arrange

            var controller = new PersonController();

 

            // Act

            var result = (RedirectToRouteResult)controller.Details(null);

 

            // Assert

            Assert.AreEqual("Index", result.RouteValues["action"]);

        }

    }

}

using System.Web.Mvc;列表18 – Controllers\PersonControllerTest.vb [VB]

Imports Microsoft.VisualStudio.TestTools.UnitTesting  
Imports System.Web.Mvc  
  
<TestClass()> Public Class PersonControllerTest  
  
    
<TestMethod()> _  
    
Public Sub DetailsWithId()  
        
' Arrange  
        Dim controller As New PersonController()  
  
        
' Act  
        Dim result As ViewResult = controller.Details(33)  
  
        
' Assert  
        Assert.AreEqual("Details", result.ViewName)  
    
End Sub  
  
    
<TestMethod()> _  
    
Public Sub DetailsWithoutId()  
        
' Arrange  
        Dim controller As New PersonController()  
  
        
' Act  
        Dim result As RedirectToRouteResult = controller.Details(Nothing)  
  
        
' Assert  
        Assert.AreEqual("Index", result.RouteValues("action"))  
    
End Sub  
  
End Class 

Imports Microsoft.VisualStudio.TestTools.UnitTesting

Imports System.Web.Mvc

 

<TestClass()> Public Class PersonControllerTest

 

    <TestMethod()> _

    Public Sub DetailsWithId()

        ' Arrange

        Dim controller As New PersonController()

 

        ' Act

        Dim result As ViewResult = controller.Details(33)

 

        ' Assert

        Assert.AreEqual("Details", result.ViewName)

    End Sub

 

    <TestMethod()> _

    Public Sub DetailsWithoutId()

        ' Arrange

        Dim controller As New PersonController()

 

        ' Act

        Dim result As RedirectToRouteResult = controller.Details(Nothing)

 

        ' Assert

        Assert.AreEqual("Index", result.RouteValues("action"))

    End Sub

 

End Class

*** Begin Note ***

如果想了解更多关于创建和运行单元测试的内容,请参考本书的附录B

*** End Note ***


Summary


本章致力于讨论ASP.NET MVC控制器,本章的目标时在如何创建控制器和控制器动作上提供深入的解释。

在本章的第一部分,我们浏览了控制器动作可以返回的不同类型的ActionResult,你可以学习到如何返回视图、重定向用户到另一个动作、返回JSON以及返回一个可以下载的文件。

接下来,我们讨论了一些特性,这些特性可以应用在控制器中,对控制器动作如何被调用进行控制的。你可以学习到如何使用AcceptVerbsActionName特性,你还可以学习到如何创建自定义ActionSelect特性来使得控制器动作只能在执行Ajax请求时被调用。

最后,我们讨论如何为控制器构建单元测试。你可以学习到如何测试控制器是否返回诸如ViewResult或者RedirectToRouteResult等不同的ActionResult

 

原文地址:https://www.cnblogs.com/wing011203/p/1402414.html