Returning null on httpContextAccessor.HttpContext

15,503

HttpContext is only valid during a request. When .NET Core creates an ApplicationDbContext class for the call to Configure there is no valid context.

You need to store a reference to the IHttpContextAccessor in your DbContext constructor and then you can use that variable to access the HttpContext property in your OnBeforeSaving() method.

For example:

public class ApplicationDbContext : IdentityDbContext<ApplicationUser, ApplicationRole, string>
{
    private readonly IHttpContextAccessor _httpContextAccessor;

    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options,
            IHttpContextAccessor httpContextAccessor
            )
    : base(options)
    {
        _httpContextAccessor = httpContextAccessor;
    }

   ....
}

Then, in your OnBeforeSaving() method:

private void OnBeforeSaving()
{
    var userName = _httpContextAccessor.HttpContext.User.Identity.Name;

    ...
}

Think of HttpContext as a telephone call. If you pick the phone up when no-one has called then there is no context i.e. it is null. When someone does call then you have a valid context. This is the same principal for a web call. The Configure method in Startup is not a web call and, as such, does not have a HttpContext.

From another site:

HttpContext object will hold information about the current http request. In detail, HttpContext object will be constructed newly for every request given to an ASP.Net application and this object will hold current request specific informations like Request, Response, Server, Session, Cache, User and etc. For every request, a new HttpContext object will be created which the ASP.Net runtime will use during the request processing. A new HttpContext object will be created at the beginning of a request and destroyed when the request is completed.

Share:
15,503
Celsius
Author by

Celsius

Updated on July 16, 2022

