Có thể thực hiện một tuyến ASP.NET MVC dựa trên một tên miền phụ?


235

Có thể có một tuyến ASP.NET MVC sử dụng thông tin tên miền phụ để xác định tuyến đường của nó không? Ví dụ:

  • user1 .domain.com đi đến một nơi
  • user2 .domain.com đi đến nơi khác?

Hoặc, tôi có thể làm cho nó để cả hai cùng đi đến cùng một bộ điều khiển / hành động với một usernametham số không?


Tôi đã triển khai một loại điều tương tự cho các ứng dụng nhiều bên thuê, nhưng sử dụng Bộ điều khiển cơ sở trừu tượng thay vì lớp Tuyến tùy chỉnh. Bài viết trên blog của tôi trên đây là ở đây .
Luke Sampson

6
Hãy chắc chắn xem xét phương pháp này: http://blog.tonywilliams.me.uk/asp-net-mvc-2-routing-subdomains-to-areas Tôi thấy nó tốt hơn khi giới thiệu đa nhiệm cho ứng dụng của tôi so với các câu trả lời khác , bởi vì các khu vực MVC là một cách hay để giới thiệu các bộ điều khiển và khung nhìn cụ thể của người thuê một cách có tổ chức.
trebormf

2
@trebormf - Tôi nghĩ bạn nên thêm nó làm câu trả lời, đây là những gì tôi đã sử dụng làm cơ sở cho giải pháp của mình.
Shagglez

@Shagglez - Cảm ơn. Đó là một câu trả lời, nhưng một người điều hành đã chuyển đổi nó thành một nhận xét vì những lý do tôi không thể hiểu.
trebormf

5
Tony như bị phá vỡ. Đây là một công việc phù hợp với tôi: blog.tonywilliams.me.uk/ Kẻ
Ronnie Overby

Câu trả lời:


168

Bạn có thể làm điều đó bằng cách tạo một tuyến đường mới và thêm nó vào bộ sưu tập các tuyến đường trong RegisterRoutes trong global.asax của bạn. Dưới đây là một ví dụ rất đơn giản về Tuyến đường tùy chỉnh:

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

1
Cảm ơn về mẫu chi tiết nhưng tôi không theo dõi cách thực thi .Add từ Global.asax.
justSteve

4
Tôi đã gọi tuyến SubdomainRoute và thêm nó là tuyến đầu tiên như thế này: tuyến.Add (new SubdomainRoute ());
Jeff Handley

6
Cách tiếp cận này có yêu cầu mã hóa cứng một danh sách các tên miền phụ có thể không?
Maxim V. Pavlov

2
Không, bạn có thể thêm trường cơ sở dữ liệu được gọi là "tên miền phụ" mà bạn sẽ mong muốn tên miền phụ sẽ dành cho một người dùng cụ thể hoặc bất cứ điều gì khác, sau đó chỉ cần tìm kiếm tên miền phụ.
Ryan Hayes

1
Bất cứ ai có thể đề nghị một phiên bản webforms này?
Matthew

52

Để nắm bắt tên miền phụ trong khi vẫn giữ các tính năng định tuyến MVC5 tiêu chuẩn , hãy sử dụng SubdomainRoutelớp sau có nguồn gốc từ Route.

Ngoài ra, SubdomainRoutecho phép tùy chọn tên miền phụ làm tham số truy vấn , tạo sub.example.com/foo/barexample.com/foo/bar?subdomain=subtương đương. Điều này cho phép bạn kiểm tra trước khi tên miền phụ DNS được định cấu hình. Tham số truy vấn (khi sử dụng) được truyền qua các liên kết mới được tạo bởiUrl.Action , v.v.

Tham số truy vấn cũng cho phép gỡ lỗi cục bộ với Visual Studio 2013 mà không phải cấu hình với Netsh hoặc chạy với tư cách Quản trị viên . Theo mặc định, IIS Express chỉ liên kết với localhost khi không nâng cao; nó sẽ không liên kết với các tên máy chủ đồng nghĩa như 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);
    }
}

Để thuận tiện, hãy gọi MapSubdomainRoutephương thức sau từ RegisterRoutesphương thức của bạn giống như bạn thường sử dụng 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()
    });
}

