Stop Autofac from filing IEnumerable<>

Viewed 164

I'm looking for a way to stop Autofac from automatically filling an IEnumerable<> in my constructor.

For example, I have a class called School with the following constructors

public School(string name) {...}
public School(string name, IEnumerable<IStudent> students) {...}

You can use the first constructor to create the School object then use a AddStudent method to add the students to the school.

You can use the second constructor to create a School and pass in a collection of students to be added when the School object is constructed.

If I create Func<string, ISchool> schoolFactory, then call schoolFactory.Invoke("Test"), Autofac will use the second constructor and will automatically create a new Student object.

The IStudent interface and Student classes are registered with Autofac and the Student class has a default constructor. The default constructor is there because it's used in our UI when a user adds a new Student. The new Student is created with a blank name and the user can enter the name after the Student object has been created.

I tried the following ...

  1. Remove the default constructor from Student. This works but it means I have to use a different constructor and pass in string.Empty for the student name. The main issue with this is that we have some other generic code for creating new objects that requires our classes to have default constructors. It's also not a solution for all of our classes.

  2. Change IEnumerable<IStudent> to List<IStudent>. This works and it's what I started doing in our code, but it then forces the use of List<> instead of the generic IEnumerable<>. It's just less flexible.

  3. Create Func<string, IEnumerable<IStudent>, ISchool> schoolFactory and use schoolFactory.Invoke("Test", Enumerable.Empty<IStudent>). This also works. It's more code, but I guess it's more explicit.

Is there a generic way I can stop Autofac from automatically filling an IEnumerable?

1 Answers

Nice question. The short answer is: I think it's not possible. I'm not really a fan of saying no when it comes to software engineering, so here's a longer explanation:

What you see here is a so called "implicit relationship type" in Autofac. Specifically, the one that supports resolving collections. These are implemented in Autofac via registration sources. A registration source is a way to handle registrations dynamically based on given criteria (in this case, the collection aspect of the type). You can check these sources in your container in the container.ComponentRegistry.Sources property; the last one should be the one responsible for this. So in order to disable this functionality, you must be able to somehow remove this registration source from your container, but here's the catch: this property is of type IEnumerable<IRegistrationSource>, which means that it's readonly and is not meant to be meddled with (even though it's an array at runtime).

But if you are interested in other solutions to the problem, I can recommend different approaches:

  1. You say you have a UI and the default ctor for Student is there so that the user interface can handle this type. Sounds like you are letting your user interface interfere with the design of your object model. I suggest you try implementing some architectural patterns designed to separate UI and BLL, like MVVM or MVC. In case of MVVM for example, you might have a StudentViewModel for the UI which can have a default ctor, and you can keep the Student clean with the parameterized ctor only.
  2. In case this is not option (as you say it is not an option for all your classes), you can try to specify the constructor when you register your type (see the docs). In this case, you can manually register the non-collection constructor and Autofac uses that:

    builder.RegisterType<School>().UsingConstructor(typeof(string));
    

EDIT:

Or, if you are really keen on disabling the whole feature altogether, I guess you can go hardcore and open-source the s**t out of it :) Create your own fork, find the src/Autofac/ContainerBuilder.cs file and in the void RegisterDefaultAdapters(IComponentRegistry componentRegistry) method, you can see the default registration sources being added. Just delete the line that says componentRegistry.AddRegistrationSource(new CollectionRegistrationSource()); (line 247 on the develop branch in the file), then build your own and use that. Don't forget the modify the unit tests ;)

Related