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

Возможно ли иметь маршрут ASP.NET MVC, который использует информацию о субдомене для определения маршрута? Например:

  • user1.domain.com отправляется в одно место
  • user2.domain.com переходит к другому?

Или, могу ли я сделать так, чтобы оба они переходили к одному и тому же контроллеру/действию с параметром username?

Ответ 1

Вы можете сделать это, создав новый маршрут и добавив его в коллекцию маршрутов в 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;
    }
}

Ответ 2

Чтобы захватить субдомен, сохраняя стандартные функции маршрутизации MVC5, используйте следующий класс SubdomainRoute, полученный из Route.

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

Параметр запроса также позволяет локальную отладку с Visual Studio 2013 без необходимости настраивать с помощью netsh или запускаться как администратор. По умолчанию IIS Express привязывается только к локальному хосту при невыполнении; он не будет связываться с синонимичными именами хостов, такими как 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"]; }
}

Ответ 3

Это не моя работа, но я должен был добавить ее в этот ответ.

Вот отличное решение этой проблемы. 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 
))

;

Ответ 4

Чтобы захватить субдомен при использовании Веб-API, переопределите селектор действий, чтобы ввести параметр запроса subdomain. Затем используйте параметры запроса поддомена в действиях ваших контроллеров, например:

public string Get(string id, string subdomain)

Этот подход упрощает отладку, поскольку вы можете указать параметр запроса вручную, если вместо имени хоста (localcode) используется localhost (см. стандартный запрос маршрутизации MVC5), Это код для 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);
    }
}

Замените селектор действий по умолчанию, добавив его в WebApiConfig.Register:

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

Ответ 5

Да, но вам нужно создать собственный обработчик маршрута.

Как правило, маршрут не знает о домене, потому что приложение может быть развернуто в любом домене, и маршрут не будет волновать так или иначе. Но в вашем случае вы хотите основать контроллер и действие вне домена, поэтому вам нужно будет создать собственный маршрут, который будет знать о домене.

Ответ 6

Я создал библиотеку для маршрутизации поддоменов, которую вы можете создать таким маршрутом. Он работает в настоящее время для .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. Если вам нравится, вы также можете использовать новую функцию, называемую помощниками тегов (FormTagHelper, AnchorTagHelper)
У этого lib еще нет документации, но есть некоторые тесты и примеры проектов, поэтому не стесняйтесь исследовать их.

Ответ 7

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

Ответ 8

После определения нового обработчика маршрута, который будет смотреть на хост, переданный в 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();
}

Я отсылаю вас к Luke Sampson Blog

Ответ 9

Если вы ищете возможности 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, вы можете взглянуть на реализацию маршрутизации домена в Marten для MVC6: https://blog.maartenballiauw.be/post/2015/02/17/domain-routing-and-resolving-current-tenant-with-aspnet-mvc-6-aspnet-5.html

Однако эти Gists не поддерживаются и нуждаются в настройке для работы с последней версией ядра ASP.NET.

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

Ответ 10

Несколько месяцев назад я разработал атрибут, который ограничивает методы или контроллеры определенными доменами.

Это довольно легко использовать:

[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(){}