Cuối cùng, để truy cập thuận tiện tên miền phụ (từ tên miền phụ thực hoặc tham số truy vấn), sẽ hữu ích khi tạo lớp cơ sở Bộ điều khiển với thuộc Subdomaintính này:

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

1
Tôi đã cập nhật mã để làm cho tên miền phụ luôn có sẵn dưới dạng giá trị tuyến. Điều này đơn giản hóa việc truy cập vào tên miền phụ.
Edward Brey

Tôi thích điều này. Rất đơn giản, và quá đủ cho dự án của tôi.
SoonDead

Đây là một câu trả lời tuyệt vời. Có cách nào để làm việc với các thuộc tính tuyến đường không? Tôi đang cố gắng để làm cho công việc này cho các đường dẫn như "subomain.domain.com/portal/register" và sử dụng các thuộc tính sẽ giúp việc này dễ dàng hơn.
perfect_element

@perinf_element - Các tuyến thuộc tính không thể mở rộng như các tuyến dựa trên quy ước. Cách duy nhất để làm một cái gì đó như thế là xây dựng hệ thống định tuyến thuộc tính của riêng bạn.
NightOwl888

23

Đây không phải là công việc của tôi, nhưng tôi đã phải thêm nó vào câu trả lời này.

Đây là một giải pháp tuyệt vời cho vấn đề này. Maartin Balliauw đã viết mã tạo ra một lớp DomainRoute có thể được sử dụng rất giống với định tuyến thông thường.

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

Sử dụng mẫu sẽ như thế này ...

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

;


5
Có một vấn đề với giải pháp này. Giả sử, bạn muốn xử lý các tên miền phụ như những người dùng khác nhau: Rout.Add ("SD", new DomainRoute ("user} .localhost", "", new {controller = "Home", action = "IndexForUser", user = "u1 "})); Nó lưu trữ trang chủ là tốt. Điều này là do regex được tạo ra. Để khắc phục điều này, bạn có thể tạo một bản sao của phương thức CreatRegex trong DomainRoute.cs, đặt tên là CreateDomainRegex, thay đổi * trên dòng này thành +: source = source.Replace ("}", @ "> ([a- zA-Z0-9 _] *)) "); và sử dụng phương thức mới này cho tên miền regx trong phương thức GetRouteData: domainRegex = CreateDomainRegex (Tên miền);
Gorkem Pacaci

Tôi không biết tại sao tôi không thể chạy mã này ... Tôi chỉ nhận được SERVER NOT FOUNDlỗi ... có nghĩa là mã không hoạt động đối với tôi ... bạn có đang đặt bất kỳ cấu hình nào khác không?
Bác sĩ TJ

Tôi đã tạo một Gist của phiên bản của tôi về này gist.github.com/IDisposable/77f11c6f7693f9d181bb
IDisposable

1
@IDis Dùng một lần MvcApplication.DnsSuffix là gì?
HaBo

Chúng tôi chỉ hiển thị tên miền DNS cơ sở trong web.config ... giá trị tiêu biểu sẽ là .example.org
IDis Dùng

4

Để nắm bắt tên miền phụ khi sử dụng API Web , ghi đè Bộ chọn hành động để thêm subdomaintham số truy vấn. Sau đó sử dụng tham số truy vấn tên miền phụ trong các hành động của bộ điều khiển của bạn như sau:

public string Get(string id, string subdomain)

Cách tiếp cận này giúp gỡ lỗi thuận tiện vì bạn có thể chỉ định tham số truy vấn bằng tay khi sử dụng localhost thay vì tên máy chủ thực tế (xem câu trả lời định tuyến MVC5 tiêu chuẩn để biết chi tiết). Đây là mã cho 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);
    }
}

Thay thế Bộ chọn hành động mặc định bằng cách thêm cái này vào WebApiConfig.Register:

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

Bất cứ ai gặp vấn đề trong đó dữ liệu tuyến đường không xuất hiện trên bộ điều khiển API web và kiểm tra Request.GetRouteData bên trong bộ điều khiển không hiển thị giá trị?
Alan Macdonald

3

Có nhưng bạn phải tạo trình xử lý tuyến đường của riêng bạn.

