하위 도메인을 기반으로 ASP.NET MVC 경로를 만들 수 있습니까?


235

하위 도메인 정보를 사용하여 경로를 결정하는 ASP.NET MVC 경로를 가질 수 있습니까? 예를 들면 다음과 같습니다.

  • user1 .domain.com 이 한 곳으로 이동
  • user2 .domain.com 이 다른 사이트로 이동합니까?

또는 둘 다 username매개 변수 가있는 동일한 컨트롤러 / 작업으로 만들 수 있습니까?


다중 테넌트 응용 프로그램에 대해서도 비슷한 종류의 작업을 구현했지만 사용자 지정 Route 클래스 대신 추상 기본 컨트롤러를 사용했습니다. 내 블로그 게시물이 여기에 있습니다 .
Luke Sampson

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/…
Ronnie

답변:


168

global.asax의 RegisterRoutes에있는 routes 컬렉션에 새 경로를 만들어 추가하면됩니다. 다음은 맞춤 경로의 매우 간단한 예입니다.

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
자세한 샘플을 가져 주셔서 감사하지만 Global.asax에서 .Add를 실행하는 방법을 따르지 않습니다.
justSteve

4
SubdomainRoute 경로를 호출하여 다음과 같이 첫 번째 경로로 추가했습니다. route.Add (new SubdomainRoute ());
Jeff Handley

6
이 방법으로 가능한 하위 도메인 목록을 하드 코딩해야합니까?
Maxim V. Pavlov

2
아니요, "하위 도메인"과 같은 데이터베이스 필드를 추가하여 하위 도메인이 특정 사용자 또는 기타 다른 도메인에있을 것으로 예상하고 하위 도메인을 조회 할 수 있습니다.
Ryan Hayes

1
누구든지 webforms 버전을 추천 할 수 있습니까?
MatthewT

52

표준 MVC5 라우팅 기능을 유지하면서 하위 도메인캡처 하려면 다음 SubdomainRoute클래스를 사용하십시오 Route.

또한, SubdomainRoute선택적으로 지정 될 수있게하는 서브 도메인 쿼리 매개 변수를 만들어 sub.example.com/foo/barexample.com/foo/bar?subdomain=sub동등한. 이를 통해 DNS 하위 도메인을 구성하기 전에 테스트 할 수 있습니다. 검색어 매개 변수 (사용중인 경우)는 Url.Action등에 의해 생성 된 새 링크를 통해 전파됩니다 .

또한 query 매개 변수를 사용하면 netsh 를 사용하여 구성하거나 Administrator로 실행할 필요없이 Visual Studio 2013에서 로컬 디버깅을 수행 할 수 있습니다 . 기본적으로 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메소드에서 다음 메소드를 호출하십시오 .RegisterRoutesMapRoute

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속성 을 사용하여 Controller 기본 클래스를 만드는 것이 좋습니다 .

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

1
하위 도메인을 항상 경로 값으로 사용할 수 있도록 코드를 업데이트했습니다. 이는 하위 도메인에 대한 액세스를 단순화합니다.
Edward Brey

나는 이것을 좋아한다. 매우 간단하고 내 프로젝트에 충분합니다.

이것은 좋은 대답입니다. 이것이 경로 속성으로 작동하는 방법이 있습니까? "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", new DomainRoute ( "user} .localhost", "", new {controller = "Home", action = "IndexForUser", user = "u1 "})); 또한 홈페이지를 캐시합니다. 이것은 생성 된 정규식 때문입니다. 이 문제를 해결하려면 DomainRoute.cs에서 CreateRegex 메서드의 복사본을 만들어 CreateDomainRegex로 이름을 지정하고이 줄의 *를 +로 변경하십시오. source = source.Replace ( "}", @ "> ([a- zA-Z0-9 _] *)) "); GetRouteData 메소드에서 도메인 regx에 대해이 새 메소드를 사용하십시오. domainRegex = CreateDomainRegex (Domain);
Gorkem Pacaci

이 코드를 실행할 수없는 이유를 모르겠습니다 ... SERVER NOT FOUND오류가 발생했습니다 ... 코드가 작동하지 않음을 의미합니다. 다른 구성이나 무언가를 설정하고 있습니까?!
Dr TJ


1
@IDisposable MvcApplication.DnsSuffix 란 무엇입니까?
HaBo

우리는 단지의 Web.config에 기본 DNS 도메인을 노출 ... 전형적인 값은 .example.org 것
는 IDisposable

4

Web API를 사용할 때 하위 도메인을 캡처하려면 조치 선택기를 대체하여 subdomain조회 매개 변수 를 삽입하십시오 . 그런 다음 컨트롤러의 작업에서 하위 도메인 쿼리 매개 변수를 다음과 같이 사용하십시오.

public string Get(string id, string subdomain)

이 방법은 실제 호스트 이름 대신 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()));

웹 API 컨트롤러에 경로 데이터가 표시되지 않고 컨트롤러 내부의 Request.GetRouteData를 검사하는 데 문제가있는 사람은 값이 표시되지 않습니까?
Alan Macdonald

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) 해당 lib를 사용하면 URL과 양식을 생성 할 수도 있습니다. 암호:

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

생성 <a href="http://user1.localhost:54575/Home/Index">User home</a> 또한 현재 호스트 위치 및 스키마에 따라 달라집니다 생성 된 URL을. 및에
HTML 도우미를 사용할 수도 있습니다 . 원하는 경우 태그 헬퍼 ( , ) 라는 새로운 기능을 사용할 수도 있습니다. 해당 lib에는 아직 문서가 없지만 일부 테스트 및 샘플 프로젝트가 있으므로 자유롭게 탐색하십시오.BeginFormUrlHelperFormTagHelperAnchorTagHelper


2

에서 ASP.NET 코어 , 호스트를 통해 사용할 수 있습니다 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/

편집 : ASP.NET 핵심 프로젝트에서 SaasKit을 사용하고 싶지 않다면 MVC6에 대한 Maarten의 도메인 라우팅 구현을 살펴볼 수 있습니다 : https://blog.maartenballiauw.be/post/2015/02/17/domain 라우팅 및 해결-현재 테넌트 -with-aspnet-mvc-6-aspnet-5.html

그러나 이러한 요점은 유지 관리되지 않으며 최신 릴리스의 ASP.NET 코어에서 작동하도록 조정해야합니다.

코드에 직접 링크 : https://gist.github.com/maartenba/77ca6f9cfef50efa96ec#file-domaintemplateroutebuilderextensions-cs


멀티 테넌시를 찾지 않고 팁을 주셔서 감사합니다!
Dan Esparza

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(){}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.