2

I have 2 controllers, both require an object of the MySettings type which represents a set of settings. Each controller needs its custom set of settings. In order to do that at the registration of the module I create 2 settings objects by hand and put them both to the container. Question is how can I specify that each controller is supposed to be injected with its own pre-defined custom-initialized instance of the MySettings type?

UPDATE:

Now there is an ugly workaround in place that basically renders Autofac useless as all resolving is done by hand:

public class MyModule : Module {
    protected override void Load(ContainerBuilder builder) {
        builder.Register(context => {
            var productControllerSettings = new MyListSettings(
                pageSize: 20,
                orderBy: "Name",
                orderDirection: OrderDirection.Ascending
            );
            // and hell of other parameters that I need to resove
            // by hands by doing context.Resolve<...> for each of them
            var productController = new ProductController(
                productControllerSettings
                /*, the reset of parameters */
            );
            return productController;
        });

        builder.Register(context => {
            var userControllerSettings = new MyListSettings {
                pageSize: 20,
                orderBy: "LastName",
                orderDirection: OrderDirection.Ascending
            };
            var userController = new UserController(
                userControllerSettings
                /*, the rest of parameters resolved by calling context.Resolve<> by hands */
            );
            return userController;
        });
    }
}

There must be a better way of doing it I hope.

UPDATE2:

Another way of getting around this shortfall is to make 2 new classes of settings based on MySettings class. This way each instance uniquely corresponds to a class and Autofac can easily resolve it. I don't want to do just for the sake of making Autofac work.

Trident D'Gao
  • 18,973
  • 19
  • 95
  • 159
  • Can you post some code? How your current registration looks like? And how based on what do you pair your settings and controller? So how do you know which setting should go in which controller? – nemesv Sep 22 '13 at 11:20
  • @nemesv, just updated the question check it out – Trident D'Gao Sep 22 '13 at 15:07
  • Why not just have the `ProductController` set up its own settings? That seems simplest. DI containers are better suited to managing services which have behavior and could have dependencies of their own. `MyListSettings` is not a service, it's just data. – default.kramer Sep 23 '13 at 13:54
  • @default.kramer for better testing and maintenance, this is what DI is for in general, isn't it? Speaking of services, you would have exactly the same problem if you have 2 implementations of the same interface and you want to inject one to the first controller and another one to the second, so basically it is no different. By the way what do you think the difference is between data and services? As far as C# goes they both are classes, so what makes them different? – Trident D'Gao Sep 23 '13 at 14:18
  • Like I said, services have behavior and may have dependencies on other services. Data never has any dependencies; the class is just a bunch of properties. The benefit of a DI container is managing dependencies, which data never has. So I just instantiate data the old-fashioned way. It's simpler, try it out. If you need testability, just make the property publicly settable and your tests can override the default settings. (Now if you need to save and load this data, that is the job of a service) – default.kramer Sep 23 '13 at 22:00
  • I don't know what you are talking about, settings are dependencies just like any other service, by your logic the constructor of SqlConnection doesn't need to be given a connection string, because it can be hardcoded the old-fashioned way inside of the connection itself. It doesn't make sense. If you look closely at the properties they just a bunch of get methods, each of which can return either a constant value or result of some computation. The exact implementation can differ, I want this to be managed outside of the controller, how difficult is that? – Trident D'Gao Sep 24 '13 at 01:04

2 Answers2

9

The easiest solution would be to use the Named registration feature of Autofac.

So register your MyControllerSettings instances with a name and use this name as a parameter when registering your controllers:

var productControllerSettings = new MyListSettings(
    pageSize: 20,
    orderBy: "Name",
    orderDirection: OrderDirection.Ascending);

builder.RegisterInstance(productControllerSettings)
       .Named<MyListSettings>("productControllerSettings");

var userControllerSettings = new MyListSettings(
        pageSize: 20,
        orderBy: "LastName",
        orderDirection: OrderDirection.Ascending);
builder.RegisterInstance(userControllerSettings)
       .Named<MyListSettings>("userControllerSettings");

builder.RegisterType<ProductController>()
    .WithParameter(
        ResolvedParameter.ForNamed<MyListSettings>("productControllerSettings"));

builder.RegisterType<UserController>()
    .WithParameter(
        ResolvedParameter.ForNamed<MyListSettings>("userControllerSettings"));

However this solution requires to list all the controller - named parameter pairs during the registration which could be error-prone.

A different approach would be that you don't directly depend on the MyListSettings in your controllers but a on "MyListSettings" provider. You can have this provider as a concrete class or you can use the Autofac's built in relation types for this like IIndex to create a lightweight provider.

So your controllers could look like this:

public class ProductController
{
    private readonly MyListSettings productControllerSettings;

    public ProductController(Func<Type, MyListSettings> settingsProvider)
    {
        this.productControllerSettings = settingsProvider(GetType());
    }
}

public class UserController
{
    private readonly MyListSettings userControllerSettings;

