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

一、为什么需要路由优先级

大家都知道我们在Asp.Net MVC项目或WebApi项目中注册路由是没有优先级的,当项目比较大、或有多个区域、或多个Web项目、或采用插件式框架开发时,我们的路由注册很可能 不是写在一个文件中的,而是分散在很多不同项目的文件中,这样一来,路由的优先级的问题就突显出来了。

比如: App_Start/RouteConfig.cs中

routes.MapRoute(
  name: "Default",
  url: "{controller}/{action}/{id}",
  defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
); 

Areas/Admin/AdminAreaRegistration.cs中 

context.MapRoute(
  name: "Login",
  url: "login",
  defaults: new { area = "Admin", controller = "Account", action = "Login", id = UrlParameter.Optional },
  namespaces: new string[] { "Wenku.Admin.Controllers" }
);

假如是先注册上面那个通用的default路由,再注册这个login的路由,那么无论怎么样,都会先匹配第一个满足条件的路由,也就是第两个路由注册是无效的。
造成这个问题的原因就是这两个路由注册的顺序问题,而Asp.Net MVC及WebApi中注册路由都没有优先级这个概念,所以今天我们就是要自己实现这个想法,在注册路由时加入一个优先级的概念。

二、解决思路

1、先分析路由注册的入口,比如我们新建一个mvc4.0的项目

public class MvcApplication : System.Web.HttpApplication
{
  protected void Application_Start()
  {
    AreaRegistration.RegisterAllAreas(); 

    WebApiConfig.Register(GlobalConfiguration.Configuration);
    FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
    RouteConfig.RegisterRoutes(RouteTable.Routes);
  }
} 

Mvc路由的注册入口有两个:
a. AreaRegistration.RegisterAllAreas();                                    注册区域路由
b. RouteConfig.RegisterRoutes(RouteTable.Routes);          注册项目路由

WebApi路由注册入口有一个:
WebApiConfig.Register(GlobalConfiguration.Configuration);  注册WebApi路由

2、注册路由的处理类分析

AreaRegistrationContext
RouteCollection
HttpRouteCollection

注册路由时主要是由这三个类来注册处理路由的。

3、路由优先级方案

a、更改路由的注册入口
b、自定义一个路由的结构类RoutePriority及HttpRoutePriority,这两个类下面都有Priority这个属性
c、自定一个RegistrationContext来注册路由,注册的对象为上述自定义路由。
d、所有的路由注册完成之后再按优先顺序添加到RouteCollection及HttpRouteCollection中实际生效。

三、具体实现

1、路由定义

public class RoutePriority : Route
{
  public string Name { get; set; }
  public int Priority { get; set; } 

  public RoutePriority(string url, IRouteHandler routeHandler)
    : base(url,routeHandler)
  { 

  }
} 

public class HttpRoutePriority
{
  public string Name { get; set; }
  public int Priority { get; set; }
  public string RouteTemplate{get;set;}
  public object Defaults{get;set;}
  public object Constraints{get;set;}
  public HttpMessageHandler Handler{get;set;}
}

2、定义路由注册的接口

public interface IRouteRegister
{
  void Register(RegistrationContext context);
} 

3、定义路由注册上下文类

public class RegistrationContext
{
  #region mvc
  public List<RoutePriority> Routes = new List<RoutePriority>(); 

  public RoutePriority MapRoute(string name, string url,int priority=0)
  {
    return MapRoute(name, url, (object)null /* defaults */, priority);
  } 

  public RoutePriority MapRoute(string name, string url, object defaults, int priority = 0)
  {
    return MapRoute(name, url, defaults, (object)null /* constraints */, priority);
  } 

  public RoutePriority MapRoute(string name, string url, object defaults, object constraints, int priority = 0)
  {
    return MapRoute(name, url, defaults, constraints, null /* namespaces */, priority);
  } 

  public RoutePriority MapRoute(string name, string url, string[] namespaces, int priority = 0)
  {
    return MapRoute(name, url, (object)null /* defaults */, namespaces, priority);
  } 

  public RoutePriority MapRoute(string name, string url, object defaults, string[] namespaces,int priority=0)
  {
    return MapRoute(name, url, defaults, null /* constraints */, namespaces, priority);
  } 

