[Turn] Asp.Net MVC4 Series--Advanced Routing (1)

Reprinted from: http://blog.csdn.net/lan_liang/article/details/22993839?utm_source=tuicool

 

create a route  

Open RouteConfig.cs and find that a default route has been created:

 

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

 In order to illustrate the url matching process of the route, temporarily comment out the default parameter.

 

 

Open Global.cs and you can see that the routing configuration file has been registered:

 

protected void Application_Start()  
{  
           AreaRegistration.RegisterAllAreas();  
   
           WebApiConfig.Register(GlobalConfiguration.Configuration);  
           FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);  
           RouteConfig.RegisterRoutes(RouteTable.Routes);  
           BundleConfig.RegisterBundles(BundleTable.Bundles);  
}

 

 

About how routing works

The routing part of Asp.net MVC Framework is inserted in the http pipeline. When receiving an http request, it will look for the registered routing table (registered at ApplicationStart, that is, when the application starts), find the routing rules, and get each routing rule The pattern tries to match the appropriate route for the current request. If the match is successful, the controller and action will be parsed, the corresponding controller will be found from the controllerfactory, and the request will be passed to the action. If parameters are passed in the request, the route will also parse the parameters and give them to the action. .

Here are some examples of url matching:

http://mysite/Admin/Index

Controller =Admin,Action=Index

http://mysite/Index/Admin

Controller=Index,Action=Admin

http://mysite/Apples/Oranges

Controller=Apples,Action=Oranges

http://mysite/Admin

Match failed, too few segments

http://mysite/Admin/Index/Soccer

Match failed, too many segments

 

The route will call the route handler to complete the routing process. By default, the mvc application will use the MVCRouteHandler. Manually add a Route, which can be reflected:

 

routes.Add("MyRoute",newRoute("{controller}/{action}", new MvcRouteHandler()));

 

 

Specify the default (default)

It was just explained that when the url matches, the default parameter was removed. At this time, let's take a look at the function of the default parameter.

 

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

 You can see the last parameter, specifying a default controller and action.

 

 

Mydomain.com

Controller = Home ,action=Index

Mydomain.com/Customer

Controller=Customer ,action=Index

Mydomain.com/Customer/List

Controller=Customer, action=List

Mydomain.com/Customer/List/All

Match failed, too many segments

 

Fixed value Segment

Scenario 1, the first segment in all requests is "public" and needs to be processed uniformly, so define a route:

 

routes.MapRoute(name: "PublicReqRoute", url:"Public/{controller}/{action}",  
                      defaults: new {controller = "PublicHome", action ="Index"});

 Example url: http://mysite/Public

Matching result: controller=PublicHome,action=Index

 

Scenario 2, requests that start with public need to be processed uniformly and define routes:

Example url: Http:/mysite/PublicX/

Matching result: controller=X, action=Index

routes.MapRoute(name: "PublicReqRoute", url:"Public{controller}/{action}",  
                      defaults: new {controller = "PublicHome", action ="Index"});  

 

 

场景3:有个controller或action不工作,需要改个bug,把所有原来指向这个controller的请求暂时路由到首页:

routes.MapRoute("myshop","Shop/OldAction",  
new { controller = "Home", action ="Index" });  

 注意:路由是按着添加顺序依次解析的,因此把最特殊的那个路由放在前面,避免先fall 到相对generall的那个。

 

获取参数

对于Route:

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

 请求:http://mysite/Home/Index/15

 

Action 中使用RouteData.Values获取传入的id:

public ActionResult CustomVariable() {  
    ViewBag.Controller = "Home";  
    ViewBag.Action = "CustomVariable";  
    ViewBag.CustomVariable = RouteData.Values["id"];  
    return View();  
}  

 

使用mvcframework Route System自动传参机制

 

除了使用RouteData.Values取出传入的参数,可以更简单的定义个参数在action,但是参数名要和route定义的相同(id)

Public  ActionResult  CustomVariable(string id) {  
    ViewBag.Controller = "Home";  
    ViewBag.Action = "CustomVariable";  
    ViewBag.CustomVariable = id;  
    return View();  
}  

 对于url:http://mysite/Home/Index/15,id就会自动被赋值为15传入action

