解读ASP.NET 5 & MVC6系列教程(11):Routing路由

新版Routing功能介绍

在ASP.NET 5和MVC6中,Routing功能被全部重写了,虽然用法有些类似,但和之前的Routing原理完全不太一样了,该Routing框架不仅可以支持MVC和Web API,还支持一般的ASP.NET5程序。新版的改变有如下几个部分。

首先,Routing系统是基于ASP.NET 5的,是一个独立于MVC的路由框架,而不是基于MVC的。MVC只是在上面扩展了一个快捷方式而已。

其次,在ASP.NET 5中,MVC和Web API控制器没有区别了,即合二为一了。两者派生于同一个Controller基类。也就是说该Routing框架是适用于两者的,适用于MVC则意味着也适用于Web API。

最后,不管在基于约定的Route声明还是基于Attribute的Route声明,都可以使用内联约束和参数选项。例如,你可以约定路由中某个参数的数据类型,也可以让一个参数标记为可选类型,再或者给其提供一个默认值。

Routing框架的主要流程

基本的Routing框架是基于Middleware来实现的,这样就可以将其添加到HTTP的请求Pipeline中了,它可以喝其它任意Middleware一起进行组合使用,如静态文件处理程序、错误页、或者SignalR服务器。

在使用Routing框架之前,首要要了解Routing的作用,作用很简单:

对于HTTP请求,Routing系统负责找出与之匹配的route,创建route数据,并将该请求派送到该route对于的处理程序(Handler)上。Controller和Action的选择,只是MVC的Handler的一个具体实现,该实现使用route数据和HTTP请求中的其它信息来选择要执行的Controller和Action。在新版的MVC6中,该处理程序的名称为MvcRouteHandler。

路由系统的执行流程如下:

ASP.NET 5监听到一个HTTP请求。然后Routing Middleware就会尝试将route集合中的route匹配该请求。一旦成功匹配一个请求,就找出该route对应的handler。调用该handler上的RouteAsync方法(因为所有的handler都要实现该接口方法)。RoutingContext有一个IsHandled标记,如果该标记设置为true,则意味着该请求已经被这个handler成功处理了;如果设置为false,则意味着该handler无法处理该请求,系统会再为此匹配一个route。

和之前的Routing系统有点不同的是,老版的Routing系统一旦成功匹配一个路由,就将其交由其对应的Handler,不管对应的Handler能不能处理该请求,所以就会出现route匹配成功了,但是找不到对应的action,此时就会出现404错误,而新版对此作出了上述第4步骤的改进(重新将控制权交回给Routing系统,进行重新匹配),看起来还是非常不错的。

Route参数和约束条件的改进

在之前的route设置中,要约束一个参数的数据类型的话,我们需要使用类型如下代码:

routes.MapRoute(
 "Product",
 "Product/{productId}",
 defaults: new { controller = "Product", action = "Details" },
 constraints: new { productId = @"\d+" });

而在新版route中,就可以直接设置Product/{productId:int}了,约束条件遵守如下约定:

{parameter:constraint}

目前支持的约束如下:

约束 示例 说明
required "Product/{ProductName:required}" 参数必选
alpha "Product/{ProductName:alpha}" 匹配字母,大小写不限
int "Product/{ProductId:int}" 匹配int类型
long "Product/{ProductId:long}" 匹配long类型
bool "Product/{ProductId:bool}" 匹配bool类型
double "Product/{ProductId:double}" 匹配double类型
float "Product/{ProductId:float}" 匹配float类型
guid "Product/{ProductId:guid}" 匹配guid类型
decimal "Product/{ProductId:decimal}" 匹配decimal类型
datetime "Search/{datetime:datetime}" 匹配datetime类型
composite "Product/{ProductId:composite}" 匹配composite类型
length "Product/{ProductName:length(5)}" 长度必须是5个字符
length "Product/{ProductName:length(5, 10)}" 长度在5-10个之间
maxlength "Product/{productId:maxlength(10)}" 最大长度为10
minlength "Product/{productId:minlength(3)}" 最小长度为3
min "Product/{ProductID:min(3)}" 大于等于3
max "Product/{ProductID:max(10)}" 小于等于10
range "Product/{ProductID:range(5, 10)}" 对应的数组在5-10之间
Regex "Product/{productId:regex(^\d{4}$)}" 符合指定的正则表达式

