Home > Enterprise >  Problem with Dependency Injection in Controller
Problem with Dependency Injection in Controller

Time:06-22

I am trying to setup an API following the guidance of the eShopOnWeb Repository as sample implementation. Other than in that implementation I want to use 'regular' REST Controller instad of the minimal controllers.

When running my application I get this error:

fail: Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware[1]
      An unhandled exception has occurred while executing the request.
      System.InvalidOperationException: Unable to resolve service for type 'Core.Interfaces.IBookService' while attempting to activate 'API.Controllers.BookController'.
         at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.GetService(IServiceProvider sp, Type type, Type requiredBy, Boolean isDefaultParameterRequired)
         at lambda_method26(Closure , IServiceProvider , Object[] )
         at Microsoft.AspNetCore.Mvc.Controllers.ControllerActivatorProvider.<>c__DisplayClass7_0.<CreateActivator>b__0(ControllerContext controllerContext)
         at Microsoft.AspNetCore.Mvc.Controllers.ControllerFactoryProvider.<>c__DisplayClass6_0.<CreateControllerFactory>g__CreateController|0(ControllerContext controllerContext)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
      --- End of stack trace from previous location ---
         at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
         at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
         at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

I assume that this indicates, that DI can't find the concrete implementation of the BookService that would fullfill IBookServices's contract

I structured the solution in followin manner:

.sln
- API (REST Controller and Startup)
- Core (Domain Objects and Services)
- Infrastructure (DB Context, Repositories)

Now I want to access all books under /api/Book.

using Core.Entities.BookAggregate;
using Core.Interfaces;
using Microsoft.AspNetCore.Mvc;

namespace API.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class BookController : ControllerBase
    {
        private readonly IBookService _bookService;

        public BookController(IBookService bookService)
        {
            _bookService = bookService;
        }

        // GET: api/<BookController>
        [HttpGet]
        public async Task<IEnumerable<Book>> Get()
        {
            return await _bookService.ListAsync();
        }

        // Omitted for focus
    }
}

The concrete Service looks like this:

using Core.Entities.BookAggregate;
using Core.Interfaces;

namespace Core.Services
{
    internal class BookService : IBookService
    {
        private readonly IRepository<Book> _bookRepository;
        private readonly IAppLogger<BookService> _logger;


        public BookService(IRepository<Book> bookRepository, IAppLogger<BookService> logger)
        {
            _bookRepository = bookRepository;
            _logger = logger;
        }

        public async Task<Book> GetByIdAsync(int id)
        {
            return await _bookRepository.GetByIdAsync(id);
        }

        public async Task<IEnumerable<Book>> ListAsync()
        {
            return await _bookRepository.ListAsync();
        }

        public async Task DeleteByIdAsync(int id)
        {
            var book = await _bookRepository.GetByIdAsync(id);

            if (book == null)
            {
                _logger.Error($"Book with id: {id} can not be found!");
                throw new ArgumentException($"Book with id: {id} can not be found!");
            }

            await _bookRepository.DeleteAsync(book);
        }

        public async Task AddAsync(Book book)
        {
            await _bookRepository.AddAsync(book);
        }

        public async Task UpdateAsyc(Book book)
        { 
            await _bookRepository.UpdateAsync(book);
        }
    }

}

IRepository<T> is bases on Ardalis:

using Ardalis.Specification;

namespace Core.Interfaces
{
    public interface IRepository<T> : IRepositoryBase<T> where T : class, IAggregateRoot
    {
    }
}

The Program.cs looks like this:

using Core.Interfaces;
using Infrastructure.Data;
using Infrastructure.Logging;
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

builder.Logging.AddConsole();

Infrastructure.Dependencies.ConfigureServices(builder.Configuration, builder.Services);

builder.Services.AddScoped(typeof(IRepository<>), typeof(EfRepository<>));
builder.Services.AddScoped(typeof(IReadRepository<>), typeof(EfRepository<>));
builder.Services.AddScoped(typeof(IAppLogger<>), typeof(LoggerAdapter<>));
builder.Services.AddDbContext<BookDesinerContext>(options => options.UseInMemoryDatabase(databaseName: "Books"));

builder.Services.AddMemoryCache();

builder.Services.AddControllers();

var app = builder.Build();

app.Logger.LogInformation("PublicApi App created...");

app.Logger.LogInformation("Seeding Database...");

using (var scope = app.Services.CreateScope())
{
    var scopedProvider = scope.ServiceProvider;
    try
    {
        var bookDesinerContext = scopedProvider.GetRequiredService<BookDesinerContext>();
        await BookDesignerContextSeed.SeedAsync(bookDesinerContext, app.Logger);
    }
    catch (Exception ex)
    {
        app.Logger.LogError(ex, "An error occurred seeding the DB.");
    }
}

if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}

app.UseHttpsRedirection();

app.UseRouting();

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllers();
});

app.Logger.LogInformation("LAUNCHING PublicApi");
app.Run();

My current best guess is, that in the provided example the use the controller to access the repository directly. Which I don't find to be good practice and I want to have the service layer inbetween. But due to this I would somehow have to register the BookService to be the concrete implementation of IBookService (possibly in Programm.cs) but I don't know how or if this is the correct way.

How can this error be resolved?

CodePudding user response:

You should add this to your startup.cs file

public void ConfigureServices(IServiceCollection services)
{
    //other codes
    builder.Services.AddScoped<IBookService, BookService>();
}
  • Related