locked
Why can't I Export/Import a property? RRS feed

  • Question

  • I have a property in MainViewModel.cs:

    [Export("ActiveView", typeof(ViewModelBase))]
    public ViewModelBase ActiveView
    {
        get { return _activeView; }
        private set
        {
            if (value == _activeView)
                return;

            _activeView = value;

            if (_activeView != null)
                _activeView.IsCurrentPage = true;

            this.OnPropertyChanged(_activeView.ToString());
        }
    }

    And DatabasLoginModel class has [Import] on ActiveView:
    [Import("ActiveView")]
    public ViewModelBase ActiveView { get; set; }

    This is pretty much the same syntax as another export/import Im using for MainWindow (a la the MEFLook example) which works just fine.  The MEF document pages show export/import of properties pretty much just like this.

    But in DatabaseLoginModel, ActiveView is null.  I would have thought that it was supposed to basically reflect what MainViewModel ActiveView was, and even fire the appropriate notifications if I set it in DatabaseViewModel?

    What am I doing wrong?

    Thursday, October 8, 2009 6:15 PM

All replies

  • Did you compose this object, or was it created for you directly by MEF?



    Reed Copsey, Jr. - http://reedcopsey.com
    Thursday, October 8, 2009 6:40 PM
  • Hmm, maybe thats what Im not getting.  I thought that the following code from App.xaml.cs identified and composed matching exports and imports through the assembly:

    private bool Compose()
    {
        var catalog = new AggregateCatalog();
        catalog.Catalogs.Add(new AssemblyCatalog(Assembly.GetExecutingAssembly()));

        _container = new CompositionContainer(catalog);
        CompositionBatch batch = new CompositionBatch();
        batch.AddPart(this);

        try
        {
            _container.Compose(batch);
        }
        catch (CompositionException compositionException)
        {
            MessageBox.Show(compositionException.ToString());
            Shutdown(1);
            return false;
        }
        return true;
    }

    It works for the MainWindow export from MainView.xaml.cs, but not my properties.

    Thursday, October 8, 2009 7:16 PM
  • I'll try to explain this in detail - any MEF experts out there, please chime in and correct me if I don't get this perfect.. :)



    It composes objects that are part of that specific batch .

    So, let's look at the code in stages (this will probably help you understand what's happening):

        var catalog = new AggregateCatalog();
        catalog.Catalogs.Add(new AssemblyCatalog(Assembly.GetExecutingAssembly()));

    This section builds a catalog to use for composition.  It's basically saying "what assemblies are we going to search for exports?" when we compose an object. In this case, you're only telling it to search the current assembly because your adding Assembly.GetExecutingAssembly().

    Next:

         _container = new CompositionContainer(catalog);

    Here, we build a CompositionContainer.  This is the object that allows us to compose objects (fulfill their dependencies).

    Finally, we build something called a composition batch:

        CompositionBatch batch = new CompositionBatch();
        batch.AddPart(this);

    The "batch" is the actual object instances we're going to compose.  In this case, you're composing the application itself , and only the application, since you added "this" as the one and only part.  (You can add as many "parts" as you want, and compose all of them in one shot.)

    Finally, you're "composing" the application:

            _container.Compose(batch);

    When you do this, MEF looks at your application, and see:

        [Import("ActiveView")]
        public ViewModelBase ActiveView { get; set; }

    Since there's an Import defined, it goes out, and finds a matching export (tagged "ActiveView"), then constructs it, and sets your property.  This "injects" the ActiveView into your Application for you.  When it does this, since MEF is constructing the ViewModelBase instance, it will automatically inject any depdencies required there, as well (and recursively keep working).  Basically, everything that's generated directly by MEF via an Import is also effectively composed.

    However, later, you're working in a different class, and in an instance that wasn't generated directly by MEF.  Since this object was one you created, and not MEF, there's nothing that says "compose me", so it's [Import] elements aren't imported.

    There are a few options.  You can make a single composition service (ie: expose this as part of your app, or have a static class,e tc), and allow objects to compose themselves.  This is easy, but a lot of purists don't like it, since the objects need to explicitly compose themselves, and you're kind of adding in a "global" feel to things.

    The MefLook sample works by having everything work off this initial view, and each object generated is imported from the main window directly.  This basically just chains through the system, and everything just works.  You only run into problems if you have an object you're generating (new MyObject()) instead of allowing MEF to import it.

    Reed Copsey, Jr. - http://reedcopsey.com
    Thursday, October 8, 2009 7:32 PM
  • Okay, so let me see if I have this straight.  First you build a catalog from the assembly, it contains everything, well, in your assembly.  Then, you create a CompositionContainer, and put the assembly in it.  Then you create the batch of parts you wish to compose, then when you call Compose from the container on that batch of parts it iterates of the assembly and looks for matching parts with import or exports.

    I think thats right anyways.

    And then, because both my MainViewModel and DatabaseViewModels are composable parts that are never added to the batch when the app initializes the ActiveView import/exports are never matched up.

    So now here's where I get fuzzy again.  I cant just do this:
    batch.AddPart(new MainViewModel());
    batch.AddPart(new DatabaseLoginViewModel());

    Because those instances are NOT the instances being used (I tried, it didn't work :).  So I think the key is this cascading via MEF.  My object instances are not being composed properly (as you said at the very beginning).  I thought I had this but it still doesnt work, here are my import export statements:

    App:
    [Import("ForensicAnalysis.Views.MainView")]
    public new Window MainWindow {...}

    MainView:
    [Export("MyNamespace.Views.MainView", typeof(Window))]
    public partial class MainView : Window {...}

    MainViewModel:
    [Export(typeof(MainViewModel))]
    public class MainViewModel : ViewModelBase {...}

    [Export("ForensicAnalysis.ViewsModels.ActiveView", typeof(ViewModelBase))]
    public ViewModelBase ActiveView {...}

    DatabaseLoginView: no decorators (problem here maybe?)

    DatabaseLoginViewModel:
    [Export(typeof(DatabaseLoginViewModel))]
    public class DatabaseLoginViewModel : ViewModelBase {...}
     
    [Import("ForensicAnalysis.ViewsModels.ActiveView")]
    public ViewModelBase ActiveView { ... }

    It looks like everything is being displayed correctly, i.e. The MainView call in App generates the Main and DatabaseLogin views properly.  So it would appear all my ViewModels are being instantiated via cascading properly (by MEF even right?), but they are not being composed properly.

    Im so close!  Missing something very small I think.....
    Thursday, October 8, 2009 8:38 PM
  • Oh wait a minute.  Maybe this is the problem in MainView.xaml?
    <Window.Resources>
        <DataTemplate DataType="{x:Type vm:DatabaseLoginViewModel}" >
            <views:DatabaseLoginView />
        </DataTemplate>
    </Window.Resource>

    then:
    <ContentControl Content="{Binding Path=ActiveView}" />

    Does Window.Resources instantiate a DatabaseLoginViewModel in this case?  If so, then this cant be the same one from MEF?
    Thursday, October 8, 2009 8:44 PM
  • Oh wait a minute.  Maybe this is the problem in MainView.xaml?
    <WINDOW.RESOURCES>
        <DATATEMPLATE datatype="{x:Type vm:DatabaseLoginViewModel}">
            <?xml:namespace prefix = views /><views:databaseloginview>
        </views:databaseloginview></DATATEMPLATE>


    then:
    <CONTENTCONTROL content="{Binding Path=ActiveView}">

    Does Window.Resources instantiate a DatabaseLoginViewModel in this case?  If so, then this cant be the same one from MEF?
    </CONTENTCONTROL></WINDOW.RESOURCES>

    It's long, Anybody can answer it? Is there a certain answer?
    Sunday, October 3, 2010 1:42 AM
  • What question are you looking for an answer to?  This was a while back, but I did get it all figured out...
    Tuesday, October 5, 2010 5:16 AM
  • The MainView call in App generates the Main and DatabaseLogin views properly.

     

     


     

    download free affiliate marketing video tutorials|how to start blogging|Squidoo Tutorials|Squidoo Ebooks|Squidoo ebook
    Thursday, November 4, 2010 4:19 PM