Blazor A second operation started on this context before a previous operation completed - asp.net-core

I makes the NavMenu dynamically and return menu i the database by users and in the index page already i returned something in the database but when i run the application or reload it show me bellow error
InvalidOperationException: A second operation started on this context
before a previous operation completed. This is usually caused by
different threads using the same instance of DbContext. For more
information on how to avoid threading issues with DbContext, see
https://go.microsoft.com/fwlink/?linkid=2097913.
NavMenu code,
List<Menu> menus = new List<Menu>();
protected override async Task OnInitializedAsync()
{
menus = await MenuService.GetMenus();
}
Index code
#if (priorities == null)
{
<p><em>Loading...</em></p>
}
else
{
<table class="table">
<thead>
<tr>
<th>Name</th>
</tr>
</thead>
<tbody>
#foreach (var priority in priorities)
{
<tr>
<td>#priority.Name</td>
</tr>
}
</tbody>
</table>
}
#code {
List<Priority> priorities;
protected override async Task OnInitializedAsync()
{
priorities = await PriorityService.GetPriorities();
}
}

The solution is to use a `DbContextFactory :
Quoting docs:
Some application types (e.g. ASP.NET Core Blazor) use dependency injection but do not create a service scope that aligns with the desired DbContext lifetime. Even where such an alignment does exist, the application may need to perform multiple units-of-work within this scope. For example, multiple units-of-work within a single HTTP request.
In these cases, AddDbContextFactory can be used to register a factory for creation of DbContext instances. For example:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContextFactory<ApplicationDbContext>(
options =>
options.UseSqlServer(#"Server=(localdb)\mssqllocaldb;Database=Test"));
}

I got this error only when I attempted to access the user's information from the same service in multiple controls. Makes sense-- they are both fighting for the same resource on initialization.
For me, the solution wasn't to change the scope-- it was to re-think what I was really doing-- why have 2 separate controls trying to access Membership features for the same user at all?

My solution for a Blazor server app was to make the ApplicatonDbContext and the service both Transient:
services.AddDbContext<ApplicationDbContext>(x => x.UseSqlServer(connectionString), ServiceLifetime.Transient);
services.AddTransient<ICustomerService, CustomerService>();

Related

Calling WebAPI with authorisation from server side blazor

Apologies if this is a bit of a dumb question - I'm trying to get my head round security setup and don't have much experience in this area. Been reading as much as I can, but can't find a clear example for what I'm trying to do.
I have created the default server-side and wasm blazor projects from the visual studio templates, and have shared the wasm project so I can re-use both client-side and server-side as per Carl Franklin's article:
http://www.appvnext.com/blog/2020/2/2/reuse-blazor-wasm-ui-in-blazor-server
That all works fine.
Next, I repeat, but add the "Individual User Accounts" option to both projects when creating, set the db string to a shared identity database. Both work individually, however, when I share the client code, and call from server-side blazor, the webapi call fails with an "unauthorized" error.
So, in short, I'm logging into the server-side blazor project successfully. The failure happens when I attempt to call the webapi which now sits in a separate project (the WASM project) and so will run in a different domain (don't think I'm hitting cors problems yet). When I attempt to call the webapi I get the unauthorized error. When I run in WASM, it all works as expected.
Can someone please give me a pointer on what steps I need to take to get this working? Full code for the razor component is below...
#page "/fetchdata"
#using Microsoft.AspNetCore.Authorization
#using Microsoft.AspNetCore.Components.WebAssembly.Authentication
#using BlazorWasm.Shared
#attribute [Authorize]
#inject HttpClient Http
<h1>Weather forecast</h1>
<p>This component demonstrates fetching data from the server.</p>
#if (forecasts == null)
{
<p><em>Loading...</em></p>
}
else
{
<table class="table">
<thead>
<tr>
<th>Date</th>
<th>Temp. (C)</th>
<th>Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
#foreach (var forecast in forecasts)
{
<tr>
<td>#forecast.Date.ToShortDateString()</td>
<td>#forecast.TemperatureC</td>
<td>#forecast.TemperatureF</td>
<td>#forecast.Summary</td>
</tr>
}
</tbody>
</table>
}
#code {
private WeatherForecast[] forecasts;
protected override async Task OnInitializedAsync()
{
try
{
string url = "https://localhost:44378/WeatherForecast";
forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>(url);
}
catch (AccessTokenNotAvailableException exception)
{
exception.Redirect();
}
catch(HttpRequestException exception)
{
string msg = exception.Message;
}
}
}

Custom AuthenticationStateProvider returns "empty user"

in our application I'd like to use the user management of our fat client. For this I have written a custom AuthenticationStateProvider:
public class MyAuthenticationStateProvider : ServerAuthenticationStateProvider, IAuthentorizationService, IDisposable
{
public MyAuthenticationStateProvider (IPermissionManager permissionManager)
{
//User management service of the fat client
_permissionManager = permissionManager;
}
public override Task<AuthenticationState> GetAuthenticationStateAsync()
{
if (_permissionManager.PermissionUser == null)
{
var emptyUser = new ClaimsPrincipal(new ClaimsIdentity(new Claim[0]));
return Task.FromResult(new AuthenticationState(emptyUser));
}
var identity = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Name, _permissionManager.PermissionUser.User.GetName())
}, "FatClientAuthentication");
var user = new ClaimsPrincipal(identity);
return Task.FromResult(new AuthenticationState(user));
}
public async Task<bool> LoginUser(string userName, string password)
{
//Login via WCF connection
var response = await _clientProxy.Login(new LoginRequest
{
LoginUserName = userName,
Password = password
});
response.LogExceptionIfFaulted(_logger);
if (response.Ok)
{
_permissionManager.Initialize(response.LoggedInUser);
NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
}
return response.Ok;
}
The login works fine. For testing purposes I always log in with fixed user credentials. After the successful login I fire the NotifyAuthenticationStateChanged event, which results in a correct call of the GetAuthenticationStateAsync method. The now logged in user is correctly wrapped inside the AuthenticationState. When debugging the code I can see that the Identity with the name claim is the correct user and the IsAuthenticated property is true.
However, when using the "AuthorizeView" component, I always get an "empty user" (no name claim, no user name, IsAuthenticated is false)
I now have a small component just for testing:
<AuthorizeView>
<Authorized>
<h2>User #context.User.Identity.Name</h2> is logged in!
Claims:
<ul>
#foreach (var claim in context.User.Claims)
{
<li>Type=#claim.Type; Value=#claim.Value</li>
}
</ul>
#context.User.Claims
<p>Current count: #currentCount</p>
<button class="btn btn-primary" #onclick="IncrementCount">Click me</button>
</Authorized>
<NotAuthorized>
<h2>User #context.User.Identity.Name</h2> #*this is an empty string*#
<h2>Authentication Type: #context.User.Identity.AuthenticationType</h2> #*empty*#
<h2>Authenticated: #context.User.Identity.IsAuthenticated</h2>#*false*#
No user is logged in!
</NotAuthorized>
Im using the AuthorizeRouteView and the CascadingAuthenticationState in the App.razor like in the official sample displayed in https://learn.microsoft.com/en-us/aspnet/core/security/blazor/?view=aspnetcore-3.1
Accessing the AuthenticationState via an CascadingParameter also results in the same "empty user".
Appreciate any help,
tilt32
EDIT 1
So I looked into the login behaviour again, making sure that the event is called.
I then figured out, that my AuthenticationStateChanged event has no subscribers (is null). My impression was, that something in the framework attaches to this event at startup. Maybe I did forget some configuration method call in the startup ? This is what I do in the configure services:
services.AddScoped<AuthenticationStateProvider, MyAuthenticationStateProvider>();
services.AddScoped<ServerAuthenticationStateProvider, MyAuthenticationStateProvider>();
//Interface which I use in my LoginCompontent and at Startup to log in with the default user or some real user credentials
services.AddScoped<IAuthenticationService, MyAuthenticationStateProvider>();
I also tried the approach suggested by user enet. Sadly with no success, the result was the same. During the login a call to NotifyAuthenticationStateChanged and hence to the event with no subscribers is done.
The WCF service we use in the background requires a logged in user. Hence i made a guest user with limited rights to solve this issue. So the app steps into the GetAuthenticationStateAsync and tries to fire the AuthenticationStateEvent directly after startup (during a loading screen).
EDIT 2
So I now tried some additional setup steps, from which Microsoft wrote in the Blazor documentation, that they should not be necessary for server-side blazor:
The ConfigureServices now looks like this
//Authentication & Authorization setup
services.AddOptions();
services.AddAuthenticationCore();
services.AddAuthorizationCore();
services.AddScoped<IPermissionManager, SingleUserPermissionManager>();
services.AddScoped<AuthenticationStateProvider, MyAuthenticationStateProvider>();
services.AddScoped<ServerAuthenticationStateProvider, MyAuthenticationStateProvider>();
services.AddScoped<IAuthenticationService, MyAuthenticationStateProvider>();
In the Configure(IApplicationBuilder app, IWebHostEnvironment env) Method, I added the following calls:
app.UseAuthentication();
app.UseAuthorization();
This did also have no effect.
I think the AuthenticationState object is not available because the AuthenticationStateChanged event is not invoked from the AuthenticationStateProvider, and thus your AuthorizeView and your CascadingAuthenticationState components are not aware of the state change. Check your logic once more in this direction. Also make sure that you properly add the subclassed provider to the DI container. I tend to believe that the issue is with this. Please, show all the relevant code from the ConfigureServices method.
Update:
Please, try this:
services.AddScoped<MyAuthenticationStateProvider>();
services.AddScoped<AuthenticationStateProvider>(provider =>
provider.GetRequiredService<MyAuthenticationStateProvider>());
Hope this helps...

How to disable CSRF antiforgery in Razor Pages

I want to disable CSRF checks when I'm running under the TestServer so I don't have to read and send the token when running automated tests.
Due to the abundance of "helpful magic" creeping into ASP.NET Core I am stuck.
There's nothing in the template code that obviously adds this, and yet looking at the filters in the debugger during this services.AddMvc(options => options.Filters) call shows no global filter.
This code also does not work.
mvcOptions.Filters.Add<IgnoreAntiforgeryTokenAttribute>(0);
And the Antiforgery.Options does not have a disable option.
How can I do this?
Try this:
services.AddMvc().AddRazorPagesOptions(o =>
{
o.Conventions.ConfigureFilter(new IgnoreAntiforgeryTokenAttribute());
});
You can also ignore it at the PageModel:
[IgnoreAntiforgeryToken(Order = 1001)]
public class IndexModel : PageModel
Regarding the the Order parameter: The built in [ValidateAntiforgeryToken]
decorator has an order of 1000, therefore setting
[IgnoreAntiforgeryToken] to 1001 will override it.

How to register properly a custom IdentityServer ConfigurationDbContext in Asp.Net Core 2?

I am trying to create my own ConfigurationDbContext from IdentityServer.
public class IdSrvConfigurationDbContext : ConfigurationDbContext<ConfigurationDbContext>
{
public IdSrvConfigurationDbContext(DbContextOptions<IdSrvConfigurationDbContext> options, ConfigurationStoreOptions storeOptions) : base(options.ChangeOptionsType<ConfigurationDbContext>(), storeOptions)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
//mylogic here
base.OnModelCreating(modelBuilder);
}
}
Now in the Startup.cs I tried the following
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddConfigurationStore(options =>
{
// (DbContextOptionsBuilder) paramBuilder
options.ConfigureDbContext = paramBuilder =>
paramBuilder.UseSqlServer(connectionString,
sql => sql.MigrationsAssembly(migrationAssembly));
});
Now when I am trying to run migrations over my project, it goes through all the startup logic injection and ending with this error:
You will need to set up your IdSrvConfigurationDbContext type to expect a DbContextOptions<ConfigurationDbContext> instead. That’s the type the underlying ConfigurationDbContext expects and that’s also what the IdentityServer is going to pass down.
Usually, you should always use a typed DbContextOptions<T> matching to the context. But when inheriting from existing contexts, this can be a bit difficult. But in those cases, you don’t need to worry much: The typed options are only used to differentiate between the various configured options. So as long as each context in your application still uses a separate type, there shouldn’t be any problems.
The main reason for me to define custom ConfigurationDbContext/PersistedGrantDbContext was to set HasMaxLength over some properties. That's because in production we use NDBCLUSTER engine and if length is not defined or it's too big default type within MySQL will be longtext. This causes a problem, because NDBCLUSTER doesn't support text family data-types.
Within extension method I am plugging custom db context and at the same time setting the common ConfigurationDbContext/PersistedGrantDbContext. I guess IdentityServer uses them for the internal stuff.
services
.AddIdentityServer(options => {})
.AddConfigurationStore<ApplicationConfigurationDbContext>()
.AddConfigurationStoreCache()
.AddOperationalStore<ApplicationPersistedGrantDbContext>();
services.AddDbContext<PersistedGrantDbContext>(options => ContextBuilder(options, connectionString));
services.AddDbContext<ConfigurationDbContext>(options => ContextBuilder(options, connectionString));
services.AddDbContext<ApplicationPersistedGrantDbContext>(options => ContextBuilder(options, connectionString));
services.AddDbContext<ApplicationConfigurationDbContext>(options => ContextBuilder(options, connectionString));
static public void ContextBuilder(DbContextOptionsBuilder options, string connectionString)
{
options.UseMySql(connectionString: connectionString,
ServerVersion.AutoDetect(connectionString),
dbOptions => dbOptions.MigrationsAssembly(DataExtensions.MigrationAssemblyName));
}
That way migrations are created and applied properly and I didn't face any problems during the runtime.

Testing Castle windsor Component with PerWebRequest lifestyle

I'm trying to do some testing with castle windsor involved, in one of my tests I want to check the windsor installers, so I check that the container can resolve my components given its interface.
So far, so good, the problem starts when the component has PerWebRequest lifestyle in its installer, at first it complained about HttpContext.Current is null, having that one solved creating a fake Context in test setup I'm now having this exception in nunit test
System.Exception : Looks like you forgot to register the http module Castle.MicroKernel.Lifestyle.PerWebRequestLifestyleModule
Add '' to the section on your web.config. If you're running IIS7 in Integrated Mode you will need to add it to section under
As I'm running this from NUnit, how I can register the module or class in windsor so it works, or how can be mocked, as in this test is not really a web request, just checking that the container resolve the type.
And also this same thing will happen if I make any integration tests with this component outside a real webrequest, is there any way to make this work or really mock a web request so this tests can be run?
Tranks in advance
Fer
In your test you could subscribe to the ComponentModelCreated event and change the lifestyle of your per-web-request components to something else. (example).
If you're writing an integration test with the scope of a single request, singleton should do.
If you're writing an integration test that spans multiple requests, you could use a contextual lifestyle to simulate the scope of requests.
Edit: including code from example (which is no longer available):
container.Kernel.ComponentModelCreated += Kernel_ComponentModelCreated;
…
void Kernel_ComponentModelCreated(Castle.Core.ComponentModel model)
{
if (model.LifestyleType == LifestyleType.Undefined)
model.LifestyleType = LifestyleType.Transient;
}
From version 5 of Windsor the accepted answer doesn't work if you are using Castle.Facilities.AspNet.SystemWeb.WebRequestScopeAccessor because the PerWebRequest lifestyle is already a scoped lifestyle.
I got it to work by changing the the ComponentModelCreated delegate to the following:
void Kernel_ComponentModelCreated(Castle.Core.ComponentModel model)
{
const string CastleScopeAccessorType = "castle.scope-accessor-type";
if (model.ExtendedProperties.Contains(CastleScopeAccessorType))
{
model.ExtendedProperties.Remove(CastleScopeAccessorType);
}
}
I ended up implementing this extension. ATTN: Must call before loading components with the PerWebRequest lifestyle:
public static class WindsorContainerExtensions
{
public static IWindsorContainer OverridePerWebRequestLifestyle(this IWindsorContainer container)
{
container.Kernel.ComponentModelCreated += model =>
{
if (model.IsPerWebRequestLifestyle())
{
model.LifestyleType = LifestyleType.Transient;
}
};
return container;
}
private static bool IsPerWebRequestLifestyle(this ComponentModel model)
{
return model.LifestyleType == LifestyleType.Scoped
&& model.HasAccessorType(typeof(WebRequestScopeAccessor));
}
private static bool HasAccessorType(this ComponentModel model, Type type)
=> model.HasExtendedProperty("castle.scope-accessor-type", type);
private static bool HasExtendedProperty<T>(this ComponentModel model, object key, T expected)
{
return model.ExtendedProperties[key] is T actual
&& EqualityComparer<T>.Default.Equals(actual, expected);
}
}
Requires these imports:
using System;
using System.Collections.Generic;
using Castle.Core;
using Castle.Facilities.AspNet.SystemWeb;
using Castle.Windsor;
If you also want to check if the type of scope is per web request you could also do this
var isPerWebRequestScope = JsonConvert.SerializeObject(model.ExtendedProperties).Contains("Castle.Facilities.AspNet.SystemWeb.WebRequestScopeAccessor")