Gathering User Information before Bootstrapper Runs

Topics: Prism v2 - Silverlight 4, Prism v4 - Silverlight 4
Apr 1, 2013 at 7:39 PM
Hello,
I am fairly new to the MVVM model and need some help.

My application is called with a GUID on the querystring.

In my App.xaml.cs I gather the GUID, run a sub called SetupUserPrincipal with that guid. Inside that function I create object UserPrincipal.
    private void Application_Startup(object sender, StartupEventArgs e)
    {
        string val = HtmlPage.Document.QueryString["GUID"];
        Bootstrapper bootStrapper = new Bootstrapper(this.SetupUserPrincipal(val));
        bootStrapper.Run();
    }

    private UserPrincipal SetupUserPrincipal(string userid)
    {
        UserPrincipal principal = new UserPrincipal(userid);
        return principal;
    }
Inside of my UserPrincipal class, I set the GUID and then call a web service to see if that GUID is correct and if so, gather some more information out of it.
public class UserPrincipal
{
    private string _guid;
    private Int32 _userID;
    private DateTime _lastAccess;

    public DateTime LastAccess
    {
        get { return this._lastAccess; }
    }

   public string GUID
    {
        get { return this._guid; }
    }

    public Int32 UserID
    {
        get { return this._userID; }
    }

    public UserPrincipal(string guid)
    {
        _guid = guid;

        LoadUserInfo();
    }

    public void LoadUserInfo()
    {
        UserSecurityServiceClient service = new UserSecurityServiceClient();
        service.GetUserByGUIDCompleted += new EventHandler<GetUserByGUIDCompletedEventArgs>(UserInfo);
        service.GetUserByGUIDAsync(this._guid);
    }

    void UserInfo(object sender, GetUserByGUIDCompletedEventArgs e)
    {
        NSAMedia.VendorAutomation.Infrastructure.UserSecurityService.Users serviceResponse = e.Result;

        for (int j = 0; j < serviceResponse.User.UserSecurity.Count; j++)
        {
            this._userID = serviceResponse.User.UserSecurity[j].userID;
            this._lastAccess = serviceResponse.User.UserSecurity[j].lastAccess;
        }
        ServiceLocator.Current.GetInstance<IEventAggregator>().GetEvent<UserInitialized>().Publish(true);
    }

}
Since I am in Silverlight, this is a async call. This means the call to run the bootstrapper and register my modules and even Register the Instance of my UserPrincipal object is called and loaded before my web service is called.
public class Bootstrapper : UnityBootstrapper
{
    UserPrincipal userPrincipal;

    protected override DependencyObject CreateShell()
    {
        Shell rootShell = new Shell();
        Application.Current.RootVisual = rootShell;
        return rootShell;
    }

    protected override void ConfigureContainer()
    {
        Container.RegisterInstance(typeof(UserPrincipal), userPrincipal, new ContainerControlledLifetimeManager());

        base.ConfigureContainer();
    }

    public Bootstrapper(UserPrincipal principal)
    {
        this.userPrincipal = principal;
    }

    protected override IModuleCatalog CreateModuleCatalog()
    {
        ModuleCatalog modules = new ModuleCatalog();
        //modules.AddModule(typeof(NSAMedia.VendorAutomation.Modules.HelloWorld.HelloWorldModule));
        modules.AddModule(typeof(NSAMedia.VendorAutomation.Modules.VendorDetail.VendorDetailModule));
        modules.AddModule(typeof(NSAMedia.VendorAutomation.Modules.VendorSelection.VendorSelectionModule));
        modules.AddModule(typeof(NSAMedia.VendorAutomation.Modules.ProductSelection.ProductSelectionModule));
        modules.AddModule(typeof(NSAMedia.VendorAutomation.Modules.ProductDetails.ProductDetailsModule));
        return modules;
    }

}
At this point I wanted to use the UserPrincipal object in each of my Modules constructors to determine if they have the ability to access the page or items on the page.
       public VendorSelectionViewModel(IVendorSelectionView view, IEventAggregator eventAggregator, User user)
        {
            View = view;
            View.Model = this;
            _user = user;
            //this.HeaderInfo = "Hello World";
            this.VendorList = new ObservableCollection<NSAMedia.VendorAutomation.Infrastructure.Model.VendorModel>();
            this.eventAggregator = eventAggregator;

            //this.eventAggregator.GetEvent<UserInitialized>().Subscribe(this.checkUser);

            try
            {
                this.LoadVendors();
            }
            catch
            {
                // log service faults 
                //this.AddError("Failed to set up data for Specialties, please see the event log for more details!");
            }

        }