    public UserController(Func<Type, MyListSettings> settingsProvider)
    {
        this.userControllerSettings = settingsProvider(GetType());
    }
}

And the corresponding registration:

var productControllerSettings = new MyListSettings(
    pageSize: 25,
    orderBy: "Name",
    orderDirection: OrderDirection.Ascending);

builder.RegisterInstance(productControllerSettings)
       .Keyed<MyListSettings>(typeof (UserController1));

var userControllerSettings = new MyListSettings(
    pageSize: 20,
    orderBy: "LastName",
    orderDirection: OrderDirection.Ascending);

builder.RegisterInstance(userControllerSettings)
       .Keyed<MyListSettings>(typeof (ProductController1));

//register the provider func
builder.Register<Func<Type, MyListSettings>>(
    c => (t) => c.Resolve<IIndex<Type, MyListSettings>>()[t]);

builder.RegisterType<ProductController>();
builder.RegisterType<UserController>();

You should note that you can use anything for the Keyed not just the a Type anything what you can for identified which controller should get which settings so strings, enums, etc.

nemesv
  • 138,284
  • 16
  • 416
  • 359
  • This smells so bad. So many ways to shoot oneself's foot, ugh! I hate IoC frameworks. What if I need to inject 2 instances of the MySetting class to the same controller? – Trident D'Gao Sep 22 '13 at 17:55
  • 3
    Then you can still use the named registrations but you need to identify your instances differently for example by the name of custructor parameter. By the way this is also a smell that you want to have two of the same type resolved from the container... IoC containers are for registering and resolving services and not for building up data... However I will update my answer with a different approach – nemesv Sep 22 '13 at 18:49
  • @nemsev, excuse me, since when injecting settings into a class started to smell? The fact that Autofac isn't capable of handling such a common scenario is a shame. Forget about settings, suppose I have 2 implementations of the same interface where one has tracing capabilities while the other one does not. I want to be in control where each one is injected. This is no different from settings yet a shortfall of Autofac too. – Trident D'Gao Sep 22 '13 at 21:17
  • Just to remind you what you can see in everyday life: SqlConnection takes a connection string, XmlWriter takes XmlWriterSettings these are just a couple of examples when you inject the settings. It is no different from my case. – Trident D'Gao Sep 22 '13 at 22:19
  • On your last update, the services locator is an anti-pattern you should avoid it: http://en.wikipedia.org/wiki/Service_locator_pattern – Trident D'Gao Sep 23 '13 at 15:18
  • @bonomo yes it could be viewed as a service locator, but a very specific and narrow one which does not do any harm but could make the container registration easier if you don't like the pure `Named` , `Keyed` feature. Still for this problem what you have Autofac's solution is the `Named` , `Keyed` registration, if you don't like it then don't use it... – nemesv Sep 23 '13 at 15:24
  • However if you must use Autofac you can create your own implementation of the `IRegistrationSource` interface and register it with `builder.RegisterSource(new MyRegistrationSoruce());` which is the ultimate hammer in Autofac. Where you can implement any kind of registration convention, or whatever makes sense for you to ease your registration concerns. – nemesv Sep 23 '13 at 15:31
  • @nemsev, Thank you for the idea with the named parameters, I think it's OK in the Autofac fan-club to use them, but to me they seem too troublesome and non-maintainable: magic-string-key-bound, vulnerable to refactoring, no static checking whatsoever yet no way to redefine at runtime... It's a sure recipe for a headache – Trident D'Gao Sep 23 '13 at 15:33
  • @bonomo OK maybe my example was not the best if the strings are bothering you you can use the `Keyed` registration where you can have any object as a key so an Enum, a Type, whatever you want. See it in the doc: https://code.google.com/p/autofac/wiki/TypedNamedAndKeyedServices#By_Key – nemesv Sep 23 '13 at 15:34
0

Another alternative would be to use AutoFac's Metadata features.

public UserController(IEnumerable<Meta<ISettings>> allSettings)
{
    this.settings = allSettings.Where(s => ....);
}

This would allow you to fetch multiple settings objects and select the ones you want based on metadata supplied with each one.

Ian Mercer
  • 38,490
  • 8
  • 97
  • 133
  • 1
    So to be clear, you want to inject one of two settings instances (from the SAME class) and you don't want to use a name or any other metadata to select between them on the receiving end and you are upset that Autofac can't read your mind and inject the right one. Good luck with that! – Ian Mercer Sep 22 '13 at 23:57
  • 2
    I am upset that it is such a piece of crap that there is no way to tell it to use the right instance to inject without making my code dependent on Autofac. A good framework revolves around my needs not vice-versa. A good framework is nonintrusive and unobtrusive. So ideally I want my code to know nothing about existence of that IoC container, so that it is possible to throw it away or switch to another one without messing with the core logic. Do you see what I am saying? I am also upset that I have to spend my time fighting it instead of working on a business problem. – Trident D'Gao Sep 23 '13 at 14:31