定义可选参数

依然对于url:

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

 Id=UrlParameter.Optional,此时id就是可选参数

Mydomain.com

Controller=home ,action=Index

Mydomain.com/customer

Controller=customer, action=Index

Mydomain.com/customer/List

Controller=customer, action=List

Mydomain.com/customer/List/All

Controller=customer , action=List, Id=All

Mydomain.com/customer/List/All/Delete

url 匹配失败

 

如果没有传参,action提供了id参数,那么id此时就为null;

public  ActionResultCustomVariable(string id) {  
    ViewBag.Controller = "Home";  
    ViewBag.Action = "CustomVariable";  
    ViewBag.CustomVariable = id == null ? "<novalue>" : id;  
    return View();  
}  

 作为另一个选择,可以指定一个默认参数,如果没url没传值,默认参数值就会被使用。

Public  ActionResultCustomVariable(string id = "DefaultId") {  
    ViewBag.Controller = "Home";  
    ViewBag.Action = "CustomVariable";  
    ViewBag.CustomVariable = id;  
    return View();  
}  

 

使用{*catchall}捕捉超出数量的segment

例如,对于这条route:

routes.MapRoute("MyRoute","{controller}/{action} /{*catchall}",  
new { controller = "Home", action ="Index",  
id = UrlParameter.Optional });  

 由于使用了{*catchall},对于url:

http://mysite/Home/Index/All/More/More/More

此时,controller=Home,Action=Index, catchall=”All/More/More/More”

这样,就把解析剩下segment的工作交给了自己处理

 

解决在不同namespace的同名controller

例如现在有两个controller,在不同的命名空间:

namespace UrlsAndRoutes.Controllers {  
    public class HomeController : Controller {  
        public ActionResult Index() {  
            ViewBag.Controller = "Additional Controllers - Home";  
            ViewBag.Action = "Index";  
            return View("ActionName");  
        }  
    }  
} 

 和

namespace UrlsAndRoutes.AdditionalControllers {  
    public  classHomeController : Controller {  
        public  ActionResultIndex() {  
            ViewBag.Controller = "Additional Controllers -Home";  
            ViewBag.Action = "Index";  
            return View("ActionName");  
        }  
    }  
}  

 对于这种情况,可能希望路由先在一个指定的命名空间里找,找到了返回:

routes.MapRoute("MyRoute","{controller}/{action}/{id}/{*catchall}",  
new { controller = "Home", action ="Index",  
id = UrlParameter.Optional  
},  
new[] { "URLsAndRoutes.AdditionalControllers" }); 

 关键在于最后的那个命名空间参数,mvc framework会优先找 “URLsAndRoutes.AdditionalControllers”里面的controller,如果没找到,会搜索其余的命名空间。

注意,这个new []{}数字里的参数是平行的,也就是说,如果mvc framework在这些命名空间里找到多个同名controller,不会找到第一个就返回,依然会抛出异常,例如:

new[] { "URLsAndRoutes.AdditionalControllers","UrlsAndRoutes.Controllers"});

 对于url:

http://mysite/home/Index/15

mvc framework会在指定的命名空间数组里找,由于找到了多个homecontroller,因此抛出异常。

如果希望这样:指定多个命名空间,找到了第一个就返回,怎么做?

可以配置多条route:

routes.MapRoute("AddContollerRoute","Home/{action}/{id}/{*catchall}",  
new { controller = "Home", action ="Index",  
id = UrlParameter.Optional },  
new[] { "URLsAndRoutes.AdditionalControllers" });  
   
routes.MapRoute("MyRoute","{controller}/{action}/{id}/{*catchall}",  
new { controller = "Home", action ="Index",  
id = UrlParameter.Optional },  
new[] { "URLsAndRoutes.Controllers" });  

 mvc framework就会按着添加的顺序依次查找匹配的controller和action,找到了把解析好的参数(如果有)传递给action就返回了。

只允许mvc framework在指定的 namespace里找,如何做?

Route myRoute=routes.MapRoute("AddContollerRoute",  
"Home/{action}/{id}/{*catchall}",  
new { controller = "Home", action ="Index",  
id = UrlParameter.Optional },  
new[] { "URLsAndRoutes.AdditionalControllers" });   
  
