Removing views in a region behaviour

Topics: Prism v4 - WPF 4
Feb 2, 2012 at 10:57 AM

I have a region behaviour which applies security to views, when security is denied it removes any added views.

I am experiencing an exception when the Region is a SingleActiveRegion, or a Region, but not when the Region is an AllActiveRegion. The exception being thrown is this:

InvalidOperationException
Cannot change ObservableCollection during a CollectionChanged event.

A dumbed down version of the behaviour which still causes the issue is as follows:

public class CheckSecurityBehaviour : RegionBehavior
{
	protected override void OnAttach()
	{
		Region.Views.CollectionChanged += ViewsChanged;
		CheckViewsHaveSecurity(Region.Views);
	}

	private void ViewsChanged(object sender, NotifyCollectionChangedEventArgs e)
	{
		if (e.Action == NotifyCollectionChangedAction.Add)
			CheckViewsHaveSecurity(e.NewItems);
		else if (e.Action == NotifyCollectionChangedAction.Reset)
			CheckViewsHaveSecurity(Region.Views);
	}

	private void CheckViewsHaveSecurity(IEnumerable views)
	{
		foreach (var view in views)
		{
			bool hasSecurity = false; // Usually service call
			if (!hasSecurity)
			   Region.Remove(view); // This is where the exception is thrown
		}
	}
}

As I say, this works when the Region is an AllActiveRegion (exposing Views through ActiveViews), but not when it is a Region or SingleActiveRegion.

Does anyone have any ideas why this would be working in some situations, but not all?

 

Thanks,

Luke

Developer
Feb 2, 2012 at 9:00 PM

Hi Luke,

As far as I know, this exception appears because you are trying to modify a collection (the Views collection) inside its CollectionChanged event handler. The ViewsChanged method is invoked when the CollectionChanged event is raised and usually, it is not possible to modify the collection inside this event.

So far, we were unable to find why this exception is not being thrown when the the region is defined in an ItemsControl; however, as a possible approach to use this behavior with other types of region, you could wrap the code used to remove the view in a delegate, and invoke it on a different thread.

Take into account that, in my opinion, this is not the recommended approach when handling this kind of scenarios: following this approach, you first create the view, its view model, all the other dependencies, subscribe to events, etc, and then if the view is not "secured" the view is removed, making all the previously steps meaningless.

I believe that another approach could be to check if a specific view should be added to the region before creating it (for example using a shared service), avoiding unnecessary processing. For example, you could create an extension method for the Region class that could internally check if the view should be created and add it (through the Add method) when required. If you are using the view discovery approach, you can register the view using a delegate and check if the view should be added or not in it. However, the implementation of such approaches would depend mostly of your personal preferences and the requirements of your scenario.

Regards,

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

Feb 3, 2012 at 10:08 AM

Hi Damien,

I agree with what you're saying, creating it first then immediately removing it isn't ideal, however my knowledge is lacking in how to achieve either of the two alternatives. I am currently using view discovery, yet by using a delegate InjectionFactory to resolve the view, I can't think of any way to actually cancel the resolve and tell the region it is being cancelled.

If I alternatively went for the other approach, of creating an extension method for Region which checks the security before creating it, I am not sure of the best way to do this, I am assuming you mean I should call this new method instead of RequestNavigate? Then only continue the navigate if security is granted? That sounds like a good approach, but my security is based upon attributes on the concrete types, so I would need to find out what type it is mapped to in the unity container first which I am not sure can be achieved without resolving.

Thanks for your help though Damian, given me some steps in the right direction.

Regards,

Luke

Feb 3, 2012 at 10:18 AM

A quick update to my last post, throwing an exception inside the InjectionFactory method would cancel the resolve and the exception gets internally caught/handled by the navigation service, this doesn't seem ideal, but a possible solution. If there are any other ways to do this, I'd love to hear about them.

Thanks,

Luke