web-dev-qa-db-fra.com

Renvoyer JSON avec le code d'état d'erreur MVC

J'essayais de retourner une erreur à l'appel au contrôleur comme indiqué dans Ce lien afin que le client puisse prendre les mesures appropriées. Le contrôleur est appelé par javascript via jquery AJAX. Je ne récupère l'objet Json que si je ne mets pas l'état sur erreur. Voici l exemple de code

if (response.errors.Length > 0)
   Response.StatusCode = (int)HttpStatusCode.BadRequest;
return Json(response);

J'obtiens le Json si je ne définis pas le code d'état. Si je définis le code d'état, je récupère le code d'état mais pas l'objet d'erreur Json.

Mise à jour Je veux envoyer un objet Error en JSON afin qu'il puisse être traité en cas de rappel d'erreur d'ajax.

39
Sarath

J'ai trouvé la solution ici

J'ai dû créer un filtre d'action pour remplacer le comportement par défaut de MVC

Voici ma classe d'exception

class ValidationException : ApplicationException
{
    public JsonResult exceptionDetails;
    public ValidationException(JsonResult exceptionDetails)
    {
        this.exceptionDetails = exceptionDetails;
    }
    public ValidationException(string message) : base(message) { }
    public ValidationException(string message, Exception inner) : base(message, inner) { }
    protected ValidationException(
    System.Runtime.Serialization.SerializationInfo info,
    System.Runtime.Serialization.StreamingContext context)
        : base(info, context) { }
}

Notez que j'ai un constructeur qui initialise mon JSON. Voici le filtre d'action

public class HandleUIExceptionAttribute : FilterAttribute, IExceptionFilter
{
    public virtual void OnException(ExceptionContext filterContext)
    {
        if (filterContext == null)
        {
            throw new ArgumentNullException("filterContext");
        }
        if (filterContext.Exception != null)
        {
            filterContext.ExceptionHandled = true;
            filterContext.HttpContext.Response.Clear();
            filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
            filterContext.HttpContext.Response.StatusCode = (int)System.Net.HttpStatusCode.InternalServerError;
            filterContext.Result = ((ValidationException)filterContext.Exception).myJsonError;
        }
    }

Maintenant que j'ai le filtre d'action, je vais décorer mon contrôleur avec l'attribut de filtre

[HandleUIException]
public JsonResult UpdateName(string objectToUpdate)
{
   var response = myClient.ValidateObject(objectToUpdate);
   if (response.errors.Length > 0)
     throw new ValidationException(Json(response));
}

Lorsque l'erreur est levée, le filtre d'action qui implémente IExceptionFilter est appelé et je récupère le Json sur le client en cas d'erreur de rappel.

32
Sarath

La meilleure solution que j'ai trouvée consiste à créer votre propre JsonResult qui étend l'implémentation d'origine et vous permet de spécifier un HttpStatusCode:

public class JsonHttpStatusResult : JsonResult
{
    private readonly HttpStatusCode _httpStatus;

    public JsonHttpStatusResult(object data, HttpStatusCode httpStatus)
    {
        Data = data;
        _httpStatus = httpStatus;
    }

    public override void ExecuteResult(ControllerContext context)
    {
        context.RequestContext.HttpContext.Response.StatusCode = (int)_httpStatus;
        base.ExecuteResult(context);
    }
}

Vous pouvez ensuite utiliser ceci dans votre action de contrôleur comme ceci:

if(thereWereErrors)
{
    var errorModel = new { error = "There was an error" };
    return new JsonHttpStatusResult(errorModel, HttpStatusCode.InternalServerError);
}
25
Richard Garside

Il existe une solution très élégante à ce problème, il suffit de configurer votre site via web.config:

<system.webServer>
    <httpErrors errorMode="DetailedLocalOnly" existingResponse="PassThrough"/>
</system.webServer>

Source: https://serverfault.com/questions/123729/iis-is-overriding-my-response-content-if-i-manual-set-the-response-statuscode

22
Gabrielius

S'appuyant sur la réponse de Richard Garside, voici la version ASP.Net Core

public class JsonErrorResult : JsonResult
{
    private readonly HttpStatusCode _statusCode;

    public JsonErrorResult(object json) : this(json, HttpStatusCode.InternalServerError)
    {
    }

    public JsonErrorResult(object json, HttpStatusCode statusCode) : base(json)
    {
        _statusCode = statusCode;
    }

