Domanda

Ho un servizio WCF che ha il suo gruppo Thread.CurrentPrincipal nel ServiceConfiguration.ClaimsAuthorizationManager.

Quando implemendo il servizio asincrono come questo:

    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
    }
.

Trovo che il filo.CurrentPrincipal è impostato sulla corretta rivendicazioniPrincipal nel metodo iniziale e anche nella confusione lavoratore, ma nel metodo finale è impostato su un genericprincipal.

So che posso abilitare la compatibilità ASP.NET per il servizio e utilizzare HttpContext.Current.User che ha il capitale corretto in tutti i metodi, ma preferirei non farlo.

C'è un modo per forzare il filo.CurrentPrincipal alla corretta rivendicazioniPrenChipal senza accendere la compatibilità ASP.NET?

È stato utile?

Soluzione

A partire da un Riassunto dei punti di estensione WCF , vedrai quello che è espressamente progettato per risolvere il tuo problema. Si chiama un callcontextinitializzatore . Dai un'occhiata a questo Articolo che dà il codice di esempio callconcontextinitilizzatore .

Se si effettua un'estensione ICallConTextinizializzatore, verrà fornito il controllo su entrambi i contesto del thread BeginXXX e il contesto del thread Endxxx. Stai dicendo che il reclamo AuthorizationManager ha correttamente stabilito il principio dell'utente nel tuo metodo BeginCexxx (...). In tal caso, si effettuano quindi un te stesso un custom ICallConTextInizializzatore che assegna o registra il currentPrincipal, a seconda che si tratti di gestire il tuo BeginXXX () o il tuo Endxxx (). Qualcosa come:

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;            
    }
    ...
 }
.

Per chiarire ulteriormente, considerare due casi. Supponiamo di aver implementato sia un ICallContextInizializzatore sia un iParameterInspector. Supponiamo che questi ganci dovrebbero eseguire con un servizio WCF sincrono e con un servizio WCF ASYNC (che è il tuo caso speciale).

Di seguito sono riportati la sequenza di eventi e la spiegazione di ciò che sta accadendo:

Custodia sincrona

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

Niente di sorprendente nel codice sopra. Ma ora guarda sotto quello che succede con le operazioni di servizio asincrono ...

caso asincrono

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();
.

Come puoi vedere, il callconcontextinitializzatore garantisce l'opportunità di inizializzare i valori come il tuo attualePrincipal poco prima della routine Endxxx (). Pertanto non importa che la routine ENDXXX () sicuramente sia eseguita su un filo diverso rispetto alla routine BeginXXX (). E sì, l'oggetto System.ServiceModel.Channels.Message che memorizza il principio dell'utente tra i metodi di inizio / fine, viene conservato e trasmesso correttamente da WCF anche se il thread è cambiato.

Complessivamente, questo approccio consente all'endxxx (IAsyncresult) di eseguire con il corretto IPRINCIPAL, senza dover ristabilire esplicitamente il CurrentPrincipal nella routine Endxxx (). E come con qualsiasi comportamento WCF, è possibile decidere se ciò si applica alle singole operazioni, tutte le operazioni su un contratto o tutte le operazioni su un endpoint.

Altri suggerimenti

Non realmente la risposta alla mia domanda, ma un approccio alternativo di implementare il servizio WCF (in .NET 4.5) che non mostra gli stessi problemi con 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;
    }
.

L'approccio valido a questo è quello di creare un'estensione:

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) { }
}
.

Ora questa estensione viene utilizzata come contenitore per oggetti che si desidera persistere tra la commutazione del filo come operazioneContext.Current rimarrà lo stesso.

Ora puoi usarlo in Beginmethod1 per salvare l'utente corrente:

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

E poi in endmethod1 puoi ottenere l'utente digitando:

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

Modifica (un altro approccio):

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));
}
.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top