Using a DB context factory in Blazor server - entity-framework

I'm having a few issues setting up the EF database connection for my server side Blazor app. It was working with the standard DbContext setup until I noticed a few issues with connections not closing properly due to the nature of Blazor using the same context throughout. My research led me to look at DbContextFactory, but the interface IDbContextFactory is now deprecated in favour of IDesignTimeDbContextFactory.
I've set up a class to implement the interface:
public class FIS2ContextFactory : IDesignTimeDbContextFactory<FIS2_DbContext>
{
private readonly DbContextOptions<FIS2_FranklinContext_AutoGenerated> options;
public FIS2ContextFactory(DbContextOptions<FIS2_FranklinContext_AutoGenerated> contextOptions)
{
options = contextOptions;
}
public FIS2_DbContext CreateDbContext(string[] args)
{
return new FIS2_DbContext(options);
}
}
The DbContext I'm wanting to use is this, which inherits and expands on the DbContext generated by EF Power Tools:
public partial class FIS2_DbContext : FIS2_FranklinContext_AutoGenerated
{
public FIS2_DbContext()
{
}
public FIS2_DbContext(DbContextOptions<FIS2_FranklinContext_AutoGenerated> options) : base(options)
{
}
public virtual DbSet<StudentBasicDetailsWithCurrentTg> StudentBasicDetailsWithCurrentTgs { get; set; }
public virtual DbSet<CurriculumSearchBasicDetails> CurriculumSearchBasicDetails { get; set; }
public virtual DbSet<StudentAllEnrolments> StudentAllEnrolments { get; set; }
}
In my startup.cs I have it set up like this in the ConfigureServices method:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContextFactory<FIS2_DbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("FIS2")));
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddScoped<IFileService, FileService>();
services.AddScoped<IEmailService, EmailService>();
services.AddScoped<ITimetableService, TimetableService>();
services.AddScoped<ICurriculumService, CurriculumServiceEf>();
services.AddScoped<IStudentService, StudentServiceEf>();
services.AddScoped<ICollectionService, CollectionsServiceEf>();
services.AddHttpContextAccessor();
services.AddHttpClient();
services.AddAuthenticationCore();
services.AddAuthentication(IISDefaults.AuthenticationScheme);
services.AddAuthorization();
services.AddSyncfusionBlazor();
services.AddScoped<SessionState>();
}
My issue is that when it gets to setting up the services that utilise this database connection, I am met with this error message in the program.cs:
Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: DataLibrary.Data.Interfaces.ITimetableService Lifetime: Scoped ImplementationType: DataLibrary.Data.BusinessLayer.TimetableService': Unable to resolve service for type 'DataLibrary.Models.FIS2ContextFactory' while attempting to activate 'DataLibrary.Data.BusinessLayer.TimetableService'.) (Error while validating the service descriptor 'ServiceType: DataLibrary.Data.Interfaces.ICurriculumService Lifetime: Scoped ImplementationType: DataLibrary.Data.BusinessLayer.CurriculumServiceEf': Unable to resolve service for type 'DataLibrary.Models.FIS2ContextFactory' while attempting to activate 'DataLibrary.Data.BusinessLayer.CurriculumServiceEf'.) (Error while validating the service descriptor 'ServiceType: DataLibrary.Data.Interfaces.IStudentService Lifetime: Scoped ImplementationType: DataLibrary.Data.BusinessLayer.StudentServiceEf': Unable to resolve service for type 'DataLibrary.Models.FIS2ContextFactory' while attempting to activate 'DataLibrary.Data.BusinessLayer.StudentServiceEf'.) (Error while validating the service descriptor 'ServiceType: DataLibrary.Data.Interfaces.ICollectionService Lifetime: Scoped ImplementationType: DataLibrary.Data.BusinessLayer.CollectionsServiceEf': Unable to resolve service for type 'DataLibrary.Models.FIS2ContextFactory' while attempting to activate 'DataLibrary.Data.BusinessLayer.CollectionsServiceEf'.)
For reference, here is an example of how the TimetableService is set up (the others are instantiated in the same way):
using DataLibrary.Data.Interfaces;
using DataLibrary.Models;
using DataLibrary.Models.timetable;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace DataLibrary.Data.BusinessLayer
{
public class TimetableService : ITimetableService
{
private FIS2ContextFactory _contextFactory;
public TimetableService(FIS2ContextFactory db)
{
_contextFactory = db;
}
public async Task<List<spGetHolidaysBetweenDatesResult>> GetHolidaysBetweenDatesAsync(DateTime startDate, DateTime endDate)
{
string[] args = { "" };
var _db = _contextFactory.CreateDbContext(args);
var procedures = _db.Procedures;
return await procedures.spGetHolidaysBetweenDatesAsync(startDate, endDate);
}
public async Task<List<PeriodsBetweenDates>> GetPeriodsBetweenDatesAsync(DateTime startDate, DateTime endDate)
{
string[] args = { "" };
var _db = _contextFactory.CreateDbContext(args);
var procedures = _db.Procedures;
var toReturn = new List<PeriodsBetweenDates>();
var results = await procedures.spGetPeriodsBetweenDatesAsync(startDate, endDate);
foreach (var item in results)
{
toReturn.Add(new PeriodsBetweenDates(item.Date, item.Timetable, item.BlockCode, item.StartTime, item.EndTime));
}
return toReturn;
}
public async Task<List<StudentTimetable>> GetStudentTimetableAsync(DateTime startDate, DateTime endDate, string studentID)
{
string[] args = { "" };
var _db = _contextFactory.CreateDbContext(args);
var procedures = _db.Procedures;
var results = await procedures.spGetStudentTimetableAsync(startDate, endDate, studentID);
List<StudentTimetable> studentTimetables = new List<StudentTimetable>();
foreach (var item in results)
{
studentTimetables.Add(JsonConvert.DeserializeObject<StudentTimetable>(item.timetable));
}
return studentTimetables;
}
}
}
Is it because I'm using the wrong method to create the context factory in the startup, or is it something later on that I've got wrong?

