Ist es möglich, eine ASP.NET MVC-Route basierend auf einer Subdomain zu erstellen?

235

Ist es möglich, eine ASP.NET MVC-Route zu haben, die Subdomain-Informationen verwendet, um ihre Route zu bestimmen? Beispielsweise:

  • user1 .domain.com geht an einen Ort
  • user2 .domain.com geht zu einem anderen?

Oder kann ich es so machen, dass beide mit einem usernameParameter zur gleichen Steuerung / Aktion gehen ?

Dan Esparza
quelle
Ich habe etwas Ähnliches für Anwendungen mit mehreren Mandanten implementiert, aber einen abstrakten Basis-Controller anstelle einer benutzerdefinierten Route-Klasse verwendet. Mein Blogbeitrag dazu ist hier .
Luke Sampson
6
Berücksichtigen Sie unbedingt diesen Ansatz: http://blog.tonywilliams.me.uk/asp-net-mvc-2-routing-subdomains-to-areas Ich fand es besser, Multitenancy in meine App einzuführen als die anderen Antworten , weil MVC-Bereiche eine gute Möglichkeit sind, mandantenspezifische Controller und Ansichten auf organisierte Weise einzuführen.
Trebormf
2
@trebormf - Ich denke, Sie sollten es als Antwort hinzufügen. Dies ist, was ich letztendlich als Grundlage für meine Lösung verwendet habe.
Shagglez
@Shagglez - Danke. Es war eine Antwort, aber ein Moderator hat sie aus Gründen, die ich nicht verstehen kann, in einen Kommentar umgewandelt.
Trebormf
5
Tony ist wie kaputt. Hier ist eine, die für mich funktioniert hat: blog.tonywilliams.me.uk/…
Ronnie Overby

Antworten:

168

Sie können dies tun, indem Sie eine neue Route erstellen und sie der Routensammlung in RegisterRoutes in Ihrer global.asax hinzufügen. Unten finden Sie ein sehr einfaches Beispiel für eine benutzerdefinierte Route:

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;
    }
}
Jon Cahill
quelle
1
Vielen Dank für das detaillierte Beispiel, aber ich verfolge nicht, wie die .Add von Global.asax ausgeführt wird.
JustSteve
4
Ich habe die Route SubdomainRoute aufgerufen und als erste Route wie folgt hinzugefügt: route.Add (new SubdomainRoute ());
Jeff Handley
6
Erfordert dieser Ansatz die Hardcodierung einer Liste möglicher Subdomains?
Maxim V. Pavlov
2
Nein, Sie können ein Datenbankfeld mit dem Namen "Subdomain" hinzufügen, das genau das ist, was Sie von der Subdomain für einen bestimmten Benutzer erwarten, oder was auch immer, und dann einfach die Subdomain nachschlagen.
Ryan Hayes
1
Könnte jemand eine Webforms-Version davon empfehlen?
MatthewT
52

Verwenden Sie die folgende von abgeleitete Klasse, um die Subdomain unter Beibehaltung der Standard-MVC5-Routingfunktionen zu erfassen .SubdomainRouteRoute

Darüber hinaus SubdomainRouteermöglicht es die Sub - Domain optional als angegeben werden Abfrageparameter , was sub.example.com/foo/barund example.com/foo/bar?subdomain=subgleichwertig. Auf diese Weise können Sie testen, bevor die DNS-Subdomänen konfiguriert werden. Der Abfrageparameter (wenn verwendet) wird über neue Links weitergegeben, die von Url.Actionusw. generiert wurden.

Der Abfrageparameter ermöglicht auch das lokale Debuggen mit Visual Studio 2013, ohne mit netsh konfigurieren oder als Administrator ausgeführt werden zu müssen . Standardmäßig bindet IIS Express nur dann an localhost, wenn es nicht erhöht ist. es wird auch nicht an hostnamen wie gebunden sub.localtest.me gebunden .

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);
    }
}

Rufen Sie zur Vereinfachung Folgendes an MapSubdomainRoute Methode aus Ihrer RegisterRoutesMethode auf, so wie Sie es einfach alt machen würden 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()
    });
}

Um bequem auf die Subdomain zugreifen zu können (entweder über eine echte Subdomain oder einen Abfrageparameter), ist es hilfreich, eine Controller-Basisklasse mit dieser SubdomainEigenschaft zu erstellen :

