Blazor Server app - Some services are not able to be constructed - c#

I am trying to write a simple Blazor Server app (which I have done before) and I am getting a 'System.AggregateException' error when the app tries to build.
Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: CrispyAcers.Services.IFarmService Lifetime: Transient ImplementationType: CrispyAcers.Services.FarmService': Unable to resolve service for type 'Microsoft.EntityFrameworkCore.IDbContextFactory`1[CrispyAcers.Models.FarmContext]' while attempting to activate 'CrispyAcers.Services.FarmService'.)
This is my Program.cs:
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
builder.Services.AddDbContext<FarmContext>(option =>
option.UseSqlServer(builder.Configuration.GetConnectionString("FarmConnection")));
builder.Services.AddTransient<IFarmService, FarmService>();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
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.MapBlazorHub();
app.MapFallbackToPage("/_Host");
app.Run();
This is the FarmService I am trying to add:
public class FarmService : IFarmService
{
private readonly IDbContextFactory<FarmContext> dbFactory;
public FarmService(IDbContextFactory<FarmContext> context)
{
dbFactory = context;
}
public List<Animals> GetAnimalsList()
{
using (var context = dbFactory.CreateDbContext())
{
return context.Animals.ToList();
}
}
}
I could very easily be missing something simple which hopefully someone will catch. If I remove the FarmService the app builds fine. I have tried Scoped, Transient, and Singleton lifetimes but nothing different happens.

For IDbContextFactory<FarmContext> service
You need to register the service of IDbContextFactory<FarmContext> type.
builder.Services.AddDbContextFactory<FarmContext>(opt =>
option.UseSqlServer(builder.Configuration.GetConnectionString("FarmConnection")));
For IDbContext<FarmContext> service
Alternatively, you should use the IDbContext<FarmContext> service instead of IDbContextFactory<FarmContext>. Modify the FarmService as below:
public class FarmService : IFarmService
{
private readonly IDbContext<FarmContext> _context;
public FarmService(IDbContext<FarmContext> context)
{
_context = context;
}
public List<Animals> GetAnimalsList()
{
return _context.Animals.ToList();
}
}
Reference
Database access - ASP.NET Core Blazor Server with Entity Framework Core (EF Core)

Related

How to solve this error with my services ASP.NET Core 6

I get this error when I start my app:
System.AggregateException: 'Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: Data.Access.Layer.Interfaces.IUnitOfWork Lifetime: Scoped ImplementationType: Data.Access.Layer.Repositories.UnitOfWork': Unable to resolve service for type 'Data.Access.Layer.EFContext' while attempting to activate 'Data.Access.Layer.Repositories.UnitOfWork'.)'
My code:
builder.Services.AddScoped<IUnitOfWork, UnitOfWork>();
public interface IUnitOfWork: IDisposable
{
IGenericRepository<ApplicationUser> Users { get; }
IGenericRepository<Painting> Paintings { get; }
IGenericRepository<OrderItem> OrdersItems { get; }
IGenericRepository<Order> Orders { get; }
IGenericRepository<ShoppingCartItem> ShoppingCartItems { get; }
}
public class UnitOfWork : IUnitOfWork
{
private readonly EFContext _eFContext;
private GenericRepository<ApplicationUser> _users;
private GenericRepository<Painting> _paintings;
private GenericRepository<Order> _orders;
private GenericRepository<OrderItem> _ordersItems;
private GenericRepository<ShoppingCartItem> _shoppingCartItems;
public UnitOfWork(EFContext eFContext)
{
_eFContext = eFContext;
}
public IGenericRepository<ApplicationUser> Users => _users ??= new GenericRepository<ApplicationUser>(_eFContext);
public IGenericRepository<Painting> Paintings => _paintings ??= new GenericRepository<Painting>(_eFContext);
public IGenericRepository<Order> Orders => _orders??= new GenericRepository<Order>(_eFContext);
public IGenericRepository<OrderItem> OrdersItems => _ordersItems ??= new GenericRepository<OrderItem>(_eFContext);
public IGenericRepository<ShoppingCartItem> ShoppingCartItems => _shoppingCartItems ??= new GenericRepository<ShoppingCartItem>(_eFContext);
private bool disposed = false;
public virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
_eFContext.Dispose();
}
this.disposed = true;
}
}
void IDisposable.Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
using AutoMapper;
using Business.Layer.Interfaces;
using Business.Layer.Services;
using Data.Access.Layer.Entities;
using Data.Access.Layer.Interfaces;
using Data.Access.Layer.Repositories;
using Presentation.Layer.Mapping;
var builder = WebApplication.CreateBuilder(args);
//Add services to the container.
builder.Services.AddScoped<IUnitOfWork, UnitOfWork>();
builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();
builder.Services.AddAutoMapper(typeof(AutoMapperProfile).Assembly, typeof(BLAutoMapperProfile).Assembly);
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
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.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
I'd like to call your attention to this part of your error message, because it tells you exactly what the problem is:
Unable to resolve service for type 'Data.Access.Layer.EFContext' while attempting to activate 'Data.Access.Layer.Repositories.UnitOfWork'.)
This tells you that the dependency injection container tried to create an instance of UnitOfWork, but was unable to do so, because that class has a dependency on EFContext, which was not registered with the container.
Your UnitOfWork class is registered here:
builder.Services.AddScoped<IUnitOfWork, UnitOfWork>();
It basically means "hey, container, when I ask you for an IUnitOfWork, you should provide me with an instance of UnitOfWork".
But, as mentioned, UnitOfWork has a dependency:
public UnitOfWork(EFContext eFContext)
{
_eFContext = eFContext;
}
To get an instance of UnitOfWork, you need an instance of EFContext. When the container realizes this, it's going to check if EFContext has been registered with the container. But you haven't told the container about EFContext, so it has no way to provide you with a UnitOfWork.
To solve the error, you need to inform the container that you have something called EFContext. For an Entity Framework DbContext, this is usually done with the AddDbContext extension method:
builder.Services.AddDbContext<EFContext>();
You might also need to pass some options to this method, depending on how you're configuring your context.
Here's a few documentation links that goes more in depth:
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection
https://learn.microsoft.com/en-us/ef/core/dbcontext-configuration/

.net core AddSingleton initialization

I'm trying to register a singleton class, providing the constructor parameters in Startup.ConfigureServices method.
After several tries, I'm still not able to make the dbContext injection working
public void ConfigureServices(IServiceCollection services)
{
services.AddGrpc();
services.AddDbContext<EFContext>();
services.AddSingleton<OPCClient>(x =>
{
string endpointURL = "opc.tcp://xxx.yyy.zzz.nnn:12345";
bool autoAccept = false;
int stopTimeout = Timeout.Infinite;
var efContext = x.GetService<EFContext>();
OPCClient client = new OPCClient(endpointURL, autoAccept, stopTimeout, efContext);
client.Run();
return client;
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
// warmup
app.ApplicationServices.GetService<OPCClient>();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapGrpcService<OPCService>();
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync("Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909");
});
});
When var efContext = x.GetService<EFContext>(); is executed, I'm getting the exception
System.InvalidOperationException: 'Cannot resolve scoped service 'EFContext' from root provider.'
Thanks for any help in injecting the DbContext in OPCClient class
It is not a good choice to use a scoped service (the EFContext) inside a singleton.
The DI container creates a new instance of a scoped service for every request, while it creates a singleton only once and this can lead to inconsistent states for your objects. Documentation here
I suggest to change the lifetime of OPCClient to scoped - using services.AddScoped instead of services.AddSingleton. If you cannot do this, pass a reference of IServiceProvider rather than EFContext and resolve that service from the container each time you need to use it:
public class OPCClient
{
private IServicePrivder _serviceProvider;
public OPCClient (IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public void DoSomething() {
EfContext efContext = _serviceProvider.GetRequiredService<EfContext>();
}
}

Use Multiple Services for Singleton in ASP.NET Core 3.1

I am using the repository method for a data access class. So the constructor looks something like this:
public class MongoDbUnitOfWork : IMongoDbUnitOfWork
{
private readonly ILogger _logger;
private readonly IConfiguration _config;
public MongoDbUnitOfWork(ILogger logger, IConfiguration config)
{
_logger = logger;
_config = config
//do other stuff here, create database connection, etc.
}
}
public interface IMongoDbUnitOfWork
{
// various methods go in here
}
The key thing is that the constructor relies on the fact that 2 services are parsed to it.
Then in startup.cs I tried to do the following:
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IMongoDbUnitOfWork>(sp =>
{
var logger = sp.GetRequiredService<ILogger>();
var config = sp.GetRequiredService<IConfiguration>();
return new MongoDbUnitOfWork(logger, config);
});
//add other services
}
This compiled but did not work when I tried to run an API route through a controller. I got an error stating:
System.InvalidOperationException: Unable to resolve service for type 'NamespaceDetailsHere.IMongoDbUnitOfWork' while attempting to activate 'NamespaceDetailsHere.Controllersv1.TestController'.
Then I ran a little Debug.WriteLine() script in startup.cs to see whether the ILogger and IConfiguration services existed. They did. I'm not sure what I'm doing wrong here.
The ASP.NET Core service container will automatically resolve the services dependencies that are injected through the constructor, so you dont need the action configuration at all. When the service is constructed, any dependencies in the constructor are automatically required (as you're able to see with the exception).
Simply register your services with
services.AddSingleton<IMongoDbUnitOfWork, MongoDbUnitOfWork>();

Retrieving claims in asp.net core using open id connect server

I'm about to implement bearer based authentication in my asp.net core app. Coming from .NET Framework, the core stuff is still quite new to me. Getting a token from server does already work great. But how can I in following request determine if a user is authenticated? In .NET Framework projects, I used to use
(ClaimsIdentity)Thread.CurrentPrincipal.Identity.IsAuthenticated;
However, this returns an identity with empty or default claims. This is my setup so far:
I've started with the OpenIdConnect.Server framework and the sample code in their Get Started section. This works great, and my client recieves a Bearer Token. I've build it in my Startup.cs in the following way:
public class Startup
{
[...]
public void ConfigureServices(IServiceCollection services)
{
services.AddApplicationInsightsTelemetry(Configuration);
services.AddMvc();
services.AddAuthentication();
[...]
}
public void Configure([...])
{
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseMvc();
app.UseOpenIdConnectServer(options =>
{
[code of example]
}
}
On client side, I use the retrieved token for further requests
Now, how do I now access the current logged in users claims or how do I know if he/she is authenticated?
I have tried
// within api controller:
var isAuth = this.User.Identity.IsAuthenticated
// using DI
public class MyClass(IHttpContextAccessor httpContextAccessor) {
public void MyMethod() {
var isAuth = httpContextAccessor.HttpContext.User.Identity.IsAuthenticated;
}
}
But this always returns false and the claims are some default values.
Am I missing something? Do I need to install some additional service or middleware?
One thing to note with the OpenID Connect server middleware is that it doesn't validate incoming access tokens for you (it only issues them). Since you're using the default token format (encrypted), you can use the AspNet.Security.OAuth.Validation package for that:
public class Startup
{
[...]
public void ConfigureServices(IServiceCollection services)
{
services.AddApplicationInsightsTelemetry(Configuration);
services.AddMvc();
services.AddAuthentication();
[...]
}
public void Configure([...])
{
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseOpenIdConnectServer(options =>
{
[code of example]
});
app.UseOAuthValidation();
app.UseMvc();
}
}

ASP.NET Core website timing out after 30 minutes

I've got an ASP.NET Core MVC app, hosted on Azure websites, where I've implemented Session and Identity. My problem is, after 30 minutes, I get logged out. It doesn't matter if I've been active in the last 30 minutes or not.
Doing some searching, I found that the issue is the SecurityStamp stuff, found here. I've tried implementing this by doing the following:
Here's my UserManager impelmentation with the security stamp stuff:
public class UserManager : UserManager<Login>
{
public UserManager(
IUserStore<Login> store,
IOptions<IdentityOptions> optionsAccessor,
IPasswordHasher<Login> passwordHasher,
IEnumerable<IUserValidator<Login>> userValidators,
IEnumerable<IPasswordValidator<Login>> passwordValidators,
ILookupNormalizer keyNormalizer,
IdentityErrorDescriber errors,
IServiceProvider services,
ILogger<UserManager<Login>> logger)
: base(store, optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, services, logger)
{
// noop
}
public override bool SupportsUserSecurityStamp => true;
public override async Task<string> GetSecurityStampAsync(Login login)
{
return await Task.FromResult("MyToken");
}
public override async Task<IdentityResult> UpdateSecurityStampAsync(Login login)
{
return await Task.FromResult(IdentityResult.Success);
}
}
Here's my ConfigureServices method on Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddApplicationInsightsTelemetry(Configuration);
services.AddSingleton(_ => Configuration);
services.AddSingleton<IUserStore<Login>, UserStore>();
services.AddSingleton<IRoleStore<Role>, RoleStore>();
services.AddIdentity<Login, Role>(o =>
{
o.Password.RequireDigit = false;
o.Password.RequireLowercase = false;
o.Password.RequireUppercase = false;
o.Password.RequiredLength = 6;
o.Cookies.ApplicationCookie.ExpireTimeSpan = TimeSpan.FromDays(365);
o.Cookies.ApplicationCookie.SlidingExpiration = true;
o.Cookies.ApplicationCookie.AutomaticAuthenticate = true;
})
.AddUserStore<UserStore>()
.AddUserManager<UserManager>()
.AddRoleStore<RoleStore>()
.AddRoleManager<RoleManager>()
.AddDefaultTokenProviders();
services.AddScoped<SignInManager<Login>, SignInManager<Login>>();
services.AddScoped<UserManager<Login>, UserManager<Login>>();
services.Configure<AuthorizationOptions>(options =>
{
options.AddPolicy("Admin", policy => policy.Requirements.Add(new AdminRoleRequirement(new RoleRepo(Configuration))));
options.AddPolicy("SuperUser", policy => policy.Requirements.Add(new SuperUserRoleRequirement(new RoleRepo(Configuration))));
options.AddPolicy("DataIntegrity", policy => policy.Requirements.Add(new DataIntegrityRoleRequirement(new RoleRepo(Configuration))));
});
services.Configure<FormOptions>(x => x.ValueCountLimit = 4096);
services.AddScoped<IPasswordHasher<Login>, PasswordHasher>();
services.AddDistributedMemoryCache();
services.AddSession();
services.AddMvc();
// repos
InjectRepos(services);
// services
InjectServices(services);
}
And lastly, here's my Configure method on Startup.cs:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
app.UseApplicationInsightsRequestTelemetry();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/home/error");
}
app.UseStatusCodePages();
app.UseStaticFiles();
app.UseSession();
app.UseIdentity();
app.UseMiddleware(typeof (ErrorHandlingMiddleware));
app.UseMiddleware(typeof (RequestLogMiddleware));
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
What's wrong with my implementation here?
UPDATE: What a second...I noticed my UserManager is not inheriting from any interfaces for the security stamp stuff, is that what's needed?
This is simply because you need to enable and configure Data Protection. The cookie and session setup looks correct. What is happening right now for you is that whenever the app is recycled or the server load balances to another server or a new deployment happens, etc, it creates a new Data protection key in memory, so your users' session keys are invalid. So all you need to do is add the following to Startup.cs:
services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo(#"D:\writable\temp\directory\"))
.SetDefaultKeyLifetime(TimeSpan.FromDays(14));
Use the documentation to learn how to properly set this up and the different options of where to save the Data Protection key (file system, redis, registry, etc). You could think of the data protection key as the replacement of the web.config's machine key in asp.net.
Since you mentioned you're using Azure, you could use this package Microsoft.AspNetCore.DataProtection.AzureStorage to save the key so that it persists. So you could use this example of how to use Azure Storage.
Are you hosted under IIS? If so, maybe nothing is wrong with your code, but your application pool could get recycled (check advanced settings on the application pool). When that happen, does your binary get unloaded from memory and replaced by a new one, its PID changing ?

Categories

Resources