而对于可选参数,则值需要在约束类型后面加一个问号即可,示例如下:

routes.MapRoute(
 "Product",
 "Product/{productId:long?}",
 new { controller = "Product", action = "Details" });

如果参数是必填的,需要保留一个默认值的话,则可以按照如下示例进行设置:

routes.MapRoute(
 "Product",
 "Product/{productId:long=1000}",
 new { controller = "Product", action = "Details" });

通用Routing

关于示例使用,我们先不从MVC开始,而是先从普通的Routing使用方式开始,新版route添加的时候默认添加的是TemplateRoute实例,并且在该实例实例化的时候要设置一个Handler

举例来说,我们先创建一个空的ASP.NET 5项目,并在project.json文件的dependencies节点中添加程序集"Microsoft.AspNet.Routing": "1.0.0-beta3",,在Startup.csConfigure方法里添加如下代码:

public void Configure(IApplicationBuilder app)
{
 RouteCollection routes = new RouteCollection();
 routes.Add(new TemplateRoute(new DebuggerRouteHandler("RouteHandlerA"), "", null));
 routes.Add(new TemplateRoute(new DebuggerRouteHandler("RouteHandlerB"), "test/{a}/{b:int}", null));
 routes.Add(new TemplateRoute(new DebuggerRouteHandler("RouteHandlerC"), "test2", null));

 app.UseRouter(routes); // 开启Routing功能
}

在这里,我们设置HTTP请求处理的的Handler为DebuggerRouteHandler,该类继承于IRouter,实例代码如下:

public class DebuggerRouteHandler : IRouter
{
 private string _name;

 public DebuggerRouteHandler(string name)
 {
 _name = name;
 }

 public string GetVirtualPath(VirtualPathContext context)
 {
 throw new NotImplementedException();
 }

 public async Task RouteAsync(RouteContext context)
 {
 var routeValues = string.Join("", context.RouteData.Values);
 var message = String.Format("{0} Values={1} ", _name, routeValues);
 await context.HttpContext.Response.WriteAsync(message);
 context.IsHandled = true;
 }
}

上述类,继承IRouter以后,必须实现一个RouteAsync的方法,并且如果处理成功,则将IsHandled设置为true

访问如下网址即可查看相应的结果:

正常:`http://localhost:5000/`
正常:`http://localhost:5000/test/yyy/12`
404 :`http://localhost:5000/test/yyy/s`
正常:`http://localhost:5000/test2`
404 :`http://localhost:5000/test3`

注意:TemplateRouteDebuggerRouteHandler都继承于IRouter,是实现前面所述的不出现404错误(继续匹配下一个路由)的核心。

MVC中的Routing

在MVC示例程序中,我们只需要配置在调用app.UseMVC方法的时候,使用委托中的MapRoute方法来定义各种route就可以了。在这里我们以空白项目为例,来看看MVC的route如何使用。

第一步:在project.json文件的dependencies节点中引用程序集"Microsoft.AspNet.Mvc": "6.0.0-beta3"
第二部:添加MVC的Middleware,并使用MVC,然后添加一条默认的路由,代码如下:

public void ConfigureServices(IServiceCollection services)
{
 services.AddMvc();
}

public void Configure(IApplicationBuilder app)
{
 app.UseMvc(routeBuilder =>
 {
 routeBuilder.MapRoute(
  name: "default",
  template: "{controller}/{action}/{id?}",
  defaults: new { controller = "Home", action = "Index" });
 });
}

第三步:分别创建如下如下三种Controller,其中ProductsController继承于Microsoft.AspNet.Mvc下的Controller

public class ProductsController : Controller
{
 public IActionResult Index()
 {
 return Content("It Works with Controller Base Class!");
 }
}

public class DemoController
{
 public IActionResult Index()
 {
 return new ObjectResult("It Works without Controller Base Class!");
 }
}

public class APIController
{
 public object Index()
 {
 return new { Code = 100000, Data = "OK" };
 }
}

访问http://localhost:5000/productshttp://localhost:5000/demo,均能显示正常的输出结果;而访问http://localhost:5000/api的时候返回的则是json数据。

这就是我们在前面ASP.NET5新特性中所讲的MVC和API合二为一了,并且也可以不继承于Controller基类(但类名要以Controller结尾)。这种技术的核心是Controller的查找机制,关于如何在一个项目中查找合适的程序集,请参考《Controller与Action》章节。

