1

I use .net core 3.1.6. There are lots of answer about this and I try all but failed each time. So I create new test MVC project and add authentication.

I try to use a "CurrentUserService" class and get logged user information. However, every each time I get null result.

My startup.cs

public void ConfigureServices(IServiceCollection services) {
    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(
            Configuration.GetConnectionString("DefaultConnection")));
    services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
        .AddEntityFrameworkStores<ApplicationDbContext>();

    services.AddHttpContextAccessor();
    services.AddScoped<ICurrentUserService, CurrentUserService>();

    services.AddControllersWithViews();
    services.AddRazorPages();


}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {
    if (env.IsDevelopment()) {
        app.UseDeveloperExceptionPage();
        app.UseDatabaseErrorPage();
    }
    else {
        app.UseExceptionHandler("/Home/Error");
        // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
        app.UseHsts();
    }
    app.UseHttpsRedirection();
    app.UseStaticFiles();

    app.UseRouting();

    app.UseAuthentication();
    app.UseAuthorization();

    app.UseEndpoints(endpoints => {
        endpoints.MapControllerRoute(
            name: "default",
            pattern: "{controller=Home}/{action=Index}/{id?}");
        endpoints.MapRazorPages();
    });
}

And my CurrentUserService.cs

public class CurrentUserService : ICurrentUserService {
    private IHttpContextAccessor _httpContextAccessor;
    public CurrentUserService(IHttpContextAccessor httpContextAccessor) {
        _httpContextAccessor = httpContextAccessor;
    //I add x for test purpose and there is no user information here.
        var x = _httpContextAccessor.HttpContext?.User?.FindFirstValue(ClaimTypes.NameIdentifier);
    }

    public string UserId {
        get {
            var userIdClaim = _httpContextAccessor.HttpContext?.User?.FindFirstValue(ClaimTypes.NameIdentifier);
            return userIdClaim;
        }
    }

    public bool IsAuthenticated => UserId != null;
}

ICurrentUser.cs

public interface ICurrentUserService {
        string UserId { get; }
        bool IsAuthenticated { get; }
}

DbContext.cs

public class ApplicationDbContext : IdentityDbContext {
        private readonly ICurrentUserService _currentUserService;

        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
            : base(options) {
        }

        public ApplicationDbContext(
            DbContextOptions<ApplicationDbContext> options,
            ICurrentUserService currentUserService)
            : base(options) {
            _currentUserService = currentUserService;
        }
    }

Debug screenshoot: enter image description here

is_oz
  • 813
  • 1
  • 8
  • 27
  • @AlexeiLevenkov which method? I add DI. – is_oz Jul 21 '20 at 17:31
  • 1
    Sorry, when copy-paste Identity is forgetten. Not user, User Identity information is null. As you can see, I logged in but there is no claim. – is_oz Jul 21 '20 at 17:38
  • I try to get user id so may be user is not null. However, there is no information about user. – is_oz Jul 21 '20 at 17:41

1 Answers1

3

HttpContext is only valid during a request.The Configure method in Startup is not a web call and, as such, does not have a HttpContext. When .NET Core creates an ApplicationDbContext class for the call to Configure there is no valid context.

You could get the HttpContext in the controller when you send request Home/Index:

public class HomeController : Controller
{
    private IHttpContextAccessor _httpContextAccessor;

    public HomeController(IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor;
        var x = _httpContextAccessor.HttpContext?.User?.FindFirstValue(ClaimTypes.NameIdentifier); //get in the constructor
    }

    public IActionResult Index()
    {
        // you could also get in your method
        var x = _httpContextAccessor.HttpContext?.User?.FindFirstValue(ClaimTypes.NameIdentifier);
        return View();
    }
}

Result: enter image description here

Update:

Only if you call this service,then you could get the data:

public class HomeController : Controller
{
    private readonly ICurrentUserService _service;
    public HomeController(ICurrentUserService service)
    {
        _service = service;
    }

    public IActionResult Index()
    {
        var data = _service.UserId;
        return View();
    }
}

If you want to get the data in the middleware,please check:

app.UseRouting();

app.UseAuthentication();
app.UseAuthorization();
app.Use(async (context, next) =>
{
    await next.Invoke();
    var data = context.User.FindFirstValue(ClaimTypes.NameIdentifier);
});

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(
        name: "default",
        pattern: "{controller=Home}/{action=Index}/{id?}");
    endpoints.MapRazorPages();
});

Update2:

No matter which way you create the ApplicationDbContext instance,it could not separately get the service unless you call it.Anyway,you always need to call the service in the next business layer.

The simple way is to create a new method then you call ApplicationDbContext:

1.ApplicationContext:

public class ApplicationDbContext : IdentityDbContext
{
    private readonly ICurrentUserService _currentUserService;
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
        : base(options)
    {
    }

    public ApplicationDbContext(
        DbContextOptions<ApplicationDbContext> options,
        ICurrentUserService currentUserService)
        : base(options)
    {
        _currentUserService = currentUserService;

    }
    public string GetId()
    {
        var data = _currentUserService.UserId;
        return data;
    }
}

2.Controller:

public class HomeController : Controller
{
    private readonly ILogger<HomeController> _logger;
    private readonly ApplicationDbContext _context;
    public HomeController(ILogger<HomeController> logger, ApplicationDbContext context)
    {
        _context = context;
        _logger = logger;
    }

    public IActionResult Index()
    {
        var data = _context.GetId();
        return View();
    }
}
Rena
  • 30,832
  • 6
  • 37
  • 72
  • Do I need to add all controller this? Is there any best practices about this? I want to use Middleware. – is_oz Jul 22 '20 at 07:51
  • According the [documentation](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/http-context?view=aspnetcore-3.1#use-httpcontext-from-custom-components), it will be possible from another class, am I wrong? – is_oz Jul 22 '20 at 08:04
  • 1
    Yes,you could use it in another class.But you need to know that you do not send request to the `CurrentUserService `.You could get into the `CurrentUserService` is because the `Configure` method would always run.This is not the web request.Check my updated answer. – Rena Jul 22 '20 at 08:13
  • Thanks your answer help me much. I have one last question. Now in controller, I get user Id but in dbcontext I haven't. `public ApplicationDbContext( DbContextOptions options, ICurrentUserService currentUserService) : base(options) { _currentUserService = currentUserService; }` – is_oz Jul 22 '20 at 08:22
  • In asp.net core,dbcontext is not a simple request as controller.For controller,you could send request directly.But for dbcontext,you could not send request directly.You could only inject it in the other layer and call it.Please check my answer. – Rena Jul 22 '20 at 08:42
  • Thanks:) Now I try to implement user credential. still I have some questions but try ownself first.:) – is_oz Jul 22 '20 at 09:03