4

I recently had the opportunity to create a new prism-based application. I had been using the 6.3 version for quite a while, but saw the prism 7 had moved out of prerelease and wanted to give it a try. I created a new prism application using the Prism Template pack and all worked as expected out of the box. I updated the view model like typically do in 6.3 to pass in the Container so I could resolve some objects that at a later time would provide information to the view, in 6.3 I would do the following:

public MainWindowViewModel(IRegionManager aRegionManager,
                           IUnityContainer aUnityContainer) : base()

Now in 7.1.0.431, I tried to do that same thing, but updated the interfaces to account for the new IOC abstraction.

public MainWindowViewModel(IRegionManager aRegionManager,
                           IContainerProvider aContainerProvider,
                           IContainerRegistry aContainerRegistry) : base()

This generates an exception from the ViewModelLocator.AutoWireViewModel for the IContainerX parameters.

System.Exception {Unity.Exceptions.ResolutionFailedException}

{"Resolution of the dependency failed, type = 'Sample.ViewModels.MainWindowViewModel', name = '(none)'.\nException occurred while: while resolving.\nException is: InvalidOperationException - The current type, Prism.Ioc.IContainerProvider, is an interface and cannot be constructed. Are you missing a type 

That acts like I am missing a reference, but I that type is being passed into the RegisterTypes call of the application, so all references should be found. Am I doing something wrong for the new 7.X release?

EDIT: Per @mnistic

Here is the code from the template pack provided App.xaml.cs where the IContainerRegistry is passed in.

  protected override void RegisterTypes(IContainerRegistry containerRegistry)
  {
      //containerRegistry is a valid instance here
  }

Update:

Digging in a little more, the IContainerRegistry that was passed into RegisterTypes lists all of the types/interfaces that are available at the time that method was called. It has an IUnityContainer instance registered. I selected Unity for the IOC when I created the project, but I assumed, maybe incorrectly, that the IContainerRegistry was hiding the clients from the actual implementation. If I update the ViewModel constructor to take in a object of IUnityContainer, it resolves properly.

public MainWindowViewModel(IRegionManager aRegionManager,
                           IUnityContainer aContainerProvider) : base()

Is this the desired behavior?

Pang
  • 9,564
  • 146
  • 81
  • 122
Rob Goodwin
  • 2,676
  • 2
  • 27
  • 47
  • You say that "the type is passed into the RegisterTypes" but we probably need to see that code. – mnistic Oct 19 '18 at 18:36
  • @mnistic Sorry about that, I left it out as it was provided by the generated project from the VS Template pack. I edited the question to include the data you requested. – Rob Goodwin Oct 19 '18 at 19:41

1 Answers1

6

Do not do this. You do not want to have the container outside your resolution root, it is horrible to test, hides otherwise obvious dependencies and comes with no benefit at all.

If you need services, inject them directly. If you need factories, inject Func<IProduct> or IHandcraftedFactory. If you need all registered types that implement ISomething, inject ISomething[] or IEnumerable<ISomething>.

Example (complex) factory with product:

public interface IFactory
{
     IProduct CreateProduct( int someParameter );
}

internal class DeviceFactory : IFactory
{
     public DeviceFactory( IService service )
     {
         _service = service;
     }

     public IProduct CreateProduct( int someParameter ) => new Device( someParameter, _someService );

     private readonly IService _service;
     private class Device : IProduct
     {
         public Device( int someParameter, IService aDependency )
         {
             // ...
         }
     }
}

If Device did not have someParameter, you'd skip IFactory and DeviceFactory and just inject an Func<IProduct>... Unity takes care that each Device receives its IService then.

Remember - the container is there to simplify things: it resolves the dependencies and creates instances and manages singletons. But if you had no container, everything would still work fine, as is the case in your unit tests. You just have to create all dependencies by hand.

Back to the topic at hand - the IContainerRegistry is just a short-lived, thin wrapper around IUnityContainer (in your case), so that the registration code looks somewhat similar in different apps using different containers. Prism tries to push you in the right direction (see above) by not registering the IContainerRegistry so that you use it where you're supposed to use it (during module initialization) and prevents you from using it elsewhere (by making it impossible to inject it).

Pang
  • 9,564
  • 146
  • 81
  • 122
Haukinger
  • 10,420
  • 2
  • 15
  • 28
  • 1
    Thanks for the response. So in the case where you want to resolve all instances of types that implement IFoo that have been registered by different modules at some time in the future. You would create an IFoo Factory and register it? How might the IFooFactory resolve all of the objects that have been registered without getting a handle to the Container? I think I am misunderstanding how to address the "Do Not do this" comment. – Rob Goodwin Oct 19 '18 at 20:24
  • 1
    I've tried to address your questions in my edit. As for "do not do this" - take that literally, just don't inject the container :-) – Haukinger Oct 19 '18 at 22:08
  • I believe I understand your statements now. It is not something that I do often, only in the cases where after initialization I was wanting to get all registered objects of time type. But I think I can eliminate that based on you answer and edit. Thanks for taking the time! – Rob Goodwin Oct 19 '18 at 22:15
  • "Do not do this" resumes the subject pretty well, but the explanation that follows is a great addition! Nice reading – Joel Aug 06 '19 at 15:48