web-dev-qa-db-fra.com

Est-il possible de créer une route ASP.NET MVC basée sur un sous-domaine?

Est-il possible d'avoir un itinéraire ASP.NET MVC qui utilise les informations de sous-domaine pour déterminer son itinéraire? Par exemple:

  • user1 . domain.com va à un endroit
  • user2 . domain.com passe à un autre?

Ou, puis-je faire en sorte que ces deux éléments soient affectés au même contrôleur/action avec un paramètre username?

229
Dan Esparza

Vous pouvez le faire en créant un nouvel itinéraire et en l’ajoutant à la collection d’itinéraires de RegisterRoutes dans votre global.asax. Vous trouverez ci-dessous un exemple très simple d'itinéraire personnalisé:

public class ExampleRoute : RouteBase
{

    public override RouteData GetRouteData(HttpContextBase httpContext)
    {
        var url = httpContext.Request.Headers["Host"];
        var index = url.IndexOf(".");

        if (index < 0)
            return null;

        var subDomain = url.Substring(0, index);

        if (subDomain == "user1")
        {
            var routeData = new RouteData(this, new MvcRouteHandler());
            routeData.Values.Add("controller", "User1"); //Goes to the User1Controller class
            routeData.Values.Add("action", "Index"); //Goes to the Index action on the User1Controller

            return routeData;
        }

        if (subDomain == "user2")
        {
            var routeData = new RouteData(this, new MvcRouteHandler());
            routeData.Values.Add("controller", "User2"); //Goes to the User2Controller class
            routeData.Values.Add("action", "Index"); //Goes to the Index action on the User2Controller

            return routeData;
        }

        return null;
    }

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

Pour capturer le sous-domaine tout en conservant les fonctionnalités de routage MVC5 standard, utilisez la classe SubdomainRoute suivante dérivée de Route.

De plus, SubdomainRoute permet éventuellement de spécifier le sous-domaine en tant que paramètre de requête , ce qui rend sub.example.com/foo/bar et example.com/foo/bar?subdomain=sub équivalents. Cela vous permet de tester avant que les sous-domaines DNS soient configurés. Le paramètre de requête (en cours d'utilisation) est propagé via de nouveaux liens générés par Url.Action, etc.

Le paramètre de requête active également le débogage local avec Visual Studio 2013 sans avoir à configurer avec netsh ou exécuter en tant qu'administrateur . Par défaut, IIS Express ne se lie qu'à localhost lorsqu'il n'est pas élevé; cela ne se liera pas à des noms d'hôtes tels que sub.localtest.me.

class SubdomainRoute : Route
{
    public SubdomainRoute(string url) : base(url, new MvcRouteHandler()) {}

    public override RouteData GetRouteData(HttpContextBase httpContext)
    {
        var routeData = base.GetRouteData(httpContext);
        if (routeData == null) return null; // Only look at the subdomain if this route matches in the first place.
        string subdomain = httpContext.Request.Params["subdomain"]; // A subdomain specified as a query parameter takes precedence over the hostname.
        if (subdomain == null) {
            string Host = httpContext.Request.Headers["Host"];
            int index = Host.IndexOf('.');
            if (index >= 0)
                subdomain = Host.Substring(0, index);
        }
        if (subdomain != null)
            routeData.Values["subdomain"] = subdomain;
        return routeData;
    }

    public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
    {
        object subdomainParam = requestContext.HttpContext.Request.Params["subdomain"];
        if (subdomainParam != null)
            values["subdomain"] = subdomainParam;
        return base.GetVirtualPath(requestContext, values);
    }
}

Pour plus de commodité, appelez la méthode MapSubdomainRoute suivante à partir de votre méthode RegisterRoutes comme vous le feriez avec l'ancien MapRoute:

static void MapSubdomainRoute(this RouteCollection routes, string name, string url, object defaults = null, object constraints = null)
{
    routes.Add(name, new SubdomainRoute(url) {
        Defaults = new RouteValueDictionary(defaults),
        Constraints = new RouteValueDictionary(constraints),
        DataTokens = new RouteValueDictionary()
    });
}

Enfin, pour accéder facilement au sous-domaine (à partir d'un vrai sous-domaine ou d'un paramètre de requête), il est utile de créer une classe de base de contrôleur avec cette propriété Subdomain:

protected string Subdomain
{
    get { return (string)Request.RequestContext.RouteData.Values["subdomain"]; }
}
52
Edward Brey

Ce n'est pas mon travail, mais je devais l'ajouter à cette réponse.

Voici une excellente solution à ce problème. Maartin Balliauw a écrit un code qui crée une classe DomainRoute pouvant être utilisée de manière très similaire au routage normal.

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

Exemple d'utilisation serait comme ça ...

routes.Add("DomainRoute", new DomainRoute( 
    "{customer}.example.com", // Domain with parameters 
    "{action}/{id}",    // URL with parameters 
    new { controller = "Home", action = "Index", id = "" }  // Parameter defaults 
))

;

23
Jim Blake

Pour capturer le sous-domaine lorsque vous utilisez Web API, substituez le sélecteur d'action pour injecter un paramètre de requête subdomain. Ensuite, utilisez le paramètre de requête de sous-domaine dans les actions de vos contrôleurs comme ceci:

public string Get(string id, string subdomain)

Cette approche facilite le débogage car vous pouvez spécifier manuellement le paramètre de requête lorsque vous utilisez localhost au lieu du nom d'hôte réel (voir le standard routage MVC5 answer pour plus de détails). C'est le code pour Action Selector:

class SubdomainActionSelector : IHttpActionSelector
{
    private readonly IHttpActionSelector defaultSelector;

    public SubdomainActionSelector(IHttpActionSelector defaultSelector)
    {
        this.defaultSelector = defaultSelector;
    }

    public ILookup<string, HttpActionDescriptor> GetActionMapping(HttpControllerDescriptor controllerDescriptor)
    {
        return defaultSelector.GetActionMapping(controllerDescriptor);
    }

    public HttpActionDescriptor SelectAction(HttpControllerContext controllerContext)
    {
        var routeValues = controllerContext.Request.GetRouteData().Values;
        if (!routeValues.ContainsKey("subdomain")) {
            string Host = controllerContext.Request.Headers.Host;
            int index = Host.IndexOf('.');
            if (index >= 0)
                controllerContext.Request.GetRouteData().Values.Add("subdomain", Host.Substring(0, index));
        }
        return defaultSelector.SelectAction(controllerContext);
    }
}

Remplacez le sélecteur d'action par défaut en l'ajoutant à WebApiConfig.Register:

config.Services.Replace(typeof(IHttpActionSelector), new SubdomainActionSelector(config.Services.GetActionSelector()));
4
Edward Brey

J'ai créé bibliothèque pour le routage de sous-domaine que vous pouvez créer un tel itinéraire. Il fonctionne actuellement pour un .NET Core 1.1 et .NET Framework 4.6.1 mais sera mis à jour dans un proche avenir. Voici comment ça marche: 
1) Carte route de sous-domaine dans Startup.cs

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    var hostnames = new[] { "localhost:54575" };