Because of the async call, my user object that was Registered has none of the information populated from my web service call.

My question is there a way to stop the system pre-bootstrapper to allow the userprincipal object to acquire the information? Or should I be doing something different?

Thanks
Developer
Apr 3, 2013 at 8:47 PM
Hi,

Based on my understanding, what you need is to wait for the asynchronous operation to complete before initializing the modules. Hence, you need to invoke the InitializeModules method after the "completed event" is raised. As far as I know, the Bootstrapper is not prepared to wait for asynchronous calls to finish, but you can override its Run method to change the bootstrapping logic to fit your needs.

For example, as a possible approach, you could override the Run method of your bootstrapper and copy the original code, with the difference that instead of calling the InitializeModules method directly, you could first check if the operation was completed (for example with the UserId property) and if not, you could subscribe to the UserInitialized event and invoke the InitializeModules method inside the event handler (remember to unsubscribe from this event.) You can find the Run method's base code in the UnityBoostrapper (in case you are using Unity) or in the MefBootstrapper (in case you are using MEF) in the Prism library's source code.

Like this, the you can ensure that when the modules are initialized the information required from the web service is available. Also, this would not block the rest of the of the initialization process as the initialization of the modules is the last step in the bootstrapping sequence. You find more about this sequence here:
I hope this helps,

Damian Cherubini
http://blogs.southworks.net/dcherubini
Apr 4, 2013 at 5:21 PM
Damian,

First, thank you for your help. I believe you are putting me on the right track, unfortunately, I am very new to this. Below is the code for my App.xaml.cs, Bootstrapper.cs and UserPrincial.cs. When I run the project with my changes I am getting an error on the 'ServiceLocator.Current.GetInstance<IEventAggregator>().GetEvent<UserInitialized>().Subscribe(this.UserDone);' line in the Bootstrapper.Run procedure. It is giving me a Microsoft.Practices.ServiceLocation.ServiceLocator.Current threw an exception of type 'System.NullReferenceException.' Am I doing something wrong? Also, is calling the InitializeModules the only thing I would have to do in the UserDone function?


Here is what I did in my code.

App.xaml.cs
    private void Application_Startup(object sender, StartupEventArgs e)
    {
        string val = HtmlPage.Document.QueryString["GUID"];
        Bootstrapper bootStrapper = new Bootstrapper(this.SetupUserPrincipal(val));
        bootStrapper.Run(false);
    }
Bootstrapper.cs - Run override method, subscribing to the UserInitialize event. Once that event is done, call UserDone method that would supply the user object filled in. I would then like to Initialize the Modules
public class Bootstrapper : UnityBootstrapper
{
    UserPrincipal userPrincipal;

    public override void Run(bool runWithDefaultConfiguration)
    {
        if (!runWithDefaultConfiguration)
        {
            if (userPrincipal.UserID == 0)
            {
                ServiceLocator.Current.GetInstance<IEventAggregator>().GetEvent<UserInitialized>().Subscribe(this.UserDone);
            }
        }
    }

    public void UserDone(UserPrincipal user)
    {
        base.InitializeModules();
    }

    protected override DependencyObject CreateShell()
    {
        Shell rootShell = new Shell();
        Application.Current.RootVisual = rootShell;
        return rootShell;
    }

    protected override void ConfigureContainer()
    {
        Container.RegisterInstance(typeof(UserPrincipal), userPrincipal, new ContainerControlledLifetimeManager());

        base.ConfigureContainer();
    }

    public Bootstrapper(UserPrincipal principal)
    {
        this.userPrincipal = principal;
    }

    protected override IModuleCatalog CreateModuleCatalog()
    {
        ModuleCatalog modules = new ModuleCatalog();
        modules.AddModule(typeof(NSAMedia.VendorAutomation.Modules.VendorDetail.VendorDetailModule));
        modules.AddModule(typeof(NSAMedia.VendorAutomation.Modules.VendorSelection.VendorSelectionModule));
        modules.AddModule(typeof(NSAMedia.VendorAutomation.Modules.ProductSelection.ProductSelectionModule));
        modules.AddModule(typeof(NSAMedia.VendorAutomation.Modules.ProductDetails.ProductDetailsModule));
        return modules;
    }

}
UserPrincipal.cs
public class UserPrincipal
{
    private string _guid;
    private Int32 _userID = 0;
    private DateTime _lastAccess;

