Posted some code on using MEF to make extensible WPF apps with MVVM

Nov 8, 2009 at 3:23 PM

I've been playing with MEF for a while now, and I'm impressed.  I've been trying to write an extensible WPF application with the MVVM pattern, and MEF was a great fit.  I cleaned it up a bit and wrote a demo app to explain how it works, and posted the code for other people to use if they're interested.  I posted an article on CodeProject that walks you through how it works:

Developer
Nov 9, 2009 at 4:42 PM

Awesome thanks for sharing. I had a look at the article and code, focusing on the use of MEF, and I must say you did a really nice job.

With respect to MEF, only few suggestions come to my mind:
1) Perhaps you should define constants for all the string contract names, this will help eliminate errors by extenders and allow them to discover the extension points. Or better yet maybe you can simply provide custom export attributes for your different extension points.
2) You may want to consider using Lazy<T, M> in some places where you don't want everything to be loaded up front. On the same note you should consider using export metadata for somethings which would allow you to inspect exports without instantiating them until it is absolutely necessary.

Nice job this is probably one of the most extensive demos of MEF that I've seen.

Nov 9, 2009 at 6:01 PM

Scott, this looks really awesome. Thanks for doing this!

Glenn

Nov 9, 2009 at 6:09 PM

I like the idea about defining the contract names as constants somewhere, perhaps even in an enumeration (since the enumeration .ToString() gives you that enumeration name).  But that means everyone who wants to extend another add-in has to have a reference to that add-in.  Up until now you only need a reference to SoapBox.Core.Contracts.  Of course... I could put all the contract names in the Contracts DLL... makes sense.  I think I'll do that.

Regarding Lazy<>, I just started digging into how Recompose works last night because my next step is to write an Add-In manager, and I want to be able to load new Add-Ins on the fly.  As soon as I got into that, I realized I'm using the ImportingConstructor way too much, and I should just be using imported properties and members, and using AllowRecomposition=true on all the ImportMany attributes.  I went through and changed all that, and I'll publish that soon and update the article at the same time.  I may go through and change certain things to Lazy as well, but in reality, pretty much everything is required as soon as WPF starts to render stuff.

Thanks very much for the feedback!

- Scott

Nov 9, 2009 at 7:51 PM

Scott,

I made a look of this sample yeasterday as well, while I was trying to understand how the MEF fits in complex MVVM SL application. Btw... this looks a best MVVM sample so far...

Anyhow, I still have same problem in this MVVM sample, if your component PinBallTable is exported component that doesn’t have fixed PinBalls to show (in your sample, it creates statically 3) but would depends on variable when created, how would you change your sample without braking MVVM principals with MEF?

If you think this problem, some of the views are GridViews that are displaying data, data details maybe implemented in different views which are exported in catalog. How would get MVVM component and pass the selected item information without breaking MVVM pricipals by MEF? To go around of this, you could get export of model first, then view and then combine them together but then you breaking pricipals of MVVM if MEF can not do better.

You have any toughts of this?

Regards,

Alexander

Nov 9, 2009 at 9:05 PM

I'm not quite following you, but I'll take a shot...

(BTW, this is just WPF, not Silverlight)

Let's say you took the PinBallTable class as an example.  In order to add a "body" like a PinBall to the table, you have to call base.AddArenaBody(new PinBall(...)) and that's a protected method so you can't do that from somewhere else, even if you had a handle to the PinBallTable instance.  Of course you could easily make a public method that did this, and your extensions could import a reference to it and add their own new pinballs to the table.

The other alternative would be to declare an import for the PinBalls like this:

[ImportMany("/PinBallTable/PinBalls", typeof(PinBall))]
private IEnumerable<PinBall> pinBalls { get; set; }

Then implement the OnImportsSatisfied() method and call AddArenaBody on all the pin balls.  Then if some extension wants to add new pin balls, you could do it.

Does that help with your question?

 

Nov 10, 2009 at 6:09 AM

Scott

It's very interesting the way you are using contracts similar to the Eclipse/OSGI (and Monoaddins) model. You are essentially defining your own virtual extension points through the paths names. Much as we discourage using string contracts, in this way it seems quite intiutivie. Like if I want to extend main window command,, i know to do something like "/Windows/MainWindow/Command".

