Resolve Class with Shared Properties Set

Topics: Prism v4 - WPF 4
Feb 16, 2011 at 8:41 PM

I am developing a WPF shell application with VS2010 that implements the MVVM pattern. I’m using the Prism 4.0 framework with the Unity 2.0 container. I cannot instantiate a class and set its properties during bootstrapper processing and resolve the class with the properties set during the instantiation of another class library implementing the Prism IModule interface. That is my goal. Am I on the right track with the process I describe below? If so am I missing a step? If I’m on the wrong track is there a better way to make application wide values available to all modules?

Authentication takes place in the application startup event where I store session information in a module reference type for later access. This occurs before the bootstrapper is run. I add a module class library I call ‘Maintenance’ in the bootstrapper ConfigureModuleCatalog() overrides setting the InitializeMode to OnDemand. In the ConfigureContainer() overrides of the bootstrapper I instantiate a session class named ‘SessionData’ that implements interface ISessionData, set its properties from the previously stored authentication information and register the instance as follows:

Dim SD As New SessionData
With SD
    .LocalSessionID = LocalSessionID
   .LocalCNS = LocalCNS
End With
Me.Container.RegisterInstance(Of ISessionData)("SessionData", SD)

 

In the InitializeShell() override of the boostrapper I can resolve the instance with no problem using the following statement:

Dim aSD As ISessionData = Container.Resolve(Of ISessionData)("SessionData")

 

When I launch the Maintenance module with button click processing and attempt to obtain a reference in the Maintenance module constructor with the same statement:

Dim sd As ISessionData = Me.container.Resolve(Of ISessionData)("SessionData")

 

it throws an error:

The current type, ISessionData, is an interface and cannot be constructed. Are you missing a type mapping?

I do not get an error with the following statement but the property values are not set:

Dim sd As Object = Me.container.Resolve(Of SessionData)("SessionData") 

In debug mode I can see that the container has the type registered.

I’ve also tried the RegisterInstance method in the bootstrapper using SessionData instead of ISessionData with similar results.

Developer
Feb 17, 2011 at 5:59 PM

Hi,

The situation you're explaining could be caused by the fact that you could be using another instance of the Unity Container to retrieve the instance in your module. One possibility to debug this would be to check if the Hash Code of the instances of the Unity Container are the same in your bootstrapper and your module.

In case you're not able to find out the cause of your issue, it would be helpful if you could provide us with a repro sample, so that we can help you further with this.

Additionally, you might find some useful information in the Unity forum.

I hope you find this helpful.

Guido Leandro Maliandi
http://blogs.southworks.net/gmaliandi

Feb 17, 2011 at 6:44 PM

Hello,

Thanks for comment. Hash code is the same in both instances. I'll put together a sample for examination. Hopefully its something simple I'm missing.

Chuck

Feb 18, 2011 at 7:10 PM

I’ve created a stripped down demo solution consisting of 2 projects, DemoModule and DemoShell which reproduces the problem I’ve described above. Would appreciate any comments.

The component I’m having a problem resolving with properties set is SessionData which implements ISessionData and which are present in both projects:

Namespace DemoProperty

    Public Class SessionData
        Implements ISessionData

        Private Shared _LocalCNS As String
        Private Shared _LocalSessionID As Integer
        Public Shared LCNS As String
        Public Shared LSID As Integer

        Public Property LocalCNS As String Implements ISessionData.LocalCNS
            Get
                Return _LocalCNS
            End Get
            Set(value As String)
                _LocalCNS = value
                LCNS = value
            End Set
        End Property

        Public Property LocalSessionID As Integer Implements ISessionData.LocalSessionID
            Get
                Return _LocalSessionID
            End Get
            Set(value As Integer)
                _LocalSessionID = value
                LSID = value
            End Set
        End Property

    End Class

End Namespace

 

Namespace DemoProperty
    Public Interface ISessionData
        Property LocalCNS As String
        Property LocalSessionID As Integer
    End Interface

End Namespace

 

The DemoModule project has a simple MaintenanceShell.xaml window. I had hoped to obtain a reference to the session information parameters with the following ModuleMaintenance class IModule implementation:

