Apakah mungkin membuat rute ASP.NET MVC berdasarkan subdomain?

235

Apakah mungkin untuk memiliki rute ASP.NET MVC yang menggunakan informasi subdomain untuk menentukan rutenya? Sebagai contoh:

  • user1 .domain.com pergi ke satu tempat
  • user2 .domain.com pergi ke yang lain?

Atau, bisakah saya membuatnya sehingga keduanya pergi ke controller / action yang sama dengan usernameparameter?

Dan Esparza
sumber
Saya menerapkan hal serupa untuk aplikasi multi-tenant, tetapi menggunakan Pengontrol basis abstrak daripada kelas rute kustom. Posting blog saya ada di sini .
Luke Sampson
6
Pastikan untuk mempertimbangkan pendekatan ini: http://blog.tonywilliams.me.uk/asp-net-mvc-2-routing-subdomains-to-areas saya merasa lebih baik untuk memperkenalkan multitenancy ke aplikasi saya daripada jawaban lain , karena area MVC adalah cara yang bagus untuk memperkenalkan pengontrol dan pandangan khusus penyewa secara terorganisir.
trebormf
2
@ trebormf - Saya pikir Anda harus menambahkannya sebagai jawaban, inilah yang akhirnya saya gunakan sebagai dasar untuk solusi saya.
Shagglez
@ Shagglez - Terima kasih. Itu adalah jawaban, tetapi seorang moderator mengubahnya menjadi komentar karena alasan yang tidak dapat saya mengerti.
trebormf
5
Seperti Tony rusak. Ini salah satu yang berhasil untuk saya: blog.tonywilliams.me.uk/…
Ronnie Overby

Jawaban:

168

Anda dapat melakukannya dengan membuat rute baru dan menambahkannya ke koleksi rute di RegisterRoutes di global.asax Anda. Di bawah ini adalah contoh rute kustom yang sangat sederhana:

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
sumber
1
Terima kasih atas sampel terperinci, tetapi saya tidak mengikuti cara menjalankan .Tambahkan dari Global.asax.
justSteve
4
Saya memanggil rute SubdomainRoute dan menambahkannya sebagai rute pertama seperti ini: routes.Add (new SubdomainRoute ());
Jeff Handley
6
Apakah pendekatan ini memerlukan pengodean keras daftar kemungkinan sub-domain?
Maxim V. Pavlov
2
Tidak, Anda dapat menambahkan bidang basis data yang disebut "subdomain" sehingga Anda akan mengharapkan subdomain untuk pengguna tertentu, atau apa pun yang lain, lalu lakukan pencarian pada subdomain tersebut.
Ryan Hayes
1
Adakah yang bisa merekomendasikan versi webforms ini?
MatthewT
52

Untuk menangkap subdomain sambil mempertahankan fitur perutean MVC5 standar , gunakan SubdomainRoutekelas berikut yang berasal dari Route.

Selain itu, SubdomainRoutememungkinkan subdomain secara opsional ditetapkan sebagai parameter kueri , pembuatan, sub.example.com/foo/bardan yang example.com/foo/bar?subdomain=subsetara. Ini memungkinkan Anda untuk menguji sebelum subdomain DNS dikonfigurasi. Parameter kueri (saat digunakan) diperbanyak melalui tautan baru yang dihasilkan oleh Url.Action, dll.

Parameter kueri juga memungkinkan debugging lokal dengan Visual Studio 2013 tanpa harus mengkonfigurasi dengan netsh atau dijalankan sebagai Administrator . Secara default, IIS Express hanya mengikat ke localhost ketika tidak ditinggikan; itu tidak akan mengikat nama host yang sama seperti 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);
    }
}

Untuk kenyamanan, panggil MapSubdomainRoutemetode berikut dari RegisterRoutesmetode Anda seperti yang biasa Anda lakukan 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()
    });
}

Akhirnya, untuk mengakses subdomain dengan mudah (baik dari subdomain asli atau parameter kueri), akan sangat membantu untuk membuat kelas dasar Pengontrol dengan Subdomainproperti ini :