    app.UseMvc(routes =>
    {
        routes.MapSubdomainRoute(
            hostnames,
            "SubdomainRoute",
            "{username}",
            "{controller}/{action}",
            new { controller = "Home", action = "Index" });
    )};

2) Contrôleurs/HomeController.cs

public IActionResult Index(string username)
{
    //code
}

3) Cette bibliothèque vous permettra également de générer des URL et des formulaires. Code:

@Html.ActionLink("User home", "Index", "Home" new { username = "user1" }, null)

Générera <a href="http://user1.localhost:54575/Home/Index">User home</a> L'URL générée dépendra également de l'emplacement et du schéma actuels de l'hôte .
Vous pouvez également utiliser des aides HTML pour BeginForm et UrlHelper. Si vous le souhaitez, vous pouvez également utiliser une nouvelle fonctionnalité appelée assistant de tag (FormTagHelper, AnchorTagHelper). 
Cette bibliothèque n’a pas encore de documentation, mais il y a des tests et des exemples de projets, alors n'hésitez pas à les explorer.

3
Mariusz

Oui, mais vous devez créer votre propre gestionnaire d’itinéraires. 

Généralement, la route n’a pas connaissance du domaine car l’application pourrait être déployée dans n’importe quel domaine et la route n’aurait aucune importance. Mais dans votre cas, vous souhaitez baser le contrôleur et l'action sur le domaine. Vous devez donc créer une route personnalisée tenant compte du domaine.

3
Nick Berardi

Dans ASP.NET Core , l'hôte est disponible via Request.Host.Host. Si vous souhaitez autoriser le remplacement de l'hôte via un paramètre de requête, cochez d'abord Request.Query.

Pour que le paramètre de requête Host se propage dans de nouvelles URL basées sur une route, ajoutez ce code à la configuration de route app.UseMvc:

routes.Routes.Add(new HostPropagationRouter(routes.DefaultHandler));

Et définissez HostPropagationRouter comme ceci:

/// <summary>
/// A router that propagates the request's "Host" query parameter to the response.
/// </summary>
class HostPropagationRouter : IRouter
{
    readonly IRouter router;

