IoC - Autofac and register multiple services with same generic Interface

601 views Asked by At

I have a service layer with the following classes / intefaces (IServices is an empty interface):

 public interface IForoChanService<T> : IService
{
    T GetById(int id);

    IQueryable SearchBy(Expression<Func<T, bool>> predicate);

    IEnumerable<T> GetAll();

    int Create(T entity);

    void CreateMany(IEnumerable<T> entities);

    void Delete(T entity);

    void Delete(int id);

    void DeleteMany(IEnumerable<T> entities);

    void Update(T entity);

}

Then I have an abstract class implementing that signature generically:

    public abstract class ForoChanServiceBase<T> : IForoChanService<T> where T : EntityBase
{
    public T GetById(int id)
     {
         return ChanDbContext.Set<T>().Find(id);
     }
     //all the other methods as well
} 

And finally the concrete classes:

    public class CategoryService : ForoChanServiceBase<Category>
{

}

I am trying to use AutoFac to inject those services (many: category, client, etc) in the constructor: I have a base controller:

 public abstract class ForoChanBaseController: Controller
{

    protected ForoChanServiceBase<Post> PostService { get; private set; }
    protected ForoChanServiceBase<Comment> CommentService { get; private set; }
    protected ForoChanServiceBase<Category> CategoryService { get; private set; }

    protected ForoChanBaseController()
    {

    }

    protected ForoChanBaseController(
        ForoChanServiceBase<Post> postService,
        ForoChanServiceBase<Comment> commentService,
        ForoChanServiceBase<Category> categoryService)
    {
        PostService = postService;
        CommentService = commentService;
        CategoryService = categoryService;
    }

}

And I am setting autofac like this:

        public static void ConfigureIoc()
    {
        var builder = new ContainerBuilder();

        builder.RegisterType<CommentService>().As<ForoChanServiceBase<Comment>>().InstancePerRequest();
        builder.RegisterType<CategoryService>().As<ForoChanServiceBase<Category>>().InstancePerRequest();
        builder.RegisterType<PostService>().As<ForoChanServiceBase<Post>>().InstancePerRequest();

        builder.Build();
    }

The problem is that I am having is when in the controller I need to use any service method that guy (CategoryService) is null:

        public ActionResult Create()
    {
        var p = new PostFormNewVm
        {
            Categories = CategoryService.GetAll().Select(c => new CategoryVm { Id = c.Id, Title = c.Title })
        };

        return View(p);
    }

Besides this error do am I doing something wrong? I can't make it work.

I tried with the inteface as well.

1

There are 1 answers

1
Steven On

Your ForoChanBaseController contains multiple constructors, which is an anti-pattern. Because of the existence of this default constructor, there is a derived class that uses this constructor instead of the overloaded one, which is causing the dependencies to be null.

Although this default ctor is the cause for you to post the question here, there are more -less obvious problems- with your design:

  • Although you can remove the default constructor, prevent having this base class at all. Bases classes are often big Single Responsibility Principle violations and are either used to stuff in cross-cutting concerns or other utility functions. By having this base class derived types are forced to require dependencies that they might not even use at all. This complicates your code and complicates testing.
  • Since you have the IForoChanService<T> interface, consumers should not depend on the ForoChanServiceBase base class. As a matter of fact, the same advise as before holds: this base class should probably not exist at all.
  • The IForoChanService<T> is big generic tool box of methods where consumers only use one or two of those methods at a time. This means you are violating the Interface Segregation Principle.
  • IForoChanService<T> implementations are likely to violate the Liskov Substitution Principle, since there will be implementations that don't allow entities to be deleted. This will cause call to Delete to fail with an exception, instead of the Delete to not exist for that entity.