    public DateTime LastAccess
    {
        get { return this._lastAccess; }
    }

   public string GUID
    {
        get { return this._guid; }
    }

    public Int32 UserID
    {
        get { return this._userID; }
    }

    public UserPrincipal(string guid)
    {
        _guid = guid;

        LoadUserInfo();
    }

    public void LoadUserInfo()
    {
        UserSecurityServiceClient service = new UserSecurityServiceClient();
        service.GetUserByGUIDCompleted += new EventHandler<GetUserByGUIDCompletedEventArgs>(UserInfo);
        service.GetUserByGUIDAsync(this._guid);
    }

    void UserInfo(object sender, GetUserByGUIDCompletedEventArgs e)
    {
        NSAMedia.VendorAutomation.Infrastructure.UserSecurityService.Users serviceResponse = e.Result;

        for (int j = 0; j < serviceResponse.User.UserSecurity.Count; j++)
        {
            this._userID = serviceResponse.User.UserSecurity[j].userID;
            this._lastAccess = serviceResponse.User.UserSecurity[j].lastAccess;
        }
        ServiceLocator.Current.GetInstance<IEventAggregator>().GetEvent<UserInitialized>().Publish(this);
    }

}
}
Apr 4, 2013 at 6:26 PM
Hi,

I believe that Damian was referring to overriding the Run method and copying the original Run method code with your modifications like this:
public override void Run(bool runWithDefaultConfiguration)
{
    this.useDefaultConfiguration = runWithDefaultConfiguration;

    this.Logger = this.CreateLogger();
    if (this.Logger == null)
    {
        throw new InvalidOperationException(Resources.NullLoggerFacadeException);
    }

    this.Logger.Log(Resources.LoggerCreatedSuccessfully, Category.Debug, Priority.Low);

    this.Logger.Log(Resources.CreatingModuleCatalog, Category.Debug, Priority.Low);
    this.ModuleCatalog = this.CreateModuleCatalog();
    if (this.ModuleCatalog == null)
    {
        throw new InvalidOperationException(Resources.NullModuleCatalogException);
    }

    this.Logger.Log(Resources.ConfiguringModuleCatalog, Category.Debug, Priority.Low);
    this.ConfigureModuleCatalog();

    this.Logger.Log(Resources.CreatingUnityContainer, Category.Debug, Priority.Low);
    this.Container = this.CreateContainer();
    if (this.Container == null)
    {
        throw new InvalidOperationException(Resources.NullUnityContainerException);
    }

    this.Logger.Log(Resources.ConfiguringUnityContainer, Category.Debug, Priority.Low);
    this.ConfigureContainer();

    this.Logger.Log(Resources.ConfiguringServiceLocatorSingleton, Category.Debug, Priority.Low);
    this.ConfigureServiceLocator();

    this.Logger.Log(Resources.ConfiguringRegionAdapters, Category.Debug, Priority.Low);
    this.ConfigureRegionAdapterMappings();

    this.Logger.Log(Resources.ConfiguringDefaultRegionBehaviors, Category.Debug, Priority.Low);
    this.ConfigureDefaultRegionBehaviors();

    this.Logger.Log(Resources.RegisteringFrameworkExceptionTypes, Category.Debug, Priority.Low);
    this.RegisterFrameworkExceptionTypes();

    this.Logger.Log(Resources.CreatingShell, Category.Debug, Priority.Low);
    this.Shell = this.CreateShell();
    if (this.Shell != null)
    {
        this.Logger.Log(Resources.SettingTheRegionManager, Category.Debug, Priority.Low);
        RegionManager.SetRegionManager(this.Shell, this.Container.Resolve<IRegionManager>());

        this.Logger.Log(Resources.UpdatingRegions, Category.Debug, Priority.Low);
        RegionManager.UpdateRegions();

        this.Logger.Log(Resources.InitializingShell, Category.Debug, Priority.Low);
        this.InitializeShell();
    }
            
    if (this.Container.IsRegistered<IModuleManager>())
    {
        //Patch to wait for the userPrincipal before initializing the modules
        if (userPrincipal.UserID == 0)
        {
            ServiceLocator.Current.GetInstance<IEventAggregator>().GetEvent<UserInitialized>().Subscribe(this.UserDone);
        }
        else
        {
            this.Logger.Log(Resources.InitializingModules, Category.Debug, Priority.Low);
            this.InitializeModules();
            this.Logger.Log(Resources.BootstrapperSequenceCompleted, Category.Debug, Priority.Low);
         }
    }
}