If you want to resolve a specific factory type, you must register with this overload, AddDbContextFactory<TContext,TFactory> documented here:
This overload allows a specific implementation of
IDbContextFactory to be registered instead of using the
default factory shipped with EF Core.
so
services.AddDbContextFactory<FIS2_DbContext,FIS2ContextFactory>(options => options.UseSqlServer(Configuration.GetConnectionString("FIS2")));

Related

An error occurred while accessing the Microsoft.Extensions.Hosting services. After Adding ASP.NET identity

I have DataContext and StartUp class in different projects and to add a new migration in Data project I used the below command:
dotnet ef migrations add IdentityAdded -s ..\API\API.csproj
And here is project structure:
I just added ASP.Net Core Identity to the project based on .Net 5 and configured it as below:
public class DataContext : IdentityDbContext<AppUser, AppRole, int,
IdentityUserClaim<int>, AppUserRole, IdentityUserLogin<int>,
IdentityRoleClaim<int>, IdentityUserToken<int>>
{
public DataContext(DbContextOptions<DataContext> options) : base(options)
{
ChangeTracker.LazyLoadingEnabled = false;
}
... DbSets
... protected override void OnModelCreating(ModelBuilder modelBuilder)
{ ... }
}
IdentityServiceExtension.cs:
public static class IdentityServiceExtension
{
public static IServiceCollection AddIdentityServices(this IServiceCollection services, IConfiguration configuration)
{
services.AddIdentityCore<AppUser>(opt =>
{
opt.Password.RequireNonAlphanumeric = false;
})
.AddRoles<AppRole>()
.AddRoleManager<RoleManager<AppRole>>()
.AddSignInManager<SignInManager<AppUser>>()
.AddRoleValidator<RoleValidator<AppUser>>()
.AddEntityFrameworkStores<DataContext>();
}
}
I just inherited some classes such as AppUser, AppRole and AppUserRole from Identity Classes like this:
public class AppRole : IdentityRole<int>
{
public ICollection<AppUserRole> TheUserRolesList { get; set; }
}
After running the migration I get the following error:
An error occurred while accessing the Microsoft.Extensions.Hosting services. Continuing without the application service provider. Error: Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: Microsoft.AspNetCore.Identity.RoleManager1[Core.Models.Entities.User.AppRole] Lifetime: Scoped ImplementationType: Microsoft.AspNetCore.Identity.RoleManager1[Core.Models.Entities.User.AppRole]': Implementation type 'Microsoft.AspNetCore.Identity.RoleValidator1[Core.Models.Entities.User.AppUser]' can't be converted to service type 'Microsoft.AspNetCore.Identity.IRoleValidator1[Core.Models.Entities.User.AppRole]')
What's wrong with this implementation?
You didn't register properly, instead of:
.AddRoleValidator<RoleValidator<AppUser>>()
add:
.AddRoleValidator<RoleValidator<AppRole>>()
Your error points out that it can't instantiate Microsoft.AspNetCore.Identity.RoleValidator with the Core.Models.Entities.User.AppUser, instead it requires Core.Models.Entities.User.AppRole.

