Blazor Server IHttpContextAccessor returning null when deployed - blazor-server-side

I am dynamically setting the connection string based on the user that is logged in. This is working perfectly when running locally/debugging but when its deployed it's returning a null reference exception. This is my DbContext and I am handling the connection string logic in the OnConfigure method:
namespace SwordfishCRM.Services.ApplicationDbContext
{
public class PrimaryApplicationDbContext : IdentityDbContext<ApplicationUser>
{
private readonly HttpContext context;
private readonly IConfiguration configuration;
public PrimaryApplicationDbContext(DbContextOptions<PrimaryApplicationDbContext> options, IHttpContextAccessor httpContextAccessor, IConfiguration configuration) : base(options)
{
this.context = httpContextAccessor.HttpContext;
this.configuration = configuration;
}
//Tables
public DbSet<Address> Addresses { get; set; }
public DbSet<ChimneySweepCycle> ChimneySweepCycles { get; set; }
public DbSet<ChimneySweepMethod> ChimneySweepMethods { get; set; }
public DbSet<ContactMethod> ContactMethods { get; set; }
public DbSet<ContactPreference> ContactPreferences { get; set; }
public DbSet<Customer> Customers { get; set; }
public DbSet<Diary> Diary { get; set; }
public DbSet<DiaryDate> DiaryDates { get; set; }
public DbSet<DiaryOption> DiaryOptions { get; set; }
public DbSet<Gender> Genders { get; set; }
public DbSet<Job> Jobs { get; set; }
public DbSet<JobActualLine> JobActualLines { get; set; }
public DbSet<JobApproval> JobApprovals { get; set; }
public DbSet<JobQuoteLine> JobQuoteLines { get; set; }
public DbSet<JobStatus> JobStatuses { get; set; }
public DbSet<JobType> JobTypes { get; set; }
public DbSet<Product> Products { get; set; }
public DbSet<Report> Reports { get; set; }
public DbSet<RetroSweepingCertificate> RetroSweepingCertificates { get; set; }
public DbSet<ScheduleDay> ScheduleDays { get; set; }
public DbSet<ScheduleExceptionDay> ScheduleExceptionDays { get; set; }
public DbSet<Title> Titles { get; set; }
//Views
public virtual DbSet<vwAvailableApoointment> vwAvailableApoointments { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder
.Entity<vwAvailableApoointment>(eb =>
{
eb.HasNoKey();
eb.ToView("vwAvailableApoointments");
});
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
var userId = context.User.Identity.Name;
if (userId == null)
{
optionsBuilder.UseSqlServer(configuration.GetConnectionString("Master"));
}
else
{
var prefix = IdentitySupport.GetConnectionPrefix(userId);
optionsBuilder.UseSqlServer(configuration.GetConnectionString(prefix));
}
}
}
}
This is the console error when it's been deployed:
Error: System.NullReferenceException: Object reference not set to an instance of an object.
at SwordfishCRM.Services.ApplicationDbContext.PrimaryApplicationDbContext.OnConfiguring(DbContextOptionsBuilder optionsBuilder) in D:\a\1\s\SwordfishCRM.Services\ApplicationDbContext\PrimaryApplicationDbContext .cs:line 67
at Microsoft.EntityFrameworkCore.DbContext.get_ContextServices()
at Microsoft.EntityFrameworkCore.DbContext.get_Model()
at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.get_EntityType()
at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.CheckState()
at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.get_EntityQueryable()
at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.System.Linq.IQueryable.get_Provider()
at System.Linq.Queryable.OrderBy[TSource,TKey](IQueryable`1 source, Expression`1 keySelector)
at SwordfishCRM.Web.Pages.Swordfish.DiaryScheduleExceptionDates.OnInitializedAsync() in D:\a\1\s\SwordfishCRM.Web\Pages\Swordfish\DiaryScheduleExceptionDates.razor:line 25
at Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync()
I am confused why this run's locally but not when deployed. I would welcome any support.
Thank you
I have tried different variations of HTTPContext but to no avail. I am expecting it to find the logged in user, so I can retrieve a code to set the connection string.
There is a fail safe to use the config for the Master database (this is not the actual Master database, just a name for the main database with all the users etc...)
There is a step before this that if they are not logged in, they wont even hit this screen.

