Échec de la sérialisation de la réponse dans l'API Web avec Json


109

Je travaille avec ASP.NET MVC 5 Web Api. Je souhaite consulter tous mes utilisateurs.

J'ai écrit api/userset je reçois ceci:

"Le type 'ObjectContent`1' n'a pas réussi à sérialiser le corps de la réponse pour le type de contenu 'application / json; charset = utf-8'"

Dans WebApiConfig, j'ai déjà ajouté ces lignes:

HttpConfiguration config = new HttpConfiguration();
config.Formatters.XmlFormatter.SupportedMediaTypes.Remove(appXmlType);
config.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore; 

Mais ça ne marche toujours pas.

Ma fonction pour les données de retour est la suivante:

public IEnumerable<User> GetAll()
{
    using (Database db = new Database())
    {
        return db.Users.ToList();
    }
}

À quoi ressemble l'objet de valeur que vous essayez de transmettre au consommateur?
mckeejm

Merci beaucoup! Juste fyi - je pense que cela devrait lire: using (Database db = new Database ()) {List <UserModel> listOfUsers = new List <UserModel> (); foreach (var user dans db.Users) {UserModel userModel = new UserModel (); userModel.FirstName = user.FirstName; userModel.LastName = user.LastName; listOfUsers.Add (userModel); } IEnumerable <UserModel> users = listOfUsers; utilisateurs de retour; } .. car les résultats renvoyaient tous les mêmes valeurs.
Jared Whittington

Réponses:


76

Quand il s'agit de renvoyer des données au consommateur à partir de Web Api (ou de tout autre service Web d'ailleurs), je recommande vivement de ne pas renvoyer les entités qui proviennent d'une base de données. Il est beaucoup plus fiable et maintenable d'utiliser des modèles dans lesquels vous avez le contrôle de l'apparence des données et non de la base de données. De cette façon, vous n'avez pas à vous soucier autant des formateurs dans WebApiConfig. Vous pouvez simplement créer un UserModel qui a des modèles enfants comme propriétés et vous débarrasser des boucles de référence dans les objets de retour. Cela rend le sérialiseur beaucoup plus heureux.

En outre, il n'est généralement pas nécessaire de supprimer les formateurs ou les types de supports pris en charge si vous spécifiez simplement l'en-tête «Accepte» dans la demande. Jouer avec ce genre de choses peut parfois rendre les choses plus confuses.

Exemple:

public class UserModel {
    public string Name {get;set;}
    public string Age {get;set;}
    // Other properties here that do not reference another UserModel class.
}

Quand vous parlez de modèles, vous voulez dire ce que je fais? Renvoie IEnumerable des utilisateurs qui est un modèle.
CampDev

5
Vous renvoyez une entité. Une entité fait référence à un objet dans la base de données qui peut être récupéré par un identifiant unique. Vous renvoyez toutes les entités utilisateur de votre base de données. Je vous suggère de créer une nouvelle classe appelée "UserModel" et pour chacune des entités utilisateur que vous obtenez de la base de données, créez une nouvelle instance de la classe de modèle de données remplie avec les informations nécessaires que vous souhaitez exposer. Renvoie un IEnumerable d'objets UserModel par opposition aux entités User. Assurez-vous que les propriétés du modèle ne font pas référence aux instances de la classe UserModel. C'est ce qui vous met dans ce problème.
jensendp

3
ncampuzano est correct, vous ne voulez pas renvoyer les entités générées automatiquement. Si vous utilisiez des procédures stockées pour accéder à la base de données, la séparation serait plus claire. Vous devez avoir généré un objet de valeur C # et mappé les valeurs de IDataReader à l'objet de valeur. Puisque vous utilisez EF, des classes sont générées pour vous, mais ce sont des classes EF spéciales qui en savent plus que les objets de valeur. Vous ne devez renvoyer que des objets de valeur «stupides» à votre client.
mckeejm

1
@Donny Si vous utilisez DBContext ou un référentiel dans votre contrôleur qui renvoie des entités de la base de données, vous pouvez simplement mapper les objets sur des modèles (un DTO par exemple) dans le contrôleur ... mais je préfère avoir le le contrôleur appelle un service qui renvoie le modèle / DTO. Découvrez AutoMapper - un excellent outil pour gérer la cartographie.
ben

