ASP.NET Core에서 라우팅

작성자: Ryan Nowak, Kirk LarkinRick Anderson

참고 항목

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

Important

이 정보는 상업적으로 출시되기 전에 실질적으로 수정될 수 있는 시험판 제품과 관련이 있습니다. Microsoft는 여기에 제공된 정보에 대해 어떠한 명시적, 또는 묵시적인 보증을 하지 않습니다.

현재 릴리스는 이 문서의 .NET 8 버전을 참조 하세요.

라우팅은 들어오는 HTTP 요청을 일치시켜 앱의 실행 가능 엔드포인트로 디스패치하는 역할을 담당합니다. 엔드포인트는 앱의 실행 가능 요청 처리 코드 단위입니다. 엔드포인트는 앱에서 정의되고 앱 시작 시 구성됩니다. 엔드포인트 일치 프로세스는 요청의 URL에서 값을 추출하고 요청 처리를 위해 이 값을 제공할 수 있습니다. 또한 라우팅은 앱의 엔드포인트 정보를 사용하여 엔드포인트에 매핑되는 URL을 생성할 수도 있습니다.

앱은 다음을 사용하여 라우팅을 구성할 수 있습니다.

  • 컨트롤러
  • Razor Pages
  • SignalR
  • gRPC 서비스
  • 상태 검사와 같은 엔드포인트 지원 미들웨어
  • 라우팅에 등록된 대리자 및 람다

이 문서에서는 ASP.NET Core 라우팅의 하위 수준 세부 정보를 설명합니다. 라우팅을 구성하는 방법은 다음을 참조하세요.

라우팅 기본 사항

다음 코드에서는 라우팅의 기본 예제를 보여 줍니다.

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

앞의 예제에는 메서드를 사용하는 단일 엔드포인트가 포함되어 있습니다 MapGet .

  • HTTP GET 요청이 루트 URL /로 전송되는 경우:
    • 요청 대리자가 실행됩니다.
    • Hello World!가 HTTP 응답에 기록됩니다.
  • 요청 메서드가 GET이 아니거나 루트 URL이 /가 아니면 일치하는 경로가 없고 HTTP 404가 반환됩니다.

라우팅은 UseRoutingUseEndpoints를 통해 등록된 미들웨어 쌍을 사용합니다.

  • UseRouting은 경로 일치를 미들웨어 파이프라인에 추가합니다. 이 미들웨어는 앱에 정의된 엔드포인트 집합을 확인하고 요청을 기반으로 가장 일치하는 항목을 선택합니다.
  • UseEndpoints는 엔드포인트 실행을 미들웨어 파이프라인에 추가합니다. 선택한 엔드포인트와 연결된 대리자를 실행합니다.

앱은 일반적으로 UseRouting 또는 UseEndpoints를 호출할 필요가 없습니다. WebApplicationBuilderUseRoutingUseEndpoints를 통해 Program.cs에 추가된 미들웨어를 래핑하는 미들웨어 파이프라인을 구성합니다. 그러나 앱은 이러한 메서드를 명시적으로 호출하여 UseRoutingUseEndpoints 실행 순서를 변경할 수 있습니다. 예를 들어 다음 코드는 UseRouting을 명시적으로 호출합니다.

app.Use(async (context, next) =>
{
    // ...
    await next(context);
});

app.UseRouting();

app.MapGet("/", () => "Hello World!");

위의 코드에서

  • app.Use에 대한 호출은 파이프라인의 시작 부분에서 실행되는 사용자 지정 미들웨어를 등록합니다.
  • UseRouting에 대한 호출은 사용자 지정 미들웨어 이후에 실행할 경로 일치 미들웨어를 구성합니다.
  • MapGet에 등록된 엔드포인트는 파이프라인의 끝부분에서 실행됩니다.

앞의 예제에 UseRouting에 대한 호출이 포함되지 않았다면 사용자 지정 미들웨어는 경로 일치 미들웨어 이후에 실행됩니다.

참고:WebApplication에 직접 추가된 경로는 파이프라인의 끝에서 실행됩니다.

엔드포인트

MapGet 메서드는 엔드포인트을 정의하는 데 사용됩니다. 엔드포인트는 다음과 같을 수 있습니다.

  • URL 및 HTTP 메서드를 일치시켜 선택됩니다.
  • 대리자를 실행하여 실행됩니다.

앱에서 일치시키고 실행할 수 있는 엔드포인트는 UseEndpoints에서 구성됩니다. 예를 들어 MapGet, MapPost이들과 유사한 메서드는 요청 대리자를 라우팅 시스템에 연결합니다. ASP.NET Core Framework 기능을 라우팅 시스템에 연결하는 데 다음과 같은 추가 메서드를 사용할 수 있습니다.

다음 예제에서는 더 복잡한 경로 템플릿을 사용한 라우팅을 보여 줍니다.

app.MapGet("/hello/{name:alpha}", (string name) => $"Hello {name}!");

/hello/{name:alpha} 문자열은 경로 템플릿이며 경로 템플릿은 엔드포인트가 일치되는 방식을 구성하는 데 사용됩니다. 이 경우 템플릿은 다음과 일치합니다.

  • /hello/Docs과 같은 URL
  • /hello/로 시작하고 그 다음에 시퀀스 영문자가 오는 URL 경로. :alpha는 영문자와만 일치하는 경로 제약 조건을 적용합니다. 경로 제약 조건은 이 문서의 뒷부분에 설명되어 있습니다.

URL 경로의 두 번째 세그먼트 {name:alpha}는 다음과 같습니다.

다음 예제에서는 상태 검사 및 권한 부여를 사용한 라우팅을 보여 줍니다.

app.UseAuthentication();
app.UseAuthorization();

app.MapHealthChecks("/healthz").RequireAuthorization();
app.MapGet("/", () => "Hello World!");

앞의 예제에서는 다음을 수행하는 방법을 보여 줍니다.

  • 권한 부여 미들웨어를 라우팅에 사용할 수 있습니다.
  • 엔드포인트를 사용하여 권한 부여 동작을 구성할 수 있습니다.

MapHealthChecks 호출은 상태 검사 엔드포인트를 추가합니다. 이 호출에 RequireAuthorization을 연결하면 권한 부여 정책이 엔드포인트에 연결됩니다.

UseAuthenticationUseAuthorization을 호출하면 인증 및 권한 부여 미들웨어가 추가됩니다. 이러한 미들웨어는 UseRoutingUseEndpoints 사이에 배치되므로 다음을 수행할 수 있습니다.

  • UseRouting에서 선택된 엔드포인트를 확인합니다.
  • 엔드포인트로 UseEndpoints가 디스패치되기 전에 권한 부여 정책을 적용합니다.

엔드포인트 메타데이터

앞의 예제에는 두 개의 엔드포인트가 있지만 상태 검사 엔드포인트에만 권한 부여 정책이 연결되어 있습니다. 요청이 상태 검사 엔드포인트 /healthz와 일치하는 경우 권한 부여 확인이 수행됩니다. 이는 엔드포인트에 추가 데이터가 연결될 수 있음을 보여 줍니다. 이러한 추가 데이터를 엔드포인트 메타데이터라고 합니다.

  • 이 메타데이터는 라우팅 인식 미들웨어에서 처리될 수 있으며
  • 모든 .NET 형식일 수 있습니다.

라우팅 개념

라우팅 시스템은 강력한 엔드포인트 개념을 추가하여 미들웨어 파이프라인을 기반으로 빌드됩니다. 엔드포인트는 라우팅, 권한 부여 및 ASP.NET Core 시스템 수의 측면에서 서로 다른 앱의 기능 단위를 나타냅니다.

ASP.NET Core 엔드포인트 정의

ASP.NET Core 엔드포인트는 다음과 같습니다.

다음 코드에서는 현재 요청과 일치하는 엔드포인트를 검색하고 검사하는 방법을 보여 줍니다.

app.Use(async (context, next) =>
{
    var currentEndpoint = context.GetEndpoint();

    if (currentEndpoint is null)
    {
        await next(context);
        return;
    }

    Console.WriteLine($"Endpoint: {currentEndpoint.DisplayName}");

    if (currentEndpoint is RouteEndpoint routeEndpoint)
    {
        Console.WriteLine($"  - Route Pattern: {routeEndpoint.RoutePattern}");
    }

    foreach (var endpointMetadata in currentEndpoint.Metadata)
    {
        Console.WriteLine($"  - Metadata: {endpointMetadata}");
    }

    await next(context);
});

app.MapGet("/", () => "Inspect Endpoint.");

엔드포인트는 선택된 경우 HttpContext에서 검색할 수 있습니다. 해당 속성을 검사할 수 있습니다. 엔드포인트 개체는 변경할 수 없으며 만든 후 수정할 수 없습니다. 가장 일반적인 형식의 엔드포인트는 RouteEndpoint입니다. RouteEndpoint에는 라우팅 시스템에서 선택할 수 있는 정보가 포함됩니다.

앞의 코드에서 app.Use는 인라인 미들웨어를 구성합니다.

다음 코드에서는 파이프라인에서 app.Use가 호출되는 위치에 따라 엔드포인트가 없을 수 있음을 보여 줍니다.

// Location 1: before routing runs, endpoint is always null here.
app.Use(async (context, next) =>
{
    Console.WriteLine($"1. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
    await next(context);
});

app.UseRouting();

// Location 2: after routing runs, endpoint will be non-null if routing found a match.
app.Use(async (context, next) =>
{
    Console.WriteLine($"2. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
    await next(context);
});

// Location 3: runs when this endpoint matches
app.MapGet("/", (HttpContext context) =>
{
    Console.WriteLine($"3. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
    return "Hello World!";
}).WithDisplayName("Hello");

app.UseEndpoints(_ => { });

// Location 4: runs after UseEndpoints - will only run if there was no match.
app.Use(async (context, next) =>
{
    Console.WriteLine($"4. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
    await next(context);
});

위의 샘플은 엔드포인트가 선택되었는지 아닌지를 표시하는 Console.WriteLine 문을 추가합니다. 명확하게 하도록 이 샘플에서는 제공된 / 엔드포인트에 표시 이름을 할당합니다.

위의 샘플에는 파이프라인 내에서 이러한 미들웨어가 실행되는 시기를 정확하게 제어하기 위한 UseRoutingUseEndpoints 호출도 포함되어 있습니다.

/의 URL을 사용하여 이 코드를 실행하면 다음이 표시됩니다.

1. Endpoint: (null)
2. Endpoint: Hello
3. Endpoint: Hello

다른 URL을 사용하여 이 코드를 실행하면 다음이 표시됩니다.

1. Endpoint: (null)
2. Endpoint: (null)
4. Endpoint: (null)

이 출력은 다음을 나타냅니다.

  • UseRouting을 호출하기 전에 엔드포인트는 항상 null입니다.
  • 일치 항목이 발견되면 UseRoutingUseEndpoints사이에서 엔드포인트가 null이 아닙니다.
  • 일치 항목이 발견되면 UseEndpoints 미들웨어가 터미널입니다. 터미널 미들웨어는 이 문서의 뒷부분에 정의되어 있습니다.
  • UseEndpoints 뒤의 미들웨어는 일치 항목이 없는 경우에만 실행됩니다.

미들웨어는 UseRouting 이 메서드를 SetEndpoint 사용하여 엔드포인트를 현재 컨텍스트에 연결합니다. UseRouting 미들웨어를 사용자 지정 논리로 바꾸어도 엔드포인트를 사용하는 이점을 얻을 수 있습니다. 엔드포인트는 미들웨어와 같은 하위 수준 기본 형식이며 라우팅 구현에 결합되지 않습니다. 대부분의 앱에서는 UseRouting을 사용자 지정 논리로 바꿀 필요가 없습니다.

UseEndpoints 미들웨어는 UseRouting 미들웨어와 함께 사용하기 위한 것입니다. 엔드포인트를 실행하는 핵심 논리는 복잡하지 않습니다. GetEndpoint를 사용하여 엔드포인트를 검색한 다음 해당 RequestDelegate 속성을 호출하면 됩니다.

다음 코드에서는 미들웨어가 라우팅에 영향을 주거나 반응하는 방식을 보여 줍니다.

app.UseHttpMethodOverride();
app.UseRouting();

app.Use(async (context, next) =>
{
    if (context.GetEndpoint()?.Metadata.GetMetadata<RequiresAuditAttribute>() is not null)
    {
        Console.WriteLine($"ACCESS TO SENSITIVE DATA AT: {DateTime.UtcNow}");
    }

    await next(context);
});

app.MapGet("/", () => "Audit isn't required.");
app.MapGet("/sensitive", () => "Audit required for sensitive data.")
    .WithMetadata(new RequiresAuditAttribute());
public class RequiresAuditAttribute : Attribute { }

앞의 예제에서는 다음과 같은 두 가지 중요한 개념을 보여 줍니다.

  • 미들웨어는 UseRouting 전에 실행되어 라우팅 작동의 기반이 되는 데이터를 수정할 수 있습니다.
  • 미들웨어는 UseRoutingUseEndpoints 사이에서 실행되어 엔드포인트가 실행되기 전에 라우팅의 결과를 처리할 수 있습니다.
    • 다음 사이에 UseRouting 실행되는 미들웨어:UseEndpoints
      • 일반적으로 메타데이터를 검사하여 엔드포인트를 이해합니다.
      • UseAuthorizationUseCors에서 하는 것처럼 보안 결정을 내리는 경우가 많습니다.
    • 미들웨어와 메타데이터를 조합하면 엔드포인트별로 정책을 구성할 수 있습니다.

위의 코드에서는 엔드포인트별 정책을 지원하는 사용자 지정 미들웨어의 예를 보여 줍니다. 이 미들웨어는 중요한 데이터에 대한 액세스의 ‘감사 로그’를 콘솔에 기록합니다. RequiresAuditAttribute 메타데이터를 사용하여 엔드포인트를 ‘감사’하도록 미들웨어를 구성할 수 있습니다. 이 샘플에서는 중요함으로 표시된 엔드포인트만 감사되는 ‘옵트인 패턴’을 보여 줍니다. 예를 들어 이 논리를 역으로 정의하여 안전한 것으로 표시되지 않은 모든 항목을 감사할 수 있습니다. 엔드포인트 메타데이터 시스템은 유연합니다. 이 논리는 사용 사례에 적합한 방식으로 설계할 수 있습니다.

앞의 샘플 코드는 엔드포인트의 기본 개념을 보여 주기 위한 것입니다. 프로덕션 용도로는 사용하지 않아야 합니다. ‘감사 로그’ 미들웨어의 전체 버전은 다음과 같습니다.

  • 파일이나 데이터베이스에 기록합니다.
  • 사용자, IP 주소, 중요한 엔드포인트의 이름 등과 같은 세부 정보를 포함합니다.

감사 정책 메타데이터 RequiresAuditAttribute는 컨트롤러 및 SignalR 같은 클래스 기반 프레임워크에서 더욱 쉽게 사용할 수 있도록 Attribute로 정의됩니다. ‘라우팅 대상 코드’를 사용하면 다음과 같이.

  • 메타데이터가 작성기 API와 연결됩니다.
  • 엔드포인트를 만들 때 해당 메서드 및 클래스의 모든 특성이 클래스 기반 프레임워크에 포함됩니다.

메타데이터 형식은 인터페이스나 특성으로 정의하는 것이 가장 좋습니다. 인터페이스 및 특성을 사용하면 코드를 다시 사용할 수 있습니다. 메타데이터 시스템은 유연하며 제한을 적용하지 않습니다.

터미널 미들웨어와 라우팅 비교

다음 예제에서는 터미널 미들웨어와 라우팅을 모두 보여줍니다.

// Approach 1: Terminal Middleware.
app.Use(async (context, next) =>
{
    if (context.Request.Path == "/")
    {
        await context.Response.WriteAsync("Terminal Middleware.");
        return;
    }

    await next(context);
});

app.UseRouting();

// Approach 2: Routing.
app.MapGet("/Routing", () => "Routing.");

Approach 1:로 표시된 미들웨어 스타일은 터미널 미들웨어입니다. 일치 작업을 수행하기 때문에 터미널 미들웨어라고 합니다.

  • 위 샘플의 일치 작업은 미들웨어의 Path == "/"와 라우팅의 Path == "/Routing"입니다.
  • 일치가 성공하면 일부 기능을 실행하고 next 미들웨어를 호출하는 대신 반환합니다.

검색을 종료하고 일부 기능을 실행한 다음 반환하기 때문에 터미널 미들웨어라고 합니다.

다음 목록에서는 터미널 미들웨어와 라우팅을 비교합니다.

  • 두 방법 모두 처리 파이프라인을 종료할 수 있습니다.
    • 미들웨어는 next를 호출하는 대신 반환하여 파이프라인을 종료합니다.
    • 엔드포인트는 항상 터미널입니다.
  • 터미널 미들웨어를 사용하면 파이프라인의 임의 위치에 미들웨어를 배치할 수 있습니다.
    • 엔드포인트는 UseEndpoints의 위치에서 실행됩니다.
  • 터미널 미들웨어를 사용하면 임의의 코드에서 미들웨어가 일치하는 시기를 확인할 수 있습니다.
    • 사용자 지정 경로 일치 코드는 길어져서 올바르게 작성하기 어려울 수 있습니다.
    • 라우팅은 일반적인 앱을 위한 간단한 솔루션을 제공합니다. 앱 대부분에는 사용자 지정 경로 일치 코드가 필요하지 않습니다.
  • 엔드포인트는 UseAuthorizationUseCors 같은 미들웨어와 상호 작용합니다.
    • UseAuthorization 또는 UseCors와 함께 터미널 미들웨어를 사용하려면 권한 부여 시스템을 수동으로 조작해야 합니다.

엔드포인트는 다음 두 가지를 모두 정의합니다.

  • 요청을 처리할 대리자
  • 임의 메타데이터의 컬렉션. 메타데이터는 각 엔드포인트에 연결된 정책과 구성에 따라 횡단 관심사(Cross-Cutting Concerns)를 구현하는 데 사용됩니다.

터미널 미들웨어는 효과적인 도구이지만 다음이 필요할 수 있습니다.

  • 상당한 양의 코딩과 테스트
  • 원하는 수준의 유연성을 얻기 위한 다른 시스템과의 수동 통합

터미널 미들웨어를 작성하기 전에 라우팅과 통합하는 것이 좋습니다.

또는MapWhen과 통합되는 기존 터미널 미들웨어는 일반적으로 라우팅 인식 엔드포인트로 전환될 수 있습니다. MapHealthChecks는 다음과 같은 라우터 방식의 패턴을 보여 줍니다.

  • IEndpointRouteBuilder에 대한 확장 메서드를 작성합니다.
  • CreateApplicationBuilder를 사용하여 중첩된 미들웨어 파이프라인을 만듭니다.
  • 새 파이프라인에 미들웨어를 연결합니다. 이 경우, UseHealthChecks입니다.
  • 미들웨어 파이프라인을 RequestDelegateBuild합니다.
  • Map을 호출하고 새 미들웨어 파이프라인을 제공합니다.
  • 확장 메서드의 Map에서 제공하는 작성기 개체를 반환합니다.

다음 코드에서는 MapHealthChecks를 사용하는 방법을 보여 줍니다.

app.UseAuthentication();
app.UseAuthorization();

app.MapHealthChecks("/healthz").RequireAuthorization();

앞의 샘플에서는 작성기 개체를 반환하는 것이 중요한 이유를 보여 줍니다. 작성기 개체를 반환하면 앱 개발자가 엔드포인트의 권한 부여와 같은 정책을 구성할 수 있습니다. 이 예제에서는 상태 검사 미들웨어가 권한 부여 시스템과 직접 통합되지 않습니다.

메타데이터 시스템은 터미널 미들웨어를 사용하는 확장성 작성자에서 발생한 문제에 대응하여 만들어졌습니다. 미들웨어마다 권한 부여 시스템과의 고유한 통합을 구현하는 것은 문제가 있습니다.

URL 일치

  • 라우팅이 들어오는 요청을 엔드포인트와 일치시키는 프로세스입니다.
  • URL 경로 및 헤더의 데이터를 기반으로 합니다.
  • 요청의 모든 데이터를 고려하도록 확장될 수 있습니다.

라우팅 미들웨어가 실행되면 Endpoint를 설정하여 현재 요청에서 HttpContext요청 기능으로 값을 라우팅합니다.

  • HttpContext.GetEndpoint를 호출하면 엔드포인트를 가져옵니다.
  • HttpRequest.RouteValues는 경로 값의 컬렉션을 가져옵니다.

라우팅 미들웨어 후에 실행되는 미들웨어 는 엔드포인트를 검사하고 작업을 수행할 수 있습니다. 예를 들어 권한 부여 미들웨어는 엔드포인트의 메타데이터 컬렉션에서 권한 부여 정책을 조사할 수 있습니다. 요청 처리 파이프라인의 미들웨어가 모두 실행된 후에 선택한 엔드포인트의 대리자가 호출됩니다.

엔드포인트 라우팅의 라우팅 시스템은 모든 디스패치를 결정합니다. 미들웨어는 선택된 엔드포인트에 기반으로 하여 정책을 적용하므로 다음이 중요합니다.

  • 디스패치나 보안 정책의 애플리케이션에 영향을 줄 수 있는 모든 결정은 라우팅 시스템 내에서 내려야 합니다.

Warning

이전 버전과의 호환성을 위해 컨트롤러 또는 Razor Pages 엔드포인트 대리자를 실행할 때 속성 RouteContext.RouteData 은 지금까지 수행된 요청 처리에 따라 적절한 값으로 설정됩니다.

RouteContext 형식은 이후 릴리스에서 obsolete로 표시됩니다.

  • RouteData.ValuesHttpRequest.RouteValues로 마이그레이션합니다.
  • 엔드포인트 메타데이터에서 검색 IDataTokensMetadata 하도록 마이그레이션 RouteData.DataTokens 합니다.

URL 일치는 구성 가능한 일련의 단계로 작동합니다. 각 단계의 출력은 일치 항목 집합입니다. 일치 항목 집합은 다음 단계에서 더욱 좁혀질 수 있습니다. 라우팅 구현에서는 일치하는 엔드포인트의 처리 순서를 보장하지 않습니다. 기능한 모든 일치 항목이 한 번에 처리됩니다. URL 일치 단계는 다음 순서로 수행됩니다. ASP.NET Core:

  1. 엔드포인트와 해당 경로 템플릿 집합에 대한 URL 경로를 처리하여 일치 항목을 모두 수집합니다.
  2. 앞의 목록을 사용하여 경로 제약 조건이 적용되지 않는 일치 항목을 제거합니다.
  3. 앞의 목록을 가져와 인스턴스 집합 MatcherPolicy 에 실패한 일치 항목을 제거합니다.
  4. EndpointSelector 값을 사용하여 이전 목록에서 최종 결정을 내립니다.

엔드포인트 목록의 우선 순위는 다음에 따라 지정됩니다.

EndpointSelector에 도달할 때까지 각 단계에서 일치하는 모든 엔드포인트가 처리됩니다. EndpointSelector는 최종 단계이며, 일치 항목에서 가장 높은 우선 순위 엔드포인트를 가장 일치하는 항목으로 선택합니다. 가장 일치하는 항목과 같은 우선 순위의 다른 일치 항목이 있으면 모호한 일치 예외가 throw됩니다.

경로 우선 순위는 더 구체적인 경로 템플릿에 높은 우선 순위가 지정되는 기준에 따라 컴퓨팅됩니다. 예를 들어 /hello/{message} 템플릿을 가정해 보겠습니다.

  • 둘 다 URL 경로 /hello와 일치합니다.
  • /hello가 더 구체적이므로 우선 순위가 높습니다.

일반적으로 경로 우선 순위는 실제로 사용되는 URL 체계에 가장 일치하는 항목을 선택하는 데 좋습니다. 모호성을 방지하는 데 필요한 경우에만 Order를 사용합니다.

라우팅에서 제공하는 확장성의 종류 때문에 라우팅 시스템이 모호한 경로를 미리 컴퓨팅할 수는 없습니다. 경로 템플릿 /{message:alpha}/{message:int}와 같은 예제를 살펴보겠습니다.

  • alpha 제약 조건은 영문자와만 일치합니다.
  • int 제약 조건은 숫자와만 일치합니다.
  • 이러한 템플릿은 경로 우선 순위가 동일하지만 둘 다와 일치하는 단일 URL은 없습니다.
  • 라우팅 시스템에서 시작 시 모호성 오류를 보고한 경우 이 유효한 사용 사례를 차단합니다.

Warning

UseEndpoints 내의 작업 순서는 라우팅 동작에 영향을 주지 않지만 한 가지 예외가 있습니다. MapControllerRouteMapAreaRoute는 호출된 순서를 기준으로 해당 엔드포인트에 순서 값을 자동으로 할당합니다. 이는 이전 라우팅 구현과 동일한 보장을 제공하는 라우팅 시스템이 없이 컨트롤러의 오래된 동작을 시뮬레이트합니다.

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

  • 경로 개념이 없습니다.
  • 순서 지정을 보장하지 않습니다. 모든 엔드포인트가 한 번에 처리됩니다.

경로 템플릿 우선 순위 및 엔드포인트 선택 영역 순서

경로 템플릿 우선 순위는 얼마나 구체적인지를 기준으로 각 경로 템플릿에 값을 할당하는 시스템입니다. 경로 템플릿 우선 순위의 특징은 다음과 같습니다.

  • 일반적인 사례에서 엔드포인트 순서를 조정할 필요가 없게 합니다.
  • 라우팅 동작에 관한 일반적인 기대에 맞추려고 합니다.

예를 들어 /Products/List/Products/{id} 템플릿을 가정해 보겠습니다. URL 경로 /Products/List에 대해 /Products/List/Products/{id}보다 더 잘 일치한다고 합리적으로 가정할 수 있습니다. 리터럴 세그먼트 /List가 매개 변수 세그먼트 /{id}보다 우선 순위가 더 높다고 간주되기 때문입니다.

우선 순위의 작동 방식에 대한 세부 정보는 경로 템플릿이 정의된 방법과 어느 정도 관련이 있습니다.

  • 세그먼트가 더 많은 템플릿은 더 구체적인 것으로 간주됩니다.
  • 리터럴 텍스트가 있는 세그먼트가 매개 변수 세그먼트보다 더 구체적인 것으로 간주됩니다.
  • 제약 조건이 있는 매개 변수 세그먼트가 제약 조건이 없는 매개 변수 세그먼트보다 더 구체적인 것으로 간주됩니다.
  • 복잡한 세그먼트는 제약 조건이 있는 매개 변수 세그먼트만큼 구체적인 것으로 간주됩니다.
  • Catch-all 매개 변수가 가장 덜 구체적입니다. Catch-all 경로에 관한 중요한 내용은 경로 템플릿 섹션에서 catch-all을 참조하세요.

URL 생성 개념

URL 생성은 다음과 같습니다.

  • 라우팅이 경로 값의 집합을 기반으로 하는 URL 경로를 만들 수 있는 프로세스입니다.
  • 엔드포인트와 이에 액세스하는 URL을 논리적으로 분리할 수 있습니다.

엔드포인트 라우팅에는 LinkGenerator API가 포함됩니다. LinkGeneratorDI에서 사용할 수 있는 싱글톤 서비스입니다. LinkGenerator API는 실행 중인 요청의 컨텍스트 외부에서 사용할 수 있습니다. Mvc.IUrlHelperIUrlHelper를 사용하는 시나리오(예: 태그 도우미, HTML 도우미 및 작업 결과)는 내부적으로 LinkGenerator API를 사용하여 링크 생성 기능을 제공합니다.

링크 생성기는 주소주소 체계의 개념으로 지원됩니다. 주소 체계는 링크 생성을 위해 고려해야 할 엔드포인트를 결정하는 방법입니다. 예를 들어 컨트롤러 및 Razor Pages에서 많은 사용자에게 친숙한 경로 이름 및 경로 값 시나리오는 주소 체계로 구현됩니다.

링크 생성기는 다음 확장 메서드를 통해 컨트롤러 및 Razor Pages에 연결할 수 있습니다.

이러한 메서드의 오버로드에는 HttpContext를 포함한 인수가 허용됩니다. 이러한 메서드는 기능적으로 Url.ActionUrl.Page와 동일하지만, 추가적인 유연성과 옵션을 제공합니다.

GetPath* 메서드는 절대 경로가 포함된 URI를 생성한다는 점에서 Url.ActionUrl.Page와 가장 비슷합니다. GetUri* 메서드는 항상 체계와 호스트를 포함한 절대 URI를 생성합니다. HttpContext를 허용하는 메서드는 실행 중인 요청의 컨텍스트에서 URI를 생성합니다. 재정의되지 않는 한 실행 중인 요청의 앰비언트 경로 값, URL 기본 경로, 체계 및 호스트가 사용됩니다.

LinkGenerator는 주소를 사용하여 호출됩니다. URI 생성은 다음 두 단계로 수행됩니다.

  1. 주소는 해당 주소와 일치하는 엔드포인트 목록에 바인딩됩니다.
  2. 제공된 값과 일치하는 경로 패턴을 찾을 때까지 각 엔드포인트의 RoutePattern이 평가됩니다. 결과 출력은 링크 생성기에 제공된 다른 URI 부분과 결합되어 반환됩니다.

LinkGenerator에서 제공하는 메서드는 모든 유형의 주소에 대해 표준 링크 생성 기능을 지원합니다. 링크 생성기를 사용하는 가장 편리한 방법은 특정 주소 유형에 대한 작업을 수행하는 확장 메서드를 사용하는 것입니다.

확장 메서드 설명
GetPathByAddress 제공된 값에 기반한 절대 경로가 있는 URI를 생성합니다.
GetUriByAddress 제공된 값에 기반한 절대 URI를 생성합니다.

Warning

LinkGenerator 메서드 호출 시 다음과 같은 의미에 주의하세요.

  • 들어오는 요청의 GetUri* 헤더의 유효성을 검사하지 않는 앱 구성에서는 Host 확장 메서드를 신중하게 사용하세요. 들어오는 요청의 Host 헤더의 유효성을 검사하지 않으면 신뢰할 수 없는 요청 입력이 보기 또는 페이지에 포함된 URI로 클라이언트에 다시 보내질 수 있습니다. 모든 프로덕션 앱은 알려진 유효한 값에 대해 Host 헤더의 유효성을 검사하도록 서버를 구성하는 것이 좋습니다.

  • 미들웨어에서 MapWhen 또는 LinkGenerator과 함께 Map를 사용할 때는 신중하게 사용하세요. Map*는 실행 중인 요청의 기본 경로를 변경하여 링크 생성의 출력에 영향을 줍니다. 모든 LinkGenerator API는 기본 경로를 지정할 수 있습니다. 링크 생성에 대한 Map*의 영향을 실행 취소하려면 빈 기본 경로를 지정합니다.

미들웨어 예제

다음 예제에서는 미들웨어에서 LinkGenerator API를 사용하여 상점 제품을 나열하는 작업 메서드에 대한 링크를 만듭니다. 링크 생성기를 클래스에 주입하고 GenerateLink를 호출하여 앱의 모든 클래스에서 해당 링크 생성기를 사용할 수 있습니다.

public class ProductsMiddleware
{
    private readonly LinkGenerator _linkGenerator;

    public ProductsMiddleware(RequestDelegate next, LinkGenerator linkGenerator) =>
        _linkGenerator = linkGenerator;

    public async Task InvokeAsync(HttpContext httpContext)
    {
        httpContext.Response.ContentType = MediaTypeNames.Text.Plain;

        var productsPath = _linkGenerator.GetPathByAction("Products", "Store");

        await httpContext.Response.WriteAsync(
            $"Go to {productsPath} to see our products.");
    }
}

경로 템플릿

{} 내의 토큰은 경로가 일치하는 경우 바인딩될 경로 매개 변수를 정의합니다. 경로 세그먼트에 둘 이상의 경로 매개 변수를 정의할 수 있지만 경로 매개 변수를 리터럴 값으로 분리해야 합니다. 예시:

{controller=Home}{action=Index}

는 리터럴 값이 {controller}{action}없기 때문에 유효한 경로가 아닙니다. 경로 매개 변수는 이름이 있어야 하며 지정된 추가 특성을 가질 수 있습니다.

경로 매개 변수 이외의 리터럴 텍스트(예: {id}) 및 경로 구분 기호(/)는 URL의 텍스트와 일치해야 합니다. 텍스트 일치는 대/소문자를 구분하지 않으며 URL 경로의 디코딩된 표현을 기반으로 합니다. 리터럴 경로 매개 변수 구분 기호 { 또는 }와 일치시키려면 문자를 반복하여(예: {{ 또는 }}) 구분 기호를 이스케이프합니다.

별표 * 또는 이중 별표 **:

  • URI의 나머지 부분에 바인딩하기 위해 경로 매개 변수의 접두사로 사용할 수 있습니다.
  • 범용 매개 변수라고 합니다. 예를 들면 다음과 같습니다. blog/{**slug}
    • blog/로 시작하고 그 다음에 임의의 값이 오는 모든 URI를 찾습니다.
    • blog/ 다음의 값은 동적 필드 경로 값에 할당됩니다.

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.

범용 매개 변수는 빈 문자열과 일치시킬 수도 있습니다.

범용 매개 변수는 경로 구분 기호 / 문자를 포함하여 URL을 생성하는 데 경로가 사용될 때 적절한 문자를 이스케이프합니다. 예를 들어 경로 값이 { path = "my/path" }인 경로 foo/{*path}foo/my%2Fpath를 생성합니다. 이스케이프된 슬래시에 주의하세요. 경로 구분 기호 문자를 왕복하려면 ** 경로 매개 변수 접두사를 사용합니다. { path = "my/path" }가 있는 경로 foo/{**path}foo/my/path를 생성합니다.

선택적 파일 확장명이 있는 파일 이름을 캡처하려고 시도하는 URL 패턴에는 추가 고려 사항이 있습니다. 예를 들어 템플릿 files/{filename}.{ext?}를 가정해 보겠습니다. filenameext 모두에 대한 값이 있으면 두 값이 채워집니다. URL에 filename에 대한 값만 있으면 후행 .가 선택 사항이므로 경로가 일치합니다. 다음 URL은 이 경로와 일치합니다.

  • /files/myFile.txt
  • /files/myFile

경로 매개 변수에는 등호(=)로 구분된 매개 변수 이름 뒤에 기본값을 지정하여 지정된 기본값이 있을 수 있습니다. 예를 들어 {controller=Home}controller에 대한 기본값으로 Home을 정의합니다. URL에 매개 변수에 대한 값이 없는 경우 기본값이 사용됩니다. 경로 매개 변수는 매개 변수 이름의 끝에 물음표(?)를 추가하면 선택적이 됩니다. 예들 들어 id?입니다. 선택적 값과 기본 경로 매개 변수의 차이는 다음과 같습니다.

  • 기본값이 있는 경로 매개 변수는 항상 값을 생성합니다.
  • 선택적 매개 변수는 요청 URL에서 값을 제공한 경우에만 값이 있습니다.

경로 매개 변수에는 URL에서 바인딩된 경로 값과 일치해야 한다는 제약 조건이 있을 수 있습니다. 경로 매개 변수 이름 뒤에 :과 제약 조건 이름을 추가하여 경로 매개 변수에서 인라인 제약 조건을 지정합니다. 제약 조건에 인수가 필요한 경우 제약 조건 이름 뒤에서 괄호 (...)로 묶입니다. 또 다른 : 및 제약 조건 이름을 추가하여 여러 ‘인라인 제약 조건’을 지정할 수 있습니다.

제약 조건 이름 및 인수는 IRouteConstraint의 인스턴스를 만드는 IInlineConstraintResolver 서비스로 전달되어 URL 처리에서 사용합니다. 예를 들어 경로 템플릿 blog/{article:minlength(10)}는 인수 10으로 minlength 제약 조건을 지정합니다. 경로 제약 조건 및 프레임워크에서 제공하는 제약 조건 목록에 대한 자세한 내용은 경로 제약 조건 섹션을 참조하세요.

경로 매개 변수에는 매개 변수 변환기가 있을 수도 있습니다. 매개 변수 변환기는 링크를 생성하고 URL에 대한 작업 및 페이지와 일치할 때 매개 변수 값을 변환합니다. 제약 조건과 마찬가지로, 매개 변수 변환기는 경로 매개 변수 이름 뒤에 :과 변환기 이름을 추가하여 경로 매개 변수에 인라인으로 추가될 수 있습니다. 예를 들어 경로 템플릿 blog/{article:slugify}slugify 변환기를 지정합니다. 매개 변수 변환기에 대한 자세한 내용은 매개 변수 변환기 섹션을 참조하세요.

다음 표에서는 경로 템플릿 예제 및 해당 동작을 보여 줍니다.

경로 템플릿 URI 일치 예제 요청 URI…
hello /hello /hello 단일 경로만 일치합니다.
{Page=Home} / 일치하고, PageHome으로 설정합니다.
{Page=Home} /Contact 일치하고, PageContact으로 설정합니다.
{controller}/{action}/{id?} /Products/List Products 컨트롤러 및 List 작업에 매핑합니다.
{controller}/{action}/{id?} /Products/Details/123 Products 컨트롤러 및 Details 작업에 매핑합니다(id가 123으로 설정됨).
{controller=Home}/{action=Index}/{id?} / Home 컨트롤러 및 Index 메서드에 매핑합니다. id는 무시됩니다.
{controller=Home}/{action=Index}/{id?} /Products Products 컨트롤러 및 Index 메서드에 매핑합니다. id는 무시됩니다.

템플릿 사용은 일반적으로 라우팅에 대한 가장 간단한 방식입니다. 제약 조건 및 기본값을 경로 템플릿 외부에서 지정할 수도 있습니다.

복잡한 세그먼트

복잡한 세그먼트는 non-greedy 방식으로 오른쪽에서 왼쪽으로 리터럴 구분 기호를 매칭하여 처리됩니다. 예를 들어 [Route("/a{b}c{d}")]는 복잡한 세그먼트입니다. 복잡한 세그먼트는 특정 방식으로 작동하므로 제대로 사용하려면 이 방식을 이해해야 합니다. 이 단원의 예제에서는 매개 변수 내에 구분 기호 텍스트가 표시되지 않는 경우에만 복잡한 세그먼트가 실제로 잘 작동하는 이유를 보여 줍니다. 더욱 복잡한 경우에는 regex를 사용한 다음 값을 수동으로 추출해야 합니다.

Warning

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

다음은 라우팅에서 /a{b}c{d} 템플릿 및 URL 경로 /abcd를 사용하여 수행하는 단계를 요약한 것입니다. |는 알고리즘의 작동 방식을 시각화하는 데 사용됩니다.

  • 오른쪽에서 왼쪽으로 첫 번째 리터럴은 c입니다. 따라서 /abcd가 오른쪽에서 검색되고 /ab|c|d를 찾습니다.
  • 이제 오른쪽의 모든 항목(d)이 경로 매개 변수 {d}와 일치합니다.
  • 오른쪽에서 왼쪽으로 다음 리터럴은 a입니다. 따라서 /ab|c|d가 중단된 위치부터 검색되고 a/|a|b|c|d와 함께 찾습니다.
  • 이제 오른쪽 값(b)이 경로 매개 변수 {b}와 일치합니다.
  • 남은 텍스트가 없고 남은 경로 템플릿도 없으므로 이것이 일치 항목입니다.

다음은 동일한 템플릿 /a{b}c{d}와 URL 경로 /aabcd를 사용하는 부정적인 사례의 예제입니다. |는 알고리즘의 작동 방식을 시각화하는 데 사용됩니다. 이 경우는 동일한 알고리즘으로 설명되는 일치 항목이 아닙니다.

  • 오른쪽에서 왼쪽으로 첫 번째 리터럴은 c입니다. 따라서 /aabcd가 오른쪽에서 검색되고 /aab|c|d를 찾습니다.
  • 이제 오른쪽의 모든 항목(d)이 경로 매개 변수 {d}와 일치합니다.
  • 오른쪽에서 왼쪽으로 다음 리터럴은 a입니다. 따라서 /aab|c|d가 중단된 위치부터 검색되고 a/a|a|b|c|d와 함께 찾습니다.
  • 이제 오른쪽 값(b)이 경로 매개 변수 {b}와 일치합니다.
  • 이제 남은 텍스트 a가 있지만 알고리즘에서 구문 분석할 경로 템플릿이 부족하므로 이것은 일치 항목이 아닙니다.

일치 알고리즘이 non-greedy이므로 다음과 같습니다.

  • 각 단계에서 가능한 최소의 텍스트와 일치합니다.
  • 매개 변수 값 내에 구분 기호 값이 표시되는 모든 경우에는 일치하지 않게 됩니다.

정규식을 사용하면 일치 동작을 더 효율적으로 제어할 수 있습니다.

최대 일치라고도 하는 욕심 일치는 정규식 패턴을 충족하는 입력 텍스트에서 가능한 가장 긴 일치 항목을 찾으려고 시도합니다. 지연 일치라고도 하는 비욕심 일치는 정규식 패턴을 충족하는 입력 텍스트에서 가능한 가장 짧은 일치 항목을 찾습니다.

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

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

[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 경로가 경로 값으로 토큰화되면 실행됩니다. 일반적으로 경로 제약 조건은 경로 템플릿을 통해 연결된 경로 값을 검사하고 값 허용 여부에 대한 true 또는 false 결정을 내립니다. 일부 경로 제약 조건은 경로 값 외부의 데이터를 사용하여 요청을 라우팅할 수 있는지 여부를 고려합니다. 예를 들어 HttpMethodRouteConstraint는 해당 HTTP 동사에 따라 요청을 허용하거나 거부할 수 있습니다. 제약 조건은 라우팅 요청 및 링크 생성에 사용됩니다.

Warning

제약 조건을 입력 유효성 검사에 사용하지 마세요. 입력 유효성 검사에 제약 조건을 사용하면 잘못된 입력으로 인해 404 찾을 수 없음 응답이 반환됩니다. 입력이 잘못되면 400 잘못된 요청과 해당하는 오류 메시지가 생성됩니다. 경로 제약 조건은 특정 경로에 대한 입력의 유효성을 검사하는 것이 아니라 비슷한 경로를 명확하게 구분하는 데 사용됩니다.

다음 표에서는 경로 제약 조건 예제 및 예상되는 해당 동작을 보여 줍니다.

제약 조건 예제 일치하는 예제 주의
int {id:int} 123456789, -123456789 임의의 정수와 일치
bool {active:bool} true, FALSE true 또는 false와 일치. 대/소문자 구분 안 함
datetime {dob:datetime} 2016-12-31, 2016-12-31 7:32pm 유효한 DateTime 값 일치(고정 문화권에서) 이전 경고를 참조하세요.
decimal {price:decimal} 49.99, -1,000.01 유효한 decimal 값 일치(고정 문화권에서) 이전 경고를 참조하세요.
double {weight:double} 1.234, -1,001.01e8 유효한 double 값 일치(고정 문화권에서) 이전 경고를 참조하세요.
float {weight:float} 1.234, -1,001.01e8 유효한 float 값 일치(고정 문화권에서) 이전 경고를 참조하세요.
guid {id:guid} CD2C1638-1638-72D5-1638-DEADBEEF1638 유효한 Guid 값 일치
long {ticks:long} 123456789, -123456789 유효한 long 값 일치
minlength(value) {username:minlength(4)} Rick 문자열은 4자 이상이어야 합니다.
maxlength(value) {filename:maxlength(8)} MyFile 문자열은 8자 이하여야 합니다.
length(length) {filename:length(12)} somefile.txt 문자열은 정확히 12자여야 합니다.
length(min,max) {filename:length(8,16)} somefile.txt 문자열의 길이는 8자 이상이며 16자 이하여야 합니다.
min(value) {age:min(18)} 19 정수 값은 18 이상이어야 합니다.
max(value) {age:max(120)} 91 정수 값은 120 이하여야 합니다.
range(min,max) {age:range(18,120)} 91 정수 값은 18 이상이며 120 이하여야 합니다.
alpha {name:alpha} Rick 문자열은 하나 이상의 영문자(a-z, 대/소문자 구분)로 구성되어야 합니다.
regex(expression) {ssn:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)} 123-45-6789 문자열은 정규식과 일치해야 합니다. 정규식을 정의하는 방법에 대한 팁을 참조하세요.
required {name:required} Rick URL을 생성하는 동안 비-매개 변수 값이 존재하도록 강제하는 데 사용됨

Warning

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

콜론으로 구분된 여러 개의 제약 조건을 단일 매개 변수에 적용할 수 있습니다. 예를 들어 다음 제약 조건은 매개 변수를 1 이상의 정수 값으로 제한합니다.

[Route("users/{id:int:min(1)}")]
public User GetUserById(int id) { }

Warning

CLR 형식으로 변환되는 URL을 확인하는 경로 제약 조건은 항상 고정 문화권을 사용합니다(예: CLR 형식 int 또는 DateTime으로 변환). 이러한 제약 조건은 URL은 지역화될 수 없다고 가정합니다. 프레임워크에서 제공한 경로 제약 조건은 경로 값에 저장된 값을 수정하지 않습니다. URL에서 구문 분석되는 모든 경로 값은 문자열로 저장됩니다. 예를 들어 float 제약 조건은 경로 값을 부동으로 변환하려고 하지만 변환된 값은 부동으로 변환될 수 있는지 확인하는 데만 사용됩니다.

제약 조건의 정규식

Warning

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

regex(...) 경로 제약 조건을 사용하여 정규식을 인라인 제약 조건으로 지정할 수 있습니다. MapControllerRoute 제품군의 메서드에서는 개체 리터럴의 제약 조건도 사용할 수 있습니다. 해당 양식을 사용하는 경우 문자열 값이 정규식으로 해석됩니다.

다음 코드에서는 인라인 regex 제약 조건을 사용합니다.

app.MapGet("{message:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)}",
    () => "Inline Regex Constraint Matched");

다음 코드에서는 개체 리터럴을 사용하여 regex 제약 조건을 지정합니다.

app.MapControllerRoute(
    name: "people",
    pattern: "people/{ssn}",
    constraints: new { ssn = "^\\d{3}-\\d{2}-\\d{4}$", },
    defaults: new { controller = "People", action = "List" });

ASP.NET Core 프레임워크는 정규식 생성자에 RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariant를 추가합니다. 이러한 멤버에 대한 설명은 RegexOptions를 참조하세요.

정규식은 라우팅 및 C# 언어에서 사용하는 것과 유사한 구분 기호 및 토큰을 사용합니다. 정규식 토큰은 이스케이프되어야 합니다. 인라인 제약 조건에서 정규식 ^\d{3}-\d{2}-\d{4}$를 사용하려면 다음 중 하나를 사용합니다.

  • \ 문자열 이스케이프 문자를 이스케이프하기 위해 C# 소스 파일에서 문자열에 제공된 \ 문자를 \\ 문자로 바꿉니다.
  • 축자 문자열 리터럴.

라우팅 매개 변수 구분 기호 문자({, }, [, ])를 이스케이프하려면 식에서 해당 문자를 이중으로 사용합니다(예: {{, }}, [[, ]]). 다음 표에서는 정규식 및 이스케이프된 버전을 보여 줍니다.

정규식 이스케이프된 정규식
^\d{3}-\d{2}-\d{4}$ ^\\d{{3}}-\\d{{2}}-\\d{{4}}$
^[a-z]{2}$ ^[[a-z]]{{2}}$

라우팅에 사용되는 정규식은 ^ 문자로 시작하고 문자열의 시작 위치와 일치하는 경우가 많습니다. 식은 $ 문자로 끝나고 문자열의 끝과 일치하는 경우가 많습니다. ^$ 문자는 정규식이 전체 경로 매개 변수 값과 일치하도록 합니다. ^$ 문자가 없는 정규식은 문자열 내의 모든 하위 문자열과 일치하지만, 이는 종종 원하는 것이 아닙니다. 다음 표에서는 예제를 제공하고, 일치하거나 일치에 실패하는 이유를 설명합니다.

문자열 Match Comment(설명)
[a-z]{2} hello 부분 문자열 일치
[a-z]{2} 123abc456 부분 문자열 일치
[a-z]{2} mz 식 일치
[a-z]{2} MZ 대/소문자 구분하지 않음
^[a-z]{2}$ hello 아니요 위의 ^$ 참조
^[a-z]{2}$ 123abc456 아니요 위의 ^$ 참조

정규식 구문에 대한 자세한 내용은 .NET Framework 정규식을 참조하세요.

가능한 값의 알려진 집합으로 매개 변수를 제한하려면 정규식을 사용합니다. 예를 들어 {action:regex(^(list|get|create)$)}action 경로 값을 list, get 또는 create으로만 일치시킵니다. 제약 조건 사전으로 전달되면 ^(list|get|create)$ 문자열은 동일합니다. 알려진 제약 조건 중 하나와 일치하지 않는 제약 조건 사전에서 전달되는 제약 조건도 정규식으로 처리됩니다. 알려진 제약 조건 중 하나와 일치하지 않는 템플릿 내에서 전달되는 제약 조건은 정규식으로 처리되지 않습니다.

사용자 지정 경로 제약 조건

IRouteConstraint 인터페이스를 구현하여 사용자 지정 경로 제약 조건을 만들 수 있습니다. IRouteConstraint 인터페이스에는 제약 조건이 충족되는 경우 true를 반환하고 그렇지 않은 경우 false를 반환하는 Match가 포함됩니다.

사용자 지정 경로 제약 조건은 거의 필요하지 않습니다. 사용자 지정 경로 제약 조건을 구현하기 전에 모델 바인딩과 같은 다른 방식을 고려해 보세요.

ASP.NET Core Constraints 폴더는 제약 조건을 만드는 좋은 예제를 제공합니다. 예를 들어 GuidRouteConstraint입니다.

사용자 지정 IRouteConstraint를 사용하려면 서비스 컨테이너에 있는 앱의 ConstraintMap에 경로 제약 조건 형식을 등록해야 합니다. ConstraintMap은 경로 제약 조건 키를 해당 제약 조건의 유효성을 검사하는 IRouteConstraint 구현으로 매핑하는 사전입니다. Program.cs에서 AddRouting 호출의 일부로 또는 builder.Services.Configure<RouteOptions>를 사용하여 직접 RouteOptions를 구성하여 앱의 ConstraintMap을 업데이트할 수 있습니다. 예시:

builder.Services.AddRouting(options =>
    options.ConstraintMap.Add("noZeroes", typeof(NoZeroesRouteConstraint)));

위의 제약 조건은 다음 코드에서 적용됩니다.

[ApiController]
[Route("api/[controller]")]
public class NoZeroesController : ControllerBase
{
    [HttpGet("{id:noZeroes}")]
    public IActionResult Get(string id) =>
        Content(id);
}

NoZeroesRouteConstraint를 구현하면 경로 매개 변수에 0이 사용되지 않습니다.

public class NoZeroesRouteConstraint : IRouteConstraint
{
    private static readonly Regex _regex = new(
        @"^[1-9]*$",
        RegexOptions.CultureInvariant | RegexOptions.IgnoreCase,
        TimeSpan.FromMilliseconds(100));

    public bool Match(
        HttpContext? httpContext, IRouter? route, string routeKey,
        RouteValueDictionary values, RouteDirection routeDirection)
    {
        if (!values.TryGetValue(routeKey, out var routeValue))
        {
            return false;
        }

        var routeValueString = Convert.ToString(routeValue, CultureInfo.InvariantCulture);

        if (routeValueString is null)
        {
            return false;
        }

        return _regex.IsMatch(routeValueString);
    }
}

Warning

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

앞의 코드가 하는 역할은 다음과 같습니다.

  • 경로의 {id} 세그먼트에 0을 사용하지 못하게 합니다.
  • 사용자 지정 제약 조건을 구현하는 기본 예제를 제공하기 위해 표시됩니다. 프로덕션 앱에 사용해서는 안 됩니다.

다음 코드는 0이 포함된 id가 처리되지 않게 하는 더 나은 방법입니다.

[HttpGet("{id}")]
public IActionResult Get(string id)
{
    if (id.Contains('0'))
    {
        return StatusCode(StatusCodes.Status406NotAcceptable);
    }

    return Content(id);
}

위의 코드는 NoZeroesRouteConstraint 접근 방식과 비교하면 다음과 같은 이점이 있습니다.

  • 사용자 지정 제약 조건이 필요하지 않습니다.
  • 경로 매개 변수에 0이 포함된 경우 더 자세한 설명이 포함된 오류를 반환합니다.

매개 변수 변환기

매개 변수 변환기는:

예를 들어, Url.Action(new { article = "MyTestArticle" })을 사용하는 경로 패턴 blog\{article:slugify}의 사용자 지정 slugify 매개 변수 변환기는 blog\my-test-article을 생성합니다.

다음 IOutboundParameterTransformer 구현을 생각해 보겠습니다.

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

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

경로 패턴에서 매개 변수 변환기를 사용하려면 Program.csConstraintMap을 사용하여 구성합니다.

builder.Services.AddRouting(options =>
    options.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer));

ASP.NET Core Framework는 매개 변수 변화기를 사용하여 엔드포인트가 해결되는 URI를 변환합니다. 예를 들어 매개 변수 변환기는 area, controller, actionpage와 일치하도록 사용되는 경로 값을 변환합니다.

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

위의 경로 템플릿을 사용하면 SubscriptionManagementController.GetAll 작업이 URI /subscription-management/get-all과 일치합니다. 매개 변수 변환기는 링크를 생성하는 데 사용되는 경로 값을 변경하지 않습니다. 예를 들어 Url.Action("GetAll", "SubscriptionManagement")/subscription-management/get-all을 출력합니다.

ASP.NET Core는 생성된 경로와 함께 매개 변수 변환기를 사용하기 위한 API 규칙을 제공합니다.

URL 생성 참조

이 단원에는 URL 생성에서 구현하는 알고리즘에 대한 참조가 포함되어 있습니다. 실제로 URL 생성의 가장 복잡한 예제에서는 컨트롤러나 Razor Pages를 사용합니다. 자세한 내용은 컨트롤러의 라우팅을 참조하세요.

URL 생성 프로세스는 호출 또는 유사한 메서드로 LinkGenerator.GetPathByAddress 시작합니다. 메서드에는 주소, 경로 값 집합 및 HttpContext의 현재 요청에 관한 정보(선택 사항)를 제공합니다.

첫 번째 단계에서는 주소를 사용하여 주소의 형식과 일치하는 IEndpointAddressScheme<TAddress>을 사용하는 후보 엔드포인트 집합을 확인합니다.

주소 체계에 따라 후보 세트를 찾으면 URL 생성 작업이 성공할 때까지 엔드포인트가 순서 지정되고 반복적으로 처리됩니다. URL 생성에서는 모호성을 확인하지 않으며, 반환되는 첫 번째 결과가 최종 결과입니다.

로깅을 사용하여 URL 생성 문제 해결

URL 생성 문제를 해결하는 첫 번째 단계는 Microsoft.AspNetCore.Routing의 로깅 수준을 TRACE로 설정하는 것입니다. LinkGenerator는 문제 해결에 유용할 수 있는 처리에 관한 여러 세부 정보를 기록합니다.

URL 생성에 대한 자세한 내용은 URL 생성 참조를 참조하세요.

주소

주소는 URL 생성에서 링크 생성기의 호출을 후보 엔드포인트 집합에 바인딩하는 데 사용되는 개념입니다.

주소는 기본적으로 다음 두 가지 구현이 함께 제공되는 확장 가능한 개념입니다.

  • 엔드포인트 이름(string)을 주소로 사용:
    • MVC의 경로 이름과 유사한 기능을 제공합니다.
    • IEndpointNameMetadata 메타데이터 형식을 사용합니다.
    • 등록된 모든 엔드포인트의 메타데이터와 제공된 문자열을 비교하여 확인합니다.
    • 여러 엔드포인트에서 같은 이름을 사용하는 경우 시작 시 예외를 throw합니다.
    • 컨트롤러 및 Razor Pages 외의 일반적인 용도에 사용하는 것이 좋습니다.
  • 경로 값(RouteValuesAddress)을 주소로 사용:
    • 컨트롤러 및 Razor Pages 레거시 URL 생성과 비슷한 기능을 제공합니다.
    • 확장 및 디버그하기가 매우 복잡합니다.
    • IUrlHelper, 태그 도우미, HTML 도우미, 작업 결과 등에서 사용하는 구현을 제공합니다.

주소 체계의 역할은 다음과 같은 임의 조건에 따라 주소와 일치하는 엔드포인트 사이를 연결하는 것입니다.

  • 엔드포인트 이름 체계에서 기본 사전 조회를 수행합니다.
  • 경로 값 체계에는 알고리즘의 복잡한 최적 하위 집합이 있습니다.

앰비언트 값 및 명시적 값

현재 요청에서 라우팅은 현재 요청의 경로 값 HttpContext.Request.RouteValues에 액세스합니다. 현재 요청과 연결된 값을 앰비언트 값이라고 합니다. 명확하게 하도록 설명서에서는 메서드에 전달된 경로 값을 명시적 값이라고 합니다.

다음 예제에서는 앰비언트 값과 명시적 값을 보여 줍니다. 현재 요청의 앰비언트 값과 명시적 값을 제공합니다.

public class WidgetController : ControllerBase
{
    private readonly LinkGenerator _linkGenerator;

    public WidgetController(LinkGenerator linkGenerator) =>
        _linkGenerator = linkGenerator;

    public IActionResult Index()
    {
        var indexPath = _linkGenerator.GetPathByAction(
            HttpContext, values: new { id = 17 })!;

        return Content(indexPath);
    }

    // ...

앞의 코드가 하는 역할은 다음과 같습니다.

  • /Widget/Index/17를 반환합니다.
  • DI를 통해 LinkGenerator를 가져옵니다.

다음 코드는 명시적 값만 제공하고 앰비언트 값은 제공하지 않습니다.

var subscribePath = _linkGenerator.GetPathByAction(
    "Subscribe", "Home", new { id = 17 })!;

앞의 메서드는 /Home/Subscribe/17을 반환합니다.

WidgetController의 다음 코드는 /Widget/Subscribe/17을 반환합니다.

var subscribePath = _linkGenerator.GetPathByAction(
    HttpContext, "Subscribe", null, new { id = 17 });

다음 코드는 현재 요청의 앰비언트 값과 명시적 값을 컨트롤러에 제공합니다.

public class GadgetController : ControllerBase
{
    public IActionResult Index() =>
        Content(Url.Action("Edit", new { id = 17 })!);
}

위의 코드에서

  • /Gadget/Edit/17이 반환됩니다.
  • UrlIUrlHelper를 가져옵니다.
  • Action은 작업 메서드의 절대 경로가 포함된 URL을 생성합니다. URL에는 지정된 action 이름과 route 값이 포함됩니다.

다음 코드에서는 현재 요청의 앰비언트 값과 명시적 값을 제공합니다.

public class IndexModel : PageModel
{
    public void OnGet()
    {
        var editUrl = Url.Page("./Edit", new { id = 17 });

        // ...
    }
}

앞의 코드에서는 Razor 편집 페이지에 다음 페이지 지시문이 포함된 경우 url/Edit/17로 설정합니다.

@page "{id:int}"

편집 페이지에 "{id:int}" 경로 템플릿이 포함되어 있지 않으면 url/Edit?id=17입니다.

MVC의 IUrlHelper 동작은 여기에 설명된 규칙 외에도 복잡성 계층을 추가합니다.

  • IUrlHelper는 항상 현재 요청의 경로 값을 앰비언트 값으로 제공합니다.
  • IUrlHelper.Action은 개발자가 재정의하는 경우 외에는 항상 현재 actioncontroller 경로 값을 명시적 값으로 복사합니다.
  • IUrlHelper.Page는 재정의되는 경우 외에는 항상 현재 page 경로 값을 명시적 값으로 복사합니다.
  • IUrlHelper.Page는 재정의되는 경우 외에는 항상 현재 handler 경로 값을 명시적 값 null로 재정의합니다.

MVC가 자체 규칙을 따르지 않기 때문에 앰비언트 값의 동작 세부 정보에 사용자가 놀라는 경우가 많습니다. 기록 및 호환성을 위해 action, controller, page, handler 등의 특정 경로 값에는 고유한 특수 사례 동작이 있습니다.

LinkGenerator.GetPathByActionLinkGenerator.GetPathByPage에서 제공하는 동일한 기능에서는 호환성을 위해 IUrlHelper의 이러한 변칙을 복제합니다.

URL 생성 프로세스

후보 엔드포인트 집합을 찾으면 URL 생성 알고리즘은 다음을 수행합니다.

  • 엔드포인트를 반복적으로 처리합니다.
  • 첫 번째 성공적인 결과를 반환합니다.

이 프로세스의 첫 번째 단계를 경로 값 무효화라고 합니다. 경로 값 무효화는 라우팅에서 앰비언트 값의 어떤 경로 값을 사용하고 무시할지 결정하는 프로세스입니다. 각 앰비언트 값이 고려되고 명시적 값과 결합되거나 무시됩니다.

앰비언트 값의 역할을 알아보려면 일반적인 사례에서 애플리케이션 개발자 입력을 저장하려고 하는 점을 고려하면 가장 좋습니다. 일반적으로 앰비언트 값이 유용한 시나리오는 MVC와 관련이 있습니다.

  • 동일한 컨트롤러의 다른 작업에 연결하는 경우에는 컨트롤러 이름을 지정할 필요가 없습니다.
  • 같은 영역의 다른 컨트롤러에 연결하는 경우 영역 이름을 지정할 필요가 없습니다.
  • 동일한 작업 메서드에 연결하는 경우 경로 값을 지정할 필요가 없습니다.
  • 앱의 다른 부분에 연결하는 경우 앱의 해당 부분에서 의미가 없는 경로 값을 전달하지 않을 수 있습니다.

null을 반환하는 LinkGenerator 또는 IUrlHelper를 호출하면 일반적으로 이해되지 않는 경로 값 무효화가 발생합니다. 경로 값 무효화 문제를 해결하려면 더 많은 경로 값을 명시적으로 지정하여 문제가 해결되는지 확인합니다.

경로 값 무효화는 앱의 URL 체계가 계층이 왼쪽에서 오른쪽으로 형성된 계층 구조라고 가정하고 작동합니다. 기본 컨트롤러 경로 템플릿 {controller}/{action}/{id?}를 사용하여 실제 작동 방법을 직관적으로 파악해 보겠습니다. 값을 변경하면 오른쪽에 표시되는 경로 값이 모두 무효화됩니다. 이는 계층 구조에 관한 가정이 반영된 것입니다. 앱에 id의 앰비언트 값이 있고 작업에서 controller에 대해 다른 값을 지정하는 경우:

  • {controller}{id?}의 왼쪽에 있으므로 id가 다시 사용되지 않습니다.

이 원칙을 보여 주는 몇 가지 예는 다음과 같습니다.

  • 명시적 값에 id의 값이 포함된 경우 id의 앰비언트 값은 무시됩니다. controlleraction의 앰비언트 값이 사용될 수 있습니다.
  • 명시적 값에 action의 값이 포함된 경우 action의 앰비언트 값은 무시됩니다. controller의 앰비언트 값이 사용될 수 있습니다. action의 명시적 값이 action의 앰비언트 값과 다른 경우 id 값은 사용되지 않습니다. action의 명시적 값이 action의 앰비언트 값과 같으면 id 값이 사용될 수 있습니다.
  • 명시적 값에 controller의 값이 포함된 경우 controller의 앰비언트 값은 무시됩니다. controller의 명시적 값이 controller의 앰비언트 값과 다른 경우 actionid 값은 사용되지 않습니다. controller의 명시적 값이 controller의 앰비언트 값과 같으면 actionid 값이 사용될 수 있습니다.

이 프로세스는 특성 경로와 전용 규칙 기반 경로가 있으면 더 복잡해집니다. {controller}/{action}/{id?}와 같은 컨트롤러 규칙 기반 경로는 경로 매개 변수를 사용하여 계층 구조를 지정합니다. 컨트롤러 및 Razor Pages에 대한 전용 규칙 기반 경로특성 경로의 경우:

  • 경로 값의 계층 구조가 있습니다.
  • 템플릿에는 표시되지 않습니다.

이러한 경우 URL 생성에서 필수 값 개념을 정의합니다. 컨트롤러 및 Razor Pages에서 만든 엔드포인트에는 경로 값 무효화가 작동할 수 있도록 필수 값이 지정되어 있습니다.

경로 값 무효화 알고리즘을 자세히 설명하면 다음과 같습니다.

  • 필요 값 이름을 경로 매개 변수와 결합한 다음 왼쪽에서 오른쪽으로 처리합니다.
  • 각 매개 변수에 대해 앰비언트 값과 명시적 값이 비교됩니다.
    • 앰비언트 값과 명시적 값이 같으면 프로세스가 계속됩니다.
    • 앰비언트 값이 있고 명시적 값이 없으면 URL을 생성할 때 앰비언트 값이 사용됩니다.
    • 앰비언트 값이 없고 명시적 값이 있으면 앰비언트 값과 모든 후속 앰비언트 값을 거부합니다.
    • 앰비언트 값과 명시적 값이 있고 두 값이 다른 경우 앰비언트 값과 모든 후속 앰비언트 값을 거부합니다.

이제 URL 생성 작업에서 경로 제약 조건을 평가할 준비가 되었습니다. 허용되는 값 집합이 제약 조건에 제공되는 매개 변수 기본값과 결합됩니다. 제약 조건이 모두 통과되면 작업이 계속됩니다.

다음으로 허용되는 값을 사용하여 경로 템플릿을 확장할 수 있습니다. 경로 템플릿은 다음과 같이 처리됩니다.

  • 왼쪽에서 오른쪽으로
  • 각 매개 변수의 허용되는 값이 대체됩니다.
  • 다음과 같은 특수한 경우를 사용합니다.
    • 허용되는 값에 누락된 값이 있고 매개 변수에 기본값이 있으면 기본값이 사용됩니다.
    • 허용되는 값에 누락된 값이 있고 매개 변수가 선택 사항이면 처리가 계속됩니다.
    • 누락된 선택적 매개 변수 오른쪽의 경로 매개 변수에 값이 있으면 작업이 실패합니다.
    • 연속된 기본값 매개 변수 및 선택적 매개 변수는 가능하면 축소됩니다.

명시적으로 제공되지만 경로의 세그먼트와 일치하지 않는 값은 쿼리 문자열에 추가됩니다. 다음 표에서 경로 템플릿 {controller}/{action}/{id?}를 사용하는 경우 결과를 보여 줍니다.

앰비언트 값 명시적 값 결과
controller = "Home" action = "About" /Home/About
controller = "Home" controller = "Order", action = "About" /Order/About
controller = "Home", color = "Red" action = "About" /Home/About
controller = "Home" action = "About", color = "Red" /Home/About?color=Red

선택적 경로 매개 변수 순서

선택적 경로 매개 변수는 모든 필수 경로 매개 변수 및 리터럴 후에 와야 합니다. 다음 코드에서 매개 변수와 name 매개 변수는 id 매개 변수 다음에 color 와야 합니다.

using Microsoft.AspNetCore.Mvc;

namespace WebApplication1.Controllers;

[Route("api/[controller]")]
public class MyController : ControllerBase
{
    // GET /api/my/red/2/joe
    // GET /api/my/red/2
    // GET /api/my
    [HttpGet("{color}/{id:int?}/{name?}")]
    public IActionResult GetByIdAndOptionalName(string color, int id = 1, string? name = null)
    {
        return Ok($"{color} {id} {name ?? ""}");
    }
}

경로 값 무효화 문제

다음 코드에서는 라우팅에서 지원하지 않는 URL 생성 체계의 예제를 보여 줍니다.

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

app.MapControllerRoute(
    "blog",
    "{culture}/{**slug}",
    new { controller = "Blog", action = "ReadPost" });

위의 코드에서 culture 경로 매개 변수는 지역화에 사용됩니다. culture 매개 변수가 앰비언트 값으로 항상 허용되게 하려는 것이 목표입니다. 그러나 culture 매개 변수는 필수 값이 작동하는 방식 때문에 앰비언트 값으로 허용되지 않습니다.

  • "default" 경로 템플릿에서 culture 경로 매개 변수는 controller의 왼쪽에 있으므로 controller를 변경해도 culture가 무효화되지 않습니다.
  • "blog" 경로 템플릿에서 culture 경로 매개 변수는 필수 값에 표시되는 controller의 오른쪽에 있는 것으로 간주됩니다.

LinkParser을(를) 사용하여 URL 경로 구문 분석

LinkParser클래스는 URL 경로를 경로 값 집합으로 구문 분석하기 위한 지원을 추가합니다. ParsePathByEndpointName 메서드는 엔드포인트 이름과 URL 경로를 사용하고 URL 경로에서 추출된 경로 값 집합을 반환합니다.

다음 예제 컨트롤러에서 GetProduct 작업은 api/Products/{id}의 경로 템플릿을 사용하며 GetProductName을 가집니다.

[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
    [HttpGet("{id}", Name = nameof(GetProduct))]
    public IActionResult GetProduct(string id)
    {
        // ...

동일한 컨트롤러 클래스에서 AddRelatedProduct 작업에는 쿼리 문자열 매개 변수로 제공할 수 있는 URL 경로 pathToRelatedProduct가 필요합니다.

[HttpPost("{id}/Related")]
public IActionResult AddRelatedProduct(
    string id, string pathToRelatedProduct, [FromServices] LinkParser linkParser)
{
    var routeValues = linkParser.ParsePathByEndpointName(
        nameof(GetProduct), pathToRelatedProduct);
    var relatedProductId = routeValues?["id"];

    // ...

앞의 예제에서 AddRelatedProduct 작업은 URL 경로에서 id 경로 값을 추출합니다. 예를 들어 /api/Products/1의 URL 경로가 있는 경우 relatedProductId 값은 1(으)로 설정됩니다. 이 방법을 사용하면 API의 클라이언트가 리소스를 참조할 때 URL 경로를 사용할 수 있으므로 이러한 URL이 어떻게 구성되는지에 대한 지식이 필요하지 않습니다.

엔드포인트 메타데이터 구성

다음 링크는 엔드포인트 메타데이터를 구성하는 방법에 대한 정보를 제공합니다.

RequireHost가 있는 경로의 호스트 일치

RequireHost는 지정된 호스트가 필요한 경로에 제약 조건을 적용합니다. RequireHost 또는 [Host] 매개 변수는 다음과 같을 수 있습니다.

  • 호스트: www.domain.com(아무 포트에서나 www.domain.com과 일치)
  • 와일드카드가 있는 호스트: *.domain.com(아무 포트에서나 www.domain.com, subdomain.domain.com 또는 www.subdomain.domain.com과 일치)
  • 포트: *:5000(아무 호스트에서나 포트 5000과 일치)
  • 호스트 및 포트: www.domain.com:5000 또는 *.domain.com:5000(호스트 및 포트와 일치)

RequireHost 또는 [Host]를 사용하여 여러 매개 변수를 지정할 수 있습니다. 제약 조건은 모든 매개 변수에 유효한 호스트와 일치합니다. 예를 들어 [Host("domain.com", "*.domain.com")]domain.com, www.domain.comsubdomain.domain.com과 일치합니다.

다음 코드는 RequireHost를 사용하여 경로상에 있는 지정된 호스트를 요구합니다.

app.MapGet("/", () => "Contoso").RequireHost("contoso.com");
app.MapGet("/", () => "AdventureWorks").RequireHost("adventure-works.com");

app.MapHealthChecks("/healthz").RequireHost("*:8080");

다음 코드는 컨트롤러의 [Host] 특성을 사용하여 지정된 호스트를 요구합니다.

[Host("contoso.com", "adventure-works.com")]
public class HostsController : Controller
{
    public IActionResult Index() =>
        View();

    [Host("example.com")]
    public IActionResult Example() =>
        View();
}

[Host] 특성이 컨트롤러 메서드와 작업 메서드에 모두 적용될 경우

  • 작업의 특성이 사용됩니다.
  • 컨트롤러의 특성은 무시됩니다.

Warning

호스트 헤더(예: HttpRequest.HostRequireHost)를 사용하는 API는 클라이언트에서 스푸핑될 수 있습니다.

호스트 및 포트 스푸핑을 방지하려면 다음 방법 중 하나를 사용합니다.

경로 그룹

MapGroup확장 메서드는 공통 접두사를 사용하여 엔드포인트 그룹을 구성하는 데 도움이 됩니다. 반복 코드를 줄이고 엔드포인트 메타데이터를 추가하는 RequireAuthorization이나 WithMetadata와 같은 메서드로서 단일 호출로 엔드포인트 전체 그룹을 사용자 지정할 수 있게 됩니다.

예를 들어 다음 코드에서는 두 개의 유사한 엔드포인트 그룹을 만듭니다.

app.MapGroup("/public/todos")
    .MapTodosApi()
    .WithTags("Public");

app.MapGroup("/private/todos")
    .MapTodosApi()
    .WithTags("Private")
    .AddEndpointFilterFactory(QueryPrivateTodos)
    .RequireAuthorization();


EndpointFilterDelegate QueryPrivateTodos(EndpointFilterFactoryContext factoryContext, EndpointFilterDelegate next)
{
    var dbContextIndex = -1;

    foreach (var argument in factoryContext.MethodInfo.GetParameters())
    {
        if (argument.ParameterType == typeof(TodoDb))
        {
            dbContextIndex = argument.Position;
            break;
        }
    }

    // Skip filter if the method doesn't have a TodoDb parameter.
    if (dbContextIndex < 0)
    {
        return next;
    }

    return async invocationContext =>
    {
        var dbContext = invocationContext.GetArgument<TodoDb>(dbContextIndex);
        dbContext.IsPrivate = true;

        try
        {
            return await next(invocationContext);
        }
        finally
        {
            // This should only be relevant if you're pooling or otherwise reusing the DbContext instance.
            dbContext.IsPrivate = false;
        }
    };
}
public static RouteGroupBuilder MapTodosApi(this RouteGroupBuilder group)
{
    group.MapGet("/", GetAllTodos);
    group.MapGet("/{id}", GetTodo);
    group.MapPost("/", CreateTodo);
    group.MapPut("/{id}", UpdateTodo);
    group.MapDelete("/{id}", DeleteTodo);

    return group;
}

이 시나리오에서는 201 Created 결과에서 Location 헤더에 대한 상대 주소를 사용할 수 있습니다.

public static async Task<Created<Todo>> CreateTodo(Todo todo, TodoDb database)
{
    await database.AddAsync(todo);
    await database.SaveChangesAsync();

    return TypedResults.Created($"{todo.Id}", todo);
}

첫 번째 엔드포인트 그룹은 접두사로 지정된 요청만 /public/todos 매칭하게 되며 인증 없이 액세스할 수 있습니다. 두 번째 엔드포인트 그룹은 접두사 /private/todos를 가지고 인증이 필요한 요청만 일치합니다.

QueryPrivateTodos엔드포인트 필터 팩터리는 경로 처리기의 TodoDb 매개 변수를 수정하여 프라이빗 할일 데이터에 접근하고 보관할 수 있도록 수정하는 로컬 함수입니다.

경로 그룹은 경로 매개 변수 및 제약 조건이 있는 중첩 그룹 및 복잡한 접두사 유형도 지원합니다. 다음 예제에서 그룹에 매핑된 user 경로 처리기는 외부 그룹 접두사에 정의된 {org}{group} 경로 매개 변수를 캡처할 수 있습니다.

접두사는 비어 있을 수도 있습니다. 이 기능은 경로 패턴을 변경하지 않고 엔드포인트 메타데이터 또는 필터를 엔드포인트 그룹에 추가하는 데 유용할 수 있습니다.

var all = app.MapGroup("").WithOpenApi();
var org = all.MapGroup("{org}");
var user = org.MapGroup("{user}");
user.MapGet("", (string org, string user) => $"{org}/{user}");

그룹에 필터 또는 메타데이터를 추가하면 내부 그룹 또는 특정 엔드포인트에 추가될 수 있는 추가 필터 또는 메타데이터를 추가하기 전에 각 엔드포인트에 개별적으로 추가하는 것과 동일한 결과가 됩니다.

var outer = app.MapGroup("/outer");
var inner = outer.MapGroup("/inner");

inner.AddEndpointFilter((context, next) =>
{
    app.Logger.LogInformation("/inner group filter");
    return next(context);
});

outer.AddEndpointFilter((context, next) =>
{
    app.Logger.LogInformation("/outer group filter");
    return next(context);
});

inner.MapGet("/", () => "Hi!").AddEndpointFilter((context, next) =>
{
    app.Logger.LogInformation("MapGet filter");
    return next(context);
});

위의 예제에서 외부 필터는 두 번째로 추가된 경우에도 내부 필터 앞에 들어오는 요청을 기록하게 됩니다. 필터가 서로 다른 그룹에 적용되었기 때문에 필터가 추가된 상대적 순서는 중요하지 않습니다. 동일한 그룹 또는 특정 엔드포인트에 적용되는 경우 주문 필터가 추가됩니다.

/outer/inner/에 대한 요청은 다음을 기록합니다.

/outer group filter
/inner group filter
MapGet filter

라우팅의 성능 지침

앱에 성능 문제가 있는 경우 라우팅이 문제의 원인으로 의심받는 경우가 많습니다. 라우팅이 의심받는 이유는 컨트롤러 및 Razor Pages 같은 프레임워크가 프레임워크 내에서 소요된 시간을 로깅 메시지로 보고하기 때문입니다. 컨트롤러에서 보고하는 시간과 요청의 총 시간 사이에 상당한 차이가 있는 경우:

  • 개발자는 문제의 원인인 앱 코드를 제거합니다.
  • 일반적으로 라우팅이 원인이라고 가정합니다.

라우팅은 수천 개의 엔드포인트를 사용하여 성능을 테스트했습니다. 일반적인 앱에서는 너무 크다고 성능 문제가 발생할 가능성은 거의 없습니다. 라우팅 성능이 저하되는 가장 일반적인 근본 원인은 일반적으로 잘못 동작하는 사용자 지정 미들웨어입니다.

다음 코드 샘플에서는 지연의 원인을 좁히기 위한 기본 기술을 보여 줍니다.

var logger = app.Services.GetRequiredService<ILogger<Program>>();

app.Use(async (context, next) =>
{
    var stopwatch = Stopwatch.StartNew();
    await next(context);
    stopwatch.Stop();

    logger.LogInformation("Time 1: {ElapsedMilliseconds}ms", stopwatch.ElapsedMilliseconds);
});

app.UseRouting();

app.Use(async (context, next) =>
{
    var stopwatch = Stopwatch.StartNew();
    await next(context);
    stopwatch.Stop();

    logger.LogInformation("Time 2: {ElapsedMilliseconds}ms", stopwatch.ElapsedMilliseconds);
});

app.UseAuthorization();

app.Use(async (context, next) =>
{
    var stopwatch = Stopwatch.StartNew();
    await next(context);
    stopwatch.Stop();

    logger.LogInformation("Time 3: {ElapsedMilliseconds}ms", stopwatch.ElapsedMilliseconds);
});

app.MapGet("/", () => "Timing Test.");

종료 시간 라우팅:

  • 앞의 코드에 표시된 타이밍 미들웨어의 복사본을 각 미들웨어에 인터리빙합니다.
  • 고유 식별자를 추가하여 타이밍 데이터를 코드와 연관 짓습니다.

이것이 예를 들어 10ms 이상의 상당한 지연이 발생하는 경우 지연을 좁히는 기본적인 방법입니다. Time 1에서 Time 2를 빼면 UseRouting 미들웨어 내에서 소요된 시간이 보고됩니다.

다음 코드에서는 앞의 타이밍 코드에 더욱 간결한 방법을 사용합니다.

public sealed class AutoStopwatch : IDisposable
{
    private readonly ILogger _logger;
    private readonly string _message;
    private readonly Stopwatch _stopwatch;
    private bool _disposed;

    public AutoStopwatch(ILogger logger, string message) =>
        (_logger, _message, _stopwatch) = (logger, message, Stopwatch.StartNew());

    public void Dispose()
    {
        if (_disposed)
        {
            return;
        }

        _logger.LogInformation("{Message}: {ElapsedMilliseconds}ms",
            _message, _stopwatch.ElapsedMilliseconds);

        _disposed = true;
    }
}
var logger = app.Services.GetRequiredService<ILogger<Program>>();
var timerCount = 0;

app.Use(async (context, next) =>
{
    using (new AutoStopwatch(logger, $"Time {++timerCount}"))
    {
        await next(context);
    }
});

app.UseRouting();

app.Use(async (context, next) =>
{
    using (new AutoStopwatch(logger, $"Time {++timerCount}"))
    {
        await next(context);
    }
});

app.UseAuthorization();

app.Use(async (context, next) =>
{
    using (new AutoStopwatch(logger, $"Time {++timerCount}"))
    {
        await next(context);
    }
});

app.MapGet("/", () => "Timing Test.");

비용이 많이 들 수 있는 라우팅 기능

다음 목록에서는 기본 경로 템플릿보다 비교적 비용이 많이 드는 라우팅 기능에 대한 몇 가지 유용한 정보를 제공합니다.

  • 정규식: 복잡한 정규식을 작성하거나 적은 양의 입력으로 장기 실행 시간을 가질 수 있습니다.
  • 복합 세그먼트({x}-{y}-{z}):
    • 일반 URL 경로 세그먼트를 구문 분석하는 것보다 훨씬 비용이 많이 듭니다.
    • 더 많은 부분 문자열이 할당됩니다.
  • 동기 데이터 액세스: 많은 복잡한 앱은 라우팅의 일부로 데이터베이스 액세스 권한을 갖습니다. MatcherPolicyEndpointSelectorContext 같은 비동기식 확장 지점을 사용합니다.

큰 라우팅 테이블 지침

기본적으로 ASP.NET Core는 메모리를 CPU 시간과 교환하는 라우팅 알고리즘을 사용합니다. 이는 경로 일치 시간이 일치시킬 경로의 길이에만 종속되고 경로 수에 종속되지 않는 좋은 효과가 있습니다. 하지만 앱에 경로가 많고(수천 개) 경로에 많은 변수 접두사가 있는 경우 이 방법은 잠재적으로 문제가 될 수 있습니다. 예를 들어 경로의 초기 세그먼트에 {parameter}/some/literal과 같은 매개 변수가 있는 경우입니다.

다음과 같은 경우가 아니면 이것이 문제가 되는 상황이 앱에 발생할 가능성은 낮습니다.

  • 이 패턴을 사용하는 앱에 많은 수의 경로가 있습니다.
  • 앱에 많은 수의 경로가 있습니다.

앱에서 큰 라우팅 테이블 문제가 발생하는지 확인하는 방법

  • 찾아야 할 두 가지 증상이 있습니다.
    • 앱이 첫 번째 요청에서 시작하는 속도가 느립니다.
      • 이 증상은 필수이지만 충분하지는 않습니다. 앱 시작 속도가 느려질 수 있는 경로 문제 외의 다른 문제가 많이 있습니다. 아래 조건을 확인하여 앱에 이 상황이 발생하고 있는지 정확하게 확인합니다.
    • 앱이 시작하는 동안 많은 메모리를 사용하고 메모리 덤프에 많은 수의 Microsoft.AspNetCore.Routing.Matching.DfaNode 인스턴스가 표시됩니다.

이 문제를 해결하는 방법

이 시나리오를 크게 개선하는 경로에 적용할 수 있는 몇 가지 기술과 최적화가 있습니다.

  • 가능한 경우 {parameter:int}, {parameter:guid}, {parameter:regex(\\d+)} 등과 같은 경로 제약 조건을 매개 변수에 적용합니다.
    • 그러면 라우팅 알고리즘이 일치에 사용되는 구조를 내부적으로 최적화하고 사용되는 메모리를 크게 줄일 수 있습니다.
    • 대부분의 경우 허용 가능한 동작으로 돌아가는 데는 이것으로 충분합니다.
  • 템플릿에서 매개 변수를 이후 세그먼트로 이동하도록 경로를 변경합니다.
    • 그러면 특정 경로의 엔드포인트와 일치하는 "경로" 수가 줄어듭니다.
  • 동적 경로를 사용하고 컨트롤러/페이지에 대한 매핑을 동적으로 수행합니다.
    • MapDynamicControllerRouteMapDynamicPageRoute를 사용하면 가능합니다.

라우팅 후 단락 미들웨어

라우팅이 엔드포인트와 일치하는 경우 일반적으로 엔드포인트 논리를 호출하기 전에 나머지 미들웨어 파이프라인을 실행할 수 있습니다. 서비스는 파이프라인 초기에 알려진 요청을 필터링하여 리소스 사용량을 줄일 수 있습니다. 확장 메서드를 ShortCircuit 사용하여 라우팅이 엔드포인트 논리를 즉시 호출한 다음 요청을 종료하도록 합니다. 예를 들어 지정된 경로는 인증 또는 CORS 미들웨어를 통과하지 않아도 될 수 있습니다. 다음 예제에서는 경로와 일치하는 /short-circuit 단락 요청을 제공합니다.

app.MapGet("/short-circuit", () => "Short circuiting!").ShortCircuit();

메서드는 ShortCircuit(IEndpointConventionBuilder, Nullable<Int32>) 필요에 따라 상태 코드를 사용할 수 있습니다.

이 메서드를 MapShortCircuit 사용하여 URL 접두사 매개 변수 배열을 전달하여 한 번에 여러 경로에 대한 단락을 설정합니다. 예를 들어 브라우저와 봇은 종종 서버에서 잘 알려진 경로(예: robots.txtfavicon.ico.)를 검색합니다. 앱에 해당 파일이 없는 경우 한 줄의 코드가 두 경로를 모두 구성할 수 있습니다.

app.MapShortCircuit(404, "robots.txt", "favicon.ico");

MapShortCircuit 는 호스트 필터링과 같은 추가 경로 제약 조건을 추가할 수 있도록 반환 IEndpointConventionBuilder 합니다.

MapShortCircuit 메서드는 ShortCircuit 앞에 UseRouting배치된 미들웨어에 영향을 미치지 않습니다. 이러한 메서드를 엔드포인트 또는 [RequireCors] 메타데이터가 있는 [Authorize] 엔드포인트에서 사용하려고 하면 요청이 실패InvalidOperationException합니다. 이 메타데이터는 특성 또는 [EnableCors] 메서드에 의해 RequireAuthorization[Authorize]RequireCors 적용됩니다.

단락 미들웨어의 효과를 보려면 다음에서 appsettings.Development.json"Microsoft" 로깅 범주를 "정보"로 설정합니다.

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

다음 코드를 실행하세요.

var app = WebApplication.Create();

app.UseHttpLogging();

app.MapGet("/", () => "No short-circuiting!");
app.MapGet("/short-circuit", () => "Short circuiting!").ShortCircuit();
app.MapShortCircuit(404, "robots.txt", "favicon.ico");

app.Run();

다음 예제는 엔드포인트를 실행하여 생성된 콘솔 로그에서 / 가져옵니다. 여기에는 로깅 미들웨어의 출력이 포함됩니다.

info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0]
      Executing endpoint 'HTTP: GET /'
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1]
      Executed endpoint 'HTTP: GET /'
info: Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware[2]
      Response:
      StatusCode: 200
      Content-Type: text/plain; charset=utf-8
      Date: Wed, 03 May 2023 21:05:59 GMT
      Server: Kestrel
      Alt-Svc: h3=":5182"; ma=86400
      Transfer-Encoding: chunked

다음 예제는 엔드포인트를 실행하는 것입니다 /short-circuit . 미들웨어가 단락되었기 때문에 로깅 미들웨어에는 아무 것도 없습니다.

info: Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware[4]
      The endpoint 'HTTP: GET /short-circuit' is being executed without running additional middleware.
info: Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware[5]
      The endpoint 'HTTP: GET /short-circuit' has been executed without running additional middleware.

라이브러리 작성자를 위한 지침

이 단원에는 라우팅을 기반으로 빌드하는 라이브러리 작성자를 위한 지침이 포함되어 있습니다. 이러한 세부 내용은 앱 개발자가 라우팅을 확장하는 라이브러리 및 프레임워크를 사용하는 좋은 환경을 갖추도록 하기 위한 것입니다.

엔드포인트 정의

URL 일치를 위해 라우팅을 사용하는 프레임워크를 만들려면 먼저 UseEndpoints를 기반으로 빌드되는 사용자 환경을 정의합니다.

IEndpointRouteBuilder를 기반으로 빌드하세요. 이렇게 하면 사용자는 혼동하지 않고 다른 ASP.NET Core 기능을 사용하여 프레임워크를 작성할 수 있습니다. 모든 ASP.NET Core 템플릿에는 라우팅이 포함됩니다. 라우팅이 있고 사용자에게 친숙하다고 간주합니다.

// Your framework
app.MapMyFramework(...);

app.MapHealthChecks("/healthz");

IEndpointConventionBuilder를 구현하는 MapMyFramework(...) 호출에서 봉인된 구체적인 형식을 반환하세요. 대부분의 프레임워크 Map... 메서드는 이 패턴을 따릅니다. IEndpointConventionBuilder 인터페이스:

  • 메타데이터를 작성할 수 있습니다.
  • 다양한 확장 메서드의 대상으로 지정됩니다.

고유의 형식을 선언하면 작성기에 사용자 고유의 프레임워크 관련 기능을 추가할 수 있습니다. 프레임워크 선언된 작성기를 래핑하고 여기에 호출을 전달해도 됩니다.

// Your framework
app.MapMyFramework(...)
    .RequireAuthorization()
    .WithMyFrameworkFeature(awesome: true);

app.MapHealthChecks("/healthz");

사용자 고유의 EndpointDataSource를 작성하는 것이 좋습니다. EndpointDataSource는 엔드포인트 컬렉션을 선언하고 업데이트하기 위한 하위 수준 기본 형식입니다. EndpointDataSource는 컨트롤러 및 Razor Pages에서 사용되는 강력한 API입니다. 자세한 내용은 동적 엔드포인트 라우팅을 참조 하세요.

라우팅 테스트에는 업데이트되지 않는 데이터 원본의 기본 예제가 있습니다.

GetGroupedEndpoints 구현을 고려합니다. 이렇게 하면 그룹화된 엔드포인트에서 실행 중인 그룹 규칙 및 최종 메타데이터를 완벽하게 제어할 수 있습니다. 예를 들어, 사용자 지정 EndpointDataSource 구현에서 그룹에 추가된 엔드포인트 필터를 실행할 수 있습니다.

기본적으로 EndpointDataSource를 등록하지 마세요. 프레임워크를 UseEndpoints에 등록하도록 사용자에게 요구하세요. 라우팅의 원리에 따르면 기본적으로 아무것도 포함되지 않으며 UseEndpoints가 엔드포인트를 등록하는 위치입니다.

라우팅 통합 미들웨어 만들기

메타데이터 형식을 인터페이스로 정의하는 것이 좋습니다.

메타데이터 형식을 클래스 및 메서드의 특성으로 사용할 수 있게 하세요.

public interface ICoolMetadata
{
    bool IsCool { get; }
}

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CoolMetadataAttribute : Attribute, ICoolMetadata
{
    public bool IsCool => true;
}

컨트롤러 및 Razor Pages와 같은 프레임워크는 형식 및 메서드에 메타데이터 특성을 적용하도록 지원합니다. 메타데이터 형식을 선언하는 경우:

  • 이 형식에 특성으로 액세스할 수 있습니다.
  • 사용자 대부분이 특성을 적용하는 데 익숙합니다.

메타데이터 형식을 인터페이스로 선언하면 또 하나의 유연성 계층이 추가됩니다.

  • 인터페이스는 구성할 수 있습니다.
  • 개발자가 여러 정책을 결합하여 고유한 형식을 선언할 수 있습니다.

다음 예제와 같이 메타데이터를 재정의할 수 있게 하세요.

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class SuppressCoolMetadataAttribute : Attribute, ICoolMetadata
{
    public bool IsCool => false;
}

[CoolMetadata]
public class MyController : Controller
{
    public void MyCool() { }

    [SuppressCoolMetadata]
    public void Uncool() { }
}

이러한 지침을 따르는 가장 좋은 방법은 마커 메타데이터를 정의하지 않는 것입니다.

  • 메타데이터 형식이 있는지만 확인하면 안 됩니다.
  • 메타데이터의 속성을 정의하고 속성을 확인합니다.

메타데이터 컬렉션은 순서 지정되며 우선 순위별로 재정의할 수 있습니다. 컨트롤러의 경우 작업 메서드의 메타데이터가 가장 구체적입니다.

라우팅이 있는지 관계없이 미들웨어를 유용하게 사용할 수 있게 하세요.

app.UseAuthorization(new AuthorizationPolicy() { ... });

// Your framework
app.MapMyFramework(...).RequireAuthorization();

이 지침의 예제에서는 UseAuthorization 미들웨어를 고려해 보겠습니다. 권한 부여 미들웨어를 사용하면 대체 정책을 전달할 수 있습니다. 대체 정책(지정된 경우)은 다음 모두에 적용됩니다.

  • 지정된 정책이 없는 엔드포인트
  • 엔드포인트와 일치하지 않는 요청

따라서 권한 부여 미들웨어는 라우팅 컨텍스트 외에서도 유용합니다. 권한 부여 미들웨어는 기존 미들웨어 프로그래밍에 사용할 수 있습니다.

디버그 진단

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

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

추가 리소스

라우팅은 들어오는 HTTP 요청을 일치시켜 앱의 실행 가능 엔드포인트로 디스패치하는 역할을 담당합니다. 엔드포인트는 앱의 실행 가능 요청 처리 코드 단위입니다. 엔드포인트는 앱에서 정의되고 앱 시작 시 구성됩니다. 엔드포인트 일치 프로세스는 요청의 URL에서 값을 추출하고 요청 처리를 위해 이 값을 제공할 수 있습니다. 또한 라우팅은 앱의 엔드포인트 정보를 사용하여 엔드포인트에 매핑되는 URL을 생성할 수도 있습니다.

앱은 다음을 사용하여 라우팅을 구성할 수 있습니다.

  • 컨트롤러
  • Razor Pages
  • SignalR
  • gRPC 서비스
  • 상태 검사와 같은 엔드포인트 지원 미들웨어
  • 라우팅에 등록된 대리자 및 람다

이 문서에서는 ASP.NET Core 라우팅의 하위 수준 세부 정보를 설명합니다. 라우팅을 구성하는 방법은 다음을 참조하세요.

라우팅 기본 사항

다음 코드에서는 라우팅의 기본 예제를 보여 줍니다.

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

앞의 예제에는 메서드를 사용하는 단일 엔드포인트가 포함되어 있습니다 MapGet .

  • HTTP GET 요청이 루트 URL /로 전송되는 경우:
    • 요청 대리자가 실행됩니다.
    • Hello World!가 HTTP 응답에 기록됩니다.
  • 요청 메서드가 GET이 아니거나 루트 URL이 /가 아니면 일치하는 경로가 없고 HTTP 404가 반환됩니다.

라우팅은 UseRoutingUseEndpoints를 통해 등록된 미들웨어 쌍을 사용합니다.

  • UseRouting은 경로 일치를 미들웨어 파이프라인에 추가합니다. 이 미들웨어는 앱에 정의된 엔드포인트 집합을 확인하고 요청을 기반으로 가장 일치하는 항목을 선택합니다.
  • UseEndpoints는 엔드포인트 실행을 미들웨어 파이프라인에 추가합니다. 선택한 엔드포인트와 연결된 대리자를 실행합니다.

앱은 일반적으로 UseRouting 또는 UseEndpoints를 호출할 필요가 없습니다. WebApplicationBuilderUseRoutingUseEndpoints를 통해 Program.cs에 추가된 미들웨어를 래핑하는 미들웨어 파이프라인을 구성합니다. 그러나 앱은 이러한 메서드를 명시적으로 호출하여 UseRoutingUseEndpoints 실행 순서를 변경할 수 있습니다. 예를 들어 다음 코드는 UseRouting을 명시적으로 호출합니다.

app.Use(async (context, next) =>
{
    // ...
    await next(context);
});

app.UseRouting();

app.MapGet("/", () => "Hello World!");

위의 코드에서

  • app.Use에 대한 호출은 파이프라인의 시작 부분에서 실행되는 사용자 지정 미들웨어를 등록합니다.
  • UseRouting에 대한 호출은 사용자 지정 미들웨어 이후에 실행할 경로 일치 미들웨어를 구성합니다.
  • MapGet에 등록된 엔드포인트는 파이프라인의 끝부분에서 실행됩니다.

앞의 예제에 UseRouting에 대한 호출이 포함되지 않았다면 사용자 지정 미들웨어는 경로 일치 미들웨어 이후에 실행됩니다.

엔드포인트

MapGet 메서드는 엔드포인트을 정의하는 데 사용됩니다. 엔드포인트는 다음과 같을 수 있습니다.

  • URL 및 HTTP 메서드를 일치시켜 선택됩니다.
  • 대리자를 실행하여 실행됩니다.

앱에서 일치시키고 실행할 수 있는 엔드포인트는 UseEndpoints에서 구성됩니다. 예를 들어 MapGet, MapPost이들과 유사한 메서드는 요청 대리자를 라우팅 시스템에 연결합니다. ASP.NET Core Framework 기능을 라우팅 시스템에 연결하는 데 다음과 같은 추가 메서드를 사용할 수 있습니다.

다음 예제에서는 더 복잡한 경로 템플릿을 사용한 라우팅을 보여 줍니다.

app.MapGet("/hello/{name:alpha}", (string name) => $"Hello {name}!");

/hello/{name:alpha} 문자열은 경로 템플릿이며 경로 템플릿은 엔드포인트가 일치되는 방식을 구성하는 데 사용됩니다. 이 경우 템플릿은 다음과 일치합니다.

  • /hello/Docs과 같은 URL
  • /hello/로 시작하고 그 다음에 시퀀스 영문자가 오는 URL 경로. :alpha는 영문자와만 일치하는 경로 제약 조건을 적용합니다. 경로 제약 조건은 이 문서의 뒷부분에 설명되어 있습니다.

URL 경로의 두 번째 세그먼트 {name:alpha}는 다음과 같습니다.

다음 예제에서는 상태 검사 및 권한 부여를 사용한 라우팅을 보여 줍니다.

app.UseAuthentication();
app.UseAuthorization();

app.MapHealthChecks("/healthz").RequireAuthorization();
app.MapGet("/", () => "Hello World!");

앞의 예제에서는 다음을 수행하는 방법을 보여 줍니다.

  • 권한 부여 미들웨어를 라우팅에 사용할 수 있습니다.
  • 엔드포인트를 사용하여 권한 부여 동작을 구성할 수 있습니다.

MapHealthChecks 호출은 상태 검사 엔드포인트를 추가합니다. 이 호출에 RequireAuthorization을 연결하면 권한 부여 정책이 엔드포인트에 연결됩니다.

UseAuthenticationUseAuthorization을 호출하면 인증 및 권한 부여 미들웨어가 추가됩니다. 이러한 미들웨어는 UseRoutingUseEndpoints 사이에 배치되므로 다음을 수행할 수 있습니다.

  • UseRouting에서 선택된 엔드포인트를 확인합니다.
  • 엔드포인트로 UseEndpoints가 디스패치되기 전에 권한 부여 정책을 적용합니다.

엔드포인트 메타데이터

앞의 예제에는 두 개의 엔드포인트가 있지만 상태 검사 엔드포인트에만 권한 부여 정책이 연결되어 있습니다. 요청이 상태 검사 엔드포인트 /healthz와 일치하는 경우 권한 부여 확인이 수행됩니다. 이는 엔드포인트에 추가 데이터가 연결될 수 있음을 보여 줍니다. 이러한 추가 데이터를 엔드포인트 메타데이터라고 합니다.

  • 이 메타데이터는 라우팅 인식 미들웨어에서 처리될 수 있으며
  • 모든 .NET 형식일 수 있습니다.

라우팅 개념

라우팅 시스템은 강력한 엔드포인트 개념을 추가하여 미들웨어 파이프라인을 기반으로 빌드됩니다. 엔드포인트는 라우팅, 권한 부여 및 ASP.NET Core 시스템 수의 측면에서 서로 다른 앱의 기능 단위를 나타냅니다.

ASP.NET Core 엔드포인트 정의

ASP.NET Core 엔드포인트는 다음과 같습니다.

다음 코드에서는 현재 요청과 일치하는 엔드포인트를 검색하고 검사하는 방법을 보여 줍니다.

app.Use(async (context, next) =>
{
    var currentEndpoint = context.GetEndpoint();

    if (currentEndpoint is null)
    {
        await next(context);
        return;
    }

    Console.WriteLine($"Endpoint: {currentEndpoint.DisplayName}");

    if (currentEndpoint is RouteEndpoint routeEndpoint)
    {
        Console.WriteLine($"  - Route Pattern: {routeEndpoint.RoutePattern}");
    }

    foreach (var endpointMetadata in currentEndpoint.Metadata)
    {
        Console.WriteLine($"  - Metadata: {endpointMetadata}");
    }

    await next(context);
});

app.MapGet("/", () => "Inspect Endpoint.");

엔드포인트는 선택된 경우 HttpContext에서 검색할 수 있습니다. 해당 속성을 검사할 수 있습니다. 엔드포인트 개체는 변경할 수 없으며 만든 후 수정할 수 없습니다. 가장 일반적인 형식의 엔드포인트는 RouteEndpoint입니다. RouteEndpoint에는 라우팅 시스템에서 선택할 수 있는 정보가 포함됩니다.

앞의 코드에서 app.Use는 인라인 미들웨어를 구성합니다.

다음 코드에서는 파이프라인에서 app.Use가 호출되는 위치에 따라 엔드포인트가 없을 수 있음을 보여 줍니다.

// Location 1: before routing runs, endpoint is always null here.
app.Use(async (context, next) =>
{
    Console.WriteLine($"1. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
    await next(context);
});

app.UseRouting();

// Location 2: after routing runs, endpoint will be non-null if routing found a match.
app.Use(async (context, next) =>
{
    Console.WriteLine($"2. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
    await next(context);
});

// Location 3: runs when this endpoint matches
app.MapGet("/", (HttpContext context) =>
{
    Console.WriteLine($"3. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
    return "Hello World!";
}).WithDisplayName("Hello");

app.UseEndpoints(_ => { });

// Location 4: runs after UseEndpoints - will only run if there was no match.
app.Use(async (context, next) =>
{
    Console.WriteLine($"4. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
    await next(context);
});

위의 샘플은 엔드포인트가 선택되었는지 아닌지를 표시하는 Console.WriteLine 문을 추가합니다. 명확하게 하도록 이 샘플에서는 제공된 / 엔드포인트에 표시 이름을 할당합니다.

위의 샘플에는 파이프라인 내에서 이러한 미들웨어가 실행되는 시기를 정확하게 제어하기 위한 UseRoutingUseEndpoints 호출도 포함되어 있습니다.

/의 URL을 사용하여 이 코드를 실행하면 다음이 표시됩니다.

1. Endpoint: (null)
2. Endpoint: Hello
3. Endpoint: Hello

다른 URL을 사용하여 이 코드를 실행하면 다음이 표시됩니다.

1. Endpoint: (null)
2. Endpoint: (null)
4. Endpoint: (null)

이 출력은 다음을 나타냅니다.

  • UseRouting을 호출하기 전에 엔드포인트는 항상 null입니다.
  • 일치 항목이 발견되면 UseRoutingUseEndpoints사이에서 엔드포인트가 null이 아닙니다.
  • 일치 항목이 발견되면 UseEndpoints 미들웨어가 터미널입니다. 터미널 미들웨어는 이 문서의 뒷부분에 정의되어 있습니다.
  • UseEndpoints 뒤의 미들웨어는 일치 항목이 없는 경우에만 실행됩니다.

미들웨어는 UseRouting 이 메서드를 SetEndpoint 사용하여 엔드포인트를 현재 컨텍스트에 연결합니다. UseRouting 미들웨어를 사용자 지정 논리로 바꾸어도 엔드포인트를 사용하는 이점을 얻을 수 있습니다. 엔드포인트는 미들웨어와 같은 하위 수준 기본 형식이며 라우팅 구현에 결합되지 않습니다. 대부분의 앱에서는 UseRouting을 사용자 지정 논리로 바꿀 필요가 없습니다.

UseEndpoints 미들웨어는 UseRouting 미들웨어와 함께 사용하기 위한 것입니다. 엔드포인트를 실행하는 핵심 논리는 복잡하지 않습니다. GetEndpoint를 사용하여 엔드포인트를 검색한 다음 해당 RequestDelegate 속성을 호출하면 됩니다.

다음 코드에서는 미들웨어가 라우팅에 영향을 주거나 반응하는 방식을 보여 줍니다.

app.UseHttpMethodOverride();
app.UseRouting();

app.Use(async (context, next) =>
{
    if (context.GetEndpoint()?.Metadata.GetMetadata<RequiresAuditAttribute>() is not null)
    {
        Console.WriteLine($"ACCESS TO SENSITIVE DATA AT: {DateTime.UtcNow}");
    }

    await next(context);
});

app.MapGet("/", () => "Audit isn't required.");
app.MapGet("/sensitive", () => "Audit required for sensitive data.")
    .WithMetadata(new RequiresAuditAttribute());
public class RequiresAuditAttribute : Attribute { }

앞의 예제에서는 다음과 같은 두 가지 중요한 개념을 보여 줍니다.

  • 미들웨어는 UseRouting 전에 실행되어 라우팅 작동의 기반이 되는 데이터를 수정할 수 있습니다.
  • 미들웨어는 UseRoutingUseEndpoints 사이에서 실행되어 엔드포인트가 실행되기 전에 라우팅의 결과를 처리할 수 있습니다.
    • 다음 사이에 UseRouting 실행되는 미들웨어:UseEndpoints
      • 일반적으로 메타데이터를 검사하여 엔드포인트를 이해합니다.
      • UseAuthorizationUseCors에서 하는 것처럼 보안 결정을 내리는 경우가 많습니다.
    • 미들웨어와 메타데이터를 조합하면 엔드포인트별로 정책을 구성할 수 있습니다.

위의 코드에서는 엔드포인트별 정책을 지원하는 사용자 지정 미들웨어의 예를 보여 줍니다. 이 미들웨어는 중요한 데이터에 대한 액세스의 ‘감사 로그’를 콘솔에 기록합니다. RequiresAuditAttribute 메타데이터를 사용하여 엔드포인트를 ‘감사’하도록 미들웨어를 구성할 수 있습니다. 이 샘플에서는 중요함으로 표시된 엔드포인트만 감사되는 ‘옵트인 패턴’을 보여 줍니다. 예를 들어 이 논리를 역으로 정의하여 안전한 것으로 표시되지 않은 모든 항목을 감사할 수 있습니다. 엔드포인트 메타데이터 시스템은 유연합니다. 이 논리는 사용 사례에 적합한 방식으로 설계할 수 있습니다.

앞의 샘플 코드는 엔드포인트의 기본 개념을 보여 주기 위한 것입니다. 프로덕션 용도로는 사용하지 않아야 합니다. ‘감사 로그’ 미들웨어의 전체 버전은 다음과 같습니다.

  • 파일이나 데이터베이스에 기록합니다.
  • 사용자, IP 주소, 중요한 엔드포인트의 이름 등과 같은 세부 정보를 포함합니다.

감사 정책 메타데이터 RequiresAuditAttribute는 컨트롤러 및 SignalR 같은 클래스 기반 프레임워크에서 더욱 쉽게 사용할 수 있도록 Attribute로 정의됩니다. ‘라우팅 대상 코드’를 사용하면 다음과 같이.

  • 메타데이터가 작성기 API와 연결됩니다.
  • 엔드포인트를 만들 때 해당 메서드 및 클래스의 모든 특성이 클래스 기반 프레임워크에 포함됩니다.

메타데이터 형식은 인터페이스나 특성으로 정의하는 것이 가장 좋습니다. 인터페이스 및 특성을 사용하면 코드를 다시 사용할 수 있습니다. 메타데이터 시스템은 유연하며 제한을 적용하지 않습니다.

터미널 미들웨어와 라우팅 비교

다음 예제에서는 터미널 미들웨어와 라우팅을 모두 보여줍니다.

// Approach 1: Terminal Middleware.
app.Use(async (context, next) =>
{
    if (context.Request.Path == "/")
    {
        await context.Response.WriteAsync("Terminal Middleware.");
        return;
    }

    await next(context);
});

app.UseRouting();

// Approach 2: Routing.
app.MapGet("/Routing", () => "Routing.");

Approach 1:로 표시된 미들웨어 스타일은 터미널 미들웨어입니다. 일치 작업을 수행하기 때문에 터미널 미들웨어라고 합니다.

  • 위 샘플의 일치 작업은 미들웨어의 Path == "/"와 라우팅의 Path == "/Routing"입니다.
  • 일치가 성공하면 일부 기능을 실행하고 next 미들웨어를 호출하는 대신 반환합니다.

검색을 종료하고 일부 기능을 실행한 다음 반환하기 때문에 터미널 미들웨어라고 합니다.

다음 목록에서는 터미널 미들웨어와 라우팅을 비교합니다.

  • 두 방법 모두 처리 파이프라인을 종료할 수 있습니다.
    • 미들웨어는 next를 호출하는 대신 반환하여 파이프라인을 종료합니다.
    • 엔드포인트는 항상 터미널입니다.
  • 터미널 미들웨어를 사용하면 파이프라인의 임의 위치에 미들웨어를 배치할 수 있습니다.
    • 엔드포인트는 UseEndpoints의 위치에서 실행됩니다.
  • 터미널 미들웨어를 사용하면 임의의 코드에서 미들웨어가 일치하는 시기를 확인할 수 있습니다.
    • 사용자 지정 경로 일치 코드는 길어져서 올바르게 작성하기 어려울 수 있습니다.
    • 라우팅은 일반적인 앱을 위한 간단한 솔루션을 제공합니다. 앱 대부분에는 사용자 지정 경로 일치 코드가 필요하지 않습니다.
  • 엔드포인트는 UseAuthorizationUseCors 같은 미들웨어와 상호 작용합니다.
    • UseAuthorization 또는 UseCors와 함께 터미널 미들웨어를 사용하려면 권한 부여 시스템을 수동으로 조작해야 합니다.

엔드포인트는 다음 두 가지를 모두 정의합니다.

  • 요청을 처리할 대리자
  • 임의 메타데이터의 컬렉션. 메타데이터는 각 엔드포인트에 연결된 정책과 구성에 따라 횡단 관심사(Cross-Cutting Concerns)를 구현하는 데 사용됩니다.

터미널 미들웨어는 효과적인 도구이지만 다음이 필요할 수 있습니다.

  • 상당한 양의 코딩과 테스트
  • 원하는 수준의 유연성을 얻기 위한 다른 시스템과의 수동 통합

터미널 미들웨어를 작성하기 전에 라우팅과 통합하는 것이 좋습니다.

또는MapWhen과 통합되는 기존 터미널 미들웨어는 일반적으로 라우팅 인식 엔드포인트로 전환될 수 있습니다. MapHealthChecks는 다음과 같은 라우터 방식의 패턴을 보여 줍니다.

  • IEndpointRouteBuilder에 대한 확장 메서드를 작성합니다.
  • CreateApplicationBuilder를 사용하여 중첩된 미들웨어 파이프라인을 만듭니다.
  • 새 파이프라인에 미들웨어를 연결합니다. 이 경우, UseHealthChecks입니다.
  • 미들웨어 파이프라인을 RequestDelegateBuild합니다.
  • Map을 호출하고 새 미들웨어 파이프라인을 제공합니다.
  • 확장 메서드의 Map에서 제공하는 작성기 개체를 반환합니다.

다음 코드에서는 MapHealthChecks를 사용하는 방법을 보여 줍니다.

app.UseAuthentication();
app.UseAuthorization();

app.MapHealthChecks("/healthz").RequireAuthorization();

앞의 샘플에서는 작성기 개체를 반환하는 것이 중요한 이유를 보여 줍니다. 작성기 개체를 반환하면 앱 개발자가 엔드포인트의 권한 부여와 같은 정책을 구성할 수 있습니다. 이 예제에서는 상태 검사 미들웨어가 권한 부여 시스템과 직접 통합되지 않습니다.

메타데이터 시스템은 터미널 미들웨어를 사용하는 확장성 작성자에서 발생한 문제에 대응하여 만들어졌습니다. 미들웨어마다 권한 부여 시스템과의 고유한 통합을 구현하는 것은 문제가 있습니다.

URL 일치

  • 라우팅이 들어오는 요청을 엔드포인트와 일치시키는 프로세스입니다.
  • URL 경로 및 헤더의 데이터를 기반으로 합니다.
  • 요청의 모든 데이터를 고려하도록 확장될 수 있습니다.

라우팅 미들웨어가 실행되면 Endpoint를 설정하여 현재 요청에서 HttpContext요청 기능으로 값을 라우팅합니다.

  • HttpContext.GetEndpoint를 호출하면 엔드포인트를 가져옵니다.
  • HttpRequest.RouteValues는 경로 값의 컬렉션을 가져옵니다.

미들웨어 는 라우팅 미들웨어가 엔드포인트를 검사하고 작업을 수행할 수 있는 후에 실행됩니다. 예를 들어 권한 부여 미들웨어는 엔드포인트의 메타데이터 컬렉션에서 권한 부여 정책을 조사할 수 있습니다. 요청 처리 파이프라인의 미들웨어가 모두 실행된 후에 선택한 엔드포인트의 대리자가 호출됩니다.

엔드포인트 라우팅의 라우팅 시스템은 모든 디스패치를 결정합니다. 미들웨어는 선택된 엔드포인트에 기반으로 하여 정책을 적용하므로 다음이 중요합니다.

  • 디스패치나 보안 정책의 애플리케이션에 영향을 줄 수 있는 모든 결정은 라우팅 시스템 내에서 내려야 합니다.

Warning

이전 버전과의 호환성을 위해 컨트롤러 또는 Razor Pages 엔드포인트 대리자를 실행할 때 속성 RouteContext.RouteData 은 지금까지 수행된 요청 처리에 따라 적절한 값으로 설정됩니다.

RouteContext 형식은 이후 릴리스에서 obsolete로 표시됩니다.

  • RouteData.ValuesHttpRequest.RouteValues로 마이그레이션합니다.
  • 엔드포인트 메타데이터에서 검색 IDataTokensMetadata 하도록 마이그레이션 RouteData.DataTokens 합니다.

URL 일치는 구성 가능한 일련의 단계로 작동합니다. 각 단계의 출력은 일치 항목 집합입니다. 일치 항목 집합은 다음 단계에서 더욱 좁혀질 수 있습니다. 라우팅 구현에서는 일치하는 엔드포인트의 처리 순서를 보장하지 않습니다. 기능한 모든 일치 항목이 한 번에 처리됩니다. URL 일치 단계는 다음 순서로 수행됩니다. ASP.NET Core:

  1. 엔드포인트와 해당 경로 템플릿 집합에 대한 URL 경로를 처리하여 일치 항목을 모두 수집합니다.
  2. 앞의 목록을 사용하여 경로 제약 조건이 적용되지 않는 일치 항목을 제거합니다.
  3. 앞의 목록을 가져와 인스턴스 집합 MatcherPolicy 에 실패한 일치 항목을 제거합니다.
  4. EndpointSelector 값을 사용하여 이전 목록에서 최종 결정을 내립니다.

엔드포인트 목록의 우선 순위는 다음에 따라 지정됩니다.

EndpointSelector에 도달할 때까지 각 단계에서 일치하는 모든 엔드포인트가 처리됩니다. EndpointSelector는 최종 단계이며, 일치 항목에서 가장 높은 우선 순위 엔드포인트를 가장 일치하는 항목으로 선택합니다. 가장 일치하는 항목과 같은 우선 순위의 다른 일치 항목이 있으면 모호한 일치 예외가 throw됩니다.

경로 우선 순위는 더 구체적인 경로 템플릿에 높은 우선 순위가 지정되는 기준에 따라 컴퓨팅됩니다. 예를 들어 /hello/{message} 템플릿을 가정해 보겠습니다.

  • 둘 다 URL 경로 /hello와 일치합니다.
  • /hello가 더 구체적이므로 우선 순위가 높습니다.

일반적으로 경로 우선 순위는 실제로 사용되는 URL 체계에 가장 일치하는 항목을 선택하는 데 좋습니다. 모호성을 방지하는 데 필요한 경우에만 Order를 사용합니다.

라우팅에서 제공하는 확장성의 종류 때문에 라우팅 시스템이 모호한 경로를 미리 컴퓨팅할 수는 없습니다. 경로 템플릿 /{message:alpha}/{message:int}와 같은 예제를 살펴보겠습니다.

  • alpha 제약 조건은 영문자와만 일치합니다.
  • int 제약 조건은 숫자와만 일치합니다.
  • 이러한 템플릿은 경로 우선 순위가 동일하지만 둘 다와 일치하는 단일 URL은 없습니다.
  • 라우팅 시스템에서 시작 시 모호성 오류를 보고한 경우 이 유효한 사용 사례를 차단합니다.

Warning

UseEndpoints 내의 작업 순서는 라우팅 동작에 영향을 주지 않지만 한 가지 예외가 있습니다. MapControllerRouteMapAreaRoute는 호출된 순서를 기준으로 해당 엔드포인트에 순서 값을 자동으로 할당합니다. 이는 이전 라우팅 구현과 동일한 보장을 제공하는 라우팅 시스템이 없이 컨트롤러의 오래된 동작을 시뮬레이트합니다.

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

  • 경로 개념이 없습니다.
  • 순서 지정을 보장하지 않습니다. 모든 엔드포인트가 한 번에 처리됩니다.

경로 템플릿 우선 순위 및 엔드포인트 선택 영역 순서

경로 템플릿 우선 순위는 얼마나 구체적인지를 기준으로 각 경로 템플릿에 값을 할당하는 시스템입니다. 경로 템플릿 우선 순위의 특징은 다음과 같습니다.

  • 일반적인 사례에서 엔드포인트 순서를 조정할 필요가 없게 합니다.
  • 라우팅 동작에 관한 일반적인 기대에 맞추려고 합니다.

예를 들어 /Products/List/Products/{id} 템플릿을 가정해 보겠습니다. URL 경로 /Products/List에 대해 /Products/List/Products/{id}보다 더 잘 일치한다고 합리적으로 가정할 수 있습니다. 리터럴 세그먼트 /List가 매개 변수 세그먼트 /{id}보다 우선 순위가 더 높다고 간주되기 때문입니다.

우선 순위의 작동 방식에 대한 세부 정보는 경로 템플릿이 정의된 방법과 어느 정도 관련이 있습니다.

  • 세그먼트가 더 많은 템플릿은 더 구체적인 것으로 간주됩니다.
  • 리터럴 텍스트가 있는 세그먼트가 매개 변수 세그먼트보다 더 구체적인 것으로 간주됩니다.
  • 제약 조건이 있는 매개 변수 세그먼트가 제약 조건이 없는 매개 변수 세그먼트보다 더 구체적인 것으로 간주됩니다.
  • 복잡한 세그먼트는 제약 조건이 있는 매개 변수 세그먼트만큼 구체적인 것으로 간주됩니다.
  • Catch-all 매개 변수가 가장 덜 구체적입니다. Catch-all 경로에 관한 중요한 내용은 경로 템플릿 섹션에서 catch-all을 참조하세요.

URL 생성 개념

URL 생성은 다음과 같습니다.

  • 라우팅이 경로 값의 집합을 기반으로 하는 URL 경로를 만들 수 있는 프로세스입니다.
  • 엔드포인트와 이에 액세스하는 URL을 논리적으로 분리할 수 있습니다.

엔드포인트 라우팅에는 LinkGenerator API가 포함됩니다. LinkGeneratorDI에서 사용할 수 있는 싱글톤 서비스입니다. LinkGenerator API는 실행 중인 요청의 컨텍스트 외부에서 사용할 수 있습니다. Mvc.IUrlHelperIUrlHelper를 사용하는 시나리오(예: 태그 도우미, HTML 도우미 및 작업 결과)는 내부적으로 LinkGenerator API를 사용하여 링크 생성 기능을 제공합니다.

링크 생성기는 주소주소 체계의 개념으로 지원됩니다. 주소 체계는 링크 생성을 위해 고려해야 할 엔드포인트를 결정하는 방법입니다. 예를 들어 컨트롤러 및 Razor Pages에서 많은 사용자에게 친숙한 경로 이름 및 경로 값 시나리오는 주소 체계로 구현됩니다.

링크 생성기는 다음 확장 메서드를 통해 컨트롤러 및 Razor Pages에 연결할 수 있습니다.

이러한 메서드의 오버로드에는 HttpContext를 포함한 인수가 허용됩니다. 이러한 메서드는 기능적으로 Url.ActionUrl.Page와 동일하지만, 추가적인 유연성과 옵션을 제공합니다.

GetPath* 메서드는 절대 경로가 포함된 URI를 생성한다는 점에서 Url.ActionUrl.Page와 가장 비슷합니다. GetUri* 메서드는 항상 체계와 호스트를 포함한 절대 URI를 생성합니다. HttpContext를 허용하는 메서드는 실행 중인 요청의 컨텍스트에서 URI를 생성합니다. 재정의되지 않는 한 실행 중인 요청의 앰비언트 경로 값, URL 기본 경로, 체계 및 호스트가 사용됩니다.

LinkGenerator는 주소를 사용하여 호출됩니다. URI 생성은 다음 두 단계로 수행됩니다.

  1. 주소는 해당 주소와 일치하는 엔드포인트 목록에 바인딩됩니다.
  2. 제공된 값과 일치하는 경로 패턴을 찾을 때까지 각 엔드포인트의 RoutePattern이 평가됩니다. 결과 출력은 링크 생성기에 제공된 다른 URI 부분과 결합되어 반환됩니다.

LinkGenerator에서 제공하는 메서드는 모든 유형의 주소에 대해 표준 링크 생성 기능을 지원합니다. 링크 생성기를 사용하는 가장 편리한 방법은 특정 주소 유형에 대한 작업을 수행하는 확장 메서드를 사용하는 것입니다.

확장 메서드 설명
GetPathByAddress 제공된 값에 기반한 절대 경로가 있는 URI를 생성합니다.
GetUriByAddress 제공된 값에 기반한 절대 URI를 생성합니다.

Warning

LinkGenerator 메서드 호출 시 다음과 같은 의미에 주의하세요.

  • 들어오는 요청의 GetUri* 헤더의 유효성을 검사하지 않는 앱 구성에서는 Host 확장 메서드를 신중하게 사용하세요. 들어오는 요청의 Host 헤더의 유효성을 검사하지 않으면 신뢰할 수 없는 요청 입력이 보기 또는 페이지에 포함된 URI로 클라이언트에 다시 보내질 수 있습니다. 모든 프로덕션 앱은 알려진 유효한 값에 대해 Host 헤더의 유효성을 검사하도록 서버를 구성하는 것이 좋습니다.

  • 미들웨어에서 MapWhen 또는 LinkGenerator과 함께 Map를 사용할 때는 신중하게 사용하세요. Map*는 실행 중인 요청의 기본 경로를 변경하여 링크 생성의 출력에 영향을 줍니다. 모든 LinkGenerator API는 기본 경로를 지정할 수 있습니다. 링크 생성에 대한 Map*의 영향을 실행 취소하려면 빈 기본 경로를 지정합니다.

미들웨어 예제

다음 예제에서는 미들웨어에서 LinkGenerator API를 사용하여 상점 제품을 나열하는 작업 메서드에 대한 링크를 만듭니다. 링크 생성기를 클래스에 주입하고 GenerateLink를 호출하여 앱의 모든 클래스에서 해당 링크 생성기를 사용할 수 있습니다.

public class ProductsMiddleware
{
    private readonly LinkGenerator _linkGenerator;

    public ProductsMiddleware(RequestDelegate next, LinkGenerator linkGenerator) =>
        _linkGenerator = linkGenerator;

    public async Task InvokeAsync(HttpContext httpContext)
    {
        httpContext.Response.ContentType = MediaTypeNames.Text.Plain;

        var productsPath = _linkGenerator.GetPathByAction("Products", "Store");

        await httpContext.Response.WriteAsync(
            $"Go to {productsPath} to see our products.");
    }
}

경로 템플릿

{} 내의 토큰은 경로가 일치하는 경우 바인딩될 경로 매개 변수를 정의합니다. 경로 세그먼트에 둘 이상의 경로 매개 변수를 정의할 수 있지만 경로 매개 변수를 리터럴 값으로 분리해야 합니다. 예시:

{controller=Home}{action=Index}

는 리터럴 값이 {controller}{action}없기 때문에 유효한 경로가 아닙니다. 경로 매개 변수는 이름이 있어야 하며 지정된 추가 특성을 가질 수 있습니다.

경로 매개 변수 이외의 리터럴 텍스트(예: {id}) 및 경로 구분 기호(/)는 URL의 텍스트와 일치해야 합니다. 텍스트 일치는 대/소문자를 구분하지 않으며 URL 경로의 디코딩된 표현을 기반으로 합니다. 리터럴 경로 매개 변수 구분 기호 { 또는 }와 일치시키려면 문자를 반복하여(예: {{ 또는 }}) 구분 기호를 이스케이프합니다.

별표 * 또는 이중 별표 **:

  • URI의 나머지 부분에 바인딩하기 위해 경로 매개 변수의 접두사로 사용할 수 있습니다.
  • 범용 매개 변수라고 합니다. 예를 들면 다음과 같습니다. blog/{**slug}
    • blog/로 시작하고 그 다음에 임의의 값이 오는 모든 URI를 찾습니다.
    • blog/ 다음의 값은 동적 필드 경로 값에 할당됩니다.

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.

범용 매개 변수는 빈 문자열과 일치시킬 수도 있습니다.

범용 매개 변수는 경로 구분 기호 / 문자를 포함하여 URL을 생성하는 데 경로가 사용될 때 적절한 문자를 이스케이프합니다. 예를 들어 경로 값이 { path = "my/path" }인 경로 foo/{*path}foo/my%2Fpath를 생성합니다. 이스케이프된 슬래시에 주의하세요. 경로 구분 기호 문자를 왕복하려면 ** 경로 매개 변수 접두사를 사용합니다. { path = "my/path" }가 있는 경로 foo/{**path}foo/my/path를 생성합니다.

선택적 파일 확장명이 있는 파일 이름을 캡처하려고 시도하는 URL 패턴에는 추가 고려 사항이 있습니다. 예를 들어 템플릿 files/{filename}.{ext?}를 가정해 보겠습니다. filenameext 모두에 대한 값이 있으면 두 값이 채워집니다. URL에 filename에 대한 값만 있으면 후행 .가 선택 사항이므로 경로가 일치합니다. 다음 URL은 이 경로와 일치합니다.

  • /files/myFile.txt
  • /files/myFile

경로 매개 변수에는 등호(=)로 구분된 매개 변수 이름 뒤에 기본값을 지정하여 지정된 기본값이 있을 수 있습니다. 예를 들어 {controller=Home}controller에 대한 기본값으로 Home을 정의합니다. URL에 매개 변수에 대한 값이 없는 경우 기본값이 사용됩니다. 경로 매개 변수는 매개 변수 이름의 끝에 물음표(?)를 추가하면 선택적이 됩니다. 예들 들어 id?입니다. 선택적 값과 기본 경로 매개 변수의 차이는 다음과 같습니다.

  • 기본값이 있는 경로 매개 변수는 항상 값을 생성합니다.
  • 선택적 매개 변수는 요청 URL에서 값을 제공한 경우에만 값이 있습니다.

경로 매개 변수에는 URL에서 바인딩된 경로 값과 일치해야 한다는 제약 조건이 있을 수 있습니다. 경로 매개 변수 이름 뒤에 :과 제약 조건 이름을 추가하여 경로 매개 변수에서 인라인 제약 조건을 지정합니다. 제약 조건에 인수가 필요한 경우 제약 조건 이름 뒤에서 괄호 (...)로 묶입니다. 또 다른 : 및 제약 조건 이름을 추가하여 여러 ‘인라인 제약 조건’을 지정할 수 있습니다.

제약 조건 이름 및 인수는 IRouteConstraint의 인스턴스를 만드는 IInlineConstraintResolver 서비스로 전달되어 URL 처리에서 사용합니다. 예를 들어 경로 템플릿 blog/{article:minlength(10)}는 인수 10으로 minlength 제약 조건을 지정합니다. 경로 제약 조건 및 프레임워크에서 제공하는 제약 조건 목록에 대한 자세한 내용은 경로 제약 조건 섹션을 참조하세요.

경로 매개 변수에는 매개 변수 변환기가 있을 수도 있습니다. 매개 변수 변환기는 링크를 생성하고 URL에 대한 작업 및 페이지와 일치할 때 매개 변수 값을 변환합니다. 제약 조건과 마찬가지로, 매개 변수 변환기는 경로 매개 변수 이름 뒤에 :과 변환기 이름을 추가하여 경로 매개 변수에 인라인으로 추가될 수 있습니다. 예를 들어 경로 템플릿 blog/{article:slugify}slugify 변환기를 지정합니다. 매개 변수 변환기에 대한 자세한 내용은 매개 변수 변환기 섹션을 참조하세요.

다음 표에서는 경로 템플릿 예제 및 해당 동작을 보여 줍니다.

경로 템플릿 URI 일치 예제 요청 URI…
hello /hello /hello 단일 경로만 일치합니다.
{Page=Home} / 일치하고, PageHome으로 설정합니다.
{Page=Home} /Contact 일치하고, PageContact으로 설정합니다.
{controller}/{action}/{id?} /Products/List Products 컨트롤러 및 List 작업에 매핑합니다.
{controller}/{action}/{id?} /Products/Details/123 Products 컨트롤러 및 Details 작업에 매핑합니다(id가 123으로 설정됨).
{controller=Home}/{action=Index}/{id?} / Home 컨트롤러 및 Index 메서드에 매핑합니다. id는 무시됩니다.
{controller=Home}/{action=Index}/{id?} /Products Products 컨트롤러 및 Index 메서드에 매핑합니다. id는 무시됩니다.

템플릿 사용은 일반적으로 라우팅에 대한 가장 간단한 방식입니다. 제약 조건 및 기본값을 경로 템플릿 외부에서 지정할 수도 있습니다.

복잡한 세그먼트

복잡한 세그먼트는 non-greedy 방식으로 오른쪽에서 왼쪽으로 리터럴 구분 기호를 매칭하여 처리됩니다. 예를 들어 [Route("/a{b}c{d}")]는 복잡한 세그먼트입니다. 복잡한 세그먼트는 특정 방식으로 작동하므로 제대로 사용하려면 이 방식을 이해해야 합니다. 이 단원의 예제에서는 매개 변수 내에 구분 기호 텍스트가 표시되지 않는 경우에만 복잡한 세그먼트가 실제로 잘 작동하는 이유를 보여 줍니다. 더욱 복잡한 경우에는 regex를 사용한 다음 값을 수동으로 추출해야 합니다.

Warning

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

다음은 라우팅에서 /a{b}c{d} 템플릿 및 URL 경로 /abcd를 사용하여 수행하는 단계를 요약한 것입니다. |는 알고리즘의 작동 방식을 시각화하는 데 사용됩니다.

  • 오른쪽에서 왼쪽으로 첫 번째 리터럴은 c입니다. 따라서 /abcd가 오른쪽에서 검색되고 /ab|c|d를 찾습니다.
  • 이제 오른쪽의 모든 항목(d)이 경로 매개 변수 {d}와 일치합니다.
  • 오른쪽에서 왼쪽으로 다음 리터럴은 a입니다. 따라서 /ab|c|d가 중단된 위치부터 검색되고 a/|a|b|c|d와 함께 찾습니다.
  • 이제 오른쪽 값(b)이 경로 매개 변수 {b}와 일치합니다.
  • 남은 텍스트가 없고 남은 경로 템플릿도 없으므로 이것이 일치 항목입니다.

다음은 동일한 템플릿 /a{b}c{d}와 URL 경로 /aabcd를 사용하는 부정적인 사례의 예제입니다. |는 알고리즘의 작동 방식을 시각화하는 데 사용됩니다. 이 경우는 동일한 알고리즘으로 설명되는 일치 항목이 아닙니다.

  • 오른쪽에서 왼쪽으로 첫 번째 리터럴은 c입니다. 따라서 /aabcd가 오른쪽에서 검색되고 /aab|c|d를 찾습니다.
  • 이제 오른쪽의 모든 항목(d)이 경로 매개 변수 {d}와 일치합니다.
  • 오른쪽에서 왼쪽으로 다음 리터럴은 a입니다. 따라서 /aab|c|d가 중단된 위치부터 검색되고 a/a|a|b|c|d와 함께 찾습니다.
  • 이제 오른쪽 값(b)이 경로 매개 변수 {b}와 일치합니다.
  • 이제 남은 텍스트 a가 있지만 알고리즘에서 구문 분석할 경로 템플릿이 부족하므로 이것은 일치 항목이 아닙니다.

일치 알고리즘이 non-greedy이므로 다음과 같습니다.

  • 각 단계에서 가능한 최소의 텍스트와 일치합니다.
  • 매개 변수 값 내에 구분 기호 값이 표시되는 모든 경우에는 일치하지 않게 됩니다.

정규식을 사용하면 일치 동작을 더 효율적으로 제어할 수 있습니다.

지연 일치라고도 하는 greedy 일치는 가능한 가장 큰 문자열과 일치합니다. Non-greedy는 가능한 가장 작은 문자열과 일치합니다.

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

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

[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 경로가 경로 값으로 토큰화되면 실행됩니다. 일반적으로 경로 제약 조건은 경로 템플릿을 통해 연결된 경로 값을 검사하고 값 허용 여부에 대한 true 또는 false 결정을 내립니다. 일부 경로 제약 조건은 경로 값 외부의 데이터를 사용하여 요청을 라우팅할 수 있는지 여부를 고려합니다. 예를 들어 HttpMethodRouteConstraint는 해당 HTTP 동사에 따라 요청을 허용하거나 거부할 수 있습니다. 제약 조건은 라우팅 요청 및 링크 생성에 사용됩니다.

Warning

제약 조건을 입력 유효성 검사에 사용하지 마세요. 입력 유효성 검사에 제약 조건을 사용하면 잘못된 입력으로 인해 404 찾을 수 없음 응답이 반환됩니다. 입력이 잘못되면 400 잘못된 요청과 해당하는 오류 메시지가 생성됩니다. 경로 제약 조건은 특정 경로에 대한 입력의 유효성을 검사하는 것이 아니라 비슷한 경로를 명확하게 구분하는 데 사용됩니다.

다음 표에서는 경로 제약 조건 예제 및 예상되는 해당 동작을 보여 줍니다.

제약 조건 예제 일치하는 예제 주의
int {id:int} 123456789, -123456789 임의의 정수와 일치
bool {active:bool} true, FALSE true 또는 false와 일치. 대/소문자 구분 안 함
datetime {dob:datetime} 2016-12-31, 2016-12-31 7:32pm 유효한 DateTime 값 일치(고정 문화권에서) 이전 경고를 참조하세요.
decimal {price:decimal} 49.99, -1,000.01 유효한 decimal 값 일치(고정 문화권에서) 이전 경고를 참조하세요.
double {weight:double} 1.234, -1,001.01e8 유효한 double 값 일치(고정 문화권에서) 이전 경고를 참조하세요.
float {weight:float} 1.234, -1,001.01e8 유효한 float 값 일치(고정 문화권에서) 이전 경고를 참조하세요.
guid {id:guid} CD2C1638-1638-72D5-1638-DEADBEEF1638 유효한 Guid 값 일치
long {ticks:long} 123456789, -123456789 유효한 long 값 일치
minlength(value) {username:minlength(4)} Rick 문자열은 4자 이상이어야 합니다.
maxlength(value) {filename:maxlength(8)} MyFile 문자열은 8자 이하여야 합니다.
length(length) {filename:length(12)} somefile.txt 문자열은 정확히 12자여야 합니다.
length(min,max) {filename:length(8,16)} somefile.txt 문자열의 길이는 8자 이상이며 16자 이하여야 합니다.
min(value) {age:min(18)} 19 정수 값은 18 이상이어야 합니다.
max(value) {age:max(120)} 91 정수 값은 120 이하여야 합니다.
range(min,max) {age:range(18,120)} 91 정수 값은 18 이상이며 120 이하여야 합니다.
alpha {name:alpha} Rick 문자열은 하나 이상의 영문자(a-z, 대/소문자 구분)로 구성되어야 합니다.
regex(expression) {ssn:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)} 123-45-6789 문자열은 정규식과 일치해야 합니다. 정규식을 정의하는 방법에 대한 팁을 참조하세요.
required {name:required} Rick URL을 생성하는 동안 비-매개 변수 값이 존재하도록 강제하는 데 사용됨

Warning

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

콜론으로 구분된 여러 개의 제약 조건을 단일 매개 변수에 적용할 수 있습니다. 예를 들어 다음 제약 조건은 매개 변수를 1 이상의 정수 값으로 제한합니다.

[Route("users/{id:int:min(1)}")]
public User GetUserById(int id) { }

Warning

CLR 형식으로 변환되는 URL을 확인하는 경로 제약 조건은 항상 고정 문화권을 사용합니다(예: CLR 형식 int 또는 DateTime으로 변환). 이러한 제약 조건은 URL은 지역화될 수 없다고 가정합니다. 프레임워크에서 제공한 경로 제약 조건은 경로 값에 저장된 값을 수정하지 않습니다. URL에서 구문 분석되는 모든 경로 값은 문자열로 저장됩니다. 예를 들어 float 제약 조건은 경로 값을 부동으로 변환하려고 하지만 변환된 값은 부동으로 변환될 수 있는지 확인하는 데만 사용됩니다.

제약 조건의 정규식

Warning

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

regex(...) 경로 제약 조건을 사용하여 정규식을 인라인 제약 조건으로 지정할 수 있습니다. MapControllerRoute 제품군의 메서드에서는 개체 리터럴의 제약 조건도 사용할 수 있습니다. 해당 양식을 사용하는 경우 문자열 값이 정규식으로 해석됩니다.

다음 코드에서는 인라인 regex 제약 조건을 사용합니다.

app.MapGet("{message:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)}",
    () => "Inline Regex Constraint Matched");

다음 코드에서는 개체 리터럴을 사용하여 regex 제약 조건을 지정합니다.

app.MapControllerRoute(
    name: "people",
    pattern: "people/{ssn}",
    constraints: new { ssn = "^\\d{3}-\\d{2}-\\d{4}$", },
    defaults: new { controller = "People", action = "List" });

ASP.NET Core 프레임워크는 정규식 생성자에 RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariant를 추가합니다. 이러한 멤버에 대한 설명은 RegexOptions를 참조하세요.

정규식은 라우팅 및 C# 언어에서 사용하는 것과 유사한 구분 기호 및 토큰을 사용합니다. 정규식 토큰은 이스케이프되어야 합니다. 인라인 제약 조건에서 정규식 ^\d{3}-\d{2}-\d{4}$를 사용하려면 다음 중 하나를 사용합니다.

  • \ 문자열 이스케이프 문자를 이스케이프하기 위해 C# 소스 파일에서 문자열에 제공된 \ 문자를 \\ 문자로 바꿉니다.
  • 축자 문자열 리터럴.

라우팅 매개 변수 구분 기호 문자({, }, [, ])를 이스케이프하려면 식에서 해당 문자를 이중으로 사용합니다(예: {{, }}, [[, ]]). 다음 표에서는 정규식 및 이스케이프된 버전을 보여 줍니다.

정규식 이스케이프된 정규식
^\d{3}-\d{2}-\d{4}$ ^\\d{{3}}-\\d{{2}}-\\d{{4}}$
^[a-z]{2}$ ^[[a-z]]{{2}}$

라우팅에 사용되는 정규식은 ^ 문자로 시작하고 문자열의 시작 위치와 일치하는 경우가 많습니다. 식은 $ 문자로 끝나고 문자열의 끝과 일치하는 경우가 많습니다. ^$ 문자는 정규식이 전체 경로 매개 변수 값과 일치하도록 합니다. ^$ 문자가 없는 정규식은 문자열 내의 모든 하위 문자열과 일치하지만, 이는 종종 원하는 것이 아닙니다. 다음 표에서는 예제를 제공하고, 일치하거나 일치에 실패하는 이유를 설명합니다.

문자열 Match Comment(설명)
[a-z]{2} hello 부분 문자열 일치
[a-z]{2} 123abc456 부분 문자열 일치
[a-z]{2} mz 식 일치
[a-z]{2} MZ 대/소문자 구분하지 않음
^[a-z]{2}$ hello 아니요 위의 ^$ 참조
^[a-z]{2}$ 123abc456 아니요 위의 ^$ 참조

정규식 구문에 대한 자세한 내용은 .NET Framework 정규식을 참조하세요.

가능한 값의 알려진 집합으로 매개 변수를 제한하려면 정규식을 사용합니다. 예를 들어 {action:regex(^(list|get|create)$)}action 경로 값을 list, get 또는 create으로만 일치시킵니다. 제약 조건 사전으로 전달되면 ^(list|get|create)$ 문자열은 동일합니다. 알려진 제약 조건 중 하나와 일치하지 않는 제약 조건 사전에서 전달되는 제약 조건도 정규식으로 처리됩니다. 알려진 제약 조건 중 하나와 일치하지 않는 템플릿 내에서 전달되는 제약 조건은 정규식으로 처리되지 않습니다.

사용자 지정 경로 제약 조건

IRouteConstraint 인터페이스를 구현하여 사용자 지정 경로 제약 조건을 만들 수 있습니다. IRouteConstraint 인터페이스에는 제약 조건이 충족되는 경우 true를 반환하고 그렇지 않은 경우 false를 반환하는 Match가 포함됩니다.

사용자 지정 경로 제약 조건은 거의 필요하지 않습니다. 사용자 지정 경로 제약 조건을 구현하기 전에 모델 바인딩과 같은 다른 방식을 고려해 보세요.

ASP.NET Core Constraints 폴더는 제약 조건을 만드는 좋은 예제를 제공합니다. 예를 들어 GuidRouteConstraint입니다.

사용자 지정 IRouteConstraint를 사용하려면 서비스 컨테이너에 있는 앱의 ConstraintMap에 경로 제약 조건 형식을 등록해야 합니다. ConstraintMap은 경로 제약 조건 키를 해당 제약 조건의 유효성을 검사하는 IRouteConstraint 구현으로 매핑하는 사전입니다. Program.cs에서 AddRouting 호출의 일부로 또는 builder.Services.Configure<RouteOptions>를 사용하여 직접 RouteOptions를 구성하여 앱의 ConstraintMap을 업데이트할 수 있습니다. 예시:

builder.Services.AddRouting(options =>
    options.ConstraintMap.Add("noZeroes", typeof(NoZeroesRouteConstraint)));

위의 제약 조건은 다음 코드에서 적용됩니다.

[ApiController]
[Route("api/[controller]")]
public class NoZeroesController : ControllerBase
{
    [HttpGet("{id:noZeroes}")]
    public IActionResult Get(string id) =>
        Content(id);
}

NoZeroesRouteConstraint를 구현하면 경로 매개 변수에 0이 사용되지 않습니다.

public class NoZeroesRouteConstraint : IRouteConstraint
{
    private static readonly Regex _regex = new(
        @"^[1-9]*$",
        RegexOptions.CultureInvariant | RegexOptions.IgnoreCase,
        TimeSpan.FromMilliseconds(100));

    public bool Match(
        HttpContext? httpContext, IRouter? route, string routeKey,
        RouteValueDictionary values, RouteDirection routeDirection)
    {
        if (!values.TryGetValue(routeKey, out var routeValue))
        {
            return false;
        }

        var routeValueString = Convert.ToString(routeValue, CultureInfo.InvariantCulture);

        if (routeValueString is null)
        {
            return false;
        }

        return _regex.IsMatch(routeValueString);
    }
}

Warning

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

앞의 코드가 하는 역할은 다음과 같습니다.

  • 경로의 {id} 세그먼트에 0을 사용하지 못하게 합니다.
  • 사용자 지정 제약 조건을 구현하는 기본 예제를 제공하기 위해 표시됩니다. 프로덕션 앱에 사용해서는 안 됩니다.

다음 코드는 0이 포함된 id가 처리되지 않게 하는 더 나은 방법입니다.

[HttpGet("{id}")]
public IActionResult Get(string id)
{
    if (id.Contains('0'))
    {
        return StatusCode(StatusCodes.Status406NotAcceptable);
    }

    return Content(id);
}

위의 코드는 NoZeroesRouteConstraint 접근 방식과 비교하면 다음과 같은 이점이 있습니다.

  • 사용자 지정 제약 조건이 필요하지 않습니다.
  • 경로 매개 변수에 0이 포함된 경우 더 자세한 설명이 포함된 오류를 반환합니다.

매개 변수 변환기

매개 변수 변환기는:

예를 들어, Url.Action(new { article = "MyTestArticle" })을 사용하는 경로 패턴 blog\{article:slugify}의 사용자 지정 slugify 매개 변수 변환기는 blog\my-test-article을 생성합니다.

다음 IOutboundParameterTransformer 구현을 생각해 보겠습니다.

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

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

경로 패턴에서 매개 변수 변환기를 사용하려면 Program.csConstraintMap을 사용하여 구성합니다.

builder.Services.AddRouting(options =>
    options.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer));

ASP.NET Core Framework는 매개 변수 변화기를 사용하여 엔드포인트가 해결되는 URI를 변환합니다. 예를 들어 매개 변수 변환기는 area, controller, actionpage와 일치하도록 사용되는 경로 값을 변환합니다.

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

위의 경로 템플릿을 사용하면 SubscriptionManagementController.GetAll 작업이 URI /subscription-management/get-all과 일치합니다. 매개 변수 변환기는 링크를 생성하는 데 사용되는 경로 값을 변경하지 않습니다. 예를 들어 Url.Action("GetAll", "SubscriptionManagement")/subscription-management/get-all을 출력합니다.

ASP.NET Core는 생성된 경로와 함께 매개 변수 변환기를 사용하기 위한 API 규칙을 제공합니다.

URL 생성 참조

이 단원에는 URL 생성에서 구현하는 알고리즘에 대한 참조가 포함되어 있습니다. 실제로 URL 생성의 가장 복잡한 예제에서는 컨트롤러나 Razor Pages를 사용합니다. 자세한 내용은 컨트롤러의 라우팅을 참조하세요.

URL 생성 프로세스는 호출 또는 유사한 메서드로 LinkGenerator.GetPathByAddress 시작합니다. 메서드에는 주소, 경로 값 집합 및 HttpContext의 현재 요청에 관한 정보(선택 사항)를 제공합니다.

첫 번째 단계에서는 주소를 사용하여 주소의 형식과 일치하는 IEndpointAddressScheme<TAddress>을 사용하는 후보 엔드포인트 집합을 확인합니다.

주소 체계에 따라 후보 세트를 찾으면 URL 생성 작업이 성공할 때까지 엔드포인트가 순서 지정되고 반복적으로 처리됩니다. URL 생성에서는 모호성을 확인하지 않으며, 반환되는 첫 번째 결과가 최종 결과입니다.

로깅을 사용하여 URL 생성 문제 해결

URL 생성 문제를 해결하는 첫 번째 단계는 Microsoft.AspNetCore.Routing의 로깅 수준을 TRACE로 설정하는 것입니다. LinkGenerator는 문제 해결에 유용할 수 있는 처리에 관한 여러 세부 정보를 기록합니다.

URL 생성에 대한 자세한 내용은 URL 생성 참조를 참조하세요.

주소

주소는 URL 생성에서 링크 생성기의 호출을 후보 엔드포인트 집합에 바인딩하는 데 사용되는 개념입니다.

주소는 기본적으로 다음 두 가지 구현이 함께 제공되는 확장 가능한 개념입니다.

  • 엔드포인트 이름(string)을 주소로 사용:
    • MVC의 경로 이름과 유사한 기능을 제공합니다.
    • IEndpointNameMetadata 메타데이터 형식을 사용합니다.
    • 등록된 모든 엔드포인트의 메타데이터와 제공된 문자열을 비교하여 확인합니다.
    • 여러 엔드포인트에서 같은 이름을 사용하는 경우 시작 시 예외를 throw합니다.
    • 컨트롤러 및 Razor Pages 외의 일반적인 용도에 사용하는 것이 좋습니다.
  • 경로 값(RouteValuesAddress)을 주소로 사용:
    • 컨트롤러 및 Razor Pages 레거시 URL 생성과 비슷한 기능을 제공합니다.
    • 확장 및 디버그하기가 매우 복잡합니다.
    • IUrlHelper, 태그 도우미, HTML 도우미, 작업 결과 등에서 사용하는 구현을 제공합니다.

주소 체계의 역할은 다음과 같은 임의 조건에 따라 주소와 일치하는 엔드포인트 사이를 연결하는 것입니다.

  • 엔드포인트 이름 체계에서 기본 사전 조회를 수행합니다.
  • 경로 값 체계에는 알고리즘의 복잡한 최적 하위 집합이 있습니다.

앰비언트 값 및 명시적 값

현재 요청에서 라우팅은 현재 요청의 경로 값 HttpContext.Request.RouteValues에 액세스합니다. 현재 요청과 연결된 값을 앰비언트 값이라고 합니다. 명확하게 하도록 설명서에서는 메서드에 전달된 경로 값을 명시적 값이라고 합니다.

다음 예제에서는 앰비언트 값과 명시적 값을 보여 줍니다. 현재 요청의 앰비언트 값과 명시적 값을 제공합니다.

public class WidgetController : ControllerBase
{
    private readonly LinkGenerator _linkGenerator;

    public WidgetController(LinkGenerator linkGenerator) =>
        _linkGenerator = linkGenerator;

    public IActionResult Index()
    {
        var indexPath = _linkGenerator.GetPathByAction(
            HttpContext, values: new { id = 17 })!;

        return Content(indexPath);
    }

    // ...

앞의 코드가 하는 역할은 다음과 같습니다.

  • /Widget/Index/17를 반환합니다.
  • DI를 통해 LinkGenerator를 가져옵니다.

다음 코드는 명시적 값만 제공하고 앰비언트 값은 제공하지 않습니다.

var subscribePath = _linkGenerator.GetPathByAction(
    "Subscribe", "Home", new { id = 17 })!;

앞의 메서드는 /Home/Subscribe/17을 반환합니다.

WidgetController의 다음 코드는 /Widget/Subscribe/17을 반환합니다.

var subscribePath = _linkGenerator.GetPathByAction(
    HttpContext, "Subscribe", null, new { id = 17 });

다음 코드는 현재 요청의 앰비언트 값과 명시적 값을 컨트롤러에 제공합니다.

public class GadgetController : ControllerBase
{
    public IActionResult Index() =>
        Content(Url.Action("Edit", new { id = 17 })!);
}

위의 코드에서

  • /Gadget/Edit/17이 반환됩니다.
  • UrlIUrlHelper를 가져옵니다.
  • Action은 작업 메서드의 절대 경로가 포함된 URL을 생성합니다. URL에는 지정된 action 이름과 route 값이 포함됩니다.

다음 코드에서는 현재 요청의 앰비언트 값과 명시적 값을 제공합니다.

public class IndexModel : PageModel
{
    public void OnGet()
    {
        var editUrl = Url.Page("./Edit", new { id = 17 });

        // ...
    }
}

앞의 코드에서는 Razor 편집 페이지에 다음 페이지 지시문이 포함된 경우 url/Edit/17로 설정합니다.

@page "{id:int}"

편집 페이지에 "{id:int}" 경로 템플릿이 포함되어 있지 않으면 url/Edit?id=17입니다.

MVC의 IUrlHelper 동작은 여기에 설명된 규칙 외에도 복잡성 계층을 추가합니다.

  • IUrlHelper는 항상 현재 요청의 경로 값을 앰비언트 값으로 제공합니다.
  • IUrlHelper.Action은 개발자가 재정의하는 경우 외에는 항상 현재 actioncontroller 경로 값을 명시적 값으로 복사합니다.
  • IUrlHelper.Page는 재정의되는 경우 외에는 항상 현재 page 경로 값을 명시적 값으로 복사합니다.
  • IUrlHelper.Page는 재정의되는 경우 외에는 항상 현재 handler 경로 값을 명시적 값 null로 재정의합니다.

MVC가 자체 규칙을 따르지 않기 때문에 앰비언트 값의 동작 세부 정보에 사용자가 놀라는 경우가 많습니다. 기록 및 호환성을 위해 action, controller, page, handler 등의 특정 경로 값에는 고유한 특수 사례 동작이 있습니다.

LinkGenerator.GetPathByActionLinkGenerator.GetPathByPage에서 제공하는 동일한 기능에서는 호환성을 위해 IUrlHelper의 이러한 변칙을 복제합니다.

URL 생성 프로세스

후보 엔드포인트 집합을 찾으면 URL 생성 알고리즘은 다음을 수행합니다.

  • 엔드포인트를 반복적으로 처리합니다.
  • 첫 번째 성공적인 결과를 반환합니다.

이 프로세스의 첫 번째 단계를 경로 값 무효화라고 합니다. 경로 값 무효화는 라우팅에서 앰비언트 값의 어떤 경로 값을 사용하고 무시할지 결정하는 프로세스입니다. 각 앰비언트 값이 고려되고 명시적 값과 결합되거나 무시됩니다.

앰비언트 값의 역할을 알아보려면 일반적인 사례에서 애플리케이션 개발자 입력을 저장하려고 하는 점을 고려하면 가장 좋습니다. 일반적으로 앰비언트 값이 유용한 시나리오는 MVC와 관련이 있습니다.

  • 동일한 컨트롤러의 다른 작업에 연결하는 경우에는 컨트롤러 이름을 지정할 필요가 없습니다.
  • 같은 영역의 다른 컨트롤러에 연결하는 경우 영역 이름을 지정할 필요가 없습니다.
  • 동일한 작업 메서드에 연결하는 경우 경로 값을 지정할 필요가 없습니다.
  • 앱의 다른 부분에 연결하는 경우 앱의 해당 부분에서 의미가 없는 경로 값을 전달하지 않을 수 있습니다.

null을 반환하는 LinkGenerator 또는 IUrlHelper를 호출하면 일반적으로 이해되지 않는 경로 값 무효화가 발생합니다. 경로 값 무효화 문제를 해결하려면 더 많은 경로 값을 명시적으로 지정하여 문제가 해결되는지 확인합니다.

경로 값 무효화는 앱의 URL 체계가 계층이 왼쪽에서 오른쪽으로 형성된 계층 구조라고 가정하고 작동합니다. 기본 컨트롤러 경로 템플릿 {controller}/{action}/{id?}를 사용하여 실제 작동 방법을 직관적으로 파악해 보겠습니다. 값을 변경하면 오른쪽에 표시되는 경로 값이 모두 무효화됩니다. 이는 계층 구조에 관한 가정이 반영된 것입니다. 앱에 id의 앰비언트 값이 있고 작업에서 controller에 대해 다른 값을 지정하는 경우:

  • {controller}{id?}의 왼쪽에 있으므로 id가 다시 사용되지 않습니다.

이 원칙을 보여 주는 몇 가지 예는 다음과 같습니다.

  • 명시적 값에 id의 값이 포함된 경우 id의 앰비언트 값은 무시됩니다. controlleraction의 앰비언트 값이 사용될 수 있습니다.
  • 명시적 값에 action의 값이 포함된 경우 action의 앰비언트 값은 무시됩니다. controller의 앰비언트 값이 사용될 수 있습니다. action의 명시적 값이 action의 앰비언트 값과 다른 경우 id 값은 사용되지 않습니다. action의 명시적 값이 action의 앰비언트 값과 같으면 id 값이 사용될 수 있습니다.
  • 명시적 값에 controller의 값이 포함된 경우 controller의 앰비언트 값은 무시됩니다. controller의 명시적 값이 controller의 앰비언트 값과 다른 경우 actionid 값은 사용되지 않습니다. controller의 명시적 값이 controller의 앰비언트 값과 같으면 actionid 값이 사용될 수 있습니다.

이 프로세스는 특성 경로와 전용 규칙 기반 경로가 있으면 더 복잡해집니다. {controller}/{action}/{id?}와 같은 컨트롤러 규칙 기반 경로는 경로 매개 변수를 사용하여 계층 구조를 지정합니다. 컨트롤러 및 Razor Pages에 대한 전용 규칙 기반 경로특성 경로의 경우:

  • 경로 값의 계층 구조가 있습니다.
  • 템플릿에는 표시되지 않습니다.

이러한 경우 URL 생성에서 필수 값 개념을 정의합니다. 컨트롤러 및 Razor Pages에서 만든 엔드포인트에는 경로 값 무효화가 작동할 수 있도록 필수 값이 지정되어 있습니다.

경로 값 무효화 알고리즘을 자세히 설명하면 다음과 같습니다.

  • 필요 값 이름을 경로 매개 변수와 결합한 다음 왼쪽에서 오른쪽으로 처리합니다.
  • 각 매개 변수에 대해 앰비언트 값과 명시적 값이 비교됩니다.
    • 앰비언트 값과 명시적 값이 같으면 프로세스가 계속됩니다.
    • 앰비언트 값이 있고 명시적 값이 없으면 URL을 생성할 때 앰비언트 값이 사용됩니다.
    • 앰비언트 값이 없고 명시적 값이 있으면 앰비언트 값과 모든 후속 앰비언트 값을 거부합니다.
    • 앰비언트 값과 명시적 값이 있고 두 값이 다른 경우 앰비언트 값과 모든 후속 앰비언트 값을 거부합니다.

이제 URL 생성 작업에서 경로 제약 조건을 평가할 준비가 되었습니다. 허용되는 값 집합이 제약 조건에 제공되는 매개 변수 기본값과 결합됩니다. 제약 조건이 모두 통과되면 작업이 계속됩니다.

다음으로 허용되는 값을 사용하여 경로 템플릿을 확장할 수 있습니다. 경로 템플릿은 다음과 같이 처리됩니다.

  • 왼쪽에서 오른쪽으로
  • 각 매개 변수의 허용되는 값이 대체됩니다.
  • 다음과 같은 특수한 경우를 사용합니다.
    • 허용되는 값에 누락된 값이 있고 매개 변수에 기본값이 있으면 기본값이 사용됩니다.
    • 허용되는 값에 누락된 값이 있고 매개 변수가 선택 사항이면 처리가 계속됩니다.
    • 누락된 선택적 매개 변수 오른쪽의 경로 매개 변수에 값이 있으면 작업이 실패합니다.
    • 연속된 기본값 매개 변수 및 선택적 매개 변수는 가능하면 축소됩니다.

명시적으로 제공되지만 경로의 세그먼트와 일치하지 않는 값은 쿼리 문자열에 추가됩니다. 다음 표에서 경로 템플릿 {controller}/{action}/{id?}를 사용하는 경우 결과를 보여 줍니다.

앰비언트 값 명시적 값 결과
controller = "Home" action = "About" /Home/About
controller = "Home" controller = "Order", action = "About" /Order/About
controller = "Home", color = "Red" action = "About" /Home/About
controller = "Home" action = "About", color = "Red" /Home/About?color=Red

선택적 경로 매개 변수 순서

선택적 경로 매개 변수는 모든 필수 경로 매개 변수 후에 와야 합니다. 다음 코드에서 매개 변수와 name 매개 변수는 id 매개 변수 다음에 color 와야 합니다.

using Microsoft.AspNetCore.Mvc;

namespace WebApplication1.Controllers;

[Route("api/[controller]")]
public class MyController : ControllerBase
{
    // GET /api/my/red/2/joe
    // GET /api/my/red/2
    // GET /api/my
    [HttpGet("{color}/{id:int?}/{name?}")]
    public IActionResult GetByIdAndOptionalName(string color, int id = 1, string? name = null)
    {
        return Ok($"{color} {id} {name ?? ""}");
    }
}

경로 값 무효화 문제

다음 코드에서는 라우팅에서 지원하지 않는 URL 생성 체계의 예제를 보여 줍니다.

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

app.MapControllerRoute(
    "blog",
    "{culture}/{**slug}",
    new { controller = "Blog", action = "ReadPost" });

위의 코드에서 culture 경로 매개 변수는 지역화에 사용됩니다. culture 매개 변수가 앰비언트 값으로 항상 허용되게 하려는 것이 목표입니다. 그러나 culture 매개 변수는 필수 값이 작동하는 방식 때문에 앰비언트 값으로 허용되지 않습니다.

  • "default" 경로 템플릿에서 culture 경로 매개 변수는 controller의 왼쪽에 있으므로 controller를 변경해도 culture가 무효화되지 않습니다.
  • "blog" 경로 템플릿에서 culture 경로 매개 변수는 필수 값에 표시되는 controller의 오른쪽에 있는 것으로 간주됩니다.

LinkParser을(를) 사용하여 URL 경로 구문 분석

LinkParser클래스는 URL 경로를 경로 값 집합으로 구문 분석하기 위한 지원을 추가합니다. ParsePathByEndpointName 메서드는 엔드포인트 이름과 URL 경로를 사용하고 URL 경로에서 추출된 경로 값 집합을 반환합니다.

다음 예제 컨트롤러에서 GetProduct 작업은 api/Products/{id}의 경로 템플릿을 사용하며 GetProductName을 가집니다.

[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
    [HttpGet("{id}", Name = nameof(GetProduct))]
    public IActionResult GetProduct(string id)
    {
        // ...

동일한 컨트롤러 클래스에서 AddRelatedProduct 작업에는 쿼리 문자열 매개 변수로 제공할 수 있는 URL 경로 pathToRelatedProduct가 필요합니다.

[HttpPost("{id}/Related")]
public IActionResult AddRelatedProduct(
    string id, string pathToRelatedProduct, [FromServices] LinkParser linkParser)
{
    var routeValues = linkParser.ParsePathByEndpointName(
        nameof(GetProduct), pathToRelatedProduct);
    var relatedProductId = routeValues?["id"];

    // ...

앞의 예제에서 AddRelatedProduct 작업은 URL 경로에서 id 경로 값을 추출합니다. 예를 들어 /api/Products/1의 URL 경로가 있는 경우 relatedProductId 값은 1(으)로 설정됩니다. 이 방법을 사용하면 API의 클라이언트가 리소스를 참조할 때 URL 경로를 사용할 수 있으므로 이러한 URL이 어떻게 구성되는지에 대한 지식이 필요하지 않습니다.

엔드포인트 메타데이터 구성

다음 링크는 엔드포인트 메타데이터를 구성하는 방법에 대한 정보를 제공합니다.

RequireHost가 있는 경로의 호스트 일치

RequireHost는 지정된 호스트가 필요한 경로에 제약 조건을 적용합니다. RequireHost 또는 [Host] 매개 변수는 다음과 같을 수 있습니다.

  • 호스트: www.domain.com(아무 포트에서나 www.domain.com과 일치)
  • 와일드카드가 있는 호스트: *.domain.com(아무 포트에서나 www.domain.com, subdomain.domain.com 또는 www.subdomain.domain.com과 일치)
  • 포트: *:5000(아무 호스트에서나 포트 5000과 일치)
  • 호스트 및 포트: www.domain.com:5000 또는 *.domain.com:5000(호스트 및 포트와 일치)

RequireHost 또는 [Host]를 사용하여 여러 매개 변수를 지정할 수 있습니다. 제약 조건은 모든 매개 변수에 유효한 호스트와 일치합니다. 예를 들어 [Host("domain.com", "*.domain.com")]domain.com, www.domain.comsubdomain.domain.com과 일치합니다.

다음 코드는 RequireHost를 사용하여 경로상에 있는 지정된 호스트를 요구합니다.

app.MapGet("/", () => "Contoso").RequireHost("contoso.com");
app.MapGet("/", () => "AdventureWorks").RequireHost("adventure-works.com");

app.MapHealthChecks("/healthz").RequireHost("*:8080");

다음 코드는 컨트롤러의 [Host] 특성을 사용하여 지정된 호스트를 요구합니다.

[Host("contoso.com", "adventure-works.com")]
public class HostsController : Controller
{
    public IActionResult Index() =>
        View();

    [Host("example.com")]
    public IActionResult Example() =>
        View();
}

[Host] 특성이 컨트롤러 메서드와 작업 메서드에 모두 적용될 경우

  • 작업의 특성이 사용됩니다.
  • 컨트롤러의 특성은 무시됩니다.

경로 그룹

MapGroup확장 메서드는 공통 접두사를 사용하여 엔드포인트 그룹을 구성하는 데 도움이 됩니다. 반복 코드를 줄이고 엔드포인트 메타데이터를 추가하는 RequireAuthorization이나 WithMetadata와 같은 메서드로서 단일 호출로 엔드포인트 전체 그룹을 사용자 지정할 수 있게 됩니다.

예를 들어 다음 코드에서는 두 개의 유사한 엔드포인트 그룹을 만듭니다.

app.MapGroup("/public/todos")
    .MapTodosApi()
    .WithTags("Public");

app.MapGroup("/private/todos")
    .MapTodosApi()
    .WithTags("Private")
    .AddEndpointFilterFactory(QueryPrivateTodos)
    .RequireAuthorization();


EndpointFilterDelegate QueryPrivateTodos(EndpointFilterFactoryContext factoryContext, EndpointFilterDelegate next)
{
    var dbContextIndex = -1;

    foreach (var argument in factoryContext.MethodInfo.GetParameters())
    {
        if (argument.ParameterType == typeof(TodoDb))
        {
            dbContextIndex = argument.Position;
            break;
        }
    }

    // Skip filter if the method doesn't have a TodoDb parameter.
    if (dbContextIndex < 0)
    {
        return next;
    }

    return async invocationContext =>
    {
        var dbContext = invocationContext.GetArgument<TodoDb>(dbContextIndex);
        dbContext.IsPrivate = true;

        try
        {
            return await next(invocationContext);
        }
        finally
        {
            // This should only be relevant if you're pooling or otherwise reusing the DbContext instance.
            dbContext.IsPrivate = false;
        }
    };
}
public static RouteGroupBuilder MapTodosApi(this RouteGroupBuilder group)
{
    group.MapGet("/", GetAllTodos);
    group.MapGet("/{id}", GetTodo);
    group.MapPost("/", CreateTodo);
    group.MapPut("/{id}", UpdateTodo);
    group.MapDelete("/{id}", DeleteTodo);

    return group;
}

이 시나리오에서는 201 Created 결과에서 Location 헤더에 대한 상대 주소를 사용할 수 있습니다.

public static async Task<Created<Todo>> CreateTodo(Todo todo, TodoDb database)
{
    await database.AddAsync(todo);
    await database.SaveChangesAsync();

    return TypedResults.Created($"{todo.Id}", todo);
}

첫 번째 엔드포인트 그룹은 접두사로 지정된 요청만 /public/todos 매칭하게 되며 인증 없이 액세스할 수 있습니다. 두 번째 엔드포인트 그룹은 접두사 /private/todos를 가지고 인증이 필요한 요청만 일치합니다.

QueryPrivateTodos엔드포인트 필터 팩터리는 경로 처리기의 TodoDb 매개 변수를 수정하여 프라이빗 할일 데이터에 접근하고 보관할 수 있도록 수정하는 로컬 함수입니다.

경로 그룹은 경로 매개 변수 및 제약 조건이 있는 중첩 그룹 및 복잡한 접두사 유형도 지원합니다. 다음 예제에서 그룹에 매핑된 user 경로 처리기는 외부 그룹 접두사에 정의된 {org}{group} 경로 매개 변수를 캡처할 수 있습니다.

접두사는 비어 있을 수도 있습니다. 이 기능은 경로 패턴을 변경하지 않고 엔드포인트 메타데이터 또는 필터를 엔드포인트 그룹에 추가하는 데 유용할 수 있습니다.

var all = app.MapGroup("").WithOpenApi();
var org = all.MapGroup("{org}");
var user = org.MapGroup("{user}");
user.MapGet("", (string org, string user) => $"{org}/{user}");

그룹에 필터 또는 메타데이터를 추가하면 내부 그룹 또는 특정 엔드포인트에 추가될 수 있는 추가 필터 또는 메타데이터를 추가하기 전에 각 엔드포인트에 개별적으로 추가하는 것과 동일한 결과가 됩니다.

var outer = app.MapGroup("/outer");
var inner = outer.MapGroup("/inner");

inner.AddEndpointFilter((context, next) =>
{
    app.Logger.LogInformation("/inner group filter");
    return next(context);
});

outer.AddEndpointFilter((context, next) =>
{
    app.Logger.LogInformation("/outer group filter");
    return next(context);
});

inner.MapGet("/", () => "Hi!").AddEndpointFilter((context, next) =>
{
    app.Logger.LogInformation("MapGet filter");
    return next(context);
});

위의 예제에서 외부 필터는 두 번째로 추가된 경우에도 내부 필터 앞에 들어오는 요청을 기록하게 됩니다. 필터가 서로 다른 그룹에 적용되었기 때문에 필터가 추가된 상대적 순서는 중요하지 않습니다. 동일한 그룹 또는 특정 엔드포인트에 적용되는 경우 주문 필터가 추가됩니다.

/outer/inner/에 대한 요청은 다음을 기록합니다.

/outer group filter
/inner group filter
MapGet filter

라우팅의 성능 지침

앱에 성능 문제가 있는 경우 라우팅이 문제의 원인으로 의심받는 경우가 많습니다. 라우팅이 의심받는 이유는 컨트롤러 및 Razor Pages 같은 프레임워크가 프레임워크 내에서 소요된 시간을 로깅 메시지로 보고하기 때문입니다. 컨트롤러에서 보고하는 시간과 요청의 총 시간 사이에 상당한 차이가 있는 경우:

  • 개발자는 문제의 원인인 앱 코드를 제거합니다.
  • 일반적으로 라우팅이 원인이라고 가정합니다.

라우팅은 수천 개의 엔드포인트를 사용하여 성능을 테스트했습니다. 일반적인 앱에서는 너무 크다고 성능 문제가 발생할 가능성은 거의 없습니다. 라우팅 성능이 저하되는 가장 일반적인 근본 원인은 일반적으로 잘못 동작하는 사용자 지정 미들웨어입니다.

다음 코드 샘플에서는 지연의 원인을 좁히기 위한 기본 기술을 보여 줍니다.

var logger = app.Services.GetRequiredService<ILogger<Program>>();

app.Use(async (context, next) =>
{
    var stopwatch = Stopwatch.StartNew();
    await next(context);
    stopwatch.Stop();

    logger.LogInformation("Time 1: {ElapsedMilliseconds}ms", stopwatch.ElapsedMilliseconds);
});

app.UseRouting();

app.Use(async (context, next) =>
{
    var stopwatch = Stopwatch.StartNew();
    await next(context);
    stopwatch.Stop();

    logger.LogInformation("Time 2: {ElapsedMilliseconds}ms", stopwatch.ElapsedMilliseconds);
});

app.UseAuthorization();

app.Use(async (context, next) =>
{
    var stopwatch = Stopwatch.StartNew();
    await next(context);
    stopwatch.Stop();

    logger.LogInformation("Time 3: {ElapsedMilliseconds}ms", stopwatch.ElapsedMilliseconds);
});

app.MapGet("/", () => "Timing Test.");

종료 시간 라우팅:

  • 앞의 코드에 표시된 타이밍 미들웨어의 복사본을 각 미들웨어에 인터리빙합니다.
  • 고유 식별자를 추가하여 타이밍 데이터를 코드와 연관 짓습니다.

이것이 예를 들어 10ms 이상의 상당한 지연이 발생하는 경우 지연을 좁히는 기본적인 방법입니다. Time 1에서 Time 2를 빼면 UseRouting 미들웨어 내에서 소요된 시간이 보고됩니다.

다음 코드에서는 앞의 타이밍 코드에 더욱 간결한 방법을 사용합니다.

public sealed class AutoStopwatch : IDisposable
{
    private readonly ILogger _logger;
    private readonly string _message;
    private readonly Stopwatch _stopwatch;
    private bool _disposed;

    public AutoStopwatch(ILogger logger, string message) =>
        (_logger, _message, _stopwatch) = (logger, message, Stopwatch.StartNew());

    public void Dispose()
    {
        if (_disposed)
        {
            return;
        }

        _logger.LogInformation("{Message}: {ElapsedMilliseconds}ms",
            _message, _stopwatch.ElapsedMilliseconds);

        _disposed = true;
    }
}
var logger = app.Services.GetRequiredService<ILogger<Program>>();
var timerCount = 0;

app.Use(async (context, next) =>
{
    using (new AutoStopwatch(logger, $"Time {++timerCount}"))
    {
        await next(context);
    }
});

app.UseRouting();

app.Use(async (context, next) =>
{
    using (new AutoStopwatch(logger, $"Time {++timerCount}"))
    {
        await next(context);
    }
});

app.UseAuthorization();

app.Use(async (context, next) =>
{
    using (new AutoStopwatch(logger, $"Time {++timerCount}"))
    {
        await next(context);
    }
});

app.MapGet("/", () => "Timing Test.");

비용이 많이 들 수 있는 라우팅 기능

다음 목록에서는 기본 경로 템플릿보다 비교적 비용이 많이 드는 라우팅 기능에 대한 몇 가지 유용한 정보를 제공합니다.

  • 정규식: 복잡한 정규식을 작성하거나 적은 양의 입력으로 장기 실행 시간을 가질 수 있습니다.
  • 복합 세그먼트({x}-{y}-{z}):
    • 일반 URL 경로 세그먼트를 구문 분석하는 것보다 훨씬 비용이 많이 듭니다.
    • 더 많은 부분 문자열이 할당됩니다.
  • 동기 데이터 액세스: 많은 복잡한 앱은 라우팅의 일부로 데이터베이스 액세스 권한을 갖습니다. MatcherPolicyEndpointSelectorContext 같은 비동기식 확장 지점을 사용합니다.

큰 라우팅 테이블 지침

기본적으로 ASP.NET Core는 메모리를 CPU 시간과 교환하는 라우팅 알고리즘을 사용합니다. 이는 경로 일치 시간이 일치시킬 경로의 길이에만 종속되고 경로 수에 종속되지 않는 좋은 효과가 있습니다. 하지만 앱에 경로가 많고(수천 개) 경로에 많은 변수 접두사가 있는 경우 이 방법은 잠재적으로 문제가 될 수 있습니다. 예를 들어 경로의 초기 세그먼트에 {parameter}/some/literal과 같은 매개 변수가 있는 경우입니다.

다음과 같은 경우가 아니면 이것이 문제가 되는 상황이 앱에 발생할 가능성은 낮습니다.

  • 이 패턴을 사용하는 앱에 많은 수의 경로가 있습니다.
  • 앱에 많은 수의 경로가 있습니다.

앱에서 큰 라우팅 테이블 문제가 발생하는지 확인하는 방법

  • 찾아야 할 두 가지 증상이 있습니다.
    • 앱이 첫 번째 요청에서 시작하는 속도가 느립니다.
      • 이 증상은 필수이지만 충분하지는 않습니다. 앱 시작 속도가 느려질 수 있는 경로 문제 외의 다른 문제가 많이 있습니다. 아래 조건을 확인하여 앱에 이 상황이 발생하고 있는지 정확하게 확인합니다.
    • 앱이 시작하는 동안 많은 메모리를 사용하고 메모리 덤프에 많은 수의 Microsoft.AspNetCore.Routing.Matching.DfaNode 인스턴스가 표시됩니다.

이 문제를 해결하는 방법

이 시나리오를 크게 개선하는 몇 가지 방법과 최적화를 경로에 적용할 수 있습니다.

  • 가능한 경우 {parameter:int}, {parameter:guid}, {parameter:regex(\\d+)} 등과 같은 경로 제약 조건을 매개 변수에 적용합니다.
    • 그러면 라우팅 알고리즘이 일치에 사용되는 구조를 내부적으로 최적화하고 사용되는 메모리를 크게 줄일 수 있습니다.
    • 대부분의 경우 허용 가능한 동작으로 돌아가는 데는 이것으로 충분합니다.
  • 템플릿에서 매개 변수를 이후 세그먼트로 이동하도록 경로를 변경합니다.
    • 그러면 특정 경로의 엔드포인트와 일치하는 "경로" 수가 줄어듭니다.
  • 동적 경로를 사용하고 컨트롤러/페이지에 대한 매핑을 동적으로 수행합니다.
    • MapDynamicControllerRouteMapDynamicPageRoute를 사용하면 가능합니다.

라이브러리 작성자를 위한 지침

이 단원에는 라우팅을 기반으로 빌드하는 라이브러리 작성자를 위한 지침이 포함되어 있습니다. 이러한 세부 내용은 앱 개발자가 라우팅을 확장하는 라이브러리 및 프레임워크를 사용하는 좋은 환경을 갖추도록 하기 위한 것입니다.

엔드포인트 정의

URL 일치를 위해 라우팅을 사용하는 프레임워크를 만들려면 먼저 UseEndpoints를 기반으로 빌드되는 사용자 환경을 정의합니다.

IEndpointRouteBuilder를 기반으로 빌드하세요. 이렇게 하면 사용자는 혼동하지 않고 다른 ASP.NET Core 기능을 사용하여 프레임워크를 작성할 수 있습니다. 모든 ASP.NET Core 템플릿에는 라우팅이 포함됩니다. 라우팅이 있고 사용자에게 친숙하다고 간주합니다.

// Your framework
app.MapMyFramework(...);

app.MapHealthChecks("/healthz");

IEndpointConventionBuilder를 구현하는 MapMyFramework(...) 호출에서 봉인된 구체적인 형식을 반환하세요. 대부분의 프레임워크 Map... 메서드는 이 패턴을 따릅니다. IEndpointConventionBuilder 인터페이스:

  • 메타데이터를 작성할 수 있습니다.
  • 다양한 확장 메서드의 대상으로 지정됩니다.

고유의 형식을 선언하면 작성기에 사용자 고유의 프레임워크 관련 기능을 추가할 수 있습니다. 프레임워크 선언된 작성기를 래핑하고 여기에 호출을 전달해도 됩니다.

// Your framework
app.MapMyFramework(...)
    .RequireAuthorization()
    .WithMyFrameworkFeature(awesome: true);

app.MapHealthChecks("/healthz");

사용자 고유의 EndpointDataSource를 작성하는 것이 좋습니다. EndpointDataSource는 엔드포인트 컬렉션을 선언하고 업데이트하기 위한 하위 수준 기본 형식입니다. EndpointDataSource는 컨트롤러 및 Razor Pages에서 사용되는 강력한 API입니다.

라우팅 테스트에는 업데이트되지 않는 데이터 원본의 기본 예제가 있습니다.

GetGroupedEndpoints 구현을 고려합니다. 이렇게 하면 그룹화된 엔드포인트에서 실행 중인 그룹 규칙 및 최종 메타데이터를 완벽하게 제어할 수 있습니다. 예를 들어, 사용자 지정 EndpointDataSource 구현에서 그룹에 추가된 엔드포인트 필터를 실행할 수 있습니다.

기본적으로 EndpointDataSource를 등록하지 마세요. 프레임워크를 UseEndpoints에 등록하도록 사용자에게 요구하세요. 라우팅의 원리에 따르면 기본적으로 아무것도 포함되지 않으며 UseEndpoints가 엔드포인트를 등록하는 위치입니다.

라우팅 통합 미들웨어 만들기

메타데이터 형식을 인터페이스로 정의하는 것이 좋습니다.

메타데이터 형식을 클래스 및 메서드의 특성으로 사용할 수 있게 하세요.

public interface ICoolMetadata
{
    bool IsCool { get; }
}

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CoolMetadataAttribute : Attribute, ICoolMetadata
{
    public bool IsCool => true;
}

컨트롤러 및 Razor Pages와 같은 프레임워크는 형식 및 메서드에 메타데이터 특성을 적용하도록 지원합니다. 메타데이터 형식을 선언하는 경우:

  • 이 형식에 특성으로 액세스할 수 있습니다.
  • 사용자 대부분이 특성을 적용하는 데 익숙합니다.

메타데이터 형식을 인터페이스로 선언하면 또 하나의 유연성 계층이 추가됩니다.

  • 인터페이스는 구성할 수 있습니다.
  • 개발자가 여러 정책을 결합하여 고유한 형식을 선언할 수 있습니다.

다음 예제와 같이 메타데이터를 재정의할 수 있게 하세요.

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class SuppressCoolMetadataAttribute : Attribute, ICoolMetadata
{
    public bool IsCool => false;
}

[CoolMetadata]
public class MyController : Controller
{
    public void MyCool() { }

    [SuppressCoolMetadata]
    public void Uncool() { }
}

이러한 지침을 따르는 가장 좋은 방법은 마커 메타데이터를 정의하지 않는 것입니다.

  • 메타데이터 형식이 있는지만 확인하면 안 됩니다.
  • 메타데이터의 속성을 정의하고 속성을 확인합니다.

메타데이터 컬렉션은 순서 지정되며 우선 순위별로 재정의할 수 있습니다. 컨트롤러의 경우 작업 메서드의 메타데이터가 가장 구체적입니다.

라우팅이 있는지 관계없이 미들웨어를 유용하게 사용할 수 있게 하세요.

app.UseAuthorization(new AuthorizationPolicy() { ... });

// Your framework
app.MapMyFramework(...).RequireAuthorization();

이 지침의 예제에서는 UseAuthorization 미들웨어를 고려해 보겠습니다. 권한 부여 미들웨어를 사용하면 대체 정책을 전달할 수 있습니다. 대체 정책(지정된 경우)은 다음 모두에 적용됩니다.

  • 지정된 정책이 없는 엔드포인트
  • 엔드포인트와 일치하지 않는 요청

따라서 권한 부여 미들웨어는 라우팅 컨텍스트 외에서도 유용합니다. 권한 부여 미들웨어는 기존 미들웨어 프로그래밍에 사용할 수 있습니다.

디버그 진단

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

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

추가 리소스

라우팅은 들어오는 HTTP 요청을 일치시켜 앱의 실행 가능 엔드포인트로 디스패치하는 역할을 담당합니다. 엔드포인트는 앱의 실행 가능 요청 처리 코드 단위입니다. 엔드포인트는 앱에서 정의되고 앱 시작 시 구성됩니다. 엔드포인트 일치 프로세스는 요청의 URL에서 값을 추출하고 요청 처리를 위해 이 값을 제공할 수 있습니다. 또한 라우팅은 앱의 엔드포인트 정보를 사용하여 엔드포인트에 매핑되는 URL을 생성할 수도 있습니다.

앱은 다음을 사용하여 라우팅을 구성할 수 있습니다.

  • 컨트롤러
  • Razor Pages
  • SignalR
  • gRPC 서비스
  • 상태 검사와 같은 엔드포인트 지원 미들웨어
  • 라우팅에 등록된 대리자 및 람다

이 문서에서는 ASP.NET Core 라우팅의 하위 수준 세부 정보를 설명합니다. 라우팅을 구성하는 방법은 다음을 참조하세요.

라우팅 기본 사항

다음 코드에서는 라우팅의 기본 예제를 보여 줍니다.

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

앞의 예제에는 메서드를 사용하는 단일 엔드포인트가 포함되어 있습니다 MapGet .

  • HTTP GET 요청이 루트 URL /로 전송되는 경우:
    • 요청 대리자가 실행됩니다.
    • Hello World!가 HTTP 응답에 기록됩니다.
  • 요청 메서드가 GET이 아니거나 루트 URL이 /가 아니면 일치하는 경로가 없고 HTTP 404가 반환됩니다.

라우팅은 UseRoutingUseEndpoints를 통해 등록된 미들웨어 쌍을 사용합니다.

  • UseRouting은 경로 일치를 미들웨어 파이프라인에 추가합니다. 이 미들웨어는 앱에 정의된 엔드포인트 집합을 확인하고 요청을 기반으로 가장 일치하는 항목을 선택합니다.
  • UseEndpoints는 엔드포인트 실행을 미들웨어 파이프라인에 추가합니다. 선택한 엔드포인트와 연결된 대리자를 실행합니다.

앱은 일반적으로 UseRouting 또는 UseEndpoints를 호출할 필요가 없습니다. WebApplicationBuilderUseRoutingUseEndpoints를 통해 Program.cs에 추가된 미들웨어를 래핑하는 미들웨어 파이프라인을 구성합니다. 그러나 앱은 이러한 메서드를 명시적으로 호출하여 UseRoutingUseEndpoints 실행 순서를 변경할 수 있습니다. 예를 들어 다음 코드는 UseRouting을 명시적으로 호출합니다.

app.Use(async (context, next) =>
{
    // ...
    await next(context);
});

app.UseRouting();

app.MapGet("/", () => "Hello World!");

위의 코드에서

  • app.Use에 대한 호출은 파이프라인의 시작 부분에서 실행되는 사용자 지정 미들웨어를 등록합니다.
  • UseRouting에 대한 호출은 사용자 지정 미들웨어 이후에 실행할 경로 일치 미들웨어를 구성합니다.
  • MapGet에 등록된 엔드포인트는 파이프라인의 끝부분에서 실행됩니다.

앞의 예제에 UseRouting에 대한 호출이 포함되지 않았다면 사용자 지정 미들웨어는 경로 일치 미들웨어 이후에 실행됩니다.

엔드포인트

MapGet 메서드는 엔드포인트을 정의하는 데 사용됩니다. 엔드포인트는 다음과 같을 수 있습니다.

  • URL 및 HTTP 메서드를 일치시켜 선택됩니다.
  • 대리자를 실행하여 실행됩니다.

앱에서 일치시키고 실행할 수 있는 엔드포인트는 UseEndpoints에서 구성됩니다. 예를 들어 MapGet, MapPost이들과 유사한 메서드는 요청 대리자를 라우팅 시스템에 연결합니다. ASP.NET Core Framework 기능을 라우팅 시스템에 연결하는 데 다음과 같은 추가 메서드를 사용할 수 있습니다.

다음 예제에서는 더 복잡한 경로 템플릿을 사용한 라우팅을 보여 줍니다.

app.MapGet("/hello/{name:alpha}", (string name) => $"Hello {name}!");

/hello/{name:alpha} 문자열은 경로 템플릿이며 경로 템플릿은 엔드포인트가 일치되는 방식을 구성하는 데 사용됩니다. 이 경우 템플릿은 다음과 일치합니다.

  • /hello/Docs과 같은 URL
  • /hello/로 시작하고 그 다음에 시퀀스 영문자가 오는 URL 경로. :alpha는 영문자와만 일치하는 경로 제약 조건을 적용합니다. 경로 제약 조건은 이 문서의 뒷부분에 설명되어 있습니다.

URL 경로의 두 번째 세그먼트 {name:alpha}는 다음과 같습니다.

다음 예제에서는 상태 검사 및 권한 부여를 사용한 라우팅을 보여 줍니다.

app.UseAuthentication();
app.UseAuthorization();

app.MapHealthChecks("/healthz").RequireAuthorization();
app.MapGet("/", () => "Hello World!");

앞의 예제에서는 다음을 수행하는 방법을 보여 줍니다.

  • 권한 부여 미들웨어를 라우팅에 사용할 수 있습니다.
  • 엔드포인트를 사용하여 권한 부여 동작을 구성할 수 있습니다.

MapHealthChecks 호출은 상태 검사 엔드포인트를 추가합니다. 이 호출에 RequireAuthorization을 연결하면 권한 부여 정책이 엔드포인트에 연결됩니다.

UseAuthenticationUseAuthorization을 호출하면 인증 및 권한 부여 미들웨어가 추가됩니다. 이러한 미들웨어는 UseRoutingUseEndpoints 사이에 배치되므로 다음을 수행할 수 있습니다.

  • UseRouting에서 선택된 엔드포인트를 확인합니다.
  • 엔드포인트로 UseEndpoints가 디스패치되기 전에 권한 부여 정책을 적용합니다.

엔드포인트 메타데이터

앞의 예제에는 두 개의 엔드포인트가 있지만 상태 검사 엔드포인트에만 권한 부여 정책이 연결되어 있습니다. 요청이 상태 검사 엔드포인트 /healthz와 일치하는 경우 권한 부여 확인이 수행됩니다. 이는 엔드포인트에 추가 데이터가 연결될 수 있음을 보여 줍니다. 이러한 추가 데이터를 엔드포인트 메타데이터라고 합니다.

  • 이 메타데이터는 라우팅 인식 미들웨어에서 처리될 수 있으며
  • 모든 .NET 형식일 수 있습니다.

라우팅 개념

라우팅 시스템은 강력한 엔드포인트 개념을 추가하여 미들웨어 파이프라인을 기반으로 빌드됩니다. 엔드포인트는 라우팅, 권한 부여 및 ASP.NET Core 시스템 수의 측면에서 서로 다른 앱의 기능 단위를 나타냅니다.

ASP.NET Core 엔드포인트 정의

ASP.NET Core 엔드포인트는 다음과 같습니다.

다음 코드에서는 현재 요청과 일치하는 엔드포인트를 검색하고 검사하는 방법을 보여 줍니다.

app.Use(async (context, next) =>
{
    var currentEndpoint = context.GetEndpoint();

    if (currentEndpoint is null)
    {
        await next(context);
        return;
    }

    Console.WriteLine($"Endpoint: {currentEndpoint.DisplayName}");

    if (currentEndpoint is RouteEndpoint routeEndpoint)
    {
        Console.WriteLine($"  - Route Pattern: {routeEndpoint.RoutePattern}");
    }

    foreach (var endpointMetadata in currentEndpoint.Metadata)
    {
        Console.WriteLine($"  - Metadata: {endpointMetadata}");
    }

    await next(context);
});

app.MapGet("/", () => "Inspect Endpoint.");

엔드포인트는 선택된 경우 HttpContext에서 검색할 수 있습니다. 해당 속성을 검사할 수 있습니다. 엔드포인트 개체는 변경할 수 없으며 만든 후 수정할 수 없습니다. 가장 일반적인 형식의 엔드포인트는 RouteEndpoint입니다. RouteEndpoint에는 라우팅 시스템에서 선택할 수 있는 정보가 포함됩니다.

앞의 코드에서 app.Use는 인라인 미들웨어를 구성합니다.

다음 코드에서는 파이프라인에서 app.Use가 호출되는 위치에 따라 엔드포인트가 없을 수 있음을 보여 줍니다.

// Location 1: before routing runs, endpoint is always null here.
app.Use(async (context, next) =>
{
    Console.WriteLine($"1. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
    await next(context);
});

app.UseRouting();

// Location 2: after routing runs, endpoint will be non-null if routing found a match.
app.Use(async (context, next) =>
{
    Console.WriteLine($"2. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
    await next(context);
});

// Location 3: runs when this endpoint matches
app.MapGet("/", (HttpContext context) =>
{
    Console.WriteLine($"3. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
    return "Hello World!";
}).WithDisplayName("Hello");

app.UseEndpoints(_ => { });

// Location 4: runs after UseEndpoints - will only run if there was no match.
app.Use(async (context, next) =>
{
    Console.WriteLine($"4. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
    await next(context);
});

위의 샘플은 엔드포인트가 선택되었는지 아닌지를 표시하는 Console.WriteLine 문을 추가합니다. 명확하게 하도록 이 샘플에서는 제공된 / 엔드포인트에 표시 이름을 할당합니다.

위의 샘플에는 파이프라인 내에서 이러한 미들웨어가 실행되는 시기를 정확하게 제어하기 위한 UseRoutingUseEndpoints 호출도 포함되어 있습니다.

/의 URL을 사용하여 이 코드를 실행하면 다음이 표시됩니다.

1. Endpoint: (null)
2. Endpoint: Hello
3. Endpoint: Hello

다른 URL을 사용하여 이 코드를 실행하면 다음이 표시됩니다.

1. Endpoint: (null)
2. Endpoint: (null)
4. Endpoint: (null)

이 출력은 다음을 나타냅니다.

  • UseRouting을 호출하기 전에 엔드포인트는 항상 null입니다.
  • 일치 항목이 발견되면 UseRoutingUseEndpoints사이에서 엔드포인트가 null이 아닙니다.
  • 일치 항목이 발견되면 UseEndpoints 미들웨어가 터미널입니다. 터미널 미들웨어는 이 문서의 뒷부분에 정의되어 있습니다.
  • UseEndpoints 뒤의 미들웨어는 일치 항목이 없는 경우에만 실행됩니다.

미들웨어는 UseRouting 이 메서드를 SetEndpoint 사용하여 엔드포인트를 현재 컨텍스트에 연결합니다. UseRouting 미들웨어를 사용자 지정 논리로 바꾸어도 엔드포인트를 사용하는 이점을 얻을 수 있습니다. 엔드포인트는 미들웨어와 같은 하위 수준 기본 형식이며 라우팅 구현에 결합되지 않습니다. 대부분의 앱에서는 UseRouting을 사용자 지정 논리로 바꿀 필요가 없습니다.

UseEndpoints 미들웨어는 UseRouting 미들웨어와 함께 사용하기 위한 것입니다. 엔드포인트를 실행하는 핵심 논리는 복잡하지 않습니다. GetEndpoint를 사용하여 엔드포인트를 검색한 다음 해당 RequestDelegate 속성을 호출하면 됩니다.

다음 코드에서는 미들웨어가 라우팅에 영향을 주거나 반응하는 방식을 보여 줍니다.

app.UseHttpMethodOverride();
app.UseRouting();

app.Use(async (context, next) =>
{
    if (context.GetEndpoint()?.Metadata.GetMetadata<RequiresAuditAttribute>() is not null)
    {
        Console.WriteLine($"ACCESS TO SENSITIVE DATA AT: {DateTime.UtcNow}");
    }

    await next(context);
});

app.MapGet("/", () => "Audit isn't required.");
app.MapGet("/sensitive", () => "Audit required for sensitive data.")
    .WithMetadata(new RequiresAuditAttribute());
public class RequiresAuditAttribute : Attribute { }

앞의 예제에서는 다음과 같은 두 가지 중요한 개념을 보여 줍니다.

  • 미들웨어는 UseRouting 전에 실행되어 라우팅 작동의 기반이 되는 데이터를 수정할 수 있습니다.
  • 미들웨어는 UseRoutingUseEndpoints 사이에서 실행되어 엔드포인트가 실행되기 전에 라우팅의 결과를 처리할 수 있습니다.
    • 다음 사이에 UseRouting 실행되는 미들웨어:UseEndpoints
      • 일반적으로 메타데이터를 검사하여 엔드포인트를 이해합니다.
      • UseAuthorizationUseCors에서 하는 것처럼 보안 결정을 내리는 경우가 많습니다.
    • 미들웨어와 메타데이터를 조합하면 엔드포인트별로 정책을 구성할 수 있습니다.

위의 코드에서는 엔드포인트별 정책을 지원하는 사용자 지정 미들웨어의 예를 보여 줍니다. 이 미들웨어는 중요한 데이터에 대한 액세스의 ‘감사 로그’를 콘솔에 기록합니다. RequiresAuditAttribute 메타데이터를 사용하여 엔드포인트를 ‘감사’하도록 미들웨어를 구성할 수 있습니다. 이 샘플에서는 중요함으로 표시된 엔드포인트만 감사되는 ‘옵트인 패턴’을 보여 줍니다. 예를 들어 이 논리를 역으로 정의하여 안전한 것으로 표시되지 않은 모든 항목을 감사할 수 있습니다. 엔드포인트 메타데이터 시스템은 유연합니다. 이 논리는 사용 사례에 적합한 방식으로 설계할 수 있습니다.

앞의 샘플 코드는 엔드포인트의 기본 개념을 보여 주기 위한 것입니다. 프로덕션 용도로는 사용하지 않아야 합니다. ‘감사 로그’ 미들웨어의 전체 버전은 다음과 같습니다.

  • 파일이나 데이터베이스에 기록합니다.
  • 사용자, IP 주소, 중요한 엔드포인트의 이름 등과 같은 세부 정보를 포함합니다.

감사 정책 메타데이터 RequiresAuditAttribute는 컨트롤러 및 SignalR 같은 클래스 기반 프레임워크에서 더욱 쉽게 사용할 수 있도록 Attribute로 정의됩니다. ‘라우팅 대상 코드’를 사용하면 다음과 같이.

  • 메타데이터가 작성기 API와 연결됩니다.
  • 엔드포인트를 만들 때 해당 메서드 및 클래스의 모든 특성이 클래스 기반 프레임워크에 포함됩니다.

메타데이터 형식은 인터페이스나 특성으로 정의하는 것이 가장 좋습니다. 인터페이스 및 특성을 사용하면 코드를 다시 사용할 수 있습니다. 메타데이터 시스템은 유연하며 제한을 적용하지 않습니다.

터미널 미들웨어와 라우팅 비교

다음 예제에서는 터미널 미들웨어와 라우팅을 모두 보여줍니다.

// Approach 1: Terminal Middleware.
app.Use(async (context, next) =>
{
    if (context.Request.Path == "/")
    {
        await context.Response.WriteAsync("Terminal Middleware.");
        return;
    }

    await next(context);
});

app.UseRouting();

// Approach 2: Routing.
app.MapGet("/Routing", () => "Routing.");

Approach 1:로 표시된 미들웨어 스타일은 터미널 미들웨어입니다. 일치 작업을 수행하기 때문에 터미널 미들웨어라고 합니다.

  • 위 샘플의 일치 작업은 미들웨어의 Path == "/"와 라우팅의 Path == "/Routing"입니다.
  • 일치가 성공하면 일부 기능을 실행하고 next 미들웨어를 호출하는 대신 반환합니다.

검색을 종료하고 일부 기능을 실행한 다음 반환하기 때문에 터미널 미들웨어라고 합니다.

다음 목록에서는 터미널 미들웨어와 라우팅을 비교합니다.

  • 두 방법 모두 처리 파이프라인을 종료할 수 있습니다.
    • 미들웨어는 next를 호출하는 대신 반환하여 파이프라인을 종료합니다.
    • 엔드포인트는 항상 터미널입니다.
  • 터미널 미들웨어를 사용하면 파이프라인의 임의 위치에 미들웨어를 배치할 수 있습니다.
    • 엔드포인트는 UseEndpoints의 위치에서 실행됩니다.
  • 터미널 미들웨어를 사용하면 임의의 코드에서 미들웨어가 일치하는 시기를 확인할 수 있습니다.
    • 사용자 지정 경로 일치 코드는 길어져서 올바르게 작성하기 어려울 수 있습니다.
    • 라우팅은 일반적인 앱을 위한 간단한 솔루션을 제공합니다. 앱 대부분에는 사용자 지정 경로 일치 코드가 필요하지 않습니다.
  • 엔드포인트는 UseAuthorizationUseCors 같은 미들웨어와 상호 작용합니다.
    • UseAuthorization 또는 UseCors와 함께 터미널 미들웨어를 사용하려면 권한 부여 시스템을 수동으로 조작해야 합니다.

엔드포인트는 다음 두 가지를 모두 정의합니다.

  • 요청을 처리할 대리자
  • 임의 메타데이터의 컬렉션. 메타데이터는 각 엔드포인트에 연결된 정책과 구성에 따라 횡단 관심사(Cross-Cutting Concerns)를 구현하는 데 사용됩니다.

터미널 미들웨어는 효과적인 도구이지만 다음이 필요할 수 있습니다.

  • 상당한 양의 코딩과 테스트
  • 원하는 수준의 유연성을 얻기 위한 다른 시스템과의 수동 통합

터미널 미들웨어를 작성하기 전에 라우팅과 통합하는 것이 좋습니다.

또는MapWhen과 통합되는 기존 터미널 미들웨어는 일반적으로 라우팅 인식 엔드포인트로 전환될 수 있습니다. MapHealthChecks는 다음과 같은 라우터 방식의 패턴을 보여 줍니다.

  • IEndpointRouteBuilder에 대한 확장 메서드를 작성합니다.
  • CreateApplicationBuilder를 사용하여 중첩된 미들웨어 파이프라인을 만듭니다.
  • 새 파이프라인에 미들웨어를 연결합니다. 이 경우, UseHealthChecks입니다.
  • 미들웨어 파이프라인을 RequestDelegateBuild합니다.
  • Map을 호출하고 새 미들웨어 파이프라인을 제공합니다.
  • 확장 메서드의 Map에서 제공하는 작성기 개체를 반환합니다.

다음 코드에서는 MapHealthChecks를 사용하는 방법을 보여 줍니다.

app.UseAuthentication();
app.UseAuthorization();

app.MapHealthChecks("/healthz").RequireAuthorization();

앞의 샘플에서는 작성기 개체를 반환하는 것이 중요한 이유를 보여 줍니다. 작성기 개체를 반환하면 앱 개발자가 엔드포인트의 권한 부여와 같은 정책을 구성할 수 있습니다. 이 예제에서는 상태 검사 미들웨어가 권한 부여 시스템과 직접 통합되지 않습니다.

메타데이터 시스템은 터미널 미들웨어를 사용하는 확장성 작성자에서 발생한 문제에 대응하여 만들어졌습니다. 미들웨어마다 권한 부여 시스템과의 고유한 통합을 구현하는 것은 문제가 있습니다.

URL 일치

  • 라우팅이 들어오는 요청을 엔드포인트와 일치시키는 프로세스입니다.
  • URL 경로 및 헤더의 데이터를 기반으로 합니다.
  • 요청의 모든 데이터를 고려하도록 확장될 수 있습니다.

라우팅 미들웨어가 실행되면 Endpoint를 설정하여 현재 요청에서 HttpContext요청 기능으로 값을 라우팅합니다.

  • HttpContext.GetEndpoint를 호출하면 엔드포인트를 가져옵니다.
  • HttpRequest.RouteValues는 경로 값의 컬렉션을 가져옵니다.

미들웨어 는 라우팅 미들웨어가 엔드포인트를 검사하고 작업을 수행할 수 있는 후에 실행됩니다. 예를 들어 권한 부여 미들웨어는 엔드포인트의 메타데이터 컬렉션에서 권한 부여 정책을 조사할 수 있습니다. 요청 처리 파이프라인의 미들웨어가 모두 실행된 후에 선택한 엔드포인트의 대리자가 호출됩니다.

엔드포인트 라우팅의 라우팅 시스템은 모든 디스패치를 결정합니다. 미들웨어는 선택된 엔드포인트에 기반으로 하여 정책을 적용하므로 다음이 중요합니다.

  • 디스패치나 보안 정책의 애플리케이션에 영향을 줄 수 있는 모든 결정은 라우팅 시스템 내에서 내려야 합니다.

Warning

이전 버전과의 호환성을 위해 컨트롤러 또는 Razor Pages 엔드포인트 대리자를 실행할 때 속성 RouteContext.RouteData 은 지금까지 수행된 요청 처리에 따라 적절한 값으로 설정됩니다.

RouteContext 형식은 이후 릴리스에서 obsolete로 표시됩니다.

  • RouteData.ValuesHttpRequest.RouteValues로 마이그레이션합니다.
  • 엔드포인트 메타데이터에서 검색 IDataTokensMetadata 하도록 마이그레이션 RouteData.DataTokens 합니다.

URL 일치는 구성 가능한 일련의 단계로 작동합니다. 각 단계의 출력은 일치 항목 집합입니다. 일치 항목 집합은 다음 단계에서 더욱 좁혀질 수 있습니다. 라우팅 구현에서는 일치하는 엔드포인트의 처리 순서를 보장하지 않습니다. 기능한 모든 일치 항목이 한 번에 처리됩니다. URL 일치 단계는 다음 순서로 수행됩니다. ASP.NET Core:

  1. 엔드포인트와 해당 경로 템플릿 집합에 대한 URL 경로를 처리하여 일치 항목을 모두 수집합니다.
  2. 앞의 목록을 사용하여 경로 제약 조건이 적용되지 않는 일치 항목을 제거합니다.
  3. 앞의 목록을 가져와 인스턴스 집합 MatcherPolicy 에 실패한 일치 항목을 제거합니다.
  4. EndpointSelector 값을 사용하여 이전 목록에서 최종 결정을 내립니다.

엔드포인트 목록의 우선 순위는 다음에 따라 지정됩니다.

EndpointSelector에 도달할 때까지 각 단계에서 일치하는 모든 엔드포인트가 처리됩니다. EndpointSelector는 최종 단계이며, 일치 항목에서 가장 높은 우선 순위 엔드포인트를 가장 일치하는 항목으로 선택합니다. 가장 일치하는 항목과 같은 우선 순위의 다른 일치 항목이 있으면 모호한 일치 예외가 throw됩니다.

경로 우선 순위는 더 구체적인 경로 템플릿에 높은 우선 순위가 지정되는 기준에 따라 컴퓨팅됩니다. 예를 들어 /hello/{message} 템플릿을 가정해 보겠습니다.

  • 둘 다 URL 경로 /hello와 일치합니다.
  • /hello가 더 구체적이므로 우선 순위가 높습니다.

일반적으로 경로 우선 순위는 실제로 사용되는 URL 체계에 가장 일치하는 항목을 선택하는 데 좋습니다. 모호성을 방지하는 데 필요한 경우에만 Order를 사용합니다.

라우팅에서 제공하는 확장성의 종류 때문에 라우팅 시스템이 모호한 경로를 미리 컴퓨팅할 수는 없습니다. 경로 템플릿 /{message:alpha}/{message:int}와 같은 예제를 살펴보겠습니다.

  • alpha 제약 조건은 영문자와만 일치합니다.
  • int 제약 조건은 숫자와만 일치합니다.
  • 이러한 템플릿은 경로 우선 순위가 동일하지만 둘 다와 일치하는 단일 URL은 없습니다.
  • 라우팅 시스템에서 시작 시 모호성 오류를 보고한 경우 이 유효한 사용 사례를 차단합니다.

Warning

UseEndpoints 내의 작업 순서는 라우팅 동작에 영향을 주지 않지만 한 가지 예외가 있습니다. MapControllerRouteMapAreaRoute는 호출된 순서를 기준으로 해당 엔드포인트에 순서 값을 자동으로 할당합니다. 이는 이전 라우팅 구현과 동일한 보장을 제공하는 라우팅 시스템이 없이 컨트롤러의 오래된 동작을 시뮬레이트합니다.

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

  • 경로 개념이 없습니다.
  • 순서 지정을 보장하지 않습니다. 모든 엔드포인트가 한 번에 처리됩니다.

경로 템플릿 우선 순위 및 엔드포인트 선택 영역 순서

경로 템플릿 우선 순위는 얼마나 구체적인지를 기준으로 각 경로 템플릿에 값을 할당하는 시스템입니다. 경로 템플릿 우선 순위의 특징은 다음과 같습니다.

  • 일반적인 사례에서 엔드포인트 순서를 조정할 필요가 없게 합니다.
  • 라우팅 동작에 관한 일반적인 기대에 맞추려고 합니다.

예를 들어 /Products/List/Products/{id} 템플릿을 가정해 보겠습니다. URL 경로 /Products/List에 대해 /Products/List/Products/{id}보다 더 잘 일치한다고 합리적으로 가정할 수 있습니다. 리터럴 세그먼트 /List가 매개 변수 세그먼트 /{id}보다 우선 순위가 더 높다고 간주되기 때문입니다.

우선 순위의 작동 방식에 대한 세부 정보는 경로 템플릿이 정의된 방법과 어느 정도 관련이 있습니다.

  • 세그먼트가 더 많은 템플릿은 더 구체적인 것으로 간주됩니다.
  • 리터럴 텍스트가 있는 세그먼트가 매개 변수 세그먼트보다 더 구체적인 것으로 간주됩니다.
  • 제약 조건이 있는 매개 변수 세그먼트가 제약 조건이 없는 매개 변수 세그먼트보다 더 구체적인 것으로 간주됩니다.
  • 복잡한 세그먼트는 제약 조건이 있는 매개 변수 세그먼트만큼 구체적인 것으로 간주됩니다.
  • Catch-all 매개 변수가 가장 덜 구체적입니다. Catch-all 경로에 관한 중요한 내용은 경로 템플릿 섹션에서 catch-all을 참조하세요.

URL 생성 개념

URL 생성은 다음과 같습니다.

  • 라우팅이 경로 값의 집합을 기반으로 하는 URL 경로를 만들 수 있는 프로세스입니다.
  • 엔드포인트와 이에 액세스하는 URL을 논리적으로 분리할 수 있습니다.

엔드포인트 라우팅에는 LinkGenerator API가 포함됩니다. LinkGeneratorDI에서 사용할 수 있는 싱글톤 서비스입니다. LinkGenerator API는 실행 중인 요청의 컨텍스트 외부에서 사용할 수 있습니다. Mvc.IUrlHelperIUrlHelper를 사용하는 시나리오(예: 태그 도우미, HTML 도우미 및 작업 결과)는 내부적으로 LinkGenerator API를 사용하여 링크 생성 기능을 제공합니다.

링크 생성기는 주소주소 체계의 개념으로 지원됩니다. 주소 체계는 링크 생성을 위해 고려해야 할 엔드포인트를 결정하는 방법입니다. 예를 들어 컨트롤러 및 Razor Pages에서 많은 사용자에게 친숙한 경로 이름 및 경로 값 시나리오는 주소 체계로 구현됩니다.

링크 생성기는 다음 확장 메서드를 통해 컨트롤러 및 Razor Pages에 연결할 수 있습니다.

이러한 메서드의 오버로드에는 HttpContext를 포함한 인수가 허용됩니다. 이러한 메서드는 기능적으로 Url.ActionUrl.Page와 동일하지만, 추가적인 유연성과 옵션을 제공합니다.

GetPath* 메서드는 절대 경로가 포함된 URI를 생성한다는 점에서 Url.ActionUrl.Page와 가장 비슷합니다. GetUri* 메서드는 항상 체계와 호스트를 포함한 절대 URI를 생성합니다. HttpContext를 허용하는 메서드는 실행 중인 요청의 컨텍스트에서 URI를 생성합니다. 재정의되지 않는 한 실행 중인 요청의 앰비언트 경로 값, URL 기본 경로, 체계 및 호스트가 사용됩니다.

LinkGenerator는 주소를 사용하여 호출됩니다. URI 생성은 다음 두 단계로 수행됩니다.

  1. 주소는 해당 주소와 일치하는 엔드포인트 목록에 바인딩됩니다.
  2. 제공된 값과 일치하는 경로 패턴을 찾을 때까지 각 엔드포인트의 RoutePattern이 평가됩니다. 결과 출력은 링크 생성기에 제공된 다른 URI 부분과 결합되어 반환됩니다.

LinkGenerator에서 제공하는 메서드는 모든 유형의 주소에 대해 표준 링크 생성 기능을 지원합니다. 링크 생성기를 사용하는 가장 편리한 방법은 특정 주소 유형에 대한 작업을 수행하는 확장 메서드를 사용하는 것입니다.

확장 메서드 설명
GetPathByAddress 제공된 값에 기반한 절대 경로가 있는 URI를 생성합니다.
GetUriByAddress 제공된 값에 기반한 절대 URI를 생성합니다.

Warning

LinkGenerator 메서드 호출 시 다음과 같은 의미에 주의하세요.

  • 들어오는 요청의 GetUri* 헤더의 유효성을 검사하지 않는 앱 구성에서는 Host 확장 메서드를 신중하게 사용하세요. 들어오는 요청의 Host 헤더의 유효성을 검사하지 않으면 신뢰할 수 없는 요청 입력이 보기 또는 페이지에 포함된 URI로 클라이언트에 다시 보내질 수 있습니다. 모든 프로덕션 앱은 알려진 유효한 값에 대해 Host 헤더의 유효성을 검사하도록 서버를 구성하는 것이 좋습니다.

  • 미들웨어에서 MapWhen 또는 LinkGenerator과 함께 Map를 사용할 때는 신중하게 사용하세요. Map*는 실행 중인 요청의 기본 경로를 변경하여 링크 생성의 출력에 영향을 줍니다. 모든 LinkGenerator API는 기본 경로를 지정할 수 있습니다. 링크 생성에 대한 Map*의 영향을 실행 취소하려면 빈 기본 경로를 지정합니다.

미들웨어 예제

다음 예제에서는 미들웨어에서 LinkGenerator API를 사용하여 상점 제품을 나열하는 작업 메서드에 대한 링크를 만듭니다. 링크 생성기를 클래스에 주입하고 GenerateLink를 호출하여 앱의 모든 클래스에서 해당 링크 생성기를 사용할 수 있습니다.

public class ProductsMiddleware
{
    private readonly LinkGenerator _linkGenerator;

    public ProductsMiddleware(RequestDelegate next, LinkGenerator linkGenerator) =>
        _linkGenerator = linkGenerator;

    public async Task InvokeAsync(HttpContext httpContext)
    {
        httpContext.Response.ContentType = MediaTypeNames.Text.Plain;

        var productsPath = _linkGenerator.GetPathByAction("Products", "Store");

        await httpContext.Response.WriteAsync(
            $"Go to {productsPath} to see our products.");
    }
}

경로 템플릿

{} 내의 토큰은 경로가 일치하는 경우 바인딩될 경로 매개 변수를 정의합니다. 경로 세그먼트에 둘 이상의 경로 매개 변수를 정의할 수 있지만 경로 매개 변수를 리터럴 값으로 분리해야 합니다. 예시:

{controller=Home}{action=Index}

는 리터럴 값이 {controller}{action}없기 때문에 유효한 경로가 아닙니다. 경로 매개 변수는 이름이 있어야 하며 지정된 추가 특성을 가질 수 있습니다.

경로 매개 변수 이외의 리터럴 텍스트(예: {id}) 및 경로 구분 기호(/)는 URL의 텍스트와 일치해야 합니다. 텍스트 일치는 대/소문자를 구분하지 않으며 URL 경로의 디코딩된 표현을 기반으로 합니다. 리터럴 경로 매개 변수 구분 기호 { 또는 }와 일치시키려면 문자를 반복하여(예: {{ 또는 }}) 구분 기호를 이스케이프합니다.

별표 * 또는 이중 별표 **:

  • URI의 나머지 부분에 바인딩하기 위해 경로 매개 변수의 접두사로 사용할 수 있습니다.
  • 범용 매개 변수라고 합니다. 예를 들면 다음과 같습니다. blog/{**slug}
    • blog/로 시작하고 그 다음에 임의의 값이 오는 모든 URI를 찾습니다.
    • blog/ 다음의 값은 동적 필드 경로 값에 할당됩니다.

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.

범용 매개 변수는 빈 문자열과 일치시킬 수도 있습니다.

범용 매개 변수는 경로 구분 기호 / 문자를 포함하여 URL을 생성하는 데 경로가 사용될 때 적절한 문자를 이스케이프합니다. 예를 들어 경로 값이 { path = "my/path" }인 경로 foo/{*path}foo/my%2Fpath를 생성합니다. 이스케이프된 슬래시에 주의하세요. 경로 구분 기호 문자를 왕복하려면 ** 경로 매개 변수 접두사를 사용합니다. { path = "my/path" }가 있는 경로 foo/{**path}foo/my/path를 생성합니다.

선택적 파일 확장명이 있는 파일 이름을 캡처하려고 시도하는 URL 패턴에는 추가 고려 사항이 있습니다. 예를 들어 템플릿 files/{filename}.{ext?}를 가정해 보겠습니다. filenameext 모두에 대한 값이 있으면 두 값이 채워집니다. URL에 filename에 대한 값만 있으면 후행 .가 선택 사항이므로 경로가 일치합니다. 다음 URL은 이 경로와 일치합니다.

  • /files/myFile.txt
  • /files/myFile

경로 매개 변수에는 등호(=)로 구분된 매개 변수 이름 뒤에 기본값을 지정하여 지정된 기본값이 있을 수 있습니다. 예를 들어 {controller=Home}controller에 대한 기본값으로 Home을 정의합니다. URL에 매개 변수에 대한 값이 없는 경우 기본값이 사용됩니다. 경로 매개 변수는 매개 변수 이름의 끝에 물음표(?)를 추가하면 선택적이 됩니다. 예들 들어 id?입니다. 선택적 값과 기본 경로 매개 변수의 차이는 다음과 같습니다.

  • 기본값이 있는 경로 매개 변수는 항상 값을 생성합니다.
  • 선택적 매개 변수는 요청 URL에서 값을 제공한 경우에만 값이 있습니다.

경로 매개 변수에는 URL에서 바인딩된 경로 값과 일치해야 한다는 제약 조건이 있을 수 있습니다. 경로 매개 변수 이름 뒤에 :과 제약 조건 이름을 추가하여 경로 매개 변수에서 인라인 제약 조건을 지정합니다. 제약 조건에 인수가 필요한 경우 제약 조건 이름 뒤에서 괄호 (...)로 묶입니다. 또 다른 : 및 제약 조건 이름을 추가하여 여러 ‘인라인 제약 조건’을 지정할 수 있습니다.

제약 조건 이름 및 인수는 IRouteConstraint의 인스턴스를 만드는 IInlineConstraintResolver 서비스로 전달되어 URL 처리에서 사용합니다. 예를 들어 경로 템플릿 blog/{article:minlength(10)}는 인수 10으로 minlength 제약 조건을 지정합니다. 경로 제약 조건 및 프레임워크에서 제공하는 제약 조건 목록에 대한 자세한 내용은 경로 제약 조건 섹션을 참조하세요.

경로 매개 변수에는 매개 변수 변환기가 있을 수도 있습니다. 매개 변수 변환기는 링크를 생성하고 URL에 대한 작업 및 페이지와 일치할 때 매개 변수 값을 변환합니다. 제약 조건과 마찬가지로, 매개 변수 변환기는 경로 매개 변수 이름 뒤에 :과 변환기 이름을 추가하여 경로 매개 변수에 인라인으로 추가될 수 있습니다. 예를 들어 경로 템플릿 blog/{article:slugify}slugify 변환기를 지정합니다. 매개 변수 변환기에 대한 자세한 내용은 매개 변수 변환기 섹션을 참조하세요.

다음 표에서는 경로 템플릿 예제 및 해당 동작을 보여 줍니다.

경로 템플릿 URI 일치 예제 요청 URI…
hello /hello /hello 단일 경로만 일치합니다.
{Page=Home} / 일치하고, PageHome으로 설정합니다.
{Page=Home} /Contact 일치하고, PageContact으로 설정합니다.
{controller}/{action}/{id?} /Products/List Products 컨트롤러 및 List 작업에 매핑합니다.
{controller}/{action}/{id?} /Products/Details/123 Products 컨트롤러 및 Details 작업에 매핑합니다(id가 123으로 설정됨).
{controller=Home}/{action=Index}/{id?} / Home 컨트롤러 및 Index 메서드에 매핑합니다. id는 무시됩니다.
{controller=Home}/{action=Index}/{id?} /Products Products 컨트롤러 및 Index 메서드에 매핑합니다. id는 무시됩니다.

템플릿 사용은 일반적으로 라우팅에 대한 가장 간단한 방식입니다. 제약 조건 및 기본값을 경로 템플릿 외부에서 지정할 수도 있습니다.

복잡한 세그먼트

복잡한 세그먼트는 non-greedy 방식으로 오른쪽에서 왼쪽으로 리터럴 구분 기호를 매칭하여 처리됩니다. 예를 들어 [Route("/a{b}c{d}")]는 복잡한 세그먼트입니다. 복잡한 세그먼트는 특정 방식으로 작동하므로 제대로 사용하려면 이 방식을 이해해야 합니다. 이 단원의 예제에서는 매개 변수 내에 구분 기호 텍스트가 표시되지 않는 경우에만 복잡한 세그먼트가 실제로 잘 작동하는 이유를 보여 줍니다. 더욱 복잡한 경우에는 regex를 사용한 다음 값을 수동으로 추출해야 합니다.

Warning

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

다음은 라우팅에서 /a{b}c{d} 템플릿 및 URL 경로 /abcd를 사용하여 수행하는 단계를 요약한 것입니다. |는 알고리즘의 작동 방식을 시각화하는 데 사용됩니다.

  • 오른쪽에서 왼쪽으로 첫 번째 리터럴은 c입니다. 따라서 /abcd가 오른쪽에서 검색되고 /ab|c|d를 찾습니다.
  • 이제 오른쪽의 모든 항목(d)이 경로 매개 변수 {d}와 일치합니다.
  • 오른쪽에서 왼쪽으로 다음 리터럴은 a입니다. 따라서 /ab|c|d가 중단된 위치부터 검색되고 a/|a|b|c|d와 함께 찾습니다.
  • 이제 오른쪽 값(b)이 경로 매개 변수 {b}와 일치합니다.
  • 남은 텍스트가 없고 남은 경로 템플릿도 없으므로 이것이 일치 항목입니다.

다음은 동일한 템플릿 /a{b}c{d}와 URL 경로 /aabcd를 사용하는 부정적인 사례의 예제입니다. |는 알고리즘의 작동 방식을 시각화하는 데 사용됩니다. 이 경우는 동일한 알고리즘으로 설명되는 일치 항목이 아닙니다.

  • 오른쪽에서 왼쪽으로 첫 번째 리터럴은 c입니다. 따라서 /aabcd가 오른쪽에서 검색되고 /aab|c|d를 찾습니다.
  • 이제 오른쪽의 모든 항목(d)이 경로 매개 변수 {d}와 일치합니다.
  • 오른쪽에서 왼쪽으로 다음 리터럴은 a입니다. 따라서 /aab|c|d가 중단된 위치부터 검색되고 a/a|a|b|c|d와 함께 찾습니다.
  • 이제 오른쪽 값(b)이 경로 매개 변수 {b}와 일치합니다.
  • 이제 남은 텍스트 a가 있지만 알고리즘에서 구문 분석할 경로 템플릿이 부족하므로 이것은 일치 항목이 아닙니다.

일치 알고리즘이 non-greedy이므로 다음과 같습니다.

  • 각 단계에서 가능한 최소의 텍스트와 일치합니다.
  • 매개 변수 값 내에 구분 기호 값이 표시되는 모든 경우에는 일치하지 않게 됩니다.

정규식을 사용하면 일치 동작을 더 효율적으로 제어할 수 있습니다.

지연 일치라고도 하는 greedy 일치는 가능한 가장 큰 문자열과 일치합니다. Non-greedy는 가능한 가장 작은 문자열과 일치합니다.

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

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

[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 경로가 경로 값으로 토큰화되면 실행됩니다. 일반적으로 경로 제약 조건은 경로 템플릿을 통해 연결된 경로 값을 검사하고 값 허용 여부에 대한 true 또는 false 결정을 내립니다. 일부 경로 제약 조건은 경로 값 외부의 데이터를 사용하여 요청을 라우팅할 수 있는지 여부를 고려합니다. 예를 들어 HttpMethodRouteConstraint는 해당 HTTP 동사에 따라 요청을 허용하거나 거부할 수 있습니다. 제약 조건은 라우팅 요청 및 링크 생성에 사용됩니다.

Warning

제약 조건을 입력 유효성 검사에 사용하지 마세요. 입력 유효성 검사에 제약 조건을 사용하면 잘못된 입력으로 인해 404 찾을 수 없음 응답이 반환됩니다. 입력이 잘못되면 400 잘못된 요청과 해당하는 오류 메시지가 생성됩니다. 경로 제약 조건은 특정 경로에 대한 입력의 유효성을 검사하는 것이 아니라 비슷한 경로를 명확하게 구분하는 데 사용됩니다.

다음 표에서는 경로 제약 조건 예제 및 예상되는 해당 동작을 보여 줍니다.

제약 조건 예제 일치하는 예제 주의
int {id:int} 123456789, -123456789 임의의 정수와 일치
bool {active:bool} true, FALSE true 또는 false와 일치. 대/소문자 구분 안 함
datetime {dob:datetime} 2016-12-31, 2016-12-31 7:32pm 유효한 DateTime 값 일치(고정 문화권에서) 이전 경고를 참조하세요.
decimal {price:decimal} 49.99, -1,000.01 유효한 decimal 값 일치(고정 문화권에서) 이전 경고를 참조하세요.
double {weight:double} 1.234, -1,001.01e8 유효한 double 값 일치(고정 문화권에서) 이전 경고를 참조하세요.
float {weight:float} 1.234, -1,001.01e8 유효한 float 값 일치(고정 문화권에서) 이전 경고를 참조하세요.
guid {id:guid} CD2C1638-1638-72D5-1638-DEADBEEF1638 유효한 Guid 값 일치
long {ticks:long} 123456789, -123456789 유효한 long 값 일치
minlength(value) {username:minlength(4)} Rick 문자열은 4자 이상이어야 합니다.
maxlength(value) {filename:maxlength(8)} MyFile 문자열은 8자 이하여야 합니다.
length(length) {filename:length(12)} somefile.txt 문자열은 정확히 12자여야 합니다.
length(min,max) {filename:length(8,16)} somefile.txt 문자열의 길이는 8자 이상이며 16자 이하여야 합니다.
min(value) {age:min(18)} 19 정수 값은 18 이상이어야 합니다.
max(value) {age:max(120)} 91 정수 값은 120 이하여야 합니다.
range(min,max) {age:range(18,120)} 91 정수 값은 18 이상이며 120 이하여야 합니다.
alpha {name:alpha} Rick 문자열은 하나 이상의 영문자(a-z, 대/소문자 구분)로 구성되어야 합니다.
regex(expression) {ssn:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)} 123-45-6789 문자열은 정규식과 일치해야 합니다. 정규식을 정의하는 방법에 대한 팁을 참조하세요.
required {name:required} Rick URL을 생성하는 동안 비-매개 변수 값이 존재하도록 강제하는 데 사용됨

Warning

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

콜론으로 구분된 여러 개의 제약 조건을 단일 매개 변수에 적용할 수 있습니다. 예를 들어 다음 제약 조건은 매개 변수를 1 이상의 정수 값으로 제한합니다.

[Route("users/{id:int:min(1)}")]
public User GetUserById(int id) { }

Warning

CLR 형식으로 변환되는 URL을 확인하는 경로 제약 조건은 항상 고정 문화권을 사용합니다(예: CLR 형식 int 또는 DateTime으로 변환). 이러한 제약 조건은 URL은 지역화될 수 없다고 가정합니다. 프레임워크에서 제공한 경로 제약 조건은 경로 값에 저장된 값을 수정하지 않습니다. URL에서 구문 분석되는 모든 경로 값은 문자열로 저장됩니다. 예를 들어 float 제약 조건은 경로 값을 부동으로 변환하려고 하지만 변환된 값은 부동으로 변환될 수 있는지 확인하는 데만 사용됩니다.

제약 조건의 정규식

Warning

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

regex(...) 경로 제약 조건을 사용하여 정규식을 인라인 제약 조건으로 지정할 수 있습니다. MapControllerRoute 제품군의 메서드에서는 개체 리터럴의 제약 조건도 사용할 수 있습니다. 해당 양식을 사용하는 경우 문자열 값이 정규식으로 해석됩니다.

다음 코드에서는 인라인 regex 제약 조건을 사용합니다.

app.MapGet("{message:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)}",
    () => "Inline Regex Constraint Matched");

다음 코드에서는 개체 리터럴을 사용하여 regex 제약 조건을 지정합니다.

app.MapControllerRoute(
    name: "people",
    pattern: "people/{ssn}",
    constraints: new { ssn = "^\\d{3}-\\d{2}-\\d{4}$", },
    defaults: new { controller = "People", action = "List" });

ASP.NET Core 프레임워크는 정규식 생성자에 RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariant를 추가합니다. 이러한 멤버에 대한 설명은 RegexOptions를 참조하세요.

정규식은 라우팅 및 C# 언어에서 사용하는 것과 유사한 구분 기호 및 토큰을 사용합니다. 정규식 토큰은 이스케이프되어야 합니다. 인라인 제약 조건에서 정규식 ^\d{3}-\d{2}-\d{4}$를 사용하려면 다음 중 하나를 사용합니다.

  • \ 문자열 이스케이프 문자를 이스케이프하기 위해 C# 소스 파일에서 문자열에 제공된 \ 문자를 \\ 문자로 바꿉니다.
  • 축자 문자열 리터럴.

라우팅 매개 변수 구분 기호 문자({, }, [, ])를 이스케이프하려면 식에서 해당 문자를 이중으로 사용합니다(예: {{, }}, [[, ]]). 다음 표에서는 정규식 및 이스케이프된 버전을 보여 줍니다.

정규식 이스케이프된 정규식
^\d{3}-\d{2}-\d{4}$ ^\\d{{3}}-\\d{{2}}-\\d{{4}}$
^[a-z]{2}$ ^[[a-z]]{{2}}$

라우팅에 사용되는 정규식은 ^ 문자로 시작하고 문자열의 시작 위치와 일치하는 경우가 많습니다. 식은 $ 문자로 끝나고 문자열의 끝과 일치하는 경우가 많습니다. ^$ 문자는 정규식이 전체 경로 매개 변수 값과 일치하도록 합니다. ^$ 문자가 없는 정규식은 문자열 내의 모든 하위 문자열과 일치하지만, 이는 종종 원하는 것이 아닙니다. 다음 표에서는 예제를 제공하고, 일치하거나 일치에 실패하는 이유를 설명합니다.

문자열 Match Comment(설명)
[a-z]{2} hello 부분 문자열 일치
[a-z]{2} 123abc456 부분 문자열 일치
[a-z]{2} mz 식 일치
[a-z]{2} MZ 대/소문자 구분하지 않음
^[a-z]{2}$ hello 아니요 위의 ^$ 참조
^[a-z]{2}$ 123abc456 아니요 위의 ^$ 참조

정규식 구문에 대한 자세한 내용은 .NET Framework 정규식을 참조하세요.

가능한 값의 알려진 집합으로 매개 변수를 제한하려면 정규식을 사용합니다. 예를 들어 {action:regex(^(list|get|create)$)}action 경로 값을 list, get 또는 create으로만 일치시킵니다. 제약 조건 사전으로 전달되면 ^(list|get|create)$ 문자열은 동일합니다. 알려진 제약 조건 중 하나와 일치하지 않는 제약 조건 사전에서 전달되는 제약 조건도 정규식으로 처리됩니다. 알려진 제약 조건 중 하나와 일치하지 않는 템플릿 내에서 전달되는 제약 조건은 정규식으로 처리되지 않습니다.

사용자 지정 경로 제약 조건

IRouteConstraint 인터페이스를 구현하여 사용자 지정 경로 제약 조건을 만들 수 있습니다. IRouteConstraint 인터페이스에는 제약 조건이 충족되는 경우 true를 반환하고 그렇지 않은 경우 false를 반환하는 Match가 포함됩니다.

사용자 지정 경로 제약 조건은 거의 필요하지 않습니다. 사용자 지정 경로 제약 조건을 구현하기 전에 모델 바인딩과 같은 다른 방식을 고려해 보세요.

ASP.NET Core Constraints 폴더는 제약 조건을 만드는 좋은 예제를 제공합니다. 예를 들어 GuidRouteConstraint입니다.

사용자 지정 IRouteConstraint를 사용하려면 서비스 컨테이너에 있는 앱의 ConstraintMap에 경로 제약 조건 형식을 등록해야 합니다. ConstraintMap은 경로 제약 조건 키를 해당 제약 조건의 유효성을 검사하는 IRouteConstraint 구현으로 매핑하는 사전입니다. Program.cs에서 AddRouting 호출의 일부로 또는 builder.Services.Configure<RouteOptions>를 사용하여 직접 RouteOptions를 구성하여 앱의 ConstraintMap을 업데이트할 수 있습니다. 예시:

builder.Services.AddRouting(options =>
    options.ConstraintMap.Add("noZeroes", typeof(NoZeroesRouteConstraint)));

위의 제약 조건은 다음 코드에서 적용됩니다.

[ApiController]
[Route("api/[controller]")]
public class NoZeroesController : ControllerBase
{
    [HttpGet("{id:noZeroes}")]
    public IActionResult Get(string id) =>
        Content(id);
}

NoZeroesRouteConstraint를 구현하면 경로 매개 변수에 0이 사용되지 않습니다.

public class NoZeroesRouteConstraint : IRouteConstraint
{
    private static readonly Regex _regex = new(
        @"^[1-9]*$",
        RegexOptions.CultureInvariant | RegexOptions.IgnoreCase,
        TimeSpan.FromMilliseconds(100));

    public bool Match(
        HttpContext? httpContext, IRouter? route, string routeKey,
        RouteValueDictionary values, RouteDirection routeDirection)
    {
        if (!values.TryGetValue(routeKey, out var routeValue))
        {
            return false;
        }

        var routeValueString = Convert.ToString(routeValue, CultureInfo.InvariantCulture);

        if (routeValueString is null)
        {
            return false;
        }

        return _regex.IsMatch(routeValueString);
    }
}

Warning

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

앞의 코드가 하는 역할은 다음과 같습니다.

  • 경로의 {id} 세그먼트에 0을 사용하지 못하게 합니다.
  • 사용자 지정 제약 조건을 구현하는 기본 예제를 제공하기 위해 표시됩니다. 프로덕션 앱에 사용해서는 안 됩니다.

다음 코드는 0이 포함된 id가 처리되지 않게 하는 더 나은 방법입니다.

[HttpGet("{id}")]
public IActionResult Get(string id)
{
    if (id.Contains('0'))
    {
        return StatusCode(StatusCodes.Status406NotAcceptable);
    }

    return Content(id);
}

위의 코드는 NoZeroesRouteConstraint 접근 방식과 비교하면 다음과 같은 이점이 있습니다.

  • 사용자 지정 제약 조건이 필요하지 않습니다.
  • 경로 매개 변수에 0이 포함된 경우 더 자세한 설명이 포함된 오류를 반환합니다.

매개 변수 변환기

매개 변수 변환기는:

예를 들어, Url.Action(new { article = "MyTestArticle" })을 사용하는 경로 패턴 blog\{article:slugify}의 사용자 지정 slugify 매개 변수 변환기는 blog\my-test-article을 생성합니다.

다음 IOutboundParameterTransformer 구현을 생각해 보겠습니다.

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

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

경로 패턴에서 매개 변수 변환기를 사용하려면 Program.csConstraintMap을 사용하여 구성합니다.

builder.Services.AddRouting(options =>
    options.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer));

ASP.NET Core Framework는 매개 변수 변화기를 사용하여 엔드포인트가 해결되는 URI를 변환합니다. 예를 들어 매개 변수 변환기는 area, controller, actionpage와 일치하도록 사용되는 경로 값을 변환합니다.

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

위의 경로 템플릿을 사용하면 SubscriptionManagementController.GetAll 작업이 URI /subscription-management/get-all과 일치합니다. 매개 변수 변환기는 링크를 생성하는 데 사용되는 경로 값을 변경하지 않습니다. 예를 들어 Url.Action("GetAll", "SubscriptionManagement")/subscription-management/get-all을 출력합니다.

ASP.NET Core는 생성된 경로와 함께 매개 변수 변환기를 사용하기 위한 API 규칙을 제공합니다.

URL 생성 참조

이 단원에는 URL 생성에서 구현하는 알고리즘에 대한 참조가 포함되어 있습니다. 실제로 URL 생성의 가장 복잡한 예제에서는 컨트롤러나 Razor Pages를 사용합니다. 자세한 내용은 컨트롤러의 라우팅을 참조하세요.

URL 생성 프로세스는 호출 또는 유사한 메서드로 LinkGenerator.GetPathByAddress 시작합니다. 메서드에는 주소, 경로 값 집합 및 HttpContext의 현재 요청에 관한 정보(선택 사항)를 제공합니다.

첫 번째 단계에서는 주소를 사용하여 주소의 형식과 일치하는 IEndpointAddressScheme<TAddress>을 사용하는 후보 엔드포인트 집합을 확인합니다.

주소 체계에 따라 후보 세트를 찾으면 URL 생성 작업이 성공할 때까지 엔드포인트가 순서 지정되고 반복적으로 처리됩니다. URL 생성에서는 모호성을 확인하지 않으며, 반환되는 첫 번째 결과가 최종 결과입니다.

로깅을 사용하여 URL 생성 문제 해결

URL 생성 문제를 해결하는 첫 번째 단계는 Microsoft.AspNetCore.Routing의 로깅 수준을 TRACE로 설정하는 것입니다. LinkGenerator는 문제 해결에 유용할 수 있는 처리에 관한 여러 세부 정보를 기록합니다.

URL 생성에 대한 자세한 내용은 URL 생성 참조를 참조하세요.

주소

주소는 URL 생성에서 링크 생성기의 호출을 후보 엔드포인트 집합에 바인딩하는 데 사용되는 개념입니다.

주소는 기본적으로 다음 두 가지 구현이 함께 제공되는 확장 가능한 개념입니다.

  • 엔드포인트 이름(string)을 주소로 사용:
    • MVC의 경로 이름과 유사한 기능을 제공합니다.
    • IEndpointNameMetadata 메타데이터 형식을 사용합니다.
    • 등록된 모든 엔드포인트의 메타데이터와 제공된 문자열을 비교하여 확인합니다.
    • 여러 엔드포인트에서 같은 이름을 사용하는 경우 시작 시 예외를 throw합니다.
    • 컨트롤러 및 Razor Pages 외의 일반적인 용도에 사용하는 것이 좋습니다.
  • 경로 값(RouteValuesAddress)을 주소로 사용:
    • 컨트롤러 및 Razor Pages 레거시 URL 생성과 비슷한 기능을 제공합니다.
    • 확장 및 디버그하기가 매우 복잡합니다.
    • IUrlHelper, 태그 도우미, HTML 도우미, 작업 결과 등에서 사용하는 구현을 제공합니다.

주소 체계의 역할은 다음과 같은 임의 조건에 따라 주소와 일치하는 엔드포인트 사이를 연결하는 것입니다.

  • 엔드포인트 이름 체계에서 기본 사전 조회를 수행합니다.
  • 경로 값 체계에는 알고리즘의 복잡한 최적 하위 집합이 있습니다.

앰비언트 값 및 명시적 값

현재 요청에서 라우팅은 현재 요청의 경로 값 HttpContext.Request.RouteValues에 액세스합니다. 현재 요청과 연결된 값을 앰비언트 값이라고 합니다. 명확하게 하도록 설명서에서는 메서드에 전달된 경로 값을 명시적 값이라고 합니다.

다음 예제에서는 앰비언트 값과 명시적 값을 보여 줍니다. 현재 요청의 앰비언트 값과 명시적 값을 제공합니다.

public class WidgetController : ControllerBase
{
    private readonly LinkGenerator _linkGenerator;

    public WidgetController(LinkGenerator linkGenerator) =>
        _linkGenerator = linkGenerator;

    public IActionResult Index()
    {
        var indexPath = _linkGenerator.GetPathByAction(
            HttpContext, values: new { id = 17 })!;

        return Content(indexPath);
    }

    // ...

앞의 코드가 하는 역할은 다음과 같습니다.

  • /Widget/Index/17를 반환합니다.
  • DI를 통해 LinkGenerator를 가져옵니다.

다음 코드는 명시적 값만 제공하고 앰비언트 값은 제공하지 않습니다.

var subscribePath = _linkGenerator.GetPathByAction(
    "Subscribe", "Home", new { id = 17 })!;

앞의 메서드는 /Home/Subscribe/17을 반환합니다.

WidgetController의 다음 코드는 /Widget/Subscribe/17을 반환합니다.

var subscribePath = _linkGenerator.GetPathByAction(
    HttpContext, "Subscribe", null, new { id = 17 });

다음 코드는 현재 요청의 앰비언트 값과 명시적 값을 컨트롤러에 제공합니다.

public class GadgetController : ControllerBase
{
    public IActionResult Index() =>
        Content(Url.Action("Edit", new { id = 17 })!);
}

위의 코드에서

  • /Gadget/Edit/17이 반환됩니다.
  • UrlIUrlHelper를 가져옵니다.
  • Action은 작업 메서드의 절대 경로가 포함된 URL을 생성합니다. URL에는 지정된 action 이름과 route 값이 포함됩니다.

다음 코드에서는 현재 요청의 앰비언트 값과 명시적 값을 제공합니다.

public class IndexModel : PageModel
{
    public void OnGet()
    {
        var editUrl = Url.Page("./Edit", new { id = 17 });

        // ...
    }
}

앞의 코드에서는 Razor 편집 페이지에 다음 페이지 지시문이 포함된 경우 url/Edit/17로 설정합니다.

@page "{id:int}"

편집 페이지에 "{id:int}" 경로 템플릿이 포함되어 있지 않으면 url/Edit?id=17입니다.

MVC의 IUrlHelper 동작은 여기에 설명된 규칙 외에도 복잡성 계층을 추가합니다.

  • IUrlHelper는 항상 현재 요청의 경로 값을 앰비언트 값으로 제공합니다.
  • IUrlHelper.Action은 개발자가 재정의하는 경우 외에는 항상 현재 actioncontroller 경로 값을 명시적 값으로 복사합니다.
  • IUrlHelper.Page는 재정의되는 경우 외에는 항상 현재 page 경로 값을 명시적 값으로 복사합니다.
  • IUrlHelper.Page는 재정의되는 경우 외에는 항상 현재 handler 경로 값을 명시적 값 null로 재정의합니다.

MVC가 자체 규칙을 따르지 않기 때문에 앰비언트 값의 동작 세부 정보에 사용자가 놀라는 경우가 많습니다. 기록 및 호환성을 위해 action, controller, page, handler 등의 특정 경로 값에는 고유한 특수 사례 동작이 있습니다.

LinkGenerator.GetPathByActionLinkGenerator.GetPathByPage에서 제공하는 동일한 기능에서는 호환성을 위해 IUrlHelper의 이러한 변칙을 복제합니다.

URL 생성 프로세스

후보 엔드포인트 집합을 찾으면 URL 생성 알고리즘은 다음을 수행합니다.

  • 엔드포인트를 반복적으로 처리합니다.
  • 첫 번째 성공적인 결과를 반환합니다.

이 프로세스의 첫 번째 단계를 경로 값 무효화라고 합니다. 경로 값 무효화는 라우팅에서 앰비언트 값의 어떤 경로 값을 사용하고 무시할지 결정하는 프로세스입니다. 각 앰비언트 값이 고려되고 명시적 값과 결합되거나 무시됩니다.

앰비언트 값의 역할을 알아보려면 일반적인 사례에서 애플리케이션 개발자 입력을 저장하려고 하는 점을 고려하면 가장 좋습니다. 일반적으로 앰비언트 값이 유용한 시나리오는 MVC와 관련이 있습니다.

  • 동일한 컨트롤러의 다른 작업에 연결하는 경우에는 컨트롤러 이름을 지정할 필요가 없습니다.
  • 같은 영역의 다른 컨트롤러에 연결하는 경우 영역 이름을 지정할 필요가 없습니다.
  • 동일한 작업 메서드에 연결하는 경우 경로 값을 지정할 필요가 없습니다.
  • 앱의 다른 부분에 연결하는 경우 앱의 해당 부분에서 의미가 없는 경로 값을 전달하지 않을 수 있습니다.

null을 반환하는 LinkGenerator 또는 IUrlHelper를 호출하면 일반적으로 이해되지 않는 경로 값 무효화가 발생합니다. 경로 값 무효화 문제를 해결하려면 더 많은 경로 값을 명시적으로 지정하여 문제가 해결되는지 확인합니다.

경로 값 무효화는 앱의 URL 체계가 계층이 왼쪽에서 오른쪽으로 형성된 계층 구조라고 가정하고 작동합니다. 기본 컨트롤러 경로 템플릿 {controller}/{action}/{id?}를 사용하여 실제 작동 방법을 직관적으로 파악해 보겠습니다. 값을 변경하면 오른쪽에 표시되는 경로 값이 모두 무효화됩니다. 이는 계층 구조에 관한 가정이 반영된 것입니다. 앱에 id의 앰비언트 값이 있고 작업에서 controller에 대해 다른 값을 지정하는 경우:

  • {controller}{id?}의 왼쪽에 있으므로 id가 다시 사용되지 않습니다.

이 원칙을 보여 주는 몇 가지 예는 다음과 같습니다.

  • 명시적 값에 id의 값이 포함된 경우 id의 앰비언트 값은 무시됩니다. controlleraction의 앰비언트 값이 사용될 수 있습니다.
  • 명시적 값에 action의 값이 포함된 경우 action의 앰비언트 값은 무시됩니다. controller의 앰비언트 값이 사용될 수 있습니다. action의 명시적 값이 action의 앰비언트 값과 다른 경우 id 값은 사용되지 않습니다. action의 명시적 값이 action의 앰비언트 값과 같으면 id 값이 사용될 수 있습니다.
  • 명시적 값에 controller의 값이 포함된 경우 controller의 앰비언트 값은 무시됩니다. controller의 명시적 값이 controller의 앰비언트 값과 다른 경우 actionid 값은 사용되지 않습니다. controller의 명시적 값이 controller의 앰비언트 값과 같으면 actionid 값이 사용될 수 있습니다.

이 프로세스는 특성 경로와 전용 규칙 기반 경로가 있으면 더 복잡해집니다. {controller}/{action}/{id?}와 같은 컨트롤러 규칙 기반 경로는 경로 매개 변수를 사용하여 계층 구조를 지정합니다. 컨트롤러 및 Razor Pages에 대한 전용 규칙 기반 경로특성 경로의 경우:

  • 경로 값의 계층 구조가 있습니다.
  • 템플릿에는 표시되지 않습니다.

이러한 경우 URL 생성에서 필수 값 개념을 정의합니다. 컨트롤러 및 Razor Pages에서 만든 엔드포인트에는 경로 값 무효화가 작동할 수 있도록 필수 값이 지정되어 있습니다.

경로 값 무효화 알고리즘을 자세히 설명하면 다음과 같습니다.

  • 필요 값 이름을 경로 매개 변수와 결합한 다음 왼쪽에서 오른쪽으로 처리합니다.
  • 각 매개 변수에 대해 앰비언트 값과 명시적 값이 비교됩니다.
    • 앰비언트 값과 명시적 값이 같으면 프로세스가 계속됩니다.
    • 앰비언트 값이 있고 명시적 값이 없으면 URL을 생성할 때 앰비언트 값이 사용됩니다.
    • 앰비언트 값이 없고 명시적 값이 있으면 앰비언트 값과 모든 후속 앰비언트 값을 거부합니다.
    • 앰비언트 값과 명시적 값이 있고 두 값이 다른 경우 앰비언트 값과 모든 후속 앰비언트 값을 거부합니다.

이제 URL 생성 작업에서 경로 제약 조건을 평가할 준비가 되었습니다. 허용되는 값 집합이 제약 조건에 제공되는 매개 변수 기본값과 결합됩니다. 제약 조건이 모두 통과되면 작업이 계속됩니다.

다음으로 허용되는 값을 사용하여 경로 템플릿을 확장할 수 있습니다. 경로 템플릿은 다음과 같이 처리됩니다.

  • 왼쪽에서 오른쪽으로
  • 각 매개 변수의 허용되는 값이 대체됩니다.
  • 다음과 같은 특수한 경우를 사용합니다.
    • 허용되는 값에 누락된 값이 있고 매개 변수에 기본값이 있으면 기본값이 사용됩니다.
    • 허용되는 값에 누락된 값이 있고 매개 변수가 선택 사항이면 처리가 계속됩니다.
    • 누락된 선택적 매개 변수 오른쪽의 경로 매개 변수에 값이 있으면 작업이 실패합니다.
    • 연속된 기본값 매개 변수 및 선택적 매개 변수는 가능하면 축소됩니다.

명시적으로 제공되지만 경로의 세그먼트와 일치하지 않는 값은 쿼리 문자열에 추가됩니다. 다음 표에서 경로 템플릿 {controller}/{action}/{id?}를 사용하는 경우 결과를 보여 줍니다.

앰비언트 값 명시적 값 결과
controller = "Home" action = "About" /Home/About
controller = "Home" controller = "Order", action = "About" /Order/About
controller = "Home", color = "Red" action = "About" /Home/About
controller = "Home" action = "About", color = "Red" /Home/About?color=Red

경로 값 무효화 문제

다음 코드에서는 라우팅에서 지원하지 않는 URL 생성 체계의 예제를 보여 줍니다.

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

app.MapControllerRoute(
    "blog",
    "{culture}/{**slug}",
    new { controller = "Blog", action = "ReadPost" });

위의 코드에서 culture 경로 매개 변수는 지역화에 사용됩니다. culture 매개 변수가 앰비언트 값으로 항상 허용되게 하려는 것이 목표입니다. 그러나 culture 매개 변수는 필수 값이 작동하는 방식 때문에 앰비언트 값으로 허용되지 않습니다.

  • "default" 경로 템플릿에서 culture 경로 매개 변수는 controller의 왼쪽에 있으므로 controller를 변경해도 culture가 무효화되지 않습니다.
  • "blog" 경로 템플릿에서 culture 경로 매개 변수는 필수 값에 표시되는 controller의 오른쪽에 있는 것으로 간주됩니다.

LinkParser을(를) 사용하여 URL 경로 구문 분석

LinkParser클래스는 URL 경로를 경로 값 집합으로 구문 분석하기 위한 지원을 추가합니다. ParsePathByEndpointName 메서드는 엔드포인트 이름과 URL 경로를 사용하고 URL 경로에서 추출된 경로 값 집합을 반환합니다.

다음 예제 컨트롤러에서 GetProduct 작업은 api/Products/{id}의 경로 템플릿을 사용하며 GetProductName을 가집니다.

[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
    [HttpGet("{id}", Name = nameof(GetProduct))]
    public IActionResult GetProduct(string id)
    {
        // ...

동일한 컨트롤러 클래스에서 AddRelatedProduct 작업에는 쿼리 문자열 매개 변수로 제공할 수 있는 URL 경로 pathToRelatedProduct가 필요합니다.

[HttpPost("{id}/Related")]
public IActionResult AddRelatedProduct(
    string id, string pathToRelatedProduct, [FromServices] LinkParser linkParser)
{
    var routeValues = linkParser.ParsePathByEndpointName(
        nameof(GetProduct), pathToRelatedProduct);
    var relatedProductId = routeValues?["id"];

    // ...

앞의 예제에서 AddRelatedProduct 작업은 URL 경로에서 id 경로 값을 추출합니다. 예를 들어 /api/Products/1의 URL 경로가 있는 경우 relatedProductId 값은 1(으)로 설정됩니다. 이 방법을 사용하면 API의 클라이언트가 리소스를 참조할 때 URL 경로를 사용할 수 있으므로 이러한 URL이 어떻게 구성되는지에 대한 지식이 필요하지 않습니다.

엔드포인트 메타데이터 구성

다음 링크는 엔드포인트 메타데이터를 구성하는 방법에 대한 정보를 제공합니다.

RequireHost가 있는 경로의 호스트 일치

RequireHost는 지정된 호스트가 필요한 경로에 제약 조건을 적용합니다. RequireHost 또는 [Host] 매개 변수는 다음과 같을 수 있습니다.

  • 호스트: www.domain.com(아무 포트에서나 www.domain.com과 일치)
  • 와일드카드가 있는 호스트: *.domain.com(아무 포트에서나 www.domain.com, subdomain.domain.com 또는 www.subdomain.domain.com과 일치)
  • 포트: *:5000(아무 호스트에서나 포트 5000과 일치)
  • 호스트 및 포트: www.domain.com:5000 또는 *.domain.com:5000(호스트 및 포트와 일치)

RequireHost 또는 [Host]를 사용하여 여러 매개 변수를 지정할 수 있습니다. 제약 조건은 모든 매개 변수에 유효한 호스트와 일치합니다. 예를 들어 [Host("domain.com", "*.domain.com")]domain.com, www.domain.comsubdomain.domain.com과 일치합니다.

다음 코드는 RequireHost를 사용하여 경로상에 있는 지정된 호스트를 요구합니다.

app.MapGet("/", () => "Contoso").RequireHost("contoso.com");
app.MapGet("/", () => "AdventureWorks").RequireHost("adventure-works.com");

app.MapHealthChecks("/healthz").RequireHost("*:8080");

다음 코드는 컨트롤러의 [Host] 특성을 사용하여 지정된 호스트를 요구합니다.

[Host("contoso.com", "adventure-works.com")]
public class HostsController : Controller
{
    public IActionResult Index() =>
        View();

    [Host("example.com")]
    public IActionResult Example() =>
        View();
}

[Host] 특성이 컨트롤러 메서드와 작업 메서드에 모두 적용될 경우

  • 작업의 특성이 사용됩니다.
  • 컨트롤러의 특성은 무시됩니다.

라우팅의 성능 지침

앱에 성능 문제가 있는 경우 라우팅이 문제의 원인으로 의심받는 경우가 많습니다. 라우팅이 의심받는 이유는 컨트롤러 및 Razor Pages 같은 프레임워크가 프레임워크 내에서 소요된 시간을 로깅 메시지로 보고하기 때문입니다. 컨트롤러에서 보고하는 시간과 요청의 총 시간 사이에 상당한 차이가 있는 경우:

  • 개발자는 문제의 원인인 앱 코드를 제거합니다.
  • 일반적으로 라우팅이 원인이라고 가정합니다.

라우팅은 수천 개의 엔드포인트를 사용하여 성능을 테스트했습니다. 일반적인 앱에서는 너무 크다고 성능 문제가 발생할 가능성은 거의 없습니다. 라우팅 성능이 저하되는 가장 일반적인 근본 원인은 일반적으로 잘못 동작하는 사용자 지정 미들웨어입니다.

다음 코드 샘플에서는 지연의 원인을 좁히기 위한 기본 기술을 보여 줍니다.

var logger = app.Services.GetRequiredService<ILogger<Program>>();

app.Use(async (context, next) =>
{
    var stopwatch = Stopwatch.StartNew();
    await next(context);
    stopwatch.Stop();

    logger.LogInformation("Time 1: {ElapsedMilliseconds}ms", stopwatch.ElapsedMilliseconds);
});

app.UseRouting();

app.Use(async (context, next) =>
{
    var stopwatch = Stopwatch.StartNew();
    await next(context);
    stopwatch.Stop();

    logger.LogInformation("Time 2: {ElapsedMilliseconds}ms", stopwatch.ElapsedMilliseconds);
});

app.UseAuthorization();

app.Use(async (context, next) =>
{
    var stopwatch = Stopwatch.StartNew();
    await next(context);
    stopwatch.Stop();

    logger.LogInformation("Time 3: {ElapsedMilliseconds}ms", stopwatch.ElapsedMilliseconds);
});

app.MapGet("/", () => "Timing Test.");

종료 시간 라우팅:

  • 앞의 코드에 표시된 타이밍 미들웨어의 복사본을 각 미들웨어에 인터리빙합니다.
  • 고유 식별자를 추가하여 타이밍 데이터를 코드와 연관 짓습니다.

이것이 예를 들어 10ms 이상의 상당한 지연이 발생하는 경우 지연을 좁히는 기본적인 방법입니다. Time 1에서 Time 2를 빼면 UseRouting 미들웨어 내에서 소요된 시간이 보고됩니다.

다음 코드에서는 앞의 타이밍 코드에 더욱 간결한 방법을 사용합니다.

public sealed class AutoStopwatch : IDisposable
{
    private readonly ILogger _logger;
    private readonly string _message;
    private readonly Stopwatch _stopwatch;
    private bool _disposed;

    public AutoStopwatch(ILogger logger, string message) =>
        (_logger, _message, _stopwatch) = (logger, message, Stopwatch.StartNew());

    public void Dispose()
    {
        if (_disposed)
        {
            return;
        }

        _logger.LogInformation("{Message}: {ElapsedMilliseconds}ms",
            _message, _stopwatch.ElapsedMilliseconds);

        _disposed = true;
    }
}
var logger = app.Services.GetRequiredService<ILogger<Program>>();
var timerCount = 0;

app.Use(async (context, next) =>
{
    using (new AutoStopwatch(logger, $"Time {++timerCount}"))
    {
        await next(context);
    }
});

app.UseRouting();

app.Use(async (context, next) =>
{
    using (new AutoStopwatch(logger, $"Time {++timerCount}"))
    {
        await next(context);
    }
});

app.UseAuthorization();

app.Use(async (context, next) =>
{
    using (new AutoStopwatch(logger, $"Time {++timerCount}"))
    {
        await next(context);
    }
});

app.MapGet("/", () => "Timing Test.");

비용이 많이 들 수 있는 라우팅 기능

다음 목록에서는 기본 경로 템플릿보다 비교적 비용이 많이 드는 라우팅 기능에 대한 몇 가지 유용한 정보를 제공합니다.

  • 정규식: 복잡한 정규식을 작성하거나 적은 양의 입력으로 장기 실행 시간을 가질 수 있습니다.
  • 복합 세그먼트({x}-{y}-{z}):
    • 일반 URL 경로 세그먼트를 구문 분석하는 것보다 훨씬 비용이 많이 듭니다.
    • 더 많은 부분 문자열이 할당됩니다.
  • 동기 데이터 액세스: 많은 복잡한 앱은 라우팅의 일부로 데이터베이스 액세스 권한을 갖습니다. MatcherPolicyEndpointSelectorContext 같은 비동기식 확장 지점을 사용합니다.

큰 라우팅 테이블 지침

기본적으로 ASP.NET Core는 메모리를 CPU 시간과 교환하는 라우팅 알고리즘을 사용합니다. 이는 경로 일치 시간이 일치시킬 경로의 길이에만 종속되고 경로 수에 종속되지 않는 좋은 효과가 있습니다. 하지만 앱에 경로가 많고(수천 개) 경로에 많은 변수 접두사가 있는 경우 이 방법은 잠재적으로 문제가 될 수 있습니다. 예를 들어 경로의 초기 세그먼트에 {parameter}/some/literal과 같은 매개 변수가 있는 경우입니다.

다음과 같은 경우가 아니면 이것이 문제가 되는 상황이 앱에 발생할 가능성은 낮습니다.

  • 이 패턴을 사용하는 앱에 많은 수의 경로가 있습니다.
  • 앱에 많은 수의 경로가 있습니다.

앱에서 큰 라우팅 테이블 문제가 발생하는지 확인하는 방법

  • 찾아야 할 두 가지 증상이 있습니다.
    • 앱이 첫 번째 요청에서 시작하는 속도가 느립니다.
      • 이 증상은 필수이지만 충분하지는 않습니다. 앱 시작 속도가 느려질 수 있는 경로 문제 외의 다른 문제가 많이 있습니다. 아래 조건을 확인하여 앱에 이 상황이 발생하고 있는지 정확하게 확인합니다.
    • 앱이 시작하는 동안 많은 메모리를 사용하고 메모리 덤프에 많은 수의 Microsoft.AspNetCore.Routing.Matching.DfaNode 인스턴스가 표시됩니다.

이 문제를 해결하는 방법

이 시나리오를 크게 개선하는 몇 가지 방법과 최적화를 경로에 적용할 수 있습니다.

  • 가능한 경우 {parameter:int}, {parameter:guid}, {parameter:regex(\\d+)} 등과 같은 경로 제약 조건을 매개 변수에 적용합니다.
    • 그러면 라우팅 알고리즘이 일치에 사용되는 구조를 내부적으로 최적화하고 사용되는 메모리를 크게 줄일 수 있습니다.
    • 대부분의 경우 허용 가능한 동작으로 돌아가는 데는 이것으로 충분합니다.
  • 템플릿에서 매개 변수를 이후 세그먼트로 이동하도록 경로를 변경합니다.
    • 그러면 특정 경로의 엔드포인트와 일치하는 "경로" 수가 줄어듭니다.
  • 동적 경로를 사용하고 컨트롤러/페이지에 대한 매핑을 동적으로 수행합니다.
    • MapDynamicControllerRouteMapDynamicPageRoute를 사용하면 가능합니다.

라이브러리 작성자를 위한 지침

이 단원에는 라우팅을 기반으로 빌드하는 라이브러리 작성자를 위한 지침이 포함되어 있습니다. 이러한 세부 내용은 앱 개발자가 라우팅을 확장하는 라이브러리 및 프레임워크를 사용하는 좋은 환경을 갖추도록 하기 위한 것입니다.

엔드포인트 정의

URL 일치를 위해 라우팅을 사용하는 프레임워크를 만들려면 먼저 UseEndpoints를 기반으로 빌드되는 사용자 환경을 정의합니다.

IEndpointRouteBuilder를 기반으로 빌드하세요. 이렇게 하면 사용자는 혼동하지 않고 다른 ASP.NET Core 기능을 사용하여 프레임워크를 작성할 수 있습니다. 모든 ASP.NET Core 템플릿에는 라우팅이 포함됩니다. 라우팅이 있고 사용자에게 친숙하다고 간주합니다.

// Your framework
app.MapMyFramework(...);

app.MapHealthChecks("/healthz");

IEndpointConventionBuilder를 구현하는 MapMyFramework(...) 호출에서 봉인된 구체적인 형식을 반환하세요. 대부분의 프레임워크 Map... 메서드는 이 패턴을 따릅니다. IEndpointConventionBuilder 인터페이스:

  • 메타데이터를 작성할 수 있습니다.
  • 다양한 확장 메서드의 대상으로 지정됩니다.

고유의 형식을 선언하면 작성기에 사용자 고유의 프레임워크 관련 기능을 추가할 수 있습니다. 프레임워크 선언된 작성기를 래핑하고 여기에 호출을 전달해도 됩니다.

// Your framework
app.MapMyFramework(...)
    .RequireAuthorization()
    .WithMyFrameworkFeature(awesome: true);

app.MapHealthChecks("/healthz");

사용자 고유의 EndpointDataSource를 작성하는 것이 좋습니다. EndpointDataSource는 엔드포인트 컬렉션을 선언하고 업데이트하기 위한 하위 수준 기본 형식입니다. EndpointDataSource는 컨트롤러 및 Razor Pages에서 사용되는 강력한 API입니다.

라우팅 테스트에는 업데이트되지 않는 데이터 원본의 기본 예제가 있습니다.

기본적으로 EndpointDataSource를 등록하지 마세요. 프레임워크를 UseEndpoints에 등록하도록 사용자에게 요구하세요. 라우팅의 원리에 따르면 기본적으로 아무것도 포함되지 않으며 UseEndpoints가 엔드포인트를 등록하는 위치입니다.

라우팅 통합 미들웨어 만들기

메타데이터 형식을 인터페이스로 정의하는 것이 좋습니다.

메타데이터 형식을 클래스 및 메서드의 특성으로 사용할 수 있게 하세요.

public interface ICoolMetadata
{
    bool IsCool { get; }
}

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CoolMetadataAttribute : Attribute, ICoolMetadata
{
    public bool IsCool => true;
}

컨트롤러 및 Razor Pages와 같은 프레임워크는 형식 및 메서드에 메타데이터 특성을 적용하도록 지원합니다. 메타데이터 형식을 선언하는 경우:

  • 이 형식에 특성으로 액세스할 수 있습니다.
  • 사용자 대부분이 특성을 적용하는 데 익숙합니다.

메타데이터 형식을 인터페이스로 선언하면 또 하나의 유연성 계층이 추가됩니다.

  • 인터페이스는 구성할 수 있습니다.
  • 개발자가 여러 정책을 결합하여 고유한 형식을 선언할 수 있습니다.

다음 예제와 같이 메타데이터를 재정의할 수 있게 하세요.

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class SuppressCoolMetadataAttribute : Attribute, ICoolMetadata
{
    public bool IsCool => false;
}

[CoolMetadata]
public class MyController : Controller
{
    public void MyCool() { }

    [SuppressCoolMetadata]
    public void Uncool() { }
}

이러한 지침을 따르는 가장 좋은 방법은 마커 메타데이터를 정의하지 않는 것입니다.

  • 메타데이터 형식이 있는지만 확인하면 안 됩니다.
  • 메타데이터의 속성을 정의하고 속성을 확인합니다.

메타데이터 컬렉션은 순서 지정되며 우선 순위별로 재정의할 수 있습니다. 컨트롤러의 경우 작업 메서드의 메타데이터가 가장 구체적입니다.

라우팅이 있는지 관계없이 미들웨어를 유용하게 사용할 수 있게 하세요.

app.UseAuthorization(new AuthorizationPolicy() { ... });

// Your framework
app.MapMyFramework(...).RequireAuthorization();

이 지침의 예제에서는 UseAuthorization 미들웨어를 고려해 보겠습니다. 권한 부여 미들웨어를 사용하면 대체 정책을 전달할 수 있습니다. 대체 정책(지정된 경우)은 다음 모두에 적용됩니다.

  • 지정된 정책이 없는 엔드포인트
  • 엔드포인트와 일치하지 않는 요청

따라서 권한 부여 미들웨어는 라우팅 컨텍스트 외에서도 유용합니다. 권한 부여 미들웨어는 기존 미들웨어 프로그래밍에 사용할 수 있습니다.

디버그 진단

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

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

추가 리소스

라우팅은 들어오는 HTTP 요청을 일치시켜 앱의 실행 가능 엔드포인트로 디스패치하는 역할을 담당합니다. 엔드포인트는 앱의 실행 가능 요청 처리 코드 단위입니다. 엔드포인트는 앱에서 정의되고 앱 시작 시 구성됩니다. 엔드포인트 일치 프로세스는 요청의 URL에서 값을 추출하고 요청 처리를 위해 이 값을 제공할 수 있습니다. 또한 라우팅은 앱의 엔드포인트 정보를 사용하여 엔드포인트에 매핑되는 URL을 생성할 수도 있습니다.

앱은 다음을 사용하여 라우팅을 구성할 수 있습니다.

  • 컨트롤러
  • Razor Pages
  • SignalR
  • gRPC 서비스
  • 상태 검사와 같은 엔드포인트 지원 미들웨어
  • 라우팅에 등록된 대리자 및 람다

이 문서에서는 ASP.NET Core 라우팅의 하위 수준 세부 정보를 설명합니다. 라우팅을 구성하는 방법은 다음을 참조하세요.

이 문서에서 설명하는 엔드포인트 라우팅 시스템은 ASP.NET Core 3.0 이상에 적용됩니다. IRouter 기반의 이전 라우팅 시스템에 대한 자세한 내용은 다음 접근 방법 중 하나를 사용하여 ASP.NET Core 2.1 버전을 선택합니다.

샘플 코드 보기 및 다운로드(다운로드 방법)

이 문서의 다운로드 샘플은 특정 Startup 클래스를 통해 사용할 수 있습니다. 특정 샘플을 실행하려면 Program.cs를 수정하여 원하는 Startup 클래스를 호출합니다.

라우팅 기본 사항

모든 ASP.NET Core 템플릿에는 생성된 코드에서의 라우팅이 포함됩니다. 라우팅은 Startup.Configure미들웨어 파이프라인에 등록됩니다.

다음 코드에서는 라우팅의 기본 예제를 보여 줍니다.

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

    app.UseRouting();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapGet("/", async context =>
        {
            await context.Response.WriteAsync("Hello World!");
        });
    });
}

라우팅은 UseRoutingUseEndpoints를 통해 등록된 미들웨어 쌍을 사용합니다.

  • UseRouting은 경로 일치를 미들웨어 파이프라인에 추가합니다. 이 미들웨어는 앱에 정의된 엔드포인트 집합을 확인하고 요청을 기반으로 가장 일치하는 항목을 선택합니다.
  • UseEndpoints는 엔드포인트 실행을 미들웨어 파이프라인에 추가합니다. 선택한 엔드포인트와 연결된 대리자를 실행합니다.

앞의 예제에는 MapGet 메서드를 사용하는 단일 ‘라우팅 대상 코드’ 엔드포인트가 포함됩니다.

  • HTTP GET 요청이 루트 URL /로 전송되는 경우:
    • 표시된 요청 대리자가 실행됩니다.
    • Hello World!가 HTTP 응답에 기록됩니다. 기본적으로 루트 URL /https://localhost:5001/입니다.
  • 요청 메서드가 GET이 아니거나 루트 URL이 /가 아니면 일치하는 경로가 없고 HTTP 404가 반환됩니다.

엔드포인트

MapGet 메서드는 엔드포인트을 정의하는 데 사용됩니다. 엔드포인트는 다음과 같을 수 있습니다.

  • URL 및 HTTP 메서드를 일치시켜 선택됩니다.
  • 대리자를 실행하여 실행됩니다.

앱에서 일치시키고 실행할 수 있는 엔드포인트는 UseEndpoints에서 구성됩니다. 예를 들어 MapGet, MapPost이들과 유사한 메서드는 요청 대리자를 라우팅 시스템에 연결합니다. ASP.NET Core Framework 기능을 라우팅 시스템에 연결하는 데 다음과 같은 추가 메서드를 사용할 수 있습니다.

다음 예제에서는 더 복잡한 경로 템플릿을 사용한 라우팅을 보여 줍니다.

app.UseEndpoints(endpoints =>
{
    endpoints.MapGet("/hello/{name:alpha}", async context =>
    {
        var name = context.Request.RouteValues["name"];
        await context.Response.WriteAsync($"Hello {name}!");
    });
});

/hello/{name:alpha} 문자열은 경로 템플릿이며 엔드포인트가 일치되는 방식을 구성합니다. 이 경우 템플릿은 다음과 일치합니다.

  • /hello/Ryan과 같은 URL
  • /hello/로 시작하고 그 다음에 시퀀스 영문자가 오는 URL 경로. :alpha는 영문자와만 일치하는 경로 제약 조건을 적용합니다. 경로 제약 조건은 이 문서의 뒷부분에 설명되어 있습니다.

URL 경로의 두 번째 세그먼트 {name:alpha}는 다음과 같습니다.

이 문서에서 설명하는 엔드포인트는 라우팅 시스템은 ASP.NET Core 3.0의 새로운 기능입니다. 그러나 모든 버전의 ASP.NET Core에서 동일한 경로 템플릿 기능 및 경로 제약 조건 집합을 지원합니다.

다음 예제에서는 상태 검사 및 권한 부여를 사용한 라우팅을 보여 줍니다.

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

    // Matches request to an endpoint.
    app.UseRouting();

    // Endpoint aware middleware. 
    // Middleware can use metadata from the matched endpoint.
    app.UseAuthentication();
    app.UseAuthorization();

    // Execute the matched endpoint.
    app.UseEndpoints(endpoints =>
    {
        // Configure the Health Check endpoint and require an authorized user.
        endpoints.MapHealthChecks("/healthz").RequireAuthorization();

        // Configure another endpoint, no authorization requirements.
        endpoints.MapGet("/", async context =>
        {
            await context.Response.WriteAsync("Hello World!");
        });
    });
}

영어 이외의 언어로 번역된 코드 주석을 보려면 이 GitHub 토론 이슈에서 알려주세요.

앞의 예제에서는 다음을 수행하는 방법을 보여 줍니다.

  • 권한 부여 미들웨어를 라우팅에 사용할 수 있습니다.
  • 엔드포인트를 사용하여 권한 부여 동작을 구성할 수 있습니다.

MapHealthChecks 호출은 상태 검사 엔드포인트를 추가합니다. 이 호출에 RequireAuthorization을 연결하면 권한 부여 정책이 엔드포인트에 연결됩니다.

UseAuthenticationUseAuthorization을 호출하면 인증 및 권한 부여 미들웨어가 추가됩니다. 이러한 미들웨어는 UseRoutingUseEndpoints 사이에 배치되므로 다음을 수행할 수 있습니다.

  • UseRouting에서 선택된 엔드포인트를 확인합니다.
  • 엔드포인트로 UseEndpoints가 디스패치되기 전에 권한 부여 정책을 적용합니다.

엔드포인트 메타데이터

앞의 예제에는 두 개의 엔드포인트가 있지만 상태 검사 엔드포인트에만 권한 부여 정책이 연결되어 있습니다. 요청이 상태 검사 엔드포인트 /healthz와 일치하는 경우 권한 부여 확인이 수행됩니다. 이는 엔드포인트에 추가 데이터가 연결될 수 있음을 보여 줍니다. 이러한 추가 데이터를 엔드포인트 메타데이터라고 합니다.

  • 이 메타데이터는 라우팅 인식 미들웨어에서 처리될 수 있으며
  • 모든 .NET 형식일 수 있습니다.

라우팅 개념

라우팅 시스템은 강력한 엔드포인트 개념을 추가하여 미들웨어 파이프라인을 기반으로 빌드됩니다. 엔드포인트는 라우팅, 권한 부여 및 ASP.NET Core 시스템 수의 측면에서 서로 다른 앱의 기능 단위를 나타냅니다.

ASP.NET Core 엔드포인트 정의

ASP.NET Core 엔드포인트는 다음과 같습니다.

다음 코드에서는 현재 요청과 일치하는 엔드포인트를 검색하고 검사하는 방법을 보여 줍니다.

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

    app.UseRouting();

    app.Use(next => context =>
    {
        var endpoint = context.GetEndpoint();
        if (endpoint is null)
        {
            return Task.CompletedTask;
        }
        
        Console.WriteLine($"Endpoint: {endpoint.DisplayName}");

        if (endpoint is RouteEndpoint routeEndpoint)
        {
            Console.WriteLine("Endpoint has route pattern: " +
                routeEndpoint.RoutePattern.RawText);
        }

        foreach (var metadata in endpoint.Metadata)
        {
            Console.WriteLine($"Endpoint has metadata: {metadata}");
        }

        return Task.CompletedTask;
    });

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapGet("/", async context =>
        {
            await context.Response.WriteAsync("Hello World!");
        });
    });
}

엔드포인트는 선택된 경우 HttpContext에서 검색할 수 있습니다. 해당 속성을 검사할 수 있습니다. 엔드포인트 개체는 변경할 수 없으며 만든 후 수정할 수 없습니다. 가장 일반적인 형식의 엔드포인트는 RouteEndpoint입니다. RouteEndpoint에는 라우팅 시스템에서 선택할 수 있는 정보가 포함됩니다.

앞의 코드에서 app.Use는 인라인 미들웨어를 구성합니다.

다음 코드에서는 파이프라인에서 app.Use가 호출되는 위치에 따라 엔드포인트가 없을 수 있음을 보여 줍니다.

// Location 1: before routing runs, endpoint is always null here
app.Use(next => context =>
{
    Console.WriteLine($"1. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
    return next(context);
});

app.UseRouting();

// Location 2: after routing runs, endpoint will be non-null if routing found a match
app.Use(next => context =>
{
    Console.WriteLine($"2. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
    return next(context);
});

app.UseEndpoints(endpoints =>
{
    // Location 3: runs when this endpoint matches
    endpoints.MapGet("/", context =>
    {
        Console.WriteLine(
            $"3. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
        return Task.CompletedTask;
    }).WithDisplayName("Hello");
});

// Location 4: runs after UseEndpoints - will only run if there was no match
app.Use(next => context =>
{
    Console.WriteLine($"4. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
    return next(context);
});

위의 샘플에서는 엔드포인트가 선택되었는지 아닌지를 표시하는 Console.WriteLine 문을 추가합니다. 명확하게 하도록 이 샘플에서는 제공된 / 엔드포인트에 표시 이름을 할당합니다.

/의 URL을 사용하여 이 코드를 실행하면 다음이 표시됩니다.

1. Endpoint: (null)
2. Endpoint: Hello
3. Endpoint: Hello

다른 URL을 사용하여 이 코드를 실행하면 다음이 표시됩니다.

1. Endpoint: (null)
2. Endpoint: (null)
4. Endpoint: (null)

이 출력은 다음을 나타냅니다.

  • UseRouting을 호출하기 전에 엔드포인트는 항상 null입니다.
  • 일치 항목이 발견되면 UseRoutingUseEndpoints사이에서 엔드포인트가 null이 아닙니다.
  • 일치 항목이 발견되면 UseEndpoints 미들웨어가 터미널입니다. 터미널 미들웨어는 이 문서의 뒷부분에 정의되어 있습니다.
  • UseEndpoints 뒤의 미들웨어는 일치 항목이 없는 경우에만 실행됩니다.

UseRouting 미들웨어는 SetEndpoint 메서드를 사용하여 엔드포인트를 현재 컨텍스트에 연결합니다. UseRouting 미들웨어를 사용자 지정 논리로 바꾸어도 엔드포인트를 사용하는 이점을 얻을 수 있습니다. 엔드포인트는 미들웨어와 같은 하위 수준 기본 형식이며 라우팅 구현에 결합되지 않습니다. 대부분의 앱에서는 UseRouting을 사용자 지정 논리로 바꿀 필요가 없습니다.

UseEndpoints 미들웨어는 UseRouting 미들웨어와 함께 사용하기 위한 것입니다. 엔드포인트를 실행하는 핵심 논리는 복잡하지 않습니다. GetEndpoint를 사용하여 엔드포인트를 검색한 다음 해당 RequestDelegate 속성을 호출하면 됩니다.

다음 코드에서는 미들웨어가 라우팅에 영향을 주거나 반응하는 방식을 보여 줍니다.

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

        // Location 1: Before routing runs. Can influence request before routing runs.
        app.UseHttpMethodOverride();

        app.UseRouting();

        // Location 2: After routing runs. Middleware can match based on metadata.
        app.Use(next => context =>
        {
            var endpoint = context.GetEndpoint();
            if (endpoint?.Metadata.GetMetadata<AuditPolicyAttribute>()?.NeedsAudit
                                                                            == true)
            {
                Console.WriteLine($"ACCESS TO SENSITIVE DATA AT: {DateTime.UtcNow}");
            }

            return next(context);
        });

        app.UseEndpoints(endpoints =>
        {         
            endpoints.MapGet("/", async context =>
            {
                await context.Response.WriteAsync("Hello world!");
            });

            // Using metadata to configure the audit policy.
            endpoints.MapGet("/sensitive", async context =>
            {
                await context.Response.WriteAsync("sensitive data");
            })
            .WithMetadata(new AuditPolicyAttribute(needsAudit: true));
        });

    } 
}

public class AuditPolicyAttribute : Attribute
{
    public AuditPolicyAttribute(bool needsAudit)
    {
        NeedsAudit = needsAudit;
    }

    public bool NeedsAudit { get; }
}

앞의 예제에서는 다음과 같은 두 가지 중요한 개념을 보여 줍니다.

  • 미들웨어는 UseRouting 전에 실행되어 라우팅 작동의 기반이 되는 데이터를 수정할 수 있습니다.
  • 미들웨어는 UseRoutingUseEndpoints 사이에서 실행되어 엔드포인트가 실행되기 전에 라우팅의 결과를 처리할 수 있습니다.
    • 다음 사이에 UseRouting 실행되는 미들웨어:UseEndpoints
      • 일반적으로 메타데이터를 검사하여 엔드포인트를 이해합니다.
      • UseAuthorizationUseCors에서 하는 것처럼 보안 결정을 내리는 경우가 많습니다.
    • 미들웨어와 메타데이터를 조합하면 엔드포인트별로 정책을 구성할 수 있습니다.

위의 코드에서는 엔드포인트별 정책을 지원하는 사용자 지정 미들웨어의 예를 보여 줍니다. 이 미들웨어는 중요한 데이터에 대한 액세스의 ‘감사 로그’를 콘솔에 기록합니다. AuditPolicyAttribute 메타데이터를 사용하여 엔드포인트를 ‘감사’하도록 미들웨어를 구성할 수 있습니다. 이 샘플에서는 중요함으로 표시된 엔드포인트만 감사되는 ‘옵트인 패턴’을 보여 줍니다. 예를 들어 이 논리를 역으로 정의하여 안전한 것으로 표시되지 않은 모든 항목을 감사할 수 있습니다. 엔드포인트 메타데이터 시스템은 유연합니다. 이 논리는 사용 사례에 적합한 방식으로 설계할 수 있습니다.

앞의 샘플 코드는 엔드포인트의 기본 개념을 보여 주기 위한 것입니다. 프로덕션 용도로는 사용하지 않아야 합니다. ‘감사 로그’ 미들웨어의 전체 버전은 다음과 같습니다.

  • 파일이나 데이터베이스에 기록합니다.
  • 사용자, IP 주소, 중요한 엔드포인트의 이름 등과 같은 세부 정보를 포함합니다.

감사 정책 메타데이터 AuditPolicyAttribute는 컨트롤러 및 SignalR 같은 클래스 기반 프레임워크에서 더욱 쉽게 사용할 수 있도록 Attribute로 정의됩니다. ‘라우팅 대상 코드’를 사용하면 다음과 같이.

  • 메타데이터가 작성기 API와 연결됩니다.
  • 엔드포인트를 만들 때 해당 메서드 및 클래스의 모든 특성이 클래스 기반 프레임워크에 포함됩니다.

메타데이터 형식은 인터페이스나 특성으로 정의하는 것이 가장 좋습니다. 인터페이스 및 특성을 사용하면 코드를 다시 사용할 수 있습니다. 메타데이터 시스템은 유연하며 제한을 적용하지 않습니다.

터미널 미들웨어와 라우팅 비교

다음 코드 샘플에서는 미들웨어 사용과 라우팅 사용을 비교합니다.

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

    // Approach 1: Writing a terminal middleware.
    app.Use(next => async context =>
    {
        if (context.Request.Path == "/")
        {
            await context.Response.WriteAsync("Hello terminal middleware!");
            return;
        }

        await next(context);
    });

    app.UseRouting();

    app.UseEndpoints(endpoints =>
    {
        // Approach 2: Using routing.
        endpoints.MapGet("/Movie", async context =>
        {
            await context.Response.WriteAsync("Hello routing!");
        });
    });
}

Approach 1:로 표시된 미들웨어 스타일은 터미널 미들웨어입니다. 일치 작업을 수행하기 때문에 터미널 미들웨어라고 합니다.

  • 위 샘플의 일치 작업은 미들웨어의 Path == "/"와 라우팅의 Path == "/Movie"입니다.
  • 일치가 성공하면 일부 기능을 실행하고 next 미들웨어를 호출하는 대신 반환합니다.

검색을 종료하고 일부 기능을 실행한 다음 반환하기 때문에 터미널 미들웨어라고 합니다.

터미널 미들웨어와 라우팅을 비교하면 다음과 같습니다.

  • 두 방법 모두 처리 파이프라인을 종료할 수 있습니다.
    • 미들웨어는 next를 호출하는 대신 반환하여 파이프라인을 종료합니다.
    • 엔드포인트는 항상 터미널입니다.
  • 터미널 미들웨어를 사용하면 파이프라인의 임의 위치에 미들웨어를 배치할 수 있습니다.
    • 엔드포인트는 UseEndpoints의 위치에서 실행됩니다.
  • 터미널 미들웨어를 사용하면 임의의 코드에서 미들웨어가 일치하는 시기를 확인할 수 있습니다.
    • 사용자 지정 경로 일치 코드는 길어져서 올바르게 작성하기 어려울 수 있습니다.
    • 라우팅은 일반적인 앱을 위한 간단한 솔루션을 제공합니다. 앱 대부분에는 사용자 지정 경로 일치 코드가 필요하지 않습니다.
  • 엔드포인트는 UseAuthorizationUseCors 같은 미들웨어와 상호 작용합니다.
    • UseAuthorization 또는 UseCors와 함께 터미널 미들웨어를 사용하려면 권한 부여 시스템을 수동으로 조작해야 합니다.

엔드포인트는 다음 두 가지를 모두 정의합니다.

  • 요청을 처리할 대리자
  • 임의 메타데이터의 컬렉션. 메타데이터는 각 엔드포인트에 연결된 정책과 구성에 따라 횡단 관심사(Cross-Cutting Concerns)를 구현하는 데 사용됩니다.

터미널 미들웨어는 효과적인 도구이지만 다음이 필요할 수 있습니다.

  • 상당한 양의 코딩과 테스트
  • 원하는 수준의 유연성을 얻기 위한 다른 시스템과의 수동 통합

터미널 미들웨어를 작성하기 전에 라우팅과 통합하는 것이 좋습니다.

또는MapWhen과 통합되는 기존 터미널 미들웨어는 일반적으로 라우팅 인식 엔드포인트로 전환될 수 있습니다. MapHealthChecks는 다음과 같은 라우터 방식의 패턴을 보여 줍니다.

  • IEndpointRouteBuilder에 대한 확장 메서드를 작성합니다.
  • CreateApplicationBuilder를 사용하여 중첩된 미들웨어 파이프라인을 만듭니다.
  • 새 파이프라인에 미들웨어를 연결합니다. 이 경우, UseHealthChecks입니다.
  • 미들웨어 파이프라인을 RequestDelegateBuild합니다.
  • Map을 호출하고 새 미들웨어 파이프라인을 제공합니다.
  • 확장 메서드의 Map에서 제공하는 작성기 개체를 반환합니다.

다음 코드에서는 MapHealthChecks를 사용하는 방법을 보여 줍니다.

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

    // Matches request to an endpoint.
    app.UseRouting();

    // Endpoint aware middleware. 
    // Middleware can use metadata from the matched endpoint.
    app.UseAuthentication();
    app.UseAuthorization();

    // Execute the matched endpoint.
    app.UseEndpoints(endpoints =>
    {
        // Configure the Health Check endpoint and require an authorized user.
        endpoints.MapHealthChecks("/healthz").RequireAuthorization();

        // Configure another endpoint, no authorization requirements.
        endpoints.MapGet("/", async context =>
        {
            await context.Response.WriteAsync("Hello World!");
        });
    });
}

앞의 샘플에서는 작성기 개체를 반환하는 것이 중요한 이유를 보여 줍니다. 작성기 개체를 반환하면 앱 개발자가 엔드포인트의 권한 부여와 같은 정책을 구성할 수 있습니다. 이 예제에서는 상태 검사 미들웨어가 권한 부여 시스템과 직접 통합되지 않습니다.

메타데이터 시스템은 터미널 미들웨어를 사용하는 확장성 작성자에서 발생한 문제에 대응하여 만들어졌습니다. 미들웨어마다 권한 부여 시스템과의 고유한 통합을 구현하는 것은 문제가 있습니다.

URL 일치

  • 라우팅이 들어오는 요청을 엔드포인트와 일치시키는 프로세스입니다.
  • URL 경로 및 헤더의 데이터를 기반으로 합니다.
  • 요청의 모든 데이터를 고려하도록 확장될 수 있습니다.

라우팅 미들웨어가 실행되면 Endpoint를 설정하여 현재 요청에서 HttpContext요청 기능으로 값을 라우팅합니다.

  • HttpContext.GetEndpoint를 호출하면 엔드포인트를 가져옵니다.
  • HttpRequest.RouteValues는 경로 값의 컬렉션을 가져옵니다.

미들웨어 는 라우팅 미들웨어가 엔드포인트를 검사하고 작업을 수행할 수 있는 후에 실행됩니다. 예를 들어 권한 부여 미들웨어는 엔드포인트의 메타데이터 컬렉션에서 권한 부여 정책을 조사할 수 있습니다. 요청 처리 파이프라인의 미들웨어가 모두 실행된 후에 선택한 엔드포인트의 대리자가 호출됩니다.

엔드포인트 라우팅의 라우팅 시스템은 모든 디스패치를 결정합니다. 미들웨어는 선택된 엔드포인트에 기반으로 하여 정책을 적용하므로 다음이 중요합니다.

  • 디스패치나 보안 정책의 애플리케이션에 영향을 줄 수 있는 모든 결정은 라우팅 시스템 내에서 내려야 합니다.

Warning

이전 버전과의 호환성을 위해 컨트롤러 또는 Razor Pages 엔드포인트 대리자를 실행할 때 RouteContext.RouteData속성은 지금까지 수행된 요청 처리에 따라 적절한 값으로 설정됩니다.

RouteContext 형식은 이후 릴리스에서 obsolete로 표시됩니다.

  • RouteData.ValuesHttpRequest.RouteValues로 마이그레이션합니다.
  • RouteData.DataTokens를 마이그레이션하여 엔드포인트 메타데이터에서 IDataTokensMetadata를 검색합니다.

URL 일치는 구성 가능한 일련의 단계로 작동합니다. 각 단계의 출력은 일치 항목 집합입니다. 일치 항목 집합은 다음 단계에서 더욱 좁혀질 수 있습니다. 라우팅 구현에서는 일치하는 엔드포인트의 처리 순서를 보장하지 않습니다. 기능한 모든 일치 항목이 한 번에 처리됩니다. URL 일치 단계는 다음 순서로 수행됩니다. ASP.NET Core:

  1. 엔드포인트와 해당 경로 템플릿 집합에 대한 URL 경로를 처리하여 일치 항목을 모두 수집합니다.
  2. 앞의 목록을 사용하여 경로 제약 조건이 적용되지 않는 일치 항목을 제거합니다.
  3. 앞의 목록을 사용하여 MatcherPolicy 인스턴스 집합에서 실패하는 항목을 제거합니다.
  4. EndpointSelector를 사용하여 앞의 목록을 통해 최종 결정을 내립니다.

엔드포인트 목록의 우선 순위는 다음에 따라 지정됩니다.

EndpointSelector에 도달할 때까지 각 단계에서 일치하는 모든 엔드포인트가 처리됩니다. EndpointSelector는 최종 단계이며, 일치 항목에서 가장 높은 우선 순위 엔드포인트를 가장 일치하는 항목으로 선택합니다. 가장 일치하는 항목과 같은 우선 순위의 다른 일치 항목이 있으면 모호한 일치 예외가 throw됩니다.

경로 우선 순위는 더 구체적인 경로 템플릿에 높은 우선 순위가 지정되는 기준에 따라 컴퓨팅됩니다. 예를 들어 /hello/{message} 템플릿을 가정해 보겠습니다.

  • 둘 다 URL 경로 /hello와 일치합니다.
  • /hello가 더 구체적이므로 우선 순위가 높습니다.

일반적으로 경로 우선 순위는 실제로 사용되는 URL 체계에 가장 일치하는 항목을 선택하는 데 좋습니다. 모호성을 방지하는 데 필요한 경우에만 Order를 사용합니다.

라우팅에서 제공하는 확장성의 종류 때문에 라우팅 시스템이 모호한 경로를 미리 컴퓨팅할 수는 없습니다. 경로 템플릿 /{message:alpha}/{message:int}와 같은 예제를 살펴보겠습니다.

  • alpha 제약 조건은 영문자와만 일치합니다.
  • int 제약 조건은 숫자와만 일치합니다.
  • 이러한 템플릿은 경로 우선 순위가 동일하지만 둘 다와 일치하는 단일 URL은 없습니다.
  • 라우팅 시스템에서 시작 시 모호성 오류를 보고한 경우 이 유효한 사용 사례를 차단합니다.

Warning

UseEndpoints 내의 작업 순서는 라우팅 동작에 영향을 주지 않지만 한 가지 예외가 있습니다. MapControllerRouteMapAreaRoute는 호출된 순서를 기준으로 해당 엔드포인트에 순서 값을 자동으로 할당합니다. 이는 이전 라우팅 구현과 동일한 보장을 제공하는 라우팅 시스템이 없이 컨트롤러의 오래된 동작을 시뮬레이트합니다.

라우팅의 레거시 구현에서는 경로가 처리되는 순서에 종속된 라우팅 확장성을 구현할 수 있습니다. ASP.NET Core 3.0 이상의 엔드포인트 라우팅은 다음과 같습니다.

  • 경로 개념이 없습니다.
  • 순서 지정을 보장하지 않습니다. 모든 엔드포인트가 한 번에 처리됩니다.

경로 템플릿 우선 순위 및 엔드포인트 선택 영역 순서

경로 템플릿 우선 순위는 얼마나 구체적인지를 기준으로 각 경로 템플릿에 값을 할당하는 시스템입니다. 경로 템플릿 우선 순위의 특징은 다음과 같습니다.

  • 일반적인 사례에서 엔드포인트 순서를 조정할 필요가 없게 합니다.
  • 라우팅 동작에 관한 일반적인 기대에 맞추려고 합니다.

예를 들어 /Products/List/Products/{id} 템플릿을 가정해 보겠습니다. URL 경로 /Products/List에 대해 /Products/List/Products/{id}보다 더 잘 일치한다고 합리적으로 가정할 수 있습니다. 리터럴 세그먼트 /List가 매개 변수 세그먼트 /{id}보다 우선 순위가 더 높다고 간주되기 때문입니다.

우선 순위의 작동 방식에 대한 세부 정보는 경로 템플릿이 정의된 방법과 어느 정도 관련이 있습니다.

  • 세그먼트가 더 많은 템플릿은 더 구체적인 것으로 간주됩니다.
  • 리터럴 텍스트가 있는 세그먼트가 매개 변수 세그먼트보다 더 구체적인 것으로 간주됩니다.
  • 제약 조건이 있는 매개 변수 세그먼트가 제약 조건이 없는 매개 변수 세그먼트보다 더 구체적인 것으로 간주됩니다.
  • 복잡한 세그먼트는 제약 조건이 있는 매개 변수 세그먼트만큼 구체적인 것으로 간주됩니다.
  • Catch-all 매개 변수가 가장 덜 구체적입니다. Catch-all 경로에 관한 중요한 내용은 경로 템플릿 참조에서 catch-all을 참조하세요.

정확한 값 참조는 source code on GitHub(GitHub의 소스 코드)을 참조하세요.

URL 생성 개념

URL 생성은 다음과 같습니다.

  • 라우팅이 경로 값의 집합을 기반으로 하는 URL 경로를 만들 수 있는 프로세스입니다.
  • 엔드포인트와 이에 액세스하는 URL을 논리적으로 분리할 수 있습니다.

엔드포인트 라우팅에는 LinkGenerator API가 포함됩니다. LinkGeneratorDI에서 사용할 수 있는 싱글톤 서비스입니다. LinkGenerator API는 실행 중인 요청의 컨텍스트 외부에서 사용할 수 있습니다. Mvc.IUrlHelperIUrlHelper를 사용하는 시나리오(예: 태그 도우미, HTML 도우미 및 작업 결과)는 내부적으로 LinkGenerator API를 사용하여 링크 생성 기능을 제공합니다.

링크 생성기는 주소주소 체계의 개념으로 지원됩니다. 주소 체계는 링크 생성을 위해 고려해야 할 엔드포인트를 결정하는 방법입니다. 예를 들어 컨트롤러 및 Razor Pages에서 많은 사용자에게 친숙한 경로 이름 및 경로 값 시나리오는 주소 체계로 구현됩니다.

링크 생성기는 다음 확장 메서드를 통해 컨트롤러 및 Razor Pages에 연결할 수 있습니다.

이러한 메서드의 오버로드에는 HttpContext를 포함한 인수가 허용됩니다. 이러한 메서드는 기능적으로 Url.ActionUrl.Page와 동일하지만, 추가적인 유연성과 옵션을 제공합니다.

GetPath* 메서드는 절대 경로가 포함된 URI를 생성한다는 점에서 Url.ActionUrl.Page와 가장 비슷합니다. GetUri* 메서드는 항상 체계와 호스트를 포함한 절대 URI를 생성합니다. HttpContext를 허용하는 메서드는 실행 중인 요청의 컨텍스트에서 URI를 생성합니다. 재정의되지 않는 한 실행 중인 요청의 앰비언트 경로 값, URL 기본 경로, 체계 및 호스트가 사용됩니다.

LinkGenerator는 주소를 사용하여 호출됩니다. URI 생성은 다음 두 단계로 수행됩니다.

  1. 주소는 해당 주소와 일치하는 엔드포인트 목록에 바인딩됩니다.
  2. 제공된 값과 일치하는 경로 패턴을 찾을 때까지 각 엔드포인트의 RoutePattern이 평가됩니다. 결과 출력은 링크 생성기에 제공된 다른 URI 부분과 결합되어 반환됩니다.

LinkGenerator에서 제공하는 메서드는 모든 유형의 주소에 대해 표준 링크 생성 기능을 지원합니다. 링크 생성기를 사용하는 가장 편리한 방법은 특정 주소 유형에 대한 작업을 수행하는 확장 메서드를 사용하는 것입니다.

확장 메서드 설명
GetPathByAddress 제공된 값에 기반한 절대 경로가 있는 URI를 생성합니다.
GetUriByAddress 제공된 값에 기반한 절대 URI를 생성합니다.

Warning

LinkGenerator 메서드 호출 시 다음과 같은 의미에 주의하세요.

  • 들어오는 요청의 GetUri* 헤더의 유효성을 검사하지 않는 앱 구성에서는 Host 확장 메서드를 신중하게 사용하세요. 들어오는 요청의 Host 헤더의 유효성을 검사하지 않으면 신뢰할 수 없는 요청 입력이 보기 또는 페이지에 포함된 URI로 클라이언트에 다시 보내질 수 있습니다. 모든 프로덕션 앱은 알려진 유효한 값에 대해 Host 헤더의 유효성을 검사하도록 서버를 구성하는 것이 좋습니다.

  • 미들웨어에서 MapWhen 또는 LinkGenerator과 함께 Map를 사용할 때는 신중하게 사용하세요. Map*는 실행 중인 요청의 기본 경로를 변경하여 링크 생성의 출력에 영향을 줍니다. 모든 LinkGenerator API는 기본 경로를 지정할 수 있습니다. 링크 생성에 대한 Map*의 영향을 실행 취소하려면 빈 기본 경로를 지정합니다.

미들웨어 예제

다음 예제에서는 미들웨어에서 LinkGenerator API를 사용하여 상점 제품을 나열하는 작업 메서드에 대한 링크를 만듭니다. 링크 생성기를 클래스에 주입하고 GenerateLink를 호출하여 앱의 모든 클래스에서 해당 링크 생성기를 사용할 수 있습니다.

public class ProductsLinkMiddleware
{
    private readonly LinkGenerator _linkGenerator;

    public ProductsLinkMiddleware(RequestDelegate next, LinkGenerator linkGenerator)
    {
        _linkGenerator = linkGenerator;
    }

    public async Task InvokeAsync(HttpContext httpContext)
    {
        var url = _linkGenerator.GetPathByAction("ListProducts", "Store");

        httpContext.Response.ContentType = "text/plain";

        await httpContext.Response.WriteAsync($"Go to {url} to see our products.");
    }
}

경로 템플릿 참조

{} 내의 토큰은 경로가 일치하는 경우 바인딩될 경로 매개 변수를 정의합니다. 경로 세그먼트에 둘 이상의 경로 매개 변수를 정의할 수 있지만 경로 매개 변수를 리터럴 값으로 분리해야 합니다. 예를 들어 {controller=Home}{action=Index}{controller}{action} 사이에 리터럴 값이 없으므로 유효 경로일 수 없습니다. 경로 매개 변수는 이름이 있어야 하며 지정된 추가 특성을 가질 수 있습니다.

경로 매개 변수 이외의 리터럴 텍스트(예: {id}) 및 경로 구분 기호(/)는 URL의 텍스트와 일치해야 합니다. 텍스트 일치는 대/소문자를 구분하지 않으며 URL 경로의 디코딩된 표현을 기반으로 합니다. 리터럴 경로 매개 변수 구분 기호 { 또는 }와 일치시키려면 문자를 반복하여(예: {{ 또는 }}) 구분 기호를 이스케이프합니다.

별표 * 또는 이중 별표 **:

  • URI의 나머지 부분에 바인딩하기 위해 경로 매개 변수의 접두사로 사용할 수 있습니다.
  • 범용 매개 변수라고 합니다. 예를 들면 다음과 같습니다. blog/{**slug}
    • /blog로 시작하고 그 다음에 임의의 값이 오는 모든 URI를 찾습니다.
    • /blog 다음의 값은 동적 필드 경로 값에 할당됩니다.

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.

범용 매개 변수는 빈 문자열과 일치시킬 수도 있습니다.

범용 매개 변수는 경로 구분 기호 / 문자를 포함하여 URL을 생성하는 데 경로가 사용될 때 적절한 문자를 이스케이프합니다. 예를 들어 경로 값이 { path = "my/path" }인 경로 foo/{*path}foo/my%2Fpath를 생성합니다. 이스케이프된 슬래시에 주의하세요. 경로 구분 기호 문자를 왕복하려면 ** 경로 매개 변수 접두사를 사용합니다. { path = "my/path" }가 있는 경로 foo/{**path}foo/my/path를 생성합니다.

선택적 파일 확장명이 있는 파일 이름을 캡처하려고 시도하는 URL 패턴에는 추가 고려 사항이 있습니다. 예를 들어 템플릿 files/{filename}.{ext?}를 가정해 보겠습니다. filenameext 모두에 대한 값이 있으면 두 값이 채워집니다. URL에 filename에 대한 값만 있으면 후행 .가 선택 사항이므로 경로가 일치합니다. 다음 URL은 이 경로와 일치합니다.

  • /files/myFile.txt
  • /files/myFile

경로 매개 변수에는 등호(=)로 구분된 매개 변수 이름 뒤에 기본값을 지정하여 지정된 기본값이 있을 수 있습니다. 예를 들어 {controller=Home}controller에 대한 기본값으로 Home을 정의합니다. URL에 매개 변수에 대한 값이 없는 경우 기본값이 사용됩니다. 경로 매개 변수는 매개 변수 이름의 끝에 물음표(?)를 추가하면 선택적이 됩니다. 예들 들어 id?입니다. 선택적 값과 기본 경로 매개 변수의 차이는 다음과 같습니다.

  • 기본값이 있는 경로 매개 변수는 항상 값을 생성합니다.
  • 선택적 매개 변수는 요청 URL에서 값을 제공한 경우에만 값이 있습니다.

경로 매개 변수에는 URL에서 바인딩된 경로 값과 일치해야 한다는 제약 조건이 있을 수 있습니다. 경로 매개 변수 이름 뒤에 :과 제약 조건 이름을 추가하여 경로 매개 변수에서 인라인 제약 조건을 지정합니다. 제약 조건에 인수가 필요한 경우 제약 조건 이름 뒤에서 괄호 (...)로 묶입니다. 또 다른 : 및 제약 조건 이름을 추가하여 여러 ‘인라인 제약 조건’을 지정할 수 있습니다.

제약 조건 이름 및 인수는 IRouteConstraint의 인스턴스를 만드는 IInlineConstraintResolver 서비스로 전달되어 URL 처리에서 사용합니다. 예를 들어 경로 템플릿 blog/{article:minlength(10)}는 인수 10으로 minlength 제약 조건을 지정합니다. 경로 제약 조건 및 프레임워크에서 제공하는 제약 조건 목록에 대한 자세한 내용은 경로 제약 조건 참조 섹션을 참조하세요.

경로 매개 변수에는 매개 변수 변환기가 있을 수도 있습니다. 매개 변수 변환기는 링크를 생성하고 URL에 대한 작업 및 페이지와 일치할 때 매개 변수 값을 변환합니다. 제약 조건과 마찬가지로, 매개 변수 변환기는 경로 매개 변수 이름 뒤에 :과 변환기 이름을 추가하여 경로 매개 변수에 인라인으로 추가될 수 있습니다. 예를 들어 경로 템플릿 blog/{article:slugify}slugify 변환기를 지정합니다. 매개 변수 변환기에 대한 자세한 내용은 매개 변수 변환기 참조 섹션을 참조하세요.

다음 표에서는 경로 템플릿 예제 및 해당 동작을 보여 줍니다.

경로 템플릿 URI 일치 예제 요청 URI…
hello /hello /hello 단일 경로만 일치합니다.
{Page=Home} / 일치하고, PageHome으로 설정합니다.
{Page=Home} /Contact 일치하고, PageContact으로 설정합니다.
{controller}/{action}/{id?} /Products/List Products 컨트롤러 및 List 작업에 매핑합니다.
{controller}/{action}/{id?} /Products/Details/123 Products 컨트롤러 및 Details 작업에 매핑합니다(id가 123으로 설정됨).
{controller=Home}/{action=Index}/{id?} / Home 컨트롤러 및 Index 메서드에 매핑합니다. id는 무시됩니다.
{controller=Home}/{action=Index}/{id?} /Products Products 컨트롤러 및 Index 메서드에 매핑합니다. id는 무시됩니다.

템플릿 사용은 일반적으로 라우팅에 대한 가장 간단한 방식입니다. 제약 조건 및 기본값을 경로 템플릿 외부에서 지정할 수도 있습니다.

복잡한 세그먼트

복잡한 세그먼트는 non-greedy 방식으로 오른쪽에서 왼쪽으로 리터럴 구분 기호를 매칭하여 처리됩니다. 예를 들어 [Route("/a{b}c{d}")]는 복잡한 세그먼트입니다. 복잡한 세그먼트는 특정 방식으로 작동하므로 제대로 사용하려면 이 방식을 이해해야 합니다. 이 단원의 예제에서는 매개 변수 내에 구분 기호 텍스트가 표시되지 않는 경우에만 복잡한 세그먼트가 실제로 잘 작동하는 이유를 보여 줍니다. 더욱 복잡한 경우에는 regex를 사용한 다음 값을 수동으로 추출해야 합니다.

Warning

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

다음은 라우팅에서 /a{b}c{d} 템플릿 및 URL 경로 /abcd를 사용하여 수행하는 단계를 요약한 것입니다. |는 알고리즘의 작동 방식을 시각화하는 데 사용됩니다.

  • 오른쪽에서 왼쪽으로 첫 번째 리터럴은 c입니다. 따라서 /abcd가 오른쪽에서 검색되고 /ab|c|d를 찾습니다.
  • 이제 오른쪽의 모든 항목(d)이 경로 매개 변수 {d}와 일치합니다.
  • 오른쪽에서 왼쪽으로 다음 리터럴은 a입니다. 따라서 /ab|c|d가 중단된 위치부터 검색되고 a/|a|b|c|d와 함께 찾습니다.
  • 이제 오른쪽 값(b)이 경로 매개 변수 {b}와 일치합니다.
  • 남은 텍스트가 없고 남은 경로 템플릿도 없으므로 이것이 일치 항목입니다.

다음은 동일한 템플릿 /a{b}c{d}와 URL 경로 /aabcd를 사용하는 부정적인 사례의 예제입니다. |는 알고리즘의 작동 방식을 시각화하는 데 사용됩니다. 이 경우는 동일한 알고리즘으로 설명되는 일치 항목이 아닙니다.

  • 오른쪽에서 왼쪽으로 첫 번째 리터럴은 c입니다. 따라서 /aabcd가 오른쪽에서 검색되고 /aab|c|d를 찾습니다.
  • 이제 오른쪽의 모든 항목(d)이 경로 매개 변수 {d}와 일치합니다.
  • 오른쪽에서 왼쪽으로 다음 리터럴은 a입니다. 따라서 /aab|c|d가 중단된 위치부터 검색되고 a/a|a|b|c|d와 함께 찾습니다.
  • 이제 오른쪽 값(b)이 경로 매개 변수 {b}와 일치합니다.
  • 이제 남은 텍스트 a가 있지만 알고리즘에서 구문 분석할 경로 템플릿이 부족하므로 이것은 일치 항목이 아닙니다.

일치 알고리즘이 non-greedy이므로 다음과 같습니다.

  • 각 단계에서 가능한 최소의 텍스트와 일치합니다.
  • 매개 변수 값 내에 구분 기호 값이 표시되는 모든 경우에는 일치하지 않게 됩니다.

정규식을 사용하면 일치 동작을 더 효율적으로 제어할 수 있습니다.

지연 일치라고도 하는 greedy 일치는 가능한 가장 큰 문자열과 일치합니다. Non-greedy는 가능한 가장 작은 문자열과 일치합니다.

경로 제약 조건 참조

경로 제약 조건은 들어오는 URL과 일치하고 URL 경로가 경로 값으로 토큰화되면 실행됩니다. 일반적으로 경로 제약 조건은 경로 템플릿을 통해 연결된 경로 값을 검사하고 값 허용 여부에 대한 true 또는 false 결정을 내립니다. 일부 경로 제약 조건은 경로 값 외부의 데이터를 사용하여 요청을 라우팅할 수 있는지 여부를 고려합니다. 예를 들어 HttpMethodRouteConstraint는 해당 HTTP 동사에 따라 요청을 허용하거나 거부할 수 있습니다. 제약 조건은 라우팅 요청 및 링크 생성에 사용됩니다.

Warning

제약 조건을 입력 유효성 검사에 사용하지 마세요. 입력 유효성 검사에 제약 조건을 사용하면 잘못된 입력으로 인해 404 찾을 수 없음 응답이 반환됩니다. 입력이 잘못되면 400 잘못된 요청과 해당하는 오류 메시지가 생성됩니다. 경로 제약 조건은 특정 경로에 대한 입력의 유효성을 검사하는 것이 아니라 비슷한 경로를 명확하게 구분하는 데 사용됩니다.

다음 표에서는 경로 제약 조건 예제 및 예상되는 해당 동작을 보여 줍니다.

제약 조건 예제 일치하는 예제 주의
int {id:int} 123456789, -123456789 임의의 정수와 일치
bool {active:bool} true, FALSE true 또는 false와 일치. 대/소문자 구분 안 함
datetime {dob:datetime} 2016-12-31, 2016-12-31 7:32pm 유효한 DateTime 값 일치(고정 문화권에서) 이전 경고를 참조하세요.
decimal {price:decimal} 49.99, -1,000.01 유효한 decimal 값 일치(고정 문화권에서) 이전 경고를 참조하세요.
double {weight:double} 1.234, -1,001.01e8 유효한 double 값 일치(고정 문화권에서) 이전 경고를 참조하세요.
float {weight:float} 1.234, -1,001.01e8 유효한 float 값 일치(고정 문화권에서) 이전 경고를 참조하세요.
guid {id:guid} CD2C1638-1638-72D5-1638-DEADBEEF1638 유효한 Guid 값 일치
long {ticks:long} 123456789, -123456789 유효한 long 값 일치
minlength(value) {username:minlength(4)} Rick 문자열은 4자 이상이어야 합니다.
maxlength(value) {filename:maxlength(8)} MyFile 문자열은 8자 이하여야 합니다.
length(length) {filename:length(12)} somefile.txt 문자열은 정확히 12자여야 합니다.
length(min,max) {filename:length(8,16)} somefile.txt 문자열의 길이는 8자 이상이며 16자 이하여야 합니다.
min(value) {age:min(18)} 19 정수 값은 18 이상이어야 합니다.
max(value) {age:max(120)} 91 정수 값은 120 이하여야 합니다.
range(min,max) {age:range(18,120)} 91 정수 값은 18 이상이며 120 이하여야 합니다.
alpha {name:alpha} Rick 문자열은 하나 이상의 영문자(a-z, 대/소문자 구분)로 구성되어야 합니다.
regex(expression) {ssn:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)} 123-45-6789 문자열은 정규식과 일치해야 합니다. 정규식을 정의하는 방법에 대한 팁을 참조하세요.
required {name:required} Rick URL을 생성하는 동안 비-매개 변수 값이 존재하도록 강제하는 데 사용됨

Warning

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

콜론으로 구분된 여러 개의 제약 조건을 단일 매개 변수에 적용할 수 있습니다. 예를 들어 다음 제약 조건은 매개 변수를 1 이상의 정수 값으로 제한합니다.

[Route("users/{id:int:min(1)}")]
public User GetUserById(int id) { }

Warning

CLR 형식으로 변환되는 URL을 확인하는 경로 제약 조건은 항상 고정 문화권을 사용합니다(예: CLR 형식 int 또는 DateTime으로 변환). 이러한 제약 조건은 URL은 지역화될 수 없다고 가정합니다. 프레임워크에서 제공한 경로 제약 조건은 경로 값에 저장된 값을 수정하지 않습니다. URL에서 구문 분석되는 모든 경로 값은 문자열로 저장됩니다. 예를 들어 float 제약 조건은 경로 값을 부동으로 변환하려고 하지만 변환된 값은 부동으로 변환될 수 있는지 확인하는 데만 사용됩니다.

제약 조건의 정규식

Warning

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

regex(...) 경로 제약 조건을 사용하여 정규식을 인라인 제약 조건으로 지정할 수 있습니다. MapControllerRoute 제품군의 메서드에서는 개체 리터럴의 제약 조건도 사용할 수 있습니다. 해당 양식을 사용하는 경우 문자열 값이 정규식으로 해석됩니다.

다음 코드에서는 인라인 regex 제약 조건을 사용합니다.

app.UseEndpoints(endpoints =>
{
    endpoints.MapGet("{message:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)}",
        context => 
        {
            return context.Response.WriteAsync("inline-constraint match");
        });
 });

다음 코드에서는 개체 리터럴을 사용하여 regex 제약 조건을 지정합니다.

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(
        name: "people",
        pattern: "People/{ssn}",
        constraints: new { ssn = "^\\d{3}-\\d{2}-\\d{4}$", },
        defaults: new { controller = "People", action = "List", });
});

ASP.NET Core 프레임워크는 정규식 생성자에 RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariant를 추가합니다. 이러한 멤버에 대한 설명은 RegexOptions를 참조하세요.

정규식은 라우팅 및 C# 언어에서 사용하는 것과 유사한 구분 기호 및 토큰을 사용합니다. 정규식 토큰은 이스케이프되어야 합니다. 인라인 제약 조건에서 정규식 ^\d{3}-\d{2}-\d{4}$를 사용하려면 다음 중 하나를 사용합니다.

  • \ 문자열 이스케이프 문자를 이스케이프하기 위해 C# 소스 파일에서 문자열에 제공된 \ 문자를 \\ 문자로 바꿉니다.
  • 축자 문자열 리터럴.

라우팅 매개 변수 구분 기호 문자({, }, [, ])를 이스케이프하려면 식에서 해당 문자를 이중으로 사용합니다(예: {{, }}, [[, ]]). 다음 표에서는 정규식 및 이스케이프된 버전을 보여 줍니다.

정규식 이스케이프된 정규식
^\d{3}-\d{2}-\d{4}$ ^\\d{{3}}-\\d{{2}}-\\d{{4}}$
^[a-z]{2}$ ^[[a-z]]{{2}}$

라우팅에 사용되는 정규식은 ^ 문자로 시작하고 문자열의 시작 위치와 일치하는 경우가 많습니다. 식은 $ 문자로 끝나고 문자열의 끝과 일치하는 경우가 많습니다. ^$ 문자는 정규식이 전체 경로 매개 변수 값과 일치하도록 합니다. ^$ 문자가 없는 정규식은 문자열 내의 모든 하위 문자열과 일치하지만, 이는 종종 원하는 것이 아닙니다. 다음 표에서는 예제를 제공하고, 일치하거나 일치에 실패하는 이유를 설명합니다.

문자열 Match Comment(설명)
[a-z]{2} hello 부분 문자열 일치
[a-z]{2} 123abc456 부분 문자열 일치
[a-z]{2} mz 식 일치
[a-z]{2} MZ 대/소문자 구분하지 않음
^[a-z]{2}$ hello 아니요 위의 ^$ 참조
^[a-z]{2}$ 123abc456 아니요 위의 ^$ 참조

정규식 구문에 대한 자세한 내용은 .NET Framework 정규식을 참조하세요.

가능한 값의 알려진 집합으로 매개 변수를 제한하려면 정규식을 사용합니다. 예를 들어 {action:regex(^(list|get|create)$)}action 경로 값을 list, get 또는 create으로만 일치시킵니다. 제약 조건 사전으로 전달되면 ^(list|get|create)$ 문자열은 동일합니다. 알려진 제약 조건 중 하나와 일치하지 않는 제약 조건 사전에서 전달되는 제약 조건도 정규식으로 처리됩니다. 알려진 제약 조건 중 하나와 일치하지 않는 템플릿 내에서 전달되는 제약 조건은 정규식으로 처리되지 않습니다.

사용자 지정 경로 제약 조건

IRouteConstraint 인터페이스를 구현하여 사용자 지정 경로 제약 조건을 만들 수 있습니다. IRouteConstraint 인터페이스에는 제약 조건이 충족되는 경우 true를 반환하고 그렇지 않은 경우 false를 반환하는 Match가 포함됩니다.

사용자 지정 경로 제약 조건은 거의 필요하지 않습니다. 사용자 지정 경로 제약 조건을 구현하기 전에 모델 바인딩과 같은 다른 방식을 고려해 보세요.

ASP.NET Core Constraints 폴더는 제약 조건을 만드는 좋은 예제를 제공합니다. 예를 들어 GuidRouteConstraint입니다.

사용자 지정 IRouteConstraint를 사용하려면 서비스 컨테이너에 있는 앱의 ConstraintMap에 경로 제약 조건 형식을 등록해야 합니다. ConstraintMap은 경로 제약 조건 키를 해당 제약 조건의 유효성을 검사하는 IRouteConstraint 구현으로 매핑하는 사전입니다. Startup.ConfigureServices에서 services.AddRouting 호출의 일부로 또는 services.Configure<RouteOptions>를 사용하여 직접 RouteOptions를 구성하여 앱의 ConstraintMap을 수정할 수 있습니다. 예시:

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

    services.AddRouting(options =>
    {
        options.ConstraintMap.Add("customName", typeof(MyCustomConstraint));
    });
}

위의 제약 조건은 다음 코드에서 적용됩니다.

[Route("api/[controller]")]
[ApiController]
public class TestController : ControllerBase
{
    // GET /api/test/3
    [HttpGet("{id:customName}")]
    public IActionResult Get(string id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }

    // GET /api/test/my/3
    [HttpGet("my/{id:customName}")]
    public IActionResult Get(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

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

MyCustomConstraint를 구현하면 경로 매개 변수에 0이 적용되지 않습니다.

class MyCustomConstraint : IRouteConstraint
{
    private Regex _regex;

    public MyCustomConstraint()
    {
        _regex = new Regex(@"^[1-9]*$",
                            RegexOptions.CultureInvariant | RegexOptions.IgnoreCase,
                            TimeSpan.FromMilliseconds(100));
    }
    public bool Match(HttpContext httpContext, IRouter route, string routeKey,
                      RouteValueDictionary values, RouteDirection routeDirection)
    {
        if (values.TryGetValue(routeKey, out object value))
        {
            var parameterValueString = Convert.ToString(value,
                                                        CultureInfo.InvariantCulture);
            if (parameterValueString == null)
            {
                return false;
            }

            return _regex.IsMatch(parameterValueString);
        }

        return false;
    }
}

Warning

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

앞의 코드가 하는 역할은 다음과 같습니다.

  • 경로의 {id} 세그먼트에 0을 사용하지 못하게 합니다.
  • 사용자 지정 제약 조건을 구현하는 기본 예제를 제공하기 위해 표시됩니다. 프로덕션 앱에 사용해서는 안 됩니다.

다음 코드는 0이 포함된 id가 처리되지 않게 하는 더 나은 방법입니다.

[HttpGet("{id}")]
public IActionResult Get(string id)
{
    if (id.Contains('0'))
    {
        return StatusCode(StatusCodes.Status406NotAcceptable);
    }

    return ControllerContext.MyDisplayRouteInfo(id);
}

위의 코드는 MyCustomConstraint 접근 방식과 비교하면 다음과 같은 이점이 있습니다.

  • 사용자 지정 제약 조건이 필요하지 않습니다.
  • 경로 매개 변수에 0이 포함된 경우 더 자세한 설명이 포함된 오류를 반환합니다.

매개 변수 변환기 참조

매개 변수 변환기는:

예를 들어, Url.Action(new { article = "MyTestArticle" })을 사용하는 경로 패턴 blog\{article:slugify}의 사용자 지정 slugify 매개 변수 변환기는 blog\my-test-article을 생성합니다.

다음 IOutboundParameterTransformer 구현을 생각해 보겠습니다.

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();
    }
}

경로 패턴에서 매개 변수 변환기를 사용하려면 Startup.ConfigureServicesConstraintMap을 사용하여 구성합니다.

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

    services.AddRouting(options =>
    {
        options.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer);
    });
}

ASP.NET Core Framework는 매개 변수 변화기를 사용하여 엔드포인트가 해결되는 URI를 변환합니다. 예를 들어 매개 변수 변환기는 area, controller, actionpage와 일치하도록 사용되는 경로 값을 변환합니다.

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

위의 경로 템플릿을 사용하면 SubscriptionManagementController.GetAll 작업이 URI /subscription-management/get-all과 일치합니다. 매개 변수 변환기는 링크를 생성하는 데 사용되는 경로 값을 변경하지 않습니다. 예를 들어 Url.Action("GetAll", "SubscriptionManagement")/subscription-management/get-all을 출력합니다.

ASP.NET Core는 생성된 경로와 함께 매개 변수 변환기를 사용하기 위한 API 규칙을 제공합니다.

URL 생성 참조

이 단원에는 URL 생성에서 구현하는 알고리즘에 대한 참조가 포함되어 있습니다. 실제로 URL 생성의 가장 복잡한 예제에서는 컨트롤러나 Razor Pages를 사용합니다. 자세한 내용은 컨트롤러의 라우팅을 참조하세요.

URL 생성 프로세스는 LinkGenerator. GetPathByAddress 또는 이와 유사한 메서드를 호출하여 시작합니다. 메서드에는 주소, 경로 값 집합 및 HttpContext의 현재 요청에 관한 정보(선택 사항)를 제공합니다.

첫 번째 단계에서는 주소를 사용하여 주소의 형식과 일치하는 IEndpointAddressScheme<TAddress>을 사용하는 후보 엔드포인트 집합을 확인합니다.

주소 체계에 따라 후보 세트를 찾으면 URL 생성 작업이 성공할 때까지 엔드포인트가 순서 지정되고 반복적으로 처리됩니다. URL 생성에서는 모호성을 확인하지 않으며, 반환되는 첫 번째 결과가 최종 결과입니다.

로깅을 사용하여 URL 생성 문제 해결

URL 생성 문제를 해결하는 첫 번째 단계는 Microsoft.AspNetCore.Routing의 로깅 수준을 TRACE로 설정하는 것입니다. LinkGenerator는 문제 해결에 유용할 수 있는 처리에 관한 여러 세부 정보를 기록합니다.

URL 생성에 대한 자세한 내용은 URL 생성 참조를 참조하세요.

주소

주소는 URL 생성에서 링크 생성기의 호출을 후보 엔드포인트 집합에 바인딩하는 데 사용되는 개념입니다.

주소는 기본적으로 다음 두 가지 구현이 함께 제공되는 확장 가능한 개념입니다.

  • 엔드포인트 이름(string)을 주소로 사용:
    • MVC의 경로 이름과 유사한 기능을 제공합니다.
    • IEndpointNameMetadata 메타데이터 형식을 사용합니다.
    • 등록된 모든 엔드포인트의 메타데이터와 제공된 문자열을 비교하여 확인합니다.
    • 여러 엔드포인트에서 같은 이름을 사용하는 경우 시작 시 예외를 throw합니다.
    • 컨트롤러 및 Razor Pages 외의 일반적인 용도에 사용하는 것이 좋습니다.
  • 경로 값(RouteValuesAddress)을 주소로 사용:
    • 컨트롤러 및 Razor Pages 레거시 URL 생성과 비슷한 기능을 제공합니다.
    • 확장 및 디버그하기가 매우 복잡합니다.
    • IUrlHelper, 태그 도우미, HTML 도우미, 작업 결과 등에서 사용하는 구현을 제공합니다.

주소 체계의 역할은 다음과 같은 임의 조건에 따라 주소와 일치하는 엔드포인트 사이를 연결하는 것입니다.

  • 엔드포인트 이름 체계에서 기본 사전 조회를 수행합니다.
  • 경로 값 체계에는 알고리즘의 복잡한 최적 하위 집합이 있습니다.

앰비언트 값 및 명시적 값

현재 요청에서 라우팅은 현재 요청의 경로 값 HttpContext.Request.RouteValues에 액세스합니다. 현재 요청과 연결된 값을 앰비언트 값이라고 합니다. 명확하게 하도록 설명서에서는 메서드에 전달된 경로 값을 명시적 값이라고 합니다.

다음 예제에서는 앰비언트 값과 명시적 값을 보여 줍니다. 현재 요청의 앰비언트 값과 명시적 값 { id = 17, }을 제공합니다.

public class WidgetController : Controller
{
    private readonly LinkGenerator _linkGenerator;

    public WidgetController(LinkGenerator linkGenerator)
    {
        _linkGenerator = linkGenerator;
    }

    public IActionResult Index()
    {
        var url = _linkGenerator.GetPathByAction(HttpContext,
                                                 null, null,
                                                 new { id = 17, });
        return Content(url);
    }

앞의 코드가 하는 역할은 다음과 같습니다.

  • /Widget/Index/17를 반환합니다.
  • DI를 통해 LinkGenerator를 가져옵니다.

다음 코드는 앰비언트 값과 { controller = "Home", action = "Subscribe", id = 17, }을 제공하지 않습니다.

public IActionResult Index2()
{
    var url = _linkGenerator.GetPathByAction("Subscribe", "Home",
                                             new { id = 17, });
    return Content(url);
}

앞의 메서드는 /Home/Subscribe/17을 반환합니다.

WidgetController의 다음 코드는 /Widget/Subscribe/17을 반환합니다.

var url = _linkGenerator.GetPathByAction("Subscribe", null,
                                         new { id = 17, });

다음 코드는 현재 요청의 앰비언트 값과 명시적 값 { action = "Edit", id = 17, }을 컨트롤러에 제공합니다.

public class GadgetController : Controller
{
    public IActionResult Index()
    {
        var url = Url.Action("Edit", new { id = 17, });
        return Content(url);
    }

위의 코드에서

  • /Gadget/Edit/17이 반환됩니다.
  • UrlIUrlHelper를 가져옵니다.
  • Action은 작업 메서드의 절대 경로가 포함된 URL을 생성합니다. URL에는 지정된 action 이름과 route 값이 포함됩니다.

다음 코드에서는 현재 요청의 앰비언트 값과 명시적 값 { page = "./Edit, id = 17, }을 제공합니다.

public class IndexModel : PageModel
{
    public void OnGet()
    {
        var url = Url.Page("./Edit", new { id = 17, });
        ViewData["URL"] = url;
    }
}

앞의 코드에서는 Razor 편집 페이지에 다음 페이지 지시문이 포함된 경우 url/Edit/17로 설정합니다.

@page "{id:int}"

편집 페이지에 "{id:int}" 경로 템플릿이 포함되어 있지 않으면 url/Edit?id=17입니다.

MVC의 IUrlHelper 동작은 여기에 설명된 규칙 외에도 복잡성 계층을 추가합니다.

  • IUrlHelper는 항상 현재 요청의 경로 값을 앰비언트 값으로 제공합니다.
  • IUrlHelper.Action은 개발자가 재정의하는 경우 외에는 항상 현재 actioncontroller 경로 값을 명시적 값으로 복사합니다.
  • IUrlHelper.Page는 재정의되는 경우 외에는 항상 현재 page 경로 값을 명시적 값으로 복사합니다.
  • IUrlHelper.Page는 재정의되는 경우 외에는 항상 현재 handler 경로 값을 명시적 값 null로 재정의합니다.

MVC가 자체 규칙을 따르지 않기 때문에 앰비언트 값의 동작 세부 정보에 사용자가 놀라는 경우가 많습니다. 기록 및 호환성을 위해 action, controller, page, handler 등의 특정 경로 값에는 고유한 특수 사례 동작이 있습니다.

LinkGenerator.GetPathByActionLinkGenerator.GetPathByPage에서 제공하는 동일한 기능에서는 호환성을 위해 IUrlHelper의 이러한 변칙을 복제합니다.

URL 생성 프로세스

후보 엔드포인트 집합을 찾으면 URL 생성 알고리즘은 다음을 수행합니다.

  • 엔드포인트를 반복적으로 처리합니다.
  • 첫 번째 성공적인 결과를 반환합니다.

이 프로세스의 첫 번째 단계를 경로 값 무효화라고 합니다. 경로 값 무효화는 라우팅에서 앰비언트 값의 어떤 경로 값을 사용하고 무시할지 결정하는 프로세스입니다. 각 앰비언트 값이 고려되고 명시적 값과 결합되거나 무시됩니다.

앰비언트 값의 역할을 알아보려면 일반적인 사례에서 애플리케이션 개발자 입력을 저장하려고 하는 점을 고려하면 가장 좋습니다. 일반적으로 앰비언트 값이 유용한 시나리오는 MVC와 관련이 있습니다.

  • 동일한 컨트롤러의 다른 작업에 연결하는 경우에는 컨트롤러 이름을 지정할 필요가 없습니다.
  • 같은 영역의 다른 컨트롤러에 연결하는 경우 영역 이름을 지정할 필요가 없습니다.
  • 동일한 작업 메서드에 연결하는 경우 경로 값을 지정할 필요가 없습니다.
  • 앱의 다른 부분에 연결하는 경우 앱의 해당 부분에서 의미가 없는 경로 값을 전달하지 않을 수 있습니다.

null을 반환하는 LinkGenerator 또는 IUrlHelper를 호출하면 일반적으로 이해되지 않는 경로 값 무효화가 발생합니다. 경로 값 무효화 문제를 해결하려면 더 많은 경로 값을 명시적으로 지정하여 문제가 해결되는지 확인합니다.

경로 값 무효화는 앱의 URL 체계가 계층이 왼쪽에서 오른쪽으로 형성된 계층 구조라고 가정하고 작동합니다. 기본 컨트롤러 경로 템플릿 {controller}/{action}/{id?}를 사용하여 실제 작동 방법을 직관적으로 파악해 보겠습니다. 값을 변경하면 오른쪽에 표시되는 경로 값이 모두 무효화됩니다. 이는 계층 구조에 관한 가정이 반영된 것입니다. 앱에 id의 앰비언트 값이 있고 작업에서 controller에 대해 다른 값을 지정하는 경우:

  • {controller}{id?}의 왼쪽에 있으므로 id가 다시 사용되지 않습니다.

이 원칙을 보여 주는 몇 가지 예는 다음과 같습니다.

  • 명시적 값에 id의 값이 포함된 경우 id의 앰비언트 값은 무시됩니다. controlleraction의 앰비언트 값이 사용될 수 있습니다.
  • 명시적 값에 action의 값이 포함된 경우 action의 앰비언트 값은 무시됩니다. controller의 앰비언트 값이 사용될 수 있습니다. action의 명시적 값이 action의 앰비언트 값과 다른 경우 id 값은 사용되지 않습니다. action의 명시적 값이 action의 앰비언트 값과 같으면 id 값이 사용될 수 있습니다.
  • 명시적 값에 controller의 값이 포함된 경우 controller의 앰비언트 값은 무시됩니다. controller의 명시적 값이 controller의 앰비언트 값과 다른 경우 actionid 값은 사용되지 않습니다. controller의 명시적 값이 controller의 앰비언트 값과 같으면 actionid 값이 사용될 수 있습니다.

이 프로세스는 특성 경로와 전용 규칙 기반 경로가 있으면 더 복잡해집니다. {controller}/{action}/{id?}와 같은 컨트롤러 규칙 기반 경로는 경로 매개 변수를 사용하여 계층 구조를 지정합니다. 컨트롤러 및 Razor Pages에 대한 전용 규칙 기반 경로특성 경로의 경우:

  • 경로 값의 계층 구조가 있습니다.
  • 템플릿에는 표시되지 않습니다.

이러한 경우 URL 생성에서 필수 값 개념을 정의합니다. 컨트롤러 및 Razor Pages에서 만든 엔드포인트에는 경로 값 무효화가 작동할 수 있도록 필수 값이 지정되어 있습니다.

경로 값 무효화 알고리즘을 자세히 설명하면 다음과 같습니다.

  • 필요 값 이름을 경로 매개 변수와 결합한 다음 왼쪽에서 오른쪽으로 처리합니다.
  • 각 매개 변수에 대해 앰비언트 값과 명시적 값이 비교됩니다.
    • 앰비언트 값과 명시적 값이 같으면 프로세스가 계속됩니다.
    • 앰비언트 값이 있고 명시적 값이 없으면 URL을 생성할 때 앰비언트 값이 사용됩니다.
    • 앰비언트 값이 없고 명시적 값이 있으면 앰비언트 값과 모든 후속 앰비언트 값을 거부합니다.
    • 앰비언트 값과 명시적 값이 있고 두 값이 다른 경우 앰비언트 값과 모든 후속 앰비언트 값을 거부합니다.

이제 URL 생성 작업에서 경로 제약 조건을 평가할 준비가 되었습니다. 허용되는 값 집합이 제약 조건에 제공되는 매개 변수 기본값과 결합됩니다. 제약 조건이 모두 통과되면 작업이 계속됩니다.

다음으로 허용되는 값을 사용하여 경로 템플릿을 확장할 수 있습니다. 경로 템플릿은 다음과 같이 처리됩니다.

  • 왼쪽에서 오른쪽으로
  • 각 매개 변수의 허용되는 값이 대체됩니다.
  • 다음과 같은 특수한 경우를 사용합니다.
    • 허용되는 값에 누락된 값이 있고 매개 변수에 기본값이 있으면 기본값이 사용됩니다.
    • 허용되는 값에 누락된 값이 있고 매개 변수가 선택 사항이면 처리가 계속됩니다.
    • 누락된 선택적 매개 변수 오른쪽의 경로 매개 변수에 값이 있으면 작업이 실패합니다.
    • 연속된 기본값 매개 변수 및 선택적 매개 변수는 가능하면 축소됩니다.

명시적으로 제공되지만 경로의 세그먼트와 일치하지 않는 값은 쿼리 문자열에 추가됩니다. 다음 표에서 경로 템플릿 {controller}/{action}/{id?}를 사용하는 경우 결과를 보여 줍니다.

앰비언트 값 명시적 값 결과
controller = "Home" action = "About" /Home/About
controller = "Home" controller = "Order", action = "About" /Order/About
controller = "Home", color = "Red" action = "About" /Home/About
controller = "Home" action = "About", color = "Red" /Home/About?color=Red

경로 값 무효화 문제

ASP.NET Core 3.0부터 이전 ASP.NET Core 버전에서 사용되는 일부 URL 생성 체계가 URL 생성에서 잘 작동하지 않습니다. ASP.NET Core 팀은 향후 릴리스에서 이러한 요구를 해결하는 기능을 추가할 계획입니다. 지금은 레거시 라우팅을 사용하는 것이 가장 좋습니다.

다음 코드에서는 라우팅에서 지원하지 않는 URL 생성 체계의 예제를 보여 줍니다.

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute("default", 
                                     "{culture}/{controller=Home}/{action=Index}/{id?}");
    endpoints.MapControllerRoute("blog", "{culture}/{**slug}", 
                                      new { controller = "Blog", action = "ReadPost", });
});

위의 코드에서 culture 경로 매개 변수는 지역화에 사용됩니다. culture 매개 변수가 앰비언트 값으로 항상 허용되게 하려는 것이 목표입니다. 그러나 culture 매개 변수는 필수 값이 작동하는 방식 때문에 앰비언트 값으로 허용되지 않습니다.

  • "default" 경로 템플릿에서 culture 경로 매개 변수는 controller의 왼쪽에 있으므로 controller를 변경해도 culture가 무효화되지 않습니다.
  • "blog" 경로 템플릿에서 culture 경로 매개 변수는 필수 값에 표시되는 controller의 오른쪽에 있는 것으로 간주됩니다.

엔드포인트 메타데이터 구성

다음 링크는 엔드포인트 메타데이터를 구성하는 방법에 대한 정보를 제공합니다.

RequireHost가 있는 경로의 호스트 일치

RequireHost는 지정된 호스트가 필요한 경로에 제약 조건을 적용합니다. RequireHost 또는 [Host] 매개 변수는 다음과 같을 수 있습니다.

  • 호스트: www.domain.com(아무 포트에서나 www.domain.com과 일치)
  • 와일드카드가 있는 호스트: *.domain.com(아무 포트에서나 www.domain.com, subdomain.domain.com 또는 www.subdomain.domain.com과 일치)
  • 포트: *:5000(아무 호스트에서나 포트 5000과 일치)
  • 호스트 및 포트: www.domain.com:5000 또는 *.domain.com:5000(호스트 및 포트와 일치)

RequireHost 또는 [Host]를 사용하여 여러 매개 변수를 지정할 수 있습니다. 제약 조건은 모든 매개 변수에 유효한 호스트와 일치합니다. 예를 들어 [Host("domain.com", "*.domain.com")]domain.com, www.domain.comsubdomain.domain.com과 일치합니다.

다음 코드는 RequireHost를 사용하여 경로상에 있는 지정된 호스트를 요구합니다.

public void Configure(IApplicationBuilder app)
{
    app.UseRouting();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapGet("/", context => context.Response.WriteAsync("Hi Contoso!"))
            .RequireHost("contoso.com");
        endpoints.MapGet("/", context => context.Response.WriteAsync("AdventureWorks!"))
            .RequireHost("adventure-works.com");
        endpoints.MapHealthChecks("/healthz").RequireHost("*:8080");
    });
}

다음 코드는 컨트롤러의 [Host] 특성을 사용하여 지정된 호스트를 요구합니다.

[Host("contoso.com", "adventure-works.com")]
public class ProductController : Controller
{
    public IActionResult Index()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [Host("example.com:8080")]
    public IActionResult Privacy()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

[Host] 특성이 컨트롤러 메서드와 작업 메서드에 모두 적용될 경우

  • 작업의 특성이 사용됩니다.
  • 컨트롤러의 특성은 무시됩니다.

라우팅의 성능 지침

ASP.NET Core 3.0에서는 성능 향상을 위해 대부분의 라우팅이 업데이트되었습니다.

앱에 성능 문제가 있는 경우 라우팅이 문제의 원인으로 의심받는 경우가 많습니다. 라우팅이 의심받는 이유는 컨트롤러 및 Razor Pages 같은 프레임워크가 프레임워크 내에서 소요된 시간을 로깅 메시지로 보고하기 때문입니다. 컨트롤러에서 보고하는 시간과 요청의 총 시간 사이에 상당한 차이가 있는 경우:

  • 개발자는 문제의 원인인 앱 코드를 제거합니다.
  • 일반적으로 라우팅이 원인이라고 가정합니다.

라우팅은 수천 개의 엔드포인트를 사용하여 성능을 테스트했습니다. 일반적인 앱에서는 너무 크다고 성능 문제가 발생할 가능성은 거의 없습니다. 라우팅 성능이 저하되는 가장 일반적인 근본 원인은 일반적으로 잘못 동작하는 사용자 지정 미들웨어입니다.

다음 코드 샘플에서는 지연의 원인을 좁히기 위한 기본 기술을 보여 줍니다.

public void Configure(IApplicationBuilder app, ILogger<Startup> logger)
{
    app.Use(next => async context =>
    {
        var sw = Stopwatch.StartNew();
        await next(context);
        sw.Stop();

        logger.LogInformation("Time 1: {ElapsedMilliseconds}ms", sw.ElapsedMilliseconds);
    });

    app.UseRouting();

    app.Use(next => async context =>
    {
        var sw = Stopwatch.StartNew();
        await next(context);
        sw.Stop();

        logger.LogInformation("Time 2: {ElapsedMilliseconds}ms", sw.ElapsedMilliseconds);
    });

    app.UseAuthorization();

    app.Use(next => async context =>
    {
        var sw = Stopwatch.StartNew();
        await next(context);
        sw.Stop();

        logger.LogInformation("Time 3: {ElapsedMilliseconds}ms", sw.ElapsedMilliseconds);
    });

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapGet("/", async context =>
        {
            await context.Response.WriteAsync("Timing test.");
        });
    });
}

종료 시간 라우팅:

  • 앞의 코드에 표시된 타이밍 미들웨어의 복사본을 각 미들웨어에 인터리빙합니다.
  • 고유 식별자를 추가하여 타이밍 데이터를 코드와 연관 짓습니다.

이것이 예를 들어 10ms 이상의 상당한 지연이 발생하는 경우 지연을 좁히는 기본적인 방법입니다. Time 1에서 Time 2를 빼면 UseRouting 미들웨어 내에서 소요된 시간이 보고됩니다.

다음 코드에서는 앞의 타이밍 코드에 더욱 간결한 방법을 사용합니다.

public sealed class MyStopwatch : IDisposable
{
    ILogger<Startup> _logger;
    string _message;
    Stopwatch _sw;

    public MyStopwatch(ILogger<Startup> logger, string message)
    {
        _logger = logger;
        _message = message;
        _sw = Stopwatch.StartNew();
    }

    private bool disposed = false;


    public void Dispose()
    {
        if (!disposed)
        {
            _logger.LogInformation("{Message }: {ElapsedMilliseconds}ms",
                                    _message, _sw.ElapsedMilliseconds);

            disposed = true;
        }
    }
}
public void Configure(IApplicationBuilder app, ILogger<Startup> logger)
{
    int count = 0;
    app.Use(next => async context =>
    {
        using (new MyStopwatch(logger, $"Time {++count}"))
        {
            await next(context);
        }

    });

    app.UseRouting();

    app.Use(next => async context =>
    {
        using (new MyStopwatch(logger, $"Time {++count}"))
        {
            await next(context);
        }
    });

    app.UseAuthorization();

    app.Use(next => async context =>
    {
        using (new MyStopwatch(logger, $"Time {++count}"))
        {
            await next(context);
        }
    });

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapGet("/", async context =>
        {
            await context.Response.WriteAsync("Timing test.");
        });
    });
}

비용이 많이 들 수 있는 라우팅 기능

다음 목록에서는 기본 경로 템플릿보다 비교적 비용이 많이 드는 라우팅 기능에 대한 몇 가지 유용한 정보를 제공합니다.

  • 정규식: 복잡한 정규식을 작성하거나 적은 양의 입력으로 장기 실행 시간을 가질 수 있습니다.
  • 복합 세그먼트({x}-{y}-{z}):
    • 일반 URL 경로 세그먼트를 구문 분석하는 것보다 훨씬 비용이 많이 듭니다.
    • 더 많은 부분 문자열이 할당됩니다.
    • 복잡한 세그먼트 논리는 ASP.NET Core 3.0 라우팅 성능 업데이트에서 업데이트되지 않았습니다.
  • 동기 데이터 액세스: 많은 복잡한 앱은 라우팅의 일부로 데이터베이스 액세스 권한을 갖습니다. ASP.NET Core 2.2 및 이전 라우팅에서는 데이터베이스 액세스 라우팅을 지원하기 위한 적합한 확장 포인트를 제공할 수 없습니다. 예를 들어 IRouteConstraintIActionConstraint는 동기식입니다. MatcherPolicyEndpointSelectorContext 같은 확장 포인트는 비동기식입니다.

라이브러리 작성자를 위한 지침

이 단원에는 라우팅을 기반으로 빌드하는 라이브러리 작성자를 위한 지침이 포함되어 있습니다. 이러한 세부 내용은 앱 개발자가 라우팅을 확장하는 라이브러리 및 프레임워크를 사용하는 좋은 환경을 갖추도록 하기 위한 것입니다.

엔드포인트 정의

URL 일치를 위해 라우팅을 사용하는 프레임워크를 만들려면 먼저 UseEndpoints를 기반으로 빌드되는 사용자 환경을 정의합니다.

IEndpointRouteBuilder를 기반으로 빌드하세요. 이렇게 하면 사용자는 혼동하지 않고 다른 ASP.NET Core 기능을 사용하여 프레임워크를 작성할 수 있습니다. 모든 ASP.NET Core 템플릿에는 라우팅이 포함됩니다. 라우팅이 있고 사용자에게 친숙하다고 간주합니다.

app.UseEndpoints(endpoints =>
{
    // Your framework
    endpoints.MapMyFramework(...);

    endpoints.MapHealthChecks("/healthz");
});

IEndpointConventionBuilder를 구현하는 MapMyFramework(...) 호출에서 봉인된 구체적인 형식을 반환하세요. 대부분의 프레임워크 Map... 메서드는 이 패턴을 따릅니다. IEndpointConventionBuilder 인터페이스:

  • 메타데이터를 작성 가능하게 합니다.
  • 다양한 확장 메서드의 대상으로 지정됩니다.

고유의 형식을 선언하면 작성기에 사용자 고유의 프레임워크 관련 기능을 추가할 수 있습니다. 프레임워크 선언된 작성기를 래핑하고 여기에 호출을 전달해도 됩니다.

app.UseEndpoints(endpoints =>
{
    // Your framework
    endpoints.MapMyFramework(...).RequireAuthorization()
                                 .WithMyFrameworkFeature(awesome: true);

    endpoints.MapHealthChecks("/healthz");
});

사용자 고유의 EndpointDataSource를 작성하는 것이 좋습니다. EndpointDataSource는 엔드포인트 컬렉션을 선언하고 업데이트하기 위한 하위 수준 기본 형식입니다. EndpointDataSource는 컨트롤러 및 Razor Pages에서 사용되는 강력한 API입니다.

라우팅 테스트에는 업데이트되지 않는 데이터 원본의 기본 예제가 있습니다.

기본적으로 EndpointDataSource를 등록하지 마세요. 프레임워크를 UseEndpoints에 등록하도록 사용자에게 요구하세요. 라우팅의 원리에 따르면 기본적으로 아무것도 포함되지 않으며 UseEndpoints가 엔드포인트를 등록하는 위치입니다.

라우팅 통합 미들웨어 만들기

메타데이터 형식을 인터페이스로 정의하는 것이 좋습니다.

메타데이터 형식을 클래스 및 메서드의 특성으로 사용할 수 있게 하세요.

public interface ICoolMetadata
{
    bool IsCool { get; }
}

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CoolMetadataAttribute : Attribute, ICoolMetadata
{
    public bool IsCool => true;
}

컨트롤러 및 Razor Pages와 같은 프레임워크는 형식 및 메서드에 메타데이터 특성을 적용하도록 지원합니다. 메타데이터 형식을 선언하는 경우:

  • 이 형식에 특성으로 액세스할 수 있습니다.
  • 사용자 대부분이 특성을 적용하는 데 익숙합니다.

메타데이터 형식을 인터페이스로 선언하면 또 하나의 유연성 계층이 추가됩니다.

  • 인터페이스는 구성할 수 있습니다.
  • 개발자가 여러 정책을 결합하여 고유한 형식을 선언할 수 있습니다.

다음 예제와 같이 메타데이터를 재정의할 수 있게 하세요.

public interface ICoolMetadata
{
    bool IsCool { get; }
}

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CoolMetadataAttribute : Attribute, ICoolMetadata
{
    public bool IsCool => true;
}

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class SuppressCoolMetadataAttribute : Attribute, ICoolMetadata
{
    public bool IsCool => false;
}

[CoolMetadata]
public class MyController : Controller
{
    public void MyCool() { }

    [SuppressCoolMetadata]
    public void Uncool() { }
}

이러한 지침을 따르는 가장 좋은 방법은 마커 메타데이터를 정의하지 않는 것입니다.

  • 메타데이터 형식이 있는지만 확인하면 안 됩니다.
  • 메타데이터의 속성을 정의하고 속성을 확인합니다.

메타데이터 컬렉션은 순서 지정되며 우선 순위별로 재정의할 수 있습니다. 컨트롤러의 경우 작업 메서드의 메타데이터가 가장 구체적입니다.

라우팅이 있는지와 관계없이 미들웨어를 유용하게 사용할 수 있게 하세요.

app.UseRouting();

app.UseAuthorization(new AuthorizationPolicy() { ... });

app.UseEndpoints(endpoints =>
{
    // Your framework
    endpoints.MapMyFramework(...).RequireAuthorization();
});

이 지침의 예제에서는 UseAuthorization 미들웨어를 고려해 보겠습니다. 권한 부여 미들웨어를 사용하면 대체 정책을 전달할 수 있습니다. 대체 정책(지정된 경우)은 다음 모두에 적용됩니다.

  • 지정된 정책이 없는 엔드포인트
  • 엔드포인트와 일치하지 않는 요청

따라서 권한 부여 미들웨어는 라우팅 컨텍스트 외에서도 유용합니다. 권한 부여 미들웨어는 기존 미들웨어 프로그래밍에 사용할 수 있습니다.

디버그 진단

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

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