在模拟时调用异步WCF服务

我在服务器上运行WCF服务,该服务器配置为接受Kerberos身份validation。

Kerberos工作正常,因此WCF服务知道哪个用户连接到他。 该服务提供一切作为异步方法。 就像这里一样(只是一个清晰的例子)。

public ExampleService : IExampleService { public Task GetUsernameAsync() { return await Task.Run(() => System.Threading.Thread.CurrentPrincipal.Name); } } 

在客户端,我有一个Controller(它是一个MVC页面,但这没关系),它调用方法异步。

 public ExampleController { public async Task Index() { using(var serviceClient = ServiceFactory.GetServiceClient()) using(Security.Impersonation.Impersonate()) { var data = await serviceClient.GetUsernameAsync(); return View(data); } } } 

只要我不使用等待,模仿就可以正常工作。

由于Task不会模拟模拟身份,因此我想知道是否有可能更改Task的执行用户或执行任何其他操作以使模拟在此用例中起作用。

我尝试了一个自定义的awaiter(因为它可以在那个案例中使用Culture),但这根本不起作用(好吧它也不会冒充)。

好的 – 经过一些更深入的研究后,我终于找到了解决方案如何在异步任务中传播模拟的Windows身份。

该解决方案是机器范围的,将为所有(在本例中)64位ASP.NET 4.5应用程序设置。

C:\Windows\Microsoft.Net\Framework64\v4.0.30319找到aspnet.config文件(可能这也适用于更高版本)并将legacyImpersonationPolicy的值legacyImpersonationPolicy为false

  

确保重新启动IIS(或重新启动计算机)。
只要您使用托管方法进行模拟,这将使模拟流动。 在我的情况下,我冒充类似于此,这工作正常:

 class Impersonation : IDisposable { public static Impersonation Impersonate() { return new Impersonation(); } private WindowsImpersonationContext ImpersonationContext { get; set; } private Impersonation() { var currentIdentity = System.Threading.Thread.CurrentPrincipal.Identity as WindowsIdentity; if (currentIdentity != null && currentIdentity.IsAuthenticated) { ImpersonationContext = currentIdentity.Impersonate(); return; } throw new SecurityException("Could not impersonate user identity"); } public void Dispose() { if(ImpersonationContext != null) ImpersonationContext.Dispose(); } } } 

这里解释了aspnet.config设置(顺便说一句,它无法在web.config文件中设置它): http : //msdn.microsoft.com/en-us/library/ms229296 (v = vs10 ) .aspx (它基本上说,如果这是真的,我们用.NET 1.1的方式)

您可以使用此方法检查窗口标识是否流动:

 System.Security.SecurityContext.IsWindowsIdentityFlowSuppressed() 

我不同意你的问题。

问题不是await你的。 但是你的Task.Run 。 在ASP.Net代码上应该没有await Task.Run 。 它的效果是一个不必要的线程切换。 由于ASP.Net上没有STA线程,因此不需要这样做,只会减慢代码速度。

如果你坚持使用真正的无线程Task你应该没有任何问题,因为你会留在一个线程中。 除非您的应用程序服务器具有非常有限的客户端和大量的CPU绑定操作,否则multithreading对于扩展是不利的,因为单个用户可以快速填写服务器的计划。

您应该使用Task.FromResultTaskCompletionSource.Task来确保您保持单线程。 通过[ThreadLocal]属性可以解决您的问题。

TL:DR

不要在服务器端使用Task.Run 。 使用Task.FromResult因此您只有一个线程。

编辑:响应

哪个线程? 在客户端,您仍将使用await 。 我从来没说过不要await 。 我说过不要直接使用await Task.Run (UI线程除外)。 我没有说你应该阻塞一个线程。 因为你的线程应该做WORK来产生你传递给Task.FromResult的结果。 BLOCKING意味着你的线程什么都不做,同时消耗资源(即内存)。 哎呀,甚至没有必要

服务器端应该使用这种模式:

 public ExampleService : IExampleService { public Task GetUsernameAsync() { var name = System.Threading.Thread.CurrentPrincipal.Name; return Task.FromResult(name); } } 

客户应该留下来

 public ExampleController { public async Task Index() { using(var serviceClient = ServiceFactory.GetServiceClient()) using(Security.Impersonation.Impersonate()) { var data = await serviceClient.GetUsernameAsync(); return View(data); } } } 

在ServiceClient本地解析的情况下,所有内容都会同步运行(速度更快,资源更少)。 这里的要点是你只是为没有异步的线程样式应用Task async模式。 Task.Run是async的并发风格,只应在需要使用另一个线程时使用(因为你是CPU绑定的,或者这个线程需要用于其他东西)。

由于我负责WCF接口,因此这是一个有效的解决方案(但我不喜欢,因为它或多或少是代码重复):

 [ServiceContract] interface IExampleService { [OperationContract] string GetUsername(); } interface IExampleServiceAsync { Task GetUserNameAsync(); } class ExampleService : IExampleService { public string GetUsername() { return System.Threading.Thread.CurrentPrincipal.Name; } } class ExpampleServiceClient : ServiceClient, IExampleServiceAsync { public Task GetUsernameAsync() { return Task.Run(() => GetUsername()); } private string GetUsername() { using(Security.Impersonation.Impersonate()) { return base.Proxy.GetUsername(); } } } 

我不得不说这是一种解决方法 – 而不是解决方案 – 它改变了服务器端的接口(仅限非Async接口),但至少它正在运行。

此解决方案的一个优点 – 您可以将模拟实现为ExampleServiceClient之上的行为模式。