Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Register a type with primitive-arguments constructor?

I have a class that has in its constructor some primitive type arguments, such as string etc.

How should I register the type with unity container?

public LoginManager(
  IRegionManager regionManager, 
  IEventAggregator eventAggregator, 
  string mainRegionName, 
  Uri login, 
  Uri target)
  {
    this.regionManager = regionManager;
    this.eventAggregator = eventAggregator;
    this.mainRegionName = mainRegionName;
    this.login = login;
    this.target = target;
  }
}

Update:
Remeber that the IRegionManager and the IEventAggregator are known types to the Prism UnityBootstrapper which is the container wrapper in my case. Do I have to re-register them?? I want to keep the type-registration as simple as possible.

Would this be considered a bad habit? Any better alternatives?

like image 418
Shimmy Weitzhandler Avatar asked May 15 '11 17:05

Shimmy Weitzhandler


Video Answer


1 Answers

Try to prevent to have a class design that has primitive or hard to resolve types in the constructor. As you have already seen from Tavares' answer, you're configuration gets very brittle (update: Tavares seems to have removed his answer for reasons that are not clear to me). You loose compile time support, and every change to that constructor would make you change your DI configuration.

There are multiple ways to change your design to prevent this. Which one is applicable for you is up to you, but here are a few ideas:

Option 1: Use an immutable configuration DTO:

private sealed class LoginManagerConfiguration
{
    public Uri Login { get; private set; }
    public Uri Target { get; private set; }
    public string MainRegionName { get; private set; }

    public LoginManagerConfiguration(Uri login, Uri target, string mainRegionName)
    {
        this.Login = login;
        this.Target = target;
        this.MainRegionName = mainRegionName;
    }
}

Now you can let your LoginManager take a dependency on LoginManagerConfiguration:

public LoginManager(IRegionManager regionManager,
    IEventAggregator eventAggregator,
    LoginManagerConfiguration configuration)
{
    ...
}

The LoginManagerConfiguration can simply be registered like this:

container.RegisterInstance<LoginManagerConfiguration>(
    new LoginManagerConfiguration(
        login: new Uri("Login"),
        target: new Uri("Target"),
        mainRegionName: ConfigurationManager.AppSettings["MainRegion"]));

It might be tempting to specify an application-wide configuration object instead of this type-specific DTO, but that's a trap. Such application-wide configuration object is the configuration equivalent of the Service Locator anti-pattern. It becomes unclear what configuration values a type requires, and makes classes harder to test.

Option 2: Derive from that class

Another option is to derive from that class, just for the purpose of DI configuration. This is especially useful when you can't change the class signature (i.e. when it's a third-party component):

private sealed class DILoginManager : LoginManager
{
    DILoginManager(IRegionManager regionManager,
        IEventAggregator eventAggregator)
        : base(regionManager, eventAggregator,
            ConfigurationManager.AppSettings["MainRegion"],
            new Uri("Login"),
            new Uri("Target"))
    {
        ...
    }
}

Define this class close to the composition root of your application. This class becomes an implementation detail of your DI configuration. Registration of your type will now be very simple:

container.RegisterType<ILoginManager, DILoginManager>();

Be very careful though with calls that lazy load configuration values, such as ConfigurationManager.AppSettings["MainRegion"]. This could easily lead to situations where configuration errors are not caught during application startup, which really is preferable.

Option 3: Use a factory delegate

The last option I would like to present is a factory. This will look much like Traveses' answer, but is more type-safe:

var mainRegion = ConfigurationManager.AppSettings["MainRegion"];

container.Register<ILoginManager>(new InjectionFactory(c =>
{
    return new LoginManager(
        c.Resolve<IRegionManager>(),
        c.Resolve<IEventAggregator>(),
        ConfigurationManager.AppSettings["MainRegion"],
        new Uri("Login"),
        new Uri("Target"));
}));
like image 50
Steven Avatar answered Oct 02 '22 13:10

Steven