2

I have an MVC app using a 3-tier pattern: DAL <-> Service <-> UI and I'm trying to add AutoFac for testing my MVC controllers as well as services.

A couple questions:

1) How do I use AutoFac to tell my service layer to use a specific context? Currently I'm using builder.Register(c => c.Resolve<IRepo>()).As<Repo>();but this exposes IRepo to the UI, which would allow them to bypass service layer validation and business rules.

2) The Repo and MockRepo are identical besides the constructor identifying which context to use. It's there because the UI doesn't have a reference to DAL and so I can't pass in an IDataContext. Is there a way to remove the redundant code/classes and still be able to use DI?

/* DAL */
public interface IDataContext
{
    T GetById<T>(int Id);
}

// Entity Framework context
public class EFContext : DbContext, IDataContext
{
    public EFContext() : base("MyConnectionString")
    {...}

    public T GetById<T>(int Id)
    {...}
}

// In-memory context for testing
public class MockContext : IDataContext
{
    public MockContext()
    {...}

    public T GetById<T>(int Id)
    {...}
}

/* Service */
public interface IRepo
{
    T GetById<T>(int id);
}

// Entity Framework Repo
public class Repo
{
    private IRepo _repo;
    public Repo()
    {
        _repo = new EFContext();
    }

    public T GetById<T>(int Id)
    {
        return _repo.GetById<T>(Id);
    }
}

// In-memory Repo
public class MockRepo : RepoBase
{
    private IRepo _repo;
    public MockRepo()
    {
        _repo = new MockContext();
    }

    public T GetById<T>(int Id)
    {
        return _repo.GetById<T>(Id);
    }
}

// Service to be used in the UI
public class StudentService
{
    public StudentService(IRepo repo)
    {
        _repo = repo;
    }

    public void ExpelStudent(int studentId)
    {
        var student = _repo.GetById(studentId);
        ...
        _repo.Save();
    }
}
user3953989
  • 1,844
  • 3
  • 25
  • 56

1 Answers1

1

Based on your comment, you would structure a project in the following fashion.

Solution Projects (Reference Chain):

  • Sample (Service, Domain, Data)
  • Service (Domain, Data)
  • Domain
  • Data (Domain)

So you associate your primary project to all other .dll's. The service layer doesn't care about anything except acting as a mediator to your data layer, thus the dependency. Then your data layer shouldn't care about anything except your domain model's.

Unless you do Clean Architecture / Onion Architecture where you loosely load the .dll rather than store the hard reference.

With the above structure you have a basic foundation, except if you do.

public class SampleContext : ISampleRepository
{
     public IEnumerable<SampleModel> GetAllSamples() => dbConnection.ExecuteQuery(query); // Dapper syntax, but you get the idea

     // Implement Dispose
}

public interface ISampleRepository : IDispose
{
     IEnumerable<SampleModel> GetAllSamples();
}

Then you build out your factory.

public class SampleFactory : ISampleFactory
{
     public ISampleRepository CreateSample() => new SampleContext();
}

public interface ISampleFactory
{
     ISampleRepository CreateSample();
}

The point, if you use a DI Container then the framework expects the DI Framework to be registered into the global file, with the associated configuration. That exists in your top level structure. So without the container your service layer could simply do.

public class SampleService
{
     public IEnumerable<SampleModel> GetAllSamplesFromDb() 
     {
          using(var context = new SampleFactory().CreateSample())
               return context.GetAllSamples();
     }
}

No Dependency Injection, but this requires the service layer to have some concrete understanding of which factory is associated. So every method would be creating a factory, which can be redundant.

So your DI Framework container would make the above service look like the following.

public class SampleService
{
     private ISampleFactory factory;

     public SampleService(ISampleFactory factory) => this.factory = factory;

     public IEnumerable<SampleModel> GetAllSamplesFromDb()
     {
          using(var context = factory.CreateSample())
              return context.GetAllSamples();
     {
}

So inside your configuration for your DI Container you would register something along these lines.

builder.RegisterType<SampleFactory>().As<ISampleFactory>();
var container = builder.Build();   

Now, I'm not super familiar with Autofac since I use Unity, Ninject, or default in Core. But you would tell the container in the top solution that, so you can reference like I displayed.

Hopefully this helps you. But you will need to compare my syntax slightly to ensure the proper Autofac syntax is used to register. But the structure and general concept should be the same.

Greg
  • 11,302
  • 2
  • 48
  • 79