blazor scoped service initializing twice - asp.net-core

I'm trying to learn asp.net core, more specifically blazor server. From the documentation, it appears a service registered as scoped will be created once per connection. My user service constructor is running twice on the first load of the page in the browser, and twice again on each refresh of the page.
I believe these are the applicable parts of the code necessary to help me determine why this is occurring. My question is how to make it create one instance of the user service for each client connection? I'm getting the correct output on screen but don't prefer it to run twice.
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddHttpContextAccessor();
services.AddDbContext<AWMOPSContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("AWMOPSContext")),
ServiceLifetime.Transient);
services.AddScoped<UserService>();
}
public class UserService
{
public Associate Associate { get; set; }
public UserService(AWMOPSContext context, IHttpContextAccessor httpContextAccessor)
{
var username = httpContextAccessor.HttpContext.User.Identity.Name.Substring(7);
Associate = context.Associates.Where(a => a.LogonName == username).FirstOrDefault();
Debug.WriteLine($"Hello {Associate.PreferredName} {Associate.LastName}");
}
}
#page "/"
#inject AWMWP.Services.UserService user;
<h1>Welcome #user.Associate.PreferredName #user.Associate.LastName</h1>

It is called twice, as you are using pre-rendering. Go to _Host.cshtml and change render-mode="ServerPrerendered" to render-mode="Server", and it would be called only once:
<app>
<component type="typeof(App)" render-mode="Server" />
</app>
Reference:
https://learn.microsoft.com/en-us/aspnet/core/blazor/lifecycle?view=aspnetcore-3.1#stateful-reconnection-after-prerendering

Related

Blazor Server get current logged user when EF DbContext execute select data query

I build blazor server multi tenant application and I wants to use HasQueryFilter in entity framework DbContext for predefined filter with TenantId. Ofcourse I have connected User with tenant. I created CurrentTenatnProvider which has method GetCurrentTenatnId. In this mehtod i use AuthentificationStateProvider and call GetAuthenticationStateAsync(). Ofcourse i get the error ''GetAuthenticationStateAsync was called before SetAuthenticationState". I Cannot use IHttpContextAccessor because in Azure app I get null reference exception.
Is there any other possibility how to get CurrentUser in time when DbContext execute select data query?
I was thinking about cache CurrnetUser but there is problem with cache key.CurrentTenatnProvider service is registered as scoped service. There is Id attribute which is set in constructor. And then is used as cache key. But this approach does not working and I get the same error.
It si possible get signal-r connection identificator and use it as cache key?
I spended 2 days with test lots of combination and read lots of documentation but unfortunately I didn't find any solution. I will be very grateful for any advice.
I have done this a few ways but the simplest way I could find was retrieving my user from the database in the MainLayout.razor file during OnInitializedAsync() and passing it in a fixed cascading parameter <CascadingValue Name="CurrentUser" IsFixed="true" Value="UserId">. From there, I could reference it where needed in any child component.
finally I found solution! From my view it is bug! Problem is because services.AddDbContextFactory is registered as Singleton. I create my own implementation of IDbContext factory and register it as Scoped. After this change everything’s works perfect. When I change registration scope of DbContextFactory to singleton, I get the error: GetAuthenticationStateAsync was called before SetAuthenticationState.
My DbContextFactory
public class BlazorContextFactory<TContext> : IDbContextFactory<TContext> where TContext : DbContext
{
private readonly IServiceProvider provider;
public BlazorContextFactory(IServiceProvider provider)
{
this.provider = provider;
}
public TContext CreateDbContext()
{
if (provider == null)
{
throw new InvalidOperationException(
$"You must configure an instance of IServiceProvider");
}
return ActivatorUtilities.CreateInstance<TContext>(provider);
}
}
My StartUp
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.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<ApplicationDbContext>();
services.AddScoped<IDbContextFactory<ApplicationDbContext>, BlazorContextFactory<ApplicationDbContext>>();
services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddScoped<AuthenticationStateProvider, RevalidatingIdentityAuthenticationStateProvider<IdentityUser>>();
services.AddSingleton<WeatherForecastService>();
}
// 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("/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.MapControllers();
endpoints.MapBlazorHub();
endpoints.MapFallbackToPage("/_Host");
});
}
}
I hope it's help other peoples! I spend 6 days with this problem :(

Concrete implementation of dependency injected service in ASP.NET Core 3 not being called

I read Introduction to Identity on ASP.NET Core to implement Identity Services in my ASP.NET Core 3 web application. Part of the walkthrough for enabling identity services is the creation of an EmailSender class (described here) to send account registration emails, etc. My problem is that my EmailSender implementation is never being called.
Following the instructions at https://learn.microsoft.com/en-us/aspnet/core/security/authentication/accconfirm?view=aspnetcore-3.1&tabs=visual-studio I created my concrete EmailSender implementation:
public class EmailSender : IEmailSender
{
public EmailSender(IOptions<AuthMessageSenderOptions> optionsAccessor)
{
Options = optionsAccessor.Value;
}
...
}
then I register this service (along with the other identity related bits) in Startup.cs with the dependency injection framework:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddDefaultIdentity<IdentityUser>(
options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddTransient<IEmailSender, EmailSender>();
services.Configure<AuthMessageSenderOptions>(Configuration);
...
}
ASP.NET Core Identity Services auto-generates code for registering, logging in, etc. and calls the email service as follows:
public class RegisterModel : PageModel
{
...
private readonly IEmailSender _emailSender;
public RegisterModel(
...
IEmailSender emailSender)
{
...
_emailSender = emailSender;
}
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
...
await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",
$"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
...
}
Problem I'm having is that EmailSender is never instantiated. Breakpoints on all methods and constructor (even made a default constructor as a test) aren't hit.
In my debugging I see that RegisterModel is instantiated and emailSender is set. But when F11ing through the statement that calls email sender:
await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",
$"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
the immediate next line of code is executed - no debugger hop to EmailSender at all.
This feels like a DI registration problem to me yet the fact that IEmailSender.emailSender is non-null means it sees the concrete implementation.
I had this issue and after some head scratching, I noticed that Microsoft's implementation was "overriding" my IEmailService implementation. Solved this by deleting it from the Using statements at the top of the file.
I commented out this line:
using Microsoft.AspNetCore.Identity.UI.Services;
to
//using Microsoft.AspNetCore.Identity.UI.Services;
This goes for any other interface you may implement, check the using statements for any other existing implementation.

Blazor: How to get the hosted service instance?

I added a background service that periodically does something, like the official sample.
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddHostedService<TimedHostedService>(); <-- here
services.AddSingleton<WeatherForecastService>();
}
The TimedHostedService has StartAsync and StopAsync. Ultimately, I want to call these in the web browser.
In the FetchData.razor file in the default scaffolding, I tried to refer that service directly, but that did not work. So, I added Start and Stop method to the WeatherForecastService and called them on the click event.
<button #onclick="()=> { ForecastService.Stop(); }">Stop</button>
Now, the problem is, that I don't know how to get the running instance of TimedHostedService in the Stop method of WeatherForecastService.
public class WeatherForecastService
{
....
public void Stop()
{
//how to get TimedHostedService instance?
}
....
}
I have tried using dependency injection to get the service provider, but GetService returned null.
IServiceProvider sp;
public WeatherForecastService(IServiceProvider sp)
{
this.sp = sp;
}
public void Stop()
{
var ts = sp.GetService(typeof(TimedHostedService)) as TimedHostedService;
ts.StopAsync(new CancellationToken());
}
I question the wisdom of manipulating the service from the GUI but if you're sure you want this then it's about how to register that service.
In startup:
services.AddSingleton<TimedHostedService>();
services.AddHostedService(sp => sp.GetRequiredService<TimedHostedService>());
and then you can
#inject TimedHostedService TimedService

Endpoints not being discovered in PageModel when Published to IIS10: Http Response 404 .Net Core RazorPages

When debugging in IIS Express all endpoints are reachable via GET. When published to IIS10 I can navigate to the page public void OnGet() is being called and renders the razor page. When calling ./MyPage/Partial on server IIS10 I receive a 404 Not Found error and this does not happen on IIS Express in Visual Studio.
public class IndexModel : PageModel
{
[BindProperty]
public MyModel MyModel { get; set; }
[HttpGet]
public void OnGet()
{
...
}
[HttpGet]
public IActionResult OnGetPartial([FromQuery] int id)
{
...
}
}
I have followed the instructions on https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/iis/?view=aspnetcore-2.2 and my best guess is that I need to configure these routes as per https://learn.microsoft.com/en-us/aspnet/core/razor-pages/razor-pages-conventions?view=aspnetcore-2.2
Although my question is why in IIS Express I can call javascript jquery $.load('./MyPage/Partial?id=1') and it works fine and when published it returns a 404 error? And what would be the specific solution?
EDIT: in my Index.cshtml I have the following #page "{handler?}" at the top in order to handle the custom REST methods.
In order to solve this I followed the instructions from https://learn.microsoft.com/en-us/aspnet/core/razor-pages/razor-pages-conventions?view=aspnetcore-2.2 in the file Startup.cs or whichever class you are using in Program.cs via
WebHost.CreateDefaultBuilder(args)
.UseKestrel()
.UseStartup<Startup>();
In the method in the file Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddMvcCore().AddRazorPages(options => options.Conventions.AddPageRoute("/MyPage", "/MyPage/Partial/{id}")).AddRazorViewEngine().AddViews();
// Other service code impl. here
}

Asp.Net Core Middleware service dependent on current User

I would like to either change a request scoped service or set one in a custom middleware layer.
Specifically, I want to be able to do something like the below contrived example in Startup.cs:
public void ConfigureServices(IServiceCollection service)
{
service.AddScoped<IMyUserDependentService>((provider) => {
return new MyService());
});
}
public void Configure(...) {
//other config removed
app.Use(async (context, next) => {
var myService = context.ApplicationServices.GetService<IMyUserDependentService>();
myService.SetUser(context.User.Identity.Name);//Name is Fred
next.Invoke();
});
}
Then in the controller do this:
public class HomeController: Controller
{
public HomeController(IMyUserDependentService myService)
{
//myService.UserName should equal Fred
}
}
The problem is, that this doesn't work. myService.UserName isn't Fred in controller, it's null. I think that the IOC container is creating a new instance in the controller, and not using the one set in the middleware.
If I change the scope of the service to Transient, Fred is remembered, but that doesn't help because the service is dependent on who the current user is.
To recap, what I need is to create/or edit a service that requires the current user (or other current request variables), but am unable to work this out.
Thanks in advance!
Have you tried using context.RequestServices?
I just ran into a similar issue, I got an error like
InvalidOperationException: Cannot resolve scoped service 'IScopedService' from root provider., the exception thrown was very not well documented.
Here is how I solved it:
[Startup.cs]
services.AddScoped<IAnyScopedService, AnyScopedService>();
services.AddSingleton<ISomeOtherSingletonService, SomeOtherSingletonService>();
[MyMiddleware.cs]
public sealed class MyMiddleware
{
private readonly RequestDelegate _next;
private readonly ISomeOtherSingletonService _Svc;
public MyMiddleware(
RequestDelegate next,
ISomeOtherSingletonService svc)
{
_next = next;
_Svc = svc;
}
public async Task Invoke(HttpContext context,
IAnyScopedService scopedService)
{
// Some work with scoped service
}
}
Indeed the Middleware is instanciated only once, but called many times.
The constructor takes therefore singleton instances, where the invoke method can get scoped injected parameters.
More details on Mark Vincze post