public void UserDone(UserPrincipal user)
{
    ServiceLocator.Current.GetInstance<IEventAggregator>().GetEvent<UserInitialized>().Unsubscribe(this.UserDone);
    this.Logger.Log(Resources.InitializingModules, Category.Debug, Priority.Low);
    this.InitializeModules();
    this.Logger.Log(Resources.BootstrapperSequenceCompleted, Category.Debug, Priority.Low);
}
In your case, you were receiving that System.NullReferenceException because the ServiceLocator was never initialized in your Run method.

Hope this helps,

Federico Martinez
http://blogs.southworks.net/fmartinez
Apr 5, 2013 at 3:28 PM
Federico,

Thank you for your help. I have a couple of issues that are stopping me from getting to my ultimate goal.
  1. On all of the Logger.Log lines, I am getting a Resource error. My system is not recognizing the Resources.LoggerCreatedSuccessfully
    Is this a file that I should be creating with constants.
  2. My bigger issue involves the CreateModuleCatalog. I might not have explained myself correctly in the initial issue.
    Here is what I want my CreateModuleCatalog to do.
    protected override IModuleCatalog CreateModuleCatalog()
    {
        ModuleCatalog modules = new ModuleCatalog();
        modules.AddModule(typeof(NSAMedia.VendorAutomation.Modules.VendorDetail.VendorDetailModule));
        if (userPrincipal.UserID != 3417)
        {
            modules.AddModule(typeof(NSAMedia.VendorAutomation.Modules.VendorSelection.VendorSelectionModule));
        }
        modules.AddModule(typeof(NSAMedia.VendorAutomation.Modules.ProductSelection.ProductSelectionModule));
        modules.AddModule(typeof(NSAMedia.VendorAutomation.Modules.ProductDetails.ProductDetailsModule));
        return modules;
    }
    