Hmm.....need to noodle on this approach some more.

Glenn

Nov 10, 2009 at 7:49 AM
Edited Nov 10, 2009 at 7:59 AM

Yes, I agree you that previous description was a bit confusing.  I tried this approach earlier, and it works if export is Shared/Any. However, if you try to make them NonShared you will get an error on this approach, but don’t remember anymore the error that was thrown by MEF.
I am still trying to see if anyone else has been wondering or trying to see solution for this issue when using MEF.

You will only have this issue if you have MVVM Views those are detail views of specific items selected. This case you don’t want to create each of your MVVM view export as Shared as you would select multiple items and each of them would have they own details.

The question still remains for MEF team, how would you get export of MVVM View export and overtake the selected item for it without coupling the triggering MVVM view and detail MVVM view?

I could also create a view factory, and ask each view to be resolved by use of view factory rather than directly resolving the view by MEF. The view factory would get a first corresponding model by MEF export, and then the view and combine them together. However, then this would become something else than MVVM but would work with MEF.

Scott, thanks for your reply... hope above helps to understand the issue better…

Alexander

Nov 10, 2009 at 7:56 AM

Hi Alexander

Can you give some sample code of what you are trying to do. It sounds like you are dynamically assembling non-shared view models and match them up to associated views. Is that the case? If not possibly some code/pseudo code would help.

Thanks

Glenn

Nov 10, 2009 at 7:59 AM

Glenn,

Happy to do that, let me asseble something in next hour from the proof of concept with MEF and see what you think.

Nov 10, 2009 at 8:05 AM
Edited Nov 10, 2009 at 8:09 AM

Glen,

I had this pseudo code from previous thread, if this is not clear I can asseble a small sample in next hour, let me know.

[Export(“Shell”,typeof(IView))]
public partial class Shell : UserControl, IView
{
public IShellViewModel ViewModel { get; private set; }
public Shell ([Import]IShellViewModel viewModel)
{
    InitializeComponents();
    this.ViewModel = viewModel;
}
public void ShowCustomer(object sender, SelectedCustomerEventArgs e)
{
   // get business customer view from container and add it the ShellPresenter 
   IView customerView = Container.GetExport<IView>(e.CustomerType);
   ShellPresenter.Childers.Add(customerView);
  }
}

[Export(“CustomerB2B”,typeof(IView))]
public class CustomerB2BView : UserControl, IView
{
      public ICustomerB2BViewModel ViewModel { get; private set; }
      [ImportingConstructor]
      public CustomerB2BView ([Import]ICustomerB2BViewModel viewModel)
      {
            InitializeComponents();
            this.ViewModel = viewModel;
            this..DataContext = this.ViewModel;
      }     
}

[Export(typeof(ICustomerB2BViewModel))]
public class CustomerB2BViewModel : ViewModelBase, ICustomerB2BViewModel
{
     public IBusinessAccountCustomer Customer { get; private set; }
     [ImportingConstructor]
     public CustomerB2BViewModel([Import]IBusinessAccount customer)
     {
         this.Customer = customer;
         LoadCustomerDetails(Customer.CustomerID);
     }
     private void LoadCustomerDetails(int customerID)
     {
           // execute UIService and populate Customer property
     }
}

Nov 10, 2009 at 10:28 AM

Hi Alexander,

I avoided the problem you're talking about completely in SoapBox Core by never using UserControls.  I did everything with DataTemplates.

Now if you wanted to have UserControls, I would suggest wrapping them in a DataTemplate, putting them in a ResourceDictionary and exporting the resource dictionary.  Would that work for you?  That's how I'm mapping all the Views to ViewModels in SoapBox Core.

- Scott

Nov 10, 2009 at 10:45 AM

@Glenn: I took a look at Mono.Addins, but most of my thinking is from SharpDevelop's Add-In model.

I like the hierarchical syntax a lot.  I do want to change it to remove the hard coded strings, but I want to create a good way of doing it first.  Unfortunately you can't use a "/" character in a constant name, so maybe underscores...

 

