참고 항목
이 문서의 최신 버전은 아닙니다. 현재 릴리스는 이 문서의 .NET 9 버전을 참조 하세요.
Important
이 정보는 상업적으로 출시되기 전에 실질적으로 수정될 수 있는 시험판 제품과 관련이 있습니다. Microsoft는 여기에 제공된 정보에 대해 어떠한 명시적, 또는 묵시적인 보증을 하지 않습니다.
현재 릴리스는 이 문서의 .NET 9 버전을 참조 하세요.
작성자: Fiyaz Bin Hasan, Martin Costello, Rick Anderson
최소 API 필터를 통해 개발자는 다음을 지원하는 비즈니스 논리를 구현할 수 있습니다.
- 엔드포인트 처리기 전후에 코드 실행
- 엔드포인트 처리기 호출 중에 제공된 매개 변수 검사 및 수정
- 엔드포인트 처리기의 응답 동작 가로채기
필터는 다음 시나리오에서 유용할 수 있습니다.
- 엔드포인트로 전송되는 요청 매개 변수 및 본문의 유효성 검사
- 요청 및 응답에 대한 정보 로깅
- 요청이 지원되는 API 버전을 대상으로 하는지 확인
필터는 EndpointFilterInvocationContext
를 사용하고 EndpointFilterDelegate
를 반환하는 대리자를 제공하여 등록할 수 있습니다. EndpointFilterInvocationContext
는 요청의 HttpContext
에 대한 액세스 권한과 처리기에 전달된 인수를 처리기 선언에 나타나는 순서대로 표시하는 Arguments
목록을 제공합니다.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
string ColorName(string color) => $"Color specified: {color}!";
app.MapGet("/colorSelector/{color}", ColorName)
.AddEndpointFilter(async (invocationContext, next) =>
{
var color = invocationContext.GetArgument<string>(0);
if (color == "Red")
{
return Results.Problem("Red not allowed!");
}
return await next(invocationContext);
});
app.Run();
앞의 코드가 하는 역할은 다음과 같습니다.
AddEndpointFilter
확장 메서드를 호출하여/colorSelector/{color}
엔드포인트에 필터를 추가합니다."Red"
값을 제외하고 지정된 색을 반환합니다./colorSelector/Red
가 요청되면 Results.Problem을 반환합니다.- 마지막 필터가 호출된 경우
EndpointFilterDelegate
로next
를 사용하고EndpointFilterInvocationContext
로invocationContext
를 사용하여 파이프라인 또는 요청 대리자에서 다음 필터를 호출합니다.
필터는 엔드포인트 처리기 전에 실행됩니다. 처리기에서 여러 AddEndpointFilter
호출이 수행되는 경우
EndpointFilterDelegate
(next
)가 호출되기 전에 호출된 필터 코드는 FIFO(선입선출) 순서로 실행됩니다.EndpointFilterDelegate
(next
)가 호출된 후에 호출된 필터 코드는 FILO(선입후출) 순서로 실행됩니다.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () =>
{
app.Logger.LogInformation(" Endpoint");
return "Test of multiple filters";
})
.AddEndpointFilter(async (efiContext, next) =>
{
app.Logger.LogInformation("Before first filter");
var result = await next(efiContext);
app.Logger.LogInformation("After first filter");
return result;
})
.AddEndpointFilter(async (efiContext, next) =>
{
app.Logger.LogInformation(" Before 2nd filter");
var result = await next(efiContext);
app.Logger.LogInformation(" After 2nd filter");
return result;
})
.AddEndpointFilter(async (efiContext, next) =>
{
app.Logger.LogInformation(" Before 3rd filter");
var result = await next(efiContext);
app.Logger.LogInformation(" After 3rd filter");
return result;
});
app.Run();
위의 코드에서 필터 및 엔드포인트는 다음 출력을 로그합니다.
Before first filter
Before 2nd filter
Before 3rd filter
Endpoint
After 3rd filter
After 2nd filter
After first filter
다음 코드는 IEndpointFilter
인터페이스를 구현하는 필터를 사용합니다.
using Filters.EndpointFilters;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () =>
{
app.Logger.LogInformation("Endpoint");
return "Test of multiple filters";
})
.AddEndpointFilter<AEndpointFilter>()
.AddEndpointFilter<BEndpointFilter>()
.AddEndpointFilter<CEndpointFilter>();
app.Run();
위의 코드에서 필터 및 처리기 로그는 이들이 실행되는 순서를 보여 줍니다.
AEndpointFilter Before next
BEndpointFilter Before next
CEndpointFilter Before next
Endpoint
CEndpointFilter After next
BEndpointFilter After next
AEndpointFilter After next
IEndpointFilter
인터페이스를 구현하는 필터는 다음 예제에 나와 있습니다.
namespace Filters.EndpointFilters;
public abstract class ABCEndpointFilters : IEndpointFilter
{
protected readonly ILogger Logger;
private readonly string _methodName;
protected ABCEndpointFilters(ILoggerFactory loggerFactory)
{
Logger = loggerFactory.CreateLogger<ABCEndpointFilters>();
_methodName = GetType().Name;
}
public virtual async ValueTask<object?> InvokeAsync(EndpointFilterInvocationContext context,
EndpointFilterDelegate next)
{
Logger.LogInformation("{MethodName} Before next", _methodName);
var result = await next(context);
Logger.LogInformation("{MethodName} After next", _methodName);
return result;
}
}
class AEndpointFilter : ABCEndpointFilters
{
public AEndpointFilter(ILoggerFactory loggerFactory) : base(loggerFactory) { }
}
class BEndpointFilter : ABCEndpointFilters
{
public BEndpointFilter(ILoggerFactory loggerFactory) : base(loggerFactory) { }
}
class CEndpointFilter : ABCEndpointFilters
{
public CEndpointFilter(ILoggerFactory loggerFactory) : base(loggerFactory) { }
}
필터를 사용하여 개체 유효성 검사
Todo
개체의 유효성을 검사하는 필터를 생각해 보겠습니다.
app.MapPut("/todoitems/{id}", async (Todo inputTodo, int id, TodoDb db) =>
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return Results.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return Results.NoContent();
}).AddEndpointFilter(async (efiContext, next) =>
{
var tdparam = efiContext.GetArgument<Todo>(0);
var validationError = Utilities.IsValid(tdparam);
if (!string.IsNullOrEmpty(validationError))
{
return Results.Problem(validationError);
}
return await next(efiContext);
});
위의 코드에서
EndpointFilterInvocationContext
개체는GetArguments
메서드를 통해 엔드포인트에 발급된 특정 요청과 연결된 매개 변수에 대한 액세스를 제공합니다.- 필터는
EndpointFilterInvocationContext
를 사용하고EndpointFilterDelegate
를 반환하는delegate
를 사용하여 등록됩니다.
대리자로 전달되는 것 외에도 IEndpointFilter
인터페이스를 구현하여 필터를 등록할 수 있습니다. 다음 코드는 구현하는 클래스에 캡슐화된 이전 필터를 보여 줍니다.IEndpointFilter
public class TodoIsValidFilter : IEndpointFilter
{
private ILogger _logger;
public TodoIsValidFilter(ILoggerFactory loggerFactory)
{
_logger = loggerFactory.CreateLogger<TodoIsValidFilter>();
}
public async ValueTask<object?> InvokeAsync(EndpointFilterInvocationContext efiContext,
EndpointFilterDelegate next)
{
var todo = efiContext.GetArgument<Todo>(0);
var validationError = Utilities.IsValid(todo!);
if (!string.IsNullOrEmpty(validationError))
{
_logger.LogWarning(validationError);
return Results.Problem(validationError);
}
return await next(efiContext);
}
}
IEndpointFilter
인터페이스를 구현하는 필터는 이전 코드와 같이 DI(종속성 주입)에서 종속성을 확인할 수 있습니다. 필터는 DI에서 종속성을 확인할 수 있지만 DI에서 필터 자체를 확인할 수는 없습니다.
ToDoIsValidFilter
는 다음 엔드포인트에 적용됩니다.
app.MapPut("/todoitems2/{id}", async (Todo inputTodo, int id, TodoDb db) =>
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return Results.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return Results.NoContent();
}).AddEndpointFilter<TodoIsValidFilter>();
app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
}).AddEndpointFilter<TodoIsValidFilter>();
다음 필터는 Todo
개체의 유효성을 검사하고 Name
속성을 수정합니다.
public class TodoIsValidUcFilter : IEndpointFilter
{
public async ValueTask<object?> InvokeAsync(EndpointFilterInvocationContext efiContext,
EndpointFilterDelegate next)
{
var todo = efiContext.GetArgument<Todo>(0);
todo.Name = todo.Name!.ToUpper();
var validationError = Utilities.IsValid(todo!);
if (!string.IsNullOrEmpty(validationError))
{
return Results.Problem(validationError);
}
return await next(efiContext);
}
}
엔드포인트 필터 팩터리를 사용하여 필터 등록
일부 시나리오에서는 필터에서 MethodInfo
에 제공된 일부 정보를 캐시해야 할 수 있습니다. 예를 들어, 엔드포인트 필터가 연결된 처리기에 Todo
형식으로 계산 되는 첫 번째 매개 변수가 있는지 확인한다고 가정해 보겠습니다.
app.MapPut("/todoitems/{id}", async (Todo inputTodo, int id, TodoDb db) =>
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return Results.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return Results.NoContent();
}).AddEndpointFilterFactory((filterFactoryContext, next) =>
{
var parameters = filterFactoryContext.MethodInfo.GetParameters();
if (parameters.Length >= 1 && parameters[0].ParameterType == typeof(Todo))
{
return async invocationContext =>
{
var todoParam = invocationContext.GetArgument<Todo>(0);
var validationError = Utilities.IsValid(todoParam);
if (!string.IsNullOrEmpty(validationError))
{
return Results.Problem(validationError);
}
return await next(invocationContext);
};
}
return invocationContext => next(invocationContext);
});
위의 코드에서
EndpointFilterFactoryContext
개체는 엔드포인트의 처리기와 연결된MethodInfo
에 대한 액세스를 제공합니다.- 처리기의 서명은 예상되는 형식 서명에 대한
MethodInfo
을 검사하여 검사됩니다. 필요한 서명이 발견되면 유효성 검사 필터가 엔드포인트에 등록됩니다. 이 팩터리 패턴은 대상 엔드포인트 처리기의 서명에 따라 달라지는 필터를 등록하는 데 유용합니다. - 일치하는 서명을 찾을 수 없는 경우 통과 필터가 등록됩니다.
컨트롤러 작업에 필터 등록
일부 시나리오에서는 경로 처리기 기반 엔드포인트와 컨트롤러 작업 모두에 대해 동일한 필터 논리를 적용해야 할 수 있습니다. 이 시나리오에서는 AddEndpointFilter
를 ControllerActionEndpointConventionBuilder
에 호출하여 작업 및 엔드포인트에서 동일한 필터 논리 실행을 지원할 수 있습니다.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapController()
.AddEndpointFilter(async (efiContext, next) =>
{
efiContext.HttpContext.Items["endpointFilterCalled"] = true;
var result = await next(efiContext);
return result;
});
app.Run();
추가 리소스
ASP.NET Core