Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I signal cancellation to Web API actions when the self-hosted OWIN server shuts down?

I have an OWIN-based ASP.NET Web API hosted in a Windows Service. Most of my ApiController actions are async, and accept CancellationToken parameters:

[Route("data/{id}")]
public async Task<IHttpActionResult> GetSomeDataAsync(int id, CancellationToken token)
{
    try
    {
        using (var _dataSource = ...)
        {
            return Ok(await _dataSource.GetDataAsync(id, token));
        }
    }
    catch (OperationCanceledException ex)
    {
        return StatusCode(HttpStatusCode.NoContent);
    }
}

Using the built-in request-cancellation features of Web API, if the client cancels the request, token is signaled and _dataSource handles it appropriately and throws the OperationCanceledException.

So far, so great.

But when my host process terminates (that is, the Windows Service stops), token isn't signaled and the cancellation-and-bail-out process isn't graceful.

I'm aware of the OWIN environment dictionary's host.onAppDisposing property, and I've dug into the source for the Microsoft.Owin[.*] and Microsoft.AspNet.WebApi.* packages to try and figure out where GetSomeDataAsync's token argument is coming from, but I'm not sure how to connect the pieces together.

I'd like to do something like

class WebServiceInAWindowsService : ServiceBase
{
    private readonly CancellationTokenSource _cts = new CancellationTokenSource();
    ...
    protected override void OnStop()
    {
        _cts.Cancel();
    }
}

But I'm not sure how to get _cts to be the source of the CancellationTokens that get fed to my actions, while not breaking the request-cancellation feature that's working well.

I'm thinking that CancellationTokenSource.CreateLinkedTokenSource() might be useful, but I'm not seeing how to put the pieces together.

Can you help? Thanks!

like image 646
David Rubin Avatar asked Oct 13 '17 18:10

David Rubin


People also ask

How OWIN works in web API?

Open Web Interface for . NET (OWIN) defines an abstraction between . NET web servers and web applications. OWIN decouples the web application from the server, which makes OWIN ideal for self-hosting a web application in your own process, outside of IIS.

What is CancellationToken in web API?

You can use a CancellationToken to stop a long running operation when the user cancels a request in the web browser. In other words, using a CancellationToken can help you stop long running requests from using resources when the user has stopped or refreshed the web page.

How to Add OWIN startup class in web API?

Create an ASP.NET Web App using OWIN Startup - In the Add New Item dialog box, enter OWIN in the search field, and change the name to Startup. cs, and then select Add. The next time you want to add an Owin Startup class, it will be in available from the Add menu.

What is Owin middleware?

OWIN allows web apps to be decoupled from web servers. It defines a standard way for middleware to be used in a pipeline to handle requests and associated responses. ASP.NET Core applications and middleware can interoperate with OWIN-based applications, servers, and middleware.


1 Answers

host.onAppDisposing is triggered when you call Dispose on the value returned from WebApp.Start.

https://github.com/aspnet/AspNetKatana/blob/9f6e09af6bf203744feb5347121fe25f6eec06d8/src/Microsoft.Owin.Hosting/Engine/HostingEngine.cs#L302-L308

https://github.com/aspnet/AspNetKatana/blob/9f6e09af6bf203744feb5347121fe25f6eec06d8/src/Microsoft.Owin.Hosting/Engine/HostingEngine.cs#L112

GetSomeDataAsync's is only associated with the request disconnect token by default (e.g. owin.CallCancelled). Via middleware or otherwise you can replace it with a linked TCS that's also connected to host.onAppDisposing.

Something like:

app.Use(async (env, next) =>
{
  var reqAbt = env.Get<CancellationToken>("owin.CallCancelled");
  var appAbt = env.Get<CancellationToken>("host.onAppDisposing"); 
  using (linked = CancellationTokenSource.CreateLinkedTokenSource(reqAbt, appAbt))
  {
    env["owin.CallCancelled"] = linked.Token;
    await next();
    env["owin.CallCancelled"] = reqAbt;
  }
});
like image 80
Tratcher Avatar answered Sep 23 '22 20:09

Tratcher