Fast_Endpoints_jejkop 1.2.1
dotnet add package Fast_Endpoints_jejkop --version 1.2.1
NuGet\Install-Package Fast_Endpoints_jejkop -Version 1.2.1
<PackageReference Include="Fast_Endpoints_jejkop" Version="1.2.1" />
<PackageVersion Include="Fast_Endpoints_jejkop" Version="1.2.1" />
<PackageReference Include="Fast_Endpoints_jejkop" />
paket add Fast_Endpoints_jejkop --version 1.2.1
#r "nuget: Fast_Endpoints_jejkop, 1.2.1"
#:package Fast_Endpoints_jejkop@1.2.1
#addin nuget:?package=Fast_Endpoints_jejkop&version=1.2.1
#tool nuget:?package=Fast_Endpoints_jejkop&version=1.2.1
FastEndpoints
Biblioteka do rejestracji endpointów w ASP.NET Core Minimal APIs opartej na konwencji klas. Zamiast definiować endpointy inline w Program.cs, tworzysz klasy dziedziczące po FastEndpoint — biblioteka automatycznie je wykrywa i rejestruje przy starcie aplikacji.
NuGet: Fast_Endpoints_jejkop
Target frameworks: net6.0, net8.0
Licencja: MIT
Instalacja
dotnet add package Fast_Endpoints_jejkop
Szybki start
1. Rejestracja w Program.cs
using FastEndpoints.Extensions;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.UseMinimalEndpoints(config =>
{
config.ProjectName = "MojaAplikacja"; // opcjonalne — ogranicza skanowanie do jednego assembly
config.IgnoreAntiforgery = true; // opcjonalne — wyłącza antiforgery na wszystkich endpointach (NET8+)
});
app.Run();
2. Tworzenie endpointu
using FastEndpoints.Configuration;
using FastEndpoints.Enum;
public class GetUsersEndpoint : FastEndpoint
{
public GetUsersEndpoint()
{
Method = HttpRequestMethodTypes.Get;
Url = "/api/users";
Name = "GetUsers";
Tag = "Users";
}
public IResult ExecuteAsync()
{
return Results.Ok(new[] { "Jan", "Anna" });
}
}
Endpoint zostanie automatycznie zarejestrowany jako GET /api/users.
Konfiguracja globalna (Config)
| Property | Typ | Opis |
|---|---|---|
ProjectName |
string? |
Nazwa assembly do skanowania. Jeśli null, skanowane są wszystkie załadowane assemblies. |
IgnoreAntiforgery |
bool? |
Wyłącza walidację antiforgery token na wszystkich endpointach. Tylko NET8+. |
Właściwości endpointu
Każdy endpoint dziedziczy po FastEndpoint i konfiguruje swoje właściwości w konstruktorze.
Wymagane
| Property | Typ | Opis |
|---|---|---|
Method |
HttpRequestMethodTypes |
Metoda HTTP: Get, Post, Put, Delete, Patch |
Url |
string? |
Wzorzec URL, np. "/api/users/{id}" |
Metadane routingu
| Property | Typ | Opis |
|---|---|---|
Name |
string? |
Nazwa endpointu — używana do generowania linków (WithName) |
Tag |
string? |
Pojedynczy tag OpenAPI/Swagger/Scalar (WithTags) |
Tags |
IEnumerable<string>? |
Wiele tagów OpenAPI. Gdy ustawione, ma priorytet nad Tag |
Odpowiedzi (Produces)
Dwie opcje — prosta (backward-compatible) i rozszerzona:
Prosta — tylko kody statusu:
Produces = [200, 404];
Rozszerzona — z typem odpowiedzi i content type (dla Swagger/Scalar):
using FastEndpoints.Models;
ProducesMetadata =
[
new ProducesMetadata { StatusCode = 200, ResponseType = typeof(UserDto), ContentType = "application/json" },
new ProducesMetadata { StatusCode = 404 }
];
Property (ProducesMetadata) |
Typ | Opis |
|---|---|---|
StatusCode |
int |
Kod statusu HTTP |
ResponseType |
Type? |
Typ odpowiedzi — pojawi się jako schemat w Swagger/Scalar |
ContentType |
string? |
Content type, np. "application/json" |
Gdy
ProducesMetadatajest ustawione,Producesjest ignorowane.
Autoryzacja
| Property | Typ | Opis |
|---|---|---|
RequireAuthorization |
bool |
Wymaga uwierzytelnienia bez konkretnej polisy (odpowiednik [Authorize]) |
AuthorizationPolicies |
IEnumerable<string>? |
Wymaga spełnienia podanych polis autoryzacji |
AllowAnonymous |
bool |
Endpoint dostępny bez uwierzytelnienia |
Priorytet: AllowAnonymous > AuthorizationPolicies > RequireAuthorization.
// Wymaga uwierzytelnienia (dowolny zalogowany użytkownik)
RequireAuthorization = true;
// Wymaga konkretnych polis
AuthorizationPolicies = ["AdminOnly", "RequireMfa"];
// Dostęp anonimowy (nadpisuje wszystko powyżej)
AllowAnonymous = true;
Dokumentacja OpenAPI (Swagger/Scalar) — NET7+
| Property | Typ | Opis |
|---|---|---|
Summary |
string? |
Krótkie podsumowanie endpointu |
Description |
string? |
Szczegółowy opis endpointu |
Summary = "Pobierz listę użytkowników";
Description = "Zwraca paginowaną listę wszystkich aktywnych użytkowników.";
SummaryiDescriptionwymagają NET7+. Na NET6 wartości zostaną ustawione, ale nie będą miały efektu.
Rate Limiting — NET7+
| Property | Typ | Opis |
|---|---|---|
RateLimitingPolicy |
string? |
Nazwa polisy rate limiting zarejestrowanej w AddRateLimiter |
Jak działa
Rate limiting ogranicza liczbę requestów, które endpoint może obsłużyć w określonym oknie czasowym. Gdy limit zostanie przekroczony, klient otrzymuje odpowiedź 503 Service Unavailable (domyślnie) lub 429 Too Many Requests (konfigurowane przez RejectionStatusCode).
Mechanizm działa na poziomie middleware ASP.NET Core — request jest odrzucany zanim dotrze do ExecuteAsync.
Konfiguracja w Program.cs
Rate limiter musi być zarejestrowany w DI i dodany jako middleware:
builder.Services.AddRateLimiter(options =>
{
// Fixed Window — stała liczba requestów w oknie czasowym
options.AddFixedWindowLimiter("CreateLimit", limiter =>
{
limiter.PermitLimit = 10; // max 10 requestów
limiter.Window = TimeSpan.FromMinutes(1); // na minutę
limiter.QueueLimit = 0; // brak kolejki — natychmiastowe odrzucenie po przekroczeniu
});
// Sliding Window — okno przesuwa się płynnie
options.AddSlidingWindowLimiter("ApiGeneral", limiter =>
{
limiter.PermitLimit = 100;
limiter.Window = TimeSpan.FromMinutes(1);
limiter.SegmentsPerWindow = 4; // okno podzielone na 4 segmenty po 15s
});
// Token Bucket — tokeny regenerują się w stałym tempie
options.AddTokenBucketLimiter("BurstAllowed", limiter =>
{
limiter.TokenLimit = 20; // max pojemność
limiter.ReplenishmentPeriod = TimeSpan.FromSeconds(10);
limiter.TokensPerPeriod = 5; // 5 tokenów co 10s
});
// Concurrency — ogranicza liczbę równoczesnych requestów
options.AddConcurrencyLimiter("LongRunning", limiter =>
{
limiter.PermitLimit = 3; // max 3 równoczesne requesty
limiter.QueueLimit = 5; // 5 requestów czeka w kolejce
limiter.QueueProcessingOrder = System.Threading.RateLimiting.QueueProcessingOrder.OldestFirst;
});
// Globalna odpowiedź przy odrzuceniu
options.RejectionStatusCode = 429; // Too Many Requests zamiast domyślnego 503
});
// WAŻNE: middleware musi być dodany przed UseMinimalEndpoints
app.UseRateLimiter();
Użycie w endpoincie
public class CreateOrderEndpoint : FastEndpoint
{
public CreateOrderEndpoint()
{
Method = HttpRequestMethodTypes.Post;
Url = "/api/orders";
RateLimitingPolicy = "CreateLimit"; // nazwa musi odpowiadać polisie z AddRateLimiter
}
public async Task<IResult> ExecuteAsync([FromBody] CreateOrderRequest request)
{
// Ten kod wykonuje się TYLKO jeśli request przeszedł rate limiter
return Results.Created($"/api/orders/1", request);
}
}
Zasady
- Polisa o podanej nazwie musi istnieć w
AddRateLimiter— w przeciwnym razie endpoint rzuci wyjątek przy starcie. - Middleware
UseRateLimiter()musi być dodany przedUseMinimalEndpoints(). - Każdy endpoint może mieć jedną polisę rate limiting.
- Jeśli
RateLimitingPolicyjestnull, endpoint nie podlega rate limitingowi (chyba że jest skonfigurowana polisa globalna). - Rate limiting działa per-serwer — w środowisku wieloinstancyjnym (load balancer) każda instancja liczy niezależnie. Do distributed rate limiting potrzebny jest zewnętrzny store (Redis).
Output Cache — NET7+
| Property | Typ | Opis |
|---|---|---|
OutputCachePolicy |
string? |
Nazwa polisy output cache zarejestrowanej w AddOutputCache |
Jak działa
Output caching zapisuje pełną odpowiedź HTTP (status code, headers, body) i serwuje ją z cache przy kolejnych identycznych requestach — ExecuteAsync nie jest wywoływane dla requestów obsłużonych z cache.
Mechanizm działa na poziomie middleware — response jest przechwytywany po pierwszym wykonaniu i zwracany bezpośrednio przy kolejnych requestach pasujących do cache key.
Konfiguracja w Program.cs
builder.Services.AddOutputCache(options =>
{
// Prosta polisa z czasem wygaśnięcia
options.AddPolicy("ShortCache", policy =>
policy.Expire(TimeSpan.FromSeconds(30)));
// Cache z wariacją po query string
options.AddPolicy("VaryByQuery", policy =>
policy.SetVaryByQuery("page", "pageSize")
.Expire(TimeSpan.FromMinutes(5)));
// Cache z wariacją po headerze
options.AddPolicy("VaryByLang", policy =>
policy.SetVaryByHeader("Accept-Language")
.Expire(TimeSpan.FromMinutes(10)));
// Cache z tagiem — umożliwia ręczną inwalidację
options.AddPolicy("ProductCache", policy =>
policy.Tag("products")
.Expire(TimeSpan.FromMinutes(15)));
});
// WAŻNE: middleware musi być dodany przed UseMinimalEndpoints
app.UseOutputCache();
Użycie w endpoincie
public class GetProductsEndpoint : FastEndpoint
{
public GetProductsEndpoint()
{
Method = HttpRequestMethodTypes.Get;
Url = "/api/products";
OutputCachePolicy = "ProductCache";
}
public IResult ExecuteAsync(IProductService service)
{
// Pierwsze wywołanie: ExecuteAsync się wykonuje, odpowiedź trafia do cache
// Kolejne wywołania (przez 15 min): odpowiedź z cache, ExecuteAsync NIE jest wywoływane
var products = service.GetAll();
return Results.Ok(products);
}
}
Ręczna inwalidacja cache
Jeśli polisa ma tag, można ręcznie wyczyścić cache (np. po dodaniu nowego produktu):
public class CreateProductEndpoint : FastEndpoint
{
public CreateProductEndpoint()
{
Method = HttpRequestMethodTypes.Post;
Url = "/api/products";
}
public async Task<IResult> ExecuteAsync(
IProductService service, IOutputCacheStore cache, [FromBody] CreateProductRequest request)
{
var product = service.Create(request);
// Inwalidacja cache z tagiem "products"
await cache.EvictByTagAsync("products", CancellationToken.None);
return Results.Created($"/api/products/{product.Id}", product);
}
}
Zasady
- Output cache działa tylko dla GET i HEAD — requesty POST/PUT/DELETE/PATCH nigdy nie są cache'owane (to zachowanie ASP.NET Core, nie FastEndpoints).
- Polisa o podanej nazwie musi istnieć w
AddOutputCache. - Middleware
UseOutputCache()musi być dodany przedUseMinimalEndpoints(). - Requesty z headerem
Authorizationnie są cache'owane domyślnie (zmiana wymaga własnej polisy). - Requesty z
Cache-Control: no-cachelubno-storepomijają cache. - Cache jest in-memory domyślnie — w środowisku wieloinstancyjnym każda instancja ma niezależny cache. Do distributed cache można zarejestrować własny
IOutputCacheStore(np. Redis). SetVaryByQuery/SetVaryByHeadertworzą osobne wpisy cache per unikalna kombinacja wartości — np./api/products?page=1i/api/products?page=2to dwa osobne wpisy.- Jeśli
OutputCachePolicyjestnull, odpowiedź nie jest cache'owana (chyba że jest skonfigurowana polisa globalna).
Endpoint Filters — NET7+
| Property | Typ | Opis |
|---|---|---|
EndpointFilters |
IEnumerable<Type>? |
Lista typów implementujących IEndpointFilter |
Jak działają
Endpoint Filters to pipeline przetwarzania per-endpoint — odpowiednik middleware, ale działający tylko na danym endpoincie. Filtry tworzą łańcuch (chain of responsibility): każdy filtr decyduje, czy przekazać request dalej, zmodyfikować go lub przerwać pipeline zwracając własną odpowiedź.
Kolejność wykonania:
Request → Filter[0] → Filter[1] → ... → Filter[N] → ExecuteAsync → Filter[N] → ... → Filter[0] → Response
Filtry są tworzone per-request za pomocą ActivatorUtilities.CreateInstance, co oznacza pełne wsparcie constructor injection z kontenera DI.
Implementacja filtra
Każdy filtr musi implementować IEndpointFilter:
public interface IEndpointFilter
{
ValueTask<object?> InvokeAsync(
EndpointFilterInvocationContext context,
EndpointFilterDelegate next);
}
context— daje dostęp doHttpContext, argumentów endpointu i serwisów DInext— delegat do następnego filtra w łańcuchu (lub doExecuteAsyncjeśli to ostatni filtr)- Zwracana wartość — wynik endpointu (np.
IResult) lub własna odpowiedź
Przykłady filtrów
Logowanie requestów:
public class LoggingFilter : IEndpointFilter
{
public async ValueTask<object?> InvokeAsync(
EndpointFilterInvocationContext context, EndpointFilterDelegate next)
{
var logger = context.HttpContext.RequestServices
.GetRequiredService<ILogger<LoggingFilter>>();
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
logger.LogInformation("[{Method}] {Path} — start",
context.HttpContext.Request.Method,
context.HttpContext.Request.Path);
var result = await next(context); // przekazanie do następnego filtra / ExecuteAsync
logger.LogInformation("[{Method}] {Path} — {ElapsedMs}ms",
context.HttpContext.Request.Method,
context.HttpContext.Request.Path,
stopwatch.ElapsedMilliseconds);
return result;
}
}
Walidacja — przerwanie pipeline (short-circuit):
public class ValidationFilter : IEndpointFilter
{
public async ValueTask<object?> InvokeAsync(
EndpointFilterInvocationContext context, EndpointFilterDelegate next)
{
// Dostęp do argumentów ExecuteAsync po indeksie
if (context.Arguments.Count > 0 && context.Arguments[0] is CreateOrderRequest request)
{
if (string.IsNullOrWhiteSpace(request.ProductName))
{
// Short-circuit — NIE wywołuje next(), pipeline się zatrzymuje
return Results.BadRequest(new { Error = "ProductName jest wymagany" });
}
}
return await next(context); // walidacja OK — kontynuuj
}
}
Filtr z DI (constructor injection):
public class TenantFilter : IEndpointFilter
{
private readonly ITenantService _tenantService;
// Konstruktor — zależności wstrzykiwane automatycznie z DI
public TenantFilter(ITenantService tenantService)
{
_tenantService = tenantService;
}
public async ValueTask<object?> InvokeAsync(
EndpointFilterInvocationContext context, EndpointFilterDelegate next)
{
var tenantId = context.HttpContext.Request.Headers["X-Tenant-Id"].FirstOrDefault();
if (tenantId is null || !await _tenantService.ExistsAsync(tenantId))
{
return Results.Unauthorized();
}
return await next(context);
}
}
Modyfikacja odpowiedzi:
public class ResponseWrappingFilter : IEndpointFilter
{
public async ValueTask<object?> InvokeAsync(
EndpointFilterInvocationContext context, EndpointFilterDelegate next)
{
var result = await next(context);
// Owijanie odpowiedzi w standardowy envelope
return Results.Ok(new
{
Success = true,
Timestamp = DateTime.UtcNow,
Data = result
});
}
}
Użycie w endpoincie
public class CreateOrderEndpoint : FastEndpoint
{
public CreateOrderEndpoint()
{
Method = HttpRequestMethodTypes.Post;
Url = "/api/orders";
// Filtry wykonują się w kolejności podania: Logging → Validation → Tenant → ExecuteAsync
EndpointFilters = [typeof(LoggingFilter), typeof(ValidationFilter), typeof(TenantFilter)];
}
public async Task<IResult> ExecuteAsync([FromBody] CreateOrderRequest request)
{
// Ten kod wykonuje się TYLKO jeśli wszystkie filtry wywołały next()
return Results.Created($"/api/orders/1", request);
}
}
Zasady
- Filtry wykonują się w kolejności podanej w tablicy — pierwszy filtr w liście jest najbardziej zewnętrzny (wykonuje się pierwszy przy request, ostatni przy response).
- Każdy filtr musi wywołać
await next(context)żeby przekazać request dalej. Jeśli tego nie zrobi (short-circuit), dalsze filtry iExecuteAsyncnie zostaną wywołane. - Filtry są tworzone per-request — każdy request dostaje nową instancję filtra. Nie ma problemów z shared state.
- Constructor injection działa automatycznie — serwisy zarejestrowane w DI zostaną wstrzyknięte do konstruktora filtra.
context.Argumentsdaje dostęp do argumentówExecuteAsyncpo indeksie (nie po nazwie) —context.Arguments[0]to pierwszy parametr metody.- Zwracana wartość filtra staje się odpowiedzią endpointu — filtr może zmienić, owinąć lub zastąpić wynik
ExecuteAsync. - Jeśli
EndpointFiltersjestnull, endpoint nie ma filtrów —ExecuteAsyncjest wywoływane bezpośrednio. - Jeden typ filtra może być użyty na wielu endpointach — jest to kwestia dodania go do tablicy
EndpointFiltersna każdym endpoincie osobno.
Metody HTTP
Method = HttpRequestMethodTypes.Get; // GET
Method = HttpRequestMethodTypes.Post; // POST
Method = HttpRequestMethodTypes.Put; // PUT
Method = HttpRequestMethodTypes.Delete; // DELETE
Method = HttpRequestMethodTypes.Patch; // PATCH (NET7+)
Metoda ExecuteAsync
Każdy endpoint musi mieć publiczną metodę instancji ExecuteAsync. Parametry są wstrzykiwane automatycznie przez Minimal API:
// Bez parametrów
public IResult ExecuteAsync()
// Z parametrem z URL
public IResult ExecuteAsync(int id)
// Z body (POST/PUT)
public IResult ExecuteAsync([FromBody] CreateUserRequest request)
// Z DI + body
public async Task<IResult> ExecuteAsync(IMediator mediator, [FromBody] CreateUserRequest request)
// Z DI + route param
public async Task<IResult> ExecuteAsync(IUserService userService, int id)
Pełny przykład
using FastEndpoints.Configuration;
using FastEndpoints.Enum;
using FastEndpoints.Models;
public class CreateOrderEndpoint : FastEndpoint
{
public CreateOrderEndpoint()
{
Method = HttpRequestMethodTypes.Post;
Url = "/api/orders";
Name = "CreateOrder";
Tags = ["Orders", "WriteOperations"];
Summary = "Utwórz nowe zamówienie";
Description = "Tworzy zamówienie na podstawie przesłanych danych. Wymaga autoryzacji.";
AuthorizationPolicies = ["RequireUser"];
RateLimitingPolicy = "OrderLimit";
ProducesMetadata =
[
new ProducesMetadata { StatusCode = 201, ResponseType = typeof(OrderDto), ContentType = "application/json" },
new ProducesMetadata { StatusCode = 400 },
new ProducesMetadata { StatusCode = 401 }
];
EndpointFilters = [typeof(ValidationFilter)];
}
public async Task<IResult> ExecuteAsync(IMediator mediator, [FromBody] CreateOrderRequest request)
{
var result = await mediator.Send(new CreateOrderCommand(request));
return Results.Created($"/api/orders/{result.Id}", result);
}
}
Dostępność funkcji wg wersji .NET
| Funkcja | NET6 | NET7+ | NET8+ |
|---|---|---|---|
| GET, POST, PUT, DELETE | v | v | v |
| PATCH | - | v | v |
| Name, Tag/Tags | v | v | v |
| Produces / ProducesMetadata | v | v | v |
| AuthorizationPolicies, AllowAnonymous | v | v | v |
| Summary, Description | - | v | v |
| RateLimitingPolicy | - | v | v |
| OutputCachePolicy | - | v | v |
| EndpointFilters | - | v | v |
| IgnoreAntiforgery (config) | - | - | v |
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- No dependencies.
-
net8.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.