I am currently attempting to remove a number of .Resolve(s) in our code. I was moving along fine until I ran into a named registration and I have not been able to get Autofac resolve using the name. What am I missing to get the named registration injected into the constructor.
Registration
builder.RegisterType<CentralDataSessionFactory>().Named<IDataSessionFactory>("central").SingleInstance();
builder.RegisterType<ClientDataSessionFactory>().Named<IDataSessionFactory>("client").SingleInstance();
builder.RegisterType<CentralUnitOfWork>().As<ICentralUnitOfWork>().InstancePerDependency();
builder.RegisterType<ClientUnitOfWork>().As<IClientUnitOfWork>().InstancePerDependency();
Current class
public class CentralUnitOfWork : UnitOfWork, ICentralUnitOfWork
{
protected override ISession CreateSession()
{
return IoCHelper.Resolve<IDataSessionFactory>("central").CreateSession();
}
}
Would Like to Have
public class CentralUnitOfWork : UnitOfWork, ICentralUnitOfWork
{
private readonly IDataSessionFactory _factory;
public CentralUnitOfWork(IDataSessionFactory factory)
{
_factory = factory;
}
protected override ISession CreateSession()
{
return _factory.CreateSession();
}
}
Change the registration to do the resolve manually:
builder.Register(c => new CentralUnitOfWork(c.Resolve<IDataSessionFactory>("central")))
.As<ICentralUnitOfWork>()
.InstancePerDependency();
Related
I have an .net core web api application where I'm using entity framework core with service layer, unit of work and repository layer pattern. For DI I'm using Autofac.
The application has multiple clients and each client has its own database and the schema for all these databases is same. With each API call I'll get the client specific connection string, using which I have to create a DbContext and use it for all its operations.
On Startup class I have registered my dbcontext ClientDbContext and all other classes. When the unit-of-work class is called I am creating my new DbContext based on the connection string. I want the repository to use this instance, but the repository is still using the initial ClientDbContext instance which was created at startup.
How can I make the repository use the new DbContext instance?
Unit of Work:
public class UnitOfWork : IUnitOfWork
{
public ClientDbContext ClientDbContext { get; private set; }
public UnitOfWork ()
{
}
public void SetDbContext(string connectionString)
{
if(ClientDbContext == null)
{
//creating new db context instance here
ClientDbContext = MembershipRepository.CreateDbContext(connectionString);
}
}
//property injection
public IGenericRepository<SomeEntity, ClientDbContext> SomeEntityGenericRepository { get; }
}
Generic Repository:
public class GenericRepository<TEntity, TDbContext> : IGenericRepository<TEntity, TDbContext> where TEntity : class
where TDbContext : DbContext
{
private readonly TDbContext _context;
private readonly DbSet<TEntity> _dbset;
public GenericRepository(TDbContext context)
{
// need to get updated context here, but getting the initial one
_context = context;
_dbset = _context.Set<TEntity>();
}
}
Autofac module called in Startup.cs:
builder.Register(a => new ClientDbContext()).InstancePerLifetimeScope();
builder.RegisterGeneric(typeof(GenericRepository<,>)).As(typeof(IGenericRepository<,>)).InstancePerLifetimeScope();
//Register Unit of Work here
builder.RegisterType<UnitOfWork>().As<IUnitOfWork>().InstancePerLifetimeScope().PropertiesAutowired();
//Register Services here
builder.RegisterType<SomeService>().As<ISomeService>().InstancePerLifetimeScope();
Can anyone please help me out on how to achieve the above requirement?
Is there any way I can make Autofac use my new created dbcontext object?
Instead of
builder.Register(a => new ClientDbContext()).InstancePerLifetimeScope();
you could use
builder.Register(c => c.Resolve<IUnitOfWork>().ClientDbContext)
.InstancePerLifetimeScope();
By the way I'm not sure what is the responsibility of your IUnitOfWork. Another way of doing this would be to have a class that would provide information about the current user :
public interface IClientContext
{
public String ClientIdentifier { get; }
}
Then a DbContextFactory that would create the DbContext based on the IClientContext
public interface IDbContextFactory
{
IDbContext CreateDbContext();
}
public class DbContextFactory
{
public DbContextFactory(IClientContext clientContext)
{
this._clientContext = clientContext;
}
private readonly IClientContext _clientContext;
public IDbContext CreateDbContext()
{
// get the connectionstring from IClientContext and return the IDbContext
}
}
The concrete implementation of IClientContext depends on the way you can get this information, it could be from current HttpContext or any other way it's up to you.
It seems that at some point you call SetDbContext you can keep this way by creating a XXXClientContextProvider where XXX is relative to the way you get this information.
public class XXXClientContextProvider
{
private IClientContext _clientContext;
public IClientContext GetClientContext()
{
if(this._clientContext == null)
{
throw new Exception("client context is null. You should do X or Y");
}
return this._clientContext;
}
public void SetClientContext(String clientId)
{
if(this._clientContext != null)
{
throw new Exception("client context has already been set");
}
this._clientContext = new StaticClientContext(clientId);
}
}
and then register everything like this :
builder.Register(c => c.Resolve<IClientContextProvider>().GetClientContext())
.As<IClientContext>()
.InstancePerLifetime();
builder.Register(c => c.Resolve<IDbContextFactory>().CreateDbContext())
.As<IDbContext>()
.InstancePerLifetime();
I have a situation where I need to instantiate my DBContext after my solution has started up. I asked this question which indicated that I could do this with a constructor argument.
It was suggested that I implement as an example this:
var connection = #"Server=(localdb)\mssqllocaldb;Database=JobsLedgerDB;Trusted_Connection=True;ConnectRetryCount=0";
var optionsBuilder = new DbContextOptionsBuilder<BloggingContext>();
optionsBuilder.UseSqlServer(connection);
using (var context = new BloggingContext(optionsBuilder.Options))
{
// do stuff
}
However I have implemented the repository pattern (for better or worst) and given my changed circumstances - not having a connection string until after the solution has run startup - I need to implement this into the base repository class and I am at a bit of a loss..
Currently I have this:
public class EntityBaseRepository<T> : IEntityBaseRepository<T> where T : class, IEntityBase, new()
{
public JobsLedgerAPIContext _context;
#region Properties
public EntityBaseRepository(JobsLedgerAPIContext context)
{
_context = context;
}
#endregion
public virtual IQueryable<T> GetAll()
{
return _context.Set<T>().AsQueryable();
}
public virtual int Count()
{
return _context.Set<T>().Count();
}
......
How do I implement this change both instantiating the DBContext in the constructor (there by bypassing the need to add the context as a service in startup) and then with the wrapping each of the virtual methods with "using" etc
EDIT.. Camilo indicated I had not identified when I have the database name.
The basic situation is that the system starts up (This is an Aurelia SPA project which is irrelevant to this issue) sends the package to the browser which shows a login screen. User logs in.. User is verified via a JWT controller.. Once verified in the controller (using a catalog database that has one table with 3 fields - username, password, database name) I use the database name to create a connection string and then instantiate my DBContext at that point.. so via a constructor.
The answers below need to be modified as the one with the factory answer (promising) has errors as discovered by this question.. Nkosi responded with an great answer to the error.
EDIT 2..
This is a response to the edited question below:
Here is my original Client Repository with :base(context) on the constructor.
using JobsLedger.DATA.Abstract;
using JobsLedger.MODEL.Entities;
namespace JobsLedger.DATA.Repositories
{
public class ClientRepository : EntityBaseRepository<Client>, IClientRepository
{
private new JobsLedgerAPIContext _context;
public ClientRepository(JobsLedgerAPIContext context) : base(context)
{
_context = context;
}
public void RelatedSuburbEntities(Suburb _suburb)
{
_context.Entry(_suburb).Reference<State>(a => a.State).Load();
}
}
}
It has a reference to the base class "context". I am not sure how to modify this given that I believe I still need that ":base(context)" at the end. As well, I have a method in this that accesses _context as well which is part of the constructor...
Further I assume that I can no longer inject the service into the controller but instead new it up once I have secured the connection string and then pass that connection string to service.
Also, Given I have now added a singleton on the startup do I need to remove the original entry? :
services.AddDbContext<JobsLedgerAPIContext>(options => options.
UseSqlServer(Configuration.GetConnectionString("DefaultConnection"), b => b.MigrationsAssembly("JobsLedger.API")));
effectively replacing it with my singleton reference as per below:
services.AddSingleton(typeof(IContextFactory<>), typeof(ContextFactory<>));
Edited
The answer has been edited to rectify the mistake spotted and
fixed by Nkosi. Thanks, #Nkosi.
Implement a factory pattern. You can create a factory, call it ContextFactory as below:
First, define the interface. Further modified, removed the connectionString parameter
public interface IContextFactory<T> where T : DbContext
{
T CreateDbContext();
}
Create a factory class that implements this interface (edited as per Nkosi answer). Further modified to inject IHttpContextAccessor
public class ContextFactory<T> : IContextFactory<T> where T : DbContext
{
private readonly HttpContext _httpContext;
public ContextFactory(IHttpContextAccessor contextAccessor)
{
_httpContext = contextAccessor.HttpContext;
}
public T CreateDbContext()
{
// retreive the connectionString from the _httpContext.Items
// this is saved in the controller action method
var connectionString = (string)_httpContext.Items["connection-string"];
var optionsBuilder = new DbContextOptionsBuilder<T>();
optionsBuilder.UseSqlServer(connectionString);
return (T)Activator.CreateInstance(typeof(T), optionsBuilder.Options);
}
}
Then modify your base repository and make the JobsLedgerAPIContext protected. This context is going to be set by the derived class. Further modified to remove the constructor. It will use the parameterless constructor.
public class EntityBaseRepository<T> : IEntityBaseRepository<T> where T : class, IEntityBase, new()
{
protected JobsLedgerApiContext Context { get; set; }
public virtual IQueryable<T> GetAll()
{
return Context.Set<T>().AsQueryable();
}
public virtual int Count()
{
return Context.Set<T>().Count();
}
}
Change your derived class to use IContextFactory. Further modified to use the _contextFactory.CreateDbContext() parameter less method
The IClientRepository should have SetContext method defined.
public class ClientRepository : EntityBaseRepository<Client>, IClientRepository
{
private readonly IContextFactory<JobsLedgerApiContext> _contextFactory;
public ClientRepository(IContextFactory<JobsLedgerApiContext> factory)
{
_contextFactory = factory;
}
// this method will set the protected Context property using the context
// created by the factory
public void SetContext()
{
Context = _contextFactory.CreateDbContext();
}
public void RelatedSuburbEntities(Suburb suburb)
{
Context.Entry(suburb).Reference<State>(a => a.State).Load();
}
}
In the controller, that receives IClientRepository instance, you can set the connection in the HttpContext.Items, which will be valid for the request. This value will then be retrieved by the ContextFactory using IHttpContextAccessor. Then you simply call the _repository.SetContext(); method on the repository.
public class HomeController : Controller
{
private readonly IClientRepository _repository;
public HomeController(IClientRepository repository)
{
_repository = repository;
}
public IActionResult Index()
{
// save the connectionString in the HttpContext.Items
HttpContext.Items["connection-string"] = "test-connection";
// set the context
_repository.SetContext();
return View();
}
}
Make sure you register the IContextFactory in ConfigureServices as open generics and Singleton as below, also register the HttpContextAccessor and IClientRepository
services.AddHttpContextAccessor();
services.AddSingleton(typeof(IContextFactory<>), typeof(ContextFactory<>));
services.AddTransient<IClientRepository, ClientRepository>();
You may define your JobsLedgerAPIContext like this:
public class JobsLedgerAPIContext : DbContext
{
// public DbSet<Job> Jobs { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("Data Source=localhost;Integrated Security=SSPI;Initial Catalog=dotnetcore;");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// may need to reflect entity classes and register them here.
base.OnModelCreating(modelBuilder);
}
}
I have a custom SiteRole class that inherits RoleProvider. I inject the IUserService in the constructor in order to do a query to get all Roles.
It kept on throwing an error that the SiteRole class needed a parameterless constructor. So I ended up injecting the IUserService like this:
public SiteRole()
{
_userService = DependencyResolver.Current.GetService<IUserService>();
}
Inside the class I override the GetRolesForUser function
public override string[] GetRolesForUser(string nickname)
{
return new string[] { _userService.GetRoleForUser(nickname) };
}
The UserService calls this LINQ query in the UserRepository (This line throws System.InvalidOperationException: 'The operation cannot be completed because the DbContext has been disposed.')
public string GetRoleForUser(string nickname)
{
return DbContext.Users.Where(u => u.Nickname == nickname).FirstOrDefault().Role.Name;
}
Any hints on how I can resolve this? You cannot inject dependencies to RoleProvider via constructor so I have to use DependencyResolver.Current.GetService().
Looks like your SiteRole class has a longer lifetime than the injected IUserService. You should check if it's really the source of issue and tune lifetimes of registered services with use of your DI container API. Here are some links for different containers: Autofac, NInject, Unity, Simple injector.
And, probably, change your SiteRole type a bit — use a property to get alive UserService instead of field, with which the only instance is created at time of SiteRole instantiation.
public SiteRole()
{
// this field should be removed
// _userService = DependencyResolver.Current.GetService<IUserService>();
}
// this property should be used instead of field
private IUserService UserService
{
get { DependencyResolver.Current.GetService<IUserService>(); }
}
Or in modern syntax
private IUserService UserService => DependencyResolver.Current.GetService<IUserService>();
But be careful and check that you won't get uncontrolled count of UserService instances created, if it's for example configured as instance per call in your DI container.
I solved the problem by doing this:
virtual Owned<IUserService> ResolveUserService()
=> DependencyResolver.Current.GetService<Owned<IUserService>>();
And call the service in the GetRolesForUser Method with a Using statement:
public override string[] GetRolesForUser(string nickname)
{
using(var userService = ResolveUserService())
{
return new string[] {userService.Value.GetRoleForUser(nickname) };
}
}
On a sidenote, DbContext is constructed in the RepositoryBase. I'm using the Generic Repository Pattern for my project.
public abstract class RepositoryBase<T> where T : class
{
#region properties
private StoreEntities dataContext;
private readonly IDbSet<T> dbSet;
protected IDbFactory DbFactory
{
get;
private set;
}
protected StoreEntities DbContext
{
get { return dataContext ?? (dataContext = DbFactory.Init()); }
}
#endregion
protected RepositoryBase(IDbFactory dbFactory)
{
DbFactory = dbFactory;
dbSet = DbContext.Set<T>();
}
#region Implementation of defaults
public virtual void Add(T entity)...........
Thanks for the help!
My current Autofac config is working to resolve my ApiControllers in a WebApi.
Where I'm struggling is, I'm attempting to create a 'BaseApiController' with generic constructor parameters, but getting exception:
No constructors on type 'Service`1[WorldRegion]' can be found with the constructor finder 'Autofac.Core.Activators.Reflection.DefaultConstructorFinder'.
Here is the code structure:
public interface IEntityKey { }
public class WorldRegion : IEntityKey { }
public interface IRepository<T> where T : IEntityKey { }
public interface IService<T> where T : IEntityKey { }
public abstract class Service<T> : IService<T> where T : IEntityKey { }
public interface IWorldRegionService : IService<WorldRegion> { }
public class WorldRegionService : Service<WorldRegion>, IWorldRegionService
{
private readonly IRepository<WorldRegion> _repository;
}
WORKING API Controller:
public class WorldRegionsController : BaseApiController
{
private readonly IWorldRegionService _worldRegionService;
private readonly ICultureService _cultureService;
public WorldRegionsController(IWorldRegionService worldRegionService, ICultureService cultureService)
{
_worldRegionService = worldRegionService;
_cultureService = cultureService;
}
}
WORKING Autofac config:
public static void Register(HttpConfiguration config, IAppBuilder app)
{
var builder = new ContainerBuilder();
builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
RegisterTypes(builder);
var container = builder.Build();
config.DependencyResolver = new AutofacWebApiDependencyResolver(container);
app.UseAutofacMiddleware(container);
app.UseAutofacWebApi(config);
}
public static void RegisterTypes(ContainerBuilder builder)
{
// Context
builder.RegisterType<DataContext>().As<IDataContext>().InstancePerRequest();
// UOW
builder.RegisterType<UnitOfWork>().As<IUnitOfWork>().InstancePerRequest();
// Repositories
builder.RegisterGeneric(typeof(Repository<>)).As(typeof(IRepository<>)).InstancePerRequest();
// Services
builder.RegisterType<CultureService>().As<ICultureService>().InstancePerRequest();
builder.RegisterType<WorldRegionService>().As<IWorldRegionService>().InstancePerRequest();
}
Here is the generic ATTEMPT:
// BaseApiController
public abstract class BaseApiController<T> : ApiController where T : IEntityKey
{
protected readonly IService<T> _service;
protected readonly ICultureService _cultureService;
public BaseApiController(IService<T> service, ICultureService cultureService)
{
_service = service;
_cultureService = cultureService;
}
}
// ApiController
public class WorldRegionsController : BaseApiController<WorldRegion>
{
public WorldRegionsController(
IService<WorldRegion> service, ICultureService cultureService)
: base(service, cultureService) {}
}
// Added to Autofac config
builder.RegisterGeneric(typeof(Service<>)).As(typeof(IService<>)).InstancePerRequest();
// Removed
builder.RegisterType<WorldRegionService>().As<IWorldRegionService>().InstancePerRequest();
With this change, I'm getting the exception message noted above (in yellow). I think I'm missing something in the Autofac config, just not sure what. Maybe include/register/add 'WorldRegion' somehow.
How should I register my types ?
Your controller expect a IService<WorldRegion>. Autofac find the following registration for this service :
builder.RegisterGeneric(typeof(Service<>)).As(typeof(IService<>)).InstancePerRequest();
So it tries to create a Service<WorldRegion> which is not possible because Service<T> is an abstract class.
Don't forget that a IWorldRegionService is a IService<WorldRegion> but a IService<WorldRegion> is not a IWorldRegionService.
You don't want to register a generic service but want to register all children as a IService<T>, you can do this by using the RegisterAssemblyTypes method with the AsClosedTypedOf
builder.RegisterAssemblyTypes(this.GetAssemblies())
.AsClosedTypesOf(typeof(IService<>));
Read Autofac documentation - Assembly scanning for more information and how to properly implement the GetAssemblies method in IIS.
At first, I tried removing the abstract from Service<T>, that didn't work, same error. Then I read the Autofac docs and searched around, and I found something similar to Cyril's answer that works:
builder.RegisterAssemblyTypes(typeof(Service<>).Assembly)
.Where(t => t.Name.EndsWith("Service")).AsImplementedInterfaces();
I tried Cyril's implementation of builder.RegisterAssemblyTypes() but this.GetAssemblies() wasn't available. I had to use the full path and this also works:
builder.RegisterAssemblyTypes(AppDomain.CurrentDomain.GetAssemblies())
.AsClosedTypesOf(typeof(IService<>));
To note, using either builder.RegisterTypes(), the Service<T> can still be abstract. And just to test, I changed WorldRegionService to abstract, and that DID NOT work.
// THIS DOES NOT WORK..!!!! Must remove 'abstract' from this class.
public abstract class WorldRegionService {}
So both above builder.RegisterTypes() work.
Registering DbContext in ASP.NET MVC Application as InstancePerRequest. (IoC Autofac)
builder.RegisterType<ADbContext>().As<IADbContext>().InstancePerRequest();
Using inside BService
public class BService : IBService
{
readonly IADbContext _dbContext;
public BService(IADbContext dbContext)
{
_dbContext = dbContext;
}
}
Trying to register IBService as Singleton.
builder.RegisterType<BService>().As<IBService>().SingleInstance();
Obviously, this gives me an error
No scope with a tag matching 'AutofacWebRequest' is visible from the scope in which the instance was requested.
Simplest solution is to register IBService as InstancePerRequest, but there is no reason having PerRequest IBService rather than error message mentioned above.
How can i use PerRequest DbContext inside Singleton service ?
First attempt, you can inject IContainer into BService. But this will look like Service locator pattern, which is not good. Otherwise, you can define factory interface
public interface IFactory<T>
{
T GetInstance();
}
Then implement it and register
public class SimpleFactory<T> : IFactory<T>
{
private IContainer _container;
public SimpleFactory(IContainer container)
{
_container = container;
}
public T GetInstance()
{
return _container.Resolve<T>();
}
}
public class DbContextFactory : SimpleFactory<IADbContext>
{
public DbContextFactory(IContainer container):base(container)
{
}
}
Finally, use this factory in your singletone
public class BService : IBService
{
IADbContext _dbContext => _dbContextFactory.GetInstance();
IFactory<IADbContext> _dbContextFactory
public BService(IFactory<IADbContext> dbContextFactory)
{
_dbContextFactory = dbContextFactory;
}
}
Each time, when you want to acess to context inside singletone, it will pass this request to IoC container, which able to return context per request.