  public RoutePriority MapRoute(string name, string url, object defaults, object constraints, string[] namespaces, int priority = 0)
  {
    var route = MapPriorityRoute(name, url, defaults, constraints, namespaces, priority);
    var areaName = GetAreaName(defaults);
    route.DataTokens["area"] = areaName; 

    // disabling the namespace lookup fallback mechanism keeps this areas from accidentally picking up
    // controllers belonging to other areas
    bool useNamespaceFallback = (namespaces == null || namespaces.Length == 0);
    route.DataTokens["UseNamespaceFallback"] = useNamespaceFallback; 

    return route;
  } 

  private static string GetAreaName(object defaults)
  {
    if (defaults != null)
    {
      var property = defaults.GetType().GetProperty("area");
      if (property != null)
        return (string)property.GetValue(defaults, null);
    } 

    return null;
  } 

  private RoutePriority MapPriorityRoute(string name, string url, object defaults, object constraints, string[] namespaces,int priority)
  {
    if (url == null)
    {
      throw new ArgumentNullException("url");
    } 

    var route = new RoutePriority(url, new MvcRouteHandler())
    {
      Name = name,
      Priority = priority,
      Defaults = CreateRouteValueDictionary(defaults),
      Constraints = CreateRouteValueDictionary(constraints),
      DataTokens = new RouteValueDictionary()
    }; 

    if ((namespaces != null) && (namespaces.Length > 0))
    {
      route.DataTokens["Namespaces"] = namespaces;
    } 

    Routes.Add(route);
    return route;
  } 

  private static RouteValueDictionary CreateRouteValueDictionary(object values)
  {
    var dictionary = values as IDictionary<string, object>;
    if (dictionary != null)
    {
      return new RouteValueDictionary(dictionary);
    } 

    return new RouteValueDictionary(values);
  }
  #endregion 

  #region http
  public List<HttpRoutePriority> HttpRoutes = new List<HttpRoutePriority>(); 

  public HttpRoutePriority MapHttpRoute(string name, string routeTemplate, int priority = 0)
  {
    return MapHttpRoute(name, routeTemplate, defaults: null, constraints: null, handler: null, priority: priority);
  } 

  public HttpRoutePriority MapHttpRoute(string name, string routeTemplate, object defaults, int priority = 0)
  {
    return MapHttpRoute(name, routeTemplate, defaults, constraints: null, handler: null, priority: priority);
  } 

  public HttpRoutePriority MapHttpRoute(string name, string routeTemplate, object defaults, object constraints, int priority = 0)
  {
    return MapHttpRoute(name, routeTemplate, defaults, constraints, handler: null, priority: priority);
  } 

  public HttpRoutePriority MapHttpRoute(string name, string routeTemplate, object defaults, object constraints, HttpMessageHandler handler, int priority = 0)
  {
    var httpRoute = new HttpRoutePriority();
    httpRoute.Name = name;
    httpRoute.RouteTemplate = routeTemplate;
    httpRoute.Defaults = defaults;
    httpRoute.Constraints = constraints;
    httpRoute.Handler = handler;
    httpRoute.Priority = priority;
    HttpRoutes.Add(httpRoute); 

    return httpRoute;
  }
  #endregion
}

4、把路由注册处理方法添加到Configuration类中

public static Configuration RegisterRoutePriority(this Configuration config)
{
  var typesSoFar = new List<Type>();
  var assemblies = GetReferencedAssemblies();
  foreach (Assembly assembly in assemblies)
  {
    var types = assembly.GetTypes().Where(t => typeof(IRouteRegister).IsAssignableFrom(t) && !t.IsAbstract && !t.IsInterface);
    typesSoFar.AddRange(types);
  } 

  var context = new RegistrationContext();
  foreach (var type in typesSoFar)
  {
    var obj = (IRouteRegister)Activator.CreateInstance(type);
    obj.Register(context);
  } 

  foreach (var route in context.HttpRoutes.OrderByDescending(x => x.Priority))
    GlobalConfiguration.Configuration.Routes.MapHttpRoute(route.Name, route.RouteTemplate, route.Defaults, route.Constraints, route.Handler); 

  foreach (var route in context.Routes.OrderByDescending(x => x.Priority))
    RouteTable.Routes.Add(route.Name, route); 

  return config;
} 