    public override void ExecuteResult(ActionContext context)
    {
        context.HttpContext.Response.StatusCode = (int)_statusCode;
        base.ExecuteResult(context);
    }

    public override Task ExecuteResultAsync(ActionContext context)
    {
        context.HttpContext.Response.StatusCode = (int)_statusCode;
        return base.ExecuteResultAsync(context);
    }
}

Ensuite, dans votre contrôleur, retournez comme suit:

// Set a json object to return. The status code defaults to 500
return new JsonErrorResult(new { message = "Sorry, an internal error occurred."});

// Or you can override the status code
return new JsonErrorResult(new { foo = "bar"}, HttpStatusCode.NotFound);
6
Lee Oades

Vous devez renvoyer vous-même l'objet d'erreur JSON après avoir défini le StatusCode, comme ceci ...

if (BadRequest)
{
    Dictionary<string, object> error = new Dictionary<string, object>();
    error.Add("ErrorCode", -1);
    error.Add("ErrorMessage", "Something really bad happened");
    return Json(error);
}

Une autre façon est d'avoir un JsonErrorModel et de le remplir

public class JsonErrorModel
{
    public int ErrorCode { get; set;}

    public string ErrorMessage { get; set; }
}

public ActionResult SomeMethod()
{

    if (BadRequest)
    {
        var error = new JsonErrorModel
        {
            ErrorCode = -1,
            ErrorMessage = "Something really bad happened"
        };

        return Json(error);
    }

   //Return valid response
}

Jetez un oeil à la réponse ici ainsi

5
Stefan Bossbaly

Vous devez décider si vous voulez "erreur de niveau HTTP" (à quoi servent les codes d'erreur) ou "erreur au niveau de l'application" (à quoi sert votre réponse JSON personnalisée).

La plupart des objets de haut niveau utilisant HTTP ne regarderont jamais dans le flux de réponse si le code d'erreur est défini sur quelque chose qui n'est pas 2xx (plage de réussite). Dans votre cas, vous définissez explicitement le code d'erreur sur échec (je pense 403 ou 500) et forcez l'objet XMLHttp à ignorer le corps de la réponse.

Pour résoudre ce problème, gérez les conditions d'erreur côté client ou ne définissez pas de code d'erreur et renvoyez JSON avec les informations d'erreur (voir la réponse de Sbossb pour plus de détails).

4
Alexei Levenkov

Et si vos besoins ne sont pas aussi complexes que ceux de Sarath, vous pouvez vous en sortir avec quelque chose d'encore plus simple:

[MyError]
public JsonResult Error(string objectToUpdate)
{
   throw new Exception("ERROR!");
}

public class MyErrorAttribute : FilterAttribute, IExceptionFilter
{
   public virtual void OnException(ExceptionContext filterContext)
   {
      if (filterContext == null)
      {
         throw new ArgumentNullException("filterContext");
      }
      if (filterContext.Exception != null)
      {
         filterContext.ExceptionHandled = true;
         filterContext.HttpContext.Response.Clear();
         filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
         filterContext.HttpContext.Response.StatusCode = (int)System.Net.HttpStatusCode.InternalServerError;
         filterContext.Result = new JsonResult() { Data = filterContext.Exception.Message };
      }
   }
}
3
Brian

La chose qui a fonctionné pour moi (et que j'ai prise de ne autre réponse stackoverflow ), est de définir l'indicateur:

Response.TrySkipIisCustomErrors = true;
3
Philippe

Un moyen simple d'envoyer une erreur à Json est de contrôler le code d'état Http de l'objet de réponse et de définir un message d'erreur personnalisé.

Contrôleur

public JsonResult Create(MyObject myObject) 
{
  //AllFine
  return Json(new { IsCreated = True, Content = ViewGenerator(myObject));

  //Use input may be wrong but nothing crashed
  return Json(new { IsCreated = False, Content = ViewGenerator(myObject));  

  //Error
  Response.StatusCode = (int)HttpStatusCode.InternalServerError;
  return Json(new { IsCreated = false, ErrorMessage = 'My error message');
}

JS

$.ajax({
     type: "POST",
     dataType: "json",
     url: "MyController/Create",
     data: JSON.stringify(myObject),
     success: function (result) {
       if(result.IsCreated)
     {
    //... ALL FINE
     }
     else
     {
    //... Use input may be wrong but nothing crashed
     }
   },
    error: function (error) {
            alert("Error:" + erro.responseJSON.ErrorMessage ); //Error
        }
  });
1