Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

No service for type 'MyType' has been registered

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.

like image 887
jacobsowles Avatar asked Mar 15 '18 19:03

jacobsowles


2 Answers

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>();

    // [...]
}
like image 173
jacobsowles Avatar answered Sep 22 '22 09:09

jacobsowles


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).

like image 20
Evk Avatar answered Sep 21 '22 09:09

Evk