8

I want to stop the actions that are called by the jQuery.ajax method on the server side. I can stop the Ajax request using $.ajax.abort() method on the client side but not on the server side.

Updated:

I used async action instead of sync action, but I didn't get what I want! As you know server can't process more than one request at the same time that's causes each request have to wait till the previous one is finished even if previous request is canceled by $.Ajax.Abort() method. I know if I use [SessionState(System.Web.SessionState.SessionStateBehavior.ReadOnly)] attribute it almost what I want but it doesn’t satisfy me.

Above all I want to abort processing method on server side by user. That's it :)

Saeed Hamed
  • 732
  • 2
  • 10
  • 28
  • If i understand correctly , you don't want any XHR to hit your server? – Rameez Ahmed Sayad Jul 19 '13 at 22:46
  • :) ... maybe something on these lines might help http://stackoverflow.com/questions/216173/differentiating-between-an-ajax-call-browser-request or http://stackoverflow.com/questions/216173/differentiating-between-an-ajax-call-browser-request – Rameez Ahmed Sayad Jul 19 '13 at 22:57
  • 2
    Thanks, but it isn't something that I want!! I call an action that takes for example 1 minute. I want to let users cancel request if they want, I'm using $.ajax.abort() and it works correctly and it cancels the request but I know the server is working on previous request. If I send another request with that session on server it must wait until end of last action. – Saeed Hamed Jul 19 '13 at 23:14
  • @user2349133 As long as it's on client side , you can check it's state , based on that ask users ... once it goes to server ... I don't see a way of cancelling that particular XHR request – Rameez Ahmed Sayad Jul 19 '13 at 23:17
  • what is the long running action doing? Can you use paging there, i.e. process 100 "things" per call? – Felipe Castro Jul 20 '13 at 00:39
  • @FelipeCastro I just said that for example. – Saeed Hamed Jul 20 '13 at 14:14

5 Answers5

5

You may want to look at using the following type of controller Using an Asynchronous Controller in ASP.NET MVC

and also see if this article helps you out as well Cancel async web service calls, sorry I couldn't give any code examples this time.

I've created an example as a proof of concept to show that you can cancel server side requests. My github async cancel example

If you're calling other sites through your code you have two options, depending on your target framework and which method you want to use. I'm including the references here for your review:

WebRequest.BeginGetResponse for use in .Net 4.0 HttpClient for use in .Net 4.5, this class has a method to cancel all pending requests.

Hope this gives you enough information to reach your goal.

Community
  • 1
  • 1
ermagana
  • 1,090
  • 6
  • 11
  • Thanks, I'm sure you've understood me correctly, could you show me a clear code sample? – Saeed Hamed Jul 20 '13 at 14:16
  • 2
    Doing a little more research it could depend on what version of mvc you're using. Just to clarify, my understanding is that you would like to begin an asynchronous request, because it's a long running process, that users can cancel. When the user cancels the request the server grabs that asynchronous thread and essentially aborts the operation. Is that correct? – ermagana Jul 20 '13 at 18:02
  • Yes, exactly! I'm using ASP.NET MVC 4, I haven't work with async in c#, if you have a sample code it will help me! However thanks :) – Saeed Hamed Jul 20 '13 at 18:40
  • 1
    Sorry I don't have any actual examples myself, but the first link in my answer uses a sample zip that you can use it's called Async.zip and it's located here: http://archive.msdn.microsoft.com/aspnetmvcsamples/Release/ProjectReleases.aspx?ReleaseId=3547 from the sample it looks like the controller has a service that it uses for its async work and that service has an abort method on it. At the very least it should give you an idea of how you'd implement the service that does the actual async work. – ermagana Jul 20 '13 at 18:53
  • @SaeedHamed How much control over the long running service do you have? – ermagana Jul 21 '13 at 18:41
  • Thanks @ermagan, I got your sample and it was useful, and I learned a lot things, but there a problem for me, you are checking the cancellation token in each loop and it's not something that I can do always, for example I want to get a stream from a url that it takes long time, so I can't check the token while it's getting stream from a url – Saeed Hamed Jul 21 '13 at 20:32
3

Here is an example Backend:

