Hi,
I'm hoping someone can educate me as to what's going on here. I'm using Csla 3.03 and WPF.
I have a very simple menu form that is my main application form. Here's the class declaration, along with the constructor, the InitInvoke handler for the DataPortal, and the handler for one of the menu items.
Partial
Public Class MyMenu Public Sub New() ' This call is required by the Windows Form Designer.InitializeComponent()
AddHandler Csla.DataPortal.DataPortalInitInvoke, AddressOf InitInvokeHandler
' Detect whether a valid Security principal has been set. If not, call one of the Logins
If Application.principal Is Nothing Then Dim frm As AutoLogin = New AutoLogin() End If End SubPublic Sub InitInvokeHandler(ByVal obj As Object) ' This routine makes sure that the current thread has a copy of the logged on Principal for security purposes If Not ReferenceEquals(Application.principal, Csla.ApplicationContext.User) Then
Csla.ApplicationContext.User = Application.principal
End If End Sub Private Sub ItemBtn_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)Cursor = Cursors.Wait
Csla.ApplicationContext.User = Application.principal 'Will not work without this
Dim selection As EditFrm = New EditFrmCursor = Cursors.Arrow
Me.Visibility = System.Windows.Visibility.Hiddenselection.ShowDialog()
Me.Visibility = System.Windows.Visibility.Visible End SubDoesn't get any simpler than this. The AutoLogin form is a special version of my regular Login that automatically Logs in my test user. It sets both ApplicationContext.User and my global user "cache" as described in the Csla 3.0 e-book. I've located the global static cache variable in the WPF 'Application' object. AutoLogin also closes itself.
If I single step through the constructor above, after the call to instantiate AutoLogin in fact all the way through the End Sub for the Constructor, Application.principal (the User cache), and ApplicationContext.User are set correctly and agree with each other.
But when I click one of the items on the displayed menu form and its handler is run, ApplicationContext.User has reverted to the "generic" user. Application.principal still contains the correct User object. Hence the call to reset ApplicationContext.User to Application.principal in the button handler. But why do I need to do this???
In trying to de-bug this, I set a breakpoint in the ApplicationContext.User Property.Set accessor in Csla. It breaks when AutoLogin initially sets the value, and breaks again when explicitly set in the handler. But nothing else seems to be changing it. I guess it could be that the constructor for the main form is running in a different thread from the button handler?
One final observation / puzzlement. I'm running a "local" remote Dataportal, which means that I think I'm using IIS on my local computer to host the Server side Dataportal. But each time the ApplicationContext.User property runs, HttpContext.User is Nothing, and ApplicationContext.User gets set to the Thread.Current value. Is this correct?
Thanks for your help.
BBM
The local data portal means the "server side" components run in the client process. There literally is no server, no cross-process communication, nothing. So there's no HttpContext, because your code is running in the WPF client process.
The principal disappearing in WPF is a known issue (Msft calls it a feature) of WPF, where they clear the principal. Total pain in the @$$. I discuss a workaround in the Using CSLA .NET 3.0 ebook. It isn't perfect, but it is the best I've come up with thus far...
Hi Rocky.
Thanks for your response.
I'm still puzzled about the "server side" when using a remote DataPortal that is hosted on the development machine. So even though the DataPortal is hosted by IIS and I have to "attach" to the ASP worker process to de-bug it - it's running in the client process??
Also, if I understand you correctly about the "lost" principal, the workaround in the 3.0 e-book just fixes DataPortal access. There are other places in WPF (like the one I stumbled over) where the principal can be lost?
Thanks again.
BBM
Read chapters 2 and 4 to understand the data portal.
The data portal runs in 1 of 2 modes.
In local mode THERE IS NO SERVER. At all. It is not hosted in
IIS. There is no server. The “server-side” components run in the
client process.
In remote mode there is a server. It might be hosted in IIS, a
custom Windows service, a remote EXE, Windows Activation Service or anywhere
else you can host WCF, remoting, asmx or COM+. For testing we have a “server”
that simply runs on a different thread in the same Windows process – but it
is truly separate from the client, which is what counts.
You are right, my ebook technique only fixes the data portal
issue with the principal. There are other places in WPF you can get into
trouble. I don’t have a good answer, and I think Microsoft really owes us
a good answer, because their design in WPF seems really flawed to me.
Rocky
I'm working on Chapter 19 now - the WPF UI chapter. So I took the time to run through this problem (the issue with a principal) in some more detail this morning.
It is a hard problem. The solutions offered by Microsoft are not good ones - specifically they recommend setting the default principal for the AppDomain as the app starts up. Which is silly, because the user has to be able to log in and out while the app is running...
Not only that, but when I tried their technique, it seemed to conflict with WCF - causing the client-side WCF proxy to lock up. I don't know why - hard to debug a total freeze...
So, after spending hours on that, I tried a different approach. This approach appears to work quite well - for CSLA code. I don't know that it will totally fix .NET code (like CAS), but it absolutely makes CSLA code work, along with any other code that drives off Csla.ApplicationContext.User.
Take the User region from this code
http://www.lhotka.net/cslacvs/viewvc.cgi/trunk/cslacs/Csla/ApplicationContext.cs?view=markup
and replace your User region in ApplicationContext.
This should allow you to skip the data portal InitInvoke event and everything - it just works. Notice how the User property now detects whether the code is running in WPF and manages the principal itself in that case. By using a static field, I'm making the value available across all threads.
If you can try this and confirm that my change is correct, that'd be very helpful - thanks!
Hi Rocky,
No problem. I'll let you know how it works.
I really like WPF and want to use it, but sometimes I think they should have named it WTF.
BBM
Hi Rocky,
I compared the User region from your link to what I'm using.
I'm using the VB version of Csla, so I had to translate the C# to VB. The only difference that I see from what I'm using (3.03) is that the User Property is declared static, but since ApplicationContext in the VB version is a module and not a class, User is already treated as static. The editor/compiler won't even allow me to make the User property shared.
So I'm confused as to what is changed?
BBM
Private _principal As
IPrincipal
''' <summary>
''' Get or set the current <see
cref="IPrincipal" />
''' object representing the user's identity.
''' </summary>
''' <remarks>
''' This is discussed in Chapter 5. When running
''' under IIS the HttpContext.Current.User value
''' is used, otherwise the current Thread.CurrentPrincipal
''' value is used.
''' </remarks>
Public Property
User() As IPrincipal
Get
Dim current As
IPrincipal
If HttpContext.Current IsNot
Nothing Then
current = HttpContext.Current.User
ElseIf System.Windows.Application.Current IsNot Nothing Then
If _principal Is
Nothing Then
If ApplicationContext.AuthenticationType <>
"Windows" Then
_principal = New
Csla.Security.UnauthenticatedPrincipal()
Else
_principal = New
WindowsPrincipal(WindowsIdentity.GetCurrent())
End If
End If
current = _principal
Else
current = Thread.CurrentPrincipal
End If
Return current
End Get
Set(ByVal value As IPrincipal)
If HttpContext.Current IsNot
Nothing Then
HttpContext.Current.User = value
ElseIf System.Windows.Application.Current IsNot Nothing Then
_principal = value
End If
Thread.CurrentPrincipal
= value
End Set
End Property
Hi Rocky,
This is working fine. I've got to run now, but I'll test it some more tonight and let you know if I run into anything.
BBM
Good, I appreciate it!
I’ve been using the changed code for a couple days now,
and it does seem to make everything much nicer.
Rocky
Certainly in 3.6, and I think I made the change to 3.5.2 as well
(check the change log).
Of course “fix” is somewhat of a strong word. I
fixed ApplicationContext.User, NOT all of .NET. So CSLA will work, and your
code will work (if you use ApplicationContext.User), but things like CAS or
other pure .NET features won’t work, because I’m not able to change
how .NET manages the principal for itself.
Rocky
From: ajj3085
[mailto:cslanet@lhotka.net]
Sent: Friday, September 26, 2008 2:39 PM
To: rocky@lhotka.net
Subject: Re: [CSLA .NET] RE: RE: RE: ApplicationContext.User is lost in
Main Window
I'm hitting this bug..
In what version is the above fix included?
Copyright (c) Marimer LLC