1

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.

Dr.Doog
  • 197
  • 4
  • 14
  • Is your `CalibrationNavigationItemViewModel` alive at the time the event is fired? The subscription alone won't prevent it from being gc'ed. – Haukinger Mar 07 '16 at 10:10
  • It is loaded and initialized to the moment of firing event. – Dr.Doog Mar 07 '16 at 10:36
  • 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 Mar 07 '16 at 10:42
  • 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 Mar 07 '16 at 10:54
  • 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 Mar 07 '16 at 11:02
  • Yes, the publisher does the same way: IEventAggregator _eventAggregator; public AuthorizationViewModel(..., IEventAggregator eventAggregator) {...this._eventAggregator = eventAggregator;} – Dr.Doog Mar 07 '16 at 13:39
  • What I'm up to, are you sure you've got the same event aggregator in both view models? Break in both, assign an object id in the first and check in the second (http://stackoverflow.com/questions/4251450/uniquely-identifying-reference-types-in-the-debugger). – Haukinger Mar 07 '16 at 18:32

2 Answers2

0

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.

  • I've tried to subscribe in background thread: this._eventAggregator.GetEvent().Subscribe(this.setRadiobuttonVisualStatus, ThreadOption.BackgroundThread, true); but it doesn't help. – Dr.Doog Mar 07 '16 at 17:00
  • Then try awaiting the task and then publish –  Mar 07 '16 at 17:01
  • I've tried it, and publishing from a seperate thread works fine for me, whether or not I use `ThreadOption.BackgroundThread` when subscribing. It shouldn't matter anyway, because `PropertyChanged` will eventually be marshalled to the UI thread, even if the `BackgroundEventSubscription` runs the handler with `Task.Run` – Haukinger Mar 07 '16 at 17:10
  • No, it does matter. PropertyChanged won't do anything regarding the subscriptions of your events. You can publish from any thread you want, but if your subscribers aren't on that thread, they won't listen for the event. –  Mar 07 '16 at 17:13
  • In AuthorizationViewModel (the publisher) I define: TaskFactory uiFactory = new TaskFactory(TaskScheduler.FromCurrentSynchronizationContext()); Then in authenticateUser method I change successCallback(userAuthenticated.AuthorizationLevel); to uiFactory.StartNew(() => successCallback(userAuthenticated.AuthorizationLevel)); But It doesn't help. – Dr.Doog Mar 07 '16 at 17:58
  • That doesn't change anything. You're still on another thread. –  Mar 07 '16 at 18:05
  • I can subscribe on one thread, publish on another and receive the event, even if the subscriber thread has ended by the time the event is published, have a look at this code http://pastebin.com/yUgHKWA3 it will always reach the `var x = data;` line – Haukinger Mar 07 '16 at 18:07
  • Since I was just going off the top of my head, I decided to code up a sample, and I cannot duplicate the problem. I am able to successfully publish and subscribe to an event using the same setup as you. Is the ViewModel you want to subscribe actually in memory (has it been created)? What do you do in your GetUsers method? You are missing something in your implementation. –  Mar 07 '16 at 18:24
  • Yes. The constructor of CalibrationNavigationItemViewModel (the subscriber) runs during app starting but the constructor of AuthorizationViewModel (the publisher) runs when user checks appropriate Radiobutton in Shell' navigation region after app has started. – Dr.Doog Mar 07 '16 at 18:52
  • You need to isolate your issue. Create a new app and only implement the messaging. –  Mar 07 '16 at 18:55
  • This is getUsers method: private void getUsers(Action> callback) { var list = new System.Collections.Generic.List(); list.Add(new AuthorizationModel { UserName = "Eugene", Password = "Buzin", AuthorizationLevel = UserAuthorizationLevel.Manufacturer }); list.Add(new AuthorizationModel { UserName = "Nicolas", Password = "Johnson", AuthorizationLevel = UserAuthorizationLevel.Maintenance }); callback(list); } – Dr.Doog Mar 07 '16 at 18:59
  • Here is GetUserByNameAndPassword method: private static AuthorizationModel GetUserByNameAndPassword(string name, string password, System.Collections.Generic.IList users) { return users.Where((u) => u.UserName == name && u.Password == password).FirstOrDefault(); } – Dr.Doog Mar 07 '16 at 19:08
  • It does not work in any way. Subscriber doesn't react. My head is bursting. – Dr.Doog Mar 08 '16 at 06:35
  • I think, at this point diagnosis is near impossible without your code. Contact me at `stackoverflow at haukinger dot de`, perhaps I can have a look at your problem. – Haukinger Mar 08 '16 at 08:08
  • Haukinger, thank you very much. Currently I try to contact with you. – Dr.Doog Mar 08 '16 at 14:31
  • I've tried to do publisher as service in Authorization (publisher) module and register this service in this module via Unity DI, but the result remains the same - subscriber doesn't respond. – Dr.Doog Mar 08 '16 at 14:51
  • Not sure what you're doing wrong or missing, but my sample works perfectly. Place a simple repo on GitHub and I'll see if anything stands out at me. –  Mar 08 '16 at 15:17
  • Thank you very much. I'll do it necessarily tomorrow evening when I return to home from my job. Now I have a cold and a high fever, and I'm lying flat. So excuse me, please. – Dr.Doog Mar 08 '16 at 15:42
  • To Brian Lagunas: I've put my application solution on GitHub at: https://github.com/EugeneBuzin/FlowmeterConfigurator – Dr.Doog Mar 09 '16 at 11:52
0

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.

Haukinger
  • 10,420
  • 2
  • 15
  • 28