11

I have hosting and domain like that:

www.EXAMPLE.com

I've created few subdomains like that:

www.PAGE1.EXAMPLE.com
www.PAGE2.EXAMPLE.com
www.PAGE3.EXAMPLE.com
... etc...

All of these subdomains point to one and the same ASP.NET MVC 5 Application.

I want to make system which will load data depending of subdomain. Example:

I have Article object which could be a Auto Review or Game review or Book Review etc...

I would like to www.auto.example.com load data where type of article is Auto, to www.book.example.com I would like to load data with type Book etc.

There will be many types of the pages.

What is best practise to do that?

The top level domain www.example.com should display something else. It would be main page for the others.

tereško
  • 58,060
  • 25
  • 98
  • 150
Ellbar
  • 3,984
  • 6
  • 24
  • 36
  • here is an answer with same question http://stackoverflow.com/a/541495/2543986 – Fereshteh Mirjalili Feb 21 '14 at 08:08
  • Check out what this guy did: http://benjii.me/2015/02/subdomain-routing-in-asp-net-mvc/ – JoshYates1980 Feb 02 '16 at 20:11
  • Possible duplicate of [Is it possible to make an ASP.NET MVC route based on a subdomain?](http://stackoverflow.com/questions/278668/is-it-possible-to-make-an-asp-net-mvc-route-based-on-a-subdomain) – Rob Jan 04 '17 at 22:56

3 Answers3

17

You can do this by writing a custom Route. Here's how (adapted from Is it possible to make an ASP.NET MVC route based on a subdomain?)

public class SubdomainRoute : RouteBase
{

    public override RouteData GetRouteData(HttpContextBase httpContext)
    {
        var host = httpContext.Request.Url.Host;
        var index = host.IndexOf(".");
        string[] segments = httpContext.Request.Url.PathAndQuery.Split('/');

        if (index < 0)
            return null;

        var subdomain = host.Substring(0, index);
        string controller = (segments.Length > 0) ? segments[0] : "Home";
        string action = (segments.Length > 1) ? segments[1] : "Index";

        var routeData = new RouteData(this, new MvcRouteHandler());
        routeData.Values.Add("controller", controller); //Goes to the relevant Controller  class
        routeData.Values.Add("action", action); //Goes to the relevant action method on the specified Controller
        routeData.Values.Add("subdomain", subdomain); //pass subdomain as argument to action method
        return routeData;
    }

    public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
    {
        //Implement your formating Url formating here
        return null;
    }
}

Add to the route table in Global.asax.cs like this:

routes.Add(new SubdomainRoute());

And your controller method:

public ActionResult Index(string subdomain)
{
    //Query your database for the relevant articles based on subdomain
    var viewmodel = MyRepository.GetArticles(subdomain);
    Return View(viewmodel);
}
Community
  • 1
  • 1
Paul Taylor
  • 5,651
  • 5
  • 44
  • 68
  • But with this code I would have to make set of all controllers for every subdomain. When i will have 200 subdomains that means that i have to write 200 controllers and every o them should have action index? There will be the same logic and code in each controller and action, the only difference will be type of loaded data :( – Ellbar Dec 16 '13 at 12:29
  • 1
    No, no need. The hard coded values are just to simplify the illustration. You can easily parse values from the url or look them up from a database or sitemap of some description. – Paul Taylor Dec 16 '13 at 12:33
  • Please, could you make example where when subDomain == user1 then i get all articles where autthor is user1 and when subDomain == user2 then i get all articles with author user2. I dont know how to use in simplest action Index for example. – Ellbar Dec 16 '13 at 12:36
  • Ok, I've reworked the example above to bring it in line with your scenario. – Paul Taylor Dec 16 '13 at 13:05
  • I have used your solution but it doesnt work. In the action I get null parameter. I'm sure i did it well. Waht could be wrong? – Ellbar Dec 16 '13 at 14:35
  • I checked that your code getting action and controller doesnt work always. Sometimes Controller is "" (empty string) and action the same and i get error. Sometimes action name has index 2 in table and sometimes 1. How to make working mechanism o getting action and string :( ? – Ellbar Dec 16 '13 at 15:10
  • If the urls you are passing do not have route paths (eg. sub1.mydomain.com/Controller/Path, you will get null values for controller and path ... you need to add default values for these cases, normal approach with MVC routes. – Paul Taylor Dec 16 '13 at 15:17
  • Thanks :) Could you give me an example how to add default values? :) – Ellbar Dec 16 '13 at 16:21
  • Example code edited to give default values for controller and action route values. – Paul Taylor Dec 16 '13 at 16:42
7

This is something I have wanted to do with ASP.NET MVC for a long time, but... This is not a concern that ASP.NET MVC is responsible for. This is a server concern (IIS). What you need to do is allow for wildcard subdomains on your IIS server and point them to your one application.

Then you can do something like this with the HttpContext:

HttpContext.Current.Request.Url.Host // user1.yourwebsite.com

Then you just need to parse that and push it into your ASP.NET MVC app anyway you see fit:

  • Push it into Session
  • Update the current route data and push a value in
  • Etc....

The choice is really up to you.

Note: The downside here is that this makes local development increasingly difficult, so you might want to mock up a way to fake a subdomain in your application.

Khalid Abuhakmeh
  • 10,709
  • 10
  • 52
  • 75
  • 3
    "What you need to do is allow for wildcard subdomains on your IIS server"... What is best practice for this? You gloss over it as if it's simple but I cant seem to find anyone talking about one good way to do this. – hally9k Aug 27 '15 at 02:38
0

I tried Paul Taylor answer above is pretty good but that didn't work entirely for me. I use this implementation of Route class.

Add your custom domain into C:/Windows/System32/drivers/etc/hosts file

  • 127.0.0.1 subdomain.localhost.com

DomainData.cs

public class DomainData
{
  public string Protocol { get; set; }
  public string HostName { get; set; }
  public string Fragment { get; set; }
}

DomainRoute.cs

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)
{
    // 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 && requestDomain.ToLower() != "tg.local" &&
        requestDomain.ToLower() != "tg.terrasynq.net" && requestDomain.ToLower() != "www.townsgossip.com" &&
        requestDomain.ToLower() != "townsgossip.com")
    {
        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 = ""
    };
}

private Regex CreateRegex(string source)
{
    // Perform replacements
    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)
{
    var 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;
  }
}

Reference: http://www.howtobuildsoftware.com/index.php/how-do/UaR/aspnet-mvc-5-domain-routing-in-mvc5

FAHID
  • 3,245
  • 3
  • 18
  • 15