1
@NH. Vous pouvez absolument utiliser les manigances mentionnées ci-dessus, mais tout a sa place. Les «entités» fournies par l'accès à la couche de données doivent généralement rester dans la couche de données. Tout ce qui souhaite utiliser ces données dans la couche métier de l'application utilisera généralement les «entités» sous une forme transformée également (objets de domaine). Et puis, les données renvoyées et entrées par l'utilisateur seront généralement également un autre formulaire (Modèles). D'accord, il peut être fastidieux de faire ce type de transformation partout, mais c'est là que des outils comme AutoMapper sont vraiment utiles.
jensendp

147

Si vous travaillez avec EF, en plus d'ajouter le code ci-dessous sur Global.asax

GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings
    .ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
GlobalConfiguration.Configuration.Formatters
    .Remove(GlobalConfiguration.Configuration.Formatters.XmlFormatter);          

N'oubliez pas d'importer

using System.Data.Entity;

Ensuite, vous pouvez retourner vos propres modèles EF

Aussi simple que cela!


Même si cela peut aider pour EF, la solution n'est pas spécifique à EF et fonctionne également avec d'autres types de modèles. L'utilisation ne semble pas nécessaire dans Global.asax. Était-il destiné aux contrôleurs?
Matthieu

16
Quelques explications sur ce que fait ce code et ses implications seraient les bienvenues.
Jacob

1
Merci, je faisais face au problème similaire, cette réponse m'a aidé à résoudre le problème.
RK_Aus

Ça marche pour moi. Pas besoin d'ajouter en utilisant System.Data.Entity; à global.asax. Je vous remercie.
Dr MAF

Ça marche. Ajout de code simple ci-dessus sur Global.asax, c'est tout, pas besoin d'importer à l'aide de System.Data.Entity;
Hemant Ramphul

52

Une bonne réponse est une voie à suivre, mais il est exagéré de pouvoir le corriger avec un seul paramètre de configuration.

Mieux vaut l'utiliser dans le constructeur dbcontext

public DbContext() // dbcontext constructor
            : base("name=ConnectionStringNameFromWebConfig")
{
     this.Configuration.LazyLoadingEnabled = false;
     this.Configuration.ProxyCreationEnabled = false;
}

