I'm new to Caliburn.Micro and I'm wondering what is the best way to handle user Login/Logout cycles in my application. I saw some suggestions online to implement this using an empty Shell-View which switches between the LoginView and the main application view, each with a custom ViewModel of course.
I don't really like this solution, because for me these are 2 separate windows with very different properties (Title, Icon, Size) and it seems an unclean solution two change one window to look like the other. Another problem is, that the Login Window comes from an utility library which I don't control and which doesn't use Caliburn.Micro, it's a plain old Window which gives me an event when the user clicks "Login".
I also saw suggestions to display this Dialog in the Bootstrapper startup method, but the problem I see with that is that the user can choose to "Logout" of the application which should display the Login dialog again. It seems wrong to me to handle the switching between the Views in the Bootstrapper.
What I would like is to have some sort of ApplicationViewModel or ApplicationController which works like a Caliburn Conductor, but instead of switching between Views inside a Window, it should switch between the LoginWindow and the MainWindow and should also handle Closing of the whole application (which also requires a Logout). On Activation it would show the LoginWindow, handle the Login event and then switch to the Main Window (Shell). If the user chooses to "LogOut", the event should bubble up to the ApplicationViewModel/Controller again which would deactivate/close the MainWindow, perform the Logout and then show the LoginDialog again. Similar a Close event would do the Logout, but then Shutdown the whole application.
So my questions are:
Thanks a lot!
I think the solution to your problem is fairly easy.
In a nutshell you are creating one ViewModel as Shell which is represented with a Login Window when the application starts. If the user logs in successfully this window closes and the same instance of the viewModel is displayed in a Content Window. If the user is doing a logout, the Login Window is shown again.
First of all create an interface IShell which exposes two delegates LoginSuccessful
and Logout
public interface IShell
{
Action LoginSuccessful { get; set; }
Action Logout { get; set; }
}
Next create a class ShellViewModel
which implements IShell
public class ShellViewModel : Screen, IShell
{
public ShellViewModel()
{
LoginSuccessful = delegate { };
Logout = delegate { };
}
public Action LoginSuccessful { get; set; }
public Action Logout { get; set; }
public void DoLogin()
{
LoginSuccessful();
}
public void DoLogout()
{
Logout();
}
}
The methods DoLogin
and DoLogout
are Actions which can be bound to a Button
or whatever control appropriate for you.
Next step is to override the OnStartupMethod
in your Bootstrapper. This premises that you have an instance of the WindowManager
and ShellViewModel
exported by an IoC Framework of your choice.
protected override void OnStartup(object sender, StartupEventArgs e)
{
var windowManager = IoC.Get<IWindowManager>();
var viewModel = IoC.Get<IShell>();
viewModel.LoginSuccessful =
() => GuardCloseAndReopen("Content");
viewModel.Logout =
() => GuardCloseAndReopen("Login");
windowManager.ShowWindow(viewModel, "Login");
}
private void GuardCloseAndReopen(string shellViewMode)
{
var windowManager = IoC.Get<IWindowManager>();
var shellScreen = IoC.Get<IShell>() as Screen;
Application.ShutdownMode = ShutdownMode.OnExplicitShutdown;
shellScreen.TryClose();
Application.ShutdownMode = ShutdownMode.OnLastWindowClose;
windowManager.ShowWindow(shellScreen, shellViewMode);
}
The trick to this is: If the DoLogout
method is called, the current window gets closed by calling TryClose
on the ShellViewModel
. At the same time you prevent the application from being shutdown by setting the Application.ShutdownMode
to OnExplicitShutdown
. Then using the windowmanager, you create another window in Login Mode by passing "Login" as Context information to the windowManager. This is actually the same ViewModel, however, with a different visual representation.
For Logout
you are doing the same thing just around.
To get this working using Caliburn Conventions, you need a special project structure as seen here (and explained there):
Now I challenge you to take this code and create a little sample application. Create a Login
View (which does Login with a Button or whatever) and create a Content
View with a Logout Button using the LoginSuccessful/ Logout Methods.
This will solve your issue with a minimum of Code and classes. Hope this will be helpful to you.
I've had a go at creating something that basically works but probably needs a bit more work to be really usable. The fully comments and source can be found on this post Caliburn.Micro Login Window sample on my website.
I used the IEventAggregator
of Caliburn.Micro to control the transition between the two windows. You get this code to open the login screen:
public void Handle(LoginEvent message)
{
LoginWindow loginWindow = new LoginWindow();
loginWindow.Login += new EventHandler<LoginEventArgs>(this.LoginWindow_Login);
loginWindow.Cancel += new EventHandler(LoginWindow_Cancel);
loginWindow.ShowDialog();
}
this same source is used for both the first time the app opens and when the Logout event is published. the Logout event looks like this:
public void Handle(LogoutEvent message)
{
Application.Current.ShutdownMode = ShutdownMode.OnExplicitShutdown;
message.Source.TryClose();
Application.Current.ShutdownMode = ShutdownMode.OnLastWindowClose;
this.events.Publish(new LoginEvent());
}
When a login is successful it uses this code to open the main window which is based on a ViewModel:
ContentViewModel viewModel;
viewModel = IoC.Get<ContentViewModel>();
viewModel.Username = e.Username;
this.windowManager.ShowWindow(viewModel);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With