Recompostion problem

Dec 17, 2009 at 4:26 PM

I'm strugling with Recomposition problem, quickly wrote a test case below, could someone point out what I am missing.

The 1st test passed, but on 2nd one I got:  exports prevented by non-recomposable import problem.

using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using NUnit.Framework;

namespace MefDemo.Test.Unit
{
    
    [TestFixture]
    public class MEFTest
    {
        private CompositionContainer _mefContainer;

        [SetUp]
        public void Setup()
        {
            var catalog = new AssemblyCatalog(System.Reflection.Assembly.GetExecutingAssembly());
            _mefContainer = new CompositionContainer(catalog);
        }

        [Test]
        public void should_compose_one_instance_of_view()
        {
            var view = new MyView("test details");
            _mefContainer.ComposeParts(view);

            Assert.IsNotNull(view.ViewModel);
            Assert.AreEqual("test details", view.ViewModel.Details);
        }      
        
        [Test]
        public void should_compose_multiple_instances_of_view()
        {
            var view = new MyView("test details");
            _mefContainer.ComposeParts(view);
            
            var view2 = new MyView("test details");
            _mefContainer.ComposeParts(view2);

            Assert.IsNotNull(view2.ViewModel);
            Assert.AreEqual("test details", view2.ViewModel.Details);
        }
    }

    
    public class MyView
    {
        [Export("Details")]
        public string Details { get; set; }

        [Import]
        public MyViewModel ViewModel { get; set; }


        public MyView(string details)
        {
            Details = details;
            
        }
    }

    [Export]
    public class MyViewModel
    {
        public string Details { get; set; }

        [ImportingConstructor]
        public MyViewModel([Import("Details", AllowRecomposition=true)]string details)
        {
            Details = details;
        }
    }
}
Dec 17, 2009 at 9:03 PM

Hi Frank

Recomposition does not work on constructor parameters. Recomposition holds on to an instance of the part in order to recompose it. A ctor parameter is not a member of the class. If you change it to a property, it should work.

You can also check this post for "an approach" to doing recomposition in a constructor.

Thanks

Glenn

 

Dec 18, 2009 at 3:20 PM

Thanks Glenn, I changed ctor parm to property, but still got error in test case 2:

More than one export was found that matches the constraint '.....'

Should I do something for export to enable multi-instance? Thanks.

Dec 21, 2009 at 8:17 PM

Frank, the first problem is your ViewModel is being shared across views. You should decorate your VM with a [CreationPolicy.NonShared] to ensure that each view gets it's own instance.

Second, there are multiple details being exported as each view exports details and there are 2 views added to the container. Thus the ViewModel cannot import a single detail, it must import a collection and be decorated with ImportMany such as below.

    [Export]
    [PartCreationPolicy(CreationPolicy.NonShared)]
    public class MyViewModel
    {
        [ImportMany("Details", AllowRecomposition=true)]
        public IEnumerable<string> Details {get;set;}
    }

Once you make these changes the results you should see are this. When you add the first View, the ViewModel should have one detail. When you add the second view, BOTH ViewModels will have 2 instances. I am guessing you are looking for different behavior as this doesn't make much sense. But that is the way that recompostion works, you add a new export and all interested improters are updated.

Glenn

Dec 21, 2009 at 10:37 PM

I was trying to make View-ViewModel one-to-one mapping. Each view has its own instance of viewmodel. 

I think the problem is that I only have one container here, and I tried to create different container for each additional view, it worked. But can't I only keep one container for the app?

Dec 22, 2009 at 12:23 AM

That works fine if you set the creation policy on the VM and the View to non-shared. However the thing you are importing which is recomposable is the details, thus each VM will  get the same details.

Dec 22, 2009 at 8:24 PM

This works so differently than StructureMap, anyway we might rethink about our design.

Dec 22, 2009 at 10:02 PM

StructureMap doesn't support recomposition, so I am not sure how it would address this.

If what you are trying to do is spin up new Views that have their own VMs, that it can do without a problem.

The thing that does not appear to be working as you expected is the importing of the details. The way you have it set up, each view is exporting it's own details. Were you expecting that those details would automatically be made available to the VM that the view imports?

Glenn

Dec 22, 2009 at 10:55 PM

Yes, Glenn, I thought I am doing extactly "new views have their own VMs", and this code was using StructureMap before, details is in the form of ctor parm. Now I try to switch to MEF, but couldn't make it work. This shouldn't be this hard.

How can I make each view only talking to their own VM when exporting?