Erreur de l'API Web Asp.Net: le type 'ObjectContent`1' n'a pas réussi à sérialiser le corps de la réponse pour le type de contenu 'application / xml; jeu de caractères = utf-8 '


votre code sera supprimé si nous mettons à jour le modèle de la base de données.
Bimal Das

1
Vous pouvez le séparer facilement en supprimant les fichiers .tt et avoir un contexte disjoint. Chaque fois que vous générez un modèle, ajoutez simplement une nouvelle classe à la place. @Brimal: Vous pouvez suivre ce youtube.com/watch?v=yex0Z6qwe7A
Md. Alim Ul Karim

1
Pour éviter d'être écrasé, vous pouvez désactiver la charge différée à partir des propriétés edmx. Cela a fonctionné pour moi.
Francisco G

@FranciscoG cela fonctionne mais il se perd si nous supprimons edmx et régénérons.
Md. Alim Ul Karim

1
@BimalDas essayez ce youtube.com/… . Il ne supprimera pas
Md. Alim Ul Karim

37

Ajoutez ce code global.asaxci-dessous sur Application_Start:

Mettre à jour de .Ignoreà .Serialize. Cela doit marcher.

GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Serialize;
            GlobalConfiguration.Configuration.Formatters.Remove(GlobalConfiguration.Configuration.Formatters.XmlFormatter);

1
Fonctionne très bien!
Jav_1

Il n'est pas nécessaire d'ajouter le sérialiseur json (du moins dans mon cas) mais la suppression du formatage Xml était nécessaire. Je suppose que le sérialiseur xml ne peut pas sérialiser les types anonymes et qu'en le supprimant, le résultat est sérialisé en tant que json. Si ma supposition est correcte, on pourrait extraire des données du contrôleur en demandant le type MIME "application / json".
LosManos le

11
public class UserController : ApiController
{

   Database db = new Database();

   // construction
   public UserController()
   {
      // Add the following code
      // problem will be solved
      db.Configuration.ProxyCreationEnabled = false;
   }

   public IEnumerable<User> GetAll()
    {
            return db.Users.ToList();
    }
}

Wow, cela a fonctionné pour moi. Mais pourquoi? Que fait la propriété ProxyCreationEnabled?
jacktric

travaillé avec moi mais qu'est-ce que ce code? J'ai également noté que toutes les sous-classes récupérées avec null !!
Waleed A. Elgalil

10

Je n'aime pas ce code:

foreach(var user in db.Users)

Comme alternative, on pourrait faire quelque chose comme ça, qui a fonctionné pour moi:

var listOfUsers = db.Users.Select(r => new UserModel
                         {
                             userModel.FirstName = r.FirstName;
                             userModel.LastName = r.LastName;

                         });

return listOfUsers.ToList();

Cependant, j'ai fini par utiliser la solution de Lucas Roselli.

Mise à jour: simplifiée en renvoyant un objet anonyme:

var listOfUsers = db.Users.Select(r => new 
                         {
                             FirstName = r.FirstName;
                             LastName = r.LastName;
                         });

return listOfUsers.ToList();

10

Je l'ai résolu en utilisant ce code dans le fichier WebApiConfig.cs

var json = config.Formatters.JsonFormatter;
json.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.Objects; 
config.Formatters.Remove(config.Formatters.XmlFormatter);

Merci beaucoup. Je ne sais pas ce que cela fait à la sécurité.
Arun Prasad ES

6

Il y a aussi ce scénario qui génère la même erreur:

Si le retour est une List<dynamic>méthode de l'API Web

Exemple:

public HttpResponseMessage Get()
{
    var item = new List<dynamic> { new TestClass { Name = "Ale", Age = 30 } };

    return Request.CreateResponse(HttpStatusCode.OK, item);
}

public class TestClass
{
    public string Name { get; set; }
    public int Age { get; set; }
}

Donc, pour ce scénario, utilisez le [KnownTypeAttribute] dans la classe de retour (tous) comme ceci:

[KnownTypeAttribute(typeof(TestClass))]
public class TestClass
{
    public string Name { get; set; }
    public int Age { get; set; }
}

Cela fonctionne pour moi!


Et si vous utilisez linq pivot avec des colonnes dynamiques?
codegrid

[KnownTypeAttribute (typeof (TestClass))] a résolu mon problème
Guillaume Raymond

6

L'ajout de ceci dans votre Application_Start()méthode de Global.asaxfichier devrait résoudre le problème

protected void Application_Start()
{
    GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings
        .ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
    GlobalConfiguration.Configuration.Formatters
        .Remove(GlobalConfiguration.Configuration.Formatters.XmlFormatter); 
// ...
}

MÉTHODE 2: [Non recommandé]
Si vous travaillez avec EntityFramework, vous pouvez désactiver le proxy dans votre constructeur de classe DbContext. REMARQUE: ce code sera supprimé si vous mettez à jour le modèle

public class MyDbContext : DbContext
{
  public MyDbContext()
  {
    this.Configuration.ProxyCreationEnabled = false;
  }
}

1
Merci Suman. J'avais le même problème. Je testais mon API Web et je suis coincé avec ce problème. votre solution résout le problème. Merci beaucoup.
TarakPrajapati

4

Mon préféré: ajoutez simplement le code ci-dessous à App_Start/WebApiConfig.cs. Cela renverra json au lieu de XML par défaut et empêchera également l'erreur que vous avez eue. Pas besoin de modifier Global.asaxpour supprimer, XmlFormatteretc.

Le type 'ObjectContent`1' n'a pas réussi à sérialiser le corps de la réponse pour le type de contenu 'application / xml; jeu de caractères = utf-8

config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html"));

2

Utiliser AutoMapper ...

public IEnumerable<User> GetAll()
    {
        using (Database db = new Database())
        {
            var users = AutoMapper.Mapper.DynamicMap<List<User>>(db.Users);
            return users;
        }
    }

2

Utilisez l'espace de noms suivant:

using System.Web.OData;

Au lieu de :

using System.Web.Http.OData;

Ça a marché pour moi


2

Ajoutez la ligne ci-dessous

