004_URL 路由 - 定制路由系统 & 使用区域
定制路由系统
路由系统是灵活可配置的,当然还可以通过下面这两种方式定制路由系统,来满足其他需求。
1、 通过创建自定义的RouteBase实现;
2、 通过创建自定义路由处理程序实现。
创建自定义的RouteBase实现
创建自定义的RouteBase实现,需要实现一个RouteBase的派生类,而这需要实现以下两个方法:
- GetRouteData(HttpContextBase httpContext):这是入站URL进行匹配的工作机制。框架依次对RouteTable.Routes的每个条目调用这个方法,直到其中之一返回一个非空值。
- GetVirtualPath(RequestContext requestContext,RouteValueDictionary values):这是出站URL生成的工作机制。框架依次对RouteTable.Routes的每一个条目调用这个方法,直到其中之一返回一个非空值。
为了演示这种自定义方式,这里创建了一个RouteBase的派生类。我们假设这样的一个需求环境:需要把一个现有的应用程序迁移到MVC框架,但不论出于什么原因,我们需要兼容之前的URL,那就可以通过这种方式来实现,当然可以通过规则的路由系统来处理——这里不对这种方式进行讨论。
首先,创建一个处理旧式路由请求的控制器,将其命名为:LegacyController,如:
using System.Web.Mvc; namespace UrlsAndRoutes.Controllers
{
/// <summary>
/// 用以处理旧式 URL 请求的控制器
/// </summary>
public class LegacyController : Controller
{ public ActionResult GetLegacyURL(string legacyURL)
{
// 应用程序迁移到 MVC 之前,请求是针对文件的,因此,实际上是需要在这里处理被请求的文件。但这里
// 只简单说明一下自定义 RouteBase 的实现原理,所以,此处仅在视图中显示这个 URL。
return View((object)legacyURL);
} }
}
上面代码对View方法中的参数做了转换,如果不转换,则C#编译器会误认为要将参数作为要指定渲染的视图的名称的字符串(View方法的一个重载版本的实现)。下面是这个动作方法的视图GetLegacyURL.cshtml:
@model string
@{
ViewBag.Title = "GetLegacyURL";
Layout = null;
} <h2>GetLegacyURL</h2> The URL requested was:@Model
1、对输入URL进行路由
在Infrastructure文件夹中创建一个LegacyRoute类,其内容如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing; namespace UrlsAndRoutes.Infrastructure
{
public class LegacyRoute : RouteBase
{
private string[] urls;
public LegacyRoute(params string[] targetUrls)
{ } public override RouteData GetRouteData(HttpContextBase httpContext)
{
RouteData result = null; string requestedURL = httpContext.Request.AppRelativeCurrentExecutionFilePath;
if (urls.Contains(requestedURL, StringComparer.OrdinalIgnoreCase))
{
result = new RouteData(this, new MvcRouteHandler());
result.Values.Add("controller", "Legacy");
result.Values.Add("action", "GetLegacyURL");
result.Values.Add("legacyURL", "requestedURL");
}
return result;
} public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
return null;
} }
}
注册一条路由,以使其使用新建的这个RouteBase派生类:
public static void RegisterRoutes(RouteCollection routes)
{
// 注册自定义的 RouteBase 实现
routes.Add(new LegacyRoute("~/articles/Windows_3.1_Overview.html", "~/old/.NET_1.0_Class_Library")); }
2、生成输出URL
在LegacyRoute中实现GetVirtualPath方法以使其能够支持输出URL的生成。如:
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
VirtualPathData result = null; if (values.ContainsKey("legactURL") && urls.Contains((string)values["legacyURL"], StringComparer.OrdinalIgnoreCase))
{
// 如果存在一个匹配,将会创建一个 VirtualPathData 对象,在其中传递一个对当前对象的引用和出站 URL。由于路由系统已经预先将
// 字符“/”附加到了这个URL,因此,必须从生成的 URL 上删除这个前导字符。
result = new VirtualPathData(this, new UrlHelper(requestContext).Content((string)values["legacyURL"]).Substring());
} return null;
}
在ActionName.cshtml视图中添加下面这段代码,以使其能礼仪自定义路由生成输出URL:
<div>
@* 经由自定义路由生成一个输出 URL *@
This is a URL:
@Html.ActionLink("Click me", "GetLegacyURL", new { legacyURL = "~/articles/Windows_3.1_Overview.html" })
</div>
上面代码将产生一个这样的a元素:
<a href=”/articles/Windows_3.1_Overview.html”>Click me</a>
用legacyURL属性创建的匿名类型被转换到了含有同名键的RouteValueDictionary类中。
创建自定义路由处理程序
路由已经依赖这个MvcRouteHandler了,因为MvcRouteHandler把路由系统连接到了MVC框架。但通过实现IRouteHandler接口,路由系统仍允许自定义自己的路由处理程序,如下面的示例:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Routing; namespace UrlsAndRoutes.Infrastructure
{
public class CustomRouteHandler : IRouteHandler
{
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
return new CustomHttpHandler();
} } public class CustomHttpHandler : IHttpHandler
{
public bool IsReusable
{
get { return false; }
} public void ProcessRequest(HttpContext context)
{
context.Response.Write("Hello");
}
}
}
IRouteHandler接口的目的是提供生成IHttpHandler接口的实现,且由它负责对请求进行处理。在该接口的MVC实现中,主要负责这几项工作:查找控制器、调用动作方法、渲染视图,并将结果写入到响应中。当然,这里的实现要简单的多,此处仅将单词“Hello”写到客户端,且只是文本形式。要想得到最终效果,需要在RouteConfig.cs文件中注册这个自定义处理程序:
public static void RegisterRoutes(RouteCollection routes)
{
// 注册自定义路由处理程序
routes.Add(new Route("SayHello", new CustomRouteHandler())); }
使用区域
MVC框架支持将Web应用程序组织成一些区域(Area),每个区域代表应用程序的一个功能端,如管理、结算、客户支持等等。这使得代码的管理很有用,尤其是大型项目,如果对所有控制器、视图和模型只使用一组文件夹,那将会是很难于管理的。
创建区域
可以直接对项目右键,选择“添加”->“区域”进行添加。还可以在当前的区域中创建其他区域。在刚刚的操作之后,项目中将会出现如下这样的区域文件夹结构:
通过Areas/Admin文件夹,可以看出这是一个小型的MVC项目。其中有“Controllers”、“Models”和“Views”的文件夹。前两个是空的,但“Views”文件夹含有一个“Shared”文件夹和一个Web.config视图引擎配置文件(这里暂不对视图引擎进行讨论)。
另外,这里还多了一个AdminAreaRegistration.cs文件,如:
using System.Web.Mvc; namespace UrlsAndRoutes.Areas.Admin
{
public class AdminAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "Admin";
}
} public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Admin_default",
"Admin/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional }
);
}
}
}
从清单中可以看出,该类中的RegisterArea方法注册了一个URL模式为Admin/{controller}/{action}/{id}的路由。当然,也可以在该方法中定义该区域专用的其他路由。
注意:如果要给路由赋名,必须确保这些名称在整个应用程序而不仅仅是某一区域中是唯一的。
由于在Global.asax的Application_Start方法中已经对路由的注册进行过处理,因此,不需要在开发的过程中采取其他措施来确保该注册方法会被调用:
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
}
上面代码中对静态方法AreaRegistration.RegisterAllAreas的调用,会导致MVC框架对应用程序的所有类进行遍历,找出派生于AreaRegistration的所有类,并调用这些类上的RegisterArea方法。
注意:不用修改Application_Start方法中与路由相关的语句顺序。如果在AreaRegistration.RegisterAllAreas之前调用RegisterRoutes,那么会在区域路由之前定义路由。由于路由系统是按顺序评估的,这意味着对区域控制器的请求有可能会用不正确的路由进行匹配。
注:AreaRegistrationContext类中的MapRoute方法会自动把注册的路由限制到包含该区域控制器的命名空间。也就是说,当某区域创建控制器时,必须把它放在其默认的命名空间中;否则,路由系统将无法找到它。
填充区域
在上一节“创建区域”一节中,已经知道在一个区域中可以创建控制器、视图以及模型等。下面,通过创建一个名为HomeController的控制器类,来演示应用程序中区域之间的分离:
在下图中的Controllers文件夹中右键添加一个空的控制器:HomeController
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc; namespace UrlsAndRoutes.Areas.Admin.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
} }
}
为了演示,对该控制器中Index动作方法右击,并添加相应的视图,添加后的视图将在:Areas/Admin/View/Home路径中。
视图内容如下:
@{
ViewBag.Title = "Index";
Layout = null;
} <!DOCTYPE html> <html>
<head>
<meta name="viewport" content="width=device-with" />
<title>Index</title>
</head>
<body>
<div>
<h2>Admin Area Index</h2>
</div>
</body>
</html>
从上面介绍可以看出,在一个区域内的工作方式与在一个MVC项目主区中工作是相当类似的。在项目中创建某个项的工作流也是相同的。其效果如下(导航路径:Admin/Home/Index):
解析不明确的控制器问题
区域可能不像它们所展示的那样是自包含的。当一个区域被注册时,所定义的任何路由都被限制到与这个区域关联的命名空间之中。这也是能够请求/Admin/Home/Index,并得到WorkingWithAreas.Admin.Controllers命名空间中HomeController类的原因。
然而,在RouteConfig.cs的RegisterRoutes方法中定义的路由却不受类似的限制。作为提醒,这里给出了示例应用程序此时的路由配置:
public static void RegisterRoutes(RouteCollection routes)
{
routes.Add(new Route("SayHello", new CustomRouteHandler())); routes.Add(new LegacyRoute("~/articles/Windows_3.1_Overview.html", "~/old/.NET_1.0_Class_Library")); routes.MapRoute("MyRoute", "{controller}/{action}");
routes.MapRoute("MyOtherRoute", "App/{action}", new { controller = "Home" });
}
名为“MyRoute”的路由把来自浏览器的输入URL转换为Home控制器上的Index动作。这时会收到一个错误的消息,因为没有为这条路由设置命名空间的约束,所以MVC框架会看到两个HomeController类。为了解决这一问题,需要在所有可能导致冲突的路由中,将主控制器命名空间列为优先,如:
public static void RegisterRoutes(RouteCollection routes)
{
routes.Add(new Route("SayHello", new CustomRouteHandler())); routes.Add(new LegacyRoute("~/articles/Windows_3.1_Overview.html", "~/old/.NET_1.0_Class_Library")); routes.MapRoute("MyRoute", "{controller}/{action}",null,new[] {“UrlsAndRoutes.Controllers”});
routes.MapRoute("MyOtherRoute", "App/{action}", new { controller = "Home" }, new[] {“UrlsAndRoutes.Controllers”});
}
上面代码中加粗部分将项目控制器作为了优先。当然也可以对某个区域中的控制器实现优先。
生成对区域动作的链接
对与同一区域中的动作,无需采取特殊的步骤来创建指向这些动作的链接。MVC框架会检测当前请求涉及的特定区域,然后出站URL生成将只在该区域定义的路由中查找一个匹配。如将下面代码添加到Admin区域的视图。
@Html.ActionLink("Click me", "About")
会生成以下HTML:
<a href=”/Admin/Home/About”>Click me</a>
为了对不同区域中的动作或根本无区域的动作创建一条链接,必须创建一个名为“area”的变量,并用它指定区域名,如:
@Html.ActionLink("Click me to go to another area", "Index", new { area = "Support" })
因此,area被保留为片段变量名。假设创建了名为Support的区域,并有对应的标准路由定义,则将生成如HTML:
<a href=”/Support/Home”>Click me to go to another area</a>
如果想链接到顶级控制器(/Controllers文件夹中的一个控制器)上的一个动作,那么应该把area指定为空字符串,如:
@Html.ActionLink("Click me to go to another area", "Index", new {area = ""})
URL方案最佳做法
1、 使URL整洁和人性化
下面摘抄一些生成友好URL的简单的纲要:
- 设计URL来描述它们的内容,而不是应用程序的实现细节。使用/Articles/AnnualReport,而不是使用/Website_v2/CachedContentServer/FromCache/AnnualReport。
- 尽可能采用内容标题而不是ID号,使用/Articles/AnnualReport,而不是/Articles/2392。如果必须使用一个ID号(以区别具有同样标题的条目或避免通过标题查找一个条目时,需要多余的数据库查询步骤),那么两者都有(如:/Articles/2392/AnnualReport)。这需要多打一些字符,但更要意义,并会改善搜索引擎排列。
- 不用对HTML页面使用文件扩展名(如,.aspx或.mvc),但对特殊文件类型要用扩展名(如,.jpg、.pdf、.zip等)。如果是适当的设置了MIME类型,Web浏览器不会在意文件的扩展名,但人们却希望对PDF文件用.pdf扩展名。
- 创建一种层次感(如:/Products/Menswear/Shirts/Red),这样,容易让人猜出父目录的URL。
- 不区分大小写。ASP.NET路由系统默认是不区分大小写的。
- 避免使用符合、代码和字符序列。需要用单词分隔符时,可以使用短横(如:/my-great-article)。下划线是不友好的,而URL编码的空格是奇特的(/my+great+article)或令人讨厌的(/my%20great%20article)。
- 不用修改URL。打破链接等于失去商务。当确实需要修改URL时,通过永久重定向(301)尽可能长时间的继续支持旧式的URL方案。
- 具有一致性。在整个应用程序中采用一种URL格式。URL应简短、易于输入、可剪辑(人性化可剪辑),且持久稳定,而且它们应该形象化网站结构。
2、GET和POST:选用正确的一个
一般来说,GET请求应该被用于所有只读信息检索,而POST请求应该被用于各种修改应用程序状态的操作。用标准的术语说,GET请求用于安全交互(除信息检索外无其他影响),而POST请求用于不安全交互(作出决定或修改某些东西)。GET请求是可设定地址的——所有信息都包含在URL中,因此它可以设为书签并链接到这些地址。(这些约定是由全球互联网联盟(W3C)在http://www.w3.org/Products/rfc2616/rfc2616-sec9.html上设定的)
004_URL 路由 - 定制路由系统 & 使用区域的更多相关文章
- 前端笔记之NodeJS(二)路由&REPL&模块系统&npm
一.路由机制(静态资源文件处理) 1.1 Nodejs没有根目录 MIME类型:http://www.w3school.com.cn/media/media_mimeref.asp 在Apache中, ...
- 网络|N1盒子做旁路由刷OpenWRT系统(小白专用)
N1盒子做旁路由刷OpenWRT系统(小白专用) 为什么要用N1盒子 现如今新上市的路由器,市面上能买到的300元以内的路由器大多数都是双频(5G Hz和2.4G Hz)和几年前相比无论是速度还是性能 ...
- 海蜘蛛网络科技官方网站 :: 做最好的中文软路由 :: 软件路由器 :: 软路由 :: 软件路由 :: RouterOs
海蜘蛛网络科技官方网站 :: 做最好的中文软路由 :: 软件路由器 :: 软路由 :: 软件路由 :: RouterOs 企业简介 武汉海蜘蛛网络科技有限公司成立于2005年,是一家专注于网络新技术研 ...
- vue路由--静态路由
vue的单页面应用是基于路由和组件的,路由用于设定访问路径,并将路径和组件映射起来.传统的页面应用,是用一些超链接来实现页面切换和跳转的.在vue-router单页面应用中,则是路径之间的切换,也就是 ...
- 06 Vue路由简介,原理,实现及嵌套路由,动态路由
路由概念 路由的本质就是一种对应关系,比如说我们在url地址中输入我们要访问的url地址之后,浏览器要去请求这个url地址对应的资源. 那么url地址和真实的资源之间就有一种对应的关系,就是路由. 路 ...
- [水煮 ASP.NET Web API2 方法论](3-2)直接式路由/属性路由
问题 怎么样可以使用更贴近资源(Controller,Action)的方式定义路由. 解决方案 可以使用属性路由直接在资源级别声明路由.只要简单的在 Action 上使用属性路由 RouteAttri ...
- ASP.NET没有魔法——ASP.NET MVC 直连路由(特性路由)
之前对Controller创建的分析中,知道了Controller的创建是有两个步骤组成,分别是Controller的类型查找以及根据类型创建Controller实例. 在查询Controller的类 ...
- Angular routing生成路由和路由的跳转
Angular routing生成路由和路由的跳转 什么是路由 路由的目的是可以让根组件按照不同的需求动态加载不同的组件. 根据不同地址,加载不同组件,实现单页面应用. Angular 命令创建一个配 ...
- vue教程3-06 vue路由嵌套(多层路由),路由其他信息
多层嵌套: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF ...
随机推荐
- android Animation 动画绘制逻辑
参考:http://www.jianshu.com/p/3683a69c38ea 1.View.draw(Canvas) 其中步骤为:/* * Draw traversal performs seve ...
- Intellij IDEA IDE部署Servlet项目
1.设置Project Structure 2.修改Modules中的Web项目文件默认class编译之后输出位置 3.给Modules中的Web项目添加Web模块 4.修改Web项目Web.xml文 ...
- Spring3系列4-多个配置文件的整合
Spring3系列4-多个配置文件的整合 在大型的Spring3项目中,所有的Bean配置在一个配置文件中不易管理,也不利于团队开发,通常在开发过程中,我们会按照功能模块的不同,或者开发人员的不同,将 ...
- Java IO 之 FileInputStream & FileOutputStream源码分析
Writer :BYSocket(泥沙砖瓦浆木匠) 微 博:BYSocket 豆 瓣:BYSocket FaceBook:BYSocket Twitter ...
- swift 类和结构体
1:类和结构体定义 类和结构体分别通过关键字class 和struct定义. swift的编码风格是类class和结构体struct名字使用大写字母开头的匈牙利表示法,相反的.类的方法和属性则用小写字 ...
- 随手写的自动批量编译部署NativeAndroid程序Python脚本
背景 有一堆工程NativeAndroid程序,要一一编译部署编译测试,手头只有AndroidManifest和Makefile,需要一个个Update,Ndk-build,和发包安装测试,很是头疼, ...
- C++ Low level performance optimize 2
C++ Low level performance optimize 2 上一篇 文章讨论了一些底层代码的优化技巧,本文继续讨论一些相关的内容. 首先,上一篇文章讨论cache missing的重要性 ...
- c与c++中的extern const的区别和联系
最近复习c++,发现了这个东西. c语言里面,我们在一个.c文件中用const定义了一个全局变量后,可以在另一个.c文件中用extern const来引用,但在c++中在链接的时候会报undefine ...
- 实现无锁的栈与队列(5):Hazard Pointer
两年多以前随手写了点与 lock free 相关的笔记:1,2,3,4,质量都不是很高其实(读者见谅),但两年来陆陆续续竟也有些阅读量了(可见剑走偏锋的技巧是多容易吸引眼球).笔记当中在解决内存释放和 ...
- [转]c#截取指定长度的字符串
/// <summary> /// 截取指定長度的字符串 /// </summary> /// <param name="s"></par ...