It turned out I needed AuthenticationStateProvider, then I needed to place a wait until the the prefix was returned. This is working a treat!!
protected override async void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
var authstate = await authenticationStateProvider.GetAuthenticationStateAsync();
var userId = authstate.User;
if (userId == null)
{
optionsBuilder.UseSqlServer(configuration.GetConnectionString("Master"));
}
else
{
var prefix = IdentitySupport.GetConnectionPrefix(userId.Identity.Name);
await Task.CompletedTask;
optionsBuilder.UseSqlServer(configuration.GetConnectionString(prefix));
}
}

Related

Using existing .Net Identity database in .NET CORE application

im new to identity core and am trying to create a mvc app using .Net Core 3.1.1 and hit a roadblock on identity.
My toughest restriction is that I need to use the old Identity database from an old MVC5 app. The app is in use and this means the table structure cannot be changed.
Followed the steps described in Sql Exeption: Invalid column name "NormalizedUserName..." in existing .net framework identiy db used by .net core project
Got Usermanager and models set up in startup:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("RaksaConnection")));
services.AddDbContext<RaksaDbContext>(options=>
options.UseSqlServer(
Configuration.GetConnectionString("RaksaConnection")));
services.AddIdentity<ApplicationUser, ApplicationRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
//services.AddTransient <UserManager<ApplicationUser>>();
services.AddControllersWithViews();
services.AddRazorPages();
}
And models copied over from older site:
public class ApplicationUser : IdentityUser
{
public ApplicationUser()
{
this.Id = Guid.NewGuid().ToString();
}
public string FirstName { get; set; }
public string LastName { get; set; }
private string PF_FName;
private string PF_FNameAndCompany;
public int? NeptonUserId { get; set; }
public string NeptonUserName { get; set; }
public int? NeptonUserPersonnelNumber { get; set; }
public virtual Guid CompanyDataId { get; set; }
public override string UserName { get; set; }
public override string NormalizedEmail { get { return this.Email.ToUpperInvariant(); } }
public override string NormalizedUserName { get { return this.UserName.ToUpperInvariant(); } }
public DateTime? LockoutEndDateUtc { get; set; }
private string PF_CompanyVat { get; set; }
private string PF_JobTitleValue { get; set; }
private DateTime PF_AccountCreated { get; set; }
public bool GIG_worker { get; set; }
public DateTime? LastLogin { get; set; }
public int CommLevelId { get; set; }
[Display(Name = "Nimi")]
public string FullName
{
get
{
this.PF_FName = FirstName + " " + LastName;
return this.PF_FName;
}
}
public DateTime AccountCreated
{
get
{
return this.PF_AccountCreated;
}
set
{
this.PF_AccountCreated = value;
}
}
private bool PF_Isrejected { get; set; }
public bool IsRejected
{
get
{
return this.PF_Isrejected;
}
set
{
this.PF_Isrejected = value;
}
}
public bool Active { get; set; }
public string JobTitle
{
get
{
return this.PF_JobTitleValue;
}
set
{
this.PF_JobTitleValue = value;
}
}
}
public class ApplicationRole : IdentityRole
{
public ApplicationRole()
{
base.Id = Guid.NewGuid().ToString();
}
public ApplicationRole(string name) : this()
{
base.Name = name;
}
public ICollection<Permission> Permissions { get; set; }
}
public class Permission
{
public Permission()
{
this.PermissionId = Guid.NewGuid().ToString();
this.RolesWithPermission = new HashSet<ApplicationRole>();
}
public string PermissionId { get; set; }
public string PermissionDescription { get; set; }
public ICollection<ApplicationRole> RolesWithPermission;
}
}
Can get the usermanager to list the users just fine, but SignInManager compalains about NormalizedUsername field:
InvalidOperationException: The LINQ expression 'DbSet<ApplicationUser>
.Where(a => a.NormalizedUserName == __normalizedUserName_0)' could not be translated.
Either rewrite the query in a form that can be translated, or switch to client evaluation
explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(),
or ToListAsync(). See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
Don't know where to go next, do I have to customize signInmanager or is there a more simple solution?

How to update an existing entity that has a nested list of entities?