this.Configuration.ProxyCreationEnabled = false;

Deux façons d'utiliser ProxyCreationEnabledcomme false.

  1. Ajoutez-le à l'intérieur du DBContextconstructeur

    public ProductEntities() : base("name=ProductEntities")
    {
        this.Configuration.ProxyCreationEnabled = false;
    }

OU

  1. Ajoutez la ligne à l'intérieur de la Getméthode

    public IEnumerable<Brand_Details> Get()
    {
        using (ProductEntities obj = new ProductEntities())
        {
            this.Configuration.ProxyCreationEnabled = false;
            return obj.Brand_Details.ToList();
        }
    }

2

Solution qui a fonctionné pour moi:

  1. À utiliser [DataContract]pour la classe et les [DataMember]attributs de chaque propriété à sérialiser. C'est suffisant pour obtenir le résultat Json (par exemple à partir de Fiddler).

  2. Pour obtenir la sérialisation xml, écrivez Global.asaxce code:

    var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter; xml.UseXmlSerializer = true;

  3. Lisez cet article, cela m'a aidé à comprendre la sérialisation: https://www.asp.net/web-api/overview/formats-and-model-binding/json-and-xml-serialization

1

Pour ajouter à la réponse de jensendp:

Je passerais l'entité à un modèle créé par l'utilisateur et utiliserais les valeurs de cette entité pour définir les valeurs dans votre modèle nouvellement créé. Par exemple:

public class UserInformation {
   public string Name { get; set; }
   public int Age { get; set; }

   public UserInformation(UserEntity user) {
      this.Name = user.name;
      this.Age = user.age;
   }
}

Puis changez votre type de retour en: IEnumerable<UserInformation>


1
il existe des moyens plus élégants de gérer la traduction pour vous afin que vous n'ayez pas à maintenir chaque propriété. AutoMapper et ValueInjecter sont 2 notables
Sonic Soul

1

C'est mon erreur

J'ajoute essentiellement une ligne dont ils sont

  • entity.Configuration.ProxyCreationEnabled = false;

à UsersController.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using UserDataAccess;

namespace SBPMS.Controllers
{
    public class UsersController : ApiController
    {


        public IEnumerable<User> Get() {
            using (SBPMSystemEntities entities = new SBPMSystemEntities()) {
                entities.Configuration.ProxyCreationEnabled = false;
                return entities.Users.ToList();
            }
        }
        public User Get(int id) {
            using (SBPMSystemEntities entities = new SBPMSystemEntities()) {
                entities.Configuration.ProxyCreationEnabled = false;
                return entities.Users.FirstOrDefault(e => e.user_ID == id);
            }
        }
    }
}

Voici ma sortie:


1

Utilisez [Serializable] pour la classe:

Exemple:

[Serializable]
public class UserModel {
    public string Name {get;set;}
    public string Age {get;set;}
}

Cela a fonctionné pour moi!


1
Formulé comme ça, on pourrait presque penser que tous ceux qui ont déjà répondu à ce post étaient stupides;) ... Eh bien, je doute sérieusement qu'ils l'étaient, donc cela signifie probablement que cette solution n'était tout simplement pas applicable lorsque la question a été posée, en 2015 ... Je ne connais pas grand-chose à cette syntaxe moi-même, mais j'ai le sentiment que soit elle est relativement nouvelle, soit il peut y avoir des inconvénients qui la rendent inutilisable dans certains cas d'utilisation. J'ai le sentiment que votre solution pourrait être utile aux futurs lecteurs qui abordent cette question, mais cela aiderait certainement si vous clarifiiez ses limites.
jwatkins

1

Vous devrez définir Serializer Formatter dans WebApiConfig.cs disponible dans App_Start Folder comme

Ajout de config.Formatters.Remove (config.Formatters.XmlFormatter); // qui vous fournira des données au format JSON

Ajout de config.Formatters.Remove (config.Formatters.JsonFormatter); // qui vous fournira des données au format XML


Vous méritez une médaille.
TheKrogrammer

1

Mettez simplement les lignes suivantes dans global.asax:

GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;  
GlobalConfiguration.Configuration.Formatters.Remove(GlobalConfiguration.Configuration.Formatters.XmlFormatter);

