I'm building an appplication where I want to give a client company a unique url like 'clientcompany.app.com' or 'app.com/clientcompany'.
When a user signs up I want to let them pick their subdomain, and they should be able to invite other users to work under that subdomain as well. The subdomain/route should be the 'parent' that all users are grouped under.
How would I achieve something like this with MVC 4 routing?
This can be achieved by creating a custom domain route:
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 (System.Diagnostics.Debugger.IsAttached == false)
            {
                if (requestDomain.IndexOf(":") > 0)
                {
                    requestDomain = requestDomain.Substring(0, requestDomain.IndexOf(":"));
                }
            }
            // Strip Multiple Subdomains
            if (requestDomain.Split('.').Length > 3)
            {
                string[] split = requestDomain.Split('.');
                requestDomain = String.Join(".", split, split.Length - 3, 3);
                string url = String.Format("{0}://{1}/", httpContext.Request.Url.Scheme, requestDomain);
                if (System.Diagnostics.Debugger.IsAttached == true)
                {
                    httpContext.Response.Cache.SetCacheability(HttpCacheability.NoCache);
                    httpContext.Response.CacheControl = "no-cache";
                }
                httpContext.Response.RedirectPermanent(url, true);
            }
        }
        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 = values.Aggregate(Domain, (current, pair) => current.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)
    {
        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;
    }
}     
public class DomainData
{
    public string Protocol { get; set; }
    public string HostName { get; set; }
    public string Fragment { get; set; }
}
Global.asax:
routes.Add(
                "DomainRoute", new DomainRoute(
                "{subdomain}.yoururl.com",     // Domain with parameters 
                "{controller}/{action}",    // URL with parameters 
                new { controller = "Home", action = "Index", subdomain = UrlParameter.Optional }  // Parameter defaults 
            ));
http://subdomain.app.com will then add the parameter, 'subdomain' to your RouteValueDictionary.
Also, make sure you create a wildcard DNS record.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With