Comments

  • Celsius
    Celsius almost 2 years

    We override SaveChangesAsync() to update automatically for DateCreated, CreatedBy, LastDateModified and LastModifiedBy. With CreatedBy and LastModifiedBt, we need to the User Id of Identity.

    In our constructor for ApplicationDbContext, we've added something like this:

    _userName = httpContextAccessor.HttpContext.User.Identity.Name; //_userID = userManager.GetUserId(httpContext.HttpContext.User);

    .. and always get the null in this httpContextAccessor.HttpContext. Any ideas? We included the source below.

    Environment:

    .NET Core 2.1

    SQL Server

    ApplicationDBContext.cs:

    using System;
    using System.Collections.Generic;
    using System.Text;
    using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
    using Microsoft.EntityFrameworkCore;
    using AthlosifyWebArchery.Models;
    using System.Linq;
    using System.Threading;
    using System.Threading.Tasks;
    using System.Linq.Expressions;
    using Microsoft.AspNetCore.Http;
    using Microsoft.AspNetCore.Identity;
    
    namespace AthlosifyWebArchery.Data
    {
        public class ApplicationDbContext : IdentityDbContext<ApplicationUser, ApplicationRole, string>
        {
            private readonly string _userID;
            private readonly string _userName;
    
    
            public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options,
                    IHttpContextAccessor httpContextAccessor
                    )
            : base(options)
            {
                _userName = httpContextAccessor.HttpContext.User.Identity.Name;
    
                //_userID = userManager.GetUserId(httpContext.HttpContext.User);
    
            }
    
            public DbSet<AthlosifyWebArchery.Models.TournamentBatchItem> TournamentBatchItem { get; set; }
            public DbSet<AthlosifyWebArchery.Models.TournamentBatch> TournamentBatch { get; set; }
    
            public virtual DbSet<AthlosifyWebArchery.Models.Host> Host { get; set; }
    
            public DbSet<AthlosifyWebArchery.Models.HostApplicationUser> HostApplicationUser { get; set; }
    
            protected override void OnModelCreating(ModelBuilder builder)
            {
                base.OnModelCreating(builder);
    
                foreach (var entityType in builder.Model.GetEntityTypes())
                {
                    // 1. Add the IsDeleted property
                    entityType.GetOrAddProperty("IsDeleted", typeof(bool));
    
                    // 2. Create the query filter
    
                    var parameter = Expression.Parameter(entityType.ClrType);
    
                    // EF.Property<bool>(post, "IsDeleted")
                    var propertyMethodInfo = typeof(EF).GetMethod("Property").MakeGenericMethod(typeof(bool));
                    var isDeletedProperty = Expression.Call(propertyMethodInfo, parameter, Expression.Constant("IsDeleted"));
    
                    // EF.Property<bool>(post, "IsDeleted") == false
                    BinaryExpression compareExpression = Expression.MakeBinary(ExpressionType.Equal, isDeletedProperty, Expression.Constant(false));
    
                    // post => EF.Property<bool>(post, "IsDeleted") == false
                    var lambda = Expression.Lambda(compareExpression, parameter);
    
                    builder.Entity(entityType.ClrType).HasQueryFilter(lambda);
                }
    
    
                // Many to Many relationship
    
                builder.Entity<HostApplicationUser>()
                    .HasKey(bc => new { bc.HostID, bc.Id });
    
    
                builder.Entity<HostApplicationUser>()
                    .HasOne(bc => bc.Host)
                    .WithMany(b => b.HostApplicationUsers)
                    .HasForeignKey(bc => bc.HostID);
    
                builder.Entity<HostApplicationUser>()
                    .HasOne(bc => bc.ApplicationUser)
                    .WithMany(c => c.HostApplicationUsers)
                    .HasForeignKey(bc => bc.Id);
    
            }
    
            public override int SaveChanges(bool acceptAllChangesOnSuccess)
            {
                OnBeforeSaving();
                return base.SaveChanges(acceptAllChangesOnSuccess);
            }
    
            public override Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default(CancellationToken))
            {
                OnBeforeSaving();
                return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
            }
    
            private void OnBeforeSaving()
            {
                // Added
                var added = ChangeTracker.Entries().Where(v => v.State == EntityState.Added && typeof(IBaseEntity).IsAssignableFrom(v.Entity.GetType())).ToList();
    
                added.ForEach(entry =>
                {
                    ((IBaseEntity)entry.Entity).DateCreated = DateTime.UtcNow;
                    ((IBaseEntity)entry.Entity).CreatedBy = _userID;
                    ((IBaseEntity)entry.Entity).LastDateModified = DateTime.UtcNow;
                    ((IBaseEntity)entry.Entity).LastModifiedBy = _userID;
                });
    
                // Modified
                var modified = ChangeTracker.Entries().Where(v => v.State == EntityState.Modified && 
                typeof(IBaseEntity).IsAssignableFrom(v.Entity.GetType())).ToList();
    
                modified.ForEach(entry =>
                {
                    ((IBaseEntity)entry.Entity).LastDateModified = DateTime.UtcNow;
                    ((IBaseEntity)entry.Entity).LastModifiedBy = _userID;
                });
    
                // Deleted
                //var deleted = ChangeTracker.Entries().Where(v => v.State == EntityState.Deleted &&
                //typeof(IBaseEntity).IsAssignableFrom(v.Entity.GetType())).ToList();
    
                var deleted = ChangeTracker.Entries().Where(v => v.State == EntityState.Deleted).ToList();
    
                deleted.ForEach(entry =>
                {
                    ((IBaseEntity)entry.Entity).DateDeleted = DateTime.UtcNow;
                    ((IBaseEntity)entry.Entity).DeletedBy = _userID;
                });
    
                foreach (var entry in ChangeTracker.Entries()
                                        .Where(e => e.State == EntityState.Deleted &&
                                        e.Metadata.GetProperties().Any(x => x.Name == "IsDeleted")))
                {
                    switch (entry.State)
                    {
                        case EntityState.Added:
                            entry.CurrentValues["IsDeleted"] = false;
                            break;
    
                        case EntityState.Deleted:
                            entry.State = EntityState.Modified;
                            entry.CurrentValues["IsDeleted"] = true;
                            break;
                    }
                }
            }
        }
    
    
    
    }
    

    Startup.cs:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    using Microsoft.AspNetCore.Builder;
    using Microsoft.AspNetCore.Identity;
    using Microsoft.AspNetCore.Hosting;
    using Microsoft.AspNetCore.Http;
    using Microsoft.AspNetCore.HttpsPolicy;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.EntityFrameworkCore;
    using AthlosifyWebArchery.Data;
    using Microsoft.Extensions.Configuration;
    using Microsoft.Extensions.DependencyInjection;
    using AthlosifyWebArchery.Models;
    using DinkToPdf.Contracts;
    using DinkToPdf;
    
    namespace AthlosifyWebArchery
    {
        public class Startup
        {
            public Startup(IConfiguration configuration)
            {
                Configuration = configuration;
            }
    
            public IConfiguration Configuration { get; }
    
            // This method gets called by the runtime. Use this method to add services to the container.
            public void ConfigureServices(IServiceCollection services)
            {
                //services.AddHttpContextAccessor();
    
                services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
                services.AddSingleton(typeof(IConverter), new SynchronizedConverter(new PdfTools()));
    
                services.Configure<CookiePolicyOptions>(options =>
                {
                    // This lambda determines whether user consent for non-essential cookies is needed for a given request.
                    options.CheckConsentNeeded = context => true;
                    options.MinimumSameSitePolicy = SameSiteMode.None;
                });
    
                services.AddDbContext<ApplicationDbContext>(options =>
                    options.UseSqlServer(
                        Configuration.GetConnectionString("DefaultConnection")));
    
                // Extended Application User from IdentityUser 
                // and ApplicationRole from IdentityRole
    
                services.AddIdentity<ApplicationUser, ApplicationRole>(
                    options => options.Stores.MaxLengthForKeys = 128)
                    .AddEntityFrameworkStores<ApplicationDbContext>()
                    .AddDefaultUI()
                    .AddDefaultTokenProviders();
    
                services.AddMvc()
                    .AddRazorPagesOptions(options =>
                    {
                        options.Conventions.AuthorizeFolder("/Tournaments");
                        options.Conventions.AuthorizeFolder("/TournamentAtheletes");
                        options.Conventions.AuthorizeFolder("/TournamentBatches");
                        options.Conventions.AuthorizeFolder("/TournamentContingents");
                        options.Conventions.AuthorizeFolder("/Admin");
                        //options.Conventions.AuthorizeFolder("/Private");
                        //options.Conventions.AllowAnonymousToPage("/Private/PublicPage");
                        //options.Conventions.AllowAnonymousToFolder("/Private/PublicPages");
                    })
                    .SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
            }
    
            // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
            public void Configure(IApplicationBuilder app, IHostingEnvironment env, 
                                    ApplicationDbContext context,
                                    RoleManager<ApplicationRole> roleManager,
                                    UserManager<ApplicationUser> userManager)
            {
                if (env.IsDevelopment())
                {
                    app.UseDeveloperExceptionPage();
                    app.UseDatabaseErrorPage();
                }
                else
                {
                    app.UseExceptionHandler("/Error");
                    app.UseHsts();
                }
    
                app.UseHttpsRedirection();
                app.UseStaticFiles();
                app.UseCookiePolicy();
    
                app.UseAuthentication();
    
                app.UseMvc();
    
                //UserManagerInitialData.Initialize(context, userManager, roleManager).Wait();
    
            }
    
    
        }
    }
    
  • Celsius
    Celsius over 5 years
    Can elaborate more on this, please?
  • Celsius
    Celsius over 5 years
    Isn't that why we did?!?! public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options, IHttpContextAccessor httpContextAccessor ) : base(options) { _userName = httpContextAccessor.HttpContext.User.Identity.Name; //_userID = userManager.GetUserId(httpContext.HttpContext.User); }
  • Celsius
    Celsius over 5 years
    Didn't think about that ... work well. Thanks. We slighly modifed to get the user id by: var userId = _httpContextAccessor.HttpContext.User.FindFirstValue(ClaimTy‌​pes.NameIdentifier);