Importer

using System.Data.Entity;

0

Un autre cas où j'ai reçu cette erreur était lorsque ma requête de base de données a renvoyé une valeur nulle mais que mon type de modèle utilisateur / vue a été défini comme non nullable. Par exemple, changer mon champ UserModel de intà int?résolu.


0

Cela se produit également lorsque le type de réponse n'est pas public! J'ai renvoyé une classe interne car j'ai utilisé Visual Studio pour me générer le type.

internal class --> public class

0

Bien que toutes ces réponses ci-dessus soient correctes, on peut vouloir vérifier InnerException> ExceptionMessage .

S'il indique quelque chose comme ceci " L'instance ObjectContext a été supprimée et ne peut plus être utilisée pour les opérations qui nécessitent une connexion. ". Cela peut être un problème en raison du comportement par défaut de l'EF.

En attribuant LazyLoadingEnabled = false dans votre constructeur DbContext fera l'affaire.

public class MyDbContext : DbContext
{
  public MyDbContext()
  {
    this.Configuration.LazyLoadingEnabled = false;
  }
}

Pour une lecture plus détaillée sur le comportement EagerLoading et LazyLoading d'EF, reportez-vous à cet article MSDN .


0

Dans mon cas, j'ai eu un message d'erreur similaire:

Le type 'ObjectContent`1' n'a pas réussi à sérialiser le corps de la réponse pour le type de contenu 'application / xml; jeu de caractères = utf-8 '.

Mais quand j'y ai creusé plus profondément, le problème était:

Tapez 'name.SomeSubRootType' avec le nom de contrat de données 'SomeSubRootType: //schemas.datacontract.org/2004/07/WhatEverService' n'est pas attendu. Envisagez d'utiliser un DataContractResolver si vous utilisez DataContractSerializer ou ajoutez des types non connus statiquement à la liste des types connus - par exemple, en utilisant l'attribut KnownTypeAttribute ou en les ajoutant à la liste des types connus transmis au sérialiseur.

La façon dont j'ai résolu en ajoutant KnownType.

[KnownType(typeof(SomeSubRootType))]
public partial class SomeRootStructureType

Cela a été résolu en s'inspirant de cette réponse .

Référence: https://msdn.microsoft.com/en-us/library/ms730167(v=vs.100).aspx


0

Visual Studio 2017 ou 2019 est totalement irréfléchi à ce sujet, car Visual Studio lui-même nécessite que la sortie soit au format json , tandis que le format par défaut de Visual Studio est « XmlFormat» (config.Formatters.XmlFormatter) .

Visual Studio devrait le faire automatiquement au lieu de donner autant de problèmes aux développeurs.

Pour corriger ce problème, accédez au fichier WebApiConfig.cs et ajoutez

var json = config.Formatters.JsonFormatter; json.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.Objects; config.Formatters.Remove (config.Formatters.XmlFormatter);

après " config.MapHttpAttributeRoutes (); " dans la méthode Register (HttpConfiguration config) . Cela permettrait à votre projet de produire une sortie json.


0

Dans mon cas, j'ai résolu la recréation de la base de données. J'ai apporté des modifications à un modèle et en lançant Update-Database dans la console du gestionnaire de packages, j'ai eu l'erreur suivante:

"L'instruction ALTER TABLE était en conflit avec la contrainte FOREIGN KEY" FK_dbo.Activities_dbo.Projects_ProjectId ". Le conflit s'est produit dans la base de données" TrackEmAllContext-20190530144302 ", table" dbo.Projects ", colonne 'Id'."


0

Au cas où: Si l'ajout de code à WebApiConfig.cs ou Global.asax.cs ne fonctionne pas pour vous:

.ToList();

Ajoutez la fonction .ToList ().

J'ai essayé toutes les solutions, mais la suite a fonctionné pour moi:

var allShops = context.shops.Where(s => s.city_id == id)**.ToList()**;
return allShops;

J'espère que ça aide.


0

dans mon cas, il a été corrigé lorsque j'ai supprimé le mot clé virtuel avant mes propriétés de navigation, je veux dire les tables de référence. alors j'ai changé

public virtual MembershipType MembershipType { get; set; }

à:

public MembershipType MembershipType { get; set; }
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.