我有一个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-method和WorkerFunction中设置为正确的ClaimsPrincipal,但是在End-method中它被设置为GenericPrincipal。
我知道我可以为服务启用ASP.NET兼容性,并使用在所有方法中具有正确主体的HttpContext.Current.User
,但我不想这样做。
有没有办法强制Thread.CurrentPrincipal到正确的ClaimsPrincipal而不打开ASP.NET兼容性?
答案 0 :(得分:4)
从summary of WCF extension points开始,您会看到明确设计用于解决问题的那个。它被称为CallContextInitializer。看一下这个article which gives CallContextInitializer sample code。
如果您创建了ICallContextInitializer扩展,您将可以控制BeginXXX线程上下文 AND EndXXX线程上下文。您说ClaimsAuthorizationManager已在BeginXXX(...)方法中正确建立了用户主体。在这种情况下,您可以为自己制作一个自定义ICallContextInitializer,它可以分配或记录CurrentPrincipal,具体取决于它是处理您的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和IParameterInspector。假设这些钩子应该使用同步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()例程运行之前初始化诸如CurrentPrincipal之类的值。因此,EndXXX()例程确实在与BeginXXX()例程不同的线程上执行并不重要。是的,即使线程发生了变化,WCF也会保留并正确传输在Begin / End方法之间存储用户主体的System.ServiceModel.Channels.Message
对象。
总的来说,这种方法允许您的EndXXX(IAsyncresult)使用正确的IPrincipal执行,而无需在EndXXX()例程中显式重新建立CurrentPrincipal。与任何WCF行为一样,您可以决定这是否适用于单个操作,合同上的所有操作或端点上的所有操作。
答案 1 :(得分:1)
不是我的问题的答案,而是实现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;
}
答案 2 :(得分:0)
有效的方法是创建扩展名:
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) { }
}
现在,此扩展用作要在线程切换之间保留的对象的容器,因为OperationContext.Current将保持不变。
现在,您可以在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));
}