首页 > 代码库 > Asp.Net MVC<四>:路由器

Asp.Net MVC<四>:路由器

 

路由的核心类型基本定义于System.Web.dll中,路由机制同样可应用与Web Forms,实现请求地址和物理文件的分离。

web form中使用路由器的示例

路由配置

protected void Application_Start(object sender, EventArgs e){    var defaults = new RouteValueDictionary { { "name", "*" }, { "id", "*" } };    //将物理文件路径映射到一个路由模版上    RouteTable.Routes.MapPageRoute("", "employees/{name}/{id}","~/default.aspx",true,defaults);}

页面

public partial class Default : System.Web.UI.Page{    protected string EmployeeId;    protected string EmployeeName;    protected void Page_Load(object sender, EventArgs e)    {        EmployeeId = this.RouteData.Values["id"] as string;        EmployeeName = this.RouteData.Values["name"] as string;    }}
<form id="form1" runat="server"><div>    EmployeeId:<%=EmployeeId %>    <br/>     EmployeeName:<%=EmployeeName %></div></form>

 

测试

访问地址:http://localhost:65042/Default.aspx

技术分享

访问地址:http://localhost:65042/employees

技术分享

访问地址:http://localhost:65042/employees/张飞

技术分享

访问地址:http://localhost:65042/employees/张飞/001

技术分享

访问地址:http://localhost:65042/employees/张飞/001/sdfs

404报错

ASP.NET路由注册

RouteTable.Routes.MapPageRoute(    routeName               : "myRoute",    routeUrl                : "dirtest/{name}/{id}",    physicalFile            : "~/default.aspx",    checkPhysicalUrlAccess  : true,    defaults                : new RouteValueDictionary { { "name", "*" }, { "id", "010" } },    constraints             : new RouteValueDictionary { { "id", @"0\d{2,3}" }, { "httpMethod", new HttpMethodConstraint("GET") } },    dataTokens              : new RouteValueDictionary { { "city", "hangzhou" } });

另一种注册方式

Route route = new Route(    url:"dirtest/{name}/{id}",    defaults: new RouteValueDictionary { { "name", "*" }, { "id", "010" } },    constraints: new RouteValueDictionary { { "id", @"0\d{2,3}" }, { "httpMethod", new HttpMethodConstraint("GET") } },    dataTokens: new RouteValueDictionary { { "city", "hangzhou" } },    routeHandler:new PageRouteHandler("~/default.aspx",true));RouteTable.Routes.Add("myRoute",route);

注册路由忽略地址技术分享

如果设置为RouteTable.Routes.RouteExistingFiles = true;路由系统会对所有请求实施路由,但这同样会带来一些问题。

例如如果应用运行在集成模式下,则静态资源的请求也会进入ASP.NET管道,于是针对静态文件的请求也可能会被重定向。

RouteTable.Routes.RouteExistingFiles = true;RouteTable.Routes.Ignore("content/{filename}.css/{*pathInfo}");

通过Ignore方法来添加例外,Ignore方法的调用必需放在路由注册之前。

直接请求现存的物理文件

借用路由可以采用一个与路径无关的URL访问某个物理文件,但如果就是希望以物理地址的方式访问对应的物理文件时。

RouteBase定义了一个布尔类型的属性RouteExistingFiles,表示是否对现有物理文件实施路由。

默认值

defaults 为路由模版中得两个变量指定了默认值。

约束

constraints 可以为变量指定基于正则的约束,也可以指定一个RouteConstraint对象。

路由变量

在Route对象的DataTokens属性中可添加路由变量

根据路由规则生成URL

通过RouteCollection的GetVirtualPath方法。

RequestContext requestContext = new RequestContext();requestContext.HttpContext = new HttpContextWrapper(HttpContext.Current);requestContext.RouteData = http://www.mamicode.com/this.RouteData;"name", "刘备" }, { "id", "013" } };Response.Write("<br>");Response.Write(RouteTable.Routes.GetVirtualPath(requestContext, values).VirtualPath);Response.Write("<br>");Response.Write(RouteTable.Routes.GetVirtualPath(requestContext, null).VirtualPath);Response.Write("<br>");Response.Write(RouteTable.Routes.GetVirtualPath(null, null).VirtualPath);

技术分享

MVC路由

MapPageRoute方法和Ignore方法的调用可以完成针对某个物理文件的路由。返回的RouteData中的RouteHander属性会返回一个PageRouteHandler对象。PageRouteHandler对象最终会创建一个针对映射.aspx文件的Page对象来处理请求。

而MVC为RouteCollection增加了一系列扩展方法,定义在System.Web.Mvc.RouteCollectionExtensions中。新增了两组方法 MapRoute和IgnoreRoute

MapRoute方法不能直接添加变量到DataTokens,但可以限定路由匹配的范围:Controller所在的命名空间。它返回的RouteData中的RouteHander属性会返回一个MvcRouteHandler对象

UrlParameter.Optional

将变量的默认值设置为UrlParameter.Optional,则只有请求URL中真正包含具体变量值的情况下生成的RouteData的Values属性中才会包含相应的路由变量。

基于Area的路由映射

通过Area可以从功能上将应用划分为几个较小的单元。每个Area相当于一个独立的子系统。特闷具有一套包含Models,Views,Controllers在内的目录结构和配置文件。每个路由结构一般也会具有各自独立的路由规则,通过AreaRegistration类型进行注册。

protected void Application_Start(){    //针对所有Area的注册    AreaRegistration.RegisterAllAreas();}

 

RegisterAllAreas方法执行时,所有被当前Web应用直接或间接引用的程序集都会被加载(如果尚未被加载),ASP.NET MVC会从中找到所有继承自AreaRegistration的类型,并通过反射创建相应的对象。针对每个被创建出来的AreaRegistration对象,一个作为Area注册上下文的AreaRegistrationContext对象会被创建出来,作为参数调用AreaRegistration对象的RegisterArea方法对相应的Area的路由进行注册。
context.State来自AreaRegistration.RegisterAllAreas方法指定的参数state。

public class adminAreaRegistration : AreaRegistration {    public override string AreaName     {        get         {            return "admin";        }    }    public override void RegisterArea(AreaRegistrationContext context)     {        context.MapRoute(           name: "admin_default",           url: "admin/{controller}/{action}/{id}",           defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }        );    }}

  

当存在相同的控制器和方法名时,如域Area中也定义了一个Home控制器并在其内声明了一个Index方法,则全局路由器会因为重复匹配而报错。需要对其添加命名空间限制。

public static void RegisterRoutes(RouteCollection routes){    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");    routes.MapRoute(        name: "Default",        url: "{controller}/{action}/{id}",        defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },        namespaces: new string[] { "WebApplication1.Controllers.*" }    );}

 

AreaRegistration注册的路由对象

由AreaRegistration注册的路由对象生成的RouteData的不同之处主要反映在DataTokens属性上,默认多出两个变量:

area标记Area的名称,

UseNamespaceFallback: 当MapRoute方法显式地指定了命名空间或者对应的AreaRegistration定义在某个命名空间下,则UseNamespaceFallback为False,反之则被设置为True。

如果MapRoute方法显式地指定了命名空间,则AreaRegistration类型所在的命名空间也会被忽略。

MVC中生成URL的快捷方式

MVC则定义了两个帮助类: HtmlHelper 和UrlHelper,通过调用它们的ActionLink/RouteLink 和Action/RouteUrl方法生成Url。从本质上讲,最终依赖于RouteCollection对象的GetVirtualPathData方法。

Action、ActionLink

UrlHelper.Action(……)返回相对地址或绝对地址,HtmlHelper.ActionLink(……) 则不仅仅返回一个Url,而是生成一个链接(<a>……</a>)。

调用的是GetVirtualPathData(RequestContext requestContext,RouteValueDictionary values)方法。 也就是使用路由表中第一个匹配的Route对象来生成URL或者链接。

RouteUrl、RouteLink 

UrlHelper.RouteUrl(……)方法和HtmlHelper.RouteLink (……) 方法则可以针对注册的某个具体的Route对象来生成URL或者链接。也即是调用GetVirtualPathData(RequestContext requestContext,string name,RouteValueDictionary values)方法。

特性路由

特性路由是对现有路由系统的扩展,提供了一种针对某个具体Controller类型或Action方法的路由注册方式。从而可以对路由规则进行细粒度的设计。

特性路由(Attribute Route)就是利用标注在Controller类型或Action方法上的特性来提供路由规则的路由注册方式

注册

默认情况下特性路由功能是关闭的,需要调用RouteCollection的扩展方法MapMvcAttributeRoutes来开启。

要放在路由注册之前

protected void Application_Start(){    RouteTable.Routes.MapMvcAttributeRoutes();    AreaRegistration.RegisterAllAreas();    RouteConfig.RegisterRoutes(RouteTable.Routes);}

  

public class MoviesController : Controller{    // GET: Movies    public ActionResult Index()    {        return View();    }    [HttpGet]    [Route("movies/{id}")]    public ActionResult GetMoviesById(string id)    {        ViewBag.id = id;        return View();    }    [HttpGet]    [Route("movies/starring/{starring}")]    [Route("movies/starring/{starring}/{genre}")]    [Route("movies/director/{director}/{genre}")]    public ActionResult FindMovies(string starring, string director, string genre)    {        ViewBag.starring = starring;        ViewBag.director = director;        ViewBag.genre = genre;        return View();    }}

  

默认情况下使用特性类型RouteAttribute,它实现了接口IRouteInfoProvider。

//// 摘要://     Provides information for building a System.Web.Routing.Route.public interface IRouteInfoProvider{    //    // 摘要:    //     Gets the name of the route to generate.    //    // 返回结果:    //     The name of the route to generate.    string Name { get; }	//    // 摘要:    //     Gets the route template describing the URI pattern to match against.    //    // 返回结果:    //     The route template describing the URI pattern to match against.    string Template { get; }}

当使用RouteAttribute时,需要提供一个路由模版来对Template这个只读属性进行初始化。而如果需要用这个路由特性注册的路由对象来生成URL,还需要为它提供一个Name,以便可以从路由表中把它提取出来。

为特性路由的变量添加约束

路由变量基本都会直接绑定到目标Action方法相应的参数上,由于这些变量值在请求URL中以字符串的形式体现,所以被绑定参数必需支持源自字符串的类型转换。

为了确保参数绑定的正常进行,需要对路由变量在数据类型上进行约束。这些约束可以直接以内联的形式定义在路由模版里

[HttpGet][Route("movies/{id:range(20,50)}")]public ActionResult GetMoviesById(int id)

对于路由系统来说,约束通过RouteContraint对象来表示,所有RouteContraint类型均实现了IRouteContraint接口。

如前面提到的特性路由可以将对某路由变量的约束直接以内联的形式定义在路由模版中,这些约束类型,如数据类型、数据值范围及字符串长度等也是实现了IRouteContraint接口。

 

系统提供的约束
约束描述用法类型
bool类型匹配 (布尔类型){x:bool}BoolRouteContraint
datetime类型匹配 (DateTime类型){x:datetime}DateTimeRouteContraint
decimal类型匹配 (Decimal类型){x:decimal}DecimalRouteContraint
double类型匹配 (Double类型){x:double}DoubleRouteContraint
float类型匹配 (Float类型){x:float}FloatRouteContraint
guid类型匹配 (Guid类型){x:guid}GuidRouteContraint
int类型匹配 (32位整数){x:int}IntRouteContraint
long类型匹配 (64位整数){x:long}LongRouteContraint
alpha字符组成(必须有拉丁字母组成){x:alpha}AlphaRouteContraint
regex字符组成(必须与指定的正则匹配){x:regex(^\d{3}-\d{3}-\d{4}$)}RegexRouteContraint
max值范围(小于或等于指定的最大值){x:max(20)}MaxRouteContraint
min值范围(大于或等于指定的最小值){x:max(20)}MinRouteContraint
range值范围(在指定的最大值和最小值之间){x:range(20,50)}RangeRouteContraint
maxlength字符串长度(小于或等于指定的最大长度){x:maxlength(20)}MaxLengthRouteContraint
minlength字符串长度(大于或等于指定的最小长度){x:minlength(20)}MinlengthRouteContraint
length字符串长度(等于指定的长度或者在指定的范围内){x:length(6)}LengthRouteContraint

为路由变量设置缺省值

1.将对应的参数定义为可缺省参数

[HttpGet][Route("movies/language/{language?}")]public ActionResult GetMoviesByLanguage(string language = "en"){    return View();}

2.将默认值定义在路由模版中

[HttpGet][Route("movies/language/{language=en}")]public ActionResult GetMoviesByLanguage(string language){    return View();}

设置模版前缀

使用RoutePrefixAttribute类型的特性,它只有一个只读属性Prefix,表示目标Controller所有Action方法共享的URL前缀。这个属性只能应用在Controller类型上,且只能有一个。

技术分享

[RoutePrefix("movies")]public class MoviesController : Controller{    [HttpGet]    [Route("{id:range(20,50)}")]    public ActionResult GetMoviesById(int id)    {        ViewBag.id = id;        return View();    }    [HttpGet]    [Route("starring/{starring}")]    [Route("starring/{starring}/{genre}")]    [Route("director/{director}/{genre}")]    public ActionResult FindMovies(string starring, string director, string genre)    {        ViewBag.starring = starring;        ViewBag.director = director;        ViewBag.genre = genre;        return View();    }    [HttpGet]    [Route("~/actors/{criteria}")] //使用“~”为前缀,屏蔽掉Controller类型上的前缀“movies”    public ActionResult FindActors(string criteria)    {        return View();    }}

设置Area名

使用RouteAreaAttribute类型的特性,它由两个属性AreaName和AreaPrefix,默认情况下使用AreaName做模版前缀,如果希望具有一个不同于Area名称的前缀,则要通过设置AreaPrefix属性来实现。

如果没有使用字符“~”前缀,特性路由的完整格式为{AreaPrefix}/{RoutePrefix}/{Template} 或者{AreaName}/{RoutePrefix}/{Template} 。

技术分享

[RouteArea("MoviesArea")]//[RouteArea()][RoutePrefix("movies")]public class MoviesController : Controller{    [HttpGet]    [Route("{id:range(20,50)}")]    public ActionResult GetMoviesById(int id)    {        ViewBag.id = id;        return View();    }    [HttpGet]    [Route("starring/{starring}")]    [Route("starring/{starring}/{genre}")]    [Route("director/{director}/{genre}")]    public ActionResult FindMovies(string starring, string director, string genre)    {        ViewBag.starring = starring;        ViewBag.director = director;        ViewBag.genre = genre;        return View();    }    [HttpGet]    [Route("~/admin/actors/{criteria}")] //使用“~”为前缀,屏蔽掉Controller类型上的前缀“movies”    public ActionResult FindActors(string criteria)    {        return View();    }}

自定义约束

 约束是以内联表达式的形式定义在路由模版里的,ASP.NET MVC通过一个实现IInlineConstraintResolver接口的对象来完成约束从字符串表达式到RouteConstraint对象之间的转换。

 系统提供的这个解析类型是DefaultInlineConstraintResolver。

public class MyInlineConstraintResolver : IInlineConstraintResolver{    public IDictionary<string, Type> ConstraintMap { get; private set; }    public MyInlineConstraintResolver()    {        this.ConstraintMap = new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase);        this.ConstraintMap.Add("bool", typeof(BoolRouteConstraint));        this.ConstraintMap.Add("datetime", typeof(DateTimeRouteConstraint));        this.ConstraintMap.Add("decimal", typeof(DecimalRouteConstraint));        this.ConstraintMap.Add("double", typeof(DoubleRouteConstraint));        this.ConstraintMap.Add("float", typeof(FloatRouteConstraint));        this.ConstraintMap.Add("guid", typeof(GuidRouteConstraint));        this.ConstraintMap.Add("int", typeof(IntRouteConstraint));        this.ConstraintMap.Add("long", typeof(LongRouteConstraint));        this.ConstraintMap.Add("minlength",typeof(MinLengthRouteConstraint));        this.ConstraintMap.Add("maxlength",typeof(MaxLengthRouteConstraint));        this.ConstraintMap.Add("length", typeof(LengthRouteConstraint));        this.ConstraintMap.Add("min", typeof(MinRouteConstraint));        this.ConstraintMap.Add("max", typeof(MaxRouteConstraint));        this.ConstraintMap.Add("range", typeof(RangeRouteConstraint));        this.ConstraintMap.Add("alpha", typeof(AlphaRouteConstraint));        this.ConstraintMap.Add("regex", typeof(RegexRouteConstraint));    }    public IRouteConstraint ResolveConstraint(string inlineConstraint)    {        string[] split = inlineConstraint.Split(‘(‘);        string type = split[0];        string argumentList = split.Length > 1 ? split[1].Trim().TrimEnd(‘)‘) : "";        Type constraintType;        if (this.ConstraintMap.TryGetValue(type, out constraintType))        {            split = string.IsNullOrEmpty(argumentList)? new string[0] : argumentList.Split(‘,‘);            ConstructorInfo[] constructors =(from c in constraintType.GetConstructors()                 where c.GetParameters().Count() == split.Length                 select c).ToArray();            if (constructors.Length != 1)            {                throw new InvalidOperationException("找不到与指定参数匹配的构造函数");            }            ConstructorInfo constructor = constructors[0];            ParameterInfo[] parameters = constructor.GetParameters();            object[] arguments = new object[parameters.Length];            for (int i = 0; i < parameters.Length; i++)            {                arguments[i] = Convert.ChangeType(split[i],parameters[i].ParameterType);            }            return (IRouteConstraint)constructor.Invoke(arguments);        }        return null;    }}
static void Main(string[] args){    string[] inlineConstraints = new string[]{    "bool","datetime", "decimal","double","float","guid","int",    "long","alpha", @"regex(^\d{3}-\d{7}$)","max(50)","min(10)","range(10,50)","maxlength(50)","minlength(10)","length(10,50)"};    MyInlineConstraintResolver constraintResolver = new MyInlineConstraintResolver();    IDictionary<string, IRouteConstraint> constraints = inlineConstraints.ToDictionary(inlineConstraint => inlineConstraint, inlineConstraint => constraintResolver.ResolveConstraint(inlineConstraint));    Console.WriteLine("{0,-24}{1}", "Expression", "RouteConstraint");    foreach (var item in constraints)    {        Console.WriteLine("{0,-24}{1}", item.Key,        item.Value.GetType().Name);    }    Console.ReadKey();}

技术分享

Asp.Net MVC<四>:路由器