94

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?

dfmetro
  • 4,462
  • 8
  • 39
  • 65
  • You use constructor of `Lookup1Controller` with `IGenericRepository` *as parameter*: `public Lookup1Controller(IGenericRepository repository)`. Thus you expect that *MVC calls* the controller constructor `Lookup1Controller` with the corresponding parameter. Who should make `new GenericRepository>()`? Should one do this once or on every call of `Index` action? Thus you have choice between `services.AddTransient`, `services.AddScoped`, `services.AddSingleton` and `services.AddInstance`. – Oleg Nov 06 '15 at 12:53
  • It's a basic CRUD system. So there is Create. Update, Delete methods I omitted. Using AddScoped seems to be the standard way to do it See:http://wildermuth.com/2015/3/17/A_Look_at_ASP_NET_5_Part_3_-_EF7. If there is a better way please share. – dfmetro Nov 06 '15 at 13:33

2 Answers2

186

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)
{
    ...
}
Pang
  • 9,564
  • 146
  • 81
  • 122
Daniel J.G.
  • 34,266
  • 9
  • 112
  • 112
  • 2
    Thank you. Changing the one line of code works. Will wait to see if others give input if it's good practice or not for an ASP.net 5 app or provide alterantives – dfmetro Nov 06 '15 at 13:41
  • The way they are registered is the same as for other DI containers like [Ninject](http://stackoverflow.com/a/10243699/1836935) or [Unity](https://msdn.microsoft.com/en-us/library/ff660936(v=pandp.20).aspx#Anchor_0). Let's just see if anybody is aware of any gotchas with the built-in container in ASP .Net 5 – Daniel J.G. Nov 06 '15 at 14:08
  • 5
    This does not works for me. I got this error: fail: Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware[0] An unhandled exception has occurred: Unable to resolve service for type 'SqlExpress.Repository.Interfaces.IChatRepository' while attempting to activate 'SqlExpress.Helpers.LessonTagHelper'. System.InvalidOperationException: Unable to resolve service for type 'SqlExpress.Repository.Interfaces.IChatRepository' while attempting to activate ' SqlExpress.Helpers.LessonTagHelper'. at ... – Beetlejuice Jun 17 '16 at 20:43
  • 18
    In case anyone else has the same issue, I was pulling my hair out over this error when starting the app: Cannot instantiate implementation type Repo1[T]' for service type IRepo1[T]'.' - This was happening because my Repo implementation was marked as abstract, I was able to fix it by simply removing the abstract declaration. – jmdon Mar 24 '18 at 12:09
  • 1
    @jmdon Thank you! I was about to lose my mind... That's what I get for refactoring before coffee kicks in. For others who stumble upon this, make sure if you change your abstract to concrete that it has a public constructor, btw. My base repo that got a promotion to concrete generic had a protected ctor, which will also die on injection attempts. – Andy_Vulhop Sep 25 '19 at 16:32
  • 2
    If it helps anyone, I started to see this because Jetbrains Rider recommended I change my GenericRepository constructor from public to protected which I did without thinking.. – Fergal Moran Oct 12 '20 at 18:24
3

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

GetoX
  • 4,225
  • 2
  • 33
  • 30