PartCreationPolicy NonShared Required for WPF + Prism + MEF?

Aug 16, 2011 at 4:35 PM

Hello,

I'm working through a proof-of-concept with WPF + Prism + MEF, and it seems that using [PartCreationPolicy(CreationPolicy.NonShared)] is required when using MEF and implementing IConfirmNavigationRequest in the ViewModel layer.  The app has two navigation regions: a navigation region on the left, and a content area taking the center stage.  I had wanted to use the default of CreationPolicy.Shared and set IsNavigationTarget = true for the views/viewmodels that go into the navigation region because even though they get populated from individual modules, once the app in launched an the imports are satisfied, they never change.  However, what I found is that if you use the combination of IsNavigationTarget=true and CreationPolicy.Shared (or don't provide the PartCreationPolicy attribute at all, that the application actually begins to leak the views (they never get disposed).  What happens is that rather than Prism reusing the view when responding to a RequestNavigate command, it creates a new instance of the view while retaining the previous instance instance in memory indefinitely.

I ran across this because I implemented an InteractionRequest in the ConfirmNavigationRequest method of the navigation view model for one of the navigation screens and found that if I navigated to that view multiple times and then triggeded the code that would fire the interaction request, that I would get a MessageBox (implemented via a Behavior) multiple times... one for each view that was hanging out in memory.

I'm ok with using [PartCreationPolicy(CreationPolicy.NonShared)] and setting IsNavigationTarget = false, but it just seems like a bug that Prism would leak views like that when trying to reuse views.

I'm also not convinced that I'm not doing something wrong, and if someone can point me to a working sample of a WPF + Prism + MEF app that uses views that implement IConfirmNavigationRequest and set IsNavigationTarget to true, I'd love to look through the code.

Sample codebehind for a view:

    [Export("OrderEntryNavigationView")]
    [PartCreationPolicy(CreationPolicy.NonShared)] //if ommitted or set to Shared, this view will leak during RequestNavigate
    public partial class OrderEntryNavigationView : UserControl
    {
        public OrderEntryNavigationView()
        {
            InitializeComponent();
        }

        [Import]
        public OrderEntryNavigationViewModel ViewModel
        {
            set { this.DataContext = value; }
        }

    }

 

ViewModel snippets:

[Export]
    public class OrderEntryNavigationViewModel : NavigationAwareViewModelBase

...
	public override bool IsNavigationTarget(NavigationContext navigationContext)
        {
            return false; //if set to true, the view + viewmodel will leak during RequestNavigate
        }
        public override void ConfirmNavigationRequest(Microsoft.Practices.Prism.Regions.NavigationContext navigationContext, Action<bool> continuationCallback)
        {
            if (!_isContentValid) //when leaking this code fires once for each instance of the viewmodel in memory
            {
                this._confirmExitNoSaveRequest.Raise(new Confirmation()
                {
                    Content = "There are errors in the current record and the record cannot be saved.  Are you sure you want to navigate away and lose unsaved changes?",
                    Title = "Confirm Discard Changes"
                },
                    c =>
                    {
                        if (c.Confirmed)
                        {
                            this.Aggrigator.GetEvent<ClearContentErrorsEvent>().Publish(true);
                            this.Aggrigator.GetEvent<ViewModelValidationEvent>().Unsubscribe(_token);
                        }
                        continuationCallback(c.Confirmed);
                    });
            }
            else
            {
                this.Aggrigator.GetEvent<ViewModelValidationEvent>().Unsubscribe(_token);
                continuationCallback(true);
            }
        }


The behavior I built:

    public class ConfirmDialogBehavior:Behavior<Control>
    {


        public IInteractionRequest InteractionRequest
        {
            get { return (IInteractionRequest)GetValue(InteractionRequestProperty); }
            set { SetValue(InteractionRequestProperty, value); }
        }

        // Using a DependencyProperty as the backing store for InteractionRequest.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty InteractionRequestProperty =
            DependencyProperty.Register("InteractionRequest", typeof(IInteractionRequest), typeof(ConfirmDialogBehavior), new UIPropertyMetadata(null));

        protected override void OnAttached()
        {
            this.AssociatedObject.Loaded += new RoutedEventHandler(AssociatedObject_Loaded);
        }

        void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
        {
            InteractionRequest.Raised += new EventHandler<InteractionRequestedEventArgs>(InteractionRequest_Raised);
        }

        void InteractionRequest_Raised(object sender, InteractionRequestedEventArgs e)
        {
            if (e.Context is Confirmation)
            {
                Confirmation confirmation = e.Context as Confirmation;
                Window parent=Window.GetWindow(this.AssociatedObject);

                if (parent != null)
                {
                    if (MessageBox.Show(parent, confirmation.Content.ToString(), confirmation.Title.ToString(), MessageBoxButton.OKCancel, MessageBoxImage.Warning) == MessageBoxResult.OK)
                    {
                        confirmation.Confirmed = true;
                    }
                    else
                    {
                        confirmation.Confirmed = false;
                    }

                    e.Callback.Invoke();
                }
            }
        }

        protected override void OnDetaching()
        {
            this.AssociatedObject.Loaded -= AssociatedObject_Loaded;
            InteractionRequest.Raised -= InteractionRequest_Raised;
        }

        
    }

 

The behavior used in xaml:

	<i:Interaction.Behaviors>
		<RQOnePOC_Core_Behaviors:ConfirmDialogBehavior InteractionRequest="{Binding ConfirmExitNoSaveRequest}"/>
	</i:Interaction.Behaviors>
So, if it's a requirement to use NonShared and set IsNavigationTarget to false then I'm ok with that.  I'd just like someone to confirm/deny that's the case.  
If it's not the cases I'd loke to see some sample code where it works.
Developer
Aug 16, 2011 at 7:51 PM
Edited Aug 17, 2011 at 6:11 PM

Hi,

It would be useful if you could provide us a repro sample application portraying this problem, so we could analyze why it is happening.

Thanks,

Damian Cherubini
http://blogs.southworks.net/dcherubini