IHttpContextAccessor returns no result in deployed website (might be because IHttpContextAccessor is not thread safe?)
Nothing to do with whether IHttpContextAccessor is not thread safe...
It's simply because the HttpContext object is not available in Blazor Server App, as communication between the client side (browser) and server side is done through the SignalR protocol, not HTTP. But there is a way how to access the HttpContext object before the Blazor App is rendered, as the initial call to the app is always made through HTTP request; that is, when you enter a url into the address bar of your browser and hit the enter button. See here how to do that...
The following code snippet describes how to inject an AuthenticationStateProvider into the ApplicationDbContext
object created by default when you select Individual Accounts in Blazor Server App.
Copy and test. It should work...
Data/ApplicationDbContext.cs
public class ApplicationDbContext : IdentityDbContext
{
public DbSet<Employee> Employees { get; set; }
private AuthenticationStateProvider _authenticationStateProvider;
public ApplicationDbContext(DbContextOptions<ApplicationDbContext>
options, AuthenticationStateProvider stateProvider)
: base(options)
{
_authenticationStateProvider = stateProvider;
}
public override async Task<int>
SaveChangesAsync(CancellationToken cancellationToken)
{
var stateProvider = await
_authenticationStateProvider.GetAuthenticationStateAsync();
if (stateProvider.User.Identity.IsAuthenticated)
{
Console.WriteLine("Authenticated User name: " +
stateProvider.User.Identity.Name);
}
// Delegate the saving action to the base class
return await base.SaveChangesAsync(cancellationToken);
}
}
Create an Employee Repository class service:
EmployeeRepository.cs
using <put here the namespace of your app>.Data;
using <put here the namespace of your app>.Models;
using Microsoft.EntityFrameworkCore;
public class EmployeeRepository
{
private readonly ApplicationDbContext ApplicationDbContext;
public EmployeeRepository(ApplicationDbContext
applicationDbContext)
{
ApplicationDbContext = applicationDbContext;
}
public async Task<Employee> CreateEmployee(Employee employee)
{
CancellationTokenSource cancellationTokenSource = new
CancellationTokenSource();
CancellationToken token = cancellationTokenSource.Token;
await ApplicationDbContext.Employees.AddAsync(employee);
await ApplicationDbContext.SaveChangesAsync(token);
return employee;
}
}
Index.razor
@inject EmployeeRepository EmployeeRepository
@using <Put here....>.Models
<button type="button" @onclick="SaveEmployee">Save Employee</button>
@if (emp != null)
{
<div>@emp.ID.ToString()</div>
<div>@emp.FirstName</div>
<div>@emp.LastName</div>
<div>@emp.City</div>
}
@code
{
private Employee emp;
private async Task SaveEmployee()
{
Employee employee = new Employee { FirstName = "Joana", LastName = "Brown", City = "London" };
emp = await EmployeeRepository.CreateEmployee(employee);
}
}
Create model class Employee:
Models/Employee.cs
public class Employee
{
public int ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string City { get; set; }
}
Note: To test this code, you'll have to create A Blazor Server App with Individual Accounts, create the database, including the Employees table
Last but not least: Startup
// Created by the default template
//services.AddDbContext<ApplicationDbContext>(options =>
// options.UseSqlServer(
// Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<IdentityUser>(options =>
options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddDbContextFactory<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")),
ServiceLifetime.Scoped);
// This is your code...
services.AddScoped<ApplicationDbContext>(p =>
p.GetRequiredService<IDbContextFactory<ApplicationDbContext>>
().CreateDbContext());
services.AddScoped<EmployeeRepository>();
services.AddScoped<AuthenticationStateProvider,
RevalidatingIdentityAuthenticationStateProvider<IdentityUser>>();
services.AddDatabaseDeveloperPageExceptionFilter();
services.AddSingleton<WeatherForecastService>();
UPDATE:
but does that no against the the recommendations of Microsoft? They ae suggesting to always use using
var context = DbFactory.CreateDbContext();
You mean:
using var context = DbFactory.CreateDbContext();
No, it is not against the recommendations of Microsoft. It's another way to instantiate the DbContext. I did it that way in order to stick to this code by you:
services.AddScoped<ApplicationDbContext>(p => p.GetRequiredService<IDbContextFactory<ApplicationDbContext>>().CreateDbContext());
Anyhow, these are the changes you should make in order to reflect "Microsoft's recommendations"
Change:
services.AddScoped<ApplicationDbContext>(p => p.GetRequiredService<IDbContextFactory<ApplicationDbContext>>().CreateDbContext());
To:
services.AddScoped<ApplicationDbContext>();
Change:
private readonly ApplicationDbContext ApplicationDbContext;
public EmployeeRepository(ApplicationDbContext
applicationDbContext)
{
ApplicationDbContext = applicationDbContext;
}
To:
private readonly IDbContextFactory<ApplicationDbContext>
DbFactory;
public EmployeeRepository(IDbContextFactory<ApplicationDbContext>
_DbFactory)
{
DbFactory = _DbFactory;
}
And change:
await ApplicationDbContext.Employees.AddAsync(employee);
await ApplicationDbContext.SaveChangesAsync(token);
To:
await context.Employees.AddAsync(employee);
await context.SaveChangesAsync(token);
Also add:
using var context = DbFactory.CreateDbContext();
at the beginning of the EmployeeRepository.CreateEmployee
method
Run and test.
Hope this work...
New Version
Data/ApplicationDbContext.cs
public class ApplicationDbContext : IdentityDbContext
{
public DbSet<Employee> Employees { get; set; }
private AuthenticationStateProvider _authenticationStateProvider;
public ApplicationDbContext(DbContextOptions<ApplicationDbContext>
options, AuthenticationStateProvider stateProvider)
: base(options)
{
_authenticationStateProvider = stateProvider;
}
public override async Task<int>
SaveChangesAsync(CancellationToken cancellationToken)
{
var stateProvider = await
_authenticationStateProvider.GetAuthenticationStateAsync();
if (stateProvider.User.Identity.IsAuthenticated)
{
Console.WriteLine("Authenticated User name: " +
stateProvider.User.Identity.Name);
}
// Delegate the saving action to the base class
return await base.SaveChangesAsync(cancellationToken);
}
}
Create an Employee Repository class service:
EmployeeRepository.cs
using <put here the namespace of your app>.Data;
using <put here the namespace of your app>.Models;
using Microsoft.EntityFrameworkCore;
public class EmployeeRepository
{
private readonly IDbContextFactory<ApplicationDbContext> DbFactory;
public EmployeeRepository(IDbContextFactory<ApplicationDbContext> _DbFactory)
{
DbFactory = _DbFactory;
}
public async Task<Employee> CreateEmployee(Employee
employee)
{
using var context = DbFactory.CreateDbContext();
// CancellationTokenSource provides the token and have authority to cancel the token
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
CancellationToken token = cancellationTokenSource.Token;
await context.Employees.AddAsync(employee);
await context.SaveChangesAsync(token);
return employee;
}
}
Index.razor
@inject EmployeeRepository EmployeeRepository
@using <Put here....>.Models
<button type="button" @onclick="SaveEmployee">Save Employee</button>
@if (emp != null)
{
<div>@emp.ID.ToString()</div>
<div>@emp.FirstName</div>
<div>@emp.LastName</div>
<div>@emp.City</div>
}
@code
{
private Employee emp;
private async Task SaveEmployee()
{
Employee employee = new Employee { FirstName = "Joana", LastName = "Brown", City = "London" };
emp = await EmployeeRepository.CreateEmployee(employee);
}
}
Create model class Employee:
Models/Employee.cs
public class Employee
{
public int ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string City { get; set; }
}
Note: To test this code, you'll have to create A Blazor Server App with Individual Accounts, create the database, including the Employees table
Last but not least: Startup
public void ConfigureServices(IServiceCollection services)
{
services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddDbContextFactory<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")),
ServiceLifetime.Scoped);
services.AddScoped<ApplicationDbContext>();
services.AddScoped<EmployeeRepository>();
services.AddScoped<AuthenticationStateProvider, RevalidatingIdentityAuthenticationStateProvider<IdentityUser>>();
services.AddDatabaseDeveloperPageExceptionFilter();
services.AddSingleton<WeatherForecastService>();
}