Click here to Skip to main content
14,541,375 members
Rate this:
Please Sign up or sign in to vote.
I've been searching the web for this, and couldn't really find a solution that actually worked. Situation is as follows: I've got a WPF application, where I want to present the user with a simple logon form. Trying to work MVVM, so I've got a LoginViewModel with the following code behind the login command:

try
            {
                WithClient(servfact.GetServiceClient<IAccountService>(), proxy =>
                {
                    principal = proxy.AuthenticateUser(Login, password);
                });
                Thread.CurrentPrincipal = principal;
            }
            catch(...) { ... }


"WithClient" is a short method in my viewmodel baseclass, which I use to instantiate and dispose of my service proxies:

protected void WithClient<T>(T proxy, Action<T> codeToExecute)
{
    try { codeToExecute(proxy); }
    finally
    {
        IDisposable toDispose = (proxy as IDisposable);
        if(toDispose != null) { toDispose.Dispose(); }
    }
}


Now, most of my services are Async, and I've got an async variant of WithClient going on, which also works fine:

protected async Task WithClientAsync<T>(T proxy, Func<T, Task> codeToExecute)
{
    try { await codeToExecute(proxy); }
    finally
    {
        IDisposable toDispose = (proxy as IDisposable);
        if(toDispose != null) { toDispose.Dispose(); }
    }
}


The trouble begins whenever I also want to do the login asynchronously. Obviously I don't want the UI to freeze up as I do the login (or visit any WCF service for that matter). That in itself is working fine, but the problem sits in the piece of code where I set the CurrentPrincipal. This problem is probably familiar to most of you: it seems to set it just fine. Then in my program I want to use the CurrentPrincipal (either on the client side or to send the users login to a WCF service in a messageheader), but it seems to be reset to a standard GenericPrincipal. When I revert the login back to being synchronous, the CurrentPrincipal is just fine. So in short: how do I set the principal in the asynchronous code, having it persist later on, instead of reverting back to a standard principal?
Posted
Updated 10-Apr-19 2:13am
v2
Rate this:
Please Sign up or sign in to vote.

Solution 1

This is a very interesting problem. :)

My first thought was that the Thread.CurrentPrincipal depends on the current thread, and an async method would end up setting it on the wrong thread. However, it appears that this property is associated with the logical call context, which correctly flows across threads in an async method.

However, it seems that any changes you make to the logical call context within an async method are discarded when the method completes. This only applies to .NET 4.5; any code targetting .NET 4.0 and using the Microsoft.Bcl.Async library will not exhibit this behaviour!

Stephen Cleary has an answer on StackOverflow[^] which discusses this problem in relation to ASP.NET, and a blog post[^] which is related to the problem:

In .NET 4.5., async methods interact with the logical call context so that it will more properly flow with async methods. ... In .NET 4.5, at the beginning of every async method, it activates a "copy-on-write" behavior for its logical call context. When (if) the logical call context is modified, it will create a local copy of itself first.


Unfortunately, there doesn't seem to be any way to change this behaviour. As soon as an async method returns, any changes to the CurrentPrincipal property will be lost.

You might be able to work around the problem by using the AppDomain.CurrentDomain.SetThreadPrincipal method[^]. However, this method can only be called once during the lifetime of an AppDomain. If your application allows users to sign out and then sign in again, you'll need to use a custom IPrincipal class to act as a façade to the real IPrincipal:
public sealed class CurrentPrincipalFacade : IPrincipal
{
    private static readonly CurrentPrincipalFacade _instance = new CurrentPrincipalFacade();
    
    private CurrentPrincipalFacade()
    {
    }
    
    public static CurrentPrincipalFacade Instance
    {
        get { return _instance; }
    }
    
    public IPrincipal WrappedValue { get; set; }
    
    public IIdentity Identity
    {
        get { return WrappedValue == null ? null : WrappedValue.Identity; }
    }
    
    public bool IsInRole(string role)
    {
        return WrappedValue != null && WrappedValue.IsInRole(role);
    }
}

// Startup code:
static void Main()
{
    AppDomain.CurrentDomain.SetThreadPrincipal(CurrentPrincipalFacade.Instance);
    ...
}

// Login:
await WithClientAsync(servfact.GetServiceClient<IAccountService>(), proxy =>
{
    CurrentPrincipalFacade.Instance.WrappedValue = proxy.AuthenticateUser(Login, password);
});
   
Rate this:
Please Sign up or sign in to vote.

Solution 2

Richard, thank you for your reply. Well, at least I'm glad that it's not just me ;) I searched extensively, and although I found some solutions for ASP.NET (specifically the one Stephen Cleary proposed), I didn't really find anything satisfying for WPF.

Initially, I went down the AppDomain route as well, but as you said yourself, my application must indeed have to capability of login off and in, so that's basically out of the question as an easy solution.

What I decided to try (and it works, although the 'proper' way of using the Thread.CurrentPrincipal would have been nice, after all it's made for it), was the following: I have a ObjectBase class, which I used as the baseclass for all my client-side entities. In that class I already have a public static CompositionContainer object, where I store all catalogs I need for dependency injection through MEF. I just included a public static ClaimsPrincipal object as well, and when I log in, I store the resulting ClaimsPrincipal that my authentication call returns in there. Upon loggin out, I simply clear that object. Seems to work just fine, as you'd expect.

So my login just looks like this now:

await WithClientAsync(servfact.GetServiceClient<iaccountservice>(), async proxy =>
{
    ObjectBase.ClaimsPrincipal = await proxy.AuthenticateUserAsync(Login, Hasher.CalculateHash(password, Login));
});


After review, I will probably change my code real quick, and use your solution, as it separates out the issue much better. Again, thanks for this!

Just for completeness: I left out some non-relevant stuff from the code, such as the encryption for the password and all of that.

So, all in all, the problem is fixed, but I would still hope that a future release would remedy this. Any ideas on how .NET 4.6 behaves in this scenario?
   
v3
Comments
Richard Deeming 23-Jun-15 14:09pm
   
I haven't tried 4.6 yet, but I doubt the behaviour's changed since 4.5; it would almost certainly be a "breaking" change.
[no name] 23-Jun-15 14:15pm
   
As expected. Anyway, thanks again for the help!
Rate this:
Please Sign up or sign in to vote.

Solution 4

If you want the thread to persist across new Windows you can set the thread like so. I saw there was an answer that contained more custom code when the reality is objects already exists

string[] = List of Roles {"a", "b", "c"}
"name" = the primary key from the database that holds the unique identity

AppDomain.CurrentDomain.SetThreadPrincipal(new GenericPrincipal(new GenericIdentity("name", WindowsIdentity.GetCurrent().AuthenticationType), string[]);

I hope this helps someone and thank you for reading.
   
Comments
[no name] 10-Apr-19 8:44am
   
Seriously, this question was asked 4 years ago... Everyone moved on I would hope ;-)
Xequence 10-Apr-19 8:56am
   
I just wanted to look cool...
Janis Pütz 2-May-19 10:09am
   
Who cares when the question was asked?

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)




CodeProject, 503-250 Ferrand Drive Toronto Ontario, M3C 3G8 Canada +1 416-849-8900 x 100