Filtry v aplikacích s minimálním rozhraním API
Fiyaz Bin Hasan, Martin Costello a Rick Anderson
Minimální filtry rozhraní API umožňují vývojářům implementovat obchodní logiku, která podporuje:
- Spuštění kódu před a za obslužnou rutinou koncového bodu
- Kontrola a úprava parametrů zadaných během vyvolání obslužné rutiny koncového bodu
- Zachycení chování odpovědi obslužné rutiny koncového bodu
Filtry můžou být užitečné v následujících scénářích:
- Ověření parametrů požadavku a textu odesílaných do koncového bodu
- Protokolování informací o požadavku a odpovědi
- Ověření, že požadavek cílí na podporovanou verzi rozhraní API.
Filtry lze zaregistrovat zadáním delegáta, který přebírá EndpointFilterInvocationContext
a vrací hodnotu EndpointFilterDelegate
. Poskytuje EndpointFilterInvocationContext
přístup k HttpContext
požadavku a Arguments
seznam označující argumenty předané obslužné rutině v pořadí, ve kterém se zobrazí v deklaraci obslužné rutiny.
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();
Předchozí kód:
- Zavolá metodu
AddEndpointFilter
rozšíření, která přidá do koncového/colorSelector/{color}
bodu filtr. - Vrátí barvu určenou s výjimkou hodnoty
"Red"
. - Vrátí hodnotu Results.Problem , pokud je
/colorSelector/Red
požadována. - Používá
next
se jakoEndpointFilterDelegate
ainvocationContext
jakoEndpointFilterInvocationContext
vyvolání dalšího filtru v kanálu nebo delegát požadavku, pokud byl vyvolán poslední filtr.
Filtr se spustí před obslužnou rutinou koncového bodu. Když se u obslužné rutiny provede více AddEndpointFilter
vyvolání:
- Kód filtru volaný před
EndpointFilterDelegate
zavolání (next
) se provede v pořadí podle pořadí First In, First Out (FIFO). - Kód filtru volaný po
EndpointFilterDelegate
zavolání (next
) se spustí v pořadí podle pořadí First In, Last Out (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();
V předchozím kódu filtry a koncový bod protokolují následující výstup:
Before first filter
Before 2nd filter
Before 3rd filter
Endpoint
After 3rd filter
After 2nd filter
After first filter
Následující kód používá filtry, které implementují IEndpointFilter
rozhraní:
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();
V předchozím kódu zobrazují filtry a obslužné rutiny pořadí jejich spuštění:
AEndpointFilter Before next
BEndpointFilter Before next
CEndpointFilter Before next
Endpoint
CEndpointFilter After next
BEndpointFilter After next
AEndpointFilter After next
Filtry implementují IEndpointFilter
rozhraní v následujícím příkladu:
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) { }
}
Ověření objektu pomocí filtru
Zvažte filtr, který ověřuje Todo
objekt:
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);
});
V předchozím kódu:
- Objekt
EndpointFilterInvocationContext
poskytuje přístup k parametrům přidruženým k určitému požadavku vydanému koncovému bodu prostřednictvímGetArguments
metody. - Filtr je registrován pomocí,
delegate
který přebíráEndpointFilterInvocationContext
a vracíEndpointFilterDelegate
.
Kromě předávání jako delegáty je možné filtry zaregistrovat implementací IEndpointFilter
rozhraní. Následující kód ukazuje předchozí zapouzdřený filtr ve třídě, která implementuje 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);
}
}
Filtry, které implementují IEndpointFilter
rozhraní, mohou přeložit závislosti z injektáže závislostí (DI), jak je znázorněno v předchozím kódu. I když filtry můžou vyřešit závislosti z DI, samotné filtry se nedají rozpoznat z DI.
Použije se ToDoIsValidFilter
na následující koncové body:
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>();
Následující filtr ověří Todo
objekt a upraví Name
vlastnost:
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);
}
}
Registrace filtru pomocí objektu pro filtrování koncových bodů
V některých scénářích může být nutné uložit některé informace uvedené ve MethodInfo
filtru do mezipaměti. Předpokládejme například, že jsme chtěli ověřit, že obslužná rutina, ke které je připojený filtr koncového bodu, má první parametr, který se vyhodnotí jako Todo
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);
});
V předchozím kódu:
- Objekt
EndpointFilterFactoryContext
poskytuje přístup kMethodInfo
obslužné rutině koncového bodu. - Podpis obslužné rutiny je zkoumán kontrolou
MethodInfo
očekávaného podpisu typu. Pokud se najde očekávaný podpis, ověřovací filtr se zaregistruje do koncového bodu. Tento vzor továrny je užitečný k registraci filtru, který závisí na podpisu obslužné rutiny cílového koncového bodu. - Pokud se nenajde odpovídající podpis, zaregistruje se průchozí filtr.
Registrace filtru akcí kontroleru
V některých scénářích může být nutné použít stejnou logiku filtru pro koncové body založené na obslužné rutině tras i akce kontroleru. V tomto scénáři je možné vyvolat volání AddEndpointFilter
, ControllerActionEndpointConventionBuilder
které podporuje provádění stejné logiky filtru u akcí a koncových bodů.
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();
Další materiály
Váš názor
https://aka.ms/ContentUserFeedback.
Připravujeme: V průběhu roku 2024 budeme postupně vyřazovat problémy z GitHub coby mechanismus zpětné vazby pro obsah a nahrazovat ho novým systémem zpětné vazby. Další informace naleznete v tématu:Odeslat a zobrazit názory pro