HttpContext null in constructor
Asked Answered
K

2

11

I have a UserContext Service where I'll put some basic functionalities ("IsAuthenticated, GetUser etc...)

In order to do that, I need to pass the HTTPContext from my WebAPI Controller to my Class Library Service.

Actually, HttpContext is always null in the web api controller.

Anybody have a solution to resolve my issue ?. Is there a better way to acheive it.

Web API User Controller

[Route("api/[controller]")]
[Authorize]
public class UserController : Controller
{
    private readonly IUserContextServices _userContextServices;
    private readonly User loggedUser;

    public UserController()
    {
       //HttpContext ALWAYS NULL
        _userContextServices = new UserContextService(HttpContext);
    }
 }  

UserContext Services

namespace MyProj.Services
{
    public interface IUserContextServices
    {
        UserContext GetUserContext();
        bool IsUserAuthenticated();
    }

    public class UserContextService : IUserContextServices
    {
        private readonly HttpContext _context;
        private UserContext _userContext;
        public UserContextService(HttpContext context)
        {
            _context = context;
            InitUserContext();
        }

        private IEnumerable<Claim> GetUserClaims()
        {
            if (IsUserAuthenticated())
            {
                return _context.User.Claims;
            }
            return null;
        }

        private void InitUserContext()
        {
            if (IsUserAuthenticated())
            {
                var claims = GetUserClaims();
                _userContext = new UserContext();
                _userContext.Email = claims.First(p => p.Type == "email").Value;
                _userContext.AspNetUserID = claims.First(p => p.Type == "sub").Value;
            }
        }

        public UserContext GetUserContext()
        {
            return _userContext;
        }

        public bool IsUserAuthenticated()
        {
            return _context.User != null && _context.User.Identity != null && _context.User.Identity.IsAuthenticated;
        }
    }
}
Kenney answered 21/6, 2017 at 18:10 Comment(2)
In the flow of calling a controller the HttpContext is not ready/assigned at the time the controller is created. Rethink the design so that the context is injected used later in the life cycle of the dependent classHolography
Which method is called after the constructor ?Kenney
H
17

HttpContext is not available when the constructor of the Controller is called. You are going to have to redesign your code to get the context later in the invocation flow. This is what the IHttpContextAccessor is for.

public interface IHttpContextAccessor {
    HttpContext HttpContext { get; }
}

Inject that into the service and then access the context later as needed.

public class UserContextService : IUserContextServices {
    private readonly IHttpContextAccessor contextAccessor;
    private UserContext _userContext;
    public UserContextService(IHttpContextAccessor accessor) {
        contextAccessor = accessor;
    }

    private HttpContext Context {
        get {
            return contextAccessor.HttpContext;
        }
    }

    public UserContext GetUserContext() {
        if (_userContext == null && IsUserAuthenticated()) {
            var claims = Context?.User?.Claims;
            _userContext = new UserContext() {
                Email = claims.First(p => p.Type == "email").Value,
                AspNetUserID = claims.First(p => p.Type == "sub").Value
            };
        }
        return _userContext;
    }

    public bool IsUserAuthenticated() {
        return Context?.User?.Identity?.IsAuthenticated;
    }
}

Inject the service abstraction into the Controller

[Route("api/[controller]")]
[Authorize]
public class UserController : Controller {
    private readonly IUserContextServices _userContextServices;
    private readonly User loggedUser;

    public UserController(IUserContextServices userContextServices) {
        _userContextServices = userContextServices;
    }

    //...
}

IHttpContextAccessor is not in the service collection by default so you need to add it in Startup.ConfigureServices manually in order to be able to inject it:

services.AddHttpContextAccessor();
services.AddTransient<IUserContextServices, UserContextService>();
Holography answered 21/6, 2017 at 19:33 Comment(0)
Y
0

I doubt you really need it on the constructor, as it will be null at that moment.

The HttpContext gets attached to controller on the base's class Initialize function.

What you can do is an override on your controller class, calling the parent's Initialize function before, and follow with your intended instructions, as in:

protected override void Initialize(RequestContext requestContext)
{
    base.Initialize(requestContext);

    // ... Do what ever you want with the already set HttpContext field
}

I tested this on an old .Net Framework 4 project, I am not sure of my approach is also viable on the newer .Net Core versions.

Yoheaveho answered 26/7 at 11:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.