Miroslav Holec
Premium

Jak správně přistupovat na HttpContext v ASP.NET Core

Miroslav Holec   9. prosince 2019

Tento článek byl napsán v roce 2019. Vývojářské technologie se neustále inovují a článek již nemusí popisovat aktuální stav technologie, ideální řešení a můj současný pohled na dané téma.

Potřebujete-li přistupovat v ASP.NET Core aplikacích na HttpContext, můžete tak snadno učinit v Controllerech, PageModelech nebo různých specifických místech (Filtry, Middlewares), kde je obvykle HttpContext součástí jiné třídy. Jak ale přistupovat na HttpContext mimo tato místa?

V controllerech musí být uklizeno

Vzhledem k tomu, že controllery nebo page modely nejsou dobrým kandidátem pro aplikační logiku, využití vlastnosti HttpContext na bázové třídě je prakticky nulové. Controllery jsou obvykle jen místem, odkud se provolávají různé services nebo fasády, které mají za úkol poskytnout data. Controllery pak rozhodují pouze o tom, zda tato data získají například JSON podobu nebo budou použitá v cshtml k vykreslení HTML stránky. Dále mohou controllery obsahovat jednoduchou logiku z pohledu aplikačního flow a v závislosti na výsledku práce připojených služeb se mohou rozhodnout, zda vrátí data, zobrazí stránku nebo například provedou redirect někam úplně jinam.

Nezřídka jsem viděl kód, ve kterém vývojář HttpContext předal z controlleru směrem do metody nějaké service. To není praktické jednak z hlediska testování, ale především to vytváří prostor k chybnému chování aplikace, zejména v multi thread kódu.

HttpContext

V ASP.NET Core není HttpContext registrován přímo v DI kontejneru. Je třeba pamatovat na to, že životní cyklus HttpContextu je managován mimo DI a samotný HttpContext není thread safe. Tyto dvě skutečnosti předurčují dva způsoby práce s HttpContextem. Prvním je přístup na context jako takový a druhým je práce s hodnotami contextu.

IHttpContextAccessor

Chcete-li získat aktuální HttpContext kdekoliv v aplikaci, je nutné registrovat do DI kontejneru singleton IHttpContextAccessor. Existuje na to i extension metoda:

public void ConfigureServices(IServiceCollection services)
{
     services.AddControllers();
     services.AddHttpContextAccessor();
}

Díky tomu si lze kamkoliv injectnout IHttpContextAccessor, který nám HttpContext zpřístupní.

public class EmailService : IEmailService
{
    private readonly IHttpContextAccessor _httpContextAccessor;

    public UserRepository(IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor;
    }

    public void SendEmail(Email email)
    {
        var username = _httpContextAccessor.HttpContext.User.Identity.Name;
        // ...
    }
}

Je důležité poznamenat, že chceme-li pracovat s aktuálním stavem HttpContextu, nechceme za žádných okolností HttpContext někam odlévat. Zejména to platí v singletonech, kde by uložení hodnoty _httpContextAccessor.HttpContext znamenalo zmrazení aktuálního stavu (stal by součástí singleton instance). Občas to k tomu svádí a dokonce v některých místech to vyloženě nedává smysl. Příkladem budiž middleware konstruktor. Ten je proveden pouze při startu aplikace, kdy je HttpContext null.

Stav HttpContextu

Naopak jsou situace, kdy potřebujeme určitou hodnotu z HttpContextu a s tou pracujeme v našem kódu. Máme-li v kódu různá asynchronní volání nebo obecně vytváříme-li další vlákna, pak je nutné si uložit potřebné hodnoty do primitivní proměnné nebo struktury a pak s nimi pracovat. Platí to například při předávání hodnot do metod, které budou provádět zpracování na pozadí. Taková předná hodnota by neměla být nikdy HttpContext, ale výhradně hodnota z HttpContextu se kterou potřebujeme pracovat.

public class EmailController : Controller
{
    public ActionResult SendEmail(Email email)
    {
        var id = HttpContext.Request.Headers["TraceId"].ToString();
        Process(id);
        return View();
    }

    private async Task Process(string id)
    {
        // ...
    }
}

A to je pro tentokrát všechno. Chcete-li si tedy zpřístupnit HttpContext, stačí si registrovat do DI IHttpContextAccessor a poté jej správně používat. HttpContext je nicméně sám o sobě tajemný a ke způsobům jeho správného použití a best practices se ještě vrátím.