Imports Microsoft.Practices.Prism.Modularity
Imports Microsoft.Practices.Unity
Imports System.Windows
Namespace DemoProperty

    Public Class ModuleMaintenance
        Implements IModule

        Private ReadOnly container As IUnityContainer
        Private MaintenanceScreen As Window

        Public Sub New(ByVal container As IUnityContainer)
            Me.container = container
            Dim i As Integer = Me.container.GetHashCode()
            MessageBox.Show("Module Container Hash Code: " & CStr(i))
            Dim sd As ISessionData
            Try
                sd = Me.container.Resolve(Of ISessionData)("SessionData")
            Catch ex As Exception
                MessageBox.Show(ex.ToString)
            End Try
            Me.MaintenanceScreen = New MaintenanceShell
            Me.MaintenanceScreen.Show()
        End Sub

        Public Sub Initialize() Implements IModule.Initialize
        End Sub

    End Class

End Namespace

 The ‘try’ throws an error. If I change the resolve to ‘Of SessionData’ it does not throw an error but the the properties do not come across.

The DemoShell project launches with the following Application.xaml code behind where authentication information is stored in the ConnectionData module:

Namespace DemoProperty

    Partial Public Class App
        Inherits System.Windows.Application

        ' Application-level events, such as Startup, Exit, and DispatcherUnhandledException
        ' can be handled in this file.

        Protected Overrides Sub OnStartup(ByVal e As StartupEventArgs)
            MyBase.OnStartup(e)
            'PROCESS AUTHENTICATION
            ConnectionData.LocalSessionID = 1000
            ConnectionData.LocalCNS = "DBConnectionString"
            Dim TCB As New TxCoreBootstrapper()
            TCB.Run()
        End Sub

        Protected Overrides Sub OnExit(ByVal e As ExitEventArgs)
            MyBase.OnExit(e)
        End Sub

    End Class

End Namespace

 Namespace DemoProperty
    Module ConnectionData
        Public SessionID As String = ""
        Public LocalSessionID As Integer = 0
        Public LocalCNS As String = ""
        Public SessionInit As Boolean = False
    End Module

End Namespace

 

The bootstrapper configures the module catalog and container and initializes the shell as follows:

Imports Microsoft.Practices.Prism.Modularity
Imports Microsoft.Practices.Prism.UnityExtensions
Imports Microsoft.Practices.ServiceLocation
Imports Microsoft.Practices.Unity
Imports System.Windows
Namespace DemoProperty

    Public Class TxCoreBootstrapper
        Inherits UnityBootstrapper

        Protected Overrides Function CreateShell() As DependencyObject
            Return ServiceLocator.Current.GetInstance(Of DemoProperty.Shell)()
        End Function

        Protected Overrides Sub InitializeShell()
            MyBase.InitializeShell()
            App.Current.MainWindow = CType(Me.Shell, Window)
            App.Current.MainWindow.Show()
        End Sub

        Protected Overrides Sub ConfigureContainer()
            MyBase.ConfigureContainer()
            Dim SD As New SessionData
            SD.LocalSessionID = LocalSessionID
            SD.LocalCNS = LocalCNS
            Me.Container.RegisterInstance(Of ISessionData)("SessionData", SD)
            Dim i As Integer = Me.Container.GetHashCode()
            MessageBox.Show("Bootstrapper Container Hash Code: " & CStr(i))
        End Sub

        Protected Overrides Sub ConfigureModuleCatalog()
            Dim moduleMaintenanceType As Type = GetType(ModuleMaintenance)
            ModuleCatalog.AddModule(New ModuleInfo() _
                                    With {.ModuleName = moduleMaintenanceType.Name, _
                                    .ModuleType = moduleMaintenanceType.AssemblyQualifiedName, _
                                    .InitializationMode = InitializationMode.OnDemand})
        End Sub

    End Class

End Namespace

 The Shell.xaml window has 1 button and saves the IModuleManager implementation and sets the data context to the ShellViewModel with the following code behind:

Imports Microsoft.Practices.Prism.Modularity
Namespace DemoProperty
    Public Class Shell
        Inherits Window
        Private moduleManager As IModuleManager

        Public Sub New(ByVal ViewModel As ShellViewModel, _
                       ByVal moduleManager As IModuleManager)
            If moduleManager Is Nothing Then
                Throw New ArgumentNullException("moduleManager")
            End If
            Me.moduleManager = moduleManager
            ShellModuleMgr = moduleManager
            InitializeComponent()
            Me.DataContext = ViewModel
        End Sub

        Public Sub Window_Loaded(ByVal sender As ObjectByVal e As RoutedEventArgs)
           
        End Sub

    End Class

