asp.net mvc的Routing、Controller、Filter学习笔记
1、深入学习Routing
首先Routing的处于的位置,一个Http请求过来,Web容器接收到以后,将信息交给Routing(路由组件),Routing再进行处理~那么Routing的作用
确定Controller、确定Action、确定其他参数、根据识别出来的数据, 将请求传递给Controller和Action.
小提示:asp.net mvc预览版的时候,Routing组件还是作为asp.net mvc的一部分,后续的版本似乎就微软将其编译成一个独立的组件提供System.Web.Routing.dll,也就是说asp.net mvc项目是开源的,但是Routing组件并没有开源。Routing组件不仅在asp.net mvc中可以使用,也可以在WebForm中使用
public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( "Default", // Route name "{controller}/{action}/{id}", // URL with parameters new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults ); }
Routes是Application级别(全局)的,在Application开始的时候,程序注册路由,新建的项目默认只注册了一条路由,看下代码,
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
MapRoute第一个参数是此路由的名称,第二个参数是URL的参数形式,{controller}相当于是一个string.Format方法中的占位符,意思是这里可以是任意一个controller名称,
同理,action、id也是一样的,那为什么请求的/Home/Index并没有Id这个参数,第三个参数是路由规则默认值,这条路由默认的controller是home,action是index,而id呢,是可选的~~~当我们请求/Home/Index的时候,会被此路由获取,而我们直接请求http://localhost的时候,可以到Home/Index的时候,路由参数有默认值
Ok,到这里我们学习了如何注册路由,我们来试着自己写一条路由
routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); //自定义路由, routes.MapRoute( "myRoute", // Route name "{controller}-{action}", // URL with parameters new { controller = "Home", action = "Index" } // Parameter defaults ); routes.MapRoute( "Default", // Route name "{controller}/{action}/{id}", // URL with parameters new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults );
然后重新启动程序,因为Routes是Application级别的,在我们修改Application级别信息后,应该退出,否则不会有效果滴,这个是好多初学者容易犯错的地方.如果是生产环境,应该重新启动下Web容器.
我们运行程序以后,请求域名/home-index一样可以请求页面,说明我们自定义的路由有效.
这里路由的规则非常灵活,我们可以自定义,以下的路由规则都可以
routes.MapRoute(
"myRoute", // Route name
"{controller}-{action}-{1}-{2}-{3}", // URL with parameters
new { controller = "Home", action = "Index" } // Parameter defaults
);
MapRoute()方法
MapRoute有以下的重载方法
MapRoute( string name, string url);
MapRoute( string name, string url, object defaults);
MapRoute( string name, string url, string[] namespaces);
MapRoute( string name, string url, object defaults, object constraints);
MapRoute( string name, string url, object defaults, string[] namespaces);
MapRoute( string name, string url, object defaults, object constraints, string[] namespaces);
name参数: 规则名称, 可以随意起名.不可以重名,否则会发生错误: 路由集合中已经存在名为“Default”的路由。路由名必须是唯一的。
url参数: url获取数据的规则, 这里不是正则表达式, 将要识别的参数括起来即可, 比如: {controller}/{action} 最少只需要传递name和url参数就可以建立一条Routing(路由)规则.比如实例中的规则完全可以改为: routes.MapRoute( "Default", "{controller}/{action}");
defaults参数: url参数的默认值.如果一个url只有controller: localhost/home/ 而且我们只建立了一条url获取数据规则: {controller}/{action} 那么这时就会为action参数设置defaults参数中规定的默认值. defaults参数是Object类型,所以可以传递一个匿名类型来初始化默认值: new { controller = "Home", action = "Index" } 实例中使用的是三个参数的MapRoute方法: routes.MapRoute( "Default", // Route name "{controller}/{action}/{id}", // URL with parameters new { controller = "Home", action = "Index", id = "" } // Parameter defaults );
constraints参数: 用来限定每个参数的规则或Http请求的类型.constraints属性是一个RouteValueDictionary对象,也就是一个字典表, 但是这个字典表的值可以有两种: 用于定义正则表达式的字符串。正则表达式不区分大小写。 一个用于实现 IRouteConstraint 接口且包含 Match 方法的对象。 通过使用正则表达式可以规定参数格式,比如controller参数只能为4位数字: new { controller = @"\d{4}"}
通过第IRouteConstraint 接口目前可以限制请求的类型.因为System.Web.Routing中提供了HttpMethodConstraint类, 这个类实现了IRouteConstraint 接口. 我们可以通过为RouteValueDictionary字典对象添加键为"httpMethod", 值为一个HttpMethodConstraint对象来为路由规则添加HTTP 谓词的限制, 比如限制一条路由规则只能处理GET请求: httpMethod = new HttpMethodConstraint( "GET", "POST" )
routes.MapRoute( "Default", // Route name "{controller}/{action}/{id}", // URL with parameters new { controller = "Home", action = "Index", id = "" }, // Parameter defaults new { controller = @"\d{4}" , httpMethod = new HttpMethodConstraint( "GET", "POST" ) } );
我们注册这样的一条路由
routes.MapRoute( "test", // Route name "{controller}-{action}-{id}", // URL with parameters new { controller = "Home", action = "Index" }, // Parameter defaults new { controller = @"^\w+", action = @"^\w+", id = @"^\d+" } );
编译且运行,当我们请求/home-index-11可以请求到,而我们/home-index-1x这样的是不能匹配的,
那这样的规则的又有什么用处呢?在这里可以对请求进行过滤,比如我们的id只能是数字类型,防止一些非法检测
路由组件的调试
假设我们写了N条路由,当我们发送请求的时候,并没有被我们想要的规则所捕获解析,so..我们需要调试。
在我们的项目添加RouteDebug.dll的引用,并在Global.asax文件中的Application_Start方法添加以下代码
protected void Application_Start() { AreaRegistration.RegisterAllAreas(); RegisterRoutes(RouteTable.Routes); //启动路由表调试 RouteDebug.RouteDebugger.RewriteRoutesForTesting(RouteTable.Routes); }
编译后,我们启动程序,会发现有一个Route Tester页面,关于RouteDebug匹配的页面信息,请大家查询RouteDebug的手册.
Route Tester中的Route Data请求就是当前请求的路由信息
我们请求/home/index,从图中可以看到被第4条路由捕获到.
那这样的路由有什么作用呢?我想大部分做SEO的朋友都有经验,二级页面和三级页面,爬虫抓取的频率显然是不一样的,这样我们可以将三级甚至更深层的页面构造成二级页面~这个也是SEO的技巧之一
注意:我们在写路由规则的时候,因为路由规则有前后顺序(指注册的先后顺序),也许写的路由规则被它前面的规则给捕获到,进行处理。那后面注册的路由就无效
2、Controller学习
在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方法)
Controller对象的职责是传递数据,获取View对象(实现了IView接口的类),通知View对象显示.
View对象的作用是显示.虽然显示的方法RenderView()是由Controller调用的,但是Controller仅仅是一个"指挥官"的作用, 具体的显示逻辑仍然在View对象中.
注意IView接口与具体的ViewPage之间的联系.在Controller和View之间还存在着IView对象.对于ASP.NET程序提供了 WebFormView对象实现了IView接口.WebFormView负责根据虚拟目录获取具体的Page类,然后调用 Page.RenderView()
Controller中的ActionResult
在Controller中,每一个Aciton返回都是ActionResult,我们通过查看ActionResult的定义
// Summary: // Encapsulates the result of an action method and is used to perform a framework-level // operation on behalf of the action method. public abstract class ActionResult { // Summary: // Initializes a new instance of the System.Web.Mvc.ActionResult class. protected ActionResult(); // Summary: // Enables processing of the result of an action method by a custom type that // inherits from the System.Web.Mvc.ActionResult class. // // Parameters: // context: // The context in which the result is executed. The context information includes // the controller, HTTP content, request context, and route data. public abstract void ExecuteResult(ControllerContext context); }
关于ActionResult的派生类,大家可以参考:http://blog.csdn.net/steven_husm/article/details/4641281
3、Filter的学习
mvc项目中,action在执行前或执行后想做一些特殊的操作,比如身份校验、行为截取等,asp.net mvc提供了以下几种默认的Filter
ASP.NET MVC 框架支持以下几种筛选器:
1、授权筛选器– 实现了 IAuthorizationFilter 接口
这一类的筛选器用来实现用户验证和对Action的访问授权。比如Authorize 就属于Authorization 筛选器。
2、Action 筛选器– 实现了 IActionFilter 接口
它可以包含一些Action执行前或者执行后的逻辑,比如有一些筛选器专门用来修改Action返回的数据。
3、Result 筛选器– 实现了 IResultFilter 接口
它可以包含一些view result生成前或者生成后的逻辑,比如有一些筛选器专门用来修改视图向浏览器展现前的结果。
4、异常筛选器– 实现了IExceptionFilter 接口
它用以用来处理Action或者Result的错误,也可以记录错误。
筛选器的默认执行顺序也和上面的列出的序号相同,比如Authorization 筛选器会先于Action 筛选器执行,而Exception 筛选器总会在最后执行。当然你也可以根据需要通过Order属性设定筛选器执行的顺序。
下面通过一个实际的例子来说明应用,新建一个mvc3项目,在项目中新加一个Common文件夹,并新加一个类LogUserOperationAttribute.cs
public class LogUserOperationAttribute : ActionFilterAttribute { public override void OnResultExecuted(ResultExecutedContext filterContext) { //todo写日志代码,这里注意并发性 File.AppendAllText(@"C:\log.txt", string.Format("{0}日志", DateTime.Now)); base.OnResultExecuted(filterContext); } }
新加一个HomeControllers,并在HomeControllers中新加一个Action--Index以及视图文件
public class HomeController : Controller { // // GET: /Home/ [LogUserOperation] public ActionResult Index() { return View(); } }
然后保存代码,F5运行,当页面显示出来以后,在C盘已经有log.txt文件.
abstract class ActionFilterAttribute这四个方法分别代表,Action执行时,Action执行后,Result返回时,Result返回后。(它们的执行顺序跟下图一致)
当然,我们也可以在ASP.NET MVC 3.0中增加Global Action Filter,这个就把此类filter变成全局的filter,所有Controller的action都会通过个filter的规则来执行,它跟我们在Controller或某个action所标识的属性有很大的区别,就是全局跟部分的区别。对于Global Action Filter 有着很多的应用,比如系统的权限、系统异常的处理
Gloable Filter实际应用--系统异常处理体系
我们程序运行过程中会有各种不可预料的情况,执行某个Action会发生异常,那我们异常信息需要记录下来,我们可以像上面的例子一样,在Action或Controller上打上标记,但是那么多action和Controller都去打标记,确实是很痛苦的事情,而asp.net mvc3有一个全局的Filter,我们只需要将我们自定义的Filter注册成全局的Filter
在Common文件夹中,新建一个类CustomHandleErrorAttribute.cs
public class CustomHandleErrorAttribute : IExceptionFilter { public void OnException(ExceptionContext filterContext) { File.AppendAllText(@"C:\error.txt", string.Format("{0}错误{1}", DateTime.Now, filterContext.Exception.Message)); } }
在Global.asax文件中,RegisterGlobalFilters方法写注册代码
public static void RegisterGlobalFilters(GlobalFilterCollection filters) { filters.Add(new CustomHandleErrorAttribute());//系统的逻辑错误通过这个filters来处理 filters.Add(new HandleErrorAttribute()); }
在HomeController中新加一个Action--->Error
public class HomeController : Controller { // // GET: /Home/ [LogUserOperation] public ActionResult Index() { return View(); } public ActionResult Error() { try { System.IO.File.Open("C:\\111111.exe", System.IO.FileMode.Open); } catch (Exception ex) { throw ex; } return View(); } }
编译后运行项目~我们请求/Home/Error这个action,,程序出现异常,我们切换到C盘,发现C盘已经有error.txt文件
注册到全局的Filters所有的Action和Result执行前后都会调用我们的CustomHandleErrorAttribute的重写的方法。
GlobalFilters、ControllerFilters、ActionFilters的执行顺序问题
GlobalFilters-->ControllerFilters-->ActionFilters《这个是有执行的前置条件的》
当然这是在CustomHandleErrorAttribute类的定义上打上标记[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]的前提下。不然 如果Action打上了标签跟Controller的相同则它只会执行Action上的Filter。