1

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?

Junior
  • 11,602
  • 27
  • 106
  • 212
  • Make sure your `EventAggregator` is registered as singleton in your IoC or, if there is no IoC container, that you pass the very same instance to the controller. Would post this as an answer but you don't say anyhing on how the constructor argument is passed to the view model. – Wiktor Zychla Mar 03 '18 at 20:46
  • @WiktorZychla I am using the default setup in Prism. The documentation show that the instance is singleton http://prismlibrary.github.io/docs/wpf/Initializing.html – Junior Mar 03 '18 at 20:57
  • You need to Subscribe to the event before you publish it. In other words, you need to instantiate the HomeViewModel before the HandleLogin method is called. – mm8 Mar 05 '18 at 13:35

1 Answers1

0

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).

Haukinger
  • 10,420
  • 2
  • 15
  • 28
  • Understood. What is the recommended approach here `RegionManager.RequestNavigate` to change the content or use `Region.Add()` to add content? "I am not clear on the difference between the two". Also, if there are logic that needs to be executed on the landing page, how would I trigger that? For example, when the user logs in, I want to show the loggen in user's fullname in the header. Setting the name on HomeView.Fullname will need to happen after the content is added or navigated too. – Junior Mar 05 '18 at 19:19
  • In your view model, you can have the region manager injected, but I don't see an obvious way to get the region, so more of a practical reason here. You also want some service that verifies a users password, stores the currently logged in user, allows to change a user's password and so on... inject that service into the landing page view model and read the logged in user name. BTW, try to give that service not one but many interfaces, one for each type of consumer (i.e. `ILogInService` for logging in, `ICurrentUserProvider` to fetch the currently logged in user and so on) – Haukinger Mar 07 '18 at 08:22