我有一个 WCF 服务,它有 Thread.CurrentPrincipal 设置在 ServiceConfiguration.ClaimsAuthorizationManager.

当我像这样异步实现服务时:

    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
    }

我发现 Thread.CurrentPrincipal 在 Begin 方法和 WorkerFunction 中设置为正确的 ClaimsPrincipal,但在 End 方法中它设置为 GenericPrincipal。

我知道我可以为服务启用 ASP.NET 兼容性并使用 HttpContext.Current.User 它在所有方法中都有正确的原理,但我宁愿不这样做。

有没有办法在不打开 ASP.NET 兼容性的情况下强制 Thread.CurrentPrincipal 为正确的 ClaimsPrincipal?

有帮助吗?

解决方案

从a WCF扩展点的摘要,您将看到明确旨在解决您的问题的那个。它被称为 callcontextinitializer 。看看这个给出callcontextinitializer示例代码

的文章

如果您制作iCallContextInitializer扩展,则将给予BeginXXX线程上下文 endxxx线程上下文。您说已从expersauthorizationManager中正确建立了BeginXXX(...)方法的用户主体。在这种情况下,您可以为自己制作一个自定义icontextinitializer,它可以分配或记录当前Principal,具体取决于它是处理BeginXXX()或endxxx()。类似:

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;            
    }
    ...
 }
. 为了进一步澄清,考虑两种情况。假设您实现了ICallContextInitializer和iParameterInePector。假设这些钩子预计将使用同步WCF服务和异步WCF服务(这是您的特殊情况)执行。

下面是事件的序列,并解释正在发生的内容:

同步案例

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

上面的代码没有令人惊讶。但现在看看异步服务操作的发生什么...

异步案例

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

如您所见,CallContextInitializer可确保您有机会初始化在Endxxx()例程运行之前才能初始化您的当前Principal。因此,它并不重要,endxxx()例程肯定是在不同的线程上执行而不是beginxxx()例程。是的,是在BEGIN / END方法之间存储用户主体的System.ServiceModel.Channels.Message对象,由WCF保留并正确传输,即使线程已更改。

总的来说,这种方法允许您的Endxxx(IASyncResult)以正确的IPrincipal执行,而无需明确地重新建立Endxxx()例程中的CurrentPrincipal。与任何WCF行为一样,您可以决定是否适用于各个操作,合同上的所有操作,或者端点上的所有操作。

其他提示

这并不是我问题的真正答案,而是实现 WCF 服务(在 .NET 4.5 中)的另一种方法,该方法不会出现与 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;
    }

此方法的方法是创建扩展名:

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

现在,此扩展用用作要在线切换之间持续存在的对象的容器,因为操作窗体将保持不变。

现在,您可以在beginmethod1中使用它来保存当前用户:

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

然后在EndMethod1中,您可以通过键入:

来获取用户
ClaimsPrincipal principal = SLOperationContext.Current.Items["Principal"];
.

编辑(另一种方法):

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

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top