asp.net mvc 学习
2011-06-23 18:24 乱世文章 阅读(272) 评论(0) 编辑 收藏 举报相对其他的资料,这个系列的文章说了很多原理的东西,我喜欢,呵呵。
转载地址:
http://www.cnblogs.com/jasenkin/archive/2010/09/11/mvc_action_filter.html
了解关于 ASP.NET MVC 应用程序与ASP.NET Web Forms 应用程序两者之间的不同点. 了解怎样去决定什么时候创建一个 ASP.NET MVC 应用程序.
ASP.NET MVC 概述(C#)
模型-视图-控制 (MVC) 结构模式将一个应用程序分隔成三个主要组成部分:模型层、视图层、控制器。ASP.NET MVC框架提供了另一种可以替代ASP.NET Web Forms模式的选择来创造基于MVC的 web应用程序。ASP.NET MVC 框架是一个轻量级的框架,它与现有的ASP.NET特征相集成,比如母板页与基于身份的验证。MVC框架下定义在System.Web.Mvc命名空间中。
MVC是许多开发者都很熟悉的标准设计模式。很多类型的Web应用程序将会受益于MVC框架。一部分将依旧会继续使用传统的基于Web Forms和回传的ASP.NET应用程序模式。其他类型的 Web应用程序将这两种方法相结合使用:两者互不排斥。
MVC框架包含以下组成部分:
•模型层。模型对象是应用程序的一部分,它实现了应用程序的数据定义的逻辑。通常,模型对象检索和储了在数据库中的模型状态。例如,一个Products对象可以在数据库检索信息,操作它,然后将修改的信息更新回SQL服务器中的Products表。
在小应用程序中,该模型常常是一个概念性的分离而不是物理上的。例如,如果应用程序仅仅读取数据集和将它发送到视图上,应用程序将不存存在一个物理模型层及相关的类。在这种情况下,数据集将具有一个模型对象的角色。
•视图层。视图层是显示应用程序的用户界面(UI)的部分。通常,用户界面UI是由模型数据所创建的。
•控制层。控制层是处理用户交互,对模型层起作用,并最终选择一个视图view来呈现那个显示的用户界面的部分。在一个MVC应用程序中,视图层view只显示信息,控制层controller处理并响应用户的输入和交互。例如,控制层处理查询字符串的值,并将这些值传递给模型层model,该模型层就会反过来使用这些值来查询数据库。
MVC模式帮助你创建应用程序,它将应用程序的不同方面的(输入逻辑、业务逻辑,和UI逻辑)进行分离,同时在这些元素之间提供一个松耦合关系。这种模式指定了每一种逻辑在应用程序中所处的位置。UI逻辑属于视图层。输入逻辑属于控制层。业务逻辑属于模型层。当你创建一个应用程序时,这种分离能帮助你处理复杂事务,因为它可以让您每一次专注于实施的一个方面。例如,你可以专注于视图层,而不依赖任何业务逻辑。
除了管理复杂的事务,MVC模式比基于Web Forms的ASP.NET Web应用程序更加容易进行测试。例如,在一个基于Web Forms的ASP.NET Web应用程序,一个单一的类被用于显示输出和响应用户的输入。因为测试单个页面,你必须实例化这个页面page类,它所有的子控件以及应用程序中额外的依赖类,所以针对于基于Web Forms的 ASP.NET应用程序写的自动测试将可能很复杂。因为有那么多类被实例化用来运行这个页面,它可能很难写出针对于应用程序的单个部分的测试代码。因此,基于Web Forms的ASP.NET Web应用程序的测试比MVC应用程序更难实现。而且,基于Web Forms的ASP.NET Web应用程序需要一个Web服务器。MVC框架将这些组成部分进行解耦,大量的使用接口,使它能够独立的测试单独的部分。
同样,MVC应用程序中的介于三个主要组成部分的松耦合也促进了平行开发。例如,一个开发者可以开发视图层,另一个开发者可以开发控制逻辑层,第三个开发者可以专注于模型层中的商业逻辑。
决定什么时候去创造一个MVC应用程序
你必须仔细考虑是否要通过采用ASP.NET MVC框架和ASP.NET Web Forms模型中任何一种来实施一个Web应用程序。MVC框架取代Web Forms模型;你能够为Web应用程序采用任何一种框架。
在你为一个具体的Web站点决定采用MVC框架还是Web Forms模型之前,权衡一下每一种方法的优势。
基于MVC的Web应用程序的优势
这个ASP.NET MVC框架提供了以下优点:
它通过将应用程序分解为模型层,视图层和控制层来使我们更容易管理复杂的事务。
它不使用视图状态或基于服务器的表单。这使得MVC框架非常适合于那些想完全控制应用程序行为的开发者。
它使用一个 Front Controller模式,它通过一个单一的控制器来处理Web应用程序请求。这使你能设计一个支持丰富的路由基础设施的应用程序。更多信息,在MSDN网站查询Front Controller。
它提供更好的驱动测试开发的支持(TDD)。
它适用于需要高度控制程序行为的开发者和网站设计者支持的应用程序的大型团队。
基于Web Forms的Web应用程序的优点
基于Web Forms的框架提供以下优点:
它支持一个能够在HTTP之上保持状态的事件模型,从而有利于line-of-business网页应用软件的开发。基于Web Forms的Web应用程序提供了被数以百计的服务器控件支持的许多事件。
它使用一个能够增加函数到单个页面的页面控制模式,。更多信息,见MSDN网站中的Page Controller。
它使用视图状态或基于服务器的表单,它可以使管理状态信息更加容易。
它适用于那些网站开发者和设计师想利用大量组件来快速应用开发的小团队。
总的来说,应用软件的开发不是那么复杂,因为组件(页类、控件等)是紧密结合在一起,通常比MVC模型需要更少的代码。
ASP.NET MVC 框架的特点
ASP.NET MVC 框架提供了以下特点:
分离的应用程序任务(输入逻辑、业务逻辑,和UI逻辑)、可测性、默认的驱动测试开发(TDD)。所有在MVC框架中的核心是interface-based和可以利用模拟对象进行测试,模拟对象能够模拟实际应用中对象的真实行为。你不一定需要在ASP.NET进程中运行控制层就能够对应用程序进行单元测试,它使得单元测试更快速和灵活。你可以使用任何兼容.net框架的单元测试框架。
一个可扩展和可插接的框架。这个ASP.NET MVC框架的组件被设计过,这样它们就能够方便地更换或自定义。你可以插入你自己的视图引擎,URL路由策略,action-method参数的序列化以及其他组件。这个 ASP.NET MVC 框架也支持使用依赖性注射(DI)和控制反转(IOC)容器模型。DI允许您注入对象到一个类中,而不是依赖这个类来创建对象本身。IOC规定,如果一个对象需要另一个对象,第一个对象应该得到来自外部源(如一个配置文件)的第二个对象。这使得测试更容易。
一个强大的URL-mapping组件,它让你建立拥有可理解并且可搜索的URL的应用程序。URL并不需要包括文件扩展名,是设计用来支持URL命名模式, 这种模式对于搜索引擎优化(SEO)和表述性状态转移(REST)寻址能够很好的运行。
支持现有的ASP.NET 功能。ASP.NET MVC让你使用功能,如表格认证和Windows认证、URL的授权,成员和角色,输出和数据缓存,会话和状态管理等。
理解MVC应用程序执行过程
基于ASP.NET MVC Web应用程序的请求首先通过一个UrlRoutingModule的对象(HTTP模块)。这个模块匹配请求,并且执行路由选择。这个UrlRoutingModule对象选择第一个匹配当前请求的路由对象。如果没有路径匹配,这个UrlRoutingModule什么也不做,让这个请求返回给常规的ASP.NET或者IIS来请求处理。
从这个被选中的Route对象,UrlRoutingModule对象获得IRouteHandler对象(IRouteHandler对象与Route对象是相互关联的)。一般来说,在一个MVC应用程序中,它将是MvcRouteHandler实例。这个IRouteHandler实例创建一个IHttpHandler对象,并且将它传递给IHttpContext对象。默认情况下,MVC IHttpHandler实例就是MvcHandler对象。然后,这个MvcHandler对象选择controller,controller将最终提交这个请求。
这个module 和 handler是ASP.NET MVC框架的入口点。它们执行下列行为:
选择合适的controller。
获得一个具体controller实例。
调用controller的执行方法。
下表列出了一个MVC Web项目的执行的各阶段。
阶段 | 详细 |
接收应用程序的第一次请求 | 在Global.asax文件中, Route对象 被添加到RouteTable对象. |
执行路由选择 | UrlRoutingModule 模块使用第一个在RouteTable 集合中匹配的Route 对象来创建RouteData对象, 然后它将使用这个RouteData对象来创建RequestContext (IHttpContext)对象. |
创建MVC request handler | MvcRouteHandler 创建MvcHandler类的一个实例,并且将它传递给RequestContext实例. |
创建controller | MvcHandler对象使用RequestContext实例来确认IControllerFactory 对象(DefaultControllerFactory类的一个实例) ,以用来创建conteoller实例。 |
执行controller | MvcHandler 实例调用controller的执行method. |
调用action | 大部分controllers 继承自Controller基础类. 与controller相关联的ControllerActionInvoker 对象决定这个controller类的哪个方法将被调用 , 然后再调用那个方法. |
执行result | 一个典型的action 方法可能接收用户输入,准备合适的响应数据, 然后通过返回一个result的类型来执行这个result. 这个内置的能够执行的result 类型 包含以下类型: ViewResult (它呈现一个视图,并且是最常用的result类型), RedirectToRouteResult, RedirectResult, ContentResult, JsonResult以及EmptyResult. |
ASP.NET Routing模块的责任是将传入的浏览器请求映射为特有的MVC controller actions。
使用默认的Route Table
当你创建一个新的ASP.NET MVC应用程序,这个应用程序已经被配置用来使用ASP.NET Routing。 ASP.NET Routing 在2个地方设置。第一个,ASP.NET Routing 在你的应用程序中的Web配置文件(Web.config文件)是有效的。在配置文件中有4个与routing相关的代码片段:system.web.httpModules代码段,system.web.httpHandlers 代码段,system.webserver.modules代码段以及 system.webserver.handlers代码段。千万注意不要删除这些代码段,如果没有这些代码段,routing将不再运行。第二个,更重要的,route table在应用程序的Global.asax文件中创建。这个Global.asax文件是一个特殊的文件,它包含ASP.NET 应用程序生命周期events的event handlers。这个route table在应用程序的起始event中创将。
在Listing 1中包含ASP.NET MVC应用程序的默认Global.asax文件.
Listing 1 - Global.asax.cs
2 {
3 public static void RegisterRoutes(RouteCollection routes)
4 {
5 routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
6 routes.MapRoute(
7 "Default", // 路由名称
8 "{controller}/{action}/{id}", // 带有参数的 URL
9 new { controller = "Home", action = "Index", id = UrlParameter.Optional } // 参数默认值
10 );
11
12 }
13
14 protected void Application_Start()
15 {
16 AreaRegistration.RegisterAllAreas();
17
18 RegisterRoutes(RouteTable.Routes);
19 }
20 }
当一个MVC应用程序第一个启动,Application_Start() 方法被调用,这个方法反过来调用RegisterRoutes() 方法。
这个默认的route table包含一个单一的route。这个默认的route将url的第一个段映射为一个controller名称,url的第二个段映射为一个controller action,第三个段映射为命名为id的参数。
假如,你在网页浏览器的地址栏中键入下面的url:/Home/Index/3,这个默认的route将这个url映射为下面的参数:
controller = Home controller名称
action = Index controller action
id = 3 id的参数
当你请求/Home/Index/3这样的url,下面的代码将执行。HomeController.Index(3)
这个默认的route包含3个默认的参数。如果你没有提供一个 controller,那么 controller默认为Home。同样,action默认为Index,id参数默认为空字符串。
让我们来看一些关于默认的route怎么映射urls为controller actions的例子。假如你在你的浏览器地址栏中输入如下的url:/Home, 由于这些默认的route参数有一些相关的默认值,键入这样的URL,将导致HomeController类的Index()方法(如Listing 2)被调用。
2 {
3 [HandleError]
4 public class HomeController : Controller
5 {
6 public ActionResult Index(string id)
7 {
8 ViewData["Message"] = "欢迎使用 ASP.NET MVC!";
9
10 return View();
11 }
12
13 public ActionResult About()
14 {
15 return View();
16 }
17 }
18 }
19
20
在Listing 2中,这个HomeController 类包含一个名为Index()的方法。这个URL /Home导致Index()方法被调用,一个空的字符串将作为id参数的值。由于mvc框架调用controller actions的这种方式,这个URL /Home同样匹配HomeController类中的Index()方法(如Listing 3)。
Listing 3 - HomeController.cs (Index action with no parameter)
[HandleError]
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
}
在Listing 3中,这个Index()方法不接收任何参数。这个URL /Home将导致Index()方法被调用。URL /Home/Index/3同样调用这个方法(ID被忽略)。
Listing 4 - HomeController.cs (Index action with nullable parameter)
[HandleError]
public class HomeController : Controller
{
public ActionResult Index(int? id)
{
return View();
}
}
在Listing 4中, Index() 方法有一个整数参数. 由于这个参数是可空参数 , Index() 将被调用而不引起错误.
最后, 使用 URL /Home 来调用如Listing 5中的Index() 方法 将导致异常,因为这个ID参数不是一个可空的参数。如果你试图去调用这个Index()方法,你将获得如下图所示的错误。
Listing 5 - HomeController.cs (Index action with Id parameter)
[HandleError]
public class HomeController : Controller
{
public ActionResult Index(int id)
{
return View();
}
}
另一方面,使用如Listing 5中的Index controller action,URL /Home/Index/3运行正常。Index controller action in Listing 5. /Home/Index/3请求将导致Index()方法被调用,ID参数拥有一个3的值。
总结
这是一个关于ASP.NET Routing的简要介绍. 应该了解了这个默认的route如何将URLs映射为controller actions。
创建自定义的Routes (C#)
这个教程,你将学会怎样添加一个自定义的route到一个asp.net mvc应用程序。你将学会在Global.asax文件中,怎样使用一个自定义的route来修改这个默认的route table。
对于许多简单的ASP.NET MVC 应用程序,这个默认的route table将运行得很好。然而,你可能发现,你可能特定的routing 需求。那样的话,你可能需要创建一个自定义的route。
设想一下,例如,你正在建立一个博客应用程序,你可能想要去处理像/Archive/12-25-2009的输入请求。
当一个用户键入这个请求,你想要返回与日期为12/25/2009相符的博客实体。为了处理这种类型的请求,你需要去创建一个自定义的route。
在 Listing 1中,这个Global.asax文件中包含一个新的名为Blog的自定义route,它处理类似于/Archive/entry date的请求。
Listing 1 - Global.asax (with custom route)
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Web;
5 using System.Web.Mvc;
6 using System.Web.Routing;
7
8 namespace MvcRoutingApp
9 {
10 // 注意: 有关启用 IIS6 或 IIS7 经典模式的说明,
11 // 请访问 http://go.microsoft.com/?LinkId=9394801
12
13 public class MvcApplication : System.Web.HttpApplication
14 {
15 public static void RegisterRoutes(RouteCollection routes)
16 {
17 routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
18 routes.MapRoute(
19 "Blog", // 路由名称
20 "Archive/{entryDate}/{id}", // 带有参数的 URL
21 new { controller = "Archive", action = "Entry", id = UrlParameter.Optional } // 参数默认值
22 );
23 routes.MapRoute(
24 "Default", // 路由名称
25 "{controller}/{action}/{id}", // 带有参数的 URL
26 new { controller = "Home", action = "Index", id = UrlParameter.Optional } // 参数默认值
27 );
28
29 }
30
31 protected void Application_Start()
32 {
33 AreaRegistration.RegisterAllAreas();
34
35 RegisterRoutes(RouteTable.Routes);
36 }
37 }
38 }
你添加到route table的routes的顺序是很重要的。我们新自定义的blog route在现存的默认route之前添加。如果你颠倒了顺序,那么这个默认的route总是先调用而不是这个自定义的route。
这个自定义的blog toute匹配任何以 /Archive/ 开头的请求。所以,它匹配所有下列URLs:
/Archive/12-25-2009
/Archive/10-6-2004
/Archive/apple
这个自定义的route将输入的请求映射至名为Archive的controller,并且调用 Entry() action。当 Entry() action被调用的时候,这个输入的日期被当作名为entryDate的参数。
Listing 2 - ArchiveController.cs
public class ArchiveController : Controller
{
public string Entry(DateTime entryDate)
{
return "You requested the date:" + entryDate.ToString();
}
}
注意,在Listing 2中这个Entry()方法接收一个类型为DateTime的参数。MVC框架是足够智能的,它自动将URL中输入的date转换为一个DateTime值。如果URL中输入的date不能转换为DateTime,错误将被引发。
总结
这个教程演示怎样来创建一个自定义的route。你学会了怎样在Global.asax 文件中添加一个自定义的route到route table。我们讨论了怎样为blog实体将请求映射为名为ArchiveController的controller,名为Entry()的controller action
创建一个路由约束(C#)
你能够使用路由约束来限制匹配一个特殊路径的浏览器请求。你能够使用一个正则表达式来制定一个路由约束。
例如,假设你已经定义路由如下:
Listing 1 - Global.asax.cs
routes.MapRoute(
"Product",
"Product/{productId}",
new {controller="Product", action="Details"}
);
Listing 1 包含一个命名为Product的路由. 你能够使用这个 Product route来将将浏览器请求映射到ProductController,如下:
Listing 2 - Controllers/ProductController.cs
using System.Web.Mvc;
namespace MvcApplication1.Controllers
{
public class ProductController : Controller
{
public ActionResult Details(int productId)
{
return View();
}
}
}
注意:Details() action 接收一个命名为 productId的单一参数. 这个参数是整型参数.
在Listing 1 will中定义的route将匹配一下的任何一个URLs:
?/Product/23
?/Product/7
遗憾的,这个route也同样匹配以下的URLs:
?/Product/blah
?/Product/apple
因为Details() action预期接收一个整型的参数,当请求中包含的内容不同于整数时,它将导致一个错误。
你真正想要做的,仅仅是匹配包含一个的整数productId的URLs。当你定义一个route时,你能够使用一个限制条件来限制URLs,使它匹配这个route。在Listing 3中,这个route包含一个只匹配整数的正则表达式约束。
Listing 3 - Global.asax.cs
routes.MapRoute(
"Product",
"Product/{productId}",
new {controller="Product", action="Details"},
new {productId = @"/d+" }
);
这个真正表达式约束/d+ 匹配一个或多个整数. 这个约束导致Product route匹配如下的URLs:
?/Product/3
?/Product/8999
但不是如下的URLs:
?/Product/apple
?/Product
这个浏览器请求将被另一个route处理。或者,如果没有匹配的routes, “The resource could not be found ”错误将被返回.
创建一个自定义路由约束 (C#)
演示如何创建一个自定义的路由约束.约束接口中的Match方法如下:
IRouteConstraint.Match Method
bool Match(
HttpContextBase httpContext,
Route route,
string parameterName,
RouteValueDictionary values,
RouteDirection routeDirection
)
你可以通过实现IRouteConstraint接口来创建一个路径约束,并且通过几个步骤把它添加到你的路径中。IRouteConstraint仅有一个Match方法,它返回一个布尔值。这个布尔值决定该请求是否应该被route对象处理。
如何创建一个ASP.NET MVC应用程序来模拟一个仅仅在视图中显示年份,月份,日期的文章系统,类似于博客系统的路径?
(一)首先,创建一个ArchiveController,它包含一个仅仅显示年份,月份,日期值的Index action 方法。
{
public class ArchiveController : Controller
{
//
// GET: /Archive
public ActionResult Index(int year, int month, int day)
{
ViewData["Year"] = year;
ViewData["Month"] = month;
ViewData["Day"] = day;
return View();
}
}
}
(二)创建一个显示数据的view。
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
Index
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>Index</h2>
<fieldset>
<legend>Fields</legend>
<p>Year:
<%= ViewData["Year"] %>
</p> <p>
Month:
<%= ViewData["Month"]%>
</p> <p>
Day:
<%= ViewData["Day"]%></p>
</fieldset>
</asp:Content>
(三)最主要的步骤,需要创建年份,月份,日期验证的三个分离的约束。它们将在路径定义中应用。以创建一个DayConstraint来开始。
using System.Globalization;
namespace MvcAppRouting.RouteConstraints
{
public class DayConstraint:System.Web.Routing.IRouteConstraint
{
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
if ((routeDirection == RouteDirection.IncomingRequest) && (parameterName.ToLower(CultureInfo.InvariantCulture) == "day"))
{
try {
int month = int.Parse(values["Month"].ToString());
int day = int.Parse(values["Day"].ToString());
if (month <= 0 || month > 12) return false;
if(day<1)return false;
switch (month)
{
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12:
if (day< 32) return true;
break;
case 2:
if (day < 29) return true;
break;
case 4:
case 6:
case 9:
case 11:
if(day<31) return true;
break;
}
}
catch {
return false;
}
}
return false;
}
}
}
年份数据限制为1950-2010。同样,月份的值在1-12之间,此处不再叙述,详见源代码。
(四)最后一步是将所有的联系在一起,使ASP.NET MVC 应用程序能够运行。这里仅仅是定义一个routes。
{
// 注意: 有关启用 IIS6 或 IIS7 经典模式的说明,
// 请访问 http://go.microsoft.com/?LinkId=9394801
public class MvcApplication : System.Web.HttpApplication
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default", // 路由名称
"{controller}/{action}/{id}", // 带有参数的 URL
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // 参数默认值
);
routes.MapRoute(
"Archive",
"archive/{year}/{month}/{day}",
new
{
controller = "Archive",
action = "Index",
year = "",
month = "",
day = ""
},
new
{
year = new RouteConstraints.YearConstraint(),
month = new RouteConstraints.MonthConstraint(),
day = new RouteConstraints.DayConstraint()
}
);
}
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterRoutes(RouteTable.Routes);
}
}
}
实例中的“archive/{year}/{month}/{day}”模式,像正常的routes一样,同样为route设置了默认的值,并且增加了一个约束对象。这个约束对象将模式中的参数映射至它的约束实例中,因此这些值能够被验证。
现在我用一个验证的请求模式来运行这个应用程序,展示的页面如下。
同样,发送一个archive/2000/2/30这个请求,它是不能通过验证,并且得到一个错误。
总结:
应该注意约束条件必须继承IRouteConstraint,并且实现Match方法
创建一个自定义action必须满足的要求
方法必须为公共的.
方法不能为静态方法.
方法不能是Control基类中的方法(如:ToString,GetHashCode等)方法不能为扩展方法.
方法不能为一个构造函数 ,getter, setter.
方法不能包含ref 或 out 参数.
使用 NonActionAttribute 特性将阻止该action被调用
{
[HandleError]
public class UserDemoController : Controller
{
//
// GET: /UserDemo/
//自定义一个简单的方法
[NonAction]
//public sealed class NonActionAttribute表示一个特性,该特性用于指示控制器方法不是操作方法。
public string DisplayString()
{
return "this is a demo string!";
}
}
}
理解Views
相对于 ASP.NET 与 Active Server Pages, ASP.NET MVC 并不包含任何直接对应的一个页面。在ASP.NET MVC 应用程序中,你键入浏览器地址栏中的URL在磁盘上并没有相应的一个页面,该URL被映射为 controller actions。与页面page最相近的正是我们所说的View。
最基本的如下:
{
return View();
}
为了探究view的本质,以显示如下的结果:
{
public RssActionResult RssShow()
{
return new RssActionResult();
}
}
我们需要创建一个继承ActionResult的RssActionResult类,如下所示:
{
/*
* System.Web.Mvc
public abstract class ActionResult
{
protected ActionResult();
public abstract void ExecuteResult(ControllerContext context);
}*/
public class RssActionResult:ActionResult
{
public RssActionResult()
{
}
//
public override void ExecuteResult(ControllerContext context)
{
if (context == null)
{
throw new ArgumentNullException("ControllerContext is null!");
}
HttpResponseBase response = context.HttpContext.Response;
Rss rss = new Rss();
rss.CreateSampleRss(response);
}
}
通过从 System.Web.Mvc.ActionResult 类继承的自定义类型,ExecuteResult(ControllerContext context)启用对操作方法结果的处理。
附:ExecuteResult()中的rss.CreateSampleRss(response)方法通过利用HttpResponseBase来处理响应流。
{
XmlTextWriter writer = new XmlTextWriter(response.OutputStream, System.Text.Encoding.UTF8);
WriteRssHeader(writer);
for (int i = 0; i < 50;i++ )
{
WriteRssItem(writer, "demo title: jasenkin" + i.ToString(), http://jasenkin/, "decription: --->" + i.ToString());
}
WriteRssBottom(writer);
writer.Flush();
writer.Close();
response.ContentEncoding = System.Text.Encoding.UTF8;
response.ContentType = "text/xml";
response.Cache.SetCacheability(HttpCacheability.Public);
response.End();
}
执行结果如下:
一个典型的Action可能接收用户输入,准备合适的响应数据, 然后通过返回一个Result的类型(如上例中的RssActionResult),系统将自动调用这个Result类型(如上例中的RssActionResult)的ExecuteResult(context)来响应浏览器请求,呈现的就是我们所说的View了(如上图)。
这仅仅是一个继承自actionresult的自定义类,其中的ExecuteResult()方法才是该类的关键之处
Action filter 是能够应用于 controller action --或整个controller的一个特性,它们的基类为System.Web.Mvc.FilterAttribute 。它限定了action执行的方式。ASP.NET MVC框架包含数个action filters。
- HandleError – 这个action 过滤器处理controller action执行时出现的错误。
- OutputCache – 这个action 过滤器将 controller action的输出缓存一段制定的时间 .
- Authorize – 这个action 过滤器使你能够限制特定的用户或角色的访问.
使用Action Filter
action filter是一个特性. 你能够应用大部分的action filters 在单个的controller action 或者整个controller上.
例如下面的Data controller有一个返回当前时间的Index()方法.这个action拥有OutputCache
action filter. 这个过滤器导致由action返回的值能够缓存10秒钟.
VaryByParam 属性使用的设置不建议通过设置“*”的值来使用所有参数进行区分。这可能会导致缓存溢出。
{
//
// GET: /Data/
[OutputCache(Duration = 20,VaryByParam ="")]
public string Index()
{
return DateTime.Now.ToString();
}
}
如果你重复调用Index()
action(不断刷新当前页面), 那么你将看到当前的内容在Duration = 20秒内是不变的.
一个单一的action filter – OutputCache
action filter – 被应用于Index()
方法. 同样,你可以应用多个action filters 在同一个action上.
不同类型的Filters
ASP.NET MVC框架支持多种不同类型的过滤器:
- Authorization filters – 实现
IAuthorizationFilter
特性. - Action filters – 实现
IActionFilter
特性. - Result filters – 实现
IResultFilter
特性. - Exception filters –实现
IExceptionFilter
特性.
Filters 按照上面列出的顺序执行。例如, authorization filters 总是在action filters之前执行,exception filters在所有其他类型的filter之后执行.
ActionFilterAttribute 基类
为了使你能够更加容易的实现自定义的action filter, ASP.NET MVC框架包含一个ActionFilterAttribute
基类. 这个类实现了IActionFilter
与IResultFilter
接口,并且继承了Filter
类。
ActionFilterAttribute
基类拥有以下可以重载的方法:
- OnActionExecuting在action method调用前发生。
- OnActionExecuted在action method调用后发生, 但是在result执行前发生 (在 view 呈现前)
- OnResultExecuting在result执行前发生(在view 呈现前)
- OnResultExecuted 在result执行后发生(在view 呈现后)
创建一个ASP.NET MVC OutputCache ActionFilterAttribute
使用ASP.NET MVC 框架, 简单的指定OutputCache 指令并不能达到理想的效果. 幸好, ActionFilterAttribute让你能够在 controller action执行的前后运行代码.
让我们使用类似的方法来创建OutputCache ActionFilterAttribute。
public ActionResult Index()
{
// ...
}
我们将使用命名为CachePolicy的枚举类型来指定OutputCache 特性应怎样以及在哪里进行缓存:
{
NoCache = 0,
Client = 1,
Server = 2,
ClientAndServer = 3
}
1.实现client-side缓存
事实上,这是很容易的。在view呈现前,我们将增加一些HTTP头到响应流。网页浏览器将获得这些头部,并且通过使用正确的缓存设置来回应请求。如果我们设置duration为60,浏览器将首页缓存一分钟。
namespace MVCActionFilters.Web.Models
{
public class OutputCache:System.Web.Mvc.ActionFilterAttribute
{
public int Duration { get; set; }
public CachePolicy CachePolicy { get; set; }
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
if (CachePolicy == CachePolicy.Client || CachePolicy == CachePolicy.ClientAndServer)
{
if (Duration <= 0) return;
//用于设置特定于缓存的 HTTP 标头以及用于控制 ASP.NET 页输出缓存
HttpCachePolicyBase cache = filterContext.HttpContext.Response.Cache;
TimeSpan cacheDuration = TimeSpan.FromSeconds(Duration);
cache.SetCacheability(HttpCacheability.Public);
cache.SetExpires(DateTime.Now.Add(cacheDuration));
cache.SetMaxAge(cacheDuration);
cache.AppendCacheExtension("must-revalidate, proxy-revalidate");
}
}
}
}
2. 实现server-side缓存
Server-side 缓存有一点难度. 首要的,在输出缓存系统中,我们将不得不准备HTTP 响应为可读的。为了这样做,我们首先保存当前的HTTP context到类的一个变量中. 然后, 我们创建一个新的httpcontext ,通过它将数据写入StringWriter,同时允许读操作可以发生:
writer = new StringWriter();
HttpResponse response = new HttpResponse(writer);
HttpContext context = new HttpContext(existingContext.Request, response)
{
User = existingContext.User
};
System.Web.HttpContext.Current = context;
{
if (CachePolicy == CachePolicy.Server || CachePolicy == CachePolicy.ClientAndServer)
{
//获取缓存实例
cache = filterContext.HttpContext.Cache;
// 获取缓存数据
object cachedData = cache.Get(GenerateKey(filterContext));
if (cachedData != null)
{
// 返回缓存数据
cacheHit = true;
filterContext.HttpContext.Response.Write(cachedData);
filterContext.Cancel = true;
}
else
{ //重新设置缓存数据
existingContext = System.Web.HttpContext.Current;
writer = new StringWriter();
HttpResponse response = new HttpResponse(writer);
HttpContext context = new HttpContext(existingContext.Request, response)
{
User = existingContext.User
};
foreach (var key in existingContext.Items.Keys)
{
context.Items[key] = existingContext.Items[key];
}
System.Web.HttpContext.Current = context;
}
}
}
利用该代码,我们能从高速缓存中检索现有项,并设置了HTTP响应能够被读取。在视图呈现之后,将数据存储在高速缓存中:
{
// 服务器端缓存?
if (CachePolicy == CachePolicy.Server || CachePolicy == CachePolicy.ClientAndServer)
{
if (!cacheHit)
{
// 存储原有的context
System.Web.HttpContext.Current = existingContext;
// 返回呈现的数据
existingContext.Response.Write(writer.ToString());
//增加数据到缓存
cache.Add(
GenerateKey(filterContext),
writer.ToString(),
null,
DateTime.Now.AddSeconds(Duration),
Cache.NoSlidingExpiration,
CacheItemPriority.Normal,
null);
}
}
}
你现在注意到添加了一个VaryByParam到 OutputCache ActionFilterAttribute。当缓存server-side时,我可以通过传入的参数来改变缓存存储。这个GenerateKey方法会产生一个依赖于controller,action和VaryByParam的键。
{
StringBuilder cacheKey = new StringBuilder();
// Controller + action
cacheKey.Append(filterContext.Controller.GetType().FullName);
if (filterContext.RouteData.Values.ContainsKey("action"))
{
cacheKey.Append("_");
cacheKey.Append(filterContext.RouteData.Values["action"].ToString());
}
// Variation by parameters
List<string> varyByParam = VaryByParam.Split(';').ToList();
if (!string.IsNullOrEmpty(VaryByParam))
{
foreach (KeyValuePair<string, object> pair in filterContext.RouteData.Values)
{
if (VaryByParam == "*" || varyByParam.Contains(pair.Key))
{
cacheKey.Append("_");
cacheKey.Append(pair.Key);
cacheKey.Append("=");
cacheKey.Append(pair.Value.ToString());
}
}
}
return cacheKey.ToString();
}
现在你可以增加 OutputCache attribute 到应用程序的任何一个controller 与controller action中 。
public string Cache()
{
return DateTime.Now.ToString();
}
设置CachePolicy为Common.CachePolicy.Client时,将直接在客户端缓存中读取数据。
总结
需注意事件的发生时间段