What will you do if you have async Web API method that runs on server for a some time but your client is dropped?
async
There are two solutions:
The first approach is simplest but might result in some overconsumption of server resources. The other method requires you to check client status from time to time.
Fortunatelly, ASP.NET provides a HttpResponse.ClientDisconnectedToken property, which is limited to IIS 7.5+ in integrated mode, but still fits our needs. So, you should request ClientDisconnectedToken, if any, and implement your async code using that token.
ClientDisconnectedToken
The following extension function gets that token:
using System.Linq; using System.Net.Http; using System.Threading.Tasks; using System.Threading; using System.Web; public static class HttpApiExtensions { public static CancellationToken GetCancellationToken( this HttpRequestMessage request) { CancellationToken cancellationToken; object value; var key = typeof(HttpApiExtensions).Namespace + ":CancellationToken"; if (request.Properties.TryGetValue(key, out value)) { return (CancellationToken)value; } var httpContext = HttpContext.Current; if (httpContext != null) { var httpResponse = httpContext.Response; if (httpResponse != null) { try { cancellationToken = httpResponse.ClientDisconnectedToken; } catch { // Do not support cancellation. } } } request.Properties[key] = cancellationToken; return cancellationToken; } }
And here is a Web API WordCount service described in the previous post:
using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; using System.Threading; using System.Threading.Tasks; public class ValuesController: ApiController { public async Task<int> GetWordCount([FromUri(Name = "url")] string[] urls) { var cancellationToken = Request.GetCancellationToken(); using(var client = new HttpClient()) { return (await Task.WhenAll( urls.Select(url => WordCountAsync(client, url, cancellationToken)))).Sum(); } } public static async Task<int> WordCountAsync( HttpClient client, string url, CancellationToken cancellationToken) { string content = await (await client.GetAsync(url, cancellationToken)). Content.ReadAsStringAsync(); return WordCount(content); } private static int WordCount(string text) { var count = 0; var space = true; for (var i = 0; i < text.Length; ++i) { if (space != char.IsWhiteSpace(text[i])) { space = !space; if (!space) { ++count; } } } return count; } }
Though is simple there is a nuisance. You should pass cancellation token here and there, which adds to a pollution from async.