新版MVC在判定Controller的时候,有2个条件:要么继承于Controller,要么是引用MVC程序集并且类名以Controller结尾。

所以,在创建MVC Controller和Web API Controller的时候,如果你不需要相关的上下文(如HTTPContext、ActionContext等)的话,则可以不必继承于Controller基类;但推荐都继承于Controller,因为可以多多利用基类的方法和属性,因为不管继承不继承,你定义的所有Controller类都要走MVC的各个生命周期,我们通过ActionFilter来验证一下:

第一步:在project.json文件的dependencies节点中引用程序集"Microsoft.AspNet.Server.WebListener": "1.0.0-beta3"
第二步:创建一个Aciton Filter,分别在Action执行前和执行后输出一行文字,代码如下:

public class ActionFilterTest : IActionFilter
{
 public void OnActionExecuting(ActionExecutingContext context)
 {
  var typeName = context.Controller.GetType().FullName;
  Console.WriteLine(typeName + "." + context.ActionDescriptor.Name + ":Start");
 }

 public void OnActionExecuted(ActionExecutedContext context)
 {
  var typeName = context.Controller.GetType().FullName;
  Console.WriteLine(typeName + "." + context.ActionDescriptor.Name + ":END");
 }
}

第三步:在ConfigureServices方法里注册该Action Filter。

services.Configure<MvcOptions>(options =>
{
 options.Filters.Add(typeof(ActionFilterTest));
});

运行程序,并访问响应的路径,三种类型的代码均会按计划输出内容,输出内容如下:

RouterTest.ProductsController.Index:Start
RouterTest.ProductsController.Index:End
RouterTest.DemoController.Index:Start
RouterTest.DemoController.Index:End
RouterTest.APIController.Index:Start
RouterTest.APIController.Index:End

普通的ASP.NET5程序和MVC程序是可以在一起混合使用Routing功能的。

自定义Route

ASP.NET 5和MVC6都提供了丰富的Route自定义功能,关于普通Route的自定义,可以参考前面小节的DebuggerRouteHandler,这种方式需要实现自己的HTTP输出,相当于原来轻量级的IHttpHandler一样。本节,我们将这种在基于MVC的Route自定义功能,即定义的Route的Handler处理程序都是MvcRouteHandler。

在之前版本的MVC中,要自定义Route,一般都是继承于RouteBase基类或Route类;而在新版的MVC6中,要实现自定义Route,有三种方式,分别如下:

继承于TemplateRoute实现IRouter实现INamedRouter(注:INamedRouter和IRouter的唯一区别是多了一个名称)

本例中,我们以继承继承于TemplateRoute为例,首先创建一个继承于该类的子类PromoTemplateRoute,该类只匹配/promo目录下的路径。

public class PromoTemplateRoute : TemplateRoute
{
 public PromoTemplateRoute(IRouter target, string routeTemplate, IInlineConstraintResolver inlineConstraintResolver)
  : base(target, routeTemplate, inlineConstraintResolver: inlineConstraintResolver)
 {
 }

 public PromoTemplateRoute(IRouter target,
       string routeTemplate,
       IDictionary<string, object> defaults,
       IDictionary<string, object> constraints,
       IDictionary<string, object> dataTokens,
       IInlineConstraintResolver inlineConstraintResolver)
  : base(target, routeTemplate, defaults, constraints, dataTokens, inlineConstraintResolver)
 {
 }

 public PromoTemplateRoute(IRouter target,
       string routeName,
       string routeTemplate,
       IDictionary<string, object> defaults,
       IDictionary<string, object> constraints,
       IDictionary<string, object> dataTokens,
       IInlineConstraintResolver inlineConstraintResolver)
  : base(target, routeName, routeTemplate, defaults, constraints, dataTokens, inlineConstraintResolver)
 { }

 public async override Task RouteAsync(RouteContext context)
 {
  var requestPath = context.HttpContext.Request.Path.Value ?? string.Empty;
  if (!requestPath.StartsWith("/promo", StringComparison.OrdinalIgnoreCase))
  {
   return;
  }
  await base.RouteAsync(context);
 }
}

为了方便使用,我们也比葫芦画瓢,创建一些扩展方法,示例如下:

public static class RouteBuilderExtensions
{
 public static IRouteBuilder MapPromoRoute(this IRouteBuilder routeCollectionBuilder, string name, string template)
 {
  MapPromoRoute(routeCollectionBuilder, name, template, defaults: null);
  return routeCollectionBuilder;
 }

