How to cancel an ActionResult method that is currently running from another ActionResult method?

1.2k views Asked by At

In my MVC website has show button for process data. This button call to the ActionResult method "ProcessData".

public ActionResult ProcessData()
    {
        bool isComplete = false;
        string errMessage = "";
        try
        {
            //Calculate something that take long time.


            isComplete = true;
        }
        catch (Exception e)
        {
            errMessage = e.Message;
        }


        return Json(new { IsComplete = isComplete, ErrorMessage = errMessage });
    }

And the second button for cancel process. This button call to the ActionResult method "CancelProcessData".

public ActionResult CancelProcessData()
    {
        bool isCancelComplete = false;
        string errMessage = "";
        try
        {
            //Cancel Process Data


            isCancelComplete = true;
        }
        catch (Exception e)
        {
            errMessage = e.Message;
        }


        return Json(new { IsCancelComplete = isCancelComplete , ErrorMessage = errMessage });
    }
  1. When user clicked at the process button, the data is processing and takes a long time.
  2. I want the user to be able to cancel a process by click at the cancel button.

How to cancel an ActionResult method that is currently running from another ActionResult method? Please introduce me.

P.S. Sorry for bad english skill.

Edit 1: this is code in ProcessData.cshtml

<script>
    function ProcessData() {
        var actionUrl = '@Url.Action("ProcessData", "Home")';
        $.ajax({
            type: "POST",
            url: actionUrl,
            contentType: "application/Json",
            success: function (result) {
                if (result.IsComplete) {
                    var reUrl = '@Url.Action("Index", "Home")';
                    window.location = reUrl;
                }
            },
            error: function () {
                alert("Error");
            }
        });
    }

    function CancelProcessData() {
        var actionUrl = '@Url.Action("CancelProcessData", "Home")';
        $.ajax({
            type: "POST",
            url: actionUrl,
            contentType: "application/Json",
            success: function (result) {
                if (result.IsComplete) {
                    alert("Cancel Complete");
                }
            },
            error: function () {
                alert("Error");
            }
        });
    }
</script>

<button onclick="ProcessData();">Process Data</button>
<button onclick="CancelProcessData();">Cancel Process Data</button>

End edit 1

Edit 2:After I search for long time. I find a solution for cancel process. this code is shown

public async System.Threading.Tasks.Task<ActionResult> ProcessData()
    {
        bool isComplete = false;
        bool isCancel = false;
        string errMessage = ""; 
        try
        {
            await System.Threading.Tasks.Task.Run(() => {
                for (int i = 0;i< Int32.MaxValue; i++)
                {
                    if (HttpContext.Response.ClientDisconnectedToken.IsCancellationRequested)
                    {
                        isCancel = true;
                        break;
                    }
                }
            });

            isComplete = true;
        }
        catch (Exception e)
        {
            errMessage = e.Message;
        }


        return Json(new { IsComplete = isComplete, ErrorMessage = errMessage, IsCancel = isCancel });
    }

this below is script function

<script>
    var ajaxProcessDatarequest;
    function ProcessData() {
        var actionUrl = '@Url.Action("ProcessData", "Home")';
        ajaxProcessDatarequest = $.ajax({
            type: "POST",
            url: actionUrl,
            contentType: "application/Json",
            success: function (result) {
                if (result.IsComplete) {
                    if (!result.IsCancel) {
                        var reUrl = '@Url.Action("Index", "Home")';
                        window.location = reUrl;
                    }
                }
            },
            error: function () {
                alert("Error");
            }
        });
    }

    function CancelProcessData() {
        ajaxProcessDatarequest.abort();
    }
</script>

End edit 2

1

There are 1 answers

3
adiga On

(Now, I'm not sure if a Session value remains same through out an Http request or gets updated)

This depends on what kind of process you're running. It won't stop any running processes but it can undo and stop the further processing.

In your javascript create a variable called processTimeStamp. Notice, it's declaration must be outside both of these functions. Just before calling the process data, assign the processTimeStamp and this will be the parameter to both ProcessData and CancelProcessData methods. Since, the variable is in global scope, the same value will be sent to both of these methods.

<script>
    var processTimeStamp;
    function ProcessData() {
        var actionUrl = '@Url.Action("ProcessData", "Home")';
        processTimeStamp = new Date().getTime().toString();

        $.ajax({
            type: "POST",
            url: actionUrl,
            data: {
              processTimeStamp: processTimeStamp 
            },
            contentType: "application/Json",
            success: function (result) {
                if (result.IsComplete) {
                    var reUrl = '@Url.Action("Index", "Home")';
                    window.location = reUrl;
                } else if (result.IsCancelComplete) {
                     alert("process was cancelled");
                }
            },
            error: function () {
                alert("Error");
            }
        });
    }

    function CancelProcessData() {
        var actionUrl = '@Url.Action("CancelProcessData", "Home")';
        $.ajax({
            type: "POST",
            url: actionUrl,
            data: {
              processTimeStamp: processTimeStamp 
            },
            contentType: "application/Json",
            success: function (result) {
            },
            error: function () {
                alert("Error");
            }
        });
    }
</script>

Controller:

In your CancelProcessData method, assign the value of processTimeStamp to Session["CancelProcessTimestamp"]. You simply can't know whether the process is cancelled or not from here.

In your ProcessData method, check if the processTimeStamp passed, is same as the Session["CancelProcessTimestamp"] before every process. I mean you don't have any option. (Kind of like how you'd handle CancellationToken in the Task Parallel Library)

public ActionResult ProcessData(string processTimeStamp)
{
    bool isComplete = false;
    bool isCancelComplete = false;
    string errMessage = "";

    try
    {

        // process 1 <-- this starts immediately. So you can't stop that.

        if( Convert.ToString(Session["CancelProcessTimestamp"]) != processTimeStamp)
        {
           // process 2
        }
        else
        {
           isCancelComplete = true;
        }

        // OTHER PROCESSES

        if(Convert.ToString(Session["CancelProcessTimestamp"]) != processTimeStamp)
        {
           // update to database
           isComplete = true; // only if it completes the last process
        }
        else
        {
           // you can undo the work done in process 1, 2 etc. 
           // Like deleting an uploaded file. Or if any primary key is returned from Process 1, then delete the corresponding entry in Data base
           isCancelComplete = true;
        }
    }
    catch (Exception e)
    {
        errMessage = e.Message;
    }

    return Json(new { IsComplete = isComplete, IsCancelComplete = isCancelComplete , ErrorMessage = errMessage });
}


public ActionResult CancelProcessData(string processTimeStamp)
{
    Session["CancelProcessTimestamp"] = processTimeStamp;
    return Json(true);
}