locked
Multiple domains in single asp.net mvc application RRS feed

  • Question

  • User152872926 posted

    Hi,

     

    I want to use my asp.net mvc application with multiple domains (maybe of hundreds),

    so I have created custom Route class that matches requests including domain.

    Each domain could has different routes, so the number of routes in RouteTable can be huge

    (eg 500 domains, each 20 routes = 10000 routes in RouteTable).

    I am considering how it will be efficient?

     

    I have an idea to replace RouteTable.Routes (of type RouteCollection)

    with custom one (of type Dictionary<string, RouteCollection>).

    But how I can override default route matching mechanism with my custom mechanism which will be two step:

    1) retrieve RouteCollection from dictionary based on domain name

    2) retrieve specyfic Route object (standard route matching mechanism)

    How can I do this?

     

    Maybe someone has better idea for solving this problem?

     

    Thanks in advance

    Janusz

    Wednesday, August 25, 2010 7:11 AM

Answers

  • User-1267218547 posted

    Hi johnnyno ,

    About multiple domains with ASP.NET MVC application. 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 

    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}...."
    VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values) method is not implemented, resulting in “strange” urls when using HtmlHelper ActionLinkhelpers. Think of http://live.localhost/Home/Index/?liveMode=false where you would have just wanted http://develop.localhost/Home/Index. 

    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 ActionLinkhelpers. 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}...."

    By the way, This should be possible with IIS using the host headers field in the bindings settings. You can add as many different host headers as you want for your application. And as long as that domain somehow goes to that host on that particular port (http or https or custom), then that application will be used.

    For more detail, please double check the following link.

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

    I hope it is helpful to you.

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Thursday, August 26, 2010 11:20 PM

All replies

  • User-1267218547 posted

    Hi johnnyno ,

    About multiple domains with ASP.NET MVC application. 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 

    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}...."
    VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values) method is not implemented, resulting in “strange” urls when using HtmlHelper ActionLinkhelpers. Think of http://live.localhost/Home/Index/?liveMode=false where you would have just wanted http://develop.localhost/Home/Index. 

    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 ActionLinkhelpers. 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}...."

    By the way, This should be possible with IIS using the host headers field in the bindings settings. You can add as many different host headers as you want for your application. And as long as that domain somehow goes to that host on that particular port (http or https or custom), then that application will be used.

    For more detail, please double check the following link.

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

    I hope it is helpful to you.

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Thursday, August 26, 2010 11:20 PM
  • User152872926 posted

    Hi,

     

    I have created custom HttpModule that replaces default MvcHttpHandler

    and custom HttpModule that replaces default UrlRoutingModule.

     

    Matching route now is two step:

    1. Matching appropriate RouteCollection from dictionary (where domain name is key and RouteCollection is value)
    2. Default asp.net mvc route matching from routes collection: RouteCollection.GetRouteData(context)

     

    Everything works fine. I can register different routes for each domain

    and also routes could be loaded dynamically.

     

    But, many thanks for your explanation. 

     

    Best regards

    Janusz

    Wednesday, September 1, 2010 3:48 AM
  • User624521857 posted

    Any possibility that you can share the code for this?  I am about to go down this same route for an existing project.  Basically, need to extract the domain name into an account and an application.  Having all the routing handled via the database would be nice as well.  If no code, any links that would be helpful?


    Thanks,

    Alex

    Tuesday, December 21, 2010 11:14 PM
  • User-1267218547 posted

    Hi hostdude99 ,

    You can try to create  DomainRoute which inherits from the Route for handling the domain requesting.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.Routing;
    using System.Web.Mvc;
    using System.Text.RegularExpressions;
    
    namespace CiCeng.MVC.DomainRouting
    {
        public class DomainRoute : Route
        {
            private Regex domainRegex;
            private Regex pathRegex;
    
            public string Domain { get; set; }
    
            public DomainRoute(string domain, string url, RouteValueDictionary defaults)
                : base(url, defaults, new MvcRouteHandler())
            {
                Domain = domain;
            }
    
            public DomainRoute(string domain, string url, RouteValueDictionary defaults, IRouteHandler routeHandler)
                : base(url, defaults, routeHandler)
            {
                Domain = domain;
            }
    
            public DomainRoute(string domain, string url, object defaults)
                : base(url, new RouteValueDictionary(defaults), new MvcRouteHandler())
            {
                Domain = domain;
            }
    
            public DomainRoute(string domain, string url, object defaults, IRouteHandler routeHandler)
                : base(url, new RouteValueDictionary(defaults), routeHandler)
            {
                Domain = domain;
            }
    
            public override RouteData GetRouteData(HttpContextBase httpContext)
            {
                domainRegex = CreateRegex(Domain);
                pathRegex = CreateRegex(Url);
    
                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 domainMatch = domainRegex.Match(requestDomain);
                Match pathMatch = pathRegex.Match(requestPath);
    
                RouteData data = null;
                if (domainMatch.Success && pathMatch.Success)
                {
                    data = new RouteData(this, RouteHandler);
    
                    if (Defaults != null)
                    {
                        foreach (KeyValuePair<string, object> item in Defaults)
                        {
                            data.Values[item.Key] = item.Value;
                        }
                    }
    
                    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;
                                }
                            }
                        }
                    }
    
                    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)
            {
                string hostname = Domain;
                foreach (KeyValuePair<string, object> pair in values)
                {
                    hostname = hostname.Replace("{" + pair.Key + "}", pair.Value.ToString());
                }
    
                return new DomainData
                {
                    Protocol = "http",
                    HostName = hostname,
                    Fragment = ""
                };
            }
    
            private Regex CreateRegex(string source)
            {
                source = source.Replace("/", @"\/?");
                source = source.Replace(".", @"\.?");
                source = source.Replace("-", @"\-?");
                source = source.Replace("{", @"(?<");
                source = source.Replace("}", @">([a-zA-Z0-9_]*))");
    
                return new Regex("^" + source + "$");
            }
    
            private RouteValueDictionary RemoveDomainTokens(RouteValueDictionary values)
            {
                Regex tokenRegex = new Regex(@"({[a-zA-Z0-9_]*})*-?\.?\/?({[a-zA-Z0-9_]*})*-?\.?\/?({[a-zA-Z0-9_]*})*-?\.?\/?({[a-zA-Z0-9_]*})*-?\.?\/?({[a-zA-Z0-9_]*})*-?\.?\/?({[a-zA-Z0-9_]*})*-?\.?\/?({[a-zA-Z0-9_]*})*-?\.?\/?({[a-zA-Z0-9_]*})*-?\.?\/?({[a-zA-Z0-9_]*})*-?\.?\/?({[a-zA-Z0-9_]*})*-?\.?\/?({[a-zA-Z0-9_]*})*-?\.?\/?({[a-zA-Z0-9_]*})*-?\.?\/?");
                Match tokenMatch = tokenRegex.Match(Domain);
                for (int i = 0; i < tokenMatch.Groups.Count; i++)
                {
                    Group group = tokenMatch.Groups[i];
                    if (group.Success)
                    {
                        string key = group.Value.Replace("{", "").Replace("}", "");
                        if (values.ContainsKey(key))
                            values.Remove(key);
                    }
                }
    
                return values;
            }
        }
    }


    http://blog.maartenballiauw.be/post/2009/05/20/ASPNET-MVC-Domain-Routing.aspx
    http://admsteck.blogspot.com/2010/04/aspnet-mvc-domain-routing-and-areas.html

    I hope it is helpful to you.

    Wednesday, December 22, 2010 12:41 AM