 public static IRouteBuilder MapPromoRoute(this IRouteBuilder routeCollectionBuilder, string name, string template, object defaults)
 {
  return MapPromoRoute(routeCollectionBuilder, name, template, defaults, constraints: null, dataTokens: null);
 }

 public static IRouteBuilder MapPromoRoute(this IRouteBuilder routeCollectionBuilder, string name, string template, object defaults, object constraints, object dataTokens)
 {
  var inlineConstraintResolver = routeCollectionBuilder.ServiceProvider.GetService<IInlineConstraintResolver>();
  routeCollectionBuilder.Routes.Add(
   new PromoTemplateRoute(
    routeCollectionBuilder.DefaultHandler,
    name,
    template,
    ObjectToDictionary(defaults),
    ObjectToDictionary(constraints),
    ObjectToDictionary(dataTokens),
    inlineConstraintResolver));

  return routeCollectionBuilder;
 }

 private static IDictionary<string, object> ObjectToDictionary(object value)
 {
  var dictionary = value as IDictionary<string, object>;
  if (dictionary != null)
  {
   return dictionary;
  }

  return new RouteValueDictionary(value);
 }
}

使用的时候,则很简单,和之前的方式非常类似,示例如下:

routes.MapPromoRoute(
 name: "default2",
 template: "promo/{controller}/{action}/{id?}",
 defaults: new { controller = "Home", action = "Index" });

通过这种方式,我们可以在符合路由匹配条件的时候,使用PromoTemplateRoute类来处理一些自定义逻辑,比如添加一些额外的文件头信息等等。

基于Attribute的Routing

基于Attribute的Routing功能一直是MVC所期待的功能,在Web API已经通过RoutePrefix(Controller上使用)和Route(Action上使用)来实现了。该特性在MVC 6中进行了重写和增强,并且由于MVC和Web API合二而一了,所以在这两种Controller上都可以使用该特性。

举例来说:

[Route("bookhome")]
public class HomeController : Controller
{
 public IActionResult Index()
 {
  return View();
 }

 [Route("about")]
 public IActionResult About()
 {
  ViewBag.Message = "Your application description page.";
  return View();
 }

 [Route("contactus")]
 public IActionResult Contact()
 {
  ViewBag.Message = "Your contact page.";
  return View();
 }
}

在上述Controller上定义一个bookhome前缀,并且在About和Contact上又分别定义了action名称,所以上述3个Action的访问地址则是如下这种形式:

/bookhome
/bookhome/about
/bookhome/contactus

在这里,我们需要注意,Controller和Action使用的Attribute都是Route,同时,在这些路由模板字符串中,依然可以使用内联参数,比如,我们可以定义类似这样的路由:

[Route("products/{productId:int}")]

Controller和Action标记位

另外,针对Route的模板字符串,不仅支持内联参数,还支持Controller和Action的标记位,即不用写死该Controller或Action的名称,使用一个[controller][action]的字符即可表示该Controller或Action的名称。比如,我们可以在Controller上定义这样的一个路由(Action上什么都不定义):

[Route("book/[controller]/[action]")]

这样访问首页的地址就变成了:/book/Home/Index

Web API的等价Route定义

在Web API中,我们一般还要定义GET、POST这样的请求方式,为了方便,新版的HTTPGET等一系列方法都集成了Route功能,直接在构造函数传入Route模板即可,示例如下:

[HttpGet("products/{productId:int}")]

上述Route的定义,即表明,既要符合products/{productId:int}的路由规则,又要是GET请求。

其实HTTPGET这一系列Attribute也可以在普通的MVC Controller上使用,因为在MVC6中,MVC Controller和Web API Controller本身就是同一个东西,只不过MVC的返回类型都是IActionResult而已。Route定义,不仅仅支持GET请求,还支持POST等其它类型的请求,即不限制请求方式。在HttpXXX系列特性中,也是支持内联参数和[controller]、[action]标记位的,大可放心使用。目前可用的特性类有:HttpGet、HttpPost、HttpPut、HttpDelete、HttpPatch。

非要重要Route定义规则

基于Attribute的Route定义很方便,但也很危险,具体规则和危险性如下。

规则1:Controller上定义了Route特性很危险

