Manually Invoke Dependency Injection (DI)

Topics: Prism v4 - Silverlight 4
Mar 24, 2011 at 4:09 PM
Edited Mar 24, 2011 at 4:32 PM

I have a UserControl (UC1) that contains a Grid with 2 columns and 2 rows.  Each cell in the Grid contains a different type of UserControl (UC2,UC3,UC4,UC5).  I use the RegionManager.RequestNavigate() method to load UC1, and all of its imports are satisfied via MEF.

However, UC2-UC5 don't have their imports satisfied.  Its seems odd that I would need to dynamically add these controls at runtime via RegionManager.RequestNavigate() in order for them all to have their imports satisfied, as I know exactly what content (UC) belongs to each cell at design time.

So my question is, can I invoke DI at runtime without having to use RegionManager.RequestNavigate() so that UC2-UC5 have their imports satisfied?

My layout seems logical to me, but perhaps there's a different (better) way of laying this out with the realm of Prism?

Thanks much!

Mar 28, 2011 at 2:03 PM
Edited Mar 30, 2011 at 3:40 PM

-

Mar 28, 2011 at 3:53 PM

Hi,

One possible way to achieve your requirement is to use the ServiceLocator to resolve your imports. Since you are using MEF and you already export all the user controls, the container will be able to resolve instances of that classes.

So your UC1 code could have something like this:

var myUserControl = ServiceLocator.Current.GetInstance<IUC2>();

Additionally, you can read Chapter 3: Managing Dependencies Between Components. Inside you will find key concepts when using Dependency Injection containers within Prism (Unity and MEF).

Let me know if this information helps you.

Thanks,

Miguel Bronzovic
http://blogs.southworks.net/mbronzovic

 

Mar 28, 2011 at 6:59 PM

That is exactly what I was after, thanks much!

Mar 28, 2011 at 10:13 PM

So the ServiceLocator worked just fine.  However, drawing on the UC2 example, now 2 issues are occuring as a result.

UC2 is marked with the [Export] attribute.

UC2 contains a VM that is being imported via [Import] attribute.

1.)  I noticed that now the constructor for UC2 is invoked twice.  This also happens in a few of the PRISM samples.

2.)  Also, UC2 contains a DataGrid whose ItemsList binds to a property of the VM (Projects).  However, the DataGrid wants to bind to Projects before MEF has imported the VM, so DataGrid is actually looking to its container's DataContext to find Projects.

Any thoughts here?

Thanks again!

Developer
Mar 29, 2011 at 2:26 PM

Hi,

Regarding your first question, it would be helpful if you could provide us with more information of the scenario in which this is happening, and also point us where you have found this behavior in the Prism QuickStarts and/or Reference Implementations, so that we can help you troubleshoot this undesired behavior.

As for your second one, you could specify the import in the constructor, using the ImportingConstructor attribute (more information here). That way you would guarantee that the export will be present in your view before it is initialized, thus avoiding what you've mentioned.

I hope you find this helpful.

Guido Leandro Maliandi
http://blogs.southworks.net/gmaliandi

Mar 29, 2011 at 4:39 PM

Guido, thanks your continued correspondence.  Unfortunately, applying the ImportingConstructor attribute to the constructor of the view doesn't work.  The view needs a public default constructor.  If it does not have this, the control will not render properly at design time, and I will also receive a XamlParseException (No matching constructor found on type...).

I tried overloading the constructor by providing the public default constructor and a constructor that takes the VM as an argument, like so:

public UC2() { InitializeComponent(); }

[ImportingConstructor] public UC2(SomeViewModel vm) { InitializeComponent(); DataContext = vm; }

When I run, I am back at square one; both constructors are called and the DataGrid is looking for the Projects property on the DataContext belonging to its container's DataContext.

 

 

Developer
Mar 29, 2011 at 6:16 PM
Edited Mar 29, 2011 at 6:16 PM

Hi,

That's correct, when you instantiate your view in XAML you must have a default constructor. If you need to have it instantiated in the XAML in order to achieve blendability, you could again use the ServiceLocator to obtain an instance of your ViewModel.

Take into account that the constructor of your view is probably being called twice because you're instantiating your view two times: one in the XAML and one in your other component through the use of the ServiceLocator.

For more information on design time support (blendability), you could check the following threads:

I hope you find this helpful.

Guido Leandro Maliandi
http://blogs.southworks.net/gmaliandi

http://compositewpf.codeplex.com/discussions/237265 to
Mar 29, 2011 at 6:49 PM

Hi Guido, yes, what you say is correct.  However, I believe there have been a couple things lost in translation, so as a quick reminder, here is what i'm trying to accomplish:

In the following layout (much code omitted for brevity sake), UC1 is being added to a region via IRegionManager.RegisterViewWithRegion()

    <UserControl Name="UC1">
        <StackPanel>
            <UC2></UC2>
            <UC3></UC3>
            <UC4></UC4>
            <UC5></UC5>
        </StackPanel>
    </UserControl>

All imports are satisfied properly on UC1.  However, none of the imports are satisfied on UC2-UC5.  UC2-UC5 are all different user controls that I want displayed at design time, therefore I am statically laying them out as opposed to relying on Region Management to add them.

To satisfy their imports, I am relying on a call to ServiceLocator.Current.GetInstance<T>() (4 times, for UC2-UC5) within the Loaded Event of UC1.

Doing this satisfies the imports, however, by the time that happens, the controls that depend on VM's in UC2-UC5 look to the DataContext in UC1.  This is due to the imports being resolved too late.

Following your advice as a test, I have placed a call ServiceLocator.Current.GetInstance<T>() within UC2's default public constructor (before call to InitializeComponent()), and this seems to have performed the trick (the controls that rely on databinding in UC2's XAML can find what they're looking for on the VM that is properly set as UC2's DataContext).

But this approach will force me to have calls to ServiceLocator.Current.GetInstance<T>() scattered throughout my code (in Views and ViewModels).

Surely there must be a better way to accomplish what I'm after?  Perhaps I should just allow UC2-UC5 to be dynamically to the StackPanel in UC1 via a Region?  Only problem with this, of course, as that I can't see UC2-UC5 displayed in UC1 at design time.

Please stick with me here, I think we're almost there.  :)

 

Developer
Mar 30, 2011 at 3:04 PM

From my understanding, the approaches you've mentioned are the most common way to deal with that scenario. When you register a view through the RegisterViewWithRegion method, it gets added to a region when it's created, and in order to do so, the view is internally resolved using the Service Locator.

An alternative possibility, as a workaround to avoid having calls to ServiceLocator.Current.GetInstance<T>() scattered throughout your code, while keeping the benefits of design-time support for your views, would be to make your views implement IPartImportsSatisfiedNotification and set the ViewModel to its DataContext and call InitializeComponent from within the ImportsSatisfied method (instead of calling the latter from the constructor). That way, you would guarantee that you would only load the view into the UI when the ViewModel import has been satisfied.

I hope you find this helpful.

Guido Leandro Maliandi
http://blogs.southworks.net/gmaliandi

Mar 30, 2011 at 3:43 PM

Thanks, Guido.  I'll give that a shot.