[转]ASP.NET MVC Domain Routing

本文转自:http://blog.maartenballiauw.be/post/2009/05/20/ASPNET-MVC-Domain-Routing.aspx

ASP.NET MVC Domain Routing 

20. May 2009 08:23  /  maartenba  /  ASP.NET . C# . General . MVC . Projects  /  Comments (33) 

Routing Ever since the release of ASP.NET MVC and its routing engine (System.Web.Routing), Microsoft has been trying to convince us that you have full control over your URL and routing. This is true to a certain extent: as long as it’s related to your application path, everything works out nicely. If you need to take care of data tokens in your (sub)domain, you’re screwed by default.

Earlier this week, Juliën Hanssens did a blog post on his approach to subdomain routing. While this is a good a approach, it has some drawbacks:
•All routing logic is hard-coded: if you want to add a new possible route, you’ll have to code for it.
•The VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values) method is not implemented, resulting in “strange” urls when using HtmlHelper ActionLink helpers. Think of http://live.localhost/Home/Index/?liveMode=false where you would have just wanted http://develop.localhost/Home/Index. 

Unfortunately, the ASP.NET MVC infrastructure is based around this VirtualPathData class. That’s right: only tokens in the URL’s path are used for routing… Check my entry on the ASP.NET MVC forums on that one.

Now for a solution… Here are some scenarios we would like to support:
•Scenario 1: Application is multilingual, where www.nl-be.example.com should map to a route like “www.{language}-{culture}.example.com”.
•Scenario 2: Application is multi-tenant, where www.acmecompany.example.com should map to a route like “www.{clientname}.example.com”.
•Scenario 3: Application is using subdomains for controller mapping: www.store.example.com maps to "www.{controller}.example.com/{action}...." 

Sit back, have a deep breath and prepare for some serious ASP.NET MVC plumbing…

kick it on DotNetKicks.com 

Defining routes

Here are some sample route definitions we want to support. An example where we do not want to specify the controller anywhere, as long as we are on home.example.com:

[code:c#]

routes.Add("DomainRoute", new DomainRoute(
    "home.example.com", // Domain with parameters
    "{action}/{id}",    // URL with parameters
    new { controller = "Home", action = "Index", id = "" }  // Parameter defaults
));

[/code]

Another example where we have our controller in the domain name:

[code:c#]

routes.Add("DomainRoute", new DomainRoute(
    "{controller}.example.com",     // Domain with parameters< br />    "{action}/{id}",    // URL with parameters
    new { controller = "Home", action = "Index", id = "" }  // Parameter defaults
));

[/code]

Want the full controller and action in the domain?

[code:c#]

routes.Add("DomainRoute", new DomainRoute(
    "{controller}-{action}.example.com",     // Domain with parameters
    "{id}",    // URL with parameters
    new { controller = "Home", action = "Index", id = "" }  // Parameter defaults
));

[/code]

Here’s the multicultural route:

[code:c#]

routes.Add("DomainRoute", new DomainRoute(
    "{language}.example.com",     // Domain with parameters
    "{controller}/{action}/{id}",    // URL with parameters
    new { language = "en", controller = "Home", action = "Index", id = "" }  // Parameter defaults
));

[/code]

HtmlHelper extension methods

Since we do not want all URLs generated by HtmlHelper ActionLink to be using full URLs, the first thing we’ll add is some new ActionLink helpers, containing a boolean flag whether you want full URLs or not. Using these, you can now add a link to an action as follows:

[code:c#]

<%= Html.ActionLink("About", "About", "Home", true)%>

[/code]

Not too different from what you are used to, no?

Here’s a snippet of code that powers the above line of code:

[code:c#]

public static class LinkExtensions
{
    public static string ActionLink(this HtmlHelper htmlHelper, string linkText, string actionName, string controllerName, bool requireAbsoluteUrl)
    {
        return htmlHelper.ActionLink(linkText, actionName, controllerName, new RouteValueDictionary(), new RouteValueDictionary(), requireAbsoluteUrl);
    }

    // more of these...

    public static string ActionLink(this HtmlHelper htmlHelper, string linkText, string actionName, string controllerName, RouteValueDictionary routeValues, IDictionary<string, object> htmlAttributes, bool requireAbsoluteUrl)
    {
        if (requireAbsoluteUrl)
        {
            HttpContextBase currentContext = new HttpContextWrapper(HttpContext.Current);
            RouteData routeData = RouteTable.Routes.GetRouteData(currentContext);

            routeData.Values["controller"] = controllerName;
            routeData.Values["action"] = actionName;

            DomainRoute domainRoute = routeData.Route as DomainRoute;
            if (domainRoute != null)
            {
                DomainData domainData = domainRoute.GetDomainData(new RequestContext(currentContext, routeData), routeData.Values);
                return htmlHelper.ActionLink(linkText, actionName, controllerName, domainData.Protocol, domainData.HostName, domainData.Fragment, routeData.Values, null);
            }
        }
        return htmlHelper.ActionLink(linkText, actionName, controllerName, routeValues, htmlAttributes);
    }
}

[/code]

Nothing special in here: a lot of extension methods, and some logic to add the domain name into the generated URL. Yes, this is one of the default ActionLink helpers I’m abusing here, getting some food from my DomainRoute class (see: Dark Magic).

Dark magic

You may have seen the DomainRoute class in my code snippets from time to time. This class is actually what drives the extraction of (sub)domain and adds token support to the domain portion of your incoming URLs.

We will be extending the Route base class, which already gives us some properties and methods we don’t want to implement ourselves. Though there are some we will define ourselves:

[code:c#]

public class DomainRoute : Route
{
    // ...

    public string Domain { get; set; }

    // ...

    public override RouteData GetRouteData(HttpContextBase httpContext)
    {
        // Build regex
        domainRegex = CreateRegex(Domain);
        pathRegex = CreateRegex(Url);

        // Request information
        string requestDomain = httpContext.Request.Headers["host"];
        if (!string.IsNullOrEmpty(requestDomain))
        {
            if (requestDomain.IndexOf(":") > 0)
            {
                requestDomain = requestDomain.Substring(0, requestDomain.IndexOf(":"));
            }
        }
        else
        {
            requestDomain = httpContext.Request.Url.Host;
        }
        string requestPath = httpContext.Request.AppRelativeCurrentExecutionFilePath.Substring(2) + httpContext.Request.PathInfo;

        // Match domain and route
        Match domainMatch = domainRegex.Match(requestDomain);
        Match pathMatch = pathRegex.Match(requestPath);

        // Route data
        RouteData data = null;
        if (domainMatch.Success && pathMatch.Success)
        {
            data = new RouteData(this, RouteHandler);

            // Add defaults first
            if (Defaults != null)
            {
                foreach (KeyValuePair<string, object> item in Defaults)
                {
                    data.Values[item.Key] = item.Value;
                }
            }

            // Iterate matching domain groups
            for (int i = 1; i < domainMatch.Groups.Count; i++)
            {
                Group group = domainMatch.Groups[i];
                if (group.Success)
                {
                    string key = domainRegex.GroupNameFromNumber(i);
                    if (!string.IsNullOrEmpty(key) && !char.IsNumber(key, 0))
                    {
                        if (!string.IsNullOrEmpty(group.Value))
                        {
                            data.Values[key] = group.Value;
                        }
                    }
                }
            }

            // Iterate matching path groups
            for (int i = 1; i < pathMatch.Groups.Count; i++)
            {
                Group group = pathMatch.Groups[i];
                if (group.Success)
                {
                    string key = pathRegex.GroupNameFromNumber(i);
                    if (!string.IsNullOrEmpty(key) && !char.IsNumber(key, 0))
                    {
                        if (!string.IsNullOrEmpty(group.Value))
                        {
                            data.Values[key] = group.Value;
                        }
                    }
                }
            }
        }

        return data;
    }

    public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
    {
        return base.GetVirtualPath(requestContext, RemoveDomainTokens(values));
    }

    public DomainData GetDomainData(RequestContext requestContext, RouteValueDictionary values)
    {
        // Build hostname
        string hostname = Domain;
        foreach (KeyValuePair<string, object> pair in values)
        {
            hostname = hostname.Replace("{" + pair.Key + "}", pair.Value.ToString());
        }

        // Return domain data
        return new DomainData
        {
            Protocol = "http",
            HostName = hostname,
            Fragment = ""
        };
    }

    // ...
}

[/code]

Wow! That’s a bunch of code! What we are doing here is converting the incoming request URL into tokens we defined in our route, on the domain level and path level. We do this by converting {controller} and things like that into a regex which we then try to match into the route values dictionary. There are some other helper methods in our DomainRoute class, but these are the most important.

Download the full code here: MvcDomainRouting.zip (250.72 kb)

[转]ASP.NET MVC Domain Routing

时间: 2024-10-15 08:56:33

[转]ASP.NET MVC Domain Routing的相关文章

ASP.NET MVC 入门3、Routing

原帖地址:http://www.cnblogs.com/QLeelulu/archive/2008/10/03/1303612.html 在一个route中,通过在大括号中放一个占位符来定义( { and } ).当解析URL的时候,符号"/"和"."被作为一个定义符来解析,而定义符之间的值则匹配到占位符中.route定义中不在大括号中的信息则作为常量值. 下面是一些示例URL: Valid route definitions Examples of matchin

[转]ASP.NET MVC 入门3、Routing

在一个route中,通过在大括号中放一个占位符来定义( { and } ).当解析URL的时候,符号"/"和"."被作为一个定义符来解析,而定义符之间的值则匹配到占位符中.route定义中不在大括号中的信息则作为常量值.下面是一些示例URL: Valid route definitions Examples of matching URL {controller}/{action}/{id} /Products/show/beverages {table}/Deta

Professional C# 6 and .NET Core 1.0 - Chapter 41 ASP.NET MVC

What's In This Chapter? Features of ASP.NET MVC 6 Routing Creating Controllers Creating Views Validating User Inputs Using Filters Working with HTML and Tag Helpers Creating Data-Driven Web Applications Implementing Authentication and Authorization W

ASP.NET MVC : Action过滤器(Filtering)

http://www.cnblogs.com/QLeelulu/archive/2008/03/21/1117092.html ASP.NET MVC : Action过滤器(Filtering) 相关文章: ASP.NET MVC URL Routing 学习 AP.NET MVC : 控制器 和 控制器Actions ASP.NET MVC 学习: 视图 有时候你想在调用action方法之前或者action方法之后处理一些逻辑,为了支持这个,ASP.NET MVC允许你创建action过滤器

Asp.net MVC Request Life Cycle

Asp.net MVC Request Life Cycle While programming with Asp.net MVC, you should be aware of the life of an Asp.net MVC request from birth to death. In this article, I am going to expose the Asp.net MVC Request Life cycle. There are seven main steps tha

Routing与ASP.NET MVC生命周期

一.Routing ASP.NET MVC的“网址路径”和“文档路径”的对应关系是通过“网址路由(Routing)”来定义的,我们可以从项目内的App_Start\RouteConfig.cs文档可以看到一个RegisterRoutes方法: public static void RegisterRoutes(RouteCollection routes) { route.IgnoreRoute("{resource}.axd/{*pathInfo}"); route.MapRoute

asp.net MVC 5 路由 Routing

ASP.NET MVC ,一个适用于WEB应用程序的经典模型 model-view-controller 模式.相对于web forms一个单一的整块,asp.net mvc是由连接在一起的各种代码层所组成. 最近又接触了关于asp.net mvc的项目,又重拾以前的记忆,感觉忘了好多,特此记录. 首先,来说说路由Routing. ASP.NET MVC 不再是要依赖于物理页面了,你可以使用自己的语法自定义URL,通过这些语法来指定资源和操作.语法通过URL模式集合表达,也称为路由. 路由是代表

ASP.NET MVC 小牛之旅3:Routing——网址路由

网址路由(Routing)在ASP.NET MVC中有两个主要用途,一个用途是匹配通过浏览器传来的HTTP请求,另一个用途则是响应适当的网址给浏览器. 3.1匹配通过浏览器传来的HTTP请求 首先我们来看下第一个用途,也就是匹配通过浏览器传来的HTTP请求. 客户端对ASP.NET网站发出请求时,能够通过Routing找到合适的HttpHandler来处理网页,大致的处理流程为: 如果HttpHandler是由MvcHandler来处理的,那么,此时就会进入ASP.NET MVC的执行生命周期,

ASP.NET MVC 開發心得分享 (21):Routing 觀念與技巧

ASP.NET MVC 預設在 Global.asax 所定義的 RegisterRoutes 方法中可以輕易的定義你希望擁有的網址格式,嚴格上來講這並非 ASP.NET MVC 的專利,而是從 ASP.NET 3.5 SP1 就加入的新特性,所以就算是傳統的 ASP.NET Web Form 一樣可以利用 Routing 所帶來的好處,今天我就來講一些 Routing 的觀念與技巧. 快速上手 我先解釋在 ASP.NET MVC 專案中 Global.asax 所定義的 Routing 程式碼