End Namespace

 

Namespace DemoProperty

    Module ShellModuleManager
        Public ShellModuleMgr As Microsoft.Practices.Prism.Modularity.IModuleManager
    End Module

End Namespace

 Imports System.ComponentModel
Imports Microsoft.Practices.Prism.Commands


Namespace DemoProperty

    Public Class ShellViewModel : Inherits FrameworkElement
        Implements INotifyPropertyChanged

        Public Sub New()

            Me.btnMaintenanceClickCmd = New DelegateCommand(Of Object) _
                        (AddressOf Me.btnMaintenanceClickProcess, AddressOf Me.CanBtnMaintenanceClick)
        End Sub

        Public Event btnMaintenanceClick As EventHandler(Of Microsoft.Practices.Prism.Events.DataEventArgs(Of ShellViewModel))

        Private _btnMaintenanceClickCmd As DelegateCommand(Of Object)

        Public Property btnMaintenanceClickCmd() As DelegateCommand(Of Object)
            Get
                Return _btnMaintenanceClickCmd
            End Get
            Private Set(ByVal value As DelegateCommand(Of Object))
                _btnMaintenanceClickCmd = value
            End Set

        End Property



        Private Function CanBtnMaintenanceClick(ByVal arg As ObjectAs Boolean
            Return True
        End Function


        Private Sub btnMaintenanceClickProcess(ByVal obj As Object)
            ShellModuleMgr.LoadModule("ModuleMaintenance")
        End Sub

        Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged

        Private Sub NotifyPropertyChanged(ByVal propertyName As String)
            If Me.PropertyChangedEvent IsNot Nothing Then
                RaiseEvent PropertyChanged(MeNew PropertyChangedEventArgs(propertyName))
            End If
        End Sub

    End Class

End Namespace

 

Developer
Feb 28, 2011 at 5:02 PM

Hi,

It could be helpful if you could upload your working sample to SkyDrive or a similar site, so as to make sure that there are no differences in the elements in your sample and the solution we would debug.

Guido Leandro Maliandi
http://blogs.southworks.net/gmaliandi

Feb 28, 2011 at 6:54 PM

I've put the sample in a '.zip' on SkyDrive. File name DemoPropertyPass.zip. Profile name 'chuck mattison'.

I've tried the following registerinstance syntax and can 'see' the set variables in container debug but still cannot resolve in the module.

Me.Container.RegisterInstance(Of ISessionData)("SessionData", SD, New ContainerControlledLifetimeManager ())

 

Feb 28, 2011 at 7:24 PM

Hi,

Could you provide us with the link in order to download your repro sample?

You can do this by positioning in the container folder where you upload the the zip file and clicking Get A Link in Share Menu.

Thanks,

Miguel Bronzovic
http://blogs.southworks.net/mbronzovic

 

Feb 28, 2011 at 7:32 PM

Thanks for the reply. Here is the link:

http://cid-369b7180ddc2e167.skydrive.live.com/redir.aspx?resid=369B7180DDC2E167!118

Developer
Mar 1, 2011 at 5:49 PM

Hi Chuck,

We've examined the sample you've uploaded and found that the issue with the Unity container you're experiencing is caused by the fact that you've two copies of ISessionData and SessionData, one in the DemoShell and one in the DemoModule projects.

When you register an instance or a type mapping in the bootstrapper, you're using the ones in the DemoShell project, but when you attempt to resolve it in the ModuleMaintenance constructor, you're trying to resolve the ones in the DemoModule project, hence the Unity container doesn't have them registered.

Since Unity can resolve a concrete type that hasn't been registered, if you try to resolve an instance of type SessionData, it will return an new instance of SessionData (from DemoModule). However, when you try to resolve an instance of type ISessionData, it throws an error since it doesn't find a type mapping for the ISessionData in DemoModule.

One possible way to achieve your desired scenario would be to place the ISessionData interface in an infrastructure project, and make both your shell project and your modules reference it. Then, you would be registering an instance or a type mapping pointing to the type in the infrastructure project, and also pointing to that type when resolving it from your modules.

I hope you find this helpful.

Guido Leandro Maliandi
http://blogs.southworks.net/gmaliandi

Mar 1, 2011 at 8:44 PM

Guido,
That did the trick. Moved ISessionData and its implementor SessionData to an infrastructure project.  Thanks for the suggestion. I was dead-ended.
Chuck