Hola!!! En este post quiero hablar un poquito sobre uno de los componentes principales de MVC, particularmente de los controladores: el HttpContext.

Todos los desarroladores tarde o temprano nos encontramos con la necesidad de manipular el HttpContext, ya sea para validar si el usuario esta autenticado o para obtener algún claim. Analicemos el siguiente ejemplo:

[ApiController]
[Route("[controller]")]
public class UsersController : ControllerBase
{
    [HttpGet]
    public IActionResult Greeting()
    {
        if(HttpContext.User.Identity.IsAuthenticated == false)
        {
            return Ok("Hello there!!!");
        }

        var username = HttpContext.User.FindFirst("username")?.Value;
        return Ok($"Hello {username}!!!");
    }
}

Dentro del Controlador tenemos disponible el HttpContext al cuál podemos acceder directamente sin ningún tipo de problemas.

Sin embargo, sabemos que lo aconsejable es utilizar una capa de servicios para desacoplar el Controlador de la capa de negocio. Es cuando introducimos esta capa de servicios donde comienza “La pesadilla del HttpContext”.

Con este ejemplo se verá mucho más claro:

[ApiController]
[Route("[controller]")]
public class UsersController : ControllerBase
{
    private readonly GreetingService _greetingService;

    public UsersController(GreetingService greetingService) =>
        _greetingService = greetingService;

    [HttpGet]
    public IActionResult Greeting()
    {
        var greeting = _greetingService.Greeting(HttpContext);
        return Ok(greeting);
    }
}

public class GreetingService
{
    public string Greeting(HttpContext httpContext)
    {
        if (httpContext.User.Identity.IsAuthenticated == false)
        {
            return "Hello there!!!";
        }

        var username = httpContext.User.FindFirst("username")?.Value;
        return $"Hello {username}!!!";
    }
}

¿Es más claro el problema ahora? Al introducir la capa de servicios, nos vemos obligados a pasar como parámetro el HttpContext. Si tuvieramos otro servicio, entonces tendríamos una cadena de métodos a los cuáles deberíamos pasar el contexto como parámetro. Algo así:

public class GreetingService
{
    public string Greeting(HttpContext httpContext)
    {
        var isAuthenticated = authService.Validate(httpContext);
        if (isAuthenticated == false)
        {
            return "Hello there!!!";
        }

        var username = httpContext.User.FindFirst("username")?.Value;
        return $"Hello {username}!!!";
    }
}

Claro que podríamos pasar como parámetro sólo las variables isAuthenticated y username, pero eso no resolvería el problema de fondo.

¿Y si usaramos inyección de dependencias?

Pienso, todos los servicios pueden ser inyectados si utilizamos el contenedor IoC y sabemos que por cada request se genera un scope (de ahí viene services.AddScoped<>()).

Además sabemos que para cada request se genera un HttpContext al cuál podemos acceder desde el Controlador, desde los filtros de MVC y también lo tenemos disponible en los middlewares.

Entonces, ¿No podríamos recibirlo mediante inyección de dependencias como cualquier otro servicio?

La respuesta es no, no podemos, pero casi…

IHttpContextAccessor

Para resolver el problema de “La pesadilla del HttpContext” tenemos disponible la interfaz IHttpContextAccessor y una implementación default HttpContextAccessor.

Esto nos va a permitir acceder al HttpContext de forma fácil y sencilla sin tener que escribir código de más. Algo así:

public class GreetingService
{
    private readonly IHttpContextAccessor contextAccessor;

    public GreetingService(IHttpContextAccessor contextAccessor) =>
        this.contextAccessor = contextAccessor;

    public string Greeting()
    {
        var httpContext = contextAccessor.HttpContext;
        if (httpContext.User.Identity.IsAuthenticated == false)
        {
            return "Hello there!!!";
        }

        var username = httpContext.User.FindFirst("username")?.Value;
        return $"Hello {username}!!!";
    }
}

Super importante, como con cualquier otro servicio, necesitamos registrarlo apropiadamente. Claro, el framework es nuestro mejor amigo, y siempre nos extiende una mano:

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        // More services...
        services.AddHttpContextAccessor();
        // More services...
    }

    public void Configure(IApplicationBuilder app)
    {
        // ...
    }
}

Conclusión

Con esta pequeña refactorización evitamos tener que pasar por parámetro el HttpContext, potencialmente en cadena, y así generar código mucho más limpio y entendible.