6

I have a MVC Web Application that runs on www.domain.com and I need to configure a different URL binding for another domain www.domain2.com for the same web application.

The new domain www.domain2.com will have to return a specific Controller Action View like /Category/Cars:

routes.MapRoute(
    name: "www.domain2.com",
    url: "www.domain2.com",
    defaults: new { controller = "Category", action = "Cars", id = UrlParameter.Optional }
);

How can I achieve this without changing the URL, so the visitor inserts the url www.domain2.com and receives the view www.domain.com/category/cars but the url remains www.domain2.com?

EDIT:

I have tried this approach but it's not working:

routes.MapRoute(
    "Catchdomain2",
    "{www.domain2.com}",
    new { controller = "Category", action = "Cars" }
);
NightOwl888
  • 55,572
  • 24
  • 139
  • 212
Patrick
  • 2,995
  • 14
  • 64
  • 125
  • 2
    See [MVC Custom Routing Subdomain](https://stackoverflow.com/a/35464970/), which is similar. – NightOwl888 Jan 31 '18 at 13:03
  • Hi! Thanks! It's not really easy to understand. I will have always a single case from www.domain2.com to www.domain.com/Category/Cars. Is there a more simple way to do that? – Patrick Jan 31 '18 at 14:41
  • @NightOwl888 I have tried a different approach based on https://stackoverflow.com/questions/1618896/asp-net-mvc-routing-how-do-i-match-the-whole-url/1618910#1618910 but it's not working :( – Patrick Feb 01 '18 at 18:38

3 Answers3

8

Domains are normally not part of routes, which is why your examples don't work. To make routes that work only on specific domains you have to customize routing.

By default, all of the routes in your route configuration will be available on all domains that can reach the web site.

The simplest solution for this is to create a custom route constraint and use it to control the domains that a specific URL will match.

DomainConstraint

    public class DomainConstraint : IRouteConstraint
    {
        private readonly string[] domains;

        public DomainConstraint(params string[] domains)
        {
            this.domains = domains ?? throw new ArgumentNullException(nameof(domains));
        }

        public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
        {
            string domain =
#if DEBUG
                // A domain specified as a query parameter takes precedence 
                // over the hostname (in debug compile only).
                // This allows for testing without configuring IIS with a 
                // static IP or editing the local hosts file.
                httpContext.Request.QueryString["domain"]; 
#else
                null;
#endif
            if (string.IsNullOrEmpty(domain))
                domain = httpContext.Request.Headers["HOST"];

            return domains.Contains(domain);
        }
    }

Usage

public class RouteConfig
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

        // This ignores Category/Cars for www.domain.com and www.foo.com
        routes.IgnoreRoute("Category/Cars", new { _ = new DomainConstraint("www.domain.com", "www.foo.com") });

        // Matches www.domain2.com/ and sends it to CategoryController.Cars
        routes.MapRoute(
            name: "HomePageDomain2",
            url: "",
            defaults: new { controller = "Category", action = "Cars" },
            constraints: new { _ = new DomainConstraint("www.domain2.com") }
        );

        routes.MapRoute(
            name: "Default",
            url: "{controller}/{action}/{id}",
            defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },

            // This constraint allows the route to work either 
            // on "www.domain.com" or "www.domain2.com" (excluding any other domain)
            constraints: new { _ = new DomainConstraint("www.domain.com", "www.domain2.com") }
        );
    }
}

If you fire this up in a new project in Visual Studio, you will notice it shows an error. This is because localhost:<port> is not a configured domain. However, if you navigate to:

/?domain=www.domain.com

You will see the home page.

This is because for the debug build only, it allows you to override the "local" domain name for testing purposes. You can configure your local IIS server to use a local static IP address (added to your network card) and add a local hosts file entry to test it locally without the query string parameter.

Note that when doing a "Release" build, there is no way to test using a query string parameter, as that would open up a potential security vulnerability.

If you use the URL:

/?domain=www.domain2.com

it will run the CategoryController.Cars action method (if one exists).

Note that since the Default route covers a wide range of URLs, most of the site will be available to both www.domain.com and www.domain2.com. For example, you will be able to reach the About page both at:

/Home/About?domain=www.domain.com
/Home/About?domain=www.domain2.com

You can use the IgnoreRoute extension method to block URLs that you don't want (and it accepts route constraints, so this solution will work there, too).

This solution will work if you largely want to share functionality between domains. If you would rather have 2 domains in one web site, but make them act like separate web sites, it would be easier to manage if you use an Area for each "web site" in your project by using the above route constraint for the Area routes.

public class Domain2AreaRegistration : AreaRegistration
{
    public override string AreaName
    {
        get
        {
            return "Domain2";
        }
    }

    public override void RegisterArea(AreaRegistrationContext context)
    {
        context.MapRoute(
            name: "Domain2_default",
            url: "{controller}/{action}/{id}",
            defaults: new { action = "Index", id = UrlParameter.Optional }, 
            constraints: new { _ = DomainConstraint("www.domain2.com") }
        );
    }
}

The above configuration would make every URL (that is 0, 1, 2, or 3 segments long) for www.domain2.com route to a controller in the Domain2 Area.

Community
  • 1
  • 1
NightOwl888
  • 55,572
  • 24
  • 139
  • 212
  • Hi, thanks! In a performance point-of-view (I have been able to put @ArthNRick solution's running for my case), which solution works best? (yours or ArthNRick's) – Patrick Feb 05 '18 at 16:17
  • From a performance point of view, they are roughly the same because ArthNRick's solution cascades the call to another action method rather than doing a redirect (which would issue a 302 response to the browser and tells it to lookup a new location on your server - extra network round trip). However, that solution won't scale well if you end up having more than a single view on `www.domain2.com`, and let's face it - that is probably going to happen. Routing is a separate concern than what goes on in an action method and domain names clearly fall into the scope of routing. – NightOwl888 Feb 05 '18 at 16:30
  • 1
    Also keep in mind, *all* URLs from `www.domain.com` will also be available on `www.domain2.com` (that is the default behavior). If that is what you intend, you should use a [Canonical Tag](https://support.google.com/webmasters/answer/139066?hl=en) to tell search engines you meant to put the same content on 2 different URLs. If that is not what you intend, then use the `DomainConstraint` to block routes from the domains where you don't want to see them. – NightOwl888 Feb 05 '18 at 16:40
0

in the default action of the application make sure that the url is the one of the second domain, then return the method that needs. something like:

      public ActionResult Index()
      {

        if (Request.Url.Host.Equals("domain2"))
            return AnotherAction();

      }
ArthNRick
  • 925
  • 1
  • 7
  • 22
  • Hi, thanks! Would it not be better using a simple route? regarding also the analytics point of view. – Patrick Feb 02 '18 at 16:01
  • I believe that this is not possible, what you need is an action redirection, since "domain.com" assumes the default route (usually "controller/action/{id}"), so on this default route you should correct location – ArthNRick Feb 02 '18 at 16:19
0

Agreed with the answer above.

If you want more beautiful implementation - try action filters. Sample of action filters usage from there.

public override void OnActionExecuting(ActionExecutingContext filterContext)
{
    var controller = (SomeControllerBase) filterContext.Controller;
    filterContext.Result = controller.RedirectToAction("index", "home");
}

Sample of getting the URL inside action filter from there.

var url = filterContext.HttpContext.Request.Url;

Put the things together and have fun :)

cortisol
  • 335
  • 2
  • 18