Pregunta

Tengo un servicio WCF que tiene su Thread.CurrentPrincipal situado en el ServiceConfiguration.ClaimsAuthorizationManager.

Cuando me implementar el servicio de forma asincrónica como este:

    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
    }

Me parece que el Hilo.CurrentPrincipal se establece para la correcta ClaimsPrincipal en el principio del método y también en el WorkerFunction, pero en el Final del método es establecer un GenericPrincipal.

Sé que lo puedo permitir ASP.NET la compatibilidad para el servicio y la utilización de HttpContext.Current.User que tiene la correcta principal en todos los métodos, pero yo prefiero no hacerlo.

Es allí una manera de forzar el Hilo.CurrentPrincipal para la correcta ClaimsPrincipal sin encender ASP.NET compatibilidad?

¿Fue útil?

Solución

Comenzando con un resumen de WCF puntos de extensión, verás la que está expresamente diseñada para resolver su problema.Se llama CallContextInitializer.Echa un vistazo a este el artículo que da CallContextInitializer código de ejemplo.

Si usted hace una ICallContextInitializer extensión, se le dará el control sobre la BeginXXX hilo contexto Y el EndXXX hilo de contexto.Usted está diciendo que el ClaimsAuthorizationManager correctamente ha establecido el usuario principal en tu BeginXXX(...) el método.En ese caso, entonces usted puede hacer por sí mismo una costumbre ICallContextInitializer que le cede o registros de la CurrentPrincipal, dependiendo de si es el manejo de su BeginXXX() o su EndXXX().Algo así como:

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

Para aclarar aún más, considerar dos casos.Supongamos que usted implementó un ICallContextInitializer y un IParameterInspector.Supongamos que estos ganchos se espera que se ejecute con un sincrónica servicio de WCF y con un async servicio WCF (que es tu caso especial).

A continuación, es la secuencia de eventos y la explicación de lo que está sucediendo:

Caso Sincrónico

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

Nada hay de sorprendente en el código anterior.Pero ahora mirar más abajo de lo que sucede con servicio asincrónico de las operaciones...

Caso Asincrónico

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

Como se puede ver, la CallContextInitializer se asegura de que usted tiene la oportunidad para inicializar los valores tales como su CurrentPrincipal justo antes de la EndXXX() rutina se ejecuta.Por lo tanto, no importa que los EndXXX() rutina seguramente se está ejecutando en un subproceso diferente de lo que hizo el BeginXXX() de rutina.Y sí, la System.ServiceModel.Channels.Message objeto que almacena el usuario principal entre Begin/End métodos, se conservan y se transmiten adecuadamente por WCF, aunque el hilo cambiado.

En general, este enfoque permite que su EndXXX(IAsyncresult) para ejecutar con la correcta IPrincipal, sin tener que explícitamente re-establecer la CurrentPrincipal en el EndXXX() de rutina.Y como con cualquier WCF comportamiento, usted puede decidir si esto se aplica a las operaciones individuales, todas las operaciones en un contrato, o todas las operaciones en un extremo.

Otros consejos

No es realmente la respuesta a mi pregunta, pero un enfoque alternativo de la implementación del servicio de WCF (en .NET 4.5) que no presentan los mismos problemas 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;
    }

El enfoque válido para esto es crear una extensión:

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

Ahora esta extensión se utiliza como un contenedor de objetos que desea persistir entre el hilo de conmutación como OperationContext.Actual seguirá siendo el mismo.

Ahora usted puede utilizar esto en BeginMethod1 para guardar usuario actual:

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

Y, a continuación, en EndMethod1 usted puede obtener el usuario, escribiendo:

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

EDITAR (Otro enfoque):

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));
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top