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