ASP.NET Core의 컨트롤러 작업에 라우팅

작성자: Ryan Nowak, Kirk LarkinRick Anderson

참고 항목

이 문서의 최신 버전은 아닙니다. 현재 릴리스는 이 문서의 ASP.NET Core 8.0 버전을 참조 하세요.

ASP.NET Core 컨트롤러는 라우팅 미들웨어를 사용하여 들어오는 요청의 URL을 매칭하고 작업에 매핑합니다. 경로 템플릿:

  • 시작 시 또는 특성에서 Program.cs 정의됩니다.
  • URL 경로가 작업과 일치되는 방식을 설명합니다.
  • 링크에 대한 URL을 생성하는 데 사용됩니다. 생성된 링크는 일반적으로 응답에서 반환됩니다.

작업은 규칙 기반 라우팅 또는 특성 라우팅입니다. 컨트롤러 또는 작업에 경로를 배치하면 해당 경로가 특성 라우팅됩니다. 자세한 내용은 혼합 라우팅을 참조하세요.

이 문서:

  • MVC와 라우팅 간의 상호 작용에 대해 설명합니다.
    • 일반적인 MVC 앱이 라우팅 기능을 사용하는 방법입니다.
    • 다음을 모두 다룹니다.
    • 고급 라우팅에 대한 자세한 내용은 라우팅을 참조하세요.
  • 엔드포인트 라우팅이라는 기본 라우팅 시스템을 나타냅니다. 호환성을 위해 이전 버전의 라우팅에서 컨트롤러를 사용할 수 있습니다. 지침은 2.2-3.0 마이그레이션 가이드를 참조하세요.

규칙 기반 경로 설정

ASP.NET Core MVC 템플릿은 다음과 유사한 규칙 기반 라우팅 코드를 생성합니다.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Home/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();

MapControllerRoute는 단일 경로를 만드는 데 사용됩니다. 단일 경로는 default 경로로 지칭됩니다. 컨트롤러 및 뷰를 사용하는 대부분의 앱은 default 경로와 유사한 경로 템플릿을 사용합니다. REST API는 특성 라우팅을 사용해야 합니다.

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

경로 템플릿 "{controller=Home}/{action=Index}/{id?}"는 다음을 수행합니다.

  • /Products/Details/5와 같은 URL 경로를 일치시킵니다.

  • 경로를 토큰화하여 경로 값 { controller = Products, action = Details, id = 5 }를 추출합니다. 경로 값을 추출하면 앱에 ProductsController라는 컨트롤러와 Details 작업이 포함된 경우 일치하는 항목이 검색됩니다.

    public class ProductsController : Controller
    {
        public IActionResult Details(int id)
        {
            return ControllerContext.MyDisplayRouteInfo(id);
        }
    }
    

    MyDisplayRouteInfoRick.Docs.Samples.RouteInfo NuGet 패키지가 제공하며 경로 정보를 표시합니다.

  • /Products/Details/5 모델은 id = 5 값을 바인딩하여 id 매개 변수를 5로 설정합니다. 자세한 내용은 모델 바인딩을 참조하세요.

  • {controller=Home}Home을 기본 controller로 정의합니다.

  • {action=Index}Index을 기본 action로 정의합니다.

  • {id?}? 문자는 id를 선택 항목으로 정의합니다.

    • 기본 및 선택적 경로 매개 변수는 매칭을 위해 URL 경로에 반드시 있어야 하는 것은 아닙니다. 경로 템플릿 구문에 대한 자세한 설명은 경로 템플릿 참조를 참조하세요.
  • URL 경로 /와 일치시킵니다.

  • 경로 값 { controller = Home, action = Index }를 생성합니다.

controlleraction에 대한 값은 기본값을 사용합니다. id는 URL 경로에 해당 세그먼트가 없기 때문에 값을 생성하지 않습니다. /HomeControllerIndex 작업이 있는 경우에만 일치됩니다.

public class HomeController : Controller
{
    public IActionResult Index() { ... }
}

위의 컨트롤러 정의와 경로 템플릿을 사용하면 다음 URL 경로에 대해 HomeController.Index 작업이 실행됩니다.

  • /Home/Index/17
  • /Home/Index
  • /Home
  • /

URL 경로 /는 경로 템플릿 기본 Home 컨트롤러 및 Index 작업을 사용합니다. URL 경로 /Home은 경로 템플릿 기본 Index 작업을 사용합니다.

편의 메서드인 MapDefaultControllerRoute를 사용하여:

app.MapDefaultControllerRoute();

다음으로 바꿉니다.

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

Important

라우팅은 UseRoutingUseEndpoints 미들웨어를 사용하여 구성합니다. 컨트롤러를 사용하려면 다음을 수행합니다.

앱은 일반적으로 UseRouting 또는 UseEndpoints를 호출할 필요가 없습니다. WebApplicationBuilderUseRoutingUseEndpoints를 통해 Program.cs에 추가된 미들웨어를 래핑하는 미들웨어 파이프라인을 구성합니다. 자세한 내용은 ASP.NET Core의 라우팅을 참조하세요.

규칙 기반 라우팅

규칙 기반 라우팅은 컨트롤러 및 뷰에서 사용됩니다. default 경로인:

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

위의 예제는 ‘규칙 기반 경로’의 예입니다. URL 경로에 대한 ‘규칙’을 설정하기 때문에 ‘규칙 기반 라우팅’이라고 부릅니다.

  • 첫 번째 경로 세그먼트 {controller=Home}은 컨트롤러 이름에 매핑됩니다.
  • 두 번째 세그먼트 {action=Index}작업 이름에 매핑됩니다.
  • 세 번째 세그먼트 {id?}는 선택적 id에 사용됩니다. {id?}?는 이러한 항목을 선택 사항으로 지정합니다. id는 모델 엔터티에 매핑하는 데 사용됩니다.

default 경로를 사용하면 다음 URL 경로

  • /Products/ListProductsController.List 작업에 매핑됩니다.
  • /Blog/Article/17BlogController.Article에 매핑되고 일반적으로 모델은 id 매개 변수를 17에 바인딩합니다.

이 매핑에는 다음이 적용됩니다.

  • 컨트롤러 및 작업 이름만을 기준으로 합니다.
  • 네임스페이스, 소스 파일 위치 또는 메서드 매개 변수를 기준으로 하지 않습니다.

기본 경로와 함께 규칙 기반 라우팅을 사용하면 각 작업마다 새 URL 패턴을 만들지 않고도 신속하게 앱을 만들 수 있습니다. CRUD 스타일 작업을 포함하는 앱의 경우, 컨트롤러 간의 URL이 일관되게 유지됩니다.

  • 코드를 간소화하는 데 유용합니다.
  • UI를 예측하기가 더 쉽습니다.

Warning

위의 코드에서 id는 경로 템플릿에서 선택 항목으로 정의됩니다. URL의 일부로 제공되는 선택적 ID 없이 작업을 실행할 수 있습니다. 일반적으로 URL에서 id가 생략되는 경우는 다음과 같습니다.

  • id가 모델 바인딩에 따라 0으로 설정됩니다.
  • 데이터베이스에서 id == 0과 일치하는 엔터티를 찾을 수 없습니다.

특성 라우팅을 사용하면 일부 작업에는 필요하고 다른 작업에는 필요하지 않은 ID를 만들기 위해 세밀하게 제어할 수 있습니다. 일반적으로 id 같은 선택적 매개 변수가 올바른 사용법으로 나타날 가능성이 있는 경우 설명서에 이러한 선택적 매개 변수가 포함됩니다.

대부분의 앱은 URL이 읽을 수 있고 의미 있도록 기본적이고 설명적인 라우팅 체계를 선택해야 합니다. 기본 기존 경로인 {controller=Home}/{action=Index}/{id?}는:

  • 기본적이고 서술적인 라우팅 체계를 지원합니다.
  • UI 기반 앱에 대한 유용한 시작점입니다.
  • 많은 웹 UI 앱에 필요한 유일한 경로 템플릿입니다. 규모가 큰 웹 UI 앱의 경우 영역을 사용하는 또 다른 경로를 사용하는 경우가 많습니다.

MapControllerRouteMapAreaRoute :

  • 호출된 순서를 기준으로 해당 엔드포인트에 순서 값을 자동으로 할당합니다.

ASP.NET Core의 엔드포인트 라우팅은 다음과 같습니다.

  • 경로 개념이 없습니다.
  • 확장성 실행 순서를 보장하지 않으며 모든 엔드포인트가 한 번에 처리됩니다.

로깅을 사용하도록 설정하여 Route와 같은 기본 제공 라우팅 구현에서 요청과 일치시키는 방법을 확인하세요.

특성 라우팅은 이 문서의 뒷부분에서 설명합니다.

다중 규칙 기반 경로

MapControllerRouteMapAreaControllerRoute에 호출을 더 추가하여 여러 규칙 경로를 구성할 수 있습니다. 이렇게 하면 여러 규칙을 정의하거나 다음과 같이 특정 작업에만 사용되는 규칙 기반 경로를 추가할 수 있습니다.

app.MapControllerRoute(name: "blog",
                pattern: "blog/{*article}",
                defaults: new { controller = "Blog", action = "Article" });
app.MapControllerRoute(name: "default",
               pattern: "{controller=Home}/{action=Index}/{id?}");

위의 코드에서 blog 경로는 전용 규칙 기반 경로입니다. 다음과 같은 이유 때문에 전용 규칙 기반 경로라고 합니다.

controlleraction은 경로 템플릿 "blog/{*article}"에 매개 변수로 표시되지 않기 때문입니다.

  • 기본값 { controller = "Blog", action = "Article" }만 지정할 수 있습니다.
  • 이 경로는 항상 작업 BlogController.Article에 매핑됩니다.

/Blog, /Blog/Article/Blog/{any-string}는 블로그 경로와 일치하는 유일한 URL 경로입니다.

위의 예제는 다음과 같습니다.

  • blog 경로는 먼저 추가되므로 default 경로보다 일치하는지 먼저 검색됩니다.
  • URL의 일부로 아티클 이름을 포함하는 것이 일반적인 동적 필드 스타일 라우팅의 예입니다.

Warning

ASP.NET Core에서 라우팅은 다음을 수행하지 않습니다.

  • ‘경로’라는 개념을 정의합니다. UseRouting은 경로 일치를 미들웨어 파이프라인에 추가합니다. UseRouting 미들웨어는 앱에 정의된 엔드포인트 집합을 확인하고 요청을 기준으로 가장 일치하는 엔드포인트를 선택합니다.
  • IRouteConstraint 또는 IActionConstraint와 같은 확장성의 실행 순서를 보장합니다.

라우팅에 대한 참조 자료는 라우팅을 참조하세요.

규칙 기반 라우팅 순서

규칙 기반 라우팅은 앱에서 정의된 작업 및 컨트롤러의 조합만 일치시킵니다. 이것은 규칙 기반 경로가 중복되는 경우를 간소화하기 위한 것입니다. MapControllerRoute, MapDefaultControllerRouteMapAreaControllerRoute는 호출된 순서를 기준으로 해당 엔드포인트에 순서 값을 자동으로 할당합니다. 이전에 표시된 경로부터 일치 항목을 먼저 검색합니다. 규칙 기반 라우팅은 순서에 의존적입니다. 일반적으로 영역을 사용하는 경로가 영역을 사용하지 않는 경로에 비해 구체적이기 때문에 앞부분에 배치되어야 합니다. {*article}과 같은 catch-all 경로 매개 변수가 있는 전용 규칙 기반 경로는 경로를 너무 탐욕적으로 만들 수 있습니다. 즉, 다른 경로와 일치시키려고 하는 URL과 일치하게 됩니다. 탐욕적 일치를 방지하려면 탐욕적 경로를 경로 테이블에 배치합니다.

Warning

