异步 WCF 最终方法中的 Thread.CurrentPrincipal 错误
-
21-12-2019 - |
题
我有一个 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示例代码
的文章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));
}
.