Search code examples
c#wpfmvvmprismprism-6

How to correctly send event-messages between modules in Prism framework?


I am trying to build a WPF Prism bases app using MVVM design pattern.

When my app first starts, I want to require the user to login. Once logged in, I want to show the default landing page with the user name and buttons.

My thought, when a user login, I would publish an event called UserLoggedIn then on the home view-model, I would listen for that event. When the event is triggered, I would show the landing/home view.

So I created the event like so

public class UserLoggedIn : PubSubEvent<User>
{
}

Then in the LoginViewModel I handle the login and publish the event like so

    private void HandleLogin(LoginView loginView)
    {
        try
        {
            User user = AuthenticationService.Authenticate(Username, loginView.GetPassport());

            IUserPassport passport = PassportManager.Get(user.Username);

            if (passport == null)
            {
                // Create a new session 
                passport = new UserPassport(new CustomIdentity(user), RegionManager);
            }
            // Assign the current session
            PassportManager.SetCurrent(passport);

            // Trigger the event
            EventAggregator.GetEvent<UserLoggedIn>().Publish(passport);

            // Deactivate the login view
            RegionManager.GetMainRegion().Deactivate(loginView);
        }
        catch (Exception ex)
        {
              //Do something with the error
        }
    }

Finally in my HomeViewModel aka my landing view, I have the following code to listen for the UserLoggedIn event.

public class HomeViewModel : BindableBase
{
    protected IUnityContainer Container { get; set; }
    protected ICoreRegionManager RegionManager { get; set; }
    private IEventAggregator EventAggregator { get; set; }

    public HomeViewModel(IUnityContainer container, ICoreRegionManager regionManager, IEventAggregator eventAggregator)
    {
        Container = container;
        RegionManager = regionManager;
        EventAggregator = eventAggregator;
        eventAggregator.GetEvent<UserLoggedIn>().Subscribe(ShowTheLangingPage);
    }

    private void ShowTheLangingPage(User user)
    {
        var homeView = Container.Resolve<HomeView>();

        RegionManager.AddToMainRegion(homeView);
        FullName = user.FirstName;
    }

    // I am using PropertyChange.Fody package, so this propery will automaticly raise the PropertyChange event.
    public string FullName { get; set; }
}

Problem is the ShowTheLangingPage method never get triggered in my HomeViewModel as expected.

I made sure the the View HomeView and HomeViewModel are wired up correctly, by directly loading the HomeView on module initialization for testing.

Additionally, if add Container.Resolve<HomeView>(); just before I publish the event, the ShowTheLangingPage I called. Its like I have to resolve the HomeView manually for it to listen for the event.

How can I correctly listen for the UserLoggedIn event so i can show the corresponding view.

So I can learn the better/recommended way, is it better to show the landing view from the LoginViewModel instead of using event/listener.... and why? Also, if showing the landing view directly from the LoginViewModel then what is the recommended method to navigate; using Region.Add() method or RegionManager.RequestNavigate?


Solution

  • is it better to show the landing view from the LoginViewModel instead of using event/listener....

    Yes.

    and why?

    Because that's what services (like the IRegionManager) are for, doing stuff for your view models and other services. Also, you have noticed, events can only be subscribed to by living objects.

    Also, if showing the landing view directly from the LoginViewModel then what is the recommended method to navigate; using Region.Add() method or RegionManager.RequestNavigate?

    If anything, a third class should listen for UserLoggedIn, but that's no gain over using the IRegionManager directly. In fact, it's even worse, because you have to artifically create this class. Side note: if you wait for the garbage collector after Container.Resolve<HomeView>(); and before logging in, you won't go to the landing page, because there's no subscriber (again).