为ASP.NET MVC及WebApi添加路由优先级
一、为什么需要路由优先级
大家都知道我们在Asp.NetMVC项目或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:newstring[]{"Wenku.Admin.Controllers"}
);
假如是先注册上面那个通用的default路由,再注册这个login的路由,那么无论怎么样,都会先匹配第一个满足条件的路由,也就是第两个路由注册是无效的。
造成这个问题的原因就是这两个路由注册的顺序问题,而Asp.NetMVC及WebApi中注册路由都没有优先级这个概念,所以今天我们就是要自己实现这个想法,在注册路由时加入一个优先级的概念。
二、解决思路
1、先分析路由注册的入口,比如我们新建一个mvc4.0的项目
publicclassMvcApplication:System.Web.HttpApplication
{
protectedvoidApplication_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、路由定义
publicclassRoutePriority:Route
{
publicstringName{get;set;}
publicintPriority{get;set;}
publicRoutePriority(stringurl,IRouteHandlerrouteHandler)
:base(url,routeHandler)
{
}
}
publicclassHttpRoutePriority
{
publicstringName{get;set;}
publicintPriority{get;set;}
publicstringRouteTemplate{get;set;}
publicobjectDefaults{get;set;}
publicobjectConstraints{get;set;}
publicHttpMessageHandlerHandler{get;set;}
}
2、定义路由注册的接口
publicinterfaceIRouteRegister
{
voidRegister(RegistrationContextcontext);
}
3、定义路由注册上下文类
publicclassRegistrationContext
{
#regionmvc
publicList<RoutePriority>Routes=newList<RoutePriority>();
publicRoutePriorityMapRoute(stringname,stringurl,intpriority=0)
{
returnMapRoute(name,url,(object)null/*defaults*/,priority);
}
publicRoutePriorityMapRoute(stringname,stringurl,objectdefaults,intpriority=0)
{
returnMapRoute(name,url,defaults,(object)null/*constraints*/,priority);
}
publicRoutePriorityMapRoute(stringname,stringurl,objectdefaults,objectconstraints,intpriority=0)
{
returnMapRoute(name,url,defaults,constraints,null/*namespaces*/,priority);
}
publicRoutePriorityMapRoute(stringname,stringurl,string[]namespaces,intpriority=0)
{
returnMapRoute(name,url,(object)null/*defaults*/,namespaces,priority);
}
publicRoutePriorityMapRoute(stringname,stringurl,objectdefaults,string[]namespaces,intpriority=0)
{
returnMapRoute(name,url,defaults,null/*constraints*/,namespaces,priority);
}
publicRoutePriorityMapRoute(stringname,stringurl,objectdefaults,objectconstraints,string[]namespaces,intpriority=0)
{
varroute=MapPriorityRoute(name,url,defaults,constraints,namespaces,priority);
varareaName=GetAreaName(defaults);
route.DataTokens["area"]=areaName;
//disablingthenamespacelookupfallbackmechanismkeepsthisareasfromaccidentallypickingup
//controllersbelongingtootherareas
booluseNamespaceFallback=(namespaces==null||namespaces.Length==0);
route.DataTokens["UseNamespaceFallback"]=useNamespaceFallback;
returnroute;
}
privatestaticstringGetAreaName(objectdefaults)
{
if(defaults!=null)
{
varproperty=defaults.GetType().GetProperty("area");
if(property!=null)
return(string)property.GetValue(defaults,null);
}
returnnull;
}
privateRoutePriorityMapPriorityRoute(stringname,stringurl,objectdefaults,objectconstraints,string[]namespaces,intpriority)
{
if(url==null)
{
thrownewArgumentNullException("url");
}
varroute=newRoutePriority(url,newMvcRouteHandler())
{
Name=name,
Priority=priority,
Defaults=CreateRouteValueDictionary(defaults),
Constraints=CreateRouteValueDictionary(constraints),
DataTokens=newRouteValueDictionary()
};
if((namespaces!=null)&&(namespaces.Length>0))
{
route.DataTokens["Namespaces"]=namespaces;
}
Routes.Add(route);
returnroute;
}
privatestaticRouteValueDictionaryCreateRouteValueDictionary(objectvalues)
{
vardictionary=valuesasIDictionary<string,object>;
if(dictionary!=null)
{
returnnewRouteValueDictionary(dictionary);
}
returnnewRouteValueDictionary(values);
}
#endregion
#regionhttp
publicList<HttpRoutePriority>HttpRoutes=newList<HttpRoutePriority>();
publicHttpRoutePriorityMapHttpRoute(stringname,stringrouteTemplate,intpriority=0)
{
returnMapHttpRoute(name,routeTemplate,defaults:null,constraints:null,handler:null,priority:priority);
}
publicHttpRoutePriorityMapHttpRoute(stringname,stringrouteTemplate,objectdefaults,intpriority=0)
{
returnMapHttpRoute(name,routeTemplate,defaults,constraints:null,handler:null,priority:priority);
}
publicHttpRoutePriorityMapHttpRoute(stringname,stringrouteTemplate,objectdefaults,objectconstraints,intpriority=0)
{
returnMapHttpRoute(name,routeTemplate,defaults,constraints,handler:null,priority:priority);
}
publicHttpRoutePriorityMapHttpRoute(stringname,stringrouteTemplate,objectdefaults,objectconstraints,HttpMessageHandlerhandler,intpriority=0)
{
varhttpRoute=newHttpRoutePriority();
httpRoute.Name=name;
httpRoute.RouteTemplate=routeTemplate;
httpRoute.Defaults=defaults;
httpRoute.Constraints=constraints;
httpRoute.Handler=handler;
httpRoute.Priority=priority;
HttpRoutes.Add(httpRoute);
returnhttpRoute;
}
#endregion
}
4、把路由注册处理方法添加到Configuration类中
publicstaticConfigurationRegisterRoutePriority(thisConfigurationconfig)
{
vartypesSoFar=newList<Type>();
varassemblies=GetReferencedAssemblies();
foreach(Assemblyassemblyinassemblies)
{
vartypes=assembly.GetTypes().Where(t=>typeof(IRouteRegister).IsAssignableFrom(t)&&!t.IsAbstract&&!t.IsInterface);
typesSoFar.AddRange(types);
}
varcontext=newRegistrationContext();
foreach(vartypeintypesSoFar)
{
varobj=(IRouteRegister)Activator.CreateInstance(type);
obj.Register(context);
}
foreach(varrouteincontext.HttpRoutes.OrderByDescending(x=>x.Priority))
GlobalConfiguration.Configuration.Routes.MapHttpRoute(route.Name,route.RouteTemplate,route.Defaults,route.Constraints,route.Handler);
foreach(varrouteincontext.Routes.OrderByDescending(x=>x.Priority))
RouteTable.Routes.Add(route.Name,route);
returnconfig;
}
privatestaticIEnumerable<Assembly>GetReferencedAssemblies()
{
varassemblies=BuildManager.GetReferencedAssemblies();
foreach(Assemblyassemblyinassemblies)
yieldreturnassembly;
}
这样一来就大功告成,使用时只需要在Global.asax.cs文件中修改原注册入口为
publicclassMvcApplication:System.Web.HttpApplication
{
protectedvoidApplication_Start()
{
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
Configuration.Instance()
.RegisterComponents()
.RegisterRoutePriority();//注册自定义路由
}
}
在每个项目中使用只需要要继承自定义路由注册接口IRouteRegister,例如:
publicclassRegistration:IRouteRegister
{
publicvoidRegister(RegistrationContextcontext)
{
//注册后端管理登录路由
context.MapRoute(
name:"Admin_Login",
url:"Admin/login",
defaults:new{area="Admin",controller="Account",action="Login",id=UrlParameter.Optional},
namespaces:newstring[]{"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:newstring[]{"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=newstring[]{"Wenku.Mobile.Http"}
},
constraints:new{action=newStartWithConstraint()},
priority:0
);
}
}
四、总结
当遇到大项目注册的路由不生效时你应该要想到有可能是因为路由顺序的原因,以上就是本文的全部内容,希望对大家的学习有所启发。