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
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Fast_Endpoints_jejkop" Version="1.2.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Fast_Endpoints_jejkop" Version="1.2.1" />
                    
Directory.Packages.props
<PackageReference Include="Fast_Endpoints_jejkop" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Fast_Endpoints_jejkop --version 1.2.1
                    
#r "nuget: Fast_Endpoints_jejkop, 1.2.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Fast_Endpoints_jejkop@1.2.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Fast_Endpoints_jejkop&version=1.2.1
                    
Install as a Cake Addin
#tool nuget:?package=Fast_Endpoints_jejkop&version=1.2.1
                    
Install as a Cake Tool

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 ProducesMetadata jest ustawione, Produces jest 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.";

Summary i Description wymagają 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 przed UseMinimalEndpoints().
  • Każdy endpoint może mieć jedną polisę rate limiting.
  • Jeśli RateLimitingPolicy jest null, 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 przed UseMinimalEndpoints().
  • Requesty z headerem Authorization nie są cache'owane domyślnie (zmiana wymaga własnej polisy).
  • Requesty z Cache-Control: no-cache lub no-store pomijają 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 / SetVaryByHeader tworzą osobne wpisy cache per unikalna kombinacja wartości — np. /api/products?page=1 i /api/products?page=2 to dwa osobne wpisy.
  • Jeśli OutputCachePolicy jest null, 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 do HttpContext, argumentów endpointu i serwisów DI
  • next — delegat do następnego filtra w łańcuchu (lub do ExecuteAsync jeś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 i ExecuteAsync nie 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.Arguments daje dostęp do argumentów ExecuteAsync po 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 EndpointFilters jest null, endpoint nie ma filtrów — ExecuteAsync jest wywoływane bezpośrednio.
  • Jeden typ filtra może być użyty na wielu endpointach — jest to kwestia dodania go do tablicy EndpointFilters na 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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • 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.

Version Downloads Last Updated
1.2.1 1,937 3/31/2026
1.2.0 124 3/31/2026
1.1.3 5,005 6/24/2024
1.1.2 3,104 5/23/2022
1.1.1 608 3/25/2022
1.1.0 591 3/10/2022
1.0.5 585 3/9/2022
1.0.4 586 3/9/2022
1.0.3 568 3/9/2022
1.0.2 572 3/9/2022
1.0.1 560 3/9/2022
1.0.0 714 3/9/2022