Search code examples
c#asp.net-coreasp.net-core-routing

How to handle routing request "john.myexample.com" in ASP.NET Core


Suppose my application URL is myexample.com, myexample.com has user john he has a public profile link john.myexample.com how to handle such types of request in ASP.NET Core application and map to the UserController which has action Profile takes username as an argument and returns Johns profile.

routes.MapRoute(
                name: "user_profile",
                template: "{controller=User}/{action=Profile}/{username}");

Solution

  • Built-in ASP.NET Core Routing does not provide any template for request subdomain. That's why you need to implement custom router to cover such scenario.

    The implementation will be pretty straightforward. You should parse hostname of incoming request to check whether it's a profile subdomain. If so, you fill controller, action and username in route data.

    Here is a working sample:

    Router implementation:

    public class SubdomainRouter : IRouter
    {
        private readonly Regex hostRegex = new Regex(@"^(.+?)\.(.+)$", RegexOptions.Compiled);
    
        private readonly IRouter defaultRouter;
        private readonly string domain;
        private readonly string controllerName;
        private readonly string actionName;
    
        public SubdomainRouter(IRouter defaultRouter, string domain, string controllerName, string actionName)
        {
            this.defaultRouter = defaultRouter;
            this.domain = domain;
            this.controllerName = controllerName;
            this.actionName = actionName;
        }
    
        public async Task RouteAsync(RouteContext context)
        {
            var request = context.HttpContext.Request;
            var hostname = request.Host.Host;
    
            var match = hostRegex.Match(hostname);
            if (match.Success && String.Equals(match.Groups[2].Value, domain, StringComparison.OrdinalIgnoreCase))
            {
                var routeData = new RouteData();
                routeData.Values["controller"] = controllerName;
                routeData.Values["action"] = actionName;
                routeData.Values["username"] = match.Groups[1].Value;
    
                context.RouteData = routeData;
                await defaultRouter.RouteAsync(context);
            }
        }
    
        public VirtualPathData GetVirtualPath(VirtualPathContext context)
        {
            return defaultRouter.GetVirtualPath(context);
        }
    }
    

    Registration in Startup.Configure():

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        app.UseMvc(routes =>
        {
            routes.Routes.Add(new SubdomainRouter(routes.DefaultHandler, "myexample.com", "User", "Profile"));
    
            routes.MapRoute(
                name: "default",
                template: "{controller=Home}/{action=Index}/{id?}");
        });
    }
    

    Sample Controller:

    public class UserController : Controller
    {
        [HttpGet]
        public IActionResult Profile(string username)
        {
            return Ok();
        }
    }
    

    Sample Project on GitHub

    UPDATE (using a route for localhost in development environment)

    You have two options for using such route for localhost in development environment:

    1. The first one fully emulates subdomain URLs like in production environment. To make it work you should configure your hosts file so that john.myexample.com point to 127.0.0.1 and make your ASP.NET Core Application to accept requests for such host. In this case you don't need any additional adjustments in the routing since the requests will have the same URLs as in production (just the port is added): http://john.myexample.com:12345/.

    2. If you want to keep things simple and use usual development URLs pointing to localhost, you should bypass described SubdomainRouter (because it parses for subdomain which will not work for localhost) and use usual ASP.NET Core routing. Here is adjusted routing configuration for this case:

      app.UseMvc(routes =>
      {
          if (env.IsDevelopment())
          {
              routes.MapRoute(
                  name: "user-profile",
                  template: "profiles/{username}",
                  defaults: new { controller = "User", action = "Profile" });
          }
          else
          {
              routes.Routes.Add(new SubdomainRouter(routes.DefaultHandler, "myexample.com", "User", "Profile"));
          }
      
          routes.MapRoute(
              name: "default",
              template: "{controller=Home}/{action=Index}/{id?}");
      });
      

      Now if you send request to URL like http://localhost:12345/profiles/john, UserController.Profile action will be called with the correct user name.