.NET Core (MYSQL) Generic Repository AutoWiring using Autofac

.net-core autofac c# entity-framework-core ioc-container

Question

What I'm aiming to achieve is the following. I'm trying to setup a new solution using: MySQL, .NET Core, Autofac, EF Core... making use of the (generic) repository pattern.

Ultimately I'll be jumping onto a project with an existing db, thus my aim is to somehow make use of (t4) templates & EF to generate some of the models, then it "should" (famous last words) be as easy as making a repo for each model I need to interact with. (Each repo will just be a small lightweight class inherits the base)

Startup.cs

public class Startup
{
    public Startup(IHostingEnvironment env)
    {
        var builder = new ConfigurationBuilder()
            .SetBasePath(env.ContentRootPath)
            .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
            .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
            .AddEnvironmentVariables();
        this.Configuration = builder.Build();
    }

    public IConfigurationRoot Configuration { get; private set; }

    // This method gets called by the runtime. 
    // Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc();
        services.
            .AddDbContext<MyContext>(o => o.UseMySQL(
          Configuration.GetConnectionString("MyConnection")));
    }

    public void ConfigureContainer(ContainerBuilder builder)
    {               
        builder.RegisterGeneric(typeof(Repository<>))
            .As(typeof(IRepository<>))
            .InstancePerLifetimeScope();

       // the below works (before adding the repos)
       builder.RegisterAssemblyTypes(AppDomain.CurrentDomain.GetAssemblies())
           .AssignableTo<IService>()
           .AsImplementedInterfaces()
           .InstancePerLifetimeScope();
    }
}

(Generic) Repository.cs

public abstract class Repository<T> : IRepository<T>
    where T : class
{
    private readonly MyContext _context;

    protected Repository(MyContext context)
    {
        _context = context;
    }

    public virtual string Add(T entity)
    {
        if (ValidateAdd(ref entity, out var message))
        {
            _context.Set<T>().Add(entity);
        }

        return message;
    }

    public virtual bool ValidateAdd(ref T entity, out string message)
    {
        message = default(string); 
        return true;
    }

}

Implementation if a Repository:

FooRepository.cs

// public interface IFooRepository: IRepository<Foo>

public class FooRepository: Repository<Foo>, IFooRepository
{
    public FooRepository(MyContext context) : base(context)
    {
    }

    public override bool ValidateAdd(ref Foo entity, out string message)
    {
        // just a hook for pre-insert stuff
        message = "All foos shall fail add";
        return false;
    }
}

Then the usage within a service or controller, or what have you.

FooService.cs

public class FooService: IFooService
{
    private readonly IFooRepository _repository;

    public FooService(IFooRepository repository)
    {
        _repository = repository;
    }

    public void DoSomethingThenAdd()
    {
       // some other business logic specific to this service
        _repository.Add(new Foo()
        {
            Id = 1,
            LastName = "Bar",
            Name = "Foo"
        });
    }
}

Problem:

How do I go about wiring all of this up... I've struggled to find proper documentation on MySQL + Ef, and I'm kind of going on the gut feel that that portion is "working". But as you can see in the error log below, that my registration of the repositories are messed up.

Error:

An error occurred during the activation of a particular registration.

See the inner exception for details.

Registration: Activator = FooService (ReflectionActivator), Services = [MyApp.Services.Interfaces.IFooService, MyApp.Services.Interfaces.IService], Lifetime = Autofac.Core.Lifetime.CurrentScopeLifetime, Sharing = Shared, Ownership = OwnedByLifetimeScope

---> None of the constructors found with 'Autofac.Core.Activators.Reflection.DefaultConstructorFinder' on type 'MyApp.Services.FooService' can be invoked with the available services and parameters:

Cannot resolve parameter 'MyApp.Data.Interfaces.IFooRepository repository' of constructor 'Void .ctor(MyApp.Data.Interfaces.IFooRepository)'. (See inner exception for details.)

--> Autofac.Core.DependencyResolutionException: None of the constructors found with 'Autofac.Core.Activators.Reflection.DefaultConstructorFinder' on type 'MyApp.Services.FooService' can be invoked with the available services and parameters:

Cannot resolve parameter 'MyApp.Data.Interfaces.IFooRepository repository' of constructor 'Void .ctor(MyApp.Data.Interfaces.IFooRepository)'.

1
4
12/13/2017 2:58:44 PM

Accepted Answer

The following line will register Repository<Foo> as IRepository<Foo> in Autofac :

builder.RegisterGeneric(typeof(Repository<>))
       .As(typeof(IRepository<>))
       .InstancePerLifetimeScope();

but a IRepository<Foo> is not a IFooRepository and FooService needs a IFooRepository. That's why Autofac fail with the following error message :

Cannot resolve parameter 'MyApp.Data.Interfaces.IFooRepository repository' of constructor 'Void .ctor(MyApp.Data.Interfaces.IFooRepository)'.

If you want to keep your FooRepository and IFooRepository you will have to register them :

builder.RegisterType<FooRepository>()
       .As<IFooRepository>()

Another solution would be to register all implementation of IRepository<>

builder.RegisterAssemblyTypes(AppDomain.CurrentDomain.GetAssemblies())
       .AsClosedTypesOf(typeof(IRepository<>))

and FooService should rely on Irepository<Foo> instead of IFooRepository

public class FooService: IFooService
{
    private readonly IRepository<Foo> _repository;

    public FooService(IRepository<Foo> repository)
    {
        this._repository = repository;
    }

    // ...
}

By the way be carefull with assembly scanning when you use IIS : Assembly scanning - IIS Hosted application

1
7/27/2017 1:56:27 PM


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow