Login 'before' Shell intialization

Jun 12, 2008 at 10:25 PM
I want the user to login via a login screen before the app even starts.  What I have is this method in my App.cs:

        protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);

            // Login procedure
            Login.Login l = new CampaignLab.Prism.Login.Login();
            bool? ret = l.ShowDialog();
            if (!(ret.HasValue && ret.Value && System.Threading.Thread.CurrentPrincipal != null))
                return;

            // Load shell / modules
            Bootstrapper bootstrapper = new Bootstrapper();
            bootstrapper.Run();
        }

Unfortunately, by the time bootstrapper.Run() is called, no shell is displayed.

Does anyone have a suggestion to solve this or a better area to drop a login screen prior to Shell load?

Thanks,
Josh
Jun 13, 2008 at 3:36 AM
What if you did something along the lines of

            if (!(ret.HasValue && ret.Value && System.Threading.Thread.CurrentPrincipal != null)) {
               
// Replace 1 with your InvalidCredentialsExitCode or similar
                Application.Current.Shutdown(1);
            } else {
                // Load shell / modules
                Bootstrapper bootstrapper = new Bootstrapper();
                bootstrapper.Run();
            }


Maybe you could do something in the bootstrapper itself? I haven't tested the theory and the only place I can see where it could go is in the CreateShell method or possibly in the constructor itself. I guess it depends on what you need for the application to startup, is your login logic in another module for example.

I'll think it through and try come up with a solution that's fairly straightforward.

-Brett
Jun 13, 2008 at 7:52 AM
Thanks for the advice, but the code reaches the bootstrapper.Run() line just fine on proper login, and it even gets through all the initialization (including the call to Shell.Show(), but nothing shows).
I suspect the non-show of the Shell has something to do with my call to ShowDialog on a Window before the bootstrapper?

Looking forward to any other feedback you or the team have!

-Josh
Jun 13, 2008 at 8:49 AM
Edited Jun 13, 2008 at 8:55 AM
Here is what I am doing to show a login dialog before the Shell.
nb I am using CastleWindsor as my container - Unity will work just as well. I am using the trunk version of Windsor.
Also I have overriden CreateContainer in Bootrapper to return the container I pass in in Bootstrapper's constructor


        protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);
 
            bool shutdown = true;
            Application.Current.ShutdownMode=ShutdownMode.OnExplicitShutdown;

            //initialize container with components necessary for login dialog
            IWindsorContainer container = new WindsorContainer();
            container.AddFacility<WcfFacility>();
            container.Register(
                Component.For<ILogin>().ImplementedBy<LoginImpl>().LifeStyle.Is(LifestyleType.Transient),
                Component.For<LoginDialog>().LifeStyle.Is(LifestyleType.Transient),
                Component.For<ILoginService>()
                    .Named("login.service")
                    .ActAs(new DefaultClientModel()
                               {
                                   Endpoint = WcfEndpoint
                                       //.BoundTo(new NetTcpBinding())
                                       //.At("net.tcp://localhost:55514/LoginService")
                                       .BoundTo(new BasicHttpBinding())
                                       .At("http://server/Login/Login.svc")
                               })
            );

            //create & show login dialog
            var loginDialog = container.Resolve<Login.LoginDialog>();
            bool? ok = loginDialog.ShowDialog();

            //was ok button clicked?
            if (ok ?? false)
            {
                //was user validated?
                if (loginDialog.ValidateUserResult == ValidateUserResult.Ok)
                {
                   //user validated - show shell - do not shutdown
                    shutdown = false;
                    Application.Current.ShutdownMode = ShutdownMode.OnMainWindowClose;

                    //pass container to bootstrapper
                    Bootstrapper bootstrapper = new Bootstrapper(container);
                    bootstrapper.Run();
                }
            }

            if(shutdown)
            {
                Shutdown();
            }
       }

Jun 13, 2008 at 8:52 AM
Ahhh, now I see what's going on, I should have looked a little deeper at your problem. Since you said it might be something to do with ShowDialog I remembered the book I read about how WPF sets Application.Current.MainWindow to the first window shown and it clicked.

What you need to do is first set the shutdown mode temporarilly to ShutdownMode.OnExplicitShutdown, then show your logon form, the WPF system will go and set that window to Application.Current.MainWindow so we need to set that to null so when you show your shell that gets set as the main window. Once more thing is to then set the shutdown mode back to ShutdownMode.OnMainWindowClose.

Okay enough telling you what needs to be done, here's my solution.

        public App() {
            Application.Current.ShutdownMode = ShutdownMode.OnExplicitShutdown;
            LogOnScreen logon = new LogOnScreen();
            bool? res = logon.ShowDialog();
            Application.Current.MainWindow = null;
            Application.Current.ShutdownMode = ShutdownMode.OnMainWindowClose;

            if (res.HasValue && res.Value && Authenticate(logon.UserName, logon.Password)) {
                LateNightBootstrapper bootStrapper = new LateNightBootstrapper();
                bootStrapper.Run();
            } else {
                MessageBox.Show(
                    "Application is exiting due to invalid credentials",
                    "Application Exit",
                    MessageBoxButton.OK,
                    MessageBoxImage.Error);
                Application.Current.Shutdown(1);
            }
        }

I hop this helps you out, I'll add it to my Late Night solution which you can find in the svn repositiory here.
-Brett
Jun 13, 2008 at 9:03 AM
@mallen101

