1
votes

In my Prism 6 WPF MVVM modular application (using Unity DI) I want to do communicating between modules using loosely coupled event - one module is publisher and the other modules are subscribers. On the side of publisher in AuthorizationViewModel class I have, in particular, the following methods:

public class AuthorizationViewModel : BindableBase
{
    . . . . .
    // This method is called from the command method when user clicks button in the view.
    private void authenticateUser(string userName, string userPassword, Action<UserAuthorizationLevel> successCallback, Action<string> failureCallback)
    {
        Task task = Task.Run(() =>
              this.getUsers((users) =>
              {
                  // Get authenticated user information.
                  var userAuthenticated = GetUserByNameAndPassword(userName, userPassword, users);
                  // Call method publishing loosely coupled event if the user exists. Else display the error message.
                  if (userAuthenticated != null)
                      successCallback(userAuthenticated.AuthorizationLevel);
                  else
                      failureCallback("Authentification failed.");
              }));
    }
    . . . . .
}

Below is successCalback definition that is in AuthorizationViewModel class too:

private void successCalback(UserAuthorizationLevel authorizationLevel)
{
    // Publish loosely coupled event.
    this._eventAggregator.GetEvent<UserAuthorizationLevelDeterminedEvent>().Publish(authorizationLevel);
}

UserAuthorizationLevel here is enum type defined in a common place of my application solution and I don't display it here. UserAuthorizationLevelDeterminedEvent is the event type that is also defined in a common place of my application solution. Below I display it:

public class UserAuthorizationLevelDeterminedEvent : PubSubEvent<UserAuthorizationLevel>
{
}

successCalback method runs whenever it is necessary and its line of code

this._eventAggregator.GetEvent<UserAuthorizationLevelDeterminedEvent>().Publish(authorizationLevel);

executes fine so event is published but the subscriber doesn't react to event at all! There is no response to event on subscriber side! Below I display code on subscriber side:

public class CalibrationNavigationItemViewModel : BindableBase
{
    . . . . .
    private IEventAggregator _eventAggregator;
    . . . . .
    // The constructor; creates instance of CalibrationNavigationItemViewModel.
    public CalibrationNavigationItemViewModel(IRegionManager regionManager, IEventAggregator eventAggregator)
    {
        . . . . .
        this._eventAggregator = eventAggregator;
        this._eventAggregator.GetEvent<UserAuthorizationLevelDeterminedEvent>().Subscribe(this.setRadiobuttonVisualStatus, ThreadOption.BackgroundThread);
        . . . . .
    }
    . . . . .
    // Changes visual status of Radiobutton in the View.
    private void setRadiobuttonVisualStatus(UserAuthorizationLevel userAuthorizationLevel)
    {
        if (userAuthorizationLevel == UserAuthorizationLevel.Manufacturer)
            this.IsVisible = Visibility.Visible;
        else
            this.IsVisible = Visibility.Collapsed;
    }

    // Controls visual status of Radiobutton in the View; the Visibility property
    // of Radiobutton in the View is bound to this property.
    public Visibility IsVisible
    {
        get { return this._isVisible; }
        set { this.SetProperty(ref this._isVisible, value); }
    }
}

(I bag your pardon I'll make a bad break here: I'm controlling visibility status of Radiobutton in module instead of loading module itself dynamically because my application must give an opportunity to change users within the same one session. Prism module can't be unloaded after its initializing.) Now, returning to our sheep; I set ThreadOption.BackgroundThread on the subscriber side because the publisher publishes the event in TPL Task but not in the UI thread. I'd like to know: Why does subscriber not react to published event at all? What I'm doing wrong? Please help me.

2
Is your CalibrationNavigationItemViewModel alive at the time the event is fired? The subscription alone won't prevent it from being gc'ed. - Haukinger
It is loaded and initialized to the moment of firing event. - Dr.Doog
Then I don't see anything preventing the event from firing. To clutch at a straw, the eventaggregator is the same instance? In weird circumstances, it might not be registered as singleton or different containers are used for resolving. - Haukinger
I create an EventAggregator instance in AuthorizationViewModel constructor (publisher side) and in CalibrationNavigationItemViewModel constructor (subscriber side). In both places initializer value is constructor parameter. - Dr.Doog
Subscriber looks good, you don't create an event aggregator there but you get it injected, as it's supposed to be. If the publisher side does it the same way, it should work. Can you breakpoint into both constructors and check whether the event aggregator instances are really the same? - Haukinger

2 Answers

0
votes

The problem here is the event being defined in a shared project and not in a class library.

This way the event is copied to the referencing projects and actually two different events exist (that have the same name), and one is subscribed to, while the other one is published.

0
votes

Haven't tested this, but it seems you are publishing the event on a separate thread. So that event will not automatically bubble up to the UI thread where your subscriber is listening.

You can try to await your task and then publish the event when the task has completed or you can try to subscribe the event on the background thread.

EDIT: I see the problem. You are using a Shared Project. This will not work. A SharedProject does not produce an assembly but rather acts as if the class is defined in the referenced assembly and is compiled within it. You have to use a PCL.