I'm trying to update an entity using entity framework but, everytime I try to do it, it raises an error saying that a nested entity the main class contains cannot be tracked.
These are my classes:
public abstract class BaseEntity
{
public int Id { get; set; }
}
public class Dashboard : BaseEntity
{
public int Order { get; set; }
public string Title { get; set; }
public bool Enabled { get; set; }
public virtual ICollection<Submenu> Submenu { get; set; }
}
public class Submenu : BaseEntity
{
public int Order { get; set; }
public bool Enabled { get; set; }
public string Title { get; set; }
public string Image { get; set; }
public string Descriptions { get; set; }
public virtual ICollection<Action> Actions { get; set; }
public int DashboardId { get; set; }
public virtual Dashboard Dashboard { get; set; }
}
public class Action : BaseEntity
{
public string Type { get; set; }
public string Label { get; set; }
public string Url { get; set; }
public string Extension { get; set; }
public virtual Submenu Submenu { get; set; }
public int SubmenuId { get; set; }
}
The one I am using to update is Dashboard, which contains the rest of the classes.
I'm trying to do it using a generic service layer and a generic repository that are defined this way:
public class GenericService<T> : IGenericService<T> where T : BaseEntity
{
private readonly IBaseRepository<T> baseRepository;
public GenericService(IBaseRepository<T> baseRepository)
{
this.baseRepository = baseRepository;
}
public async Task Update(T entity, T attachedEntity)
{
await baseRepository.Update(entity, attachedEntity);
}
}
public class BaseRepository<T> : IBaseRepository<T> where T : BaseEntity
{
private readonly PortalContext dataContext;
private DbSet<T> DbSet { get; set; }
public BaseRepository(PortalContext context)
{
dataContext = context;
DbSet = dataContext.Set<T>();
}
public async Task Update(T entity, T attachedEntity)
{
dataContext.Entry(attachedEntity).State = EntityState.Detached;
DbSet.Attach(entity);
dataContext.Entry(entity).State = EntityState.Modified;
await dataContext.SaveChangesAsync();
}
}
And, at last but no least, this is the way I am configuring everything at Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<PortalContext>(
options => options.UseSqlServer(Configuration.GetConnectionString("PortalContext"))
);
services.AddTransient(typeof(IGenericService<>), typeof(GenericService<>));
services.AddTransient(typeof(IBaseRepository<>), typeof(BaseRepository<>));
services.AddTransient<Func<string, ClaimsPrincipal, IRoleCheck>>((serviceProvider) =>
{
return (controllerName, claimsPrincipal) =>
new RoleCheck(serviceProvider.GetRequiredService<IGenericService<Dossier>>(),
serviceProvider.GetRequiredService<IGenericService<DossierTemplate>>(),
serviceProvider.GetRequiredService<IGenericService<Dashboard>>(),
controllerName, claimsPrincipal);
});
}
What the application first does is calling the RoleCheck class to retrieve and filter the required entities and, after that, the user can update them.
When I call the update function at the controller
public async Task<ActionResult<Dashboard>> Put(int id, [FromBody] Dashboard dashboard)
{
var currentDashboard = await service.Get(id);
if (currentDashboard == null)
{
return NotFound();
}
await service.Update(dashboard, currentDashboard);
return Ok();
}
I always receive the next error at the repository:
error
Is there something I am doing wrong? I have been stuck with this for a week now...
Thanks in advance and sorry for the long text, but I wanted it to be clear.
I could finally solve it by adding .AsNoTracking() at the Get() method of my repository:
public async Task<T> Get(int id, Func<IQueryable<T>, IIncludableQueryable<T, object>> includes)
{
IQueryable <T> query = DbSet.AsNoTracking();
if (includes != null)
{
query = includes(query);
}
return await query.FirstOrDefaultAsync(m => m.Id == id);
}

asp.net core identity. insert logged in user ID to a table as FK

