Manejo de errores de ASP.NET MVC Ajax

117

¿Cómo manejo las excepciones lanzadas en un controlador cuando jquery ajax llama a una acción?

Por ejemplo, me gustaría un código javascript global que se ejecute en cualquier tipo de excepción del servidor durante una llamada ajax que muestre el mensaje de excepción si está en modo de depuración o simplemente un mensaje de error normal.

En el lado del cliente, llamaré a una función en el error de ajax.

En el lado del servidor, ¿necesito escribir un filtro de acción personalizado?

Shawn Mclean
fuente
8
Consulte la publicación de Beckelmans para ver un buen ejemplo. La respuesta de Darin a esta publicación es buena, pero no establezca el código de estado correcto para un error.
Dan
6
Lamentablemente, ese vínculo ahora está roto
Chris Nevill
1
Aquí está el enlace en wayback machine: web.archive.org/web/20111011105139/http://beckelman.net/post/…
BruceHill

Respuestas:

161

Si el servidor envía algún código de estado diferente a 200, se ejecuta la devolución de llamada de error:

$.ajax({
    url: '/foo',
    success: function(result) {
        alert('yeap');
    },
    error: function(XMLHttpRequest, textStatus, errorThrown) {
        alert('oops, something bad happened');
    }
});

y para registrar un controlador de errores global, puede usar el $.ajaxSetup()método:

$.ajaxSetup({
    error: function(XMLHttpRequest, textStatus, errorThrown) {
        alert('oops, something bad happened');
    }
});

Otra forma es usar JSON. Entonces, podría escribir un filtro de acción personalizado en el servidor que detecta la excepción y la transforma en una respuesta JSON:

public class MyErrorHandlerAttribute : FilterAttribute, IExceptionFilter
{
    public void OnException(ExceptionContext filterContext)
    {
        filterContext.ExceptionHandled = true;
        filterContext.Result = new JsonResult
        {
            Data = new { success = false, error = filterContext.Exception.ToString() },
            JsonRequestBehavior = JsonRequestBehavior.AllowGet
        };
    }
}

y luego decora la acción de tu controlador con este atributo:

[MyErrorHandler]
public ActionResult Foo(string id)
{
    if (string.IsNullOrEmpty(id))
    {
        throw new Exception("oh no");
    }
    return Json(new { success = true });
}

y finalmente invocarlo:

$.getJSON('/home/foo', { id: null }, function (result) {
    if (!result.success) {
        alert(result.error);
    } else {
        // handle the success
    }
});
Darin Dimitrov
fuente
1
Gracias por esto, esto último era lo que estaba buscando. Entonces, para la excepción asp.net mvc, ¿hay alguna forma específica en la que deba lanzarla para que el controlador de errores jquery pueda detectarla?
Shawn Mclean
1
@Lol codificador, no importa cómo arroje una excepción dentro de la acción del controlador, el servidor devolverá el código de estado 500 y errorse ejecutará la devolución de llamada.
Darin Dimitrov
Gracias, perfecto, justo lo que buscaba.
Shawn Mclean
1
¿No sería incorrecto un código de estado de 500? Para citar este capítulo, broadcast.oreilly.com/2011/06/… : "No darme cuenta de que un error 4xx significa que me equivoqué y un 5xx significa que tú lo arruinaste", donde yo es el cliente y tú eres el servidor.
Chris Nevill
¿Esta respuesta sigue siendo válida para las versiones más recientes de ASPNET?
gog
73

Después de buscar en Google, escribo un manejo de excepción simple basado en el filtro de acción MVC:

public class HandleExceptionAttribute : HandleErrorAttribute
{
    public override void OnException(ExceptionContext filterContext)
    {
        if (filterContext.HttpContext.Request.IsAjaxRequest() && filterContext.Exception != null)
        {
            filterContext.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
            filterContext.Result = new JsonResult
            {
                JsonRequestBehavior = JsonRequestBehavior.AllowGet,
                Data = new
                {
                    filterContext.Exception.Message,
                    filterContext.Exception.StackTrace
                }
            };
            filterContext.ExceptionHandled = true;
        }
        else
        {
            base.OnException(filterContext);
        }
    }
}

y escribe en global.ascx:

 public static void RegisterGlobalFilters(GlobalFilterCollection filters)
 {
      filters.Add(new HandleExceptionAttribute());
 }

y luego escriba este script en el diseño o la página maestra:

<script type="text/javascript">
      $(document).ajaxError(function (e, jqxhr, settings, exception) {
                       e.stopPropagation();
                       if (jqxhr != null)
                           alert(jqxhr.responseText);
                     });
</script>

Finalmente, debe activar el error personalizado. y luego disfrútalo :)

Arash
fuente
Puedo ver el error en Firebug pero no se redirige a la página de Error.
user2067567
1
¡Gracias por esto! debe marcarse como la respuesta, en mi opinión, ya que su filtrado en las solicitudes de ajax y hereda la clase correcta en lugar de lo que hereda el HandleErrorAttribute
mtbennett
2
¡Maravillosa respuesta! : D
Leniel Maccaferri
1
Creo que "Request.IsAjaxRequest ()" no es tan confiable a veces.
Will Huang
Para la configuración de depuración, siempre funciona, pero no siempre funciona en la configuración de lanzamiento y devuelve html en su lugar, ¿alguien tiene una solución para tal caso?
Hitendra
9

