1

I am trying to configure scrutor so that I can pass in an IRepository<T> which then will resolve to a concreate UserRepository that inherits BaseRepository<T> : IRepositroy<T>

This is my controller

    [Route("api/[controller]")]
    [ApiController]
    public class GetUsersController : ControllerBase
    {
        private readonly IMapper _mapper;
        private IRepository<User> _repository;

        public GetUsersController(IRepository<User> repository, IMapper mapper)
        {
            _mapper = mapper;
            _repository = repository;
        }

        [HttpGet]
        public ActionResult<IEnumerable<UserReadDto>> GetUsers()
        {
            var userItems = _repository.GetAll();
            return Ok(_mapper.Map<IEnumerable<UserReadDto>>(userItems));
        }

My Generic Interface

public interface IRepository<T> where T : class
{
    bool SaveChanges();
    IEnumerable<T> GetAll();
    T GetById(int id);
    void Create(T entity);
    void Remove(int id);
}

The concrete base class using generics

    public class BaseRepository<T> : IRepository<T> where T : Entity
    {
        private AppDbContext _context;
        private readonly DbSet<T> dbSet;

        public BaseRepository(AppDbContext context)
        {
            _context = context;
            dbSet = context.Set<T>();
        }

        public bool SaveChanges() => _context.SaveChanges() > 0;

        public IEnumerable<T> GetAll() => _context.Set<T>().ToList();

        public T GetById(int id) =>
            _context.Set<T>().FirstOrDefault(i => i.Id == id);

        public void Create(T entity) => _context.Set<T>().Add(entity);

        public void Remove(int id) => _context.Set<T>().Remove(GetById(id));
    }

The concrete user repository where I will add user specific logic

public class UserRepository : BaseRepository<User>
{
    public UserRepository(AppDbContext context) : base(context)
    {
    }
}

my base entity passed to the base repository for T

public class Entity
{
    [Key] public int Id { get; set; }
    [Required] public string Name { get; set; }
}

The user entity which inherits from Entity

public class User : Entity
{
    [Required] public string Role { get; set; }
    [Required] public string Email { get; set; }
}

My Program.cs where I am trying to use Scrutor for the DI

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

builder.Services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());

builder.Services.Scan(
    scan =>
    scan.FromCallingAssembly()
    .AddClasses(classes => classes.AssignableTo(typeof(IRepository<>)))
    .AsImplementedInterfaces());

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();

app.Run();

I have tried to use Scrutor's scan inside of progam.cs but get the following error:

System.InvalidOperationException: Unable to resolve service for type 'Account.Service.Data.Repository.IRepository`1[Account.Service.Core.Entities.User]' while attempting to activate 'Account.Service.Api.Controllers.Users.GetUsersController'.

   at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.GetService(IServiceProvider sp, Type type, Type requiredBy, Boolean isDefaultParameterRequired)
   at lambda_method3(Closure, IServiceProvider, Object[])
   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.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
   at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
   at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)

It may be worth mentioning that I have these across three separate projects.

The controllers are in an API project. The User and Entity are in a Core project. The Repository classes are in a data project.

Steven
  • 166,672
  • 24
  • 332
  • 435
CodeSmith
  • 11
  • 4

1 Answers1

0

I was having similar problems when I tried to use scrutor. Through debugging the lambda expression in the AddClasses method I found out that I wasn't scanning all the assemblies I needed. I think you have the same issue here. You are scanning the executing assembly, which is just the startup project of your application. You need to instead add the assembly of the data project that contains your Repositories.

builder.Services.Scan(scan =>
    scan.FromAssemblyOf<IDataAssemblyMarker>()
        .AddClasses(classes => classes.AssignableTo(typeof(IRepository<>)))
        .AsImplementedInterfaces());

Where IDataAssemblyMarker is an interface inside your data project. You can also use any other class inside of the data project, but I would recommend creating an empty interface just for that purpose to avoid confusing other developers or yourself in the future. Then you can also add a description to the interface and explain why you use it.

/// <summary>
/// This is an assembly marker used for scanning
/// the data projects assembly for dependency injection
/// </summary>
public interface IDataAssemblyMarker
{
}

Also in the code you provided the AppDbContext is not injected and therefore the repositories cannot be resolved. When you are using Entity Framework (Core) you can simply to this before the assembly scan.

builder.Services.AddDbContext()

Check out Microsofts documentation for more details.

With these two fixes your problem should be solved. However you should also keep in mind that by default .AsImplementedInterfaces() injects your Repositories as Singleton. You probably want to use Scoped or Transient instead. For further details look here, here or here.

René
  • 100
  • 1
  • 8