Frage

Ich habe einen WCF-Dienst, der seinen hat Thread.CurrentPrincipal eingestellt in der ServiceConfiguration.ClaimsAuthorizationManager.

Wenn ich den Dienst asynchron wie folgt umsetze:

    public IAsyncResult BeginMethod1(AsyncCallback callback, object state)
    {
        // Audit log call (uses Thread.CurrentPrincipal)

        var task = Task<int>.Factory.StartNew(this.WorkerFunction, state);

        return task.ContinueWith(res => callback(task));
    }

    public string EndMethod1(IAsyncResult ar)
    {
        // Audit log result (uses Thread.CurrentPrincipal)

        return ar.AsyncState as string;
    }

    private int WorkerFunction(object state)
    {
        // perform work
    }

Ich finde, dass Thread.CurrentPrincipal in der Begin-Methode und auch in der WorkerFunction auf den richtigen ClaimsPrincipal gesetzt ist, in der End-Methode jedoch auf einen GenericPrincipal.

Ich weiß, dass ich die ASP.NET-Kompatibilität für den Dienst und die Verwendung aktivieren kann HttpContext.Current.User Das hat in allen Methoden das richtige Prinzip, aber ich würde das lieber nicht tun.

Gibt es eine Möglichkeit, den Thread.CurrentPrincipal auf den richtigen ClaimsPrincipal zu zwingen, ohne die ASP.NET-Kompatibilität zu aktivieren?

War es hilfreich?

Lösung

Beginnend mit a Zusammenfassung der WCF-Erweiterungspunkte, sehen Sie diejenige, die speziell zur Lösung Ihres Problems entwickelt wurde.Es heißt a CallContextInitializer.Schau dir das an Artikel, der CallContextInitializer-Beispielcode enthält.

Wenn Sie eine ICallContextInitializer-Erweiterung erstellen, erhalten Sie die Kontrolle über den BeginXXX-Thread-Kontext UND der EndXXX-Thread-Kontext.Sie sagen, dass der ClaimsAuthorizationManager den Benutzerprinzipal in Ihrer BeginXXX(...)-Methode korrekt festgelegt hat.In diesem Fall erstellen Sie dann selbst einen benutzerdefinierten ICallContextInitializer, der den CurrentPrincipal entweder zuweist oder aufzeichnet, je nachdem, ob er Ihren BeginXXX() oder Ihren EndXXX() verarbeitet.Etwas wie:

public object BeforeInvoke(System.ServiceModel.InstanceContext instanceContext, System.ServiceModel.IClientChannel channel, System.ServiceModel.Channels.Message request){
    object principal = null;
    if (request.Properties.TryGetValue("userPrincipal", out principal))
    {
        //If we got here, it means we're about to call the EndXXX(...) method.
        Thread.CurrentPrincipal = (IPrincipal)principal;
    }
    else
    {
        //If we got here, it means we're about to call the BeginXXX(...) method.
        request.Properties["userPrincipal"] = Thread.CurrentPrincipal;            
    }
    ...
 }

Betrachten Sie zur weiteren Verdeutlichung zwei Fälle.Angenommen, Sie haben sowohl einen ICallContextInitializer als auch einen IParameterInspector implementiert.Angenommen, diese Hooks sollen mit einem synchronen WCF-Dienst und mit einem asynchronen WCF-Dienst ausgeführt werden (was Ihr Sonderfall ist).

Nachfolgend finden Sie den Ablauf der Ereignisse und die Erklärung des Geschehens:

Synchroner Fall

ICallContextInitializer.BeforeInvoke();
IParemeterInspector.BeforeCall();
//...service executes...
IParameterInspector.AfterCall();
ICallContextInitializer.AfterInvoke();

Im obigen Code ist nichts Überraschendes.Aber schauen Sie sich nun unten an, was mit asynchronen Dienstvorgängen passiert ...

Asynchroner Fall

ICallContextInitializer.BeforeInvoke();  //TryGetValue() fails, so this records the UserPrincipal.
IParameterInspector.BeforeCall();
//...Your BeginXXX() routine now executes...
ICallContextInitializer.AfterInvoke();

