Uwaga
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Note
Nie jest to najnowsza wersja tego artykułu. Aby zapoznać się z bieżącą wersją, zobacz wersję tego artykułu platformy .NET 9.
Important
Te informacje odnoszą się do produktu w wersji wstępnej, który może zostać znacząco zmodyfikowany, zanim zostanie wydany komercyjnie. Firma Microsoft nie udziela żadnych gwarancji, jawnych lub domniemanych, w odniesieniu do informacji podanych w tym miejscu.
Aby zapoznać się z bieżącą wersją, zobacz wersję tego artykułu platformy .NET 9.
Przez Fiyaz Bin Hasan, Martin Costello i Rick Anderson
Minimalne filtry interfejsu API umożliwiają deweloperom implementowanie logiki biznesowej, która obsługuje:
- Uruchamianie kodu przed programem obsługi punktu końcowego i po nim.
- Sprawdzanie i modyfikowanie parametrów podanych podczas wywołania programu obsługi punktu końcowego.
- Przechwytywanie zachowania odpowiedzi programu obsługi punktu końcowego.
Filtry mogą być przydatne w następujących scenariuszach:
- Weryfikowanie parametrów żądania i treści wysyłanych do punktu końcowego.
- Rejestrowanie informacji o żądaniu i odpowiedzi.
- Weryfikowanie, czy żądanie dotyczy obsługiwanej wersji interfejsu API.
Filtry można zarejestrować, podając delegata Obiekt EndpointFilterInvocationContext zapewnia dostęp do HttpContext żądania i Arguments listy wskazującej argumenty przekazane do programu obsługi w kolejności, w której są wyświetlane w deklaracji programu obsługi.
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();
Powyższy kod ma następujące działanie:
- Wywołuje metodę
AddEndpointFilterrozszerzenia, aby dodać filtr do punktu końcowego/colorSelector/{color}. - Zwraca określony kolor z wyjątkiem wartości
"Red". - Zwraca wartość Results.Problem , gdy
/colorSelector/Redżądanie jest żądane. - Używa
nextelementu iEndpointFilterDelegatejakoinvocationContextEndpointFilterInvocationContextelementu , aby wywołać następny filtr w potoku lub delegat żądania, jeśli ostatni filtr został wywołany.
Filtr jest uruchamiany przed programem obsługi punktu końcowego. Gdy na procedurze obsługi jest wykonywane wiele AddEndpointFilter wywołań:
- Kod filtru wywoływany przed wywołaniem
EndpointFilterDelegate(next) jest wykonywany w kolejności od pierwszego wyjętego wyjścia (FIFO). - Kod filtru wywoływany po wywołaniu
EndpointFilterDelegatemetody (next) jest wykonywany w kolejności od pierwszego w, ostatniego wyjścia (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();
W poprzednim kodzie filtry i dziennik punktu końcowego zawierają następujące dane wyjściowe:
Before first filter
Before 2nd filter
Before 3rd filter
Endpoint
After 3rd filter
After 2nd filter
After first filter
Poniższy kod używa filtrów implementujących IEndpointFilter interfejs:
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();
W poprzednim kodzie dzienniki filtrów i procedur obsługi pokazują kolejność ich uruchamiania:
AEndpointFilter Before next
BEndpointFilter Before next
CEndpointFilter Before next
Endpoint
CEndpointFilter After next
BEndpointFilter After next
AEndpointFilter After next
Filtry implementowane interfejsu IEndpointFilter są wyświetlane w poniższym przykładzie:
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) { }
}
Weryfikowanie obiektu za pomocą filtru
Rozważ filtr weryfikujący Todo obiekt:
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);
});
Powyższy kod:
- Obiekt
EndpointFilterInvocationContextzapewnia dostęp do parametrów skojarzonych z określonym żądaniem wystawionym dla punktu końcowegoGetArgumentsza pośrednictwem metody . - Filtr jest rejestrowany przy użyciu elementu
delegate, który przyjmujeEndpointFilterInvocationContextelement i zwraca wartośćEndpointFilterDelegate.
Oprócz przekazywania jako delegatów filtry można zarejestrować przez zaimplementowanie interfejsu IEndpointFilter . Poniższy kod przedstawia powyższy filtr hermetyzowany w klasie, która implementuje IEndpointFilterelement :
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);
}
}
Filtry, które implementują IEndpointFilter interfejs, mogą rozpoznawać zależności od wstrzykiwania zależności (DI), jak pokazano w poprzednim kodzie. Chociaż filtry mogą rozwiązywać zależności z di, same filtry nie mogą być rozpoznawane z di.
Element ToDoIsValidFilter jest stosowany do następujących punktów końcowych:
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>();
Następujący filtr weryfikuje Todo obiekt i modyfikuje Name właściwość:
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);
}
}
Rejestrowanie filtru przy użyciu fabryki filtrów punktów końcowych
W niektórych scenariuszach może być konieczne buforowanie niektórych informacji podanych w MethodInfo filtrze. Załóżmy na przykład, że chcemy sprawdzić, czy program obsługi jest dołączony do filtru punktu końcowego Todo , ma pierwszy parametr, który ocenia typ.
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);
});
Powyższy kod:
- Obiekt
EndpointFilterFactoryContextzapewnia dostęp do skojarzonegoMethodInfoz programem obsługi punktu końcowego. - Podpis procedury obsługi jest badany przez sprawdzenie
MethodInfopod kątem oczekiwanego podpisu typu. Jeśli zostanie znaleziony oczekiwany podpis, filtr weryfikacji zostanie zarejestrowany w punkcie końcowym. Ten wzorzec fabryki jest przydatny do rejestrowania filtru, który zależy od podpisu docelowego programu obsługi punktów końcowych. - Jeśli pasujący podpis nie zostanie znaleziony, zostanie zarejestrowany filtr przekazywania.
Rejestrowanie filtru w akcjach kontrolera
W niektórych scenariuszach może być konieczne zastosowanie tej samej logiki filtru dla punktów końcowych opartych na trasach i akcji kontrolera. W tym scenariuszu można wywołać metodę AddEndpointFilter w ControllerActionEndpointConventionBuilder celu obsługi wykonywania tej samej logiki filtru w akcjach i punktach końcowych.
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();