private static IEnumerable<Assembly> GetReferencedAssemblies()
{
  var assemblies = BuildManager.GetReferencedAssemblies();
  foreach (Assembly assembly in assemblies)
    yield return assembly;
}
这样一来就大功告成,使用时只需要在Global.asax.cs文件中修改原注册入口为

public class MvcApplication : System.Web.HttpApplication
{
  protected void Application_Start()
  {
    WebApiConfig.Register(GlobalConfiguration.Configuration);
    FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
    RouteConfig.RegisterRoutes(RouteTable.Routes); 

    Configuration.Instance()
      .RegisterComponents()
      .RegisterRoutePriority(); //注册自定义路由
  }
}
在每个项目中使用只需要要继承自定义路由注册接口IRouteRegister,例如:

public class Registration : IRouteRegister
{
  public void Register(RegistrationContext context)
  {
    //注册后端管理登录路由
    context.MapRoute(
     name: "Admin_Login",
     url: "Admin/login",
     defaults: new { area = "Admin", controller = "Account", action = "Login", id = UrlParameter.Optional },
     namespaces: new string[] { "Wenku.Admin.Controllers" },
     priority: 11
   ); 

    //注册后端管理页面默认路由
    context.MapRoute(
      name: "Admin_default",
      url: "Admin/{controller}/{action}/{id}",
      defaults: new { area = "Admin", controller = "Home", action = "Index", id = UrlParameter.Optional },
      namespaces: new string[] { "Wenku.Admin.Controllers" },
      priority: 10
    ); 

    //注册手机访问WebApi路由
    context.MapHttpRoute(
      name: "Mobile_Api",
      routeTemplate: "api/mobile/{controller}/{action}/{id}",
      defaults: new
      {
        area = "mobile",
        action = RouteParameter.Optional,
        id = RouteParameter.Optional,
        namespaceName = new string[] { "Wenku.Mobile.Http" }
      },
      constraints: new { action = new StartWithConstraint() },
      priority: 0
    );
  }
}

四、总结

当遇到大项目注册的路由不生效时你应该要想到有可能是因为路由顺序的原因,以上就是本文的全部内容,希望对大家的学习有所启发。

(0)