Error while validating the service descriptor 'ServiceType: MediatR.IRequestHandler

I implement JwtService from IJwtService at the Infrastructure layer
IJwtService declare at the Application layer
and
I implement IdentityService from IIdentityService at the Infrastructure layer
I register both at infrastructure dependency injection like
services.AddTransient< IJwtService,JwtService>();
services.AddTransient<IIdentityService,IdentityService>();
Then I implement LoginQueryHandler implement from : IRequestHandler<LoginViewModel, LoginDto>
within LoginQueryHandler() i inject IIdentityService and IJwtService
I register Mediator at Application Layer as this
services.AddMediatR(Assembly.GetExecutingAssembly());
using mediator I send a request to LoginQuery Handler
public async Task Login([FromBody] LoginViewModel model)
{
return await Mediator.Send(model);
}
This is LoginQueryHandler Class
public class LoginViewModel: IRequest<LoginDto>
{
public string Email { get; set; }
public string Password { get; set; }
}
public class LoginQueryHandler : IRequestHandler<LoginViewModel, LoginDto>
{
private readonly IIdentityService _identityService;
private readonly IJwtService _jwtService;
public LoginQueryHandler(IIdentityService identityService,IJwtService jwtService)
{
_identityService=identityService;
_jwtService=jwtService;
}
public async Task<LoginDto> Handle(LoginViewModel request, CancellationToken cancellationToken)
{
try
{
var user = await _identityService.FindByEmailAsync(request.Email);
// codes....
return new LoginDto();
}
catch (Exception ex)
{
throw ex;
}
}
}
but it throws the following error
System.InvalidOperationException: Error while validating the service descriptor 'ServiceType: MediatR.IRequestHandler`2[Application.Login.Queries.LoginViewModel,Application.Login.Queries.LoginDto] Lifetime: Transient ImplementationType: Application.Login.Queries.LoginQueryHandler': Unable to resolve service for type 'TechneTravel.Infrastructure.Services.JwtService' while attempting to activate 'Newproject.Infrastructure.Identity.IdentityService'.
---> System.InvalidOperationException: Unable to resolve service for type 'Newproject.Infrastructure.Services.JwtService' while attempting to activate 'Newproject.Infrastructure.Identity.IdentityService'
Then I tried three ways to register Request Handler at the Application layer as bellow
services.AddTransient(typeof(IRequestHandler<LoginViewModel, LoginDto>), typeof(LoginQueryHandler));
services.AddTransient<IRequestHandler<LoginViewModel, LoginDto>, LoginQueryHandler>();
services.AddTransient(typeof(LoginQueryHandler));
but not solved
Based on your error message it seems that you are trying to resolve JwtService in your Newproject.Infrastructure.Identity.IdentityService but you have only interface registration:
services.AddTransient<IJwtService, JwtService>();
So either change your IdentityService to accept IJwtService instead of JwtService (I would say that it is far better option) or change/add registration to inject using concrete class:
services.AddTransient<JwtService>();