protected string Subdomain
{
    get { return (string)Request.RequestContext.RouteData.Values["subdomain"]; }
}
Edward Brey
quelle
1
Ich habe den Code aktualisiert, um die Subdomain immer als Routenwert verfügbar zu machen. Dies vereinfacht den Zugriff auf die Subdomain.
Edward Brey
Ich mag das. Sehr einfach und mehr als genug für mein Projekt.
SoonDead
Dies ist eine großartige Antwort. Gibt es eine Möglichkeit, mit Routenattributen zu arbeiten? Ich versuche, diese Funktion für Pfade wie "subdomain.domain.com/portal/register" zu verwenden, und die Verwendung von Attributen würde dies vereinfachen.
perfect_element
@perfect_element - Attributrouten sind nicht erweiterbar wie konventionelle Routen. Die einzige Möglichkeit, so etwas zu tun, besteht darin, ein eigenes Attribut-Routing-System zu erstellen.
NightOwl888
23

Dies ist nicht meine Arbeit, aber ich musste sie zu dieser Antwort hinzufügen.

Hier ist eine großartige Lösung für dieses Problem. Maartin Balliauw hat Code geschrieben, der eine DomainRoute-Klasse erstellt, die sehr ähnlich wie das normale Routing verwendet werden kann.

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

Beispielgebrauch wäre so ...

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

;;

Jim Blake
quelle
5
Bei dieser Lösung liegt ein Problem vor. Angenommen, Sie möchten Subdomains als unterschiedliche Benutzer behandeln: route.Add ("SD", neue DomainRoute ("user} .localhost", "", new {controller = "Home", action = "IndexForUser", user = "u1) "})); Es wird auch die Homepage zwischengespeichert. Dies liegt an der generierten Regex. Um dies zu beheben, können Sie eine Kopie der CreateRegex-Methode in DomainRoute.cs erstellen, CreateDomainRegex nennen und das * in dieser Zeile in +: source = source.Replace ("}", @ "> ([a- zA-Z0-9 _] *)) "); und verwenden Sie diese neue Methode für die Domäne regx in der GetRouteData-Methode: domainRegex = CreateDomainRegex (Domain);
Gorkem Pacaci
Ich weiß nicht, warum ich diesen Code nicht ausführen kann ... Ich erhalte nur eine SERVER NOT FOUNDFehlermeldung ... bedeutet, dass der Code bei mir nicht funktioniert ... stellen Sie eine andere Konfiguration ein oder so?!
Dr. TJ
Ich habe einen Gist meiner Version dieses gist.github.com/IDisposable/77f11c6f7693f9d181bb
IDisposable
1
@IDisposable Was ist MvcApplication.DnsSuffix?
HaBo
Wir legen nur die Basis-DNS-Domäne in web.config offen ... typischer Wert wäre .example.org
IDisposable
4

Um die Subdomain bei Verwendung der Web-API zu erfassen , überschreiben Sie die Aktionsauswahl, um einen subdomainAbfrageparameter einzufügen. Verwenden Sie dann den Abfrageparameter für die Subdomain in den Aktionen Ihrer Controller wie folgt:

public string Get(string id, string subdomain)

Dieser Ansatz erleichtert das Debuggen, da Sie den Abfrageparameter manuell angeben können, wenn Sie localhost anstelle des tatsächlichen Hostnamens verwenden ( Einzelheiten finden Sie in der Standard-MVC5-Routing-Antwort ). Dies ist der Code für die Aktionsauswahl:

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);
    }
}

Ersetzen Sie die Standard-Aktionsauswahl, indem Sie diese hinzufügen WebApiConfig.Register :

config.Services.Replace(typeof(IHttpActionSelector), new SubdomainActionSelector(config.Services.GetActionSelector()));
Edward Brey
quelle
Bei Problemen, bei denen die Routendaten nicht auf dem Web-API-Controller angezeigt werden und die Request.GetRouteData im Controller überprüft werden, werden keine Werte angezeigt.
Alan Macdonald
3

Ja, aber Sie müssen Ihren eigenen Routen-Handler erstellen.

Normalerweise kennt die Route die Domäne nicht, da die Anwendung für jede Domäne bereitgestellt werden kann und die Route auf die eine oder andere Weise keine Rolle spielt. In Ihrem Fall möchten Sie den Controller und die Aktion jedoch außerhalb der Domäne einrichten, sodass Sie eine benutzerdefinierte Route erstellen müssen, die die Domäne kennt.

Nick Berardi
quelle
3

Ich habe eine Bibliothek für das Subdomain-Routing erstellt, mit der Sie eine solche Route erstellen können. Es funktioniert derzeit für .NET Core 1.1 und .NET Framework 4.6.1, wird jedoch in naher Zukunft aktualisiert. So funktioniert es:
1) Ordnen Sie die Subdomain-Route in Startup.cs zu

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) Controller / HomeController.cs

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

3) Mit dieser Bibliothek können Sie auch URLs und Formulare generieren. Code:

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

Die generierte <a href="http://user1.localhost:54575/Home/Index">User home</a> URL wird auch vom aktuellen Hostspeicherort und -schema abhängen.
Sie können auch HTML-Helfer für BeginFormund verwenden UrlHelper. Wenn Sie möchten, können Sie auch die neue Funktion tag helpers ( FormTagHelper, AnchorTagHelper) verwenden.
Diese Bibliothek verfügt noch über keine Dokumentation, es gibt jedoch einige Test- und Beispielprojekte. Sie können sie also gerne erkunden.

Mariusz
quelle
2

In ASP.NET Core ist der Host über verfügbar Request.Host.Host. Wenn Sie das Überschreiben des Hosts über einen Abfrageparameter zulassen möchten, überprüfen Sie zunächstRequest.Query .

Fügen Sie diesen Code zur Routenkonfiguration hinzu, damit ein Host-Abfrageparameter an neue routenbasierte URLs weitergegeben wird app.UseMvc:

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

Und so definieren HostPropagationRouter:

/// <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);
}
Edward Brey
quelle
1

Nachdem Sie einen neuen Route-Handler definiert haben, der sich mit dem in der URL übergebenen Host befasst , können Sie sich für einen Basis-Controller entscheiden, der die Site kennt, auf die zugegriffen wird. Es sieht aus wie das:

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 ist eine einfache Schnittstelle:

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

Ich verweise Sie gehen zu Luke Sampson Blog

Amirhossein Mehrvarzi
quelle
1

Wenn Sie Ihrem Projekt MultiTenancy-Funktionen mit unterschiedlichen Domänen / Subdomänen für jeden Mandanten geben möchten, sollten Sie sich SaasKit ansehen:

https://github.com/saaskit/saaskit

Codebeispiele finden Sie hier: http://benfoster.io/blog/saaskit-multi-tenancy-made-easy

Einige Beispiele für die Verwendung des ASP.NET-Kerns: http://andrewlock.net/forking-the-pipeline-adding-tenant-specific-files-with-saaskit-in-asp-net-core/

BEARBEITEN: Wenn Sie SaasKit in Ihrem ASP.NET-Kernprojekt nicht verwenden möchten, können Sie sich Maartens Implementierung des Domänenroutings für MVC6 ansehen: https://blog.maartenballiauw.be/post/2015/02/17/domain -routing-and-Auflösung-aktueller-Mieter-mit-Aspnet-MVC-6-Aspnet-5.html

Diese Gists werden jedoch nicht verwaltet und müssen optimiert werden, um mit der neuesten Version von ASP.NET Core zu arbeiten.

Direkter Link zum Code: https://gist.github.com/maartenba/77ca6f9cfef50efa96ec#file-domaintemplateroutebuilderextensions-cs

Darxtar
quelle
Nicht auf der Suche nach Mandantenfähigkeit - aber danke für den Tipp!
Dan Esparza
0

Vor einigen Monaten habe ich ein Attribut entwickelt, das Methoden oder Controller auf bestimmte Domänen beschränkt.

Es ist ganz einfach zu bedienen:

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

Sie können es auch direkt auf einen Controller anwenden.

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
    }
}

Einschränkung: Möglicherweise können Sie nicht zwei gleiche Routen auf unterschiedlichen Methoden mit unterschiedlichen Filtern verwenden. Ich meine, Folgendes kann eine Ausnahme für doppelte Routen auslösen:

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

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