Nov 10, 2009 at 2:14 PM

Glenn,

I constructed a small sample from proof of concept I have. Please see the sample at http://cid-8358d6aacc50a938.skydrive.live.com/browse.aspx/.Public 

Please make the following notes:

I have not refractored this to interface but in proof of concept I have them in interface. Either way this works with the workaround I currently have.

This sample shows the customers, once you click "Show Orders" you get new view resolved by MEF and inserted into the current view. If you click the Order you see the order details.

My problem here is that the parent view is aware of child view. I havent be able to populate the child view information any other way by MEF. See the CustomersViewModel and ShowDetails method. Please note, even if I would refactor this to interface I would not be able to get rid of the coupling unless I am able to send some kind of parameters while MEF is resolving the export or constructing the child view in this case.  

Any thoughts and directions would be great… So far MEF has made this MVVM model much simpler than Unity and I would love to keep it in MEF as I would have smaller code base than with similar solution with Unity.

Regards,
Alexander

 

Nov 10, 2009 at 10:11 PM

Scott, they are similar, MonoDevelop was created based on forking SharpDevelop. MonoAddins was then refactored out of that.

Agree on the challenges of constants, not sure what the answer is. Underscores don't have the same effect. Maybe camel casing would work. I think what you could do though (which Wes and I discussed) is create custom MEF exports for each of your different "types" maybe that accepts a contract param which is the path.

Glenn

Nov 11, 2009 at 1:46 AM
Edited Nov 11, 2009 at 3:30 AM

Glenn,

Wes suggested nested static classes to organize the constants and I thought that would work, and it gets me pretty close, but I'm still having issues.  Who knew organizing constants in a hierarchy would be so hard. 

The specific problem is when I reference them in the pin ball assembly (which has extension points of it's own).  If SoapBox.Core has an Extensions class and SoapBox.Demo.PinBall has an Extensions class, and in PinBall we have a using SoapBox.Core, then I have to preface all export contracts as SoapBox.Core.Extensions... which is not only long, but confusing because you've got one namespace masking another one.

Leave it with me, I'll figure it out.

**EDIT**

Ok, I'm going to declare my ExtensionPoints like this, in their own sub-namespace rather than in a static class.  This seems to make the name resolution a bit more reasonable.  Yes, that means I'll have to use the fully qualified namespace when extending another assembly, but at least the using statements don't cause confusion:

 

namespace SoapBox.Core.ExtensionPoints
{
    public static class Host
    {
        public const string Styles = "ExtensionPoints.Host.Styles";
        public const string Views = "ExtensionPoints.Host.Views";
        public const string StartupCommands = "ExtensionPoints.Host.StartupCommands";
    }
    public static class Workbench
    {
        public const string ToolBars = "ExtensionPoints.Workbench.ToolBars";
        public const string StatusBar = "ExtensionPoints.Workbench.StatusBar";
        public const string Pads = "ExtensionPoints.Workbench.Pads";
        public const string Documents = "ExtensionPoints.Workbench.Documents";

        public static class MainMenu
        {
            public const string Self = "ExtensionPoints.Workbench.MainMenu";
            public const string FileMenu = "ExtensionPoints.Workbench.MainMenu.FileMenu";
            public const string EditMenu = "ExtensionPoints.Workbench.MainMenu.EditMenu";
            public const string ViewMenu = "ExtensionPoints.Workbench.MainMenu.ViewMenu";
            public const string ToolsMenu = "ExtensionPoints.Workbench.MainMenu.ToolsMenu";
            public const string WindowMenu = "ExtensionPoints.Workbench.MainMenu.WindowMenu";
            public const string HelpMenu = "ExtensionPoints.Workbench.MainMenu.HelpMenu";
        }
    }

    public static class Options
    {
        public static class OptionsDialog
        {
            public const string OptionsItems = "ExtensionPoints.Options.OptionsDialog.OptionsItems";
        }
    }
}

 

I'm also creating sub-namespaces for Services, and for CompositionPoints (which are for times when I use MEF for composition even within a part, or within the framework, rather than intending to export it as a service).

I just finished changing it all, and I'm happy with the result, so that'll go into the next version, likely this weekend.