I want to wait for my UserPrincipal to complete (which the system is allowing) because I want to add some modules based on who is logging in. In the code you supplied, I am still calling the CreateModuleCatalog, ConfigureModuleCatalog, CreateContainer, ConfigureContainer, ... before the web service is done. In this case my userPrincipal.UserID = 0 and all Modules are loaded, even though after the service is done, userPrinicipal.userID = 3417 and the VendorSelection module should not be loaded. I was hoping to do something like the following but cannot because the ServiceLocator is null again as in my previous entry.
    public override void Run(bool runWithDefaultConfiguration)
    {
        //this.useDefaultConfiguration = runWithDefaultConfiguration;
        ServiceLocator.Current.GetInstance<IEventAggregator>().GetEvent<UserInitialized>().Subscribe(this.UserDone);
    }

    public void UserDone(UserPrincipal user)
    {
        ServiceLocator.Current.GetInstance<IEventAggregator>().GetEvent<UserInitialized>().Unsubscribe(this.UserDone);

        this.Logger = this.CreateLogger();
        if (this.Logger == null)
        {
            //throw new InvalidOperationException(Resources.NullLoggerFacadeException);
        }

        //this.Logger.Log(Resources.LoggerCreatedSuccessfully, Category.Debug, Priority.Low);

        //this.Logger.Log(Resources.CreatingModuleCatalog, Category.Debug, Priority.Low);
        this.ModuleCatalog = this.CreateModuleCatalog();
        if (this.ModuleCatalog == null)
        {
            //throw new InvalidOperationException(Resources.NullModuleCatalogException);
        }

        //this.Logger.Log(Resources.ConfiguringModuleCatalog, Category.Debug, Priority.Low);
        this.ConfigureModuleCatalog();

        //this.Logger.Log(Resources.CreatingUnityContainer, Category.Debug, Priority.Low);
        this.Container = this.CreateContainer();
        if (this.Container == null)
        {
            //throw new InvalidOperationException(Resources.NullUnityContainerException);
        }

        //this.Logger.Log(Resources.ConfiguringUnityContainer, Category.Debug, Priority.Low);
        this.ConfigureContainer();

        //this.Logger.Log(Resources.ConfiguringServiceLocatorSingleton, Category.Debug, Priority.Low);
        this.ConfigureServiceLocator();

        //this.Logger.Log(Resources.ConfiguringRegionAdapters, Category.Debug, Priority.Low);
        this.ConfigureRegionAdapterMappings();

        //this.Logger.Log(Resources.ConfiguringDefaultRegionBehaviors, Category.Debug, Priority.Low);
        this.ConfigureDefaultRegionBehaviors();

        //this.Logger.Log(Resources.RegisteringFrameworkExceptionTypes, Category.Debug, Priority.Low);
        this.RegisterFrameworkExceptionTypes();

        //this.Logger.Log(Resources.CreatingShell, Category.Debug, Priority.Low);
        this.Shell = this.CreateShell();
        if (this.Shell != null)
        {
            //this.Logger.Log(Resources.SettingTheRegionManager, Category.Debug, Priority.Low);
            RegionManager.SetRegionManager(this.Shell, this.Container.Resolve<IRegionManager>());

            //this.Logger.Log(Resources.UpdatingRegions, Category.Debug, Priority.Low);
            RegionManager.UpdateRegions();

            //this.Logger.Log(Resources.InitializingShell, Category.Debug, Priority.Low);
            this.InitializeShell();
        }

        if (this.Container.IsRegistered<IModuleManager>())
        {
            //this.Logger.Log(Resources.InitializingModules, Category.Debug, Priority.Low);
            this.InitializeModules();
            //this.Logger.Log(Resources.BootstrapperSequenceCompleted, Category.Debug, Priority.Low);
        }
    }

    protected override void ConfigureContainer()
    {
        Container.RegisterInstance(typeof(UserPrincipal), userPrincipal, new ContainerControlledLifetimeManager());

        base.ConfigureContainer();
    }


   protected override IModuleCatalog CreateModuleCatalog()
    {
        ModuleCatalog modules = new ModuleCatalog();
        modules.AddModule(typeof(NSAMedia.VendorAutomation.Modules.VendorDetail.VendorDetailModule));
        if (userPrincipal.UserID != 3417)
        {
            modules.AddModule(typeof(NSAMedia.VendorAutomation.Modules.VendorSelection.VendorSelectionModule));
        }
        modules.AddModule(typeof(NSAMedia.VendorAutomation.Modules.ProductSelection.ProductSelectionModule));
        modules.AddModule(typeof(NSAMedia.VendorAutomation.Modules.ProductDetails.ProductDetailsModule));
        return modules;
    }

Apr 5, 2013 at 5:25 PM
Hi,

Based on my understading, your issues could be fixed the following way:

1- Regarding the Log lines, they are related to Prism debugging and you can remove them since they don't affect the functionality of your application

2- Regarding the problem with the ModuleCatalog, if you need to use UserPrincipal at the time you are configuring your ModuleCatalog, then you can just include it to the section of the code that was related to this UserPrincipal. Therefore, your Bootstrapper should look like this:
public override void Run(bool runWithDefaultConfiguration)
{
    this.useDefaultConfiguration = runWithDefaultConfiguration;

    this.Logger = this.CreateLogger();
    if (this.Logger == null)
    {
        throw new InvalidOperationException("NullLoggerFacadeException");
    }

    this.Container = this.CreateContainer();
    if (this.Container == null)
    {
        throw new InvalidOperationException("NullUnityContainerException");
    }

    this.ConfigureContainer();

    this.ConfigureServiceLocator();

    this.ConfigureRegionAdapterMappings();

    this.ConfigureDefaultRegionBehaviors();

    this.RegisterFrameworkExceptionTypes();

    this.Shell = this.CreateShell();
    if (this.Shell != null)
    {
        RegionManager.SetRegionManager(this.Shell, this.Container.Resolve<IRegionManager>());

        RegionManager.UpdateRegions();

        this.InitializeShell();
    }
            
    if (this.Container.IsRegistered<IModuleManager>())
    {
        //Patch to wait for the userPrincipal before initializing the modules
        if (userPrincipal.UserID == 0)
        {
            ServiceLocator.Current.GetInstance<IEventAggregator>().GetEvent<UserInitialized>().Subscribe(this.UserDone);
        }
        else
        {
            this.DoModuleConfig();
        }
    }
}