//...Now your Task async code executes (or finishes executing)...

ICallContextInitializercut.BeforeInvoke();  //TryGetValue succeeds, so this assigns the UserPrincipal.
//...Your EndXXX() routine now executes...
IParameterInspector.AfterCall();
ICallContextInitializer.AfterInvoke();

Wie Sie sehen können, stellt der CallContextInitializer sicher, dass Sie die Möglichkeit haben, Werte wie Ihren CurrentPrincipal unmittelbar vor der Ausführung der EndXXX()-Routine zu initialisieren.Daher spielt es keine Rolle, dass die EndXXX()-Routine mit Sicherheit in einem anderen Thread ausgeführt wird als die BeginXXX()-Routine.Und ja, das System.ServiceModel.Channels.Message Das Objekt, das Ihren Benutzerprinzipal zwischen Begin/End-Methoden speichert, wird von WCF beibehalten und ordnungsgemäß übertragen, auch wenn sich der Thread geändert hat.

Insgesamt ermöglicht dieser Ansatz, dass Ihr EndXXX(IAsyncresult) mit dem richtigen IPrincipal ausgeführt wird, ohne dass der CurrentPrincipal explizit in der EndXXX()-Routine neu eingerichtet werden muss.Und wie bei jedem WCF-Verhalten können Sie entscheiden, ob dies für einzelne Vorgänge, alle Vorgänge in einem Vertrag oder alle Vorgänge auf einem Endpunkt gilt.

Andere Tipps

Nicht wirklich die Antwort auf meine Frage, aber ein alternativer Ansatz zur Implementierung des WCF-Dienstes (in .NET 4.5), bei dem nicht die gleichen Probleme auftreten Thread.CurrentPrincipal.

    public async Task<string> Method1()
    {
        // Audit log call (uses Thread.CurrentPrincipal)

        try
        {
            return await Task.Factory.StartNew(() => this.WorkerFunction());
        }
        finally 
        {
            // Audit log result (uses Thread.CurrentPrincipal)
        }
    }

    private string WorkerFunction()
    {
        // perform work
        return string.Empty;
    }

Der gültige Ansatz hierfür besteht darin, eine Erweiterung zu erstellen:

public class SLOperationContext : IExtension<OperationContext>
{
    private readonly IDictionary<string, object> items;

    private static ReaderWriterLockSlim _instanceLock = new ReaderWriterLockSlim();

    private SLOperationContext()
    {
        items = new Dictionary<string, object>();
    }

    public IDictionary<string, object> Items
    {
        get { return items; }
    }

    public static SLOperationContext Current
    {
        get
        {
            SLOperationContext context = OperationContext.Current.Extensions.Find<SLOperationContext>();
            if (context == null)
            {
                _instanceLock.EnterWriteLock();
                context = new SLOperationContext();
                OperationContext.Current.Extensions.Add(context);
                _instanceLock.ExitWriteLock();
            }
            return context;
        }
    }

    public void Attach(OperationContext owner) { }
    public void Detach(OperationContext owner) { }
}

Jetzt wird diese Erweiterung als Container für Objekte verwendet, die Sie zwischen Threadwechseln beibehalten möchten, da OperationContext.Current gleich bleibt.

Jetzt können Sie dies in BeginMethod1 verwenden, um den aktuellen Benutzer zu speichern:

SLOperationContext.Current.Items["Principal"] = OperationContext.Current.ClaimsPrincipal;

Und dann können Sie in EndMethod1 den Benutzer abrufen, indem Sie Folgendes eingeben:

ClaimsPrincipal principal = SLOperationContext.Current.Items["Principal"];

BEARBEITEN (ein anderer Ansatz):

public IAsyncResult BeginMethod1(AsyncCallback callback, object state)
{
    var task = Task.Factory.StartNew(this.WorkerFunction, state);

    var ec = ExecutionContext.Capture();

    return task.ContinueWith(res =>
        ExecutionContext.Run(ec, (_) => callback(task), null));
}
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top