Filter di aplikasi Minimal API
Oleh Fiyaz Bin Hasan, Martin Costello, dan Rick Anderson
Filter API minimal memungkinkan pengembang untuk menerapkan logika bisnis yang mendukung:
- Menjalankan kode sebelum dan sesudah handler titik akhir.
- Memeriksa dan memodifikasi parameter yang disediakan selama pemanggilan handler titik akhir.
- Mencegat perilaku respons handler titik akhir.
Filter dapat membantu dalam skenario berikut:
- Memvalidasi parameter dan isi permintaan yang dikirim ke titik akhir.
- Mencatat informasi tentang permintaan dan respons.
- Memvalidasi bahwa permintaan menargetkan versi API yang didukung.
Filter dapat didaftarkan dengan menyediakan Delegasi yang mengambil EndpointFilterInvocationContext
dan mengembalikan EndpointFilterDelegate
. EndpointFilterInvocationContext
menyediakan akses ke HttpContext
permintaan dan daftar yang Arguments
menunjukkan argumen yang diteruskan ke handler dalam urutan muncul dalam deklarasi handler.
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();
Kode sebelumnya:
AddEndpointFilter
Memanggil metode ekstensi untuk menambahkan filter ke/colorSelector/{color}
titik akhir.- Mengembalikan warna yang ditentukan kecuali untuk nilai
"Red"
. - Mengembalikan Hasil.Masalah saat
/colorSelector/Red
diminta. next
Menggunakan sebagai daninvocationContext
sebagaiEndpointFilterDelegate
EndpointFilterInvocationContext
untuk memanggil filter berikutnya dalam alur atau delegasi permintaan jika filter terakhir telah dipanggil.
Filter dijalankan sebelum handler titik akhir. Ketika beberapa AddEndpointFilter
pemanggilan dilakukan pada handler:
- Kode filter yang
EndpointFilterDelegate
disebut sebelum (next
) dipanggil dijalankan dalam urutan Urutan Pertama Masuk, Keluar Pertama (FIFO). - Kode filter yang
EndpointFilterDelegate
dipanggil setelah (next
) dipanggil dijalankan dalam urutan Urutan Masuk Pertama, Terakhir Keluar (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();
Dalam kode sebelumnya, filter dan titik akhir mencatat output berikut:
Before first filter
Before 2nd filter
Before 3rd filter
Endpoint
After 3rd filter
After 2nd filter
After first filter
Kode berikut menggunakan filter yang mengimplementasikan IEndpointFilter
antarmuka:
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();
Dalam kode sebelumnya, filter dan log handler menunjukkan urutan dijalankannya:
AEndpointFilter Before next
BEndpointFilter Before next
CEndpointFilter Before next
Endpoint
CEndpointFilter After next
BEndpointFilter After next
AEndpointFilter After next
Filter yang mengimplementasikan IEndpointFilter
antarmuka ditampilkan dalam contoh berikut:
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) { }
}
Memvalidasi objek dengan filter
Pertimbangkan filter yang memvalidasi Todo
objek:
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);
});
Dalam kode sebelumnya:
- Objek
EndpointFilterInvocationContext
menyediakan akses ke parameter yang terkait dengan permintaan tertentu yang dikeluarkan ke titik akhir melaluiGetArguments
metode . - Filter terdaftar menggunakan
delegate
yang mengambilEndpointFilterInvocationContext
dan mengembalikanEndpointFilterDelegate
.
Selain diteruskan sebagai delegasi, filter dapat didaftarkan dengan menerapkan IEndpointFilter
antarmuka. Kode berikut menunjukkan filter sebelumnya yang dienkapsulasi dalam kelas yang mengimplementasikan 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);
}
}
Filter yang mengimplementasikan IEndpointFilter
antarmuka dapat mengatasi dependensi dari Dependency Injection(DI), seperti yang ditunjukkan pada kode sebelumnya. Meskipun filter dapat mengatasi dependensi dari DI, filter itu sendiri tidak dapat diselesaikan dari DI.
ToDoIsValidFilter
diterapkan ke titik akhir berikut:
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>();
Filter berikut memvalidasi Todo
objek dan memodifikasi Name
properti:
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);
}
}
Mendaftarkan filter menggunakan pabrik filter titik akhir
Dalam beberapa skenario, mungkin perlu untuk menyimpan beberapa informasi yang disediakan dalam MethodInfo
filter. Misalnya, mari kita asumsikan bahwa kita ingin memverifikasi bahwa handler filter titik akhir dilampirkan untuk memiliki parameter pertama yang mengevaluasi ke jenis 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);
});
Dalam kode sebelumnya:
- Objek
EndpointFilterFactoryContext
menyediakan akses ke yangMethodInfo
terkait dengan handler titik akhir. - Tanda tangan handler diperiksa dengan memeriksa
MethodInfo
tanda tangan jenis yang diharapkan. Jika tanda tangan yang diharapkan ditemukan, filter validasi terdaftar ke titik akhir. Pola pabrik ini berguna untuk mendaftarkan filter yang bergantung pada tanda tangan handler titik akhir target. - Jika tanda tangan yang cocok tidak ditemukan, filter pass-through akan didaftarkan.
Mendaftarkan filter pada tindakan pengontrol
Dalam beberapa skenario, mungkin perlu untuk menerapkan logika filter yang sama untuk titik akhir berbasis penangan rute dan tindakan pengontrol. Untuk skenario ini, dimungkinkan untuk memanggil AddEndpointFilter
ControllerActionEndpointConventionBuilder
untuk mendukung eksekusi logika filter yang sama pada tindakan dan titik akhir.
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();
Sumber Tambahan
ASP.NET Core
Saran dan Komentar
https://aka.ms/ContentUserFeedback.
Segera hadir: Sepanjang tahun 2024 kami akan menghentikan penggunaan GitHub Issues sebagai mekanisme umpan balik untuk konten dan menggantinya dengan sistem umpan balik baru. Untuk mengetahui informasi selengkapnya, lihat:Kirim dan lihat umpan balik untuk