catch-all 매개 변수는 라우팅의 버그로 인해 경로와 일치하지 않을 수 있습니다. 이 버그의 영향을 받는 앱은 다음과 같은 특징이 있습니다.

  • catch-all 경로(예: {**slug}")
  • catch-all 경로가 일치해야 하는 요청과 일치하지 않습니다.
  • 다른 경로를 제거하면 catch-all 경로 작동이 시작됩니다.

이 버그에 해당하는 사례는 GitHub 버그 1867716579를 참조하세요.

이 버그에 대한 옵트인 픽스가 .NET Core 3.1.301 SDK 이상에 포함되어 있습니다. 다음 코드는 이 버그를 수정하는 내부 스위치를 설정합니다.

public static void Main(string[] args)
{
   AppContext.SetSwitch("Microsoft.AspNetCore.Routing.UseCorrectCatchAllBehavior", 
                         true);
   CreateHostBuilder(args).Build().Run();
}
// Remaining code removed for brevity.

모호한 작업 확인

두 엔드포인트가 라우팅을 통해 일치되면 라우팅은 다음 중 하나를 수행해야 합니다.

  • 가장 적합한 후보를 선택합니다.
  • 예외를 throw합니다.

예시:

public class Products33Controller : Controller
{
    public IActionResult Edit(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }

    [HttpPost]
    public IActionResult Edit(int id, Product product)
    {
        return ControllerContext.MyDisplayRouteInfo(id, product.name);
    }
}

위의 컨트롤러는 다음과 일치하는 두 작업을 정의합니다.

  • URL 경로 /Products33/Edit/17
  • 경로 데이터 { controller = Products33, action = Edit, id = 17 }.

MVC 컨트롤러의 일반적인 패턴은 다음과 같습니다.

  • Edit(int)는 제품을 편집하기 위한 폼을 표시합니다.
  • Edit(int, Product)은 게시된 양식을 처리합니다.

올바른 경로를 확인하기 위해 다음이 수행됩니다.

  • 요청이 HTTP POST인 경우 Edit(int, Product)가 선택됩니다.
  • HTTP 동사가 다른 경우 Edit(int)가 선택됩니다. Edit(int)는 일반적으로 GET을 통해 호출됩니다.

요청의 HTTP 메서드에 따라 선택할 수 있도록 HttpPostAttribute, [HttpPost]가 라우팅에 제공됩니다. HttpPostAttributeEdit(int, Product)Edit(int)보다 더 잘 일치시키도록 만듭니다.

HttpPostAttribute와 같은 특성의 역할을 이해하는 것이 중요합니다. 유사한 특성이 다른 HTTP 동사에 대해 정의됩니다. 규칙 기반 라우팅에서는 작업이 폼 표시, 폼 제출 워크플로의 일부인 경우 작업에서 동일한 작업 이름을 사용하는 것이 일반적입니다. 예를 들어 두 개의 Edit 작업 메서드 검사를 참조하세요.

라우팅에서 가장 적합한 후보를 선택할 수 없는 경우 AmbiguousMatchException이 throw되어 일치하는 여러 엔드포인트가 나열됩니다.

규칙 기반 경로 이름

다음 예제의 "blog""default" 문자열은 규칙 경로 이름입니다.

app.MapControllerRoute(name: "blog",
                pattern: "blog/{*article}",
                defaults: new { controller = "Blog", action = "Article" });
app.MapControllerRoute(name: "default",
               pattern: "{controller=Home}/{action=Index}/{id?}");

이 경로 이름은 경로에 논리적 이름을 지정합니다. 명명된 경로는 URL 생성에 사용할 수 있습니다. 명명된 경로를 사용하면 경로 순서 지정으로 인해 URL 생성이 복잡해질 수 있는 상황에서 URL 생성 방법이 매우 간단해집니다. 경로 이름은 애플리케이션 전체에서 고유해야 합니다.

경로 이름:

  • URL 일치 또는 요청 처리에는 영향을 주지 않습니다.
  • URL 생성에만 사용됩니다.

경로 이름 개념은 라우팅에서 IEndpointNameMetadata로 표시됩니다. 용어 경로 이름엔드포인트 이름은 다음과 같습니다.

  • 서로 교환하여 사용할 수 있습니다.
  • 설명서와 코드에 어떤 용어가 사용되는지는 설명되는 API에 따라 달라집니다.

REST API에 대한 특성 라우팅

REST API는 특성 라우팅을 사용하여 HTTP 동사로 작업을 나타내는 리소스 집합으로 앱의 기능을 모델링해야 합니다.

특성 라우팅은 특성 모음을 사용하여 작업을 경로 템플릿에 직접 매핑합니다. 다음 코드는 REST API에 일반적이며 다음 샘플에서 사용합니다.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

var app = builder.Build();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

이전 코드에서 특성 라우팅 컨트롤러를 매핑하기 위해 MapControllers를 호출합니다.

다음 예제에서

  • HomeController는 기본 규칙 기반 경로 {controller=Home}/{action=Index}/{id?}가 일치시키는 경로와 유사한 URL 집합을 일치시킵니다.
public class HomeController : Controller
{
    [Route("")]
    [Route("Home")]
    [Route("Home/Index")]
    [Route("Home/Index/{id?}")]
    public IActionResult Index(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }

    [Route("Home/About")]
    [Route("Home/About/{id?}")]
    public IActionResult About(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

HomeController.Index 작업은 URL 경로 /, /Home, /Home/Index 또는 /Home/Index/3에 대해 실행됩니다.

이 예제에서는 특성 라우팅과 규칙 기반 라우팅 간의 주요 프로그래밍 차이점을 강조합니다. 특성 라우팅은 경로를 지정하기 위해 더 많은 입력이 필요합니다. 규칙 기반 기본 경로는 경로를 좀 더 간략하게 처리합니다. 그러나 특성 라우팅을 사용하면 각 작업에 적용되는 경로 템플릿을 정확하게 제어할 수 있습니다(또 그래야만 합니다).

특성 라우팅을 사용하면 토큰 교체를 사용하지 않는 한, 컨트롤러 및 작업 이름은 일치시키는 작업을 결정하는 데 영향을 미치지 않습니다. 다음 예제에서는 이전 예제와 동일한 URL을 일치시킵니다.

public class MyDemoController : Controller
{
    [Route("")]
    [Route("Home")]
    [Route("Home/Index")]
    [Route("Home/Index/{id?}")]
    public IActionResult MyIndex(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }

    [Route("Home/About")]
    [Route("Home/About/{id?}")]
    public IActionResult MyAbout(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

다음 코드에서는 actioncontroller에 대해 토큰 대체를 사용합니다.

public class HomeController : Controller
{
    [Route("")]
    [Route("Home")]
    [Route("[controller]/[action]")]
    public IActionResult Index()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [Route("[controller]/[action]")]
    public IActionResult About()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

다음 코드는 컨트롤러에 [Route("[controller]/[action]")]를 적용합니다.

[Route("[controller]/[action]")]
public class HomeController : Controller
{
    [Route("~/")]
    [Route("/Home")]
    [Route("~/Home/Index")]
    public IActionResult Index()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    public IActionResult About()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

위의 코드에서 Index 메서드 템플릿은 / 또는 ~/를 경로 템플릿 앞에 추가해야 합니다. 작업에 적용된 / 또는 ~/로 시작하는 경로 템플릿은 컨트롤러에 적용된 경로 템플릿과 결합되지 않습니다.

경로 템플릿 선택에 대한 내용은 경로 템플릿 우선 순위를 참조하세요.

예약된 라우팅 이름

컨트롤러 또는 Razor Pages를 사용하는 경우 다음 키워드는 예약된 경로 매개 변수 이름입니다.

  • action
  • area
  • controller
  • handler
  • page

특성 라우팅에 경로 매개 변수로 page를 사용하는 것은 일반적인 오류입니다. 이렇게 하면 URL 생성 시 일관되지 않은 혼란스러운 동작이 발생합니다.

public class MyDemo2Controller : Controller
{
    [Route("/articles/{page}")]
    public IActionResult ListArticles(int page)
    {
        return ControllerContext.MyDisplayRouteInfo(page);
    }
}

특수 매개 변수 이름은 URL 생성 작업에서 Razor Page 또는 컨트롤러를 참조하는지를 결정하는 데 사용됩니다.

다음 키워드는 Razor 뷰 또는 Razor 페이지의 컨텍스트에서 예약됩니다.

  • page
  • using
  • namespace
  • inject
  • section
  • inherits
  • model
  • addTagHelper
  • removeTagHelper

이러한 키워드는 링크 생성, 모델 바인딩 매개 변수 또는 최상위 속성에 사용하면 안 됩니다.

HTTP 동사 템플릿

ASP.NET Core에는 다음과 같은 HTTP 동사 템플릿이 있습니다.

경로 템플릿

ASP.NET Core에는 다음과 같은 경로 템플릿이 있습니다.

Http 동사 특성을 사용한 특성 라우팅

다음 컨트롤러를 고려해보세요.

[Route("api/[controller]")]
[ApiController]
public class Test2Controller : ControllerBase
{
    [HttpGet]   // GET /api/test2
    public IActionResult ListProducts()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [HttpGet("{id}")]   // GET /api/test2/xyz
    public IActionResult GetProduct(string id)
    {
       return ControllerContext.MyDisplayRouteInfo(id);
    }

    [HttpGet("int/{id:int}")] // GET /api/test2/int/3
    public IActionResult GetIntProduct(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }

    [HttpGet("int2/{id}")]  // GET /api/test2/int2/3
    public IActionResult GetInt2Product(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

위의 코드에서

  • 각 작업에는 HTTP GET 요청으로만 일치를 제한하는 [HttpGet] 특성이 포함됩니다.
  • GetProduct 작업에는 "{id}" 템플릿이 포함되므로 id가 컨트롤러의 "api/[controller]" 템플릿에 추가됩니다. 메서드 템플릿은 "api/[controller]/{id}"입니다. 따라서 이 작업은 /api/test2/xyz,/api/test2/123,/api/test2/{any string} 등의 폼에 대해서만 GET 요청을 일치시킵니다.
    [HttpGet("{id}")]   // GET /api/test2/xyz
    public IActionResult GetProduct(string id)
    {
       return ControllerContext.MyDisplayRouteInfo(id);
    }
    
  • GetIntProduct 작업에는 "int/{id:int}" 템플릿이 포함됩니다. 템플릿의 :int 부분은 id 경로 값을 정수로 변환할 수 있는 문자열로 제한합니다. 다음을 위한 GET 요청입니다./api/test2/int/abc
    • 이 작업을 일치시키지 않습니다.
    • 404 찾을 수 없음 오류를 반환합니다.
      [HttpGet("int/{id:int}")] // GET /api/test2/int/3
      public IActionResult GetIntProduct(int id)
      {
          return ControllerContext.MyDisplayRouteInfo(id);
      }
      
  • GetInt2Product 작업의 템플릿에는 {id}가 포함되지만 id를 정수로 변환할 수 있는 값으로 제한하지 않습니다. 다음을 위한 GET 요청입니다./api/test2/int2/abc
    • 이 경로를 일치시킵니다.
    • 모델 바인딩이 abc를 정수로 변환하지 못합니다. 메서드의 id 매개 변수는 정수입니다.
    • 모델 바인딩이 정수로 변환 abc 하지 못했기 때문에 400 잘못된 요청을 반환합니다.
      [HttpGet("int2/{id}")]  // GET /api/test2/int2/3
      public IActionResult GetInt2Product(int id)
      {
          return ControllerContext.MyDisplayRouteInfo(id);
      }
      

특성 라우팅은 HttpPostAttribute, HttpPutAttributeHttpDeleteAttribute와 같은 HttpMethodAttribute 특성을 사용할 수 있습니다. 모든 HTTP 동사 특성은 경로 템플릿을 허용합니다. 다음 예제는 동일한 경로 템플릿과 일치하는 두 가지 작업을 보여 줍니다.

[ApiController]
public class MyProductsController : ControllerBase
{
    [HttpGet("/products3")]
    public IActionResult ListProducts()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [HttpPost("/products3")]
    public IActionResult CreateProduct(MyProduct myProduct)
    {
        return ControllerContext.MyDisplayRouteInfo(myProduct.Name);
    }
}

URL 경로 /products3를 사용하면 다음 결과가 나타납니다.

  • MyProductsController.ListProducts 작업은 HTTP 동사GET일 때 실행됩니다.
  • MyProductsController.CreateProduct 작업은 HTTP 동사POST일 때 실행됩니다.

REST API를 빌드할 때 작업이 모든 HTTP 메서드를 허용하기 때문에 작업 메서드에 [Route(...)]를 사용해야 하는 경우는 드뭅니다. 보다 구체적인 HTTP 동사 특성을 사용하여 API에서 지원하는 항목을 정확하게 지정하는 것이 좋습니다. REST API의 클라이언트는 특정 논리 작업에 매핑되는 경로 및 HTTP 동사를 알아야 합니다.

REST API는 특성 라우팅을 사용하여 HTTP 동사로 작업을 나타내는 리소스 집합으로 앱의 기능을 모델링해야 합니다. 이는 동일한 논리 리소스의 많은 작업(예: GET 및 POST)이 동일한 URL을 사용한다는 뜻입니다. 특성 라우팅은 API의 공용 엔드포인트 레이아웃을 신중하게 설계하는 데 필요한 제어 수준을 제공합니다.

특성 경로는 특정 작업에 적용되므로 경로 템플릿 정의의 일환으로 필요한 매개 변수를 간단하게 만들 수 있습니다. 다음 예제에서 id는 URL 경로에 포함해야 합니다.

[ApiController]
public class Products2ApiController : ControllerBase
{
    [HttpGet("/products2/{id}", Name = "Products_List")]
    public IActionResult GetProduct(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

Products2ApiController.GetProduct(int) 작업은 다음과 같습니다.

  • /products2/3와 같은 URL 경로를 통해 실행됩니다.
  • URL 경로 /products2를 사용하여 실행되지 않습니다.

[사용] 특성을 사용하면 작업에서 지원되는 요청 콘텐츠 형식을 제한할 수 있습니다. 자세한 내용은 Consumes 특성을 사용하여 지원되는 요청 콘텐츠 형식 정의를 참조하세요.

경로 템플릿 및 관련 옵션에 대한 전체 설명은 라우팅을 참조하세요.

[ApiController]에 대한 자세한 내용은 ApiController 특성을 참조하세요.

경로 이름

다음 코드는 Products_List의 경로 이름을 정의합니다.

[ApiController]
public class Products2ApiController : ControllerBase
{
    [HttpGet("/products2/{id}", Name = "Products_List")]
    public IActionResult GetProduct(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

경로 이름을 사용하여 특정 경로 기반의 URL을 생성할 수 있습니다. 경로 이름:

  • 라우팅의 URL 일치 동작에는 영향을 주지 않습니다.
  • URL 생성에만 사용됩니다.

경로 이름은 애플리케이션 전체에서 고유해야 합니다.

id 매개 변수를 선택 사항({id?})으로 정의하는 규칙 기반 기본 경로를 사용하는 이전 코드와는 대조적입니다. API를 정확하게 지정하는 기능은 /products/products/5를 서로 다른 작업에 디스패치할 수 있는 등의 장점이 있습니다.

특성 경로 결합

특성 라우팅의 반복 횟수를 줄이기 위해 컨트롤러의 경로 특성은 개별 작업의 경로 특성과 결합됩니다. 컨트롤러에 정의된 경로 템플릿은 작업의 경로 템플릿에 앞에 추가됩니다. 경로 특성을 컨트롤러에 배치하면 컨트롤러의 모든 작업이 특성 라우팅을 사용합니다.

[ApiController]
[Route("products")]
public class ProductsApiController : ControllerBase
{
    [HttpGet]
    public IActionResult ListProducts()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [HttpGet("{id}")]
    public IActionResult GetProduct(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

앞의 예에서:

  • URL 경로 /productsProductsApi.ListProducts와 일치할 수 있습니다.
  • URL 경로 /products/5ProductsApi.GetProduct(int)와 일치할 수 있습니다.

이러한 두 작업은 모두 [HttpGet] 특성으로 표시되기 때문에 HTTP GET만 일치시킵니다.

작업에 적용된 / 또는 ~/로 시작하는 경로 템플릿은 컨트롤러에 적용된 경로 템플릿과 결합되지 않습니다. 다음 예제는 기본 경로와 비슷한 URL 경로 집합을 일치시킵니다.

[Route("Home")]
public class HomeController : Controller
{
    [Route("")]
    [Route("Index")]
    [Route("/")]
    public IActionResult Index()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [Route("About")]
    public IActionResult About()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

다음 표에서는 이전 코드의 [Route] 특성에 대해 설명합니다.

Attribute [Route("Home")]과 결합합니다. 경로 템플릿을 정의합니다.
[Route("")] "Home"
[Route("Index")] "Home/Index"
[Route("/")] 아니요 ""
[Route("About")] "Home/About"

특성 경로 순서

라우팅은 트리를 빌드하고 모든 엔드포인트를 동시에 일치시킵니다.

  • 경로 항목은 이상적인 순서로 배치된 것처럼 동작합니다.
  • 가장 구체적인 경로는 보다 일반적인 경로보다 먼저 실행될 가능성이 있습니다.

예를 들어 blog/search/{topic}과 같은 특성 경로는 blog/{*article}과 같은 특성 경로보다 더 구체적입니다. blog/search/{topic} 경로는 더 구체적이기 때문에 기본적으로 우선 순위가 높습니다. 규칙 기반 라우팅을 사용하면 개발자가 원하는 순서대로 경로를 배치해야 합니다.

특성 경로는 Order 속성을 사용하여 순서를 구성할 수 있습니다. 모든 프레임워크 제공 경로 특성에는 Order가 포함됩니다. 경로는 Order 속성의 오름차순 정렬에 따라 처리됩니다. 기본 순서는 0입니다. Order = -1을 사용한 경로 설정은 순서를 설정하지 않는 경로보다 먼저 실행됩니다. Order = 1을 사용한 경로 설정은 기본 경로 순서보다 늦게 실행됩니다.

Order에 따라 결정하지 않도록 합니다. 앱의 URL 공간에 올바른 라우팅을 위한 명시적 순서 값이 필요한 경우 클라이언트에서도 혼란이 발생할 수 있습니다. 일반적으로 특성 라우팅은 URL이 일치하는 올바른 경로를 선택합니다. URL 생성에 사용되는 기본 순서가 작동하지 않는 경우 일반적으로 경로 이름을 우선적으로 사용하는 것이 Order 속성을 적용하는 것보다 간단합니다.

둘 다 경로 일치 /home을 정의하는 다음 두 컨트롤러를 고려합니다.

public class HomeController : Controller
{
    [Route("")]
    [Route("Home")]
    [Route("Home/Index")]
    [Route("Home/Index/{id?}")]
    public IActionResult Index(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }

    [Route("Home/About")]
    [Route("Home/About/{id?}")]
    public IActionResult About(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}
public class MyDemoController : Controller
{
    [Route("")]
    [Route("Home")]
    [Route("Home/Index")]
    [Route("Home/Index/{id?}")]
    public IActionResult MyIndex(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }

    [Route("Home/About")]
    [Route("Home/About/{id?}")]
    public IActionResult MyAbout(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

위의 코드를 사용하여 /home을 요청하면 다음과 유사한 예외가 throw됩니다.

AmbiguousMatchException: The request matched multiple endpoints. Matches:

 WebMvcRouting.Controllers.HomeController.Index
 WebMvcRouting.Controllers.MyDemoController.MyIndex

경로 특성 중 하나에 Order를 추가하면 모호성이 해결됩니다.

[Route("")]
[Route("Home", Order = 2)]
[Route("Home/MyIndex")]
public IActionResult MyIndex()
{
    return ControllerContext.MyDisplayRouteInfo();
}

위의 코드를 통해 /homeHomeController.Index 엔드포인트를 실행합니다. MyDemoController.MyIndex로 돌아가려면 /home/MyIndex를 요청합니다. :

  • 위의 코드는 예제이거나 적절하지 못한 라우팅 디자인입니다. Order 속성을 설명하는 데 사용되었습니다.
  • Order 속성은 모호성만 해결하며, 해당 템플릿은 일치시킬 수 없습니다. [Route("Home")] 템플릿을 제거하는 것이 더 바람직합니다.

Razor Pages를 사용한 경로 순서에 대한 내용은 Razor Pages 경로 및 앱 규칙: 경로 순서를 참조하세요.

일부 경우에는 모호한 경로를 사용하여 HTTP 500 오류가 반환됩니다. 로깅을 사용하여 AmbiguousMatchException을 발생시킨 엔드포인트를 확인합니다.

경로 템플릿 [컨트롤러], [작업], [영역]에서 토큰 바꾸기

편의를 위해 특성 경로는 토큰을 대괄호([, ])로 묶는 방식으로 ‘토큰 교체’를 지원합니다. [action], [area][controller] 토큰은 경로가 정의된 작업의 작업 이름, 영역 이름 및 컨트롤러 이름으로 교체됩니다.

[Route("[controller]/[action]")]
public class Products0Controller : Controller
{
    [HttpGet]
    public IActionResult List()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }


    [HttpGet("{id}")]
    public IActionResult Edit(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

위의 코드에서

[HttpGet]
public IActionResult List()
{
    return ControllerContext.MyDisplayRouteInfo();
}
  • /Products0/List를 일치시킵니다.
[HttpGet("{id}")]
public IActionResult Edit(int id)
{
    return ControllerContext.MyDisplayRouteInfo(id);
}
  • /Products0/Edit/{id}를 일치시킵니다.

토큰 교체는 특성 경로 빌드 과정의 마지막 단계로 발생합니다. 앞의 예제는 다음 코드와 동일하게 동작합니다.

public class Products20Controller : Controller
{
    [HttpGet("[controller]/[action]")]  // Matches '/Products20/List'
    public IActionResult List()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [HttpGet("[controller]/[action]/{id}")]   // Matches '/Products20/Edit/{id}'
    public IActionResult Edit(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

영어가 아닌 다른 언어로 이 문서를 읽는 경우 네이티브 언어로 코드 주석을 보려면 이 GitHub 토론 문제에 알려주세요.

특성 경로를 상속과 결합할 수도 있습니다. 특히 토큰 교체와 결합하면 더욱 강력합니다. 토큰 교체는 특성 경로에 정의된 경로 이름에도 적용됩니다. [Route("[controller]/[action]", Name="[controller]_[action]")]은 각 작업에 대해 고유한 경로 이름을 생성합니다.

[ApiController]
[Route("api/[controller]/[action]", Name = "[controller]_[action]")]
public abstract class MyBase2Controller : ControllerBase
{
}

public class Products11Controller : MyBase2Controller
{
    [HttpGet]                      // /api/products11/list
    public IActionResult List()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [HttpGet("{id}")]             //    /api/products11/edit/3
    public IActionResult Edit(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

리터럴 토큰 교체 구분 기호 [or]을 매칭하려면 문자([[or]])를 반복하여 이스케이프합니다.

매개 변수 변환기를 사용하여 토큰 교체 사용자 지정

매개 변수 변환기를 사용하여 토큰 교체를 사용자 지정할 수 있습니다. 매개 변수 변환기는 IOutboundParameterTransformer를 구현하며 매개 변수의 값을 변환합니다. 예를 들어 사용자 지정 SlugifyParameterTransformer 매개 변수 변환기는 SubscriptionManagement 경로 값을 subscription-management로 변경합니다.

using System.Text.RegularExpressions;

public class SlugifyParameterTransformer : IOutboundParameterTransformer
{
    public string? TransformOutbound(object? value)
    {
        if (value == null) { return null; }

        return Regex.Replace(value.ToString()!,
                             "([a-z])([A-Z])",
                             "$1-$2",
                             RegexOptions.CultureInvariant,
                             TimeSpan.FromMilliseconds(100)).ToLowerInvariant();
    }
}

RouteTokenTransformerConvention은 다음과 같은 응용 프로그램 모델 규칙입니다.

  • 애플리케이션의 모든 특성 경로에 매개 변수 변환기를 적용합니다.
  • 대체되는 특성 경로 토큰 값을 사용자 지정합니다.
public class SubscriptionManagementController : Controller
{
    [HttpGet("[controller]/[action]")]
    public IActionResult ListAll()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

앞의 ListAll 메서드는 /subscription-management/list-all을 일치시킵니다.

RouteTokenTransformerConvention은 옵션으로 등록됩니다.

using Microsoft.AspNetCore.Mvc.ApplicationModels;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews(options =>
{
    options.Conventions.Add(new RouteTokenTransformerConvention(
                                 new SlugifyParameterTransformer()));
});

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Home/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapControllerRoute(name: "default",
               pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();

동적 필드의 정의에 대해서는 동적 필드에 대한 MDN 웹 문서를 참조하세요.

Warning

System.Text.RegularExpressions를 사용하여 신뢰할 수 없는 입력을 처리하는 경우 시간 제한을 전달합니다. 악의적인 사용자가 RegularExpressions에 대한 입력을 제공하여 서비스 거부 공격을 일으킬 수 있습니다. RegularExpressions를 사용하는 ASP.NET Core Framework API는 시간 제한을 전달합니다.

여러 특성 경로

특성 경로는 동일한 작업에 도달하는 여러 경로를 정의하는 것을 지원합니다. 가장 일반적인 사용 방법은 다음 예제와 같이 기본 규칙 기반 경로의 동작을 모방하는 것입니다.

[Route("[controller]")]
public class Products13Controller : Controller
{
    [Route("")]     // Matches 'Products13'
    [Route("Index")] // Matches 'Products13/Index'
    public IActionResult Index()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

컨트롤러에 여러 경로 특성을 배치하면 각 경로 특성이 작업 메서드의 각 경로 특성과 결합됩니다.

[Route("Store")]
[Route("[controller]")]
public class Products6Controller : Controller
{
    [HttpPost("Buy")]       // Matches 'Products6/Buy' and 'Store/Buy'
    [HttpPost("Checkout")]  // Matches 'Products6/Checkout' and 'Store/Checkout'
    public IActionResult Buy()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

모든 HTTP 동사 경로 제약 조건은 IActionConstraint를 구현합니다.

IActionConstraint를 구현하는 여러 경로 특성이 작업에 배치되면 다음이 수행됩니다.

  • 각 작업 제약 조건은 컨트롤러에 적용된 경로 템플릿과 결합됩니다.
[Route("api/[controller]")]
public class Products7Controller : ControllerBase
{
    [HttpPut("Buy")]        // Matches PUT 'api/Products7/Buy'
    [HttpPost("Checkout")]  // Matches POST 'api/Products7/Checkout'
    public IActionResult Buy()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

작업에 여러 경로를 사용하면 유용하고 강력해 보일 수 있습니다. 앱의 URL 공간을 기본적이고 잘 정의된 상태로 유지하는 것이 좋습니다. 기존 클라이언트를 지원하려는 경우처럼 꼭 필요한 경우에만 작업에 여러 경로를 사용합니다.

특성 경로 선택적 매개 변수, 기본값 및 제약 조건 지정

특성 경로는 선택적 매개 변수, 기본값 및 제약 조건을 지정할 수 있는 규칙 기반 경로와 동일한 인라인 구문을 지원합니다.

public class Products14Controller : Controller
{
    [HttpPost("product14/{id:int}")]
    public IActionResult ShowProduct(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

위의 코드에서 [HttpPost("product14/{id:int}")]는 경로 제약 조건을 적용합니다. Products14Controller.ShowProduct 작업은 /product14/3과 같은 URL 경로로만 일치됩니다. 경로 템플릿 부분 {id:int}는 해당 세그먼트를 정수로 제한합니다.

경로 템플릿 구문에 대한 자세한 설명은 경로 템플릿 참조를 참조하세요.

IRouteTemplateProvider를 사용하는 사용자 지정 경로 특성

모든 경로 특성IRouteTemplateProvider를 구현합니다. ASP.NET Core 런타임은 다음을 수행합니다.

  • 앱이 시작될 때 컨트롤러 클래스 및 작업 메서드에서 특성을 찾습니다.
  • IRouteTemplateProvider를 구현하는 특성을 사용하여 초기 경로 집합을 작성합니다.

IRouteTemplateProvider를 구현하여 사용자 지정 경로 특성을 정의합니다. 각각의 IRouteTemplateProvider를 사용하여 사용자 지정 경로 템플릿, 순서 및 이름으로 단일 경로를 정의할 수 있습니다.

public class MyApiControllerAttribute : Attribute, IRouteTemplateProvider
{
    public string Template => "api/[controller]";
    public int? Order => 2;
    public string Name { get; set; } = string.Empty;
}

[MyApiController]
[ApiController]
public class MyTestApiController : ControllerBase
{
    // GET /api/MyTestApi
    [HttpGet]
    public IActionResult Get()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

앞의 Get 메서드는 Order = 2, Template = api/MyTestApi를 반환합니다.

애플리케이션 모델을 사용하여 특성 경로 사용자 지정

애플리케이션 모델은 다음과 같습니다.

  • 에서 시작할 때 만든 개체 모델입니다 Program.cs.
  • 앱에서 작업을 라우팅하고 실행하기 위해 ASP.NET Core가 사용하는 모든 메타데이터를 포함합니다.

애플리케이션 모델은 경로 특성으로부터 수집한 모든 데이터를 포함합니다. 경로 특성의 데이터는 IRouteTemplateProvider 구현에 의해 제공됩니다. 규칙:

  • 애플리케이션 모델을 수정하여 라우팅 동작 방식을 사용자 지정하도록 작성할 수 있습니다.
  • 앱 시작 시 읽습니다.

이 섹션에서는 애플리케이션 모델을 사용하여 라우팅을 사용자 지정하는 기본적인 예제를 보여 줍니다. 다음 코드는 경로가 프로젝트의 폴더 구조와 거의 일치하게 만듭니다.

public class NamespaceRoutingConvention : Attribute, IControllerModelConvention
{
    private readonly string _baseNamespace;

    public NamespaceRoutingConvention(string baseNamespace)
    {
        _baseNamespace = baseNamespace;
    }

    public void Apply(ControllerModel controller)
    {
        var hasRouteAttributes = controller.Selectors.Any(selector =>
                                                selector.AttributeRouteModel != null);
        if (hasRouteAttributes)
        {
            return;
        }

        var namespc = controller.ControllerType.Namespace;
        if (namespc == null)
            return;
        var template = new StringBuilder();
        template.Append(namespc, _baseNamespace.Length + 1,
                        namespc.Length - _baseNamespace.Length - 1);
        template.Replace('.', '/');
        template.Append("/[controller]/[action]/{id?}");

        foreach (var selector in controller.Selectors)
        {
            selector.AttributeRouteModel = new AttributeRouteModel()
            {
                Template = template.ToString()
            };
        }
    }
}

다음 코드는 특성이 라우팅되는 컨트롤러에 namespace 규칙이 적용되지 않도록 합니다.

public void Apply(ControllerModel controller)
{
    var hasRouteAttributes = controller.Selectors.Any(selector =>
                                            selector.AttributeRouteModel != null);
    if (hasRouteAttributes)
    {
        return;
    }

예를 들어 다음 컨트롤러는 NamespaceRoutingConvention을 사용하지 않습니다.

[Route("[controller]/[action]/{id?}")]
public class ManagersController : Controller
{
    // /managers/index
    public IActionResult Index()
    {
        var template = ControllerContext.ActionDescriptor.AttributeRouteInfo?.Template;
        return Content($"Index- template:{template}");
    }

    public IActionResult List(int? id)
    {
        var path = Request.Path.Value;
        return Content($"List- Path:{path}");
    }
}

NamespaceRoutingConvention.Apply 메서드는 다음 작업을 수행합니다.

  • 컨트롤러가 특성 라우팅된 경우 아무 것도 수행하지 않습니다.
  • 기본 namespace를 제거한 상태로 namespace를 기준으로 하는 컨트롤러 템플릿을 설정합니다.

NamespaceRoutingConventionProgram.cs에 적용할 수 있습니다.

using My.Application.Controllers;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews(options =>
{
    options.Conventions.Add(
     new NamespaceRoutingConvention(typeof(HomeController).Namespace!));
});

var app = builder.Build();

다음 컨트롤러를 예로 들 수 있습니다.

using Microsoft.AspNetCore.Mvc;

namespace My.Application.Admin.Controllers
{
    public class UsersController : Controller
    {
        // GET /admin/controllers/users/index
        public IActionResult Index()
        {
            var fullname = typeof(UsersController).FullName;
            var template = 
                ControllerContext.ActionDescriptor.AttributeRouteInfo?.Template;
            var path = Request.Path.Value;

            return Content($"Path: {path} fullname: {fullname}  template:{template}");
        }

        public IActionResult List(int? id)
        {
            var path = Request.Path.Value;
            return Content($"Path: {path} ID:{id}");
        }
    }
}

위의 코드에서

  • 기본 namespaceMy.Application입니다.
  • 이전 컨트롤러의 전체 이름은 My.Application.Admin.Controllers.UsersController입니다.
  • NamespaceRoutingConvention은 컨트롤러 템플릿을 Admin/Controllers/Users/[action]/{id?로 설정합니다.

NamespaceRoutingConvention을 컨트롤러의 특성으로 적용할 수도 있습니다.

[NamespaceRoutingConvention("My.Application")]
public class TestController : Controller
{
    // /admin/controllers/test/index
    public IActionResult Index()
    {
        var template = ControllerContext.ActionDescriptor.AttributeRouteInfo?.Template;
        var actionname = ControllerContext.ActionDescriptor.ActionName;
        return Content($"Action- {actionname} template:{template}");
    }

    public IActionResult List(int? id)
    {
        var path = Request.Path.Value;
        return Content($"List- Path:{path}");
    }
}

혼합 라우팅: 특성 라우팅 및 규칙 기반 라우팅

ASP.NET Core 앱은 규칙 기반 라우팅과 특성 라우팅을 혼합해서 사용할 수 있습니다. 일반적으로 브라우저에 HTML 페이지를 제공하는 컨트롤러에 대한 기존 경로와 REST API를 제공하는 컨트롤러에 대한 특성 라우팅을 사용합니다.

작업은 규약 기반으로 라우팅되거나 특성 라우팅됩니다. 컨트롤러 또는 작업에 경로를 배치하면 해당 경로가 특성 라우팅됩니다. 특성 경로를 정의하는 동작은 규칙 기반 경로를 통해 도달할 수 없으며 그 반대도 마찬가지입니다. 컨트롤러의 모든 경로 특성은 컨트롤러의 모든 작업에서 특성 라우팅을 사용하게 만듭니다.

특성 라우팅 및 규칙 기반 라우팅은 동일한 라우팅 엔진을 사용합니다.

특수 문자를 사용하여 라우팅

특수 문자를 사용하여 라우팅하면 예기치 않은 결과가 발생할 수 있습니다. 예를 들어 다음 작업 메서드를 사용하는 컨트롤러를 고려합니다.

[HttpGet("{id?}/name")]
public async Task<ActionResult<string>> GetName(string id)
{
    var todoItem = await _context.TodoItems.FindAsync(id);

    if (todoItem == null || todoItem.Name == null)
    {
        return NotFound();
    }

    return todoItem.Name;
}

다음과 같은 인코딩된 값이 포함된 경우 string id 예기치 않은 결과가 발생할 수 있습니다.

ASCII 인코딩된
/ %2F
+

경로 매개 변수가 항상 URL 디코딩되는 것은 아닙니다. 이 문제는 나중에 해결될 수 있습니다. 자세한 내용은 이 GitHub 문제를 참조하세요.

URL 생성 및 앰비언트 값

앱은 라우팅의 URL 생성 기능을 사용하여 작업에 대한 URL 링크를 생성할 수 있습니다. URL을 생성하면 하드 코딩된 URL이 제거되어 코드를 더욱 견고하게 유지할 수 있습니다. 이 섹션에서는 MVC에서 제공하는 URL 생성 기능을 중심으로 기본적인 URL 생성 원리에 대해서만 다룰 것입니다. URL 생성에 대한 자세한 설명은 라우팅을 참조하세요.

IUrlHelper 인터페이스는 URL 생성을 위한 MVC와 라우팅 간의 기본 인프라 요소입니다. 컨트롤러, 보기 및 보기 구성 요소의 Url 속성을 통해서 사용 가능한 IUrlHelper의 인스턴스를 찾을 수 있습니다.

다음 예제에서 IUrlHelper 인터페이스는 Controller.Url 속성을 통해 다른 작업에 대한 URL을 생성하는 데 사용됩니다.

public class UrlGenerationController : Controller
{
    public IActionResult Source()
    {
        // Generates /UrlGeneration/Destination
        var url = Url.Action("Destination");
        return ControllerContext.MyDisplayRouteInfo("", $" URL = {url}");
    }

    public IActionResult Destination()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

앱이 기본 규칙 기반 경로를 사용할 경우 url 변수의 값은 URL 경로 문자열 /UrlGeneration/Destination입니다. 이 URL 경로는 다음을 결합하여 라우팅하여 생성합니다.

  • 앰비언트 값이라고 하는 현재 요청의 경로 값
  • Url.Action에 전달되는 값으로, 해당 값을 경로 템플릿으로 대체합니다.
ambient values: { controller = "UrlGeneration", action = "Source" }
values passed to Url.Action: { controller = "UrlGeneration", action = "Destination" }
route template: {controller}/{action}/{id?}

result: /UrlGeneration/Destination

경로 템플릿에 있는 각 경로 매개 변수의 값은 이름을 값 및 앰비언트 값과 매칭한 값으로 바뀝니다. 값이 없는 경로 매개 변수는 다음과 같을 수 있습니다.

  • 기본값이 있는 경우 기본값을 사용합니다.
  • 선택 사항인 경우 생략됩니다. 경로 템플릿 {controller}/{action}/{id?}id를 예로 들 수 있습니다.

필요한 경로 매개 변수에 해당 값이 없는 경우 URL 생성에 실패합니다. 경로에 대한 URL 생성이 실패하면 모든 경로를 시도하거나 일치 항목을 찾을 때까지 그 다음 경로를 시도합니다.

위의 Url.Action 예에서는 규칙 기반 라우팅을 가정합니다. 개념은 서로 다르지만 URL 생성은 특성 라우팅과 유사하게 작동합니다. 규칙 기반 라우팅을 사용하는 경우:

  • 경로 값은 템플릿을 확장하는 데 사용됩니다.
  • controlleraction에 대한 경로 값은 일반적으로 해당 템플릿에 표시됩니다. 이는 라우팅에 의해 일치하는 URL이 규칙을 준수하기 때문에 작동합니다.

다음 예제는 특성 라우팅을 사용합니다.

public class UrlGenerationAttrController : Controller
{
    [HttpGet("custom")]
    public IActionResult Source()
    {
        var url = Url.Action("Destination");
        return ControllerContext.MyDisplayRouteInfo("", $" URL = {url}");
    }

    [HttpGet("custom/url/to/destination")]
    public IActionResult Destination()
    {
       return ControllerContext.MyDisplayRouteInfo();
    }
}

위의 코드에서 Source 작업은 custom/url/to/destination을 생성합니다.

LinkGeneratorIUrlHelper의 대안으로 ASP.NET Core 3.0에 추가되었습니다. LinkGenerator는 유사하지만 좀 더 유연한 기능을 제공합니다. IUrlHelper의 각 메서드에는 LinkGenerator에 대한 해당 메서드 패밀리도 있습니다.

작업 이름으로 URL 생성

Url.Action, LinkGenerator.GetPathByAction 및 모든 관련된 오버로드는 컨트롤러 이름과 작업 이름을 지정하여 대상 엔드포인트를 생성하도록 디자인되었습니다.

Url.Action을 사용하는 경우 controlleraction에 대한 현재 경로 값이 런타임에서 제공됩니다.

  • controlleraction의 값은 앰비언트 값 및 값 둘 다에 속합니다. Url.Action 메서드는 항상 actioncontroller의 현재 값을 사용하며 현재 작업에 라우팅하는 URL 경로를 생성합니다.

라우팅은 앰비언트 값의 값을 사용하여 개발자가 URL을 생성할 때 제공되지 않은 정보를 채웁니다. 앰비언트 값 { a = Alice, b = Bob, c = Carol, d = David }를 갖는 {a}/{b}/{c}/{d}와 같은 경로를 고려합니다.

  • 라우팅에는 추가 값 없이 URL을 생성하기에 충분한 정보가 있습니다.
  • 모든 경로 매개 변수에 값이 있으므로 라우팅에 충분한 정보가 있습니다.

{ d = Donovan }이 추가된 경우:

  • { d = David } 값이 무시됩니다.
  • 생성된 URL 경로는 Alice/Bob/Carol/Donovan입니다.

경고: URL 경로는 계층적입니다. 앞의 예제에서 다음과 같이 값 { c = Cheryl }이 추가됩니다.

  • 두 값 { c = Carol, d = David }은 모두 무시됩니다.
  • d에 대한 값이 더 이상 없으며 URL 생성이 실패합니다.
  • URL을 생성하려면 cd의 원하는 값을 지정해야 합니다.

기본 경로 {controller}/{action}/{id?}를 사용할 때 이 문제가 발생할 수 있습니다. Url.Action은 항상 명시적으로 controlleraction 값을 지정하기 때문에 이 문제는 드물게 발생합니다.

Url.Action을 몇 번 오버로드하면 controlleraction 이외의 경로 매개 변수의 값을 제공하기 위한 경로 값 개체가 사용됩니다. 경로 값 개체는 id와 함께 자주 사용됩니다. 예: Url.Action("Buy", "Products", new { id = 17 }). 경로 값 개체에는 다음이 적용됩니다.

  • 규칙에 따라 일반적으로 무명 형식의 개체입니다.
  • IDictionary<> 또는 POCO일 수 있습니다.

경로 매개 변수와 일치하지 않는 추가 경로 값들은 쿼리 문자열에 포함됩니다.

public IActionResult Index()
{
    var url = Url.Action("Buy", "Products", new { id = 17, color = "red" });
    return Content(url!);
}

위의 코드는 /Products/Buy/17?color=red를 생성합니다.

다음 코드는 절대 URL을 생성합니다.

public IActionResult Index2()
{
    var url = Url.Action("Buy", "Products", new { id = 17 }, protocol: Request.Scheme);
    // Returns https://localhost:5001/Products/Buy/17
    return Content(url!);
}

절대 URL을 만들려면 다음 중 하나를 사용합니다.

  • protocol을 허용하는 오버로드입니다. 앞의 코드를 예로 들 수 있습니다.
  • LinkGenerator.GetUriByAction은 기본적으로 절대 URL을 생성합니다.

경로별 URL 생성

이전 코드에서는 컨트롤러 및 작업 이름을 전달하여 URL을 생성하는 방법을 보여 줍니다. IUrlHelperUrl.RouteUrl 메서드 패밀리도 제공합니다. 이러한 메서드는 Url.Action과 비슷하지만 actioncontroller의 현재 값을 경로 값에 복사하지 않습니다. 가장 일반적인 Url.RouteUrl 사용에서는 다음이 수행됩니다.

  • URL을 생성할 경로 이름을 지정합니다.
  • 일반적으로 컨트롤러 또는 작업 이름은 지정하지 않습니다.
public class UrlGeneration2Controller : Controller
{
    [HttpGet("")]
    public IActionResult Source()
    {
        var url = Url.RouteUrl("Destination_Route");
        return ControllerContext.MyDisplayRouteInfo("", $" URL = {url}");
    }

    [HttpGet("custom/url/to/destination2", Name = "Destination_Route")]
    public IActionResult Destination()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

다음 Razor 파일은 Destination_Route에 대한 HTML 링크를 생성합니다.

<h1>Test Links</h1>

<ul>
    <li><a href="@Url.RouteUrl("Destination_Route")">Test Destination_Route</a></li>
</ul>

HTML 및 Razor에서 URL 생성

IHtmlHelper는 각각 <form><a> 요소를 생성하기 위해 HtmlHelper 메서드 Html.BeginFormHtml.ActionLink를 제공합니다. 이러한 메서드는 Url.Action 메서드를 사용하여 URL을 생성하며 비슷한 인수를 받습니다. HtmlHelper에 대한 Url.RouteUrl 보조 도구는 Html.BeginRouteFormHtml.RouteLink이며 서로 기능이 비슷합니다.

TagHelper는 form TagHelper 및 <a> TagHelper를 통해 URL을 생성합니다. 둘 다 구현에 IUrlHelper를 사용합니다. 자세한 내용은 폼의 태그 도우미를 참조하세요.

보기 내에서 IUrlHelperUrl 속성을 통해서 위에서 다루지 않은 임시 URL을 생성하기 위해 사용할 수 있습니다.

작업 결과의 URL 생성

위의 예제는 컨트롤러에서 IUrlHelper를 사용하는 방법을 보여 주었습니다. 컨트롤러에서 가장 일반적인 사용 방법은 URL을 작업 결과의 일부로 생성하는 것입니다.

ControllerBaseController 기본 클래스는 다른 작업을 참조하는 작업 결과에 대한 편의 메서드를 제공합니다. 한 가지 일반적인 사용법은 사용자 입력을 수락한 후 리디렉션하는 것입니다.

[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Edit(int id, Customer customer)
{
    if (ModelState.IsValid)
    {
        // Update DB with new details.
        ViewData["Message"] = $"Successful edit of customer {id}";
        return RedirectToAction("Index");
    }
    return View(customer);
}

RedirectToActionCreatedAtAction과 같은 작업 결과 팩터리 메서드는 IUrlHelper의 메서드와 비슷한 패턴을 따릅니다.

전용 규칙 기반 경로의 특별한 사례

규칙 기반 라우팅전용 규칙 기반 경로라고 하는 특별한 경로 정의를 사용할 수 있습니다. 다음 예제에서 blog라는 이름의 경로는 전용 규칙 기반 경로입니다.

app.MapControllerRoute(name: "blog",
                pattern: "blog/{*article}",
                defaults: new { controller = "Blog", action = "Article" });
app.MapControllerRoute(name: "default",
               pattern: "{controller=Home}/{action=Index}/{id?}");

위의 경로 정의에서 Url.Action("Index", "Home")default 경로를 사용하여 URL 경로 /를 생성합니다. 하지만 그 이유는 무엇일까요? { controller = Home, action = Index } 경로 값이면 blog를 사용하여 URL을 생성하기에 충분하며, 그 결과는 /blog?action=Index&controller=Home가 될 것이라고 추측할 수도 있습니다.

전용 규칙 기반 경로는 URL 생성과 관련하여 너무 탐욕적인 경로가 되지 않도록 하는 해당 경로 매개 변수가 없는 기본 값의 특별한 동작을 사용합니다. 이 예제에서 기본값은 { controller = Blog, action = Article }이고, controlleraction이 경로 매개 변수로 표시되지 않습니다. 라우팅이 URL 생성을 수행할 때 입력한 값이 기본값과 일치해야 합니다. { controller = Home, action = Index } 값이 { controller = Blog, action = Article }과 일치하지 않기 때문에 blog를 사용한 URL 생성은 실패합니다. 그 후 라우팅이 default로 대체되고, 이것은 성공합니다.

지역

영역은 관련 기능을 다음과 같은 별도의 그룹으로 구성하는 데 사용되는 MVC 기능입니다.

  • 컨트롤러 작업에 대한 라우팅 네임스페이스
  • 뷰용 폴더 구조

영역을 사용하면 컨트롤러의 영역이 서로 다른 한, 앱 하나에서 이름이 같은 컨트롤러를 여러 개 사용할 수 있습니다. 영역을 사용하면 또 다른 경로 매개 변수 areacontrolleraction에 추가하여 라우팅을 위한 계층 구조를 생성합니다. 이 섹션에서는 라우팅이 영역과 상호 작용하는 방법을 설명합니다. 영역을 뷰에서 사용하는 방법에 대한 자세한 내용은 영역을 참조하세요.

다음 예제는 기본 규칙 기반 경로 및 Blog라는 이름의 area에 대한 area 경로를 사용하도록 MVC를 구성합니다.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{    
    app.UseExceptionHandler("/Home/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapAreaControllerRoute("blog_route", "Blog",
        "Manage/{controller}/{action}/{id?}");
app.MapControllerRoute("default_route", "{controller}/{action}/{id?}");

app.Run();

위의 코드에서 MapAreaControllerRoute"blog_route"를 만들기 위해 호출됩니다. 두 번째 매개 변수인 "Blog"는 영역 이름입니다.

/Manage/Users/AddUser와 같은 URL 경로를 일치시킬 경우 "blog_route" 경로는 경로 값 { area = Blog, controller = Users, action = AddUser }를 생성합니다. area 경로 값은 area에 대한 기본값으로 생성됩니다. MapAreaControllerRoute에서 만든 경로는 다음과 같습니다.

app.MapControllerRoute("blog_route", "Manage/{controller}/{action}/{id?}",
        defaults: new { area = "Blog" }, constraints: new { area = "Blog" });
app.MapControllerRoute("default_route", "{controller}/{action}/{id?}");

MapAreaControllerRoute는 제공된 영역 이름(여기에서는 Blog)을 사용하는 area에 대해 기본값 및 제약 조건을 모두 사용하여 경로를 생성합니다. 기본값은 경로가 항상 { area = Blog, ... }를 생성함을 보장하고, 제약 조건은 URL 생성을 위해 { area = Blog, ... } 값을 필요로 합니다.

규칙 기반 라우팅은 순서에 의존적입니다. 일반적으로 영역을 사용하는 경로가 영역을 사용하지 않는 경로에 비해 구체적이기 때문에 앞부분에 배치되어야 합니다.

이전 예제를 사용하면 경로 값 { area = Blog, controller = Users, action = AddUser }가 다음 작업을 일치시킵니다.

using Microsoft.AspNetCore.Mvc;

namespace MyApp.Namespace1
{
    [Area("Blog")]
    public class UsersController : Controller
    {
        // GET /manage/users/adduser
        public IActionResult AddUser()
        {
            var area = ControllerContext.ActionDescriptor.RouteValues["area"];
            var actionName = ControllerContext.ActionDescriptor.ActionName;
            var controllerName = ControllerContext.ActionDescriptor.ControllerName;

            return Content($"area name:{area}" +
                $" controller:{controllerName}  action name: {actionName}");
        }        
    }
}

[Area] 특성은 컨트롤러를 영역의 일부로 나타내는 것입니다. 이 컨트롤러는 Blog 영역에 있습니다. [Area] 특성이 없는 컨트롤러는 어떠한 영역의 구성원도 아니며, 라우팅에서 area 경로 값을 제공해도 일치되지 않습니다. 다음 예제에서는 나열된 첫 번째 컨트롤러만 { area = Blog, controller = Users, action = AddUser } 경로 값과 매칭됩니다.

using Microsoft.AspNetCore.Mvc;

namespace MyApp.Namespace1
{
    [Area("Blog")]
    public class UsersController : Controller
    {
        // GET /manage/users/adduser
        public IActionResult AddUser()
        {
            var area = ControllerContext.ActionDescriptor.RouteValues["area"];
            var actionName = ControllerContext.ActionDescriptor.ActionName;
            var controllerName = ControllerContext.ActionDescriptor.ControllerName;

            return Content($"area name:{area}" +
                $" controller:{controllerName}  action name: {actionName}");
        }        
    }
}
using Microsoft.AspNetCore.Mvc;

namespace MyApp.Namespace2
{
    // Matches { area = Zebra, controller = Users, action = AddUser }
    [Area("Zebra")]
    public class UsersController : Controller
    {
        // GET /zebra/users/adduser
        public IActionResult AddUser()
        {
            var area = ControllerContext.ActionDescriptor.RouteValues["area"];
            var actionName = ControllerContext.ActionDescriptor.ActionName;
            var controllerName = ControllerContext.ActionDescriptor.ControllerName;

            return Content($"area name:{area}" +
                $" controller:{controllerName}  action name: {actionName}");
        }        
    }
}
using Microsoft.AspNetCore.Mvc;

namespace MyApp.Namespace3
{
    // Matches { area = string.Empty, controller = Users, action = AddUser }
    // Matches { area = null, controller = Users, action = AddUser }
    // Matches { controller = Users, action = AddUser }
    public class UsersController : Controller
    {
        // GET /users/adduser
        public IActionResult AddUser()
        {
            var area = ControllerContext.ActionDescriptor.RouteValues["area"];
            var actionName = ControllerContext.ActionDescriptor.ActionName;
            var controllerName = ControllerContext.ActionDescriptor.ControllerName;

            return Content($"area name:{area}" +
                $" controller:{controllerName}  action name: {actionName}");
        }
    }
}

완성도를 위해 각 컨트롤러의 네임스페이스가 여기에 표시됩니다. 이전 컨트롤러가 동일한 네임스페이스를 사용하는 경우 컴파일러 오류가 생성됩니다. 클래스 네임스페이스는 MVC의 라우팅에 영향을 주지 않습니다.

처음 두 컨트롤러는 영역의 구성원이며, area 경로 값으로 해당 영역 이름을 제공하는 경우에만 매칭됩니다. 세 번째 컨트롤러는 어떤 영역의 구성원도 아니며, 라우팅에서 area에 대한 값을 제공하지 않을 때에만 매칭됩니다.

매칭 측면에서 값 없음, 즉 area 값이 없는 것은 area의 값이 null 또는 빈 문자열인 경우와 동일합니다.

영역 내에서 작업을 실행하면 area의 경로 값이 라우팅에서 URL을 생성하기 위해 사용되는 앰비언트 값으로 제공됩니다. 즉, 기본적으로 영역은 다음 예제에서 볼 수 있듯이 URL 생성을 위한 스티커처럼 동작합니다.

app.MapAreaControllerRoute(name: "duck_route",
                                     areaName: "Duck",
                                     pattern: "Manage/{controller}/{action}/{id?}");
app.MapControllerRoute(name: "default",
                             pattern: "Manage/{controller=Home}/{action=Index}/{id?}");
using Microsoft.AspNetCore.Mvc;

namespace MyApp.Namespace4
{
    [Area("Duck")]
    public class UsersController : Controller
    {
        // GET /Manage/users/GenerateURLInArea
        public IActionResult GenerateURLInArea()
        {
            // Uses the 'ambient' value of area.
            var url = Url.Action("Index", "Home");
            // Returns /Manage/Home/Index
            return Content(url);
        }

        // GET /Manage/users/GenerateURLOutsideOfArea
        public IActionResult GenerateURLOutsideOfArea()
        {
            // Uses the empty value for area.
            var url = Url.Action("Index", "Home", new { area = "" });
            // Returns /Manage
            return Content(url);
        }
    }
}

다음 코드에서는 /Zebra/Users/AddUser에 대한 URL을 생성합니다.

public class HomeController : Controller
{
    public IActionResult About()
    {
        var url = Url.Action("AddUser", "Users", new { Area = "Zebra" });
        return Content($"URL: {url}");
    }

작업 정의

NonAction 특성이 있는 메서드를 제외하고 컨트롤러의 퍼블릭 메서드는 작업입니다.

샘플 코드

디버그 진단

자세한 라우팅 진단 출력을 위해 Logging:LogLevel:MicrosoftDebug로 설정합니다. 개발 환경에서 다음에서 로그 수준을 설정합니다.appsettings.Development.json

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Debug",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  }
}

ASP.NET Core 컨트롤러는 라우팅 미들웨어를 사용하여 들어오는 요청의 URL을 매칭하고 작업에 매핑합니다. 경로 템플릿:

  • 시작 코드 또는 특성에서 정의됩니다.
  • URL 경로가 작업과 일치되는 방식을 설명합니다.
  • 링크에 대한 URL을 생성하는 데 사용됩니다. 생성된 링크는 일반적으로 응답에서 반환됩니다.

작업은 규칙 기반 라우팅 또는 특성 라우팅입니다. 컨트롤러 또는 작업에 경로를 배치하면 해당 경로가 특성 라우팅됩니다. 자세한 내용은 혼합 라우팅을 참조하세요.

이 문서:

  • MVC와 라우팅 간의 상호 작용에 대해 설명합니다.
    • 일반적인 MVC 앱이 라우팅 기능을 사용하는 방법입니다.
    • 다음을 모두 다룹니다.
    • 고급 라우팅에 대한 자세한 내용은 라우팅을 참조하세요.
  • ASP.NET Core 3.0에 추가된 기본 라우팅 시스템(엔드포인트 라우팅이라고 함)을 나타냅니다. 호환성을 위해 이전 버전의 라우팅에서 컨트롤러를 사용할 수 있습니다. 지침은 2.2-3.0 마이그레이션 가이드를 참조하세요. 레거시 라우팅 시스템에 대한 참조 자료는 이 문서의 2.2 버전을 참조하세요.

규칙 기반 경로 설정

Startup.Configure는 일반적으로 규칙 기반 라우팅을 사용할 때 다음과 비슷한 코드가 있습니다.

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(
        name: "default",
        pattern: "{controller=Home}/{action=Index}/{id?}");
});

UseEndpoints 호출 내에서 MapControllerRoute는 단일 경로를 만드는 데 사용됩니다. 단일 경로는 default 경로로 지칭됩니다. 컨트롤러 및 뷰를 사용하는 대부분의 앱은 default 경로와 유사한 경로 템플릿을 사용합니다. REST API는 특성 라우팅을 사용해야 합니다.

경로 템플릿 "{controller=Home}/{action=Index}/{id?}"는 다음을 수행합니다.

  • /Products/Details/5와 같은 URL 경로를 일치시킵니다.

  • 경로를 토큰화하여 경로 값 { controller = Products, action = Details, id = 5 }를 추출합니다. 경로 값을 추출하면 앱에 ProductsController라는 컨트롤러와 Details 작업이 포함된 경우 일치하는 항목이 검색됩니다.

    public class ProductsController : Controller
    {
        public IActionResult Details(int id)
        {
            return ControllerContext.MyDisplayRouteInfo(id);
        }
    }
    

    MyDisplayRouteInfoRick.Docs.Samples.RouteInfo NuGet 패키지가 제공하며 경로 정보를 표시합니다.

  • /Products/Details/5 모델은 id = 5 값을 바인딩하여 id 매개 변수를 5로 설정합니다. 자세한 내용은 모델 바인딩을 참조하세요.

  • {controller=Home}Home을 기본 controller로 정의합니다.

  • {action=Index}Index을 기본 action로 정의합니다.

  • {id?}? 문자는 id를 선택 항목으로 정의합니다.

  • 기본 및 선택적 경로 매개 변수는 매칭을 위해 URL 경로에 반드시 있어야 하는 것은 아닙니다. 경로 템플릿 구문에 대한 자세한 설명은 경로 템플릿 참조를 참조하세요.

  • URL 경로 /와 일치시킵니다.

  • 경로 값 { controller = Home, action = Index }를 생성합니다.

controlleraction에 대한 값은 기본값을 사용합니다. id는 URL 경로에 해당 세그먼트가 없기 때문에 값을 생성하지 않습니다. /HomeControllerIndex 작업이 있는 경우에만 일치됩니다.

public class HomeController : Controller
{
  public IActionResult Index() { ... }
}

위의 컨트롤러 정의와 경로 템플릿을 사용하면 다음 URL 경로에 대해 HomeController.Index 작업이 실행됩니다.

  • /Home/Index/17
  • /Home/Index
  • /Home
  • /

URL 경로 /는 경로 템플릿 기본 Home 컨트롤러 및 Index 작업을 사용합니다. URL 경로 /Home은 경로 템플릿 기본 Index 작업을 사용합니다.

편의 메서드인 MapDefaultControllerRoute를 사용하여:

endpoints.MapDefaultControllerRoute();

다음으로 바꿉니다.

endpoints.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}");

Important

라우팅은 UseRouting, MapControllerRouteMapAreaControllerRoute 미들웨어를 사용하여 구성합니다. 컨트롤러를 사용하려면 다음을 수행합니다.

규칙 기반 라우팅

규칙 기반 라우팅은 컨트롤러 및 뷰에서 사용됩니다. default 경로인:

endpoints.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

위의 예제는 ‘규칙 기반 경로’의 예입니다. URL 경로에 대한 ‘규칙’을 설정하기 때문에 ‘규칙 기반 라우팅’이라고 부릅니다.

  • 첫 번째 경로 세그먼트 {controller=Home}은 컨트롤러 이름에 매핑됩니다.
  • 두 번째 세그먼트 {action=Index}작업 이름에 매핑됩니다.
  • 세 번째 세그먼트 {id?}는 선택적 id에 사용됩니다. {id?}?는 이러한 항목을 선택 사항으로 지정합니다. id는 모델 엔터티에 매핑하는 데 사용됩니다.

default 경로를 사용하면 다음 URL 경로

  • /Products/ListProductsController.List 작업에 매핑됩니다.
  • /Blog/Article/17BlogController.Article에 매핑되고 일반적으로 모델은 id 매개 변수를 17에 바인딩합니다.

이 매핑에는 다음이 적용됩니다.

  • 컨트롤러 및 작업 이름만을 기준으로 합니다.
  • 네임스페이스, 소스 파일 위치 또는 메서드 매개 변수를 기준으로 하지 않습니다.

기본 경로와 함께 규칙 기반 라우팅을 사용하면 각 작업마다 새 URL 패턴을 만들지 않고도 신속하게 앱을 만들 수 있습니다. CRUD 스타일 작업을 포함하는 앱의 경우, 컨트롤러 간의 URL이 일관되게 유지됩니다.

  • 코드를 간소화하는 데 유용합니다.
  • UI를 예측하기가 더 쉽습니다.

Warning

위의 코드에서 id는 경로 템플릿에서 선택 항목으로 정의됩니다. URL의 일부로 제공되는 선택적 ID 없이 작업을 실행할 수 있습니다. 일반적으로 URL에서 id가 생략되는 경우는 다음과 같습니다.

  • id가 모델 바인딩에 따라 0으로 설정됩니다.
  • 데이터베이스에서 id == 0과 일치하는 엔터티를 찾을 수 없습니다.

특성 라우팅을 사용하면 일부 작업에는 필요하고 다른 작업에는 필요하지 않은 ID를 만들기 위해 세밀하게 제어할 수 있습니다. 일반적으로 id 같은 선택적 매개 변수가 올바른 사용법으로 나타날 가능성이 있는 경우 설명서에 이러한 선택적 매개 변수가 포함됩니다.

대부분의 앱은 URL이 읽을 수 있고 의미 있도록 기본적이고 설명적인 라우팅 체계를 선택해야 합니다. 기본 기존 경로인 {controller=Home}/{action=Index}/{id?}는:

  • 기본적이고 서술적인 라우팅 체계를 지원합니다.
  • UI 기반 앱에 대한 유용한 시작점입니다.
  • 많은 웹 UI 앱에 필요한 유일한 경로 템플릿입니다. 규모가 큰 웹 UI 앱의 경우 영역을 사용하는 또 다른 경로를 사용하는 경우가 많습니다.

MapControllerRouteMapAreaRoute :

  • 호출된 순서를 기준으로 해당 엔드포인트에 순서 값을 자동으로 할당합니다.

ASP.NET Core 3.0 이상의 엔드포인트 라우팅은 다음과 같습니다.

  • 경로 개념이 없습니다.
  • 확장성 실행 순서를 보장하지 않으며 모든 엔드포인트가 한 번에 처리됩니다.

로깅을 사용하도록 설정하여 Route와 같은 기본 제공 라우팅 구현에서 요청과 일치시키는 방법을 확인하세요.

특성 라우팅은 이 문서의 뒷부분에서 설명합니다.

다중 규칙 기반 경로

MapControllerRouteMapAreaControllerRoute에 대한 더 많은 호출을 추가하여 여러 규칙 기반 경로UseEndpoints 내에 추가할 수 있습니다. 이렇게 하면 여러 규칙을 정의하거나 다음과 같이 특정 작업에만 사용되는 규칙 기반 경로를 추가할 수 있습니다.

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(name: "blog",
                pattern: "blog/{*article}",
                defaults: new { controller = "Blog", action = "Article" });
    endpoints.MapControllerRoute(name: "default",
                pattern: "{controller=Home}/{action=Index}/{id?}");
});

위의 코드에서 blog 경로는 전용 규칙 기반 경로입니다. 다음과 같은 이유 때문에 전용 규칙 기반 경로라고 합니다.

controlleraction은 경로 템플릿 "blog/{*article}"에 매개 변수로 표시되지 않기 때문입니다.

  • 기본값 { controller = "Blog", action = "Article" }만 지정할 수 있습니다.
  • 이 경로는 항상 작업 BlogController.Article에 매핑됩니다.

/Blog, /Blog/Article/Blog/{any-string}는 블로그 경로와 일치하는 유일한 URL 경로입니다.

위의 예제는 다음과 같습니다.

  • blog 경로는 먼저 추가되므로 default 경로보다 일치하는지 먼저 검색됩니다.
  • URL의 일부로 아티클 이름을 포함하는 것이 일반적인 동적 필드 스타일 라우팅의 예입니다.

Warning

ASP.NET Core 3.0 이상에서 라우팅은 다음을 수행하지 않습니다.

  • ‘경로’라는 개념을 정의합니다. UseRouting은 경로 일치를 미들웨어 파이프라인에 추가합니다. UseRouting 미들웨어는 앱에 정의된 엔드포인트 집합을 확인하고 요청을 기준으로 가장 일치하는 엔드포인트를 선택합니다.
  • IRouteConstraint 또는 IActionConstraint와 같은 확장성의 실행 순서를 보장합니다.

라우팅에 대한 참조 자료는 라우팅을 참조하세요.

규칙 기반 라우팅 순서

규칙 기반 라우팅은 앱에서 정의된 작업 및 컨트롤러의 조합만 일치시킵니다. 이것은 규칙 기반 경로가 중복되는 경우를 간소화하기 위한 것입니다. MapControllerRoute, MapDefaultControllerRouteMapAreaControllerRoute는 호출된 순서를 기준으로 해당 엔드포인트에 순서 값을 자동으로 할당합니다. 이전에 표시된 경로부터 일치 항목을 먼저 검색합니다. 규칙 기반 라우팅은 순서에 의존적입니다. 일반적으로 영역을 사용하는 경로가 영역을 사용하지 않는 경로에 비해 구체적이기 때문에 앞부분에 배치되어야 합니다. {*article}과 같은 catch-all 경로 매개 변수가 있는 전용 규칙 기반 경로는 경로를 너무 탐욕적으로 만들 수 있습니다. 즉, 다른 경로와 일치시키려고 하는 URL과 일치하게 됩니다. 탐욕적 일치를 방지하려면 탐욕적 경로를 경로 테이블에 배치합니다.

Warning

catch-all 매개 변수는 라우팅의 버그로 인해 경로와 일치하지 않을 수 있습니다. 이 버그의 영향을 받는 앱은 다음과 같은 특징이 있습니다.

  • catch-all 경로(예: {**slug}")
  • catch-all 경로가 일치해야 하는 요청과 일치하지 않습니다.
  • 다른 경로를 제거하면 catch-all 경로 작동이 시작됩니다.

이 버그에 해당하는 사례는 GitHub 버그 1867716579를 참조하세요.

이 버그에 대한 옵트인 픽스가 .NET Core 3.1.301 SDK 이상에 포함되어 있습니다. 다음 코드는 이 버그를 수정하는 내부 스위치를 설정합니다.

public static void Main(string[] args)
{
   AppContext.SetSwitch("Microsoft.AspNetCore.Routing.UseCorrectCatchAllBehavior", 
                         true);
   CreateHostBuilder(args).Build().Run();
}
// Remaining code removed for brevity.

모호한 작업 확인

두 엔드포인트가 라우팅을 통해 일치되면 라우팅은 다음 중 하나를 수행해야 합니다.

  • 가장 적합한 후보를 선택합니다.
  • 예외를 throw합니다.

예시:

    public class Products33Controller : Controller
    {
        public IActionResult Edit(int id)
        {
            return ControllerContext.MyDisplayRouteInfo(id);
        }

        [HttpPost]
        public IActionResult Edit(int id, Product product)
        {
            return ControllerContext.MyDisplayRouteInfo(id, product.name);
        }
    }
}

위의 컨트롤러는 다음과 일치하는 두 작업을 정의합니다.

  • URL 경로 /Products33/Edit/17
  • 경로 데이터 { controller = Products33, action = Edit, id = 17 }.

MVC 컨트롤러의 일반적인 패턴은 다음과 같습니다.

  • Edit(int)는 제품을 편집하기 위한 폼을 표시합니다.
  • Edit(int, Product)은 게시된 양식을 처리합니다.

올바른 경로를 확인하기 위해 다음이 수행됩니다.

  • 요청이 HTTP POST인 경우 Edit(int, Product)가 선택됩니다.
  • HTTP 동사가 다른 경우 Edit(int)가 선택됩니다. Edit(int)는 일반적으로 GET을 통해 호출됩니다.

요청의 HTTP 메서드에 따라 선택할 수 있도록 HttpPostAttribute, [HttpPost]가 라우팅에 제공됩니다. HttpPostAttributeEdit(int, Product)Edit(int)보다 더 잘 일치시키도록 만듭니다.

HttpPostAttribute와 같은 특성의 역할을 이해하는 것이 중요합니다. 유사한 특성이 다른 HTTP 동사에 대해 정의됩니다. 규칙 기반 라우팅에서는 작업이 폼 표시, 폼 제출 워크플로의 일부인 경우 작업에서 동일한 작업 이름을 사용하는 것이 일반적입니다. 예를 들어 두 개의 Edit 작업 메서드 검사를 참조하세요.

라우팅에서 가장 적합한 후보를 선택할 수 없는 경우 AmbiguousMatchException이 throw되어 일치하는 여러 엔드포인트가 나열됩니다.

규칙 기반 경로 이름

다음 예제의 "blog""default" 문자열은 규칙 경로 이름입니다.

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(name: "blog",
                pattern: "blog/{*article}",
                defaults: new { controller = "Blog", action = "Article" });
    endpoints.MapControllerRoute(name: "default",
                pattern: "{controller=Home}/{action=Index}/{id?}");
});

이 경로 이름은 경로에 논리적 이름을 지정합니다. 명명된 경로는 URL 생성에 사용할 수 있습니다. 명명된 경로를 사용하면 경로 순서 지정으로 인해 URL 생성이 복잡해질 수 있는 상황에서 URL 생성 방법이 매우 간단해집니다. 경로 이름은 애플리케이션 전체에서 고유해야 합니다.

경로 이름:

  • URL 일치 또는 요청 처리에는 영향을 주지 않습니다.
  • URL 생성에만 사용됩니다.

경로 이름 개념은 라우팅에서 IEndpointNameMetadata로 표시됩니다. 용어 경로 이름엔드포인트 이름은 다음과 같습니다.

  • 서로 교환하여 사용할 수 있습니다.
  • 설명서와 코드에 어떤 용어가 사용되는지는 설명되는 API에 따라 달라집니다.

REST API에 대한 특성 라우팅

REST API는 특성 라우팅을 사용하여 HTTP 동사로 작업을 나타내는 리소스 집합으로 앱의 기능을 모델링해야 합니다.

특성 라우팅은 특성 모음을 사용하여 작업을 경로 템플릿에 직접 매핑합니다. 다음 StartUp.Configure 코드는 REST API에 일반적이며 다음 샘플에서 사용합니다.

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseHttpsRedirection();

    app.UseRouting();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });
}

위의 코드에서 MapControllers는 특성 라우팅 컨트롤러를 매핑하기 위해 UseEndpoints 내부에서 호출됩니다.

다음 예제에서

  • HomeController는 기본 규칙 기반 경로 {controller=Home}/{action=Index}/{id?}가 일치시키는 경로와 유사한 URL 집합을 일치시킵니다.
public class HomeController : Controller
{
    [Route("")]
    [Route("Home")]
    [Route("Home/Index")]
    [Route("Home/Index/{id?}")]
    public IActionResult Index(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }

    [Route("Home/About")]
    [Route("Home/About/{id?}")]
    public IActionResult About(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

HomeController.Index 작업은 URL 경로 /, /Home, /Home/Index 또는 /Home/Index/3에 대해 실행됩니다.

이 예제에서는 특성 라우팅과 규칙 기반 라우팅 간의 주요 프로그래밍 차이점을 강조합니다. 특성 라우팅은 경로를 지정하기 위해 더 많은 입력이 필요합니다. 규칙 기반 기본 경로는 경로를 좀 더 간략하게 처리합니다. 그러나 특성 라우팅을 사용하면 각 작업에 적용되는 경로 템플릿을 정확하게 제어할 수 있습니다(또 그래야만 합니다).

특성 라우팅을 사용하면 토큰 교체를 사용하지 않는 한, 컨트롤러 및 작업 이름은 일치시키는 작업을 결정하는 데 영향을 미치지 않습니다. 다음 예제에서는 이전 예제와 동일한 URL을 일치시킵니다.

public class MyDemoController : Controller
{
    [Route("")]
    [Route("Home")]
    [Route("Home/Index")]
    [Route("Home/Index/{id?}")]
    public IActionResult MyIndex(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }

    [Route("Home/About")]
    [Route("Home/About/{id?}")]
    public IActionResult MyAbout(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

다음 코드에서는 actioncontroller에 대해 토큰 대체를 사용합니다.

public class HomeController : Controller
{
    [Route("")]
    [Route("Home")]
    [Route("[controller]/[action]")]
    public IActionResult Index()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [Route("[controller]/[action]")]
    public IActionResult About()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

다음 코드는 컨트롤러에 [Route("[controller]/[action]")]를 적용합니다.

[Route("[controller]/[action]")]
public class HomeController : Controller
{
    [Route("~/")]
    [Route("/Home")]
    [Route("~/Home/Index")]
    public IActionResult Index()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    public IActionResult About()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

위의 코드에서 Index 메서드 템플릿은 / 또는 ~/를 경로 템플릿 앞에 추가해야 합니다. 작업에 적용된 / 또는 ~/로 시작하는 경로 템플릿은 컨트롤러에 적용된 경로 템플릿과 결합되지 않습니다.

경로 템플릿 선택에 대한 내용은 경로 템플릿 우선 순위를 참조하세요.

예약된 라우팅 이름

컨트롤러 또는 Razor Pages를 사용하는 경우 다음 키워드는 예약된 경로 매개 변수 이름입니다.

  • action
  • area
  • controller
  • handler
  • page

특성 라우팅에 경로 매개 변수로 page를 사용하는 것은 일반적인 오류입니다. 이렇게 하면 URL 생성 시 일관되지 않은 혼란스러운 동작이 발생합니다.

public class MyDemo2Controller : Controller
{
    [Route("/articles/{page}")]
    public IActionResult ListArticles(int page)
    {
        return ControllerContext.MyDisplayRouteInfo(page);
    }
}

특수 매개 변수 이름은 URL 생성 작업에서 Razor Page 또는 컨트롤러를 참조하는지를 결정하는 데 사용됩니다.

다음 키워드는 Razor 뷰 또는 Razor 페이지의 컨텍스트에서 예약됩니다.

  • page
  • using
  • namespace
  • inject
  • section
  • inherits
  • model
  • addTagHelper
  • removeTagHelper

이러한 키워드는 링크 생성, 모델 바인딩 매개 변수 또는 최상위 속성에 사용하면 안 됩니다.

HTTP 동사 템플릿

ASP.NET Core에는 다음과 같은 HTTP 동사 템플릿이 있습니다.

경로 템플릿

ASP.NET Core에는 다음과 같은 경로 템플릿이 있습니다.

Http 동사 특성을 사용한 특성 라우팅

다음 컨트롤러를 고려해보세요.

[Route("api/[controller]")]
[ApiController]
public class Test2Controller : ControllerBase
{
    [HttpGet]   // GET /api/test2
    public IActionResult ListProducts()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [HttpGet("{id}")]   // GET /api/test2/xyz
    public IActionResult GetProduct(string id)
    {
       return ControllerContext.MyDisplayRouteInfo(id);
    }

    [HttpGet("int/{id:int}")] // GET /api/test2/int/3
    public IActionResult GetIntProduct(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }

    [HttpGet("int2/{id}")]  // GET /api/test2/int2/3
    public IActionResult GetInt2Product(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

위의 코드에서

  • 각 작업에는 HTTP GET 요청으로만 일치를 제한하는 [HttpGet] 특성이 포함됩니다.
  • GetProduct 작업에는 "{id}" 템플릿이 포함되므로 id가 컨트롤러의 "api/[controller]" 템플릿에 추가됩니다. 메서드 템플릿은 "api/[controller]/{id}"입니다. 따라서 이 작업은 /api/test2/xyz,/api/test2/123,/api/test2/{any string} 등의 폼에 대해서만 GET 요청을 일치시킵니다.
    [HttpGet("{id}")]   // GET /api/test2/xyz
    public IActionResult GetProduct(string id)
    {
       return ControllerContext.MyDisplayRouteInfo(id);
    }
    
  • GetIntProduct 작업에는 "int/{id:int}" 템플릿이 포함됩니다. 템플릿의 :int 부분은 id 경로 값을 정수로 변환할 수 있는 문자열로 제한합니다. 다음을 위한 GET 요청입니다./api/test2/int/abc
    • 이 작업을 일치시키지 않습니다.
    • 404 찾을 수 없음 오류를 반환합니다.
      [HttpGet("int/{id:int}")] // GET /api/test2/int/3
      public IActionResult GetIntProduct(int id)
      {
          return ControllerContext.MyDisplayRouteInfo(id);
      }
      
  • GetInt2Product 작업의 템플릿에는 {id}가 포함되지만 id를 정수로 변환할 수 있는 값으로 제한하지 않습니다. 다음을 위한 GET 요청입니다./api/test2/int2/abc
    • 이 경로를 일치시킵니다.
    • 모델 바인딩이 abc를 정수로 변환하지 못합니다. 메서드의 id 매개 변수는 정수입니다.
    • 모델 바인딩이 정수로 변환 abc 하지 못했기 때문에 400 잘못된 요청을 반환합니다.
      [HttpGet("int2/{id}")]  // GET /api/test2/int2/3
      public IActionResult GetInt2Product(int id)
      {
          return ControllerContext.MyDisplayRouteInfo(id);
      }
      

특성 라우팅은 HttpPostAttribute, HttpPutAttributeHttpDeleteAttribute와 같은 HttpMethodAttribute 특성을 사용할 수 있습니다. 모든 HTTP 동사 특성은 경로 템플릿을 허용합니다. 다음 예제는 동일한 경로 템플릿과 일치하는 두 가지 작업을 보여 줍니다.

[ApiController]
public class MyProductsController : ControllerBase
{
    [HttpGet("/products3")]
    public IActionResult ListProducts()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [HttpPost("/products3")]
    public IActionResult CreateProduct(MyProduct myProduct)
    {
        return ControllerContext.MyDisplayRouteInfo(myProduct.Name);
    }
}

URL 경로 /products3를 사용하면 다음 결과가 나타납니다.

  • MyProductsController.ListProducts 작업은 HTTP 동사GET일 때 실행됩니다.
  • MyProductsController.CreateProduct 작업은 HTTP 동사POST일 때 실행됩니다.

REST API를 빌드할 때 작업이 모든 HTTP 메서드를 허용하기 때문에 작업 메서드에 [Route(...)]를 사용해야 하는 경우는 드뭅니다. 보다 구체적인 HTTP 동사 특성을 사용하여 API에서 지원하는 항목을 정확하게 지정하는 것이 좋습니다. REST API의 클라이언트는 특정 논리 작업에 매핑되는 경로 및 HTTP 동사를 알아야 합니다.

REST API는 특성 라우팅을 사용하여 HTTP 동사로 작업을 나타내는 리소스 집합으로 앱의 기능을 모델링해야 합니다. 이는 동일한 논리 리소스의 많은 작업(예: GET 및 POST)이 동일한 URL을 사용한다는 뜻입니다. 특성 라우팅은 API의 공용 엔드포인트 레이아웃을 신중하게 설계하는 데 필요한 제어 수준을 제공합니다.

특성 경로는 특정 작업에 적용되므로 경로 템플릿 정의의 일환으로 필요한 매개 변수를 간단하게 만들 수 있습니다. 다음 예제에서 id는 URL 경로에 포함해야 합니다.

[ApiController]
public class Products2ApiController : ControllerBase
{
    [HttpGet("/products2/{id}", Name = "Products_List")]
    public IActionResult GetProduct(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

Products2ApiController.GetProduct(int) 작업은 다음과 같습니다.

  • /products2/3와 같은 URL 경로를 통해 실행됩니다.
  • URL 경로 /products2를 사용하여 실행되지 않습니다.

[사용] 특성을 사용하면 작업에서 지원되는 요청 콘텐츠 형식을 제한할 수 있습니다. 자세한 내용은 Consumes 특성을 사용하여 지원되는 요청 콘텐츠 형식 정의를 참조하세요.

경로 템플릿 및 관련 옵션에 대한 전체 설명은 라우팅을 참조하세요.

[ApiController]에 대한 자세한 내용은 ApiController 특성을 참조하세요.

경로 이름

다음 코드는 Products_List의 경로 이름을 정의합니다.

[ApiController]
public class Products2ApiController : ControllerBase
{
    [HttpGet("/products2/{id}", Name = "Products_List")]
    public IActionResult GetProduct(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

경로 이름을 사용하여 특정 경로 기반의 URL을 생성할 수 있습니다. 경로 이름:

  • 라우팅의 URL 일치 동작에는 영향을 주지 않습니다.
  • URL 생성에만 사용됩니다.

경로 이름은 애플리케이션 전체에서 고유해야 합니다.

id 매개 변수를 선택 사항({id?})으로 정의하는 규칙 기반 기본 경로를 사용하는 이전 코드와는 대조적입니다. API를 정확하게 지정하는 기능은 /products/products/5를 서로 다른 작업에 디스패치할 수 있는 등의 장점이 있습니다.

특성 경로 결합

특성 라우팅의 반복 횟수를 줄이기 위해 컨트롤러의 경로 특성은 개별 작업의 경로 특성과 결합됩니다. 컨트롤러에 정의된 경로 템플릿은 작업의 경로 템플릿에 앞에 추가됩니다. 경로 특성을 컨트롤러에 배치하면 컨트롤러의 모든 작업이 특성 라우팅을 사용합니다.

[ApiController]
[Route("products")]
public class ProductsApiController : ControllerBase
{
    [HttpGet]
    public IActionResult ListProducts()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [HttpGet("{id}")]
    public IActionResult GetProduct(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

앞의 예에서:

  • URL 경로 /productsProductsApi.ListProducts와 일치할 수 있습니다.
  • URL 경로 /products/5ProductsApi.GetProduct(int)와 일치할 수 있습니다.

이러한 두 작업은 모두 [HttpGet] 특성으로 표시되기 때문에 HTTP GET만 일치시킵니다.

작업에 적용된 / 또는 ~/로 시작하는 경로 템플릿은 컨트롤러에 적용된 경로 템플릿과 결합되지 않습니다. 다음 예제는 기본 경로와 비슷한 URL 경로 집합을 일치시킵니다.

[Route("Home")]
public class HomeController : Controller
{
    [Route("")]
    [Route("Index")]
    [Route("/")]
    public IActionResult Index()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [Route("About")]
    public IActionResult About()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

다음 표에서는 이전 코드의 [Route] 특성에 대해 설명합니다.

Attribute [Route("Home")]과 결합합니다. 경로 템플릿을 정의합니다.
[Route("")] "Home"
[Route("Index")] "Home/Index"
[Route("/")] 아니요 ""
[Route("About")] "Home/About"

특성 경로 순서

라우팅은 트리를 빌드하고 모든 엔드포인트를 동시에 일치시킵니다.

  • 경로 항목은 이상적인 순서로 배치된 것처럼 동작합니다.
  • 가장 구체적인 경로는 보다 일반적인 경로보다 먼저 실행될 가능성이 있습니다.

예를 들어 blog/search/{topic}과 같은 특성 경로는 blog/{*article}과 같은 특성 경로보다 더 구체적입니다. blog/search/{topic} 경로는 더 구체적이기 때문에 기본적으로 우선 순위가 높습니다. 규칙 기반 라우팅을 사용하면 개발자가 원하는 순서대로 경로를 배치해야 합니다.

특성 경로는 Order 속성을 사용하여 순서를 구성할 수 있습니다. 모든 프레임워크 제공 경로 특성에는 Order가 포함됩니다. 경로는 Order 속성의 오름차순 정렬에 따라 처리됩니다. 기본 순서는 0입니다. Order = -1을 사용한 경로 설정은 순서를 설정하지 않는 경로보다 먼저 실행됩니다. Order = 1을 사용한 경로 설정은 기본 경로 순서보다 늦게 실행됩니다.

Order에 따라 결정하지 않도록 합니다. 앱의 URL 공간에 올바른 라우팅을 위한 명시적 순서 값이 필요한 경우 클라이언트에서도 혼란이 발생할 수 있습니다. 일반적으로 특성 라우팅은 URL이 일치하는 올바른 경로를 선택합니다. URL 생성에 사용되는 기본 순서가 작동하지 않는 경우 일반적으로 경로 이름을 우선적으로 사용하는 것이 Order 속성을 적용하는 것보다 간단합니다.

둘 다 경로 일치 /home을 정의하는 다음 두 컨트롤러를 고려합니다.

public class HomeController : Controller
{
    [Route("")]
    [Route("Home")]
    [Route("Home/Index")]
    [Route("Home/Index/{id?}")]
    public IActionResult Index(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }

    [Route("Home/About")]
    [Route("Home/About/{id?}")]
    public IActionResult About(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}
public class MyDemoController : Controller
{
    [Route("")]
    [Route("Home")]
    [Route("Home/Index")]
    [Route("Home/Index/{id?}")]
    public IActionResult MyIndex(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }

    [Route("Home/About")]
    [Route("Home/About/{id?}")]
    public IActionResult MyAbout(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

위의 코드를 사용하여 /home을 요청하면 다음과 유사한 예외가 throw됩니다.

AmbiguousMatchException: The request matched multiple endpoints. Matches:

 WebMvcRouting.Controllers.HomeController.Index
 WebMvcRouting.Controllers.MyDemoController.MyIndex

경로 특성 중 하나에 Order를 추가하면 모호성이 해결됩니다.

[Route("")]
[Route("Home", Order = 2)]
[Route("Home/MyIndex")]
public IActionResult MyIndex()
{
    return ControllerContext.MyDisplayRouteInfo();
}

위의 코드를 통해 /homeHomeController.Index 엔드포인트를 실행합니다. MyDemoController.MyIndex로 돌아가려면 /home/MyIndex를 요청합니다. :

  • 위의 코드는 예제이거나 적절하지 못한 라우팅 디자인입니다. Order 속성을 설명하는 데 사용되었습니다.
  • Order 속성은 모호성만 해결하며, 해당 템플릿은 일치시킬 수 없습니다. [Route("Home")] 템플릿을 제거하는 것이 더 바람직합니다.

Razor Pages를 사용한 경로 순서에 대한 내용은 Razor Pages 경로 및 앱 규칙: 경로 순서를 참조하세요.

일부 경우에는 모호한 경로를 사용하여 HTTP 500 오류가 반환됩니다. 로깅을 사용하여 AmbiguousMatchException을 발생시킨 엔드포인트를 확인합니다.

경로 템플릿 [컨트롤러], [작업], [영역]에서 토큰 바꾸기

편의를 위해 특성 경로는 토큰을 대괄호([, ])로 묶는 방식으로 ‘토큰 교체’를 지원합니다. [action], [area][controller] 토큰은 경로가 정의된 작업의 작업 이름, 영역 이름 및 컨트롤러 이름으로 교체됩니다.

[Route("[controller]/[action]")]
public class Products0Controller : Controller
{
    [HttpGet]
    public IActionResult List()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }


    [HttpGet("{id}")]
    public IActionResult Edit(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

위의 코드에서

[HttpGet]
public IActionResult List()
{
    return ControllerContext.MyDisplayRouteInfo();
}
  • /Products0/List를 일치시킵니다.
[HttpGet("{id}")]
public IActionResult Edit(int id)
{
    return ControllerContext.MyDisplayRouteInfo(id);
}
  • /Products0/Edit/{id}를 일치시킵니다.

토큰 교체는 특성 경로 빌드 과정의 마지막 단계로 발생합니다. 앞의 예제는 다음 코드와 동일하게 동작합니다.

public class Products20Controller : Controller
{
    [HttpGet("[controller]/[action]")]  // Matches '/Products20/List'
    public IActionResult List()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [HttpGet("[controller]/[action]/{id}")]   // Matches '/Products20/Edit/{id}'
    public IActionResult Edit(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

영어가 아닌 다른 언어로 이 문서를 읽는 경우 네이티브 언어로 코드 주석을 보려면 이 GitHub 토론 문제에 알려주세요.

특성 경로를 상속과 결합할 수도 있습니다. 특히 토큰 교체와 결합하면 더욱 강력합니다. 토큰 교체는 특성 경로에 정의된 경로 이름에도 적용됩니다. [Route("[controller]/[action]", Name="[controller]_[action]")]은 각 작업에 대해 고유한 경로 이름을 생성합니다.

[ApiController]
[Route("api/[controller]/[action]", Name = "[controller]_[action]")]
public abstract class MyBase2Controller : ControllerBase
{
}

public class Products11Controller : MyBase2Controller
{
    [HttpGet]                      // /api/products11/list
    public IActionResult List()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [HttpGet("{id}")]             //    /api/products11/edit/3
    public IActionResult Edit(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

리터럴 토큰 교체 구분 기호 [or]을 매칭하려면 문자([[or]])를 반복하여 이스케이프합니다.

매개 변수 변환기를 사용하여 토큰 교체 사용자 지정

매개 변수 변환기를 사용하여 토큰 교체를 사용자 지정할 수 있습니다. 매개 변수 변환기는 IOutboundParameterTransformer를 구현하며 매개 변수의 값을 변환합니다. 예를 들어 사용자 지정 SlugifyParameterTransformer 매개 변수 변환기는 SubscriptionManagement 경로 값을 subscription-management로 변경합니다.

public class SlugifyParameterTransformer : IOutboundParameterTransformer
{
    public string TransformOutbound(object value)
    {
        if (value == null) { return null; }

        return Regex.Replace(value.ToString(),
                             "([a-z])([A-Z])",
                             "$1-$2",
                             RegexOptions.CultureInvariant,
                             TimeSpan.FromMilliseconds(100)).ToLowerInvariant();
    }
}

RouteTokenTransformerConvention은 다음과 같은 응용 프로그램 모델 규칙입니다.

  • 애플리케이션의 모든 특성 경로에 매개 변수 변환기를 적용합니다.
  • 대체되는 특성 경로 토큰 값을 사용자 지정합니다.
public class SubscriptionManagementController : Controller
{
    [HttpGet("[controller]/[action]")]
    public IActionResult ListAll()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

앞의 ListAll 메서드는 /subscription-management/list-all을 일치시킵니다.

RouteTokenTransformerConventionConfigureServices에 옵션으로 등록됩니다.

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews(options =>
    {
        options.Conventions.Add(new RouteTokenTransformerConvention(
                                     new SlugifyParameterTransformer()));
    });
}

동적 필드의 정의에 대해서는 동적 필드에 대한 MDN 웹 문서를 참조하세요.

Warning

System.Text.RegularExpressions를 사용하여 신뢰할 수 없는 입력을 처리하는 경우 시간 제한을 전달합니다. 악의적인 사용자가 RegularExpressions에 대한 입력을 제공하여 서비스 거부 공격을 일으킬 수 있습니다. RegularExpressions를 사용하는 ASP.NET Core Framework API는 시간 제한을 전달합니다.

여러 특성 경로

특성 경로는 동일한 작업에 도달하는 여러 경로를 정의하는 것을 지원합니다. 가장 일반적인 사용 방법은 다음 예제와 같이 기본 규칙 기반 경로의 동작을 모방하는 것입니다.

[Route("[controller]")]
public class Products13Controller : Controller
{
    [Route("")]     // Matches 'Products13'
    [Route("Index")] // Matches 'Products13/Index'
    public IActionResult Index()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

컨트롤러에 여러 경로 특성을 배치하면 각 경로 특성이 작업 메서드의 각 경로 특성과 결합됩니다.

[Route("Store")]
[Route("[controller]")]
public class Products6Controller : Controller
{
    [HttpPost("Buy")]       // Matches 'Products6/Buy' and 'Store/Buy'
    [HttpPost("Checkout")]  // Matches 'Products6/Checkout' and 'Store/Checkout'
    public IActionResult Buy()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

모든 HTTP 동사 경로 제약 조건은 IActionConstraint를 구현합니다.

IActionConstraint를 구현하는 여러 경로 특성이 작업에 배치되면 다음이 수행됩니다.

  • 각 작업 제약 조건은 컨트롤러에 적용된 경로 템플릿과 결합됩니다.
[Route("api/[controller]")]
public class Products7Controller : ControllerBase
{
    [HttpPut("Buy")]        // Matches PUT 'api/Products7/Buy'
    [HttpPost("Checkout")]  // Matches POST 'api/Products7/Checkout'
    public IActionResult Buy()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

작업에 여러 경로를 사용하면 유용하고 강력해 보일 수 있습니다. 앱의 URL 공간을 기본적이고 잘 정의된 상태로 유지하는 것이 좋습니다. 기존 클라이언트를 지원하려는 경우처럼 꼭 필요한 경우에만 작업에 여러 경로를 사용합니다.

특성 경로 선택적 매개 변수, 기본값 및 제약 조건 지정

특성 경로는 선택적 매개 변수, 기본값 및 제약 조건을 지정할 수 있는 규칙 기반 경로와 동일한 인라인 구문을 지원합니다.

public class Products14Controller : Controller
{
    [HttpPost("product14/{id:int}")]
    public IActionResult ShowProduct(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

위의 코드에서 [HttpPost("product14/{id:int}")]는 경로 제약 조건을 적용합니다. Products14Controller.ShowProduct 작업은 /product14/3과 같은 URL 경로로만 일치됩니다. 경로 템플릿 부분 {id:int}는 해당 세그먼트를 정수로 제한합니다.

경로 템플릿 구문에 대한 자세한 설명은 경로 템플릿 참조를 참조하세요.

IRouteTemplateProvider를 사용하는 사용자 지정 경로 특성

모든 경로 특성IRouteTemplateProvider를 구현합니다. ASP.NET Core 런타임은 다음을 수행합니다.

  • 앱이 시작될 때 컨트롤러 클래스 및 작업 메서드에서 특성을 찾습니다.
  • IRouteTemplateProvider를 구현하는 특성을 사용하여 초기 경로 집합을 작성합니다.

IRouteTemplateProvider를 구현하여 사용자 지정 경로 특성을 정의합니다. 각각의 IRouteTemplateProvider를 사용하여 사용자 지정 경로 템플릿, 순서 및 이름으로 단일 경로를 정의할 수 있습니다.

public class MyApiControllerAttribute : Attribute, IRouteTemplateProvider
{
    public string Template => "api/[controller]";
    public int? Order => 2;
    public string Name { get; set; }
}

[MyApiController]
[ApiController]
public class MyTestApiController : ControllerBase
{
    // GET /api/MyTestApi
    [HttpGet]
    public IActionResult Get()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

앞의 Get 메서드는 Order = 2, Template = api/MyTestApi를 반환합니다.

애플리케이션 모델을 사용하여 특성 경로 사용자 지정

애플리케이션 모델은 다음과 같습니다.

  • 시작할 때 만든 개체 모델입니다.
  • 앱에서 작업을 라우팅하고 실행하기 위해 ASP.NET Core가 사용하는 모든 메타데이터를 포함합니다.

애플리케이션 모델은 경로 특성으로부터 수집한 모든 데이터를 포함합니다. 경로 특성의 데이터는 IRouteTemplateProvider 구현에 의해 제공됩니다. 규칙:

  • 애플리케이션 모델을 수정하여 라우팅 동작 방식을 사용자 지정하도록 작성할 수 있습니다.
  • 앱 시작 시 읽습니다.

이 섹션에서는 애플리케이션 모델을 사용하여 라우팅을 사용자 지정하는 기본적인 예제를 보여 줍니다. 다음 코드는 경로가 프로젝트의 폴더 구조와 거의 일치하게 만듭니다.

public class NamespaceRoutingConvention : Attribute, IControllerModelConvention
{
    private readonly string _baseNamespace;

    public NamespaceRoutingConvention(string baseNamespace)
    {
        _baseNamespace = baseNamespace;
    }

    public void Apply(ControllerModel controller)
    {
        var hasRouteAttributes = controller.Selectors.Any(selector =>
                                                selector.AttributeRouteModel != null);
        if (hasRouteAttributes)
        {
            return;
        }

        var namespc = controller.ControllerType.Namespace;
        if (namespc == null)
            return;
        var template = new StringBuilder();
        template.Append(namespc, _baseNamespace.Length + 1,
                        namespc.Length - _baseNamespace.Length - 1);
        template.Replace('.', '/');
        template.Append("/[controller]/[action]/{id?}");

        foreach (var selector in controller.Selectors)
        {
            selector.AttributeRouteModel = new AttributeRouteModel()
            {
                Template = template.ToString()
            };
        }
    }
}

다음 코드는 특성이 라우팅되는 컨트롤러에 namespace 규칙이 적용되지 않도록 합니다.

public void Apply(ControllerModel controller)
{
    var hasRouteAttributes = controller.Selectors.Any(selector =>
                                            selector.AttributeRouteModel != null);
    if (hasRouteAttributes)
    {
        return;
    }

예를 들어 다음 컨트롤러는 NamespaceRoutingConvention을 사용하지 않습니다.

[Route("[controller]/[action]/{id?}")]
public class ManagersController : Controller
{
    // /managers/index
    public IActionResult Index()
    {
        var template = ControllerContext.ActionDescriptor.AttributeRouteInfo?.Template;
        return Content($"Index- template:{template}");
    }

    public IActionResult List(int? id)
    {
        var path = Request.Path.Value;
        return Content($"List- Path:{path}");
    }
}

NamespaceRoutingConvention.Apply 메서드는 다음 작업을 수행합니다.

  • 컨트롤러가 특성 라우팅된 경우 아무 것도 수행하지 않습니다.
  • 기본 namespace를 제거한 상태로 namespace를 기준으로 하는 컨트롤러 템플릿을 설정합니다.

NamespaceRoutingConventionStartup.ConfigureServices에 적용할 수 있습니다.

namespace My.Application
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews(options =>
            {
                options.Conventions.Add(
                    new NamespaceRoutingConvention(typeof(Startup).Namespace));
            });
        }
        // Remaining code ommitted for brevity.

다음 컨트롤러를 예로 들 수 있습니다.

using Microsoft.AspNetCore.Mvc;

namespace My.Application.Admin.Controllers
{
    public class UsersController : Controller
    {
        // GET /admin/controllers/users/index
        public IActionResult Index()
        {
            var fullname = typeof(UsersController).FullName;
            var template = 
                ControllerContext.ActionDescriptor.AttributeRouteInfo?.Template;
            var path = Request.Path.Value;

            return Content($"Path: {path} fullname: {fullname}  template:{template}");
        }

        public IActionResult List(int? id)
        {
            var path = Request.Path.Value;
            return Content($"Path: {path} ID:{id}");
        }
    }
}

위의 코드에서

  • 기본 namespaceMy.Application입니다.
  • 이전 컨트롤러의 전체 이름은 My.Application.Admin.Controllers.UsersController입니다.
  • NamespaceRoutingConvention은 컨트롤러 템플릿을 Admin/Controllers/Users/[action]/{id?로 설정합니다.

NamespaceRoutingConvention을 컨트롤러의 특성으로 적용할 수도 있습니다.

[NamespaceRoutingConvention("My.Application")]
public class TestController : Controller
{
    // /admin/controllers/test/index
    public IActionResult Index()
    {
        var template = ControllerContext.ActionDescriptor.AttributeRouteInfo?.Template;
        var actionname = ControllerContext.ActionDescriptor.ActionName;
        return Content($"Action- {actionname} template:{template}");
    }

    public IActionResult List(int? id)
    {
        var path = Request.Path.Value;
        return Content($"List- Path:{path}");
    }
}

혼합 라우팅: 특성 라우팅 및 규칙 기반 라우팅

ASP.NET Core 앱은 규칙 기반 라우팅과 특성 라우팅을 혼합해서 사용할 수 있습니다. 일반적으로 브라우저에 HTML 페이지를 제공하는 컨트롤러에 대한 기존 경로와 REST API를 제공하는 컨트롤러에 대한 특성 라우팅을 사용합니다.

작업은 규약 기반으로 라우팅되거나 특성 라우팅됩니다. 컨트롤러 또는 작업에 경로를 배치하면 해당 경로가 특성 라우팅됩니다. 특성 경로를 정의하는 동작은 규칙 기반 경로를 통해 도달할 수 없으며 그 반대도 마찬가지입니다. 컨트롤러의 모든 경로 특성은 컨트롤러에 있는 모든 작업에서 특성 라우팅을 사용하게 만듭니다.

특성 라우팅 및 규칙 기반 라우팅은 동일한 라우팅 엔진을 사용합니다.

URL 생성 및 앰비언트 값

앱은 라우팅의 URL 생성 기능을 사용하여 작업에 대한 URL 링크를 생성할 수 있습니다. URL을 생성하면 하드 코딩된 URL이 제거되어 코드를 더욱 견고하게 유지할 수 있습니다. 이 섹션에서는 MVC에서 제공하는 URL 생성 기능을 중심으로 기본적인 URL 생성 원리에 대해서만 다룰 것입니다. URL 생성에 대한 자세한 설명은 라우팅을 참조하세요.

IUrlHelper 인터페이스는 URL 생성을 위한 MVC와 라우팅 간의 기본 인프라 요소입니다. 컨트롤러, 보기 및 보기 구성 요소의 Url 속성을 통해서 사용 가능한 IUrlHelper의 인스턴스를 찾을 수 있습니다.

다음 예제에서 IUrlHelper 인터페이스는 Controller.Url 속성을 통해 다른 작업에 대한 URL을 생성하는 데 사용됩니다.

public class UrlGenerationController : Controller
{
    public IActionResult Source()
    {
        // Generates /UrlGeneration/Destination
        var url = Url.Action("Destination");
        return ControllerContext.MyDisplayRouteInfo("", $" URL = {url}");
    }

    public IActionResult Destination()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

앱이 기본 규칙 기반 경로를 사용할 경우 url 변수의 값은 URL 경로 문자열 /UrlGeneration/Destination입니다. 이 URL 경로는 다음을 결합하여 라우팅하여 생성합니다.

  • 앰비언트 값이라고 하는 현재 요청의 경로 값
  • Url.Action에 전달되는 값으로, 해당 값을 경로 템플릿으로 대체합니다.
ambient values: { controller = "UrlGeneration", action = "Source" }
values passed to Url.Action: { controller = "UrlGeneration", action = "Destination" }
route template: {controller}/{action}/{id?}

result: /UrlGeneration/Destination

경로 템플릿에 있는 각 경로 매개 변수의 값은 이름을 값 및 앰비언트 값과 매칭한 값으로 바뀝니다. 값이 없는 경로 매개 변수는 다음과 같을 수 있습니다.

  • 기본값이 있는 경우 기본값을 사용합니다.
  • 선택 사항인 경우 생략됩니다. 경로 템플릿 {controller}/{action}/{id?}id를 예로 들 수 있습니다.

필요한 경로 매개 변수에 해당 값이 없는 경우 URL 생성에 실패합니다. 경로에 대한 URL 생성이 실패하면 모든 경로를 시도하거나 일치 항목을 찾을 때까지 그 다음 경로를 시도합니다.

위의 Url.Action 예에서는 규칙 기반 라우팅을 가정합니다. 개념은 서로 다르지만 URL 생성은 특성 라우팅과 유사하게 작동합니다. 규칙 기반 라우팅을 사용하는 경우:

  • 경로 값은 템플릿을 확장하는 데 사용됩니다.
  • controlleraction에 대한 경로 값은 일반적으로 해당 템플릿에 표시됩니다. 이는 라우팅에 의해 일치하는 URL이 규칙을 준수하기 때문에 작동합니다.

다음 예제는 특성 라우팅을 사용합니다.

public class UrlGenerationAttrController : Controller
{
    [HttpGet("custom")]
    public IActionResult Source()
    {
        var url = Url.Action("Destination");
        return ControllerContext.MyDisplayRouteInfo("", $" URL = {url}");
    }

    [HttpGet("custom/url/to/destination")]
    public IActionResult Destination()
    {
       return ControllerContext.MyDisplayRouteInfo();
    }
}

위의 코드에서 Source 작업은 custom/url/to/destination을 생성합니다.

LinkGeneratorIUrlHelper의 대안으로 ASP.NET Core 3.0에 추가되었습니다. LinkGenerator는 유사하지만 좀 더 유연한 기능을 제공합니다. IUrlHelper의 각 메서드에는 LinkGenerator에 대한 해당 메서드 패밀리도 있습니다.

작업 이름으로 URL 생성

Url.Action, LinkGenerator.GetPathByAction 및 모든 관련된 오버로드는 컨트롤러 이름과 작업 이름을 지정하여 대상 엔드포인트를 생성하도록 디자인되었습니다.

Url.Action을 사용하는 경우 controlleraction에 대한 현재 경로 값이 런타임에서 제공됩니다.

  • controlleraction의 값은 앰비언트 값 및 값 둘 다에 속합니다. Url.Action 메서드는 항상 actioncontroller의 현재 값을 사용하며 현재 작업에 라우팅하는 URL 경로를 생성합니다.

라우팅은 앰비언트 값의 값을 사용하여 개발자가 URL을 생성할 때 제공되지 않은 정보를 채웁니다. 앰비언트 값 { a = Alice, b = Bob, c = Carol, d = David }를 갖는 {a}/{b}/{c}/{d}와 같은 경로를 고려합니다.

  • 라우팅에는 추가 값 없이 URL을 생성하기에 충분한 정보가 있습니다.
  • 모든 경로 매개 변수에 값이 있으므로 라우팅에 충분한 정보가 있습니다.

{ d = Donovan }이 추가된 경우:

  • { d = David } 값이 무시됩니다.
  • 생성된 URL 경로는 Alice/Bob/Carol/Donovan입니다.

경고: URL 경로는 계층적입니다. 앞의 예제에서 다음과 같이 값 { c = Cheryl }이 추가됩니다.

  • 두 값 { c = Carol, d = David }은 모두 무시됩니다.
  • d에 대한 값이 더 이상 없으며 URL 생성이 실패합니다.
  • URL을 생성하려면 cd의 원하는 값을 지정해야 합니다.

기본 경로 {controller}/{action}/{id?}를 사용할 때 이 문제가 발생할 수 있습니다. Url.Action은 항상 명시적으로 controlleraction 값을 지정하기 때문에 이 문제는 드물게 발생합니다.

Url.Action을 몇 번 오버로드하면 controlleraction 이외의 경로 매개 변수의 값을 제공하기 위한 경로 값 개체가 사용됩니다. 경로 값 개체는 id와 함께 자주 사용됩니다. 예: Url.Action("Buy", "Products", new { id = 17 }). 경로 값 개체에는 다음이 적용됩니다.

  • 규칙에 따라 일반적으로 무명 형식의 개체입니다.
  • IDictionary<> 또는 POCO일 수 있습니다.

경로 매개 변수와 일치하지 않는 추가 경로 값들은 쿼리 문자열에 포함됩니다.

public IActionResult Index()
{
    var url = Url.Action("Buy", "Products", new { id = 17, color = "red" });
    return Content(url);
}

위의 코드는 /Products/Buy/17?color=red를 생성합니다.

다음 코드는 절대 URL을 생성합니다.

public IActionResult Index2()
{
    var url = Url.Action("Buy", "Products", new { id = 17 }, protocol: Request.Scheme);
    // Returns https://localhost:5001/Products/Buy/17
    return Content(url);
}

절대 URL을 만들려면 다음 중 하나를 사용합니다.

  • protocol을 허용하는 오버로드입니다. 앞의 코드를 예로 들 수 있습니다.
  • LinkGenerator.GetUriByAction은 기본적으로 절대 URL을 생성합니다.

경로별 URL 생성

이전 코드에서는 컨트롤러 및 작업 이름을 전달하여 URL을 생성하는 방법을 보여 줍니다. IUrlHelperUrl.RouteUrl 메서드 패밀리도 제공합니다. 이러한 메서드는 Url.Action과 비슷하지만 actioncontroller의 현재 값을 경로 값에 복사하지 않습니다. 가장 일반적인 Url.RouteUrl 사용에서는 다음이 수행됩니다.

  • URL을 생성할 경로 이름을 지정합니다.
  • 일반적으로 컨트롤러 또는 작업 이름은 지정하지 않습니다.
public class UrlGeneration2Controller : Controller
{
    [HttpGet("")]
    public IActionResult Source()
    {
        var url = Url.RouteUrl("Destination_Route");
        return ControllerContext.MyDisplayRouteInfo("", $" URL = {url}");
    }

    [HttpGet("custom/url/to/destination2", Name = "Destination_Route")]
    public IActionResult Destination()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

다음 Razor 파일은 Destination_Route에 대한 HTML 링크를 생성합니다.

<h1>Test Links</h1>

<ul>
    <li><a href="@Url.RouteUrl("Destination_Route")">Test Destination_Route</a></li>
</ul>

HTML 및 Razor에서 URL 생성

IHtmlHelper는 각각 <form><a> 요소를 생성하기 위해 HtmlHelper 메서드 Html.BeginFormHtml.ActionLink를 제공합니다. 이러한 메서드는 Url.Action 메서드를 사용하여 URL을 생성하며 비슷한 인수를 받습니다. HtmlHelper에 대한 Url.RouteUrl 보조 도구는 Html.BeginRouteFormHtml.RouteLink이며 서로 기능이 비슷합니다.

TagHelper는 form TagHelper 및 <a> TagHelper를 통해 URL을 생성합니다. 둘 다 구현에 IUrlHelper를 사용합니다. 자세한 내용은 폼의 태그 도우미를 참조하세요.

보기 내에서 IUrlHelperUrl 속성을 통해서 위에서 다루지 않은 임시 URL을 생성하기 위해 사용할 수 있습니다.

작업 결과의 URL 생성

위의 예제는 컨트롤러에서 IUrlHelper를 사용하는 방법을 보여 주었습니다. 컨트롤러에서 가장 일반적인 사용 방법은 URL을 작업 결과의 일부로 생성하는 것입니다.

ControllerBaseController 기본 클래스는 다른 작업을 참조하는 작업 결과에 대한 편의 메서드를 제공합니다. 한 가지 일반적인 사용법은 사용자 입력을 수락한 후 리디렉션하는 것입니다.

[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Edit(int id, Customer customer)
{
    if (ModelState.IsValid)
    {
        // Update DB with new details.
        ViewData["Message"] = $"Successful edit of customer {id}";
        return RedirectToAction("Index");
    }
    return View(customer);
}

RedirectToActionCreatedAtAction과 같은 작업 결과 팩터리 메서드는 IUrlHelper의 메서드와 비슷한 패턴을 따릅니다.

전용 규칙 기반 경로의 특별한 사례

규칙 기반 라우팅전용 규칙 기반 경로라고 하는 특별한 경로 정의를 사용할 수 있습니다. 다음 예제에서 blog라는 이름의 경로는 전용 규칙 기반 경로입니다.

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(name: "blog",
                pattern: "blog/{*article}",
                defaults: new { controller = "Blog", action = "Article" });
    endpoints.MapControllerRoute(name: "default",
                pattern: "{controller=Home}/{action=Index}/{id?}");
});

위의 경로 정의에서 Url.Action("Index", "Home")default 경로를 사용하여 URL 경로 /를 생성합니다. 하지만 그 이유는 무엇일까요? { controller = Home, action = Index } 경로 값이면 blog를 사용하여 URL을 생성하기에 충분하며, 그 결과는 /blog?action=Index&controller=Home가 될 것이라고 추측할 수도 있습니다.

전용 규칙 기반 경로는 URL 생성과 관련하여 너무 탐욕적인 경로가 되지 않도록 하는 해당 경로 매개 변수가 없는 기본 값의 특별한 동작을 사용합니다. 이 예제에서 기본값은 { controller = Blog, action = Article }이고, controlleraction이 경로 매개 변수로 표시되지 않습니다. 라우팅이 URL 생성을 수행할 때 입력한 값이 기본값과 일치해야 합니다. { controller = Home, action = Index } 값이 { controller = Blog, action = Article }과 일치하지 않기 때문에 blog를 사용한 URL 생성은 실패합니다. 그 후 라우팅이 default로 대체되고, 이것은 성공합니다.

지역

영역은 관련 기능을 다음과 같은 별도의 그룹으로 구성하는 데 사용되는 MVC 기능입니다.

  • 컨트롤러 작업에 대한 라우팅 네임스페이스
  • 뷰용 폴더 구조

영역을 사용하면 컨트롤러의 영역이 서로 다른 한, 앱 하나에서 이름이 같은 컨트롤러를 여러 개 사용할 수 있습니다. 영역을 사용하면 또 다른 경로 매개 변수 areacontrolleraction에 추가하여 라우팅을 위한 계층 구조를 생성합니다. 이 섹션에서는 라우팅이 영역과 상호 작용하는 방법을 설명합니다. 영역을 뷰에서 사용하는 방법에 대한 자세한 내용은 영역을 참조하세요.

다음 예제는 기본 규칙 기반 경로 및 Blog라는 이름의 area에 대한 area 경로를 사용하도록 MVC를 구성합니다.

app.UseEndpoints(endpoints =>
{
    endpoints.MapAreaControllerRoute("blog_route", "Blog",
        "Manage/{controller}/{action}/{id?}");
    endpoints.MapControllerRoute("default_route", "{controller}/{action}/{id?}");
});

위의 코드에서 MapAreaControllerRoute"blog_route"를 만들기 위해 호출됩니다. 두 번째 매개 변수인 "Blog"는 영역 이름입니다.

/Manage/Users/AddUser와 같은 URL 경로를 일치시킬 경우 "blog_route" 경로는 경로 값 { area = Blog, controller = Users, action = AddUser }를 생성합니다. area 경로 값은 area에 대한 기본값으로 생성됩니다. MapAreaControllerRoute에서 만든 경로는 다음과 같습니다.

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute("blog_route", "Manage/{controller}/{action}/{id?}",
        defaults: new { area = "Blog" }, constraints: new { area = "Blog" });
    endpoints.MapControllerRoute("default_route", "{controller}/{action}/{id?}");
});

MapAreaControllerRoute는 제공된 영역 이름(여기에서는 Blog)을 사용하는 area에 대해 기본값 및 제약 조건을 모두 사용하여 경로를 생성합니다. 기본값은 경로가 항상 { area = Blog, ... }를 생성함을 보장하고, 제약 조건은 URL 생성을 위해 { area = Blog, ... } 값을 필요로 합니다.

규칙 기반 라우팅은 순서에 의존적입니다. 일반적으로 영역을 사용하는 경로가 영역을 사용하지 않는 경로에 비해 구체적이기 때문에 앞부분에 배치되어야 합니다.

이전 예제를 사용하면 경로 값 { area = Blog, controller = Users, action = AddUser }가 다음 작업을 일치시킵니다.

using Microsoft.AspNetCore.Mvc;

namespace MyApp.Namespace1
{
    [Area("Blog")]
    public class UsersController : Controller
    {
        // GET /manage/users/adduser
        public IActionResult AddUser()
        {
            var area = ControllerContext.ActionDescriptor.RouteValues["area"];
            var actionName = ControllerContext.ActionDescriptor.ActionName;
            var controllerName = ControllerContext.ActionDescriptor.ControllerName;

            return Content($"area name:{area}" +
                $" controller:{controllerName}  action name: {actionName}");
        }        
    }
}

[Area] 특성은 컨트롤러를 영역의 일부로 나타내는 것입니다. 이 컨트롤러는 Blog 영역에 있습니다. [Area] 특성이 없는 컨트롤러는 어떠한 영역의 구성원도 아니며, 라우팅에서 area 경로 값을 제공해도 일치되지 않습니다. 다음 예제에서는 나열된 첫 번째 컨트롤러만 { area = Blog, controller = Users, action = AddUser } 경로 값과 매칭됩니다.

using Microsoft.AspNetCore.Mvc;

namespace MyApp.Namespace1
{
    [Area("Blog")]
    public class UsersController : Controller
    {
        // GET /manage/users/adduser
        public IActionResult AddUser()
        {
            var area = ControllerContext.ActionDescriptor.RouteValues["area"];
            var actionName = ControllerContext.ActionDescriptor.ActionName;
            var controllerName = ControllerContext.ActionDescriptor.ControllerName;

            return Content($"area name:{area}" +
                $" controller:{controllerName}  action name: {actionName}");
        }        
    }
}
using Microsoft.AspNetCore.Mvc;

namespace MyApp.Namespace2
{
    // Matches { area = Zebra, controller = Users, action = AddUser }
    [Area("Zebra")]
    public class UsersController : Controller
    {
        // GET /zebra/users/adduser
        public IActionResult AddUser()
        {
            var area = ControllerContext.ActionDescriptor.RouteValues["area"];
            var actionName = ControllerContext.ActionDescriptor.ActionName;
            var controllerName = ControllerContext.ActionDescriptor.ControllerName;

            return Content($"area name:{area}" +
                $" controller:{controllerName}  action name: {actionName}");
        }        
    }
}
using Microsoft.AspNetCore.Mvc;

namespace MyApp.Namespace3
{
    // Matches { area = string.Empty, controller = Users, action = AddUser }
    // Matches { area = null, controller = Users, action = AddUser }
    // Matches { controller = Users, action = AddUser }
    public class UsersController : Controller
    {
        // GET /users/adduser
        public IActionResult AddUser()
        {
            var area = ControllerContext.ActionDescriptor.RouteValues["area"];
            var actionName = ControllerContext.ActionDescriptor.ActionName;
            var controllerName = ControllerContext.ActionDescriptor.ControllerName;

            return Content($"area name:{area}" +
                $" controller:{controllerName}  action name: {actionName}");
        }
    }
}

완성도를 위해 각 컨트롤러의 네임스페이스가 여기에 표시됩니다. 이전 컨트롤러가 동일한 네임스페이스를 사용하는 경우 컴파일러 오류가 생성됩니다. 클래스 네임스페이스는 MVC의 라우팅에 영향을 주지 않습니다.

처음 두 컨트롤러는 영역의 구성원이며, area 경로 값으로 해당 영역 이름을 제공하는 경우에만 매칭됩니다. 세 번째 컨트롤러는 어떤 영역의 구성원도 아니며, 라우팅에서 area에 대한 값을 제공하지 않을 때에만 매칭됩니다.

매칭 측면에서 값 없음, 즉 area 값이 없는 것은 area의 값이 null 또는 빈 문자열인 경우와 동일합니다.

영역 내에서 작업을 실행하면 area의 경로 값이 라우팅에서 URL을 생성하기 위해 사용되는 앰비언트 값으로 제공됩니다. 즉, 기본적으로 영역은 다음 예제에서 볼 수 있듯이 URL 생성을 위한 스티커처럼 동작합니다.

app.UseEndpoints(endpoints =>
{
    endpoints.MapAreaControllerRoute(name: "duck_route", 
                                     areaName: "Duck",
                                     pattern: "Manage/{controller}/{action}/{id?}");
    endpoints.MapControllerRoute(name: "default",
                                 pattern: "Manage/{controller=Home}/{action=Index}/{id?}");
});
using Microsoft.AspNetCore.Mvc;

namespace MyApp.Namespace4
{
    [Area("Duck")]
    public class UsersController : Controller
    {
        // GET /Manage/users/GenerateURLInArea
        public IActionResult GenerateURLInArea()
        {
            // Uses the 'ambient' value of area.
            var url = Url.Action("Index", "Home");
            // Returns /Manage/Home/Index
            return Content(url);
        }

        // GET /Manage/users/GenerateURLOutsideOfArea
        public IActionResult GenerateURLOutsideOfArea()
        {
            // Uses the empty value for area.
            var url = Url.Action("Index", "Home", new { area = "" });
            // Returns /Manage
            return Content(url);
        }
    }
}

다음 코드에서는 /Zebra/Users/AddUser에 대한 URL을 생성합니다.

public class HomeController : Controller
{
    public IActionResult About()
    {
        var url = Url.Action("AddUser", "Users", new { Area = "Zebra" });
        return Content($"URL: {url}");
    }

작업 정의

NonAction 특성이 있는 메서드를 제외하고 컨트롤러의 퍼블릭 메서드는 작업입니다.

샘플 코드

디버그 진단

자세한 라우팅 진단 출력을 위해 Logging:LogLevel:MicrosoftDebug로 설정합니다. 개발 환경에서 다음에서 로그 수준을 설정합니다.appsettings.Development.json

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Debug",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  }
}