HttpContext Request Features

V ASP.NET Core sme zvyknutí pracovať s dependency injection a zvykneme väčšinu informácií prenášať práve cez injektovanie závislostí do tried kde ich potrebujeme. Niekedy však pracujeme s informáciami, ktoré sú úzko späté s konkrétnym requestom a nepotrebujeme, nechceme alebo nemôžeme ich injektovať do tried ako závislosti. Pre takéto prípady je tu HttpContext.Features.

Vlastnosť Features je jednoduchá kolekcia typu IFeatureCollection, do ktorej si môžeme ukladať silne typové objektu a následne na miestach kde ich potrebujeme získavať. Táto kolekcia je dostupná v rámci HttpContext a je dostupná počas celého životného cyklu requestu. Sám ASP.NET Core framework využíva túto kolekciu na ukladanie rôznych informácii, napríklad:

  • IRouteValuesFeature - obsahuje informácie o parametroch a ich hodnotách z cesty dotazu
  • IEndpointFeature - obsahuje informácie o koncovom endpointe, ktorý spracováva request
  • ...

Vlastnosť Features je dostupná aj pre nás a môžeme si do nej ukladať vlastné informácie, ktoré budeme potrebovať počas spracovania requestu. Príklad použitia môže byť napríklad ukladanie informácii o aktuálnom používateľovi, aktuálnom tenantovi alebo iných informácii, ktoré sú potrebné pre spracovanie requestu s sú určitým spôsobom viazané, respektíve získavané zo samotného requestu.

Informácie sa do Features zvyčajne ukladajú v rámci middleware, ktorý je zaregistrovaný v rámci pipeline aplikácie. Middleware môže získavať informácie z requestu, spracovať ich a následne ich uložiť do Features pre ďalšie spracovanie v rámci aplikácie.

Príklad middleware, ktorý získava informácie o aktuálnom tenantovi z requestu a ukladá ich do Features môže vyzerať nasledovne:

public class CurrentTenantInfoMiddleware(RequestDelegate next)
{
    private readonly RequestDelegate _next = next;

    public async Task InvokeAsync(HttpContext context, ITenantService tenantService)
    {
        // 👇 More complex logic to determine the current tenant can be added here
        if (context.Request.Headers.TryGetValue("X-TenantId", out var routeValue)
            && Guid.TryParse(routeValue.First()?.ToString(), out var tenantId))
        {
            var tenantInfo = await tenantService.GetTenantInfoAsync(tenantId);
            context.Features.Set(tenantInfo);
        }

        await _next(context);
    }
}

// 👇 Register the middleware
app.UseMiddleware<CurrentTenantInfoMiddleware>();

Následne môžeme v rámci ďalších častí aplikácie pristupovať k informáciám o aktuálnom tenantovi z Features:

app.MapGet("/api/", (HttpContext context) =>
{
    // 👇 Access the current tenant info from the context
    var currentTenantInfo = context.Features.Get<ICurrentTenantInfo>();
    return Results.Ok(currentTenantInfo?.Id);
});

Pre zjednodušenie si môžeme vytvoriť extension metódu pre IFeatureCollection:

public static class IFeatureCollectionExtensions
{
    public static ICurrentTenantInfo? GetTenant(this IFeatureCollection features)
    {
        return features.Get<ICurrentTenantInfo>();
    }
}

var tenantInfo = context.Features.GetTenant();

Pre úplnosť: ďalšia možnosť ako si prenášať informácie počas jedného requestu je použitie HttpContext.Items. Táto kolekcia je dostupná v rámci HttpContext a je dostupná počas celého životného cyklu requestu. Avšak, Items nie je silne typový. Je to kľúč hodnota. Preto je vhodnejšie použiť Features ak potrebujeme ukladať silne typové objekty.