Persistent Data on Events

Topics: Prism v4 - Silverlight 4, Prism v4 - WPF 4
Mar 13, 2014 at 12:08 AM
Edited Mar 13, 2014 at 12:09 AM
Has anyone else run into this problem? I'm growing fond of the EventAggretator pattern to communicate between loosely coupled components. However, there's no persistence to the event and it's getting to be a problem. I have an application where a URI is used to select the object being viewed. Once everything is loaded, the volatile event works just fine, but on initialization you don't know if the event has fired or not. Let's say that Region 1 fires the UriChanged event. Region 2 has no idea when it loads if the event has fired or not so it can end up uninitialized. I've solved the problem by providing a shared object through the CompositionContainer ("SharedUri") to go with the UriChanged event, but it would be much easier if I could just query the last value of the UriChanged event.
Mar 13, 2014 at 9:16 PM
Hello,

I believe you are refering that Modules initialization fires and subscribes to Events instead of Regions, is this right?
Therefore, if I understood correctly, you are trying to Publish an Event from one Module's initialization where Region1 get the Views registered, and then you would be subscribing to that Event on another Module's Initialize() method where a Region2 is loaded with the corresponding views.

In this case, the Event would be Published, but it would not be handled because the Subscriber's Module instance dies after the initialize() action is complete. If you want to keep the subscription alive, then you should implement it on a particular ViewModel for example which it would keep the reference alive.

On the other hand, if you would need to subscribe to an Event during initialization, then a Shared object would be needed in order to work as an auxiliar interlocutor between both Modules. However, subscribing to an event on a Module initialization, gives me the idea that something may not be correctly designed.

If you would give me more details of the scenario you are trying to accomplish, I would give you better support to solve it.

Regards.

Gabriel Ostrowsky
https://blogs.southworks.net/gostrowsky
Mar 13, 2014 at 9:48 PM
Edited Mar 13, 2014 at 10:07 PM
Gabriel,

I'm not really sure to what class you are referring as you mention Event in bold, but I'm using EventAggregator class. If I missed a useful class, please point me in the right direction. Here's my module initialization:
        /// <summary>
        /// Initializes the region manager with the component regions.
        /// </summary>
        public void Initialize()
        {
            this.regionManager.RegisterViewWithRegion(RegionNames.ToolBarRegion, typeof(ToolbarViewModel));
            this.regionManager.RegisterViewWithRegion(RegionNames.DetailRegion, typeof(DetailbarViewModel));
            this.regionManager.RegisterViewWithRegion(RegionNames.ContentRegion, typeof(DirectoryViewModel));
            this.regionManager.RegisterViewWithRegion(RegionNames.HierarchyRegion, typeof(HierarchyViewModel));
            this.regionManager.RegisterViewWithRegion(RegionNames.MainRegion, typeof(MainView));
        }
The classes ToolbarViewModel, DetailViewModel and HierarchyViewModel all consume the UriChanged event. The DirectoryViewModel publishes it when it has initialized. The problem is that I've found no way to control the initialization of the regions; Prism seems to have it's own logic and reasons as to when each is instantiated. So even though the DirectoryViewModel publishes the new URI when it is initialized, I have found no way to guarantee that ToolbarViewModel or DetailViewModel will be around to see the event. Both of these view models require a URI to initialize, so it's become semi-painful to provide a globally available URI value in the CompostionContainer in addition to the UriChanged EventAggregator.

What would be ideal is something like this:

Uri uri = this.eventAggregator.GetEvent<UriChanged>().LastValue;

I hope that's a little more clear. Any ideas that streamline this process would be appreciated. Am I missing some aspect of the architecture?
Mar 14, 2014 at 5:30 PM
Hi DRAirey1,

I apologize for the missunderstanding about the Event word in bold, as it would only refer to any Event class in general.

Thank you for deeper desciption of the scenario. One possible way you could perform to solve this issue would be to Publish an InitilizationCompletedEvent for example on the BootStrapper and subscribe to this event on the DirectoryViewModel so it would know that every other ViewModel has already been initialized. Therefore, you could Publish the UriChanged event on the InitializationCompletedEventHandler() method.

In order to Publish the InitializationCompletedEvent, you would need to execute it as always on the overriden BootStrapper's run() method because it would execute right after the Modules initialization according to the BootStrapper's run() order's execution. Then, you would need to Subscribe to InitializationCompletedEvent on the DirectoryViewModel's constructor, in addition to implementing the InitializationCompletedEventHandler from where you would Publish your UriChanged event.

For more information, you could refer to the following disscusion which talks about a similar issue.

I hope this helped you.
Regards.

Gabriel Ostrowsky
https://blogs.southworks.net/gostrowsky
Mar 14, 2014 at 9:31 PM
Gabriel,

Thanks for the effort, but I'm afraid we're dealing with a different problem than module initialization. Your solution appears to be promising for controlling the ordering of events for modules, but it doesn't work for regions. That is, after stepping through the debugger, your method of overriding the Run method guarantees that all the modules have loaded, but apparently it doesn't guarantee that the Regions have been loaded. This is the rub: since the loading of viewers into regions is left up to the run-time, there's no accessible event or property that tells us when all the regions have loaded. Theoretically, if you had a view switching application, you could keep loading up regions continuously and there's no real way to say that you're 'done' initializing the regions.

Again I come back to the fact that we need a persistent property on the CompositePresentationEvent<TType> class that gives us the last published value.
Developer
Mar 17, 2014 at 9:41 PM
Edited Mar 18, 2014 at 6:46 PM
Hi,

While the CompositePresentationEvent class does not provide this functionality by its own I think it's possible to use a custom event class in your application to do this without problems. For example, you could create the following base class that extends original the CompositePresentationEvent:
public class CompositePresentationEventWithPersistentPayload<T> : CompositePresentationEvent<T>
{
    public T LastMessage { get; private set; }

    public override void Publish(T payload)
    {
        this.LastMessage = payload;
        base.Publish(payload);
    }
}
That class defines a LastMessage property and overrides the Publish method to store the last payload used in it. If you change your event to inherit from this class, then you should be able to use it like this:
MyEvent myEvent = eventAggregator.GetEvent<MyEvent>();
myEvent.Publish(message);

// Now we can obtain the last message published thanks to the property.
var lastMessage = myEvent.LastMessage;
I hope this helps.

Damian Cherubini
http://blogs.southworks.net/dcherubini
Mar 18, 2014 at 6:57 PM
We had the same issue. I swapped out the CompositePresentationEvent with a new class, just needs to inherit from EventBase. The new class just wrapped the Reactive Extension ReplaySubject. The RX ReplaySubject is built for late subscribers to get all the past events that happened. We even added a way for it to tell how many last messages it wanted but usually it's either just the last-one or all.

The code isn't that hard and that's the great thing about Prism is being able to change or swap out all the parts you want changed or don't like.

The only thing that ours doesn't have is a "Contains" because RX doesn't have a built in way to query subscribers but we could add our own if we wanted but for us the contains wasn't that big of a deal. Our Contains always returns true.
Mar 29, 2014 at 4:23 PM
Damian,

Perfect. That's exactly what I was looking for.
Should be incorporated into the standard library. This is a very useful function during initialization. Thanks.

Don