Чи можливо зробити маршрут ASP.NET MVC на основі субдомену?


235

Чи можливо мати маршрут ASP.NET MVC, який використовує інформацію про піддомен для визначення свого маршруту? Наприклад:

  • user1 .domain.com йде в одне місце
  • user2 .domain.com переходить до іншого?

Або я можу зробити так, щоб обидва вони перейшли до одного контролера / дії з usernameпараметром?


Я реалізував подібний предмет для багатожитлових додатків, але використовуючи абстрактний базовий контролер, а не спеціальний клас Route. Мій пост у цьому блозі є тут .
Люк Сампсон

6
Не забудьте врахувати такий підхід: http://blog.tonywilliams.me.uk/asp-net-mvc-2-routing-subdomains-to-areas Я вважав, що це краще для впровадження багатостороннього зв’язку в моє додаток, ніж інші відповіді , оскільки зони MVC - це приємний спосіб організувати контролери та погляди, характерні для орендарів.
trebormf

2
@trebormf - Я думаю, ви повинні додати це як відповідь, саме це я і в кінцевому підсумку використовував як основу для свого рішення.
Shagglez

@Shagglez - Дякую Це була відповідь, але модератор перетворив її на коментар із причин, яких я не можу зрозуміти.
trebormf

5
Наче Тоні було зламано. Ось такий, який працював для мене: blog.tonywilliams.me.uk/…
Ронні Овербі

Відповіді:


168

Ви можете це зробити, створивши новий маршрут і додавши його до колекції маршрутів у RegisterRoutes на своєму global.asax. Нижче наведено дуже простий приклад користувальницького маршруту:

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
Дякуємо за детальний зразок, але я не стежу за тим, як виконати .Add від Global.asax.
простоSteve

4
Я назвав маршрут SubdomainRoute і додав його в якості першого маршруту на зразок цього: route.Add (новий SubdomainRoute ());
Джефф Хендлі

6
Чи вимагає такого підходу жорстке кодування списку можливих субдоменів?
Павлов Максим Вікторович

2
Ні, ви можете додати поле бази даних, яке називається на кшталт "субдомен", яке ви будете тим, що очікуєте, що субдомен буде певним користувачем, або будь-яким іншим, тоді просто зробіть пошук піддомену.
Райан Хейс

1
Хтось може порекомендувати версію веб-форми?
MatthewT

52

Щоб захопити субдомен при збереженні стандартних функцій маршрутизації MVC5 , використовуйте такий SubdomainRouteклас, похідний від Route.

Крім того, SubdomainRouteдозволяє субдомен необов'язково визначати як параметр запиту , внесення sub.example.com/foo/barта example.com/foo/bar?subdomain=subеквівалент. Це дозволяє протестувати до налаштування субдоменів DNS. Параметр запиту (коли використовується) поширюється за допомогою нових посилань, згенерованих Url.Actionтощо.

Параметр запиту також дозволяє локальну налагодження з Visual Studio 2013 без необхідності конфігурувати з netsh або запускатись як адміністратор . За замовчуванням IIS Express прив'язується до localhost лише коли не підвищений; він не буде прив'язуватися до синонімічних імен хостів, таких як 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);
    }
}

Для зручності, викличте наступний MapSubdomainRouteметод з вашого RegisterRoutesметоду так само , як ви б звичайний старий 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()
    });
}

Нарешті, для зручного доступу до субдомену (або з істинного субдомену або параметра запиту) корисно створити базовий клас контролера з цим Subdomainвластивістю:

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

1
Я оновив код, щоб субдомен завжди був доступний як значення маршруту. Це спрощує доступ до піддомену.
Едвард Брей

Мені подобається це. Дуже просто, і більш ніж достатньо для мого проекту.
Незабаром

Це чудова відповідь. Чи існує спосіб для цього працювати з атрибутами маршруту? Я намагаюся зробити цю роботу для таких шляхів, як "subdomain.domain.com/portal/register", а використання атрибутів полегшило б це.
perfect_element

@perfect_element - маршрути атрибутів не розширюються, як і маршрути на основі конвенції. Єдиний спосіб зробити щось подібне - створити власну систему маршрутизації атрибутів.
NightOwl888

23

Це не моя робота, але мені довелося додати цю відповідь.

Ось чудове рішення цієї проблеми. Maartin Balliauw написав код, який створює клас DomainRoute, який можна використовувати дуже аналогічно звичайній маршрутизації.

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

Використання зразка було б таким ...

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
У цьому рішенні є проблема. Скажімо, ви хочете обробляти субдомени як різні користувачі: route.Add ("SD", новий DomainRoute ("користувач} .localhost", "", новий {controller = "Home", action = "IndexForUser", user = "u1 "})); Він також кешує домашню сторінку. Це через генерований регулярний вираз. Щоб виправити це, ви можете зробити копію методу CreateRegex в DomainRoute.cs, назвати його CreateDomainRegex, змінити * у цьому рядку на +: source = source.Replace ("}", @ "> ([a- zA-Z0-9 _] *)) "); і використовувати цей новий метод для regx домену в методі GetRouteData: domainRegex = CreateDomainRegex (Домен);
Горкем Пакачі

