Proceso asincrónico en asp.net mvc con jquery

Lo que vamos a hacer en esta oportunidad es una aplicación asp.net mvc con un proceso de duración considerable como para querer tener una barra de progreso usando jquery y jquery.ui

Básicamente, esto se logra haciendo llamadas asincrónicas (e independientes) desde la view a las acciones del controller.

mvc

Controller

En el controller vamos a tener dos acciones, una que se va a invocar mediante un POST (el proceso principal) y otra que se va a invocar mediante otro POST y va a devolver un JSON (consulta del estado del proceso).

Nota: Vamos a hacer uso de los items de HttpApplication para guardar el estado del mismo y para poder ejecutar varios procesos en forma simultanea es que los mismos tienen un processId. Otra opción sería, por ejemplo, persistirlo en una base de datos.

Proceso principal
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult LongProcess(int loops, string processId)
{
    HttpContext.Application["Status_" + processId] = 0;
    HttpContext.Application["Loops_" + processId] = loops;

    for (int i = 0; i < loops; i++)
    {
        Thread.Sleep(1000); //Action

        HttpContext.Application["Status_" + processId] = i + 1; //Update status
    }
    return View("Result", new ResultModel {Loops = loops});
}

donde Thread.Sleep(1000) está representando la duración de una etapa del proceso.

Consulta del estado del proceso

aquí tomamos los valores del estado del proceso, calculamos el porcentaje y armamos una respuesta JSON.

[AcceptVerbs(HttpVerbs.Post)]
public JsonResult GetStatus(string processId)
{
    int status = HttpContext.Application["Status_" + processId] != null
                    ? (int)HttpContext.Application["Status_" + processId]
                    : 0;
    int loops = HttpContext.Application["Loops_" + processId] != null
                    ? (int)HttpContext.Application["Loops_" + processId]
                    : 0;

    int value = 0;
    if (status != 0 && loops != 0)
        value = status * 100 / loops;

    return Json(new { Result = value });
}

View

La vista propiamente dicha contiene un div con los controles que realizan la invocación:

  • un formulario: fromprocess
  • un hidden para guardar el Id de proceso: processId
  • otro hidden para guardar el Id del intervalo que consulta el estado periódicamente: intervalId
  • una caja de texto para ingresar la cantidad de ciclos del loop que estamos simulando: loops.
  • un botón de tipo submit para invocar al post del formulario.

y otro div para mostrar el progreso, el estado y el resultado.

<h2><%= Html.Encode(ViewData["Message"]) %></h2>
<% using (Html.BeginForm("LongProcess", "Home", FormMethod.Post, new {id = "fromprocess"}))
   {%>
    <div id="invoke">
        <fieldset>
            <%=Html.Hidden("processId", Model.ProcessId, new { id = "processId" })%>
            <input id="intervalId" type="hidden" />
            <%= Html.TextBox("loops", 10, new {id = "loops"}) %>
            <input type="submit" value="Run Long process" />
        </fieldset>
    </div>
    <div id="progress">
        <div id="progressbar"></div>
        <div id="status"></div>
        <div id="result"></div>
    </div>
<% } %>

La función javascript para consultar el estado

Esta función consulta el estado del proceso haciendo un POST e interpretando el JSON de respuesta. En url indicamos el link a consultar, en data los parámetros de la acción (en el controller) y cuando la consulta se completa tomamos el resultado (que ya viene parseado a JSON), asignamos la propiedad Result al progressbar y en texto al div status.

function updateProgress() {
    $.ajax({
        url: '<%=Url.Action("GetStatus", "Home") %>',
        data: { processId: $("#processId").val() },
        success: function(data) {
            if (data != null) {
                $("#progressbar").progressbar("option", "value", data.Result);
                $("#status").html("Progress: " + data.Result + "%");
            }
        },
        error: function(msg) {
            $("#status").html("Error: " + msg);
        }
    });
}
La función javascript para hacer el POST asincrónico

cuando la página se carga estamos inicializando algunas propiedades…

  • en la primera línea estamos configurando el div progressbar como una progressbar de jquery.
  • en la segunda estamos ocultando el div progress (será visible cuando se inicie el proceso).
  • la tercer línea es muy importante para que funcione correctamente en IE, sino IE hace un cache de las llamadas ajax y nunca vuelve a consultar el estado al servidor sino que se queda con la primer respuesta. (para FF y Chrome no es necesaria).
  • en la cuarta línea estamos agregando una función al submit del formulario de nuestra página que va a realizar la misma invocación que haría el formulario pero en forma asincrónica.
$(document).ready(function() {
    $("#progressbar").progressbar({ value: 0 });
    $("#progress").hide();

    $.ajaxSetup({ cache: false }); // this line is needed to ajax on IE

    $("#fromprocess").submit(function() {
        $("#invoke").hide(1000);

        $("#progressbar").progressbar("option", "value", 0);
        $("#status").html("loading...");
        $("#progress").show(1000);

        $.ajax({
            type: "POST",
            url: $("#fromprocess").attr("action"),
            data: $("#fromprocess").serialize(),
            success: function(data) {
                clearInterval($("#intervalId").val());
                $("#progressbar").progressbar("option", "value", 100);
                $("#status").html("complete");
                $("#result").html(data);
            },
            error: function(msg) {
                clearInterval($("#intervalId").val());
                $("#status").html("error");
                $("#result").html(msg);
            }
        });

        var intervalId = setInterval("updateProgress()", 100);
        $("#intervalId").val(intervalId);

        return false;
    });
});

la función que acabamos de poner en el submit hace una invocación ajax de tipo POST a url que tiene configurada como action el form fromprocess, en data envía el formulario serializado, esto hace que no nos quedemos esperando el resultado del post sino que indicamos una función js que se va a invocar cuando el proceso se complete.

Luego seteamos un intervalo de ejecución para la función updateProgress y guardamos el Id del intervalo para, que cuando se complete el proceso, detenerlo.

Cuando se completa el proceso:

  • detenemos la consulta de estado
  • seteamos el progressbar en 100%
  • ponemos el status en complete
  • mostramos el resultado en el div result.

Código completo

para descargar el código completo, click aquí

1 comentario:

  1. Gracias por tu aporte, por favor podrias subir nuevamente el codigo completo para descargarlo.
    Saludos

    ResponderBorrar