一旦在Controller上定义了Route特性,该Controller下的所有路由规则都不受其它规则控制了,比如,如果你定义了类似这样的

[Route("book")]
public class HomeController : Controller
{
 public IActionResult Index()
 {
  return View();
 }

 public IActionResult About()
 {
  ViewBag.Message = "Your application description page.";
  return View();
 }
}

那么,上述2个Action你都再也没办法访问了,因为默认的action的名称根本就不会起作用,即/book/index/book/about这两个路径无法路由到对应的Action方法上。而且/book也访问不了,因为有两个以上的Action,系统无法定位到其中一个Action上。

所以要让上述Action能访问,必须要在其中一个Action上定义再Route,例如:

[Route("book")]
public class HomeController : Controller
{
 public IActionResult Index()
 {
  return View();
 }

 [Route("about")]
 public IActionResult About()
 {
  ViewBag.Message = "Your application description page.";
  return View();
 }
}

这样,就可以通过/book/about来访问About方法了,而访问/book则可以访问默认的index方法了,因为该index方法是默认唯一一个没有定义路由的方法,所以他就是/book路由规则的默认Action。如果,有3个Action的话,则必须要至少给两个Action定义Route,示例如下:

[Route("book")]
public class HomeController : Controller
{
 [Route("index")]
 public IActionResult Index()
 {
  return View();
 }

 [Route("about")]
 public IActionResult About()
 {
  ViewBag.Message = "Your application description page.";
  return View();
 }
 public IActionResult Contact()
 {
  ViewBag.Message = "Your contact page.";
  return View();
 }
}

此时,Contact方法就是默认/book路由的Action了,访问/book路径的话,就会显示Contact对应的页面。

规则2:Route和HttpGet可以一起使用,但也很危险

我们前面提到,在Action上即可以使用Route特性,也可以使用HttpGet特性,两者之间的不同,就是多了一个Http Method。很多同学可以要问两个特性在一起使用的时候会有问题么?

其实,这两个特性是可以在一起使用的,示例如下:

[Route("book")]
public class HomeController : Controller
{
 [Route("Contact")]
 [HttpGet("home/Contact2")]
 public IActionResult Contact()
 {
  ViewBag.Message = "Your contact page.";

  return View();
 }
}

这样/book/contact/book/home/contact2这两个网址,都可以访问了。但如果这里定义HttpGet,情况就不一样了,示例如下:

[Route("Contact")]
[HttpPost("home/Contact2")]

此时,访问该Action的方式,要么是以GET的方式访问/book/contact地址,要么是以POST的方式访问/book/home/contact2。所以为了避免出错,建议使用的时候不要讲两者混用,即便是要同时支持GET和POST,那也是建议用同类型的HttpXXX来定义这些路由,例如:

[HttpGet("Contact")]
[HttpPost("home/Contact2")]

这样,看起来就清晰多了。

规则3:多个Route和多个HttpXXX也可以一起使用,但也很危险

在如下示例中,我们为HomeController定义了2个Route特性,而Contact定义了2个Route特性和1个HttpPost特性。

[Route("book")]
[Route("tom")]
public class HomeController : Controller
{
 [Route("Contact")]
 [Route("ContactUS")]
 [HttpPost("home/Contact2")]
 public IActionResult Contact()
 {
  ViewBag.Message = "Your contact page.";
  return View();
 }
}

那么,在上述代码生效后,我们将有六种访问来访问该Action,这六种方式分布如下:

GET:/book/contact
GET:/book/contactus
GET:/tom/contact
GET:/tom/contactus
POST:/book/home/contact2
POST:/tom/home/contact2

但是,在视图文件中,通过@Html.ActionLink("Contact", "Contact", "Home")生成链接地址的话,则默认会使用第一个定义的Route,如果要强制指定顺序,则可以使用Order属性来定义排序值,默认会优先使用最小的值。示例如下:

[Route("book", Order = 1)]
[Route("tom", Order = 0)]
public class HomeController : Controller
{
 [Route("Contact", Order = 1)]
 [Route("ContactUS", Order = 0)]
 [HttpPost("home/Contact2", Order = 2)]
 public IActionResult Contact()
 {
  ViewBag.Message = "Your contact page.";
  return View();
 }
}

自定义内联参数约束

在前面的介绍中,我们知道任意类型的路由在定义的时候都支持不同的内联参数约束,因为这些约束是基于ASP.NET 5的,而不是基于MVC6的,并且这些约束还是可以扩展的,本节我们就来看看如何自定义一些扩展。