Desafortunadamente, ninguna de las respuestas es buena para mí. Sorprendentemente, la solución es mucho más sencilla. Regreso del controlador:

return new HttpStatusCodeResult(HttpStatusCode.BadRequest, e.Response.ReasonPhrase);

Y manejarlo como un error HTTP estándar en el cliente como desee.

alehro
fuente
@Will Huang: el nombre de la instancia de excepción
schmendrick
Tengo que lanzar el primer argumento a int. Además, cuando hago esto, el resultado se pasa al ajax successcontrolador y no al errorcontrolador. ¿Es este el comportamiento esperado?
Jonathan Wood
4

Hice una solución rápida porque tenía poco tiempo y funcionó bien. Aunque creo que la mejor opción es usar un filtro de excepción, tal vez mi solución pueda ayudar en el caso de que se necesite una solución simple.

Hice lo siguiente. En el método del controlador, devolví un JsonResult con una propiedad "Success" dentro de los datos:

    [HttpPut]
    public JsonResult UpdateEmployeeConfig(EmployeConfig employeToSave) 
    {
        if (!ModelState.IsValid)
        {
            return new JsonResult
            {
                Data = new { ErrorMessage = "Model is not valid", Success = false },
                ContentEncoding = System.Text.Encoding.UTF8,
                JsonRequestBehavior = JsonRequestBehavior.DenyGet
            };
        }
        try
        {
            MyDbContext db = new MyDbContext();

            db.Entry(employeToSave).State = EntityState.Modified;
            db.SaveChanges();

            DTO.EmployeConfig user = (DTO.EmployeConfig)Session["EmployeLoggin"];

            if (employeToSave.Id == user.Id)
            {
                user.Company = employeToSave.Company;
                user.Language = employeToSave.Language;
                user.Money = employeToSave.Money;
                user.CostCenter = employeToSave.CostCenter;

                Session["EmployeLoggin"] = user;
            }
        }
        catch (Exception ex) 
        {
            return new JsonResult
            {
                Data = new { ErrorMessage = ex.Message, Success = false },
                ContentEncoding = System.Text.Encoding.UTF8,
                JsonRequestBehavior = JsonRequestBehavior.DenyGet
            };
        }

        return new JsonResult() { Data = new { Success = true }, };
    }

Más adelante, en la llamada ajax, solo pedí esta propiedad para saber si tenía una excepción:

$.ajax({
    url: 'UpdateEmployeeConfig',
    type: 'PUT',
    data: JSON.stringify(EmployeConfig),
    contentType: "application/json;charset=utf-8",
    success: function (data) {
        if (data.Success) {
            //This is for the example. Please do something prettier for the user, :)
            alert('All was really ok');                                           
        }
        else {
            alert('Oups.. we had errors: ' + data.ErrorMessage);
        }
    },
    error: function (request, status, error) {
       alert('oh, errors here. The call to the server is not working.')
    }
});

Espero que esto ayude. Código feliz! :PAGS

Daniel Silva
fuente
4

De acuerdo con la respuesta de aleho, aquí hay un ejemplo completo. Funciona de maravilla y es super simple.

Código del controlador

[HttpGet]
public async Task<ActionResult> ChildItems()
{
    var client = TranslationDataHttpClient.GetClient();
    HttpResponseMessage response = await client.GetAsync("childItems);

    if (response.IsSuccessStatusCode)
        {
            string content = response.Content.ReadAsStringAsync().Result;
            List<WorkflowItem> parameters = JsonConvert.DeserializeObject<List<WorkflowItem>>(content);
            return Json(content, JsonRequestBehavior.AllowGet);
        }
        else
        {
            return new HttpStatusCodeResult(response.StatusCode, response.ReasonPhrase);
        }
    }
}

Código Javascript en la vista

var url = '@Html.Raw(@Url.Action("ChildItems", "WorkflowItemModal")';

$.ajax({
    type: "GET",
    dataType: "json",
    url: url,
    contentType: "application/json; charset=utf-8",
    success: function (data) {
        // Do something with the returned data
    },
    error: function (xhr, status, error) {
        // Handle the error.
    }
});

¡Espero que esto ayude a alguien más!

Rymnel
fuente
0

Para manejar errores de llamadas ajax en el lado del cliente, asigna una función a la erroropción de la llamada ajax.

Para establecer un valor predeterminado a nivel mundial, puede utilizar la función que se describe aquí: http://api.jquery.com/jQuery.ajaxSetup .

Brian Ball
fuente
¿Una respuesta que di hace más de 4 años de repente recibe un voto negativo? ¿A alguien le importa dar una razón por qué?
Brian Ball
1
Póngase en contacto con SOF y pídale a su DBA que pregunte quién dio el voto en contra. A continuación, envíe un mensaje a esa persona para que pueda explicarlo. No cualquiera puede dar una razón.
JoshYates1980