Sam
Sam

Reputation: 5667

Why am I getting the error "Cannot instantiate implementation type" for my generic service?

I have a generic repository that I have been instantiating right in my WEB API Controller no problem for a while.

This is what my controller used to look like:

[Route("api/[controller]")]
public class EmployeesController : Controller
{
    private IGenericRepository<Employee> _empRepo;

    public EmployeesController(IGenericRepository<Employee> employeeRepo)
    {
        _empRepo = employeeRepo;
    }

    // GET: api/employees
    [HttpGet]
    public async Task<IEnumerable<Employee>> GetEmployeesAsync(
            string firstName = null, string lastName = null)
    {
        //return await _empRepo.GetAll().Include("Organization").Include("PayPlan").Include("GradeRank").Include("PositionTitle").Include("Series").Include("BargainingUnit")
        //    .Where(e => (string.IsNullOrEmpty(firstName) || e.FirstName.Contains(firstName))
        //        && (string.IsNullOrEmpty(lastName) || e.LastName.Contains(lastName))
        //    )
        //    .ToListAsync();

        return await _empRepo.GetAllIncluding(
                a => a.Organization,
                b => b.PayPlan,
                c => c.GradeRank,
                d => d.PositionTitle,
                e => e.Series,
                f => f.BargainingUnit)
            .Where(e => (string.IsNullOrEmpty(firstName) || e.FirstName.Contains(firstName))
                && (string.IsNullOrEmpty(lastName) || e.LastName.Contains(lastName))
            )
            .ToListAsync();
    }

    // GET api/employees/5
    [HttpGet("{id}", Name = "GetEmployeeById")]
    public async Task<IActionResult> GetEmployeeByIdAsync(long id)
    {         
        //var employee = await _empRepo.Find(id).Include("Organization").Include("PayPlan").Include("GradeRank").Include("PositionTitle").Include("Series").Include("BargainingUnit").SingleAsync();
        var employee = await  _empRepo.GetSingleIncludingAsync(id,
            a => a.Organization,
            b => b.PayPlan,
            c => c.GradeRank, 
            d => d.PositionTitle,
            e => e.Series,
            f => f.BargainingUnit);

        if (employee == null)
        {
            return NotFound();
        }
        else
        {
            return new ObjectResult(employee);
        }
    }

    // PUT api/employees/id
    [HttpPut("{id}")]
    public async Task<IActionResult> PutEmployeeAsync([FromBody] Employee emp)
    {
        var employee = await _empRepo.UpdateAsync(emp);
        if (employee == null)
        {
            return NotFound();
        }

        await _empRepo.SaveAsync();
        return new ObjectResult(employee);
    }
}

and we would configure the DI in StartUp like this:

services.AddScoped(typeof(IGenericRepository<>), typeof(GenericRepository<>));

Now I have just refactored and tried to put a service layer in between the controller and the generic repo.

So here is my second DI line in StartUp:

services.AddScoped(typeof(IGenericService<>), typeof(IGenericService<>));

So now I have these two DI lines:

services.AddScoped(typeof(IGenericRepository<>), typeof(GenericRepository<>));
services.AddScoped(typeof(IGenericService<>), typeof(IGenericService<>));

The following is my current code:

Starting with the Generic Repo:

public enum FilteredSource
{
    All,
    GetAllIncluding,
}

public class GenericRepository<T> : IGenericRepository<T>
    where T: BaseEntity
{
    protected readonly ApplicationDbContext _context;
    protected DbSet<T> _dbSet;

    public GenericRepository(ApplicationDbContext context)
    {
        _context = context;
        _dbSet = context.Set<T>();
    }

    // no eager loading
    private IQueryable<T> All => _dbSet.Cast<T>();

    #region FIXME : DELETE
    // FIXME: Delete and use ALL instead.
    public IQueryable<T> GetAll() => _dbSet.AsQueryable();

    // FIXME: Delete and use GetSingleIncludingAsync instead.
    public IQueryable<T> Find(long id) =>
        _dbSet.Where(e => e.Id == id).AsQueryable();
    #endregion

    // eager loading
    private IQueryable<T> GetAllIncluding(
        params Expression<Func<T, object>>[] includeProperties) =>
         includeProperties.Aggregate(All, (currentEntity, includeProperty) => currentEntity.Include(includeProperty));

    // no eager loading
    public async Task<T> GetSingleIncludingAsync(long id)
    {
        return await _dbSet.SingleOrDefaultAsync(e => e.Id == id);
    }

    /// <summary>
    /// Takes in a lambda selector and let's you filter results from GetAllIncluding and All.
    /// </summary>
    /// <param name="selector">labmda expression to filter results by.</param>
    /// <param name="getFilteredSource">All or GetAllIncluding as the method to get results from.</param>
    /// <param name="includeProperties">array of eager load lamda expressions.</param>
    /// <returns></returns>
    public async Task<IEnumerable<T>> GetFiltered(
        Expression<Func<T, bool>> selector, FilteredSource filteredSource,
        Expression<Func<T, object>>[] includeProperties = null)
    {
        var results = default(IEnumerable<T>);
        switch (filteredSource)
        {
            case FilteredSource.All:
                results = All.Where(selector);
                break;
            case FilteredSource.GetAllIncluding:
                results = GetAllIncluding(includeProperties).Where(selector);
                break;
        }
        return await results.AsQueryable().ToListAsync();
    }

    // eager loading
    public async Task<T> GetSingleIncludingAsync(
        long id, params Expression<Func<T, object>>[] includeProperties)
    {
        IQueryable<T> entities = GetAllIncluding(includeProperties);
        //return await Filter<long>(entities, x => x.Id, id).FirstOrDefaultAsync();
        return await entities.SingleOrDefaultAsync(e => e.Id == id);
    }

    public async Task<T> InsertAsync(T entity)
    {
        if (entity == null)
        {
            throw new ArgumentNullException($"No {nameof(T)}  Entity was provided for Insert");
        }
        await _dbSet.AddAsync(entity);
        return entity;
    }

    public async Task<T> UpdateAsync(T entity)
    {
        T entityToUpdate = await
            _dbSet.AsNoTracking().SingleOrDefaultAsync(e => e.Id == entity.Id);
        if (entityToUpdate == null)
        {
            //return null;
            throw new ArgumentNullException($"No {nameof(T)}  Entity was provided for Update");
        }

        _dbSet.Update(entity);
        return entity;
    }

    public async Task<T> DeleteAsync(T entity)
    {
        _dbSet.Remove(entity);
        return await Task.FromResult(entity);
    }

    public Task SaveAsync() => _context.SaveChangesAsync();

Interface Definition:

public interface IGenericRepository<T>
    where T : BaseEntity
{
    #region FIXME : DELETE
    // FIXME: Delete and use ALL instead.
    IQueryable<T> GetAll();

    // FIXME: Delete and use GetSingleIncludingAsync instead.
    IQueryable<T> Find(long id);
    #endregion

    // eager loading
    Task<T> GetSingleIncludingAsync(
        long id, params Expression<Func<T, object>>[] includeProperties);

    Task<IEnumerable<T>> GetFiltered(
        Expression<Func<T, bool>> selector, FilteredSource filteredSource,
        Expression<Func<T, object>>[] includeProperties = null);

    Task<T> InsertAsync(T entity);
    Task<T> UpdateAsync(T entity);
    Task<T> DeleteAsync(T entity);

    #region Possible TODOs:
    //Task<IEnumerable<T>> FindBy(Expression<Func<T, bool>> predicate);
    //Task AddRange(IEnumerable<T> entities);
    //Task RemoveRange(IEnumerable<T> entities);
    #endregion

    Task SaveAsync();
}

This gets injected into my Generic Service:

public class GenericService<T> : IGenericService<T>
    where T : BaseEntity
{
    private IGenericRepository<T> _genericRepo;

    public GenericService(IGenericRepository<T> genericRepo)
    {
        _genericRepo = genericRepo;
    }

    public async Task<IEnumerable<T>> GetFiltered(
        Expression<Func<T, bool>> selector, FilteredSource filteredSource,
        Expression<Func<T, object>>[] includeProperties = null)
    {
        return await _genericRepo.GetFiltered(selector, filteredSource,
            includeProperties);
    }

    // eager loading
    public async Task<T> GetSingleIncludingAsync(long id, params Expression<Func<T, object>>[] includeProperties)
    {
        IEnumerable<T> entities = await _genericRepo.GetFiltered(null, FilteredSource.GetAllIncluding, includeProperties);
        //return await Filter<long>(entities, x => x.Id, id).FirstOrDefaultAsync();
        return entities.SingleOrDefault(e => e.Id == id);
    }

    public async Task<T> InsertAsync(T entity)
    {
        var result = await _genericRepo.InsertAsync(entity);
        await _genericRepo.SaveAsync();
        return entity;
    }

    public async Task<T> UpdateAsync(T entity)
    {
        var result = await _genericRepo.UpdateAsync(entity);
        if (result != null)
        {
            await _genericRepo.SaveAsync();
        }
        return result;
    }

    public async Task<T> DeleteAsync(T entity)
    {
        throw new NotImplementedException();
    }
}

Interface Definition for the Service:

public interface IGenericService<T>
    where T : BaseEntity
{
    Task<IEnumerable<T>> GetFiltered(
        Expression<Func<T, bool>> selector, FilteredSource filteredSource,
        Expression<Func<T, object>>[] includeProperties = null);

    // eager loading
    Task<T> GetSingleIncludingAsync(long id, params Expression<Func<T, object>>[] includeProperties);

    Task<T> InsertAsync(T entity);
    Task<T> UpdateAsync(T entity);
    Task<T> DeleteAsync(T entity);
}

and finally, here is the controller:

[Route("api/[controller]")]
public class EmployeesController : Controller
{
    private IGenericService<Employee> _genericService;

    public EmployeesController(IGenericService<Employee> genericService)
    {
        _genericService = genericService;
    }

    // GET: api/employees
    [HttpGet]
    public async Task<IEnumerable<Employee>> GetEmployeesAsync(
            string firstName = null, string lastName = null)
    {
        return await _genericService.GetFiltered(
                e => (string.IsNullOrEmpty(firstName) || e.FirstName.Contains(firstName))
                && (string.IsNullOrEmpty(lastName) || e.LastName.Contains(lastName)),
                FilteredSource.GetAllIncluding,
                new Expression<Func<Employee, object>>[] { a => a.Organization,
                b => b.PayPlan,
                c => c.GradeRank,
                d => d.PositionTitle,
                e => e.Series,
                f => f.BargainingUnit }
            );
    }

    // GET api/employees/5
    [HttpGet("{id}", Name = "GetEmployeeById")]
    public async Task<IActionResult> GetEmployeeByIdAsync(long id)
    {
        //var employee = await _empRepo.Find(id).Include("Organization").Include("PayPlan").Include("GradeRank").Include("PositionTitle").Include("Series").Include("BargainingUnit").SingleAsync();
        var employee = await _genericService.GetSingleIncludingAsync(id,
            a => a.Organization,
            b => b.PayPlan,
            c => c.GradeRank,
            d => d.PositionTitle,
            e => e.Series,
            f => f.BargainingUnit);

        if (employee == null)
        {
            return NotFound();
        }
        else
        {
            return new ObjectResult(employee);
        }
    }

    // PUT api/employees/id
    [HttpPut("{id}")]
    public async Task<IActionResult> PutEmployeeAsync([FromBody] Employee emp)
    {
        var employee = await _genericService.UpdateAsync(emp);
        if (employee == null)
        {
            return NotFound();
        }

        return new ObjectResult(employee);
    }
}

I'm just not sure how to plug in the middle service layer.

Upvotes: 78

Views: 107020

Answers (10)

Brian Thomas
Brian Thomas

Reputation: 11

Serj mentioned Assembly scanning. In my case, The DI container was trying to add nested compiler generated classes when I scanned an assembly:

    public IEnumerable<Type> GetTypes(string namespaceName, Assembly assembly)
    {

    var types = assembly.GetTypes()
        .Where(t => t.Namespace == namespaceName
                    && t.IsClass
                    && !t.IsNested //Adding this fixed my issue.
        );

    return types;
    }

Upvotes: 0

Thomas
Thomas

Reputation: 736

My problem was attempting to inject the config without an instance... face palm. Changing

services.AddSingleton<IOptions<AzureBlobConfiguration>>();

to

builder.Services.AddOptions<AzureBlobConfiguration>()
    .Bind(builder.Configuration.GetSection("AzureBlobConfiguration"))
    .ValidateOnStart();

fixed it for me.

Upvotes: 0

Serj Sagan
Serj Sagan

Reputation: 30247

This seems obvious now that I've figured it out, but if you are doing assembly scanning registrations, you'll want to exclude your abstract classes and interfaces:

var assembly = AppDomain.CurrentDomain.Load("My.Namespace");
if (assembly == null)
    return;

var types = assembly.GetTypes().Where(x => x?.Namespace != null &&
                                           x.Namespace.Contains("My.Namespace.ViewModels") &&
                                           x.Name.Contains("ViewModel") &&
                                           !x.Name.Contains(nameof(MyAbstractViewModelBase)) &&
                                           !x.Name.Contains(nameof(IMyAbstractViewModel)));

foreach (var thing in types)
    services.AddScoped(thing);

Upvotes: 2

builder.Services.AddTransient<Istudent , StudentService>();

Where the implementation of the Istudent interface is done in the StudentService class.

Upvotes: -1

Bamdad
Bamdad

Reputation: 856

Based on David's answer, I had to remove the 'abstract' in my service implementation class. However I had some abstract methods and properties in the class too. So after making them non-abstract and creating a public constructor I was able to register the service.

Upvotes: 7

Skrymsli
Skrymsli

Reputation: 5313

Following on Bartosz answer, another reason for this message is if you are trying to satisfy a non-generic interface with a generic concrete type like this:

services.AddScoped(typeof(IMyDependency), typeof(MyDependency<,>));

This won't work because the DI container doesn't know what generic arguments to provide when instantiating MyDependency. Either the interface has to be converted to generic or you need a factory to instantiate it manually.

Upvotes: 6

Bartosz
Bartosz

Reputation: 4786

Great question and great answers so far, however...

If your problem is different but the error message is the same

...and you are registering the service for a default implementation, without specifying the implementation type, like here:

services.AddScoped<IMyDependency>();

Then the error is unhelpful because it does not give details on why things don't work:

Cannot instantiate implementation type 'MyApp.IMyDependency' for service type 'MyApp.IMyDependency'.

In that case, change the service registration to:

services.AddScoped<IMyDependency, MyDependency>();

That will improve the error message and it will tell you where is the problem, like so:

Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: MyApp.IMyDependency Lifetime: Transient ImplementationType: MyApp.MyDependency': Unable to resolve service for type 'MyApp.ILowerDependency' while attempting to activate 'MyApp.IMyDependency'.)

In the default ASP.NET core DI container, entire dependency graph needs to be registered explicitly (except maybe for some exceptions like ILogger)

Upvotes: 16

David Nutting
David Nutting

Reputation: 2015

This happened to me recently. I had the implementation class marked "abstract", an artifact of an earlier design. I removed it and the container was able to instantiate the class without a problem.

Upvotes: 169

Amirhossein Yari
Amirhossein Yari

Reputation: 2326

This happened to me when service class didn't inherit from IService interface

Upvotes: 3

Sam
Sam

Reputation: 5667

I'm an idiot. I had the Inteface for the implementation. I changed:

services.AddScoped(typeof(IGenericService<>), typeof(IGenericService<>));

to

services.AddScoped(typeof(IGenericService<>), typeof(GenericService<>));

Upvotes: 135

Related Questions