无参数约束

首先,我们来看一个比较简单的约束,即无参数约束,类似于{productId:int}这样的类型约束,假设我们要实现一个AABBCC字符串限定的约束,示例如下:

[Route("index/{productId:aabbcc}")]

为了确保/index/112233和/index/aabbcc是符合约束的,而/index/aabbccdd是不符合约束的,我们首先要自定义一个约束类AABBCCRouteConstraint,并实现IRouteConstraint接口,示例如下:

public class AABBCCRouteConstraint : IRouteConstraint
{
 public bool Match(HttpContext httpContext, IRouter route, string routeKey, IDictionary<string, object> values, RouteDirection routeDirection)
 {
  bool b = false;

  object value;
  if (values.TryGetValue(routeKey, out value) && value != null)
  {
   if (value is string) // 获取传入的值,比如aabbcc或112233
   {
    string aabbcc = value.ToString();
    b = !string.IsNullOrWhiteSpace(aabbcc) && aabbcc.Length == 6 && aabbcc[0] == aabbcc[1] && aabbcc[2] == aabbcc[3] && aabbcc[4] == aabbcc[5];

   }
  }

  return b;
 }
}

在该实现类中,要实现Match方法,根据传入的各种参数,判断是否符合定义的约束,并返回true或false,Match方法的参数中,其中routeKey是约束{productId:aabbcc}对应的参数名称(本例中是productId),values集合中会有该productId所对应的数字(如112233),在该方法通过响应的判断返回true和false。

下一步,就是要将该约束类注册到Routing系统的约束集合中,在Startup.csConfigureServices方法中,执行如下语句:

services.Configure<RouteOptions>(opt =>
{
 opt.ConstraintMap.Add("aabbcc", typeof(AABBCCRouteConstraint));
});

注意,这里注册的aabbcc就是前面我们所指定约束名称,完成上述步骤以后,即可实现类似{productId:int}的功能了。

有参数约束

一般情况下,有些时候可能需要定义一些约束的值,比如Length(1,10)来表示1-10之间的字符串长度,举例来说,加入我们要定义一个4个参数的约束规则,如abcd(1,10,20,30)来表示一个特殊的验证项,则需要声明有4个参数的构造函数,示例如下:

public class ABCDRouteConstraint : IRouteConstraint
{
 public int A { get; private set; }
 public int B { get; private set; }
 public int C { get; private set; }
 public int D { get; private set; }
 public ABCDRouteConstraint(int a, int b, int c, int d)
 {
  A = a;B = b;C = c;D = d;
 }

 public bool Match(HttpContext httpContext, IRouter route, string routeKey, IDictionary<string, object> values, RouteDirection routeDirection)
 {
  bool b = false;

  object value;
  if (values.TryGetValue(routeKey, out value) && value != null)
  {
   var valueString = value.ToString();//这里需要进行进一步的验证工作
   return true;
  }

  return b;
 }
}

假如你在Action上了定义了如下约束:

[Route("index/{productId:abcd(1,20,30,40)}")]

那么,在注册该约束类型以后,系统启动厚扫描所有的Route进行注册的时候,会分析你定义的这4个值,然后会将这4个值赋值给该路由对应的约束实例上的A、B、C、D四个属性上,以便在HTTP请求过来的时候,分析URL上的值,看是否符合Match里定义的规则(在验证的时候就可以使用这4个属性值)。

默认约束的所有代码可以参考: https://github.com/aspnet/Routing/tree/dev/src/Microsoft.AspNet.Routing/Constraints

另外,如果定义了4个参数的约束,那么在action上定义路由的时候则必须符合参数的数据类型,如果不符合,系统启动的时候就会出错,示例错误如下:

[Route("index/{productId:abcd}")] //没有为该对象定义无参数的构造函数

[Route("index/{productId:abcd(a)}")]
[Route("index/{productId:abcd('a')}")] //输入字符串的格式不正确

[Route("index/{productId:abcd(1,2,3)}")] //构造函数的参数个数和定义的参数个数不一致。

如果你定义的参数类型是字符串类型,则下面2种形式的定义都是合法的:

[Route("index/{productId:abcd(a,b,c,d)}")]
[Route("index/{productId:abcd('a','b','c','d')}")]