    public HostPropagationRouter(IRouter router)
    {
        this.router = router;
    }

    public VirtualPathData GetVirtualPath(VirtualPathContext context)
    {
        if (context.HttpContext.Request.Query.TryGetValue("Host", out var Host))
            context.Values["Host"] = Host;
        return router.GetVirtualPath(context);
    }

    public Task RouteAsync(RouteContext context) => router.RouteAsync(context);
}
2
Edward Brey

Après avoir défini un nouveau gestionnaire d'itinéraire qui examinerait l'hôte transmis dans l'URL, vous pouvez vous faire une idée d'un contrôleur de base connaissant le site pour lequel il est utilisé. Cela ressemble à ceci:

public abstract class SiteController : Controller {
    ISiteProvider _siteProvider;

    public SiteController() {
        _siteProvider = new SiteProvider();
    }

    public SiteController(ISiteProvider siteProvider) {
        _siteProvider = siteProvider;
    }

    protected override void Initialize(RequestContext requestContext) {
        string[] Host = requestContext.HttpContext.Request.Headers["Host"].Split(':');

        _siteProvider.Initialise(Host[0]);

        base.Initialize(requestContext);
    }

    protected override void OnActionExecuting(ActionExecutingContext filterContext) {
        ViewData["Site"] = Site;

        base.OnActionExecuting(filterContext);
    }

    public Site Site {
        get {
            return _siteProvider.GetCurrentSite();
        }
    }

}

ISiteProvider est une interface simple:

public interface ISiteProvider {
    void Initialise(string Host);
    Site GetCurrentSite();
}

Je vous renvoie à Blog Luke Sampson

1

Si vous envisagez de doter votre projet de fonctionnalités MultiTenancy avec différents domaines/sous-domaines pour chaque client hébergé, consultez SaasKit:

https://github.com/saaskit/saaskit

Des exemples de code peuvent être vus ici: http://benfoster.io/blog/saaskit-multi-tenancy-made-easy

Quelques exemples utilisant le noyau ASP.NET: http://andrewlock.net/forking-the-pipeline-adding-tenant-specific-files-with-saaskit-in-asp-net-core/

EDIT: Si vous ne voulez pas utiliser SaasKit dans votre projet ASP.NET, vous pouvez jeter un coup d'œil à la mise en œuvre de Maarten du routage de domaine pour MVC6: https://blog.maartenballiauw.be/post/2015/ 02/17/domaine-routage-et-résolution-actuel-locataire-avec-aspnet-mvc-6-aspnet-5.html

Cependant, ces Gists ne sont pas maintenus et doivent être modifiés pour fonctionner avec la dernière version du noyau ASP.NET.

Lien direct vers le code: https://Gist.github.com/maartenba/77ca6f9cfef50efa96ec#file-domaintemplateroutebuilderextensions-cs

1
Darxtar

Il y a quelques mois, j'ai développé un attribut qui limite les méthodes ou les contrôleurs à des domaines spécifiques. 

C'est assez facile à utiliser: 

[IsDomain("localhost","example.com","www.example.com","*.t1.example.com")]
[HttpGet("RestrictedByHost")]
public IActionResult Test(){}

Vous pouvez également l'appliquer directement sur un contrôleur.

public class IsDomainAttribute : Attribute, Microsoft.AspNetCore.Mvc.Filters.IAuthorizationFilter
{

    public IsDomainAttribute(params string[]  domains)
    {
        Domains = domains;
    }

    public string[] Domains { get; }

    public void OnAuthorization(AuthorizationFilterContext context)
    {
        var Host = context.HttpContext.Request.Host.Host;
        if (Domains.Contains(Host))
            return;
        if (Domains.Any(d => d.EndsWith("*"))
                && Domains.Any(d => Host.StartsWith(d.Substring(0, d.Length - 1))))
            return;
        if (Domains.Any(d => d.StartsWith("*"))
                && Domains.Any(d => Host.EndsWith(d.Substring(1))))
            return;

        context.Result = new Microsoft.AspNetCore.Mvc.NotFoundResult();//.ChallengeResult
    }
}

Restriction: Il se peut que vous ne puissiez pas avoir deux mêmes itinéraires sur des méthodes différentes avec des filtres différents Je veux dire ce qui suit peut lever une exception pour un itinéraire en double:

[IsDomain("test1.example.com")]
[HttpGet("/Test")]
public IActionResult Test1(){}

[IsDomain("test2.example.com")]
[HttpGet("/Test")]
public IActionResult Test2(){}
0
Jean