protected string Subdomain
{
    get { return (string)Request.RequestContext.RouteData.Values["subdomain"]; }
}
Edward Brey
sumber
1
Saya memperbarui kode untuk membuat subdomain selalu tersedia sebagai nilai rute. Ini menyederhanakan akses ke subdomain.
Edward Brey
Saya suka ini. Sangat sederhana, dan lebih dari cukup untuk proyek saya.
SoonDead
Ini jawaban yang bagus. Apakah ada cara untuk ini bekerja dengan atribut rute? Saya mencoba membuat ini berfungsi untuk jalur seperti "subdomain.domain.com/portal/register" dan menggunakan atribut akan membuatnya lebih mudah.
perfect_element
@perfect_element - Rute atribut tidak dapat diperpanjang seperti rute berbasis konvensi. Satu-satunya cara untuk melakukan hal seperti itu adalah dengan membangun sistem perutean atribut Anda sendiri.
NightOwl888
23

Ini bukan pekerjaan saya, tetapi saya harus menambahkannya pada jawaban ini.

Berikut ini solusi hebat untuk masalah ini. Maartin Balliauw menulis kode yang menciptakan kelas DomainRoute yang dapat digunakan sangat mirip dengan perutean normal.

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

Penggunaan sampel akan seperti ini ...

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
sumber
5
Ada masalah dengan solusi ini. Katakanlah, Anda ingin menangani subdomain sebagai pengguna yang berbeda: route.Add ("SD", DomainRoute baru ("user} .localhost", "", new {controller = "Home", action = "IndexForUser", user = "u1 "})); Tembolok homepage juga. Ini karena regex yang dihasilkan. Untuk memperbaikinya, Anda dapat membuat salinan metode CreateRegex di DomainRoute.cs, beri nama CreateDomainRegex, ubah * pada baris ini menjadi +: source = source.Replace ("}", @ "> ([a- zA-Z0-9 _] *)) "); dan gunakan metode baru ini untuk regx domain dalam metode GetRouteData: domainRegex = CreateDomainRegex (Domain);
Gorkem Pacaci
Saya tidak tahu mengapa saya tidak dapat menjalankan kode ini ... Saya hanya menerima SERVER NOT FOUNDkesalahan ... berarti kode tersebut tidak berfungsi untuk saya ... apakah Anda mengatur konfigurasi lain atau apa?
Dr TJ
Saya telah membuat Gist versi gist.github.com/IDisposable/77f11c6f7693f9d181bb
IDisposable
1
@Isposable, apa itu MvcApplication.DnsSuffix?
HaBo
Kami baru saja mengekspos domain DNS dasar di web.config ... nilai khasnya adalah .example.org
IDisposable
4

Untuk menangkap subdomain saat menggunakan API Web , ganti Pemilih Tindakan untuk menyuntikkan subdomainparameter kueri. Kemudian gunakan parameter permintaan subdomain dalam tindakan pengontrol Anda seperti ini:

public string Get(string id, string subdomain)

Pendekatan ini memudahkan debugging karena Anda dapat menentukan parameter kueri dengan tangan saat menggunakan localhost alih-alih nama host yang sebenarnya (lihat jawaban perutean MVC5 standar untuk detailnya). Ini adalah kode untuk Pemilih Aksi:

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

Ganti Pemilih Aksi default dengan menambahkan ini ke WebApiConfig.Register:

config.Services.Replace(typeof(IHttpActionSelector), new SubdomainActionSelector(config.Services.GetActionSelector()));
Edward Brey
sumber
Adakah yang mengalami masalah ketika data rute tidak muncul di pengontrol API web dan memeriksa Request.GetRouteData di dalam pengontrol tidak menunjukkan nilai?
Alan Macdonald
3

Ya tetapi Anda harus membuat pengendali rute Anda sendiri.

