È possibile creare una route MVC ASP.NET in base a un sottodominio?
-
07-07-2019 - |
Domanda
È possibile avere una route ASP.NET MVC che utilizza le informazioni del sottodominio per determinarne la route? Ad esempio:
- user1 .domain.com va in un posto
- user2 .domain.com va in un altro?
Oppure posso farlo in modo che entrambi vadano sullo stesso controller / azione con un parametro username
?
Soluzione
Puoi farlo creando una nuova rotta e aggiungendola alla raccolta delle rotte in RegisterRoutes in global.asax. Di seguito è riportato un esempio molto semplice di un percorso personalizzato:
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;
}
}
Altri suggerimenti
Per acquisire il sottodominio mantenendo le funzionalità di routing MVC5 standard , utilizzare la seguente SubdomainRoute
classe derivata da Route
.
Inoltre, sub.example.com/foo/bar
consente facoltativamente di specificare il sottodominio come parametro di query , rendendo example.com/foo/bar?subdomain=sub
e Url.Action
equivalenti. Ciò consente di eseguire il test prima di configurare i sottodomini DNS. Il parametro di query (quando in uso) viene propagato attraverso nuovi collegamenti generati da MapSubdomainRoute
, ecc.
Il parametro query abilita anche il debug locale con Visual Studio 2013 senza dover configurare con netsh o eseguire come amministratore . Per impostazione predefinita, IIS Express si lega a localhost solo quando non elevato; non si legherà nemmeno a nomi host come 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);
}
}
Per comodità, chiama il seguente RegisterRoutes
metodo dal tuo MapRoute
metodo come faresti con il vecchio Subdomain
:
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()
});
}
Infine, per accedere comodamente al sottodominio (da un sottodominio vero o da un parametro di query), è utile creare una classe base del controller con questa proprietà <=>:
protected string Subdomain
{
get { return (string)Request.RequestContext.RouteData.Values["subdomain"]; }
}
Questo non è il mio lavoro, ma ho dovuto aggiungerlo a questa risposta.
Ecco un'ottima soluzione a questo problema. Maartin Balliauw ha scritto codice che crea una classe DomainRoute che può essere utilizzata in modo molto simile al normale routing.
http: //blog.maartenballiauw. essere / post / 2009/05/20 / ASPNET-MVC-Domain-Routing.aspx
L'uso del campione sarebbe come questo ...
routes.Add("DomainRoute", new DomainRoute(
"{customer}.example.com", // Domain with parameters
"{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = "" } // Parameter defaults
))
;
Per acquisire il sottodominio quando si utilizza API Web , sovrascrivere il Selettore azioni per iniettare un parametro di query subdomain
. Quindi utilizzare il parametro di query del sottodominio nelle azioni dei controller in questo modo:
public string Get(string id, string subdomain)
Questo approccio rende conveniente il debug poiché è possibile specificare manualmente il parametro di query quando si utilizza localhost invece del nome host effettivo (consultare risposta al routing MVC5 standard per dettagli). Questo è il codice per 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);
}
}
Sostituisci il Selettore azioni predefinito aggiungendolo a WebApiConfig.Register
:
config.Services.Replace(typeof(IHttpActionSelector), new SubdomainActionSelector(config.Services.GetActionSelector()));
Sì, ma devi creare il tuo gestore di percorsi.
In genere la route non è a conoscenza del dominio perché l'applicazione potrebbe essere distribuita a qualsiasi dominio e la route non si occuperebbe in un modo o nell'altro. Ma nel tuo caso vuoi basare il controller e l'azione fuori dal dominio, quindi dovrai creare un percorso personalizzato che sia a conoscenza del dominio.
Ho creato libreria per il routing di sottodomini che puoi creare tale percorso. Funziona attualmente per .NET Core 1.1 e .NET Framework 4.6.1 ma verrà aggiornato nel prossimo futuro. Funziona così:
1) Mappa il percorso del sottodominio in 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) Controller / HomeController.cs
public IActionResult Index(string username)
{
//code
}
3) Quella lib ti permetterà anche di generare URL e moduli. Codice:
@Html.ActionLink("User home", "Index", "Home" new { username = "user1" }, null)
Genera <a href="http://user1.localhost:54575/Home/Index">User home</a>
L'URL generato dipenderà anche dalla posizione e dallo schema dell'host corrente.
Puoi anche utilizzare gli helper HTML per BeginForm
e UrlHelper
. Se lo desideri, puoi anche utilizzare una nuova funzione chiamata helper tag (FormTagHelper
, AnchorTagHelper
)
Quella lib non ha ancora documentazione, ma ci sono alcuni progetti di test e campioni, quindi sentiti libero di esplorarla.
In ASP.NET Core , l'host è disponibile tramite Request.Host.Host
. Se si desidera consentire la sostituzione dell'host tramite un parametro di query, selezionare prima Request.Query
.
Per fare in modo che un parametro della query host si propaghi in nuovi URL basati su route, aggiungi questo codice alla app.UseMvc
configurazione route:
routes.Routes.Add(new HostPropagationRouter(routes.DefaultHandler));
E definisci HostPropagationRouter
in questo modo:
/// <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);
}
Dopo aver definito un nuovo gestore di route che guarderebbe l'host passato nell'URL , puoi andare con l'idea di un controller di base che è a conoscenza del sito & # 8217; a cui si accede per. Sembra così:
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
è un'interfaccia semplice:
public interface ISiteProvider {
void Initialise(string host);
Site GetCurrentSite();
}
Mi riferisco a Blog di Luke Sampson
Se stai cercando di offrire funzionalità MultiTenancy al tuo progetto con domini / sottodomini diversi per ciascun inquilino, dovresti dare un'occhiata a SaasKit:
https://github.com/saaskit/saaskit
Esempi di codice sono disponibili qui: http://benfoster.io/blog / saaskit-multi-tenancy-made-facile
Alcuni esempi che utilizzano il core ASP.NET: http://andrewlock.net/forking-the-pipeline-adding-tenant-specific-files-with-saaskit-in-asp-net-core/
EDIT: Se non vuoi usare SaasKit nel tuo progetto principale ASP.NET puoi dare un'occhiata all'implementazione di Maarten del routing del dominio per MVC6: https://blog.maartenballiauw.be/post/2015/02/ 17 / dominio di routing-e-risolvere-corrente-tenant-con-ASPNET-MVC-6-aspnet-5.html
Tuttavia questi Gists non sono mantenuti e devono essere ottimizzati per funzionare con l'ultima versione del core ASP.NET.
Link diretto al codice: https://gist.github.com/ maartenba / 77ca6f9cfef50efa96ec # file-domaintemplateroutebuilderextensions-CS
Pochi mesi fa ho sviluppato un attributo che limita metodi o controller a domini specifici.
È abbastanza facile da usare:
[IsDomain("localhost","example.com","www.example.com","*.t1.example.com")]
[HttpGet("RestrictedByHost")]
public IActionResult Test(){}
Puoi anche applicarlo direttamente su un controller.
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
}
}
Limitazione: potresti non essere in grado di avere due stessi percorsi su metodi diversi con filtri diversi Intendo che quanto segue può generare un'eccezione per il percorso duplicato:
[IsDomain("test1.example.com")]
[HttpGet("/Test")]
public IActionResult Test1(){}
[IsDomain("test2.example.com")]
[HttpGet("/Test")]
public IActionResult Test2(){}