I have a generic repository architecture that looks like this:
Repository
public interface IRepository<T> where T: class
{
IList<T> Get(Func<T, bool> where);
}
public abstract class Repository<T> : IRepository<T> where T: class
{
private readonly DbSet<T> _entity;
protected Repository(ApplicationDbContext dbContext)
{
_entity = dbContext.Set<T>();
}
public IList<T> Get(Func<T, bool> where)
{
return _entity.Where(where).ToList();
}
}
Then concrete implementations are created like this:
UserRepository
public class UserRepository : Repository<ApplicationUser>
{
public UserRepository(ApplicationDbContext dbContext) : base(dbContext) {}
// Can add any other methods I want that aren't included in IRepository
}
I'll likely have quite a few services, so rather than having each one passed into the controller individually, I thought I'd try passing in a single factory that can produce repositories for me.
RepositoryFactory
public interface IRepositoryFactory
{
T GetRepository<T>() where T : class;
}
public class RepositoryFactory : IRepositoryFactory
{
private readonly IServiceProvider _provider;
public RepositoryFactory(IServiceProvider serviceProvider)
{
_provider = serviceProvider;
}
public T GetRepository<T>() where T : class
{
return _provider.GetRequiredService<T>(); // ERROR: No service for type 'UserRepository' has been registered
}
}
Now, in setting up dependency injection, I registered the services like this:
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
// [...]
services.AddScoped<IRepository<ApplicationUser>, UserRepository>();
services.AddScoped<IRepositoryFactory, RepositoryFactory>();
// [...]
}
This is all used in the controller like this:
Controller
public class HomeController : Controller
{
private readonly UserRepository _userRepository;
public HomeController(IRepositoryFactory repositoryFactory)
{
_userRepository = repositoryFactory.GetRepository<UserRepository>(); // ERROR: No service for type 'UserRepository' has been registered
}
// [...]
}
When I call _provider.GetRequiredService<T>()
in the repositoryFactory.GetRepository<UserRepository>()
method, I get the error in the comment above.
The RepositoryFactory
is coming through just fine, but the UserRepository
isn't getting registered. What am I missing? I've tried calling the GetRepository
method outside of the constructor, and I've tried change AddScoped
to the other Add
variants (Transient and Singleton), but to no avail.
Looks like my problem had to do with my understanding of AddScoped
. Looking at the docs, the two type parameters in AddScoped
are TService
and TImplementation
. So in my Startup class above, the SERVICE I'm registering is IRepository<ApplicationUser>
, not ApplicationRepository
. The IMPLEMENTATION I'm registering is ApplicationRepository
.
To fix this, I changed my Startup
class to say that UserRepository
is the service being registered.
public void ConfigureServices(IServiceCollection services)
{
// [...]
// OLD: services.AddScoped<IRepository<ApplicationUser>, UserRepository>();
services.AddScoped<UserRepository>();
services.AddScoped<IRepositoryFactory, RepositoryFactory>();
// [...]
}
But this approach relies on an implementation rather than an abstraction (interface), so I took it a step further and introduced an interface for the UserRepository
class and registered that in Startup
.
UserRepository
public interface IUserRepository : IRepository<ApplicationUser>
{
void DoTheThing();
}
public class UserRepository : Repository<ApplicationUser>, IUserRepository
{
public UserRepository(ApplicationDbContext dbContext) : base(dbContext) {}
public void DoTheThing()
{
}
}
Startup
public void ConfigureServices(IServiceCollection services)
{
// [...]
services.AddScoped<IUserRepository, UserRepository>();
services.AddScoped<IRepositoryFactory, RepositoryFactory>();
// [...]
}
You are taking dependency on UserRepository
, which is implementation and not interface, that's not good (given that you actually have that interface registered in container). Instead you need to take dependency on interface: IRepository<ApplicationUser>
. So change your factory interface:
public interface IRepositoryFactory
{
IRepository<T> GetRepository<T>() where T : class;
}
Implementation:
public IRepository<T> GetRepository<T>() where T : class
{
return _provider.GetRequiredService<IRepository<T>>(); // ERROR: No service for type 'UserRepository' has been registered
}
And controller:
public class HomeController : Controller
{
private readonly IRepository<ApplicationUser> _userRepository;
public HomeController(IRepositoryFactory repositoryFactory)
{
_userRepository = repositoryFactory.GetRepository<ApplicationUser>(); // ERROR: No service for type 'UserRepository' has been registered
}
// [...]
}
Now it doesn't work, because you registered IRepository<ApplicationUser>
in container (with implementation being UserRepository
), but UserRepository
which you are trying to resolve is not registered (and as said above - you should not resolve it anyway).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With