LOL, I was 3 mins too late, hehe, solution is basically the same, you also made me realise you don't need to set the MainWindow to null as the framwork won't set it if the ShutdownMode is ShutdownMode.OnExplicitShutdow. I also like your approach as you start up and register the container which means login services could be plugged in.

I'll probably base your solution on within my Late Night sample.

-Brett


mallen101 wrote:
Here is what I am doing to show a login dialog before the Shell.
nb I am using CastleWindsor as my container - Unity will work just as well. I am using the trunk version of Windsor.
Also I have overriden CreateContainer in Bootrapper to return the container I pass in in Bootstrapper's constructor

// multi-line
{{
        protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);
 
            bool shutdown = true;
            Application.Current.ShutdownMode=ShutdownMode.OnExplicitShutdown;

            //initialize container with components necessary for login dialog
            IWindsorContainer container = new WindsorContainer();
            container.AddFacility<WcfFacility>();
            container.Register(
                Component.For<ILogin>().ImplementedBy<LoginImpl>().LifeStyle.Is(LifestyleType.Transient),
                Component.For<LoginDialog>().LifeStyle.Is(LifestyleType.Transient),
                Component.For<ILoginService>()
                    .Named("login.service")
                    .ActAs(new DefaultClientModel()
                               {
                                   Endpoint = WcfEndpoint
                                       //.BoundTo(new NetTcpBinding())
                                       //.At("net.tcp://localhost:55514/LoginService")
                                       .BoundTo(new BasicHttpBinding())
                                       .At("http://server/Login/Login.svc")
                               })
            );

            //create & show login dialog
            var loginDialog = container.Resolve<Login.LoginDialog>();
            bool? ok = loginDialog.ShowDialog();

            //was ok button clicked?
            if (ok ?? false)
            {
                //was user validated?
                if (loginDialog.ValidateUserResult == ValidateUserResult.Ok)
                {
                   //user validated - show shell - do not shutdown
                    shutdown = false;
                    Application.Current.ShutdownMode = ShutdownMode.OnMainWindowClose;

                    //pass container to bootstrapper
                    Bootstrapper bootstrapper = new Bootstrapper(container);
                    bootstrapper.Run();
                }
            }

            if(shutdown)
            {
                Shutdown();
            }
       }
}}



Jul 2, 2008 at 4:43 AM
@pianomanjh

I had a decent crack at this and have built it into my cwpf samples project with both concepts of my own mixed with the suggestion from mallen101. What I've done override the OnStartup method as mallen101 did but have allowed the implementation to be pluggable by supplying unity configuration information in the application configuration file. This requires adding a reference to Microsoft.Practices.Unity.Configuration.

Check out my samples here, you will need to check out the source version as I've not built a sample with this (just checked in a minuite ago).

Anyway, I'm guessing you've solved the problem in some way, but this may serve as a fairly workable example :)

-Brett
Jul 2, 2008 at 6:21 PM
Thanks so much guys! This is perfect, and Brett your samples are great.  Thanks for continuing to improve/update the samples as well!

Josh
Jul 2, 2008 at 7:30 PM
No worries, it's the least I can do, I am the only guy in my team so it's hard to bounce ideas, it's better to get them out there for others feedback, I don't have a great deal of time, but I do my best :)

I'm glad to hear it's helped solved what you're after, I'm putting together a module that works with accounts, and orders, currently the backend is a Progress OpenEdge App-Server, but I'll replace the service with a SQL server equivelant :)

-Brett
Sep 30, 2008 at 4:07 PM
Not sure if it's worth posting this, as the thread hasn't seen any action for a while, I found the above solutions very helpful as I was initially completely stuck.. but I am using a Unity container and wanted to access dynamically loaded modules to do my authentication. My eventual solution was to move the 'Showing' of the Shell view out of CreateShell(). I could then run my bootstrapper to load all my modules, access the initialized container to obtain my authentication interface and then on verifying username and password either show the shell view or call it a day..
-----------------------------------
    public class Bootstrapper : UnityBootstrapper
    {
        private ShellPresenter _presenter;

        protected override DependencyObject CreateShell()
        {
            _presenter = Container.Resolve<ShellPresenter>();
            IShellView view = _presenter.View;

            // don't do this now, wait until we actually want to show the application, this gives us time to show our log on window,
            // but allows us to load all our modules before hand
            //view.ShowView(); 
            return view as DependencyObject;
        }

        public void Show()
        {
            _presenter.View.ShowView();
        }
....
}

Then in App.xaml.cs:
protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);
            Licenser.LicenseKey = "DGF20-AUTJ7-3K8MD-DNNA";

                Bootstrapper bootStrapper = new Bootstrapper();
                bootStrapper.Run();

                // set so that the main window is not set to the logon window etc.
                ShutdownMode = ShutdownMode.OnExplicitShutdown;

                // attempt to log on - TODO LogOn resolved by container and container dependcy injected into constructor.
                LogOn logOnWindow = new LogOn(bootStrapper.Container);
                bool? logonResult = logOnWindow.ShowDialog();

                if (logonResult.HasValue && logonResult.Value)
                    bootStrapper.Show();

                Current.ShutdownMode = ShutdownMode.OnMainWindowClose;
        }

just in case anyone else has my problem as well as the original issue

cheers

Trev