Я не знаю, чому я не можу запустити цей код ... Я просто отримую SERVER NOT FOUNDпомилку ... значить, код не працює для мене ... Ви встановлюєте якусь іншу конфігурацію чи щось ?!
Доктор TJ

Я створив історію
IDisposable

1
@IDisposable що таке MvcApplication.DnsSuffix?
HaBo

Ми просто виставляємо базовий домен DNS в web.config ... типовим значенням буде .example.org
IDisposable

4

Щоб захопити субдомен під час використання веб-API , замініть селектор дій, щоб ввести subdomainпараметр запиту. Потім використовуйте параметр запиту піддомену в таких діях контролерів:

public string Get(string id, string subdomain)

Цей підхід робить налагодження зручним, оскільки ви можете вказати параметр запиту вручну, використовуючи localhost замість фактичного імені хоста ( детальну інформацію див. У стандартній відповіді на маршрутизацію MVC5 ). Це код для селектора дій:

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

Замініть селектор дій за замовчуванням, додавши це до WebApiConfig.Register:

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

У когось проблеми, коли дані маршруту не відображаються на контролері веб-API та перевіряють Request.GetRouteData всередині контролера, значення не відображається?
Алан Макдональд

3

Так, але ви повинні створити власний обробник маршрутів.

Зазвичай маршрут не відомий домену, оскільки додаток може бути розгорнуто до будь-якого домену, і маршрут не буде турбуватись так чи інакше. Але у вашому випадку ви хочете базувати контролер і діяти над доменом, тому вам доведеться створити користувацький маршрут, який відомий домену.


3

Я створив бібліотеку для маршрутизації субдоменів, в якій можна створити такий маршрут. Зараз він працює для .NET Core 1.1 та .NET Framework 4.6.1, але буде оновлений найближчим часом. Ось як це працює:
1) Позначте маршрут субдомена в 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) Контролери / HomeController.cs

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

3) Ця вкладка також дозволить генерувати URL-адреси та форми. Код:

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

Згенерована створена <a href="http://user1.localhost:54575/Home/Index">User home</a> URL-адреса також залежатиме від поточного місцезнаходження та схеми хоста.
Ви також можете використовувати html-помічники для BeginFormта UrlHelper. Якщо вам подобається, ви можете також скористатися новою функцією під назвою helpers helg ( FormTagHelper, AnchorTagHelper) У
цієї lib ще немає жодної документації, але є кілька проектів тестів та зразків, тому сміливо вивчайте її.


2

У ASP.NET Core хост доступний через Request.Host.Host. Якщо ви хочете дозволити переопределення хоста за допомогою параметра запиту, спочатку перевірте Request.Query.

Щоб змусити параметр запиту хоста поширюватися на нові URL-адреси на основі маршруту, додайте цей код до app.UseMvcконфігурації маршруту:

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

І визначте 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);
}

1

Визначивши новий обробник маршруту, який би розглядав хост, що передається в URL-адресі , можна перейти до ідеї базового контролера, який обізнаний про веб-сайт, до якого отримують доступ. Це виглядає приблизно так:

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 це простий інтерфейс:

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

Я посилаюсь, ви переходите до блогу Люка Сампсона


1

Якщо ви хочете надати можливості MultiTenancy своєму проекту з різними доменами / субдоменами для кожного орендаря, вам слід ознайомитися з SaasKit:

https://github.com/saaskit/saaskit

Приклади коду можна побачити тут: http://benfoster.io/blog/saaskit-multi-tenancy-made-easy

Деякі приклади використання ядра ASP.NET: http://andrewlock.net/forking-the-pipeline-adding-tenant-specific-files-with-saaskit-in-asp-net-core/

EDIT: Якщо ви не хочете використовувати SaasKit у своєму базовому проекті ASP.NET, ви можете ознайомитись з реалізацією Maarten маршрутизації домену для MVC6: https://blog.maartenballiauw.be/post/2015/02/17/domain -проведення та вирішення-поточного-орендаря-з-aspnet-mvc-6-aspnet-5.html

Однак ці Gists не підтримуються, і їх потрібно налаштувати для роботи з останньою версією ядра ASP.NET.

Пряме посилання на код: https://gist.github.com/maartenba/77ca6f9cfef50efa96ec#file-domaintemplateroutebuilderextensions-cs


Не шукаю багатожиття - але дякую за пораду!
Дан Еспарза

0

Кілька місяців тому я розробив атрибут, який обмежує методи чи контролери на конкретні домени.

Це досить просто у використанні:

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

Ви також можете застосувати його безпосередньо на контролері.

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

Обмеження: можливо, ви не зможете мати два однакові маршрути для різних методів з різними фільтрами. Я маю на увазі, що для дублювання маршруту може бути виняток:

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

[IsDomain("test2.example.com")]
[HttpGet("/Test")]
public IActionResult Test2(){}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.