Thông thường, tuyến đường không nhận biết được tên miền vì ứng dụng có thể được triển khai cho bất kỳ tên miền nào và tuyến đường sẽ không quan tâm theo cách này hay cách khác. Nhưng trong trường hợp của bạn, bạn muốn đặt bộ điều khiển và hành động khỏi miền, vì vậy bạn sẽ phải tạo một tuyến tùy chỉnh nhận biết tên miền.


3

Tôi đã tạo thư viện để định tuyến tên miền phụ mà bạn có thể tạo tuyến đường như vậy. Hiện tại nó đang hoạt động cho .NET Core 1.1 và .NET Framework 4.6.1 nhưng sẽ được cập nhật trong tương lai gần. Đây là cách nó hoạt động:
1) Bản đồ tuyến tên miền phụ trong 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) Bộ điều khiển / HomeContoder.cs

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

3) lib đó cũng sẽ cho phép bạn tạo URL và biểu mẫu. Mã số:

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

Sẽ tạo <a href="http://user1.localhost:54575/Home/Index">User home</a> URL được tạo cũng sẽ phụ thuộc vào vị trí và lược đồ máy chủ hiện tại.
Bạn cũng có thể sử dụng trợ giúp html cho BeginFormUrlHelper. Nếu bạn thích, bạn cũng có thể sử dụng tính năng mới được gọi là trình trợ giúp thẻ ( FormTagHelper, AnchorTagHelper)
lib đó chưa có tài liệu nào, nhưng có một số dự án thử nghiệm và mẫu vì vậy hãy thoải mái khám phá nó.


2

Trong ASP.NET Core , máy chủ có sẵn thông qua Request.Host.Host. Nếu bạn muốn cho phép ghi đè máy chủ thông qua tham số truy vấn, trước tiên hãy kiểm traRequest.Query .

Để khiến tham số truy vấn máy chủ truyền vào các URL dựa trên tuyến mới, hãy thêm mã này vào app.UseMvccấu hình tuyến:

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

Và định nghĩa HostPropagationRouternhư thế này:

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

1

Sau khi xác định trình xử lý Tuyến mới sẽ xem xét máy chủ được truyền trong URL , bạn có thể biết ý tưởng về Trình điều khiển cơ sở nhận biết về Trang web mà nó đang được truy cập. Nó trông như thế này:

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 là một giao diện đơn giản:

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

Tôi giới thiệu bạn đến Blog Luke Sampson


1

Nếu bạn đang xem xét cung cấp các khả năng MultiTenancy cho dự án của bạn với các tên miền / tên miền phụ khác nhau cho mỗi người thuê, bạn nên xem SaasKit:

https://github.com/saaskit/saaskit

Ví dụ mã có thể được nhìn thấy ở đây: http://benfoster.io/blog/saaskit-multi-tenancy-ADE-easy

Một số ví dụ sử dụng lõi ASP.NET: http://andrewlock.net/forking-the-pipeline-adding-tenant-specific-files-with-saaskit-in-asp-net-core/

EDIT: Nếu bạn không muốn sử dụng SaasKit trong dự án lõi ASP.NET của mình, bạn có thể xem triển khai định tuyến tên miền của Maarten cho MVC6: https://blog.maartenballiauw.be/post/2015/02/17/domain -routing-and-decving-current-tenant-with-aspnet-mvc-6-aspnet-5.html

Tuy nhiên, các Gist đó không được duy trì và cần được điều chỉnh để hoạt động với phiên bản mới nhất của lõi ASP.NET.

Liên kết trực tiếp tới mã: https://gist.github.com/maartenba/77ca6f9cfef50efa96ec#file-domaintemplateroutebuilderextensions-cs


Không tìm kiếm sự đa dạng - nhưng cảm ơn vì tiền boa!
Dan Esparza

0

Vài tháng trước tôi đã phát triển một thuộc tính giới hạn các phương thức hoặc bộ điều khiển cho các miền cụ thể.

Nó khá dễ sử dụng:

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

Bạn cũng có thể áp dụng nó trực tiếp trên một bộ điều khiển.

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

Hạn chế: bạn không thể có hai tuyến đường giống nhau trên các phương thức khác nhau với các bộ lọc khác nhau. Ý tôi là sau đây có thể đưa ra một ngoại lệ cho tuyến đường trùng lặp:

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

[IsDomain("test2.example.com")]
[HttpGet("/Test")]
public IActionResult Test2(){}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.