myRoute.DataTokens["UseNamespaceFallback"] = false;

 由于把DataTokens[“UseNamespaceFallback”] 设为false,因此mvcframework在指定的命名空间:URLsAndRoutes.AdditionalControllers"里面找完了,就不去其他地方找了。

 

给路由加限制

 

正则限制

可以加正则限制在controller和action:

routes.MapRoute("MyRoute","{controller}/{action}/{id}/{*catchall}",  
new { controller = "Home", action ="Index", id = UrlParameter.Optional },  
new { controller = "^H.*"},  
new[] { "URLsAndRoutes.Controllers"}); 

 由于给controller加上了正则表达式:”^H.*”的限制,因此对于url匹配了urlpattern,解析出controller,如果不是以H开头的,也会被过滤掉。

类似的,也可以使用给action加正则限制:

new { controller = "^H.*", action ="^Index$|^About$"}  

 这样,就限制了action必须是Index和About。

 

使用Http Method限制

可以限制请求是Get ,Post亦或是Put ,Delete等类型的。

new { controller = "^H.*", action ="Index|About",  
httpMethod = new HttpMethodConstraint("GET") } ,  

 这样,就限制了请求必须是Get方式的。

 

注意,给route加httpmethod限制,和给controller还有action加httpmethod区别在于,route在httppipeline很早的位置就被处理了,而到controller和action的时候,已经是httppipeline很晚的时候了,controller和action已经被解析了,参数也已经被解析了(如果有)。

 

自定义限制

Mvc 提供了IRouteConstranit 接口,可以自己定义限制,通过实现这个接口:

public class UserAgentConstraint : IRouteConstraint {  
    private string requiredUserAgent;  
    public UserAgentConstraint(string  agentParam) {  
        requiredUserAgent = agentParam;  
    }  
    Public  boolMatch(HttpContextBase  httpContext, Routeroute, string  parameterName,  
RouteValueDictionary  values,RouteDirection routeDirection) {  
        return httpContext.Request.UserAgent != null &&  httpContext.Request.UserAgent.Contains(requiredUserAgent);  
    }  
}  

 以上代码,实现了一个限制,必须Request对象中的代理对象不为空,也就是必须使用代理才能访问,并且代理名称包含指定的名称。

使用自定义限制:

routes.MapRoute("ChromeRoute","{*catchall}",  
new { controller = "Home", action ="Index" },  
new {  
customConstraint = newUserAgentConstraint("Chrome")  
},  
new[] { "UrlsAndRoutes.AdditionalControllers" });

 这样,就实现了,必须使用chrome才能访问的限制。

 

让访问物理文件目录的请求也参与路由

默认情况下,route system会先检查url是否指向了一个物理文件目录,如果是,返回找到这个文件,返回;否则,执行注册到路由表里面的每一条route。也就是说,指向物理文件目录的请求实际在mvc路由机制之前已经被处理掉了,默认没有参与路由。

 

如果要改变这个行为,例如访问

http://mysite/resource/pic/  ,不希望route去找resource/pic物理目录,而希望route找resourcecontroller ,pic action,那么:

在注册路由时,加上

routes.RouteExistingFiles = true;

这样,本地文件的检测 就会在路由机制执行之后了。

在路由前后,httppipeline的示意图

 

 其中,httpmodule 1和 httpmodule2 在pipeline上面routesystem前后的两个module。

加上route.RouteExistingFiles=true

Route system中,就会先执行MVC Route,后执行CheckDisk Files .最后,为物理文件指定一个route:

routes.MapRoute("DiskFile","Content/StaticContent.html",  
new {  
controller = "Resource",  
action = "List",  
}); 

 

By Passing Route System

有些访问资源文件的请求,是需要ignore的,这种需求直接用ignoreRoute就可以了:

 

routes.IgnoreRoute("Content/{filename}.html");  

 这样,对于url:http://mysite/Content.test.html 的请求就被route忽略了,访问页面,就会出现资源无法找到和404错误:

HTTP Error 404.0 - Not Found

The resource you are looking for has beenremoved, had its name changed, or is temporarily unavailable.

 

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=326995571&siteId=291194637