相关推荐

  • 剖析Asp.Net Web API路由系统---WebHost部署方式

    上一篇我们剖析了Asp.Net路由系统,今天我们再来简单剖析一下Asp.Net Web API以WebHost方式部署时,Asp.Net Web API的路由系统内部是怎样实现的.还是以一个简单实例开头. 创建一个空的WebApi项目,在Global中注册路由信息: public class WebApiApplication : System.Web.HttpApplication { protected void Application_Start() { //注册路由 GlobalConf

  • 详解ASP.NET WEB API 之属性路由

    以下为常规MVC路由 config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional }, ); 如果我们要实现类似以下效果路由的话,使用常规公约路由比较麻烦. order/Miles/三只松鼠干果/2袋 order/2017/1/13 如果使用属性路由的话就比较简单

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

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

  • 如何使用签名保证ASP.NET MVC OR WEBAPI的接口安全

    目录 签名算法 签名的参数 验证签名 ApiController基类 预防Replay Attack 客户端调用 当我们开发一款App的时候,App需要跟后台服务进行通信获取或者提交数据.如果我们没有完善的安全机制则很容易被别用心的人伪造请求而篡改数据. 所以我们需要使用某种安全机制来保证请求的合法.现在最常用的办法是给每个http请求添加一个签名,服务端来验证签名的合法性,如果签名合法则执行响应的操作,如果签名非法则直接拒绝请求. 签名算法 签名算法一般都使用Hash散列算法,常用的有MD5,

  • ASP.NET MVC前台动态添加文本框并在后台使用FormCollection接收值

    在"MVC批量添加,增加一条记录的同时添加N条集合属性所对应的个体"中,对于前台传来的多个TextBox值,在控制器方法中通过强类型来接收.使用FormCollection也可以接收来自前台的多个TextBox值.实现效果如下: 动态添加TextBox: 后台使用FormCollection接收来自前台的TextBox值,再以TempData把接收到的值返回: 当页面没有TextBox,点击"移除",提示"没有文本框可被移除": 在HomeCon

  • ASP.NET MVC中的路由原理与用法

    目录 一.概述 二.路由原理 1.注册路由 2.路由匹配 2.1.匹配方式一 2.2.匹配方式二 2.3.匹配方式三 3.URL参数默认值 3.1.参数默认值一 3.2.参数默认值二 3.4.参数默认值三 3.4.参数默认值四 4.参数值约束 1.使用正则表达式 2.使用约束类 5.路由匹配顺序 6.排除路由 7.由URL到控制器 8.从控制器中获取URL值的方式 8.1.Request.QueryString 8.2.RouteData.Values 8.3.action参数 9.由路由到UR

  • ASP.NET MVC 使用Bootstrap的方法

    作为一名Web开发者而言,如果不借助任何前端框架,从零开始使用HTML和CSS来构建友好的页面是非常困难的.特别是对于Windows Form的开发者而言,更是难上加难. 正是由于这样的原因,Bootstrap诞生了.Twitter Bootstrap为开发者提供了丰富的CSS样式.组件.插件.响应式布局等.同时微软已经完全集成在ASP.NET MVC 模板中. Bootstrap结构介绍 你可以通过http://getbootstrap.com.来下载最新版本的Bootstrap. 解压文件夹

  • asp.net mvc webapi 实用的接口加密方法示例

    在很多项目中,因为webapi是对外开放的,这个时候,我们就要得考虑接口交换数据的安全性. 安全机制也比较多,如andriod与webapi 交换数据的时候,可以走双向证书方法,但是开发成本比较大, 今天我们不打算介绍这方面的知识,我们说说一个较简单也较常见的安全交换机制 在这里要提醒读者,目前所有的加密机制都不是绝对的安全! 我们的目标是,任何用户或者软件获取到我们的webapi接口url后用来再次访问该地址都是无效的! 达到这种目标的话,我们必须要在url中增加一个时间戳,但是仅仅如此还是不

  • ASP.NET Core MVC学习教程之路由(Routing)

    前言 ASP.NET Core MVC 路由是建立在ASP.NET Core 路由的,一项强大的URL映射组件,它可以构建具有理解和搜索网址的应用程序.这使得我们可以自定义应用程序的URL命名形式,使得它在搜索引擎优化(SEO)和链接生成中运行良好,而不用关心Web服务器上的文件是怎么组织的.我们可以方便的使用路由模板语法定义路由,路由模板语法支持路由值约束,默认值和可选值. 基于约束的路由允许全局定义应用支持的URL格式,以及这些格式是怎样各自在给定的控制器中映射到指定的操作方法(Action

  • ASP.NET MVC实现区域路由

    目录 一.区域路由 二.示例程序 1.新建区域路由 2.注册区域路由 2.1.区域路由文件 2.2.全局注册区域路由 一.区域路由 为了管理网站中大量的文件,在ASP.NET MVC 2.0版本中引入了一个新概念:区域(Area). 有了区域以后,可以让我们的项目不至于太复杂而导致管理混乱.每个模块的页面都放入相应的区域内进行管理很方便.看下面的截图: 上图中有两个模块:一个是User模块,另一个是Product模块,所有关于这两个模块的Controller.Model.View都放入各自的模块

  • ASP.NET MVC用存储过程批量添加修改数据操作

    用Entity Framework 进行数据库交互,在代码里直接用lamda表达式和linq对数据库操作,中间为程序员省去了数据库访问的代码时间,程序员直接可以专注业务逻辑层的编写.但是对于比较复杂的表关系关联查询或者修改就比较费劲了.通常可以采用的方式是用EF执行SQL语句或者"存储过程",特别是执行复杂批量任务,当然也可以在MVC底层用ADO.NET,这里就不多说了.怎么做批量呢?这里讲讲在EF下用存储过程批量添加修改数据. 需求是这样的:需要批量添加修改产品类别的投放任务数额,每

  • 基于ASP.NET MVC的ABP框架入门学习教程

    为什么使用ABP 我们近几年陆续开发了一些Web应用和桌面应用,需求或简单或复杂,实现或优雅或丑陋.一个基本的事实是:我们只是积累了一些经验或提高了对,NET的熟悉程度. 随着软件开发经验的不断增加,我们发现其实很多工作都是重复机械的,而且随着软件复杂度的不断提升,以往依靠经验来完成一些简单的增删改查的做法已经行不通了.特别是用户的要求越来越高,希望添加的功能越来多,目前这种开发模式,已经捉襟见肘.我很难想象如何在现有的模式下进行多系统的持续集成并添加一些新的特性. 开发一个系统时,我们不可避免

随机推荐