虽然ASP.NET 5 和MVC6的路由使用方式很简单,但是相关的使用规则却很复杂,大家使用的时候需要多加注意。

(0)

相关推荐

  • ASP.NET MVC3关于生成纯静态后如何不再走路由直接访问静态页面

    要解决这个问题,我们需要先了解ASP.NET应用程序的生命周期,先看下面作者整理的一张图片: 从图中我们可以清楚的看到:通用IIS访问应用程序时,每次的单个页面URL访问时,都会先经过HttpApplication 管线处理请求,走过BeginRequest 事件之后才会去走路由访问具体的Controller和Action,最后结束的时候会请求EndRequest事件.下面用一张图来表示这个顺序: 注意图中标示的红色部分就是我们要实现的部分,实现如下:1 新建MyHandler.cs 复制代码

  • 为ASP.NET MVC及WebApi添加路由优先级

    一.为什么需要路由优先级 大家都知道我们在Asp.Net MVC项目或WebApi项目中注册路由是没有优先级的,当项目比较大.或有多个区域.或多个Web项目.或采用插件式框架开发时,我们的路由注册很可能 不是写在一个文件中的,而是分散在很多不同项目的文件中,这样一来,路由的优先级的问题就突显出来了. 比如: App_Start/RouteConfig.cs中 routes.MapRoute( name: "Default", url: "{controller}/{actio

  • ASP.NET Core中使用默认MVC路由的配置

    ASP.NET Core里Route这块的改动不大,只是一些用法上有了调整,提供了一些更加简洁的语法. 而对于自定义路由的支持当然也是没有问题的,这个功能应该是从MVC1.0版本就已经有这个功能. 先看看ASP.NET Core里面实现默认MVC路由的配置方式 通常情况下,在使用MVC项目的时候,默认的路由就足够了,就是常见的通过Controller和Action获取具体的方法的方式. 从一个最基本的项目开始,执行以下步骤,就可以使得项目支持MVC路由 1.创建一个空白的ASP.NET Core

  • asp.net mvc路由篇 如何找到 IHttpHandler方法介绍

    学习是使用asp.net已经有很长一段时间了,现在就来分析一下mvc的整过过程吧.个人计划写一个mvc系列的博文,仅从源代码的角度来分析mvc.在接触mvc时我们一定会经历路由,那么路由这东东是怎么搞出来的啊.在我们的web.config中有这么一句: <add assembly="System.Web.Routing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" /> 看来路由是它咋

  • ASP.NET Core MVC 配置全局路由前缀

    ASP.NET Core MVC 配置全局路由前缀 前言 大家好,今天给大家介绍一个 ASP.NET Core MVC 的一个新特性,给全局路由添加统一前缀.严格说其实不算是新特性,不过是Core MVC特有的. 应用背景 不知道大家在做 Web Api 应用程序的时候,有没有遇到过这种场景,就是所有的接口都是以 /api 开头的,也就是我们的api 接口请求地址是像这样的: http://www.example.com/api/order/333 或者是这样的需求 http://www.exa

  • 解读ASP.NET 5 & MVC6系列教程(1):ASP.NET 5简介

    ASP.NET 5简介 ASP.NET 5是一个跨时代的改写,所有的功能和模块都进行了独立拆分,做到了彻底解耦.为了这些改写,微软也是蛮 拼的,几乎把.NET Framwrok全部改写了一遍,形成了一个.NET Core的东西. 在.NET Core里一切都是可配置的,包括Session.MVC等功能,而一切可配置的功能都是可以在Nuget上进行下载. 目前ASP.NET 5依旧兼容老的.NET Framwrok,但要在进行跨平台的部署,还是只能使用新改版的.NET Core CLR. 目前的A

  • 解读ASP.NET 5 & MVC6系列教程(11):Routing路由

    新版Routing功能介绍 在ASP.NET 5和MVC6中,Routing功能被全部重写了,虽然用法有些类似,但和之前的Routing原理完全不太一样了,该Routing框架不仅可以支持MVC和Web API,还支持一般的ASP.NET5程序.新版的改变有如下几个部分. 首先,Routing系统是基于ASP.NET 5的,是一个独立于MVC的路由框架,而不是基于MVC的.MVC只是在上面扩展了一个快捷方式而已. 其次,在ASP.NET 5中,MVC和Web API控制器没有区别了,即合二为一了

  • 解读ASP.NET 5 & MVC6系列教程(13):TagHelper

    在新版的MVC6中,微软提供了强大的TagHelper功能,以便让我们摆脱如下的臃肿代码: @Html.LabelFor(model => model.FullName) @Html.EditFor(model => model.FullName) @Html.ValidationMessageFor(model => model.FullName) 引入新功能TagHelper以后,我们只需要这样定义就可以了,代码如下: @addTagHelper "*, Microsoft

  • 解读ASP.NET 5 & MVC6系列教程(12):基于Lamda表达式的强类型Routing实现

    前面的深入理解Routing章节,我们讲到了在MVC中,除了使用默认的ASP.NET 5的路由注册方式,还可以使用基于Attribute的特性(Route和HttpXXX系列方法)来定义.本章,我们将讲述一种基于Lambda表达式的强类型类型. 这种方式的基本使用示例如下: services.Configure<MvcOptions>(opt => { opt.EnableTypedRouting(); opt.GetRoute("homepage", c =>

  • 解读ASP.NET 5 & MVC6系列教程(7):依赖注入

    在前面的章节(Middleware章节)中,我们提到了依赖注入功能(Dependency Injection),ASP.NET 5正式将依赖注入进行了全功能的实现,以便开发人员能够开发更具弹性的组件程序,MVC6也利用了依赖注入的功能重新对Controller和View的服务注入功能进行了重新设计:未来的依赖注入功能还可能提供更多的API,所有如果还没有开始接触依赖注入的话,就得好好学一下了. 在之前版本的依赖注入功能里,依赖注入的入口有MVC中的IControllerFactory和Web A

  • 解读ASP.NET 5 & MVC6系列教程(2):初识项目

    初识项目 打开VS2015,创建Web项目,选择ASP.NET Web Application,在弹出的窗口里选择ASP.NET 5 Website模板创建项目,图示如下: 我们可以看到,此时Web Forms\MVC\Web API复选框都选择不了,原有是因为在ASP.NET 5中做了大量更改,移除了Web Forms功能,将MVC.Web API.Web Pages这些功能合在了一起,所以自然就不需要这些复选框了.另外由于是CTP版,所以目前还没有提供单元测试项目的创建. 新创建的项目在VS

  • 解读ASP.NET 5 & MVC6系列教程(17):MVC中的其他新特性

    (GlobalImport全局导入功能) 默认新建立的MVC程序中,在Views目录下,新增加了一个_GlobalImport.cshtml文件和_ViewStart.cshtml平级,该文件的功能类似于之前Views目录下的web.config文件,之前我们在该文件中经常设置全局导入的命名空间,以避免在每个view文件中重复使用@using xx.xx语句. 默认的示例如下: @using BookStore @using Microsoft.Framework.OptionsModel @a

  • 解读ASP.NET 5 & MVC6系列教程(16):自定义View视图文件查找逻辑

    之前MVC5和之前的版本中,我们要想对View文件的路径进行控制的话,则必须要对IViewEngine接口的FindPartialView或FindView方法进行重写,所有的视图引擎都继承于该IViewEngine接口,比如默认的RazorViewEngine.但新版本MVC6中,对视图文件的路径方式却不太一样了,目前有两种方式,一种是通过RazorViewEngine,另外一种是通过新特性IViewLocationExpander接口. 通过RazorViewEngine来控制View路径

  • 解读ASP.NET 5 & MVC6系列教程(15):MvcOptions配置

    程序模型处理 IApplicationModelConvention 在MvcOptions的实例对象上,有一个ApplicationModelConventions属性(类型是:List<IApplicationModelConvention>),该属性IApplicationModelConvention类型的接口集合,用于处理应用模型ApplicationModel,该集合是在MVC程序启动的时候进行调用,所以在调用之前,我们可以对其进行修改或更新,比如,我们可以针对所有的Control

  • 解读ASP.NET 5 & MVC6系列教程(14):View Component

    在之前的MVC中,我们经常需要类似一种小部件的功能,通常我们都是使用Partial View来实现,因为MVC中没有类似Web Forms中的WebControl的功能.但在MVC6中,这一功能得到了极大的改善.新版MVC6中,提供了一种叫做View Component的功能. 你可以将View Component看做是一个mini的Controller--它只负责渲染一小部分内容,而非全部响应,所有Partial View能解决的问题,你都可以使用View Component来解决,比如:动态

随机推荐