How can I insert current user into the Invite table ask FK?
Invite Model have been scaffolded
Models
{
public int ID { get; set; }
[ForeignKey("UserId")] //ICollection<Invite> in User
[Display(Name = "Users")]
public virtual ApplicationUser User { get; set; }
}
=================
public class ApplicationUser : IdentityUser
{
public string Description { get; set; }
[ForeignKey("GameID")]
public int? GameID { get; set; }
public Game Game { get; set; }
public string GameTag { get; set; }
public virtual ICollection<Invite> Invite { get; set; }
For your Invite model does not specify the ForeingnKey as a property,you just get the current user and add to the Invite instance:
public class InvitesController : Controller
{
private readonly ApplicationDbContext _context;
private readonly UserManager<ApplicationUser> _userManager;
public InvitesController(ApplicationDbContext context, UserManager<ApplicationUser> userManager)
{
_userManager = userManager;
_context = context;
}
[HttpPost]
public async Task<IActionResult> Create(Invite invite)
{
if (ModelState.IsValid)
{
invite.User = await _userManager.GetUserAsync(User); //add this...
_context.Add(invite);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(invite);
}
}
Model:
public class Invite
{
public int ID { get; set; }
[ForeignKey("UserId")] //ICollection<Invite> in User
[Display(Name = "Users")]
public virtual ApplicationUser User { get; set; }
}
public class ApplicationUser : IdentityUser
{
public string Description { get; set; }
[ForeignKey("GameID")]
public int? GameID { get; set; }
public Game Game { get; set; }
public string GameTag { get; set; }
public virtual ICollection<Invite> Invite { get; set; }
}
public class Game
{
public int Id { get; set; }
public string Name { get; set; }
}

ASP .NET Core 3 - Setup Identity Results in Errors

I am trying to setup a new asp .net core 3 project. I am trying to asp .net identity into it. When I attempt to create a database migration, I get the following error;
An error occurred while accessing the Microsoft.Extensions.Hosting services. Continuing without the application service provider. Error: GenericArguments[0], 'Microsoft.AspNetCore.Identity.IdentityUser', on 'Microsoft.AspNetCore.Identity.EntityFrameworkCore.UserOnlyStore`6[TUser,TContext,TKey,TUserClaim,TUserLogin,TUserToken]' violates the constraint of type 'TUser'.
I've spent all afternoon trying to figure this out, but I can't figure out what this means, or more importantly, how to fix it.
My program class looks like this:
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
My Startup.cs file looks like this:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<McClureDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<McClureDbContext>();
services.AddRazorPages();
services.Configure<IdentityOptions>(options =>
{
// Password settings.
options.Password.RequireDigit = true;
options.Password.RequireLowercase = true;
options.Password.RequireNonAlphanumeric = true;
options.Password.RequireUppercase = true;
options.Password.RequiredLength = 6;
options.Password.RequiredUniqueChars = 1;
// Lockout settings.
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
options.Lockout.MaxFailedAccessAttempts = 5;
options.Lockout.AllowedForNewUsers = true;
// User settings.
options.User.AllowedUserNameCharacters =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._#+";
options.User.RequireUniqueEmail = false;
});
services.ConfigureApplicationCookie(options =>
{
// Cookie settings
options.Cookie.HttpOnly = true;
options.ExpireTimeSpan = TimeSpan.FromMinutes(5);
options.LoginPath = "/Account/Login";
options.AccessDeniedPath = "/Account/AccessDenied";
options.SlidingExpiration = true;
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
});
}
}
My DbContext Class looks like this:
public class McClureDbContext : Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityDbContext<ApplicationUser, ApplicationRole, Guid>
{
public McClureDbContext()
{
}
public McClureDbContext(DbContextOptions<McClureDbContext> options): base(options)
{
}
public DbSet<Home> Homes { get; set; }
public DbSet<HomePicture> HomePictures { get; set; }
public DbSet<HomeVideo> HomeVideos { get; set; }
public DbSet<ApplicationUser> AspNetUsers { get; set; }
public DbSet<Province> Province { get; set; }
public DbSet<Country> Country { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
var builder = new ConfigurationBuilder()
.SetBasePath(System.IO.Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json");
var Configuration = builder.Build();
var connString = Configuration.GetConnectionString("DefaultConnection");
optionsBuilder.UseSqlServer(connString, options => options.EnableRetryOnFailure());
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<Home>().Property(n => n.Sold).HasDefaultValue(false);
}
}
My three data types (for the moment), look like this:
public class Home
{
public Home()
{
}
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Int32 HomeId { get; set; }
public Guid AspNetUsersId { get; set; }
[ForeignKey("AspNetUsersId")]
public ApplicationUser ApplicationUser { get; set; }
[MaxLength(100)]
public string Address1 { get; set; }
[MaxLength(100)]
public string Address2 { get; set; }
[MaxLength(100)]
public string City { get; set; }
public Int64 ProvinceId { get; set; }
[ForeignKey("ProvinceId")]
public Province Province { get; set; }
[MaxLength(20)]
public string PostalCode { get; set; }
public double? Latitude { get; set; }
public double? Longitude { get; set; }
public bool Sold { get; set; }
public ICollection<HomeVideo> HomeVideos { get; set; }
public ICollection<HomePicture> HomePictures { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime DateEntered { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime DateUpdated { get; set; }
}
public class HomeVideo
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Int32 HomeVideoId { get; set; }
public Int32 HomeId { get; set; }
[ForeignKey("HomeId")]
public Home Home { get; set; }
[MaxLength(500)]
public string Description { get; set; }
[MaxLength(1024)]
public string VideoUrl { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime DateEntered { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime DateUpdated { get; set; }
}
public class HomePicture
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Int32 HomePictureId { get; set; }
public Int32 HomeId { get; set; }
[ForeignKey("HomeId")]
public Home Home { get; set; }
[MaxLength(500)]
public string Description { get; set; }
[MaxLength(1024)]
public string PictureUrl { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime DateEntered { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime DateUpdated { get; set; }
}
I don't remember having any of these types of issues when I built stuff in asp .net core 2.x. I figure that I am doing something wrong, but have no idea how to fix it. Searching google has not been much help today. I am at a loss. Any ideas/directions are appreciated.
TIA,
Wally

Setting up Automapper 5.1

I am having trouble following the wiki in this instance. I wanted to use Automapper 5.2. I cannot find a simple end for end example that shows a solid configuration with context. By context I mean where do you put the config files and whats the difference between static and instance api?
I checked out the DNRTV download but it deals with the 1.0 version.
How do you set this package up? I have a model called Client as below.
public class Client : IEntityBase
{
public Client()
{
Jobs = new List<Job>();
}
public int Id { get; set; }
public int ClientNo { get; set; }
public bool Company { get; set; }
public string CompanyName { get; set; }
public string ClientFirstName { get; set; }
public DateTime DeActivated { get; set; }
public bool Activity { get; set; }
public DateTime DateCreated { get; set; }
public DateTime DateUpdated { get; set; }
public int? StateId { get; set; }
public State State { get; set; }
public int CreatorId { get; set; }
public User Creator { get; set; }
public ICollection<Job> Jobs { get; set; }
}
and a ClientViewModel as so:
public class ClientViewModel
{
public int Id { get; set; }
public int ClientNo { get; set; }
public bool Company { get; set; }
public string CompanyName { get; set; }
public string ClientFirstName { get; set; }
public DateTime DeActivated { get; set; }
public bool Activity { get; set; }
public DateTime DateCreated { get; set; }
public DateTime DateUpdated { get; set; }
public int? StateId { get; set; }
public int CreatorId { get; set; }
public int[] Jobs { get; set; }
}
I am unsure how to set AutoMapper up with regard to configuration. That is, they talk about a global.asax file and I am using aspnet core.. there is no Global.asax file..
What do you put in the Startup.cs file if anything.
Given these two files above what do I need to do to use Automapper with them?
Regards
Here is the steps to configure the automapper in asp.net core mvc.
1. Create the mapping profile class which extends from Profile
public class ClientMappingProfile : Profile
{
public ClientMappingProfile ()
{
CreateMap<Client, ClientViewModel>().ReverseMap();
}
}
2. Create the AutoMapper Configuration Class and add your mapping profile class here.
public class AutoMapperConfiguration
{
public MapperConfiguration Configure()
{
var config = new MapperConfiguration(cfg =>
{
cfg.AddProfile<ClientMappingProfile>();
});
return config;
}
}
3. Create extension method so, we can add this to Startup.cs ConfigureServices method
public static class CustomMvcServiceCollectionExtensions
{
public static void AddAutoMapper(this IServiceCollection services)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
var config = new AutoMapperConfiguration().Configure();
services.AddSingleton<IMapper>(sp => config.CreateMapper());
}
}
4. Call the extension method in Startup.cs ConfigureServices method
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<DBContext>(options =>options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddMvc();
services.AddAutoMapper();
}