Можно ли сделать маршрут ASP.NET MVC на основе поддомен?
Возможно ли иметь маршрут ASP.NET MVC, который использует информацию о поддомене для определения своего маршрута? Например:
- user1.domain.com отправляется в одно место
- user2.domain.com переходит к другому?
Или я могу сделать так, чтобы оба они пошли к одному контроллеру / действию с username
параметр?
10 ответов
Вы можете сделать это, создав новый маршрут и добавив его в коллекцию маршрутов в 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;
}
}
Чтобы захватить поддомен при сохранении стандартных функций маршрутизации 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()
});
}
Наконец, для удобного доступа к субдомену (либо из истинного субдомена, либо из параметра запроса) полезно создать базовый класс Controller с этим Subdomain
имущество:
protected string Subdomain
{
get { return (string)Request.RequestContext.RouteData.Values["subdomain"]; }
}
Это не моя работа, но я должен был добавить это к этому ответу.
Вот отличное решение этой проблемы. 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
))
;
Чтобы захватить поддомен при использовании веб-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()));
Да, но вы должны создать свой собственный обработчик маршрута.
Обычно маршрут не знает о домене, потому что приложение может быть развернуто в любом домене, и маршрут не будет заботиться так или иначе. Но в вашем случае вы хотите основать контроллер и действие вне домена, поэтому вам придется создать собственный маршрут, который знает о домене.
Я создал библиотеку для маршрутизации поддоменов, по которой вы можете создать такой маршрут. В настоящее время он работает для.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
, Если вам нравится, вы также можете использовать новую функцию под названием Tag Helpers (FormTagHelper
, AnchorTagHelper
)
Эта библиотека еще не имеет документации, но есть несколько тестов и примеров проектов, поэтому не стесняйтесь исследовать ее.
В 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);
}
Несколько месяцев назад я разработал атрибут, ограничивающий методы или контроллеры конкретными доменами.
Это довольно легко использовать:
[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(){}
После определения нового обработчика маршрута, который будет проверять хост, переданный в 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();
}
Я имею в виду, вы идете в блог Люка Сэмпсона
Если вы хотите предоставить возможности 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/
РЕДАКТИРОВАТЬ: Если вы не хотите использовать SaasKit в вашем основном проекте ASP.NET, вы можете взглянуть на реализацию Maarten доменной маршрутизации для 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