MEF in MVVM

I read a post about MEF vs. IOC which cleared the confusion: MEF should be used external of app, while IOC should be used internally.

Good point. I have been struggling with how to separate my view and view model correctly in WPF app for a long time.   In fact Bil Simer already suggested that MEF is perfect fit in MVVM/Presentation Model last year during the Edmonton Code Camp 2008.

Ideally, all view models can be compiled into a separated assembly and put into specific folder to be imported by UI views!

Some code changes during my code conversion include:

  • Remove ctor args from view model, I haven’t figure out how to feed ctor args when MEF importing. This is not easy as auto-wiring in IOC. I ended up with a separate initialize method in each view model, which is not too bad, because I have to call wireUpViewModel from view anyway, this Initialize method has a perfect place to put.
  • I’m still not sure where should I put Container.Compose() method, because this method needs the instance the object to be imported into, I think compose as needed is better for WPF app.
  • Shared mode is the default behavior for export and import, it messed up my event handling code, (true, I didn’t do -= before +=). I had to explicitly add this everywhere.
  • AllowRecomposition is a pain for me, couldn’t get it work at all. I have tried many different ways to create a second instance of the exported class. PartCreationPolicy? AllowRecomposition attribute? Maybe because I’m working on a beta version?   Recomposition doesn’t work in contrustor paramteters, see Glen’s post.
  • Too many attributes kinds of pollute my code. Adding those export/import attributes just like playing ping-pong. If you are not good at it, you become dizzy very quickly.
    [Export]
    [PartCreationPolicy(CreationPolicy.NonShared)]
    
    [Import(typeof(NewRequestTypeViewModel), RequiredCreationPolicy = CreationPolicy.NonShared)]
    
    [ImportingConstructor]
    
    [Import(AllowRecomposition=true)] //this can apply directly on ctor parameters, but recompositorion won't work in ctor (for now)
  • I still feel using StructureMap to hookup viewmodel inside view is better and more comfortable. Even this violates some principals,  but in shell to open the view is much simpler, just new view(). ViewModel should sit behind the view, those UI developers/designers shouldn’t be ware of the exist of any view modles.
public RequestDetailsView(RequestDto requestDto)
{
  InitializeComponent();
  _viewModel = ObjectFactory.With("requestDto").EqualTo(requestDto).GetInstance<MyViewModel>();
  DataContext = _viewModel;
}
Advertisements

WPF ListView Control

We build framework, you build application.

This should be the motto for Microsoft, and any framework provider. But thing is different in WPF world, try google how many people are working so hard to make ListView header click sortable.

I don’t like code behind, the classic event hook up isn’t a clean solution at all. There are a few smart guys using attached property to implement this sort feature, xaml can be much simpler, and we can slowly build our own WPF framewrok extension. (?)

The idea can be found from this post, but the attached source code are missing, this post uses same technology with full version of source code. In fact, I think Thomas’ solution is better, he is using a technique called Adorner, which is cleaner than the other one using switching template. The only part I don’t like is the extra GridViewSort.PropertyName in his xmal to set the sorting property name. Why not just use the native binding name? like this:

propertyName = ((System.Windows.Data.Binding)headerClicked.Column.DisplayMemberBinding).Path.Path;

That’s for click-header-sorting.

How about double-click to trigger event/command in view model? Check this post.

Is there an open source project somewhere called WPF contrib? Yes, check out the WPF tool kit on codeplex, at least the click-header-sorting is supported, including some other fancy feature, such as the alternate row color.

Popup messagebox in ViewModel

Very interesting topic, Jeremy’s IMessageBoxService idea is very easy to handle, unit-test. The pain is have to inject this IMessageBox to every class needed.

Glenn Block’s suggestion is using event handler, code can be a little bit cleanner. Test is a little bit harder.


[Test]
 public void should_validate_new_mail_role_and_raise_show_message_event()
 {
    var subscriber = MockRepository.GenerateMock<IEventSubscriber>();
     _emailRoleMaintViewModel.ShowMessageEvent
     += new System.EventHandler<EventArgs<string>>(subscriber.Handler);

     subscriber.Expect(x=>x.Handler( _emailRoleMaintViewModel, new EventArgs<string>("") ))
     .IgnoreArguments();

     _emailRoleMaintViewModel.AddEmailRole("");

     subscriber.VerifyAllExpectations();

 }

More difficult problem is to get the confirmation from view, Glenn’s solution is ‘to raise a separate event to indicate selection which the VM subscribes to‘. Too much overkill for a simple messagebox.

Dong Scott’ s post about CancellableCommand is another option, ViewModel only focus on success logic, but give options to view to ‘cancel’ the action. The comfirmation rending and analyzing code won’t be testable then, but who cares? It’s just a messagebox.


public class CancellableCommand : RelayCommand
{

   public void Execute(object parameter)
   {
   var eventHandler = Executing;
   CancelEventArgs cancelEventArgs = new CancelEventArgs(true);
   if (eventHandler != null)
   {
     eventHandler(parameter, cancelEventArgs);
   }

   if (cancelEventArgs.Cancel) return;

    _execute(parameter);
   }

    public event CancelEventHandler Executing;

}

[Test]
 public void should_stop_if_view_cancel()
 {
 var subscriber = MockRepository.GenerateMock<IEventSubscriber<CancelEventArgs>>();
 _emailRoleMaintViewModel.DeleteCommandObject.Executing +=
 new CancelEventHandler(subscriber.Handler);

 subscriber.Expect(x => x.Handler(_emailRoleMaintViewModel, new CancelEventArgs(true)))
 .IgnoreArguments();

 _emailRoleMaintViewModel.DeleteCommandObject.Execute("");

 subscriber.VerifyAllExpectations();
 _mockLookupProxy.AssertWasNotCalled(x => x.DeleteEmailRole(null), opt => opt.IgnoreArguments());

 }

 [Test]
 public void should_delete_if_view_did_not_cancel()
 {
 var subscriber = MockRepository.GenerateMock<IEventSubscriber<CancelEventArgs>>();
 _emailRoleMaintViewModel.DeleteCommandObject.Executing +=
 new CancelEventHandler(subscriber.Handler);

 subscriber.Expect(x => x.Handler(_emailRoleMaintViewModel, new CancelEventArgs(true)))
 .IgnoreArguments()
 .Do(new EventHandler<CancelEventArgs>(
 (o, e) => { e.Cancel = false; }
 ));

 _emailRoleMaintViewModel.DeleteCommandObject.Execute("");

 subscriber.VerifyAllExpectations();
 _mockLookupProxy.AssertWasCalled(x => x.DeleteEmailRole(null), opt => opt.IgnoreArguments());
 }

    public interface IEventSubscriber<TEventArgs> where TEventArgs : EventArgs
    {
        void Handler(object sender, TEventArgs e);
    }