public void UserDone(UserPrincipal user)
{
    ServiceLocator.Current.GetInstance<IEventAggregator>().GetEvent<UserInitialized>().Unsubscribe(this.UserDone);
    this.DoModuleConfig();
}

public void DoModuleConfig()
{
    this.ModuleCatalog = this.CreateModuleCatalog();
    if (this.ModuleCatalog == null)
    {
        throw new InvalidOperationException("NullModuleCatalogException");
    }

    this.ConfigureModuleCatalog();
    this.InitializeModules();
}
Hope this helps,

Federico Martinez
http://blogs.southworks.net/fmartinez
Apr 25, 2013 at 4:23 PM
Federico,

Thank you for your reply. Sorry for the delay.

I am getting an error in the Bootstrapper Run function on line this.ConfigureContainer();
 protected override void ConfigureContainer()
        {
            Container.RegisterInstance(typeof(UserPrincipal), userPrincipal, new ContainerControlledLifetimeManager());

            base.ConfigureContainer();
        }
The error is on the base.ConfigureContainer line. It states Value cannot be null. Parameter name instance.

Please let me know if I am doing something wrong.
Developer
Apr 25, 2013 at 6:52 PM
Edited Apr 25, 2013 at 7:05 PM
Hi,

Based on my understanding, the default ConfigureContainer method of the bootstrapper tries to register the ModuleCatalog in the container. However, as we delayed the creation of the ModuleCatalog until the DoModuleConfig is executed, the ConfigureContainter method fails. A possible approach to workaround this is to include the logic of the original ConfigureContainer method in the overriding version but without registering the ModuleCatalog. Then, you could register the ModuleCatalog in the DoModuleConfig method. As a result, your ConfigureContainer method should be like this:
protected override void ConfigureContainer()
{
    this.Container.AddNewExtension<UnityBootstrapperExtension>();

    Container.RegisterInstance<ILoggerFacade>(Logger);

    RegisterTypeIfMissing(typeof(IServiceLocator), typeof(UnityServiceLocatorAdapter), true);
    RegisterTypeIfMissing(typeof(IModuleInitializer), typeof(ModuleInitializer), true);
    RegisterTypeIfMissing(typeof(IModuleManager), typeof(ModuleManager), true);
    RegisterTypeIfMissing(typeof(RegionAdapterMappings), typeof(RegionAdapterMappings), true);
    RegisterTypeIfMissing(typeof(IRegionManager), typeof(RegionManager), true);
    RegisterTypeIfMissing(typeof(IEventAggregator), typeof(EventAggregator), true);
    RegisterTypeIfMissing(typeof(IRegionViewRegistry), typeof(RegionViewRegistry), true);
    RegisterTypeIfMissing(typeof(IRegionBehaviorFactory), typeof(RegionBehaviorFactory), true);
    RegisterTypeIfMissing(typeof(IRegionNavigationJournalEntry), typeof(RegionNavigationJournalEntry), false);
    RegisterTypeIfMissing(typeof(IRegionNavigationJournal), typeof(RegionNavigationJournal), false);
    RegisterTypeIfMissing(typeof(IRegionNavigationService), typeof(RegionNavigationService), false);
    RegisterTypeIfMissing(typeof(IRegionNavigationContentLoader), typeof(RegionNavigationContentLoader), true);

    Container.RegisterInstance(typeof(UserPrincipal), userPrincipal, new ContainerControlledLifetimeManager());
}
And the DoModuleConfig like this:
public void DoModuleConfig()
{
    this.ModuleCatalog = this.CreateModuleCatalog();
    if (this.ModuleCatalog == null)
    {
        throw new InvalidOperationException("NullModuleCatalogException");
    }

    this.Container.RegisterInstance(this.ModuleCatalog);

    this.ConfigureModuleCatalog();
    this.InitializeModules();
}
Regards,

Damian Cherubini
http://blogs.southworks.net/dcherubini
Apr 25, 2013 at 7:38 PM
Damian and Federico

This seems to do it. I thank you for your knowledge.