Reputation: 5667
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
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
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
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
Reputation: 1
builder.Services.AddTransient<Istudent , StudentService>();
Where the implementation of the Istudent
interface is done in the StudentService
class.
Upvotes: -1
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
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
Reputation: 4786
Great question and great answers so far, however...
...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
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
Reputation: 2326
This happened to me when service class didn't inherit from IService interface
Upvotes: 3
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