Generic repository in ASP.NET Core without having a separate AddScoped line per table in Startup.cs?

GenericsDependency Injectionasp.net Coreasp.net Core-Mvc

Generics Problem Overview


I have a generic repository in my project. Consider the following controller snippet

public class Lookup1Controller : Controller
{
    readonly MyDbContext _db;

    public Lookup1Controller(MyDbContext dataContext)
    {
        _db = dataContext;
    }

    public async Task<IActionResult> Index()
    {

        IGenericRepository<Lookup1> _repository = new GenericRepository<Lookup1>(_db);
        var lookup1s = await _repository.SelectAll();

        return  View(lookup1s);
    }

I don't see the need to have my Database reference both in my Generic repository as well as each of my controllers.

I refactor it to:

public class Lookup1Controller : Controller
{
    private IGenericRepository<Lookup1> _repository;

    public Lookup1Controller(IGenericRepository<Lookup1> repository)
    {
        _repository = repository;
    }

    public async Task<IActionResult> Index()
    {
        var lookup1s = await _repository.SelectAll();

        return  View(lookup1s);
    }

}

which is much neater and ASP.NET 5 best practice from what I read. but I will get the following error if I access that controller route in my browser:

InvalidOperationException: Unable to resolve service for type 'MyProject.Data.IGenericRepository`1[MyProject.Models.Lookup1]' while attempting to activate 'MyProject.Controllers.Lookup1.

because of I haven't injected the GenericRepository to use the interface.

I add to my Startup.cs an AddScoped line for each and every of my tables in the ConfigureServices method

services.AddScoped<IGenericRepository<Lookup1>,GenericRepository<Lookup1>> ();
services.AddScoped<IGenericRepository<Lookup2>,GenericRepository<Lookup2>> ();
services.AddScoped<IGenericRepository<Lookup3>,GenericRepository<Lookup3>> ();
services.AddScoped<IGenericRepository<Lookup4>,GenericRepository<Lookup4>> ();
etc

so that my code runs without throwing an exception.

However my database has about 100 simple lookup tables. When I look at the above 100 lines of code it just doesn't look right.

It feels like copy and paste code. Each time I add a new table by adding a new model and controller with view my code will compile without giving me an error. But if I run the program and go to that view I could get the controller run error if I forgot to add the AddScoped line to my Startup.cs. Not really good for maintainability.

My question:

  1. Is it really best practice to have a services.AddScoped for each and every lookup table in the ConfigureServices method of Startup.cs?

  2. It is a generic repository so isn't there be a way to write those 100 copy and paste lines in one line?

  3. If not then what is the best practice way to do this using my code?

Generics Solutions


Solution 1 - Generics

Just use the non-generic registration overloads (the ones where you need to pass the 2 Type objects.) Then provide the open generic types of both your interface and the implementation:

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

In your controller, add a dependency for a repository of a specific type (a closed generic type):

public HomeController(IGenericRepository<Lookup1> repository)
{
    ...
}

Solution 2 - Generics

If you would like to register all IGenericRepository<> implementations in Assembly:

services.AddAllGenericTypes(typeof(IGenericRepository<>), new[] {typeof(MyDbContext).GetTypeInfo().Assembly});

With extension from: https://gist.github.com/GetoXs/5caf0d8cfe6faa8a855c3ccef7c5a541

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestiondfmetroView Question on Stackoverflow
Solution 1 - GenericsDaniel J.G.View Answer on Stackoverflow
Solution 2 - GenericsGetoXView Answer on Stackoverflow