[HttpGet]
public List<SomeEntity> Get(){
        var gotResult = false;
        var result = new List<SomeEntity>();
        var tokenSource2 = new CancellationTokenSource();
        CancellationToken ct = tokenSource2.Token;
        Task.Factory.StartNew(() =>
        {
            // Do something with cancelation token to break current operation
            result = SomeWhere.GetSomethingReallySlow();
            gotResult = true;
        }, ct);
        while (!gotResult)
        {
            // When you call abort Response.IsClientConnected will = false
            if (!Response.IsClientConnected)
            {
                tokenSource2.Cancel();
                return result;
            }
            Thread.Sleep(100);
        }
        return result;
}

Javascript:

var promise = $.post("/Somewhere")
setTimeout(function(){promise.abort()}, 1000)

Hope I'm not to late.

Maris
  • 4,608
  • 6
  • 39
  • 68
2

I found this article: Cancelling Long Running Queries in ASP.NET MVC and Web API extremely helpful. I'll sum up the article here.

Quick notes before I get started:

  • I'm running MVC5, not ASP.NET Core MVC.
  • I want to be able to cancel actions when I navigate away from the page or cancel them with jQuery abort.

I ended up with something like this:

// Just add the CancellationToken parameter to the end of your parameter list
public async Task<ActionResult> GetQueryDataAsync(
    int id,
    CancellationToken cancellationToken)
{
    CancellationToken dcToken = Response.ClientDisconnectedToken;
    var linkTokenSrc = CancellationTokenSource
        .CreateLinkedTokenSource(
            cancellationToken, 
            dcToken
        );

    using (var dbContext = new Entities())
    {
        var data = await dbContext.ComplicatedView
            /* A bunch of linq statements */
            .ToListAsync(linkTokenSrc.Token)
            .ConfigureAwait(true);

        // Do stuff and return data unless cancelled
        // ...
    }
}

Since I'm using MVC5, I had to use the workaround prescribed in the article, i.e. reading from the Response.ClientDisconnectedToken and linking the token sources.

If you are using ASP.NET Core MVC instead, you should be able to just use the CancellationToken parameter directly instead of having to link it according to the article.

After this, all I had to do was make sure I aborted the XHR's in the JavaScript when I navigate away from the page.

General Grievance
  • 4,555
  • 31
  • 31
  • 45
0

We have seen the problem in IE, where an aborted request still got forwarded to the controller action - however, with the arguments stripped, which lead to different error, reported in our logs and user activity entries.

I have solved this using a filter like the following

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using TWV.Infrastructure;
using TWV.Models;

namespace TWV.Controllers
{
    public class TerminateOnAbortAttribute : FilterAttribute, IActionFilter
    {
        public void OnActionExecuting(ActionExecutingContext filterContext)
        {
            // IE does not always terminate when ajax request has been aborted - however, the input stream gets wiped
            // The content length - stays the same, and thus we can determine if the request has been aborted
            long contentLength = filterContext.HttpContext.Request.ContentLength;
            long inputLength = filterContext.HttpContext.Request.InputStream.Length;
            bool isAborted = contentLength > 0 && inputLength == 0;
            if (isAborted)
            {
                filterContext.Result = new EmptyResult();
            }
        }

        public void OnActionExecuted(ActionExecutedContext filterContext)
        {
            // Do nothing
        }
    }
}
Jesper Kleis
  • 51
  • 1
  • 6
-2

A simple solution is to use something like below. I use it for cancelling long running tasks (specifically generating thousands of notifications). I also use the same approach to poll progress and update progress bar via AJAX. Also, this works practically on any version of MVC and does not depends on new features of .NET

public class MyController : Controller
{

private static m_CancelAction = false;

public string CancelAction()
{
    m_CancelAction = true;
    return "ok";
}

public string LongRunningAction()
{
    while(...)
    {
        Dosomething (i.e. Send email, notification, write to file, etc)

        if(m_CancelAction)
        {
            m_CancelAction = false;
            break;
            return "aborted";
        }
    }

    return "ok";
}
}
kleopatra
  • 51,061
  • 28
  • 99
  • 211
  • Is there not a security risk that another client could cancel the action of a long running request from say the first client. – Chris Nevill Jul 14 '15 at 15:41