Filter in Minimal-API-Apps
Von Fiyaz Bin Hasan, Martin Costello und Rick Anderson
Minimal-API-Filter ermöglichen Entwicklern die Implementierung von Geschäftslogik, die Folgendes unterstützt:
- Ausführen von Code vor und nach dem Endpunkthandler
- Untersuchen und Ändern von Parametern, die während des Aufrufs eines Endpunkthandlers bereitgestellt werden
- Abfangen des Antwortverhaltens eines Endpunkthandlers
Filter können in den folgenden Szenarien hilfreich sein:
- Überprüfen der Anforderungsparameter und des Texts, die an einen Endpunkt gesendet werden.
- Protokollierung von Informationen über die Anforderung und Antwort.
- Überprüfen, ob eine Anforderung auf eine unterstützte API-Version ausgerichtet ist.
Filter können registriert werden, indem ein Delegat bereitgestellt wird, der einen EndpointFilterInvocationContext
entgegennimmt und einen EndpointFilterDelegate
zurückgibt. Der EndpointFilterInvocationContext
bietet Zugriff auf den HttpContext
der Anforderung und eine Arguments
-Liste, die die an den Handler übergebenen Argumente in der Reihenfolge angibt, in der sie in der Deklaration des Handlers auftreten.
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();
Der vorangehende Code:
- Ruft die
AddEndpointFilter
-Erweiterungsmethode auf, um dem/colorSelector/{color}
-Endpunkt einen Filter hinzuzufügen. - Gibt die angegebene Farbe außer dem Wert
"Red"
zurück. - Gibt Results.Problem zurück, wenn
/colorSelector/Red
angefordert wird. - Verwendet
next
alsEndpointFilterDelegate
undinvocationContext
alsEndpointFilterInvocationContext
, um den nächsten Filter in der Pipeline oder dem Anforderungsdelegaten aufzurufen, wenn der letzte Filter aufgerufen wurde.
Der Filter wird vor dem Endpunkthandler ausgeführt. Wenn mehrere AddEndpointFilter
-Aufrufe auf einem Handler vorgenommen werden:
- Filtercode, der aufgerufen wird, bevor
EndpointFilterDelegate
(next
) aufgerufen wird, wird in der Reihenfolge First In, First Out (FIFO) ausgeführt. - Filtercode, der aufgerufen wird, nachdem
EndpointFilterDelegate
(next
) aufgerufen wird, wird in der Reihenfolge First In, Last Out (FILO) ausgeführt.
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();
Im vorherigen Code protokollieren die Filter und Endpunkte folgende Ausgabe:
Before first filter
Before 2nd filter
Before 3rd filter
Endpoint
After 3rd filter
After 2nd filter
After first filter
Der folgende Code verwendet Filter, die die IEndpointFilter
-Schnittstelle implementieren:
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();
Im vorherigen Code zeigen die Filter- und Handlerprotokolle die Reihenfolge an, in der sie ausgeführt werden:
AEndpointFilter Before next
BEndpointFilter Before next
CEndpointFilter Before next
Endpoint
CEndpointFilter After next
BEndpointFilter After next
AEndpointFilter After next
Filter, die die IEndpointFilter
-Schnittstelle implementieren, werden im folgenden Beispiel gezeigt:
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) { }
}
Überprüfen eines Objekts mit einem Filter
Betrachten Sie einen Filter, der ein Todo
-Objekt überprüft:
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);
});
Für den Code oben gilt:
- Das
EndpointFilterInvocationContext
-Objekt bietet Zugriff auf die Parameter, die einer bestimmten Anforderung zugeordnet sind, die über dieGetArguments
-Methode an den Endpunkt ausgegeben wurde. - Der Filter wird mithilfe eines
delegate
registriert, der einenEndpointFilterInvocationContext
entgegennimmt und einenEndpointFilterDelegate
zurückgibt.
Zusätzlich zum Übergeben als Delegaten können Filter durch Implementieren der IEndpointFilter
-Schnittstelle registriert werden. Der folgende Code zeigt den vorherigen Filter, der in einer Klasse gekapselt ist, die IEndpointFilter
implementiert:
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);
}
}
Filter, die die IEndpointFilter
-Schnittstelle implementieren, können Abhängigkeiten von Dependency Injection (DI) auflösen, wie im vorherigen Code gezeigt. Obwohl Filter Abhängigkeiten aus DI auflösen können, können Filter selbst nicht aus DI aufgelöst werden.
Der ToDoIsValidFilter
wird auf die folgenden Endpunkte angewendet:
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>();
Der folgende Filter überprüft das Todo
-Objekt und ändert die Name
-Eigenschaft:
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);
}
}
Registrieren eines Filters mit einer Endpunktfilter-Factory
In einigen Szenarien ist es möglicherweise erforderlich, einige der Informationen zwischenzuspeichern, die einem Filter in MethodInfo
bereitgestellt werden. Angenommen, wir möchten überprüfen, ob der Handler, an den ein Endpunktfilter angefügt ist, einen ersten Parameter hat, der zu einem Todo
-Typ ausgewertet wird.
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);
});
Für den Code oben gilt:
- Das
EndpointFilterFactoryContext
-Objekt bietet Zugriff auf dieMethodInfo
, die dem Handler des Endpunkts zugeordnet ist. - Die Signatur des Handlers wird geprüft, indem
MethodInfo
auf die erwartete Typsignatur untersucht wird. Wenn die erwartete Signatur gefunden wird, wird der Validierungsfilter auf dem Endpunkt registriert. Dieses Factorymuster ist nützlich, um einen Filter zu registrieren, der von der Signatur des Zielendpunkthandlers abhängt. - Wird keine übereinstimmende Signatur gefunden, dann wird ein Passthrough-Filter registriert.
Registrieren eines Filters für Controlleraktionen
In einigen Szenarien kann es erforderlich sein, sowohl für routenhandlerbasierte Endpunkte als auch für Controlleraktionen dieselbe Filterlogik anzuwenden. Für dieses Szenario kann AddEndpointFilter
auf ControllerActionEndpointConventionBuilder
aufgerufen werden, um die Ausführung derselben Filterlogik auf Aktionen und Endpunkten zu unterstützen.
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();
Weitere Ressourcen
- Umfassende Einblicke in Endpunktfilter
- Anzeigen oder Herunterladen von Beispielcode (Vorgehensweise zum Herunterladen)
- ValidationFilterRouteHandlerBuilderExtensions Validierungserweiterungsmethoden.
- Tutorial: Erstellen einer minimalen API mit ASP.NET Core
- Authentifizierung und Autorisierung in Minimal-API-Apps
Feedback
https://aka.ms/ContentUserFeedback.
Bald verfügbar: Im Laufe des Jahres 2024 werden wir GitHub-Issues stufenweise als Feedbackmechanismus für Inhalte abbauen und durch ein neues Feedbacksystem ersetzen. Weitere Informationen finden Sie unterFeedback senden und anzeigen für