Biasanya rute tidak mengetahui domain karena aplikasi dapat digunakan untuk domain apa pun dan rute tidak akan peduli dengan satu atau lain cara. Tetapi dalam kasus Anda, Anda ingin mendasarkan controller dan bertindak dari domain, sehingga Anda harus membuat rute kustom yang menyadari domain.

Nick Berardi
sumber
3

Saya membuat pustaka untuk perutean subdomain yang dapat Anda buat rute seperti itu. Ini bekerja saat ini untuk .NET Core 1.1 dan .NET Framework 4.6.1 tetapi akan diperbarui dalam waktu dekat. Beginilah cara kerjanya:
1) Peta rute subdomain di 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) Pengendali / HomeController.cs

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

3) Lib itu juga akan memungkinkan Anda untuk menghasilkan URL dan formulir. Kode:

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

Akan menghasilkan <a href="http://user1.localhost:54575/Home/Index">User home</a> URL yang Dihasilkan juga akan tergantung pada lokasi dan skema host saat ini.
Anda juga dapat menggunakan bantuan html untuk BeginFormdan UrlHelper. Jika Anda suka, Anda juga dapat menggunakan fitur baru yang disebut tag helpers ( FormTagHelper, AnchorTagHelper)
Lib itu belum memiliki dokumentasi, tetapi ada beberapa tes dan sampel proyek jadi jangan ragu untuk menjelajahinya.

Mariusz
sumber
2

Di ASP.NET Core , host tersedia melalui Request.Host.Host. Jika Anda ingin mengizinkan mengesampingkan host melalui parameter kueri, periksa duluRequest.Query .

Untuk menyebabkan parameter kueri inang menyebar ke URL berbasis rute baru, tambahkan kode ini ke app.UseMvckonfigurasi rute:

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

Dan definisikan HostPropagationRouterseperti ini:

/// <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
sumber
1

Setelah menentukan pengendali rute baru yang akan melihat host yang disahkan dalam URL , Anda bisa pergi dengan gagasan Pengontrol dasar yang mengetahui Situs itu sedang diakses. Ini terlihat seperti ini:

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 adalah antarmuka yang sederhana:

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

Saya merujuk Anda ke Blog Luke Sampson

Amirhossein Mehrvarzi
sumber
1

Jika Anda ingin memberikan kemampuan MultiTenancy ke proyek Anda dengan berbagai domain / subdomain untuk setiap penyewa, Anda harus melihat pada SaasKit:

https://github.com/saaskit/saaskit

Contoh kode dapat dilihat di sini: http://benfoster.io/blog/saaskit-multi-tenancy-made-easy

Beberapa contoh menggunakan inti ASP.NET: http://andrewlock.net/forking-the-pipeline-adding-tenant-specific-files-with-saaskit-in-asp-net-core/

EDIT: Jika Anda tidak ingin menggunakan SaasKit dalam proyek inti ASP.NET Anda, Anda dapat melihat implementasi perutean domain Maarten untuk MVC6: https://blog.maartenballiauw.be/post/2015/02/17/domain -routing-and-resolving-current-tenant-with-aspnet-mvc-6-aspnet-5.html

Namun, intisari tersebut tidak dikelola dan perlu diubah untuk bekerja dengan rilis terbaru dari inti ASP.NET.

Tautan langsung ke kode: https://gist.github.com/maartenba/77ca6f9cfef50efa96ec#file-domaintemplateroutebuilderextensions-cs

Darxtar
sumber
Tidak mencari multitenancy - tapi terima kasih atas tipnya!
Dan Esparza
0

Beberapa bulan yang lalu saya telah mengembangkan atribut yang membatasi metode atau pengontrol ke domain tertentu.

Sangat mudah digunakan:

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

Anda juga dapat menerapkannya langsung pada pengontrol.

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

Pembatasan: Anda mungkin tidak dapat memiliki dua rute yang sama pada metode yang berbeda dengan filter yang berbeda. Maksud saya, yang berikut ini dapat menimbulkan pengecualian untuk rute duplikat:

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

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