How do I resolve a WebAPI dependency in Autofac that requires a parameter from the route?

I am struggling with wiring dependencies through autofac in my WebApi 2 project. I have a following interface and class that i'd like to inject in my GET and POST controller actions,
public interface IRepository
{
IContext Context
{
get;
}
void SomeOperation();
}
public MyRepository : IRepository
{
IContext _context;
public MyRepository(IContext context)
{
_context = context;
}
public Context
{
get
{
return _context;
}
}
public void SomeOperation
{
// Perform some operation using _context;
}
}
I 'd like IRepository to be injected in controller like this,
public class MyController : ApiController
{
private readonly IRepository _repo;
public ApplicationsController(IRepository repo)
{
_repo = repo;
}
// GET: api/v1/Contexts({contextId})
public IHttpActionResult Get(string contextId)
{
_repo.SomeOperation();
}
}
IContext object to be injected in MyRepository has to be fetched from a factory, something like this
public class ContextFactory
{
Hashtable contextMap;
IContext Get(string contextId)
{
if contextMap.Contains(contextId)
return contextMap[contextId].Value;
else
{
IContextConfiguration configuration = ContextConfigurationFactory.Get(contextId);
IContext context = new ConcreteContext(configuration);
contextMap.Add[contextId, context];
return context;
}
}
}
I am not sure how to wire all the classes and convert logic in factory classes by injecting relationships through Autofac so that context id passed in url is passed to ContextConfigurationFactory.Get and instantiate ConcreteContext object when not found in hash and eventually Autofac injecting right context object in MyRepository before passing it on to Get action in the controller.
Let's simplify this a bit. What you're trying to do is:
Get the context ID from a route parameter.
Use that route parameter in the factory to create a context.
The rest seems pretty much peripheral - the repository, the controller, all that. The crux of the question is that you need to get a route parameter into your factory.
Given that, let's put together some simplified code:
public class ContextFactory
{
public IContext Get(string contextId)
{
return new Context(contextId);
}
}
public interface IContext
{
string Id { get; }
}
public class Context : IContext
{
public Context(string id)
{
this.Id = id;
}
public string Id { get; private set; }
}
That's basically what you have:
An IContext interface that things need.
A ContextFactory that is basically responsible for building these things.
A Context concrete implementation of IContext that is built by the factory.
I would probably do something like this:
var builder = new ContainerBuilder();
builder.RegisterType<ContextFactory>();
builder.Register(ctx =>
{
var routeData = HttpContext.Current.Request.RequestContext.RouteData;
var id = routeData.Values["contextId"] as string;
var factory = ctx.Resolve<ContextFactory>();
return factory.Get(id);
}).As<IContext>()
.InstancePerLifetimeScope();
Now when you resolve IContext it will use your factory, get the current context ID from route data, and pass it through the factory.
I will leave the following for you to look into:
What happens if the route parameter isn't there? (Autofac won't let you return null.)
What happens if the route parameter has invalid data?
The route parameter is pretty hackable, is this a security risk?
...and so on.

Using Microsoft.AspNetCore.Identity.MongoDB for Multi Tenancy. How do we inject dynamic Tenant into MongoDbContext

Does anyone know how we can inject context into User Manager > MongoDB serStore at runtime in .net core 2.0.
We cannot do this at startup due to the context being dynamic but the UserStore is not accessible and UserManager has too many variables to new up, and it is wrong. Are there any solutions?
public class UserStore<TUser> :
IUserPasswordStore<TUser>,
IUserRoleStore<TUser>,
IUserLoginStore<TUser>,
IUserSecurityStampStore<TUser>,
IUserEmailStore<TUser>,
IUserClaimStore<TUser>,
IUserPhoneNumberStore<TUser>,
IUserTwoFactorStore<TUser>,
IUserLockoutStore<TUser>,
IQueryableUserStore<TUser>,
IUserAuthenticationTokenStore<TUser>
where TUser : IdentityUser
{
private readonly IMongoCollection<TUser> _Users;
//THIS IS WHERE WE WANT TO INJECT THE users AT RUNTIME
public UserStore(IMongoCollection<TUser> users)
{
_Users = users;
}
public virtual void Dispose()
{
// no need to dispose of anything, mongodb handles connection pooling automatically
}
public virtual async Task<IdentityResult> CreateAsync(TUser user, CancellationToken token)
{
await _Users.InsertOneAsync(user, cancellationToken: token);
return IdentityResult.Success;
}
unfortunately users is null at startup, and should be as the tenant has not been created at that point.
We have also been using the saaskit.Multitenancy and just can't find a solution.
Any help would be much appreciated.
Thanks
i think u need a generic repository to act as a wrapper for IMongoCollection then inject the repository inside controllers
public class Repository<T>
{
public IMongoCollection<T> Collection { get; private set; }
public Repository(IDbFactory dbFactory)
{
MongoClient client = new MongoClient("ur connection string");
this.Collection = client.GetDatabase("db").GetCollection<T>(typeof(T).Name);
}
public T Find(Expression<Func<T, bool>> filter)
{
return this.Collection.AsQueryable<T>().FirstOrDefault<T>(filter);
}
public async Task<T> FindAsync(Expression<Func<T, bool>> filter)
{
return await this.Collection.AsQueryable<T>().FirstOrDefaultAsync<T>(filter);
}
// here add more methods
}
then register the dependency as below inside Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient(typeof(IRepository<>), typeof(Repository<>));
services.AddMvc();
}
finally inside controllers u inject the generic repository, also dont forget to Implement the IDisopsible in genereic repository
public class ProductController : Controller
{
private readonly IRepository<Product> _productRepository = null;
public ProductController(IRepository<Product> productRepository)
{
this._productRepository = productRepository;
}
}

Registering concrete type with parameter is null using Autofac

I have the following class:
public class Errors
{
private readonly string _connectionString;
public Errors(string connectionString)
{
_connectionString = connectionString;
}
}
I'm trying to register using Autofac like so:
builder.RegisterType<Errors>().WithParameter("connectionString", System.Configuration.ConfigurationManager.ConnectionStrings["myConn"].ConnectionString);
This object is getting injected into another object but it's always null. Looking further into the exception, the following error message is displayed:
Cannot choose between multiple constructors with equal length 1 on type 'System.String'.
Select the constructor explicitly, with the UsingConstructor() configuration method, when the component is registered.
I've tried registering using the UsingConstructor and WithParameter and there's no change.
Try This, there are 2 ways to register
First Type:
builder.Register(c => new Errors(System.Configuration.ConfigurationManager.ConnectionStrings["myConn"].ConnectionString)).InstancePerLifetimeScope();
Second Type:
public class Errors
{
private string _connectionString{ get; set; }
public Errors(string connectionString)
{
_connectionString = connectionString;
}
}
Register Like below
builder.RegisterType<Errors>().WithParameter("connectionString", System.Configuration.ConfigurationManager.ConnectionStrings["myConn"].ConnectionString);
EDIT: Testing
class Program
{
static void Main(string[] args)
{
var builder = new ContainerBuilder();
builder.Register(c => new Errors(ConfigurationManager.ConnectionStrings["myConn"].ConnectionString)).InstancePerLifetimeScope(); // Type 1
// builder.RegisterType<Errors>().WithParameter("connectionString", ConfigurationManager.ConnectionStrings["myConn"].ConnectionString); // Type 2
var container = builder.Build();
var objErrors = container.Resolve<Errors>();
}
}
And the config file looks like
<connectionStrings>
<add name="myConn" connectionString="Some Connection String"/>
</connectionStrings>
Please read this Autofac Wiki
Tested in Autofac Version: 2.6.1.841
Result: