Impossibile serializzare la risposta nell'API Web con Json


109

Sto lavorando con ASP.NET MVC 5 Web Api. Voglio consultare tutti i miei utenti.

Ho scritto api/userse ricevo questo:

"Il tipo" ObjectContent`1 "non è riuscito a serializzare il corpo della risposta per il tipo di contenuto" application / json; charset = utf-8 ""

In WebApiConfig, ho già aggiunto queste righe:

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

Ma ancora non funziona.

La mia funzione per i dati di ritorno è questa:

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

Che aspetto ha l'oggetto valore che stai cercando di trasmettere al consumatore?
mckeejm

Grazie mille! Solo fyi - penso che dovrebbe leggere: using (Database db = new Database ()) {List <UserModel> listOfUsers = new List <UserModel> (); foreach (var user in db.Users) {UserModel userModel = new UserModel (); userModel.FirstName = user.FirstName; userModel.LastName = user.LastName; listOfUsers.Add (userModel); } IEnumerable <UserModel> users = listOfUsers; utenti di ritorno; } .. poiché i risultati restituivano tutti gli stessi valori.
Jared Whittington

Risposte:


76

Quando si tratta di restituire i dati al consumatore da Web Api (o da qualsiasi altro servizio Web per quella materia), consiglio vivamente di non restituire entità che provengono da un database. È molto più affidabile e gestibile utilizzare modelli in cui si ha il controllo dell'aspetto dei dati e non del database. In questo modo non devi perdere tempo con i formattatori nel WebApiConfig. Puoi semplicemente creare un UserModel che ha modelli figlio come proprietà e sbarazzarti dei loop di riferimento negli oggetti di ritorno. Ciò rende il serializzatore molto più felice.

Inoltre, non è necessario rimuovere i formattatori oi tipi di media supportati in genere se stai specificando solo l'intestazione "Accetta" nella richiesta. Giocare con quella roba a volte può creare confusione.

Esempio:

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

Quando ti riferisci ai modelli, vuoi dire cosa sto facendo? Restituisce IEnumerable di utenti che è un modello.
CampDev

5
Stai restituendo un'entità. Un'entità fa riferimento a un oggetto nel DB che può essere recuperato da un ID univoco. Stai restituendo tutte le entità utente dal tuo database. Ti suggerisco di creare una nuova classe chiamata "UserModel" e per ciascuna delle entità utente che ottieni dal database, creare una nuova istanza della classe del modello dati popolata con le informazioni necessarie che vuoi esporre. Restituisce un IEnumerable di oggetti UserModel in contrapposizione a entità utente. Assicurati che le proprietà del modello non facciano riferimento alle istanze della classe UserModel. Questo è ciò che ti sta portando in questo problema.
jensendp

3
ncampuzano è corretto, non vuoi restituire entità generate automaticamente. Se si utilizzassero procedure memorizzate per l'accesso al database la separazione sarebbe più chiara. Dovresti aver generato un oggetto valore C # e mappato i valori da IDataReader all'oggetto valore. Dal momento che stai usando EF, ci sono classi che vengono generate per te, ma quelle sono classi EF speciali che ne sanno più degli oggetti valore. Dovresti restituire solo oggetti di valore "stupido" al tuo client.
mckeejm

1
@Donny Se stai usando DBContext o un repository nel tuo controller che sta restituendo entità dal DB, puoi semplicemente mappare gli oggetti ai modelli (un DTO per esempio) nel controller ... ma preferisco avere il il controller chiama un servizio che restituisce il modello / DTO. Dai un'occhiata a AutoMapper, ottimo strumento per la gestione della mappatura.
ben

1
@NH. Puoi assolutamente usare i suddetti imbrogli, ma tutto ha il suo posto. Le "entità" fornite dall'accesso al livello dati dovrebbero in genere rimanere nel livello dati. Tutto ciò che desidera utilizzare questi dati all'interno del livello aziendale dell'applicazione utilizzerà in genere anche le "entità" in una forma trasformata (oggetti di dominio). E poi i dati che vengono restituiti e inseriti dall'utente saranno in genere anche un altro modulo (Modelli). D'accordo che può essere noioso fare questo tipo di trasformazione ovunque, ma è qui che strumenti come AutoMapper sono davvero utili.
jensendp

147

Se stai lavorando con EF, oltre ad aggiungere il codice qui sotto su Global.asax

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

Non dimenticare di importare

using System.Data.Entity;

Quindi puoi restituire i tuoi modelli EF

Semplice come quella!


Anche se potrebbe aiutare per EF, la soluzione non è specifica per EF e funziona anche con altri tipi di modelli. L'utilizzo non sembra essere necessario in Global.asax. Era destinato ai controllori?
Matthieu

16
Alcune spiegazioni su cosa fa questo codice e le sue implicazioni sarebbero ben accette.
Jacob,

1
Grazie stavo affrontando il problema simile, questa risposta mi ha aiutato a risolvere il problema.
RK_Aus

Per me funziona. Non è necessario aggiungere utilizzando System.Data.Entity; a global.asax. Grazie.
Dr. MAF

Funziona. Semplice aggiunta di codice sopra su Global.asax, questo è tutto, non c'è bisogno di importare usando System.Data.Entity;
Hemant Ramphul

52

Data la risposta giusta è un modo per andare, tuttavia è eccessivo quando puoi risolverlo con le impostazioni di una configurazione.

Meglio usarlo nel costruttore dbcontext

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

Errore API Web Asp.Net: il tipo "ObjectContent" 1 "non è riuscito a serializzare il corpo della risposta per il tipo di contenuto" application / xml; charset = utf-8'


il tuo codice verrà rimosso se aggiorniamo il modello dal database.
Bimal Das

1
Puoi separarlo facilmente rimuovendo i file .tt e avere un contesto disgiunto. Ogni volta che si genera un modello, è sufficiente aggiungere una nuova classe nel luogo. @Brimal: Puoi seguire questo youtube.com/watch?v=yex0Z6qwe7A
Md. Alim Ul Karim

1
Per evitare la sovrascrittura, puoi disabilitare il caricamento lento, dalle proprietà di edmx. Ha funzionato per me.
Francisco G

@FranciscoG funziona ma si perde se rimuoviamo edmx e rigeneriamo.
Md. Alim Ul Karim

1
@BimalDas prova questo youtube.com/… . Non rimuoverà
Md. Alim Ul Karim

37

Aggiungi questo codice di global.asaxseguito su Application_Start:

Aggiorna da .Ignorea .Serialize. Deve funzionare.

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

1
Funziona alla grande grazie !, Potresti spiegare meglio perché dobbiamo rimuovere il formattatore Xml per farlo funzionare?
Jav_1

Non è necessario aggiungere il serializzatore json (almeno nel mio caso) ma è stato necessario rimuovere la formattazione Xml. io immagino il serialiser XML non può puntate tipi anonimi e rimuovendolo il risultato viene serializzato come JSON. Se la mia ipotesi è corretta, si sarebbe in grado di estrarre dati dal controller chiedendo il tipo MIME "application / json".
LosManos,

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, ha funzionato per me. Ma perché? Cosa fa la proprietà ProxyCreationEnabled?
jacktric

ha lavorato con me ma cos'è questo codice? ho notato anche che tutte le sottoclassi recuperate con null !!
Waleed A. Elgalil

10

Non mi piace questo codice:

foreach(var user in db.Users)

In alternativa, si potrebbe fare qualcosa di simile, che ha funzionato per me:

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

                         });

return listOfUsers.ToList();

Tuttavia, ho finito per utilizzare la soluzione di Lucas Roselli.

Aggiornamento: semplificato restituendo un oggetto anonimo:

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

return listOfUsers.ToList();

10

L'ho risolto utilizzando questo codice nel file WebApiConfig.cs

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

molte grazie. non so cosa questo faccia per la sicurezza però.
Arun Prasad ES

6

C'è anche questo scenario che genera lo stesso errore:

Nel caso in cui il ritorno sia un List<dynamic>metodo api web

Esempio:

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; }
}

Quindi, per questo scenario usa [KnownTypeAttribute] nella classe di ritorno (tutti) in questo modo:

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

Questo funziona per me!


Cosa succede se si utilizza linq pivot con colonne dinamiche?
codegrid

[KnownTypeAttribute (typeof (TestClass))] ha risolto il mio problema
Guillaume Raymond,

6

L'aggiunta di questo nel tuo Application_Start()metodo di Global.asaxfile dovrebbe risolvere il problema

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

METODO 2: [Non consigliato]
Se stai lavorando con EntityFramework, puoi disabilitare il proxy nel costruttore della classe DbContext. NOTA: questo codice verrà rimosso se aggiorni il modello

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

1
Grazie Suman. Stavo avendo lo stesso problema. Stavo testando la mia API Web e sono rimasto bloccato con questo problema. la tua soluzione risolve il problema. molte grazie.
TarakPrajapati

4

Il mio preferito personale: aggiungi il codice qui sotto a App_Start/WebApiConfig.cs. Questo restituirà json invece di XML per impostazione predefinita e impedirà anche l'errore che hai avuto. Non è necessario modificare Global.asaxper rimuovere XmlFormatterecc.

Il tipo "ObjectContent" 1 "non è riuscito a serializzare il corpo della risposta per il tipo di contenuto" application / xml; charset = utf-8

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

2

Usa AutoMapper ...

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

2

Utilizza il seguente spazio dei nomi:

using System.Web.OData;

Invece di :

using System.Web.Http.OData;

Ha funzionato per me


2

Aggiungi la riga sottostante

this.Configuration.ProxyCreationEnabled = false;

Due modi per usare ProxyCreationEnabledcome false.

  1. Aggiungilo all'interno di DBContextConstructor

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

O

  1. Aggiungi la linea all'interno del Getmetodo

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

2

Soluzione che ha funzionato per me:

  1. Utilizzare [DataContract]per la classe e gli [DataMember]attributi per ogni proprietà da serializzare. Questo è sufficiente per ottenere il risultato di Json (ad es. Dal violinista).

  2. Per ottenere la serializzazione xml scrivi Global.asaxquesto codice:

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

  3. Leggi questo articolo, mi ha aiutato a capire la serializzazione: https://www.asp.net/web-api/overview/formats-and-model-binding/json-and-xml-serialization

1

Per aggiungere alla risposta di jensendp:

Passerei l'entità a un modello creato dall'utente e utilizzerei i valori di tale entità per impostare i valori nel modello appena creato. Per esempio:

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;
   }
}

Quindi modifica il tipo di reso in: IEnumerable<UserInformation>


1
ci sono modi più eleganti per gestire la traduzione per te in modo da non dover mantenere tutte le proprietà .. AutoMapper e ValueInjecter sono 2 notevoli
Sonic Soul

1

Questo è il mio errore

Fondamentalmente aggiungo una riga che sono

  • Entity.Configuration.ProxyCreationEnabled = false;

a 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);
            }
        }
    }
}

Ecco il mio output:


1

Usa [Serializable] per la classe:

Esempio:

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

Ha funzionato per me!


1
Detto in questo modo, si potrebbe quasi pensare che tutti quelli che hanno risposto a questo post prima erano stupidi;) ... Beh, dubito seriamente che lo fossero, quindi probabilmente significa che questa soluzione semplicemente non era applicabile quando è stata posta la domanda, nel 2015 ... Non ne so molto di questa sintassi, ma ho la sensazione che sia relativamente nuova o che ci possano essere degli svantaggi che la rendono inutilizzabile in alcuni casi d'uso. Ho la sensazione che la tua soluzione potrebbe essere utile ai futuri lettori che raggiungono questa domanda, ma sarebbe sicuramente d'aiuto se chiarissi i suoi limiti.
jwatkins

1

Dovrai definire Serializer Formatter all'interno di WebApiConfig.cs disponibile in App_Start Folder come

Aggiunta di config.Formatters.Remove (config.Formatters.XmlFormatter); // che ti fornirà i dati in formato JSON

Aggiunta di config.Formatters.Remove (config.Formatters.JsonFormatter); // che ti fornirà i dati in formato XML


Ti meriti una medaglia.
TheKrogrammer

1

Basta inserire le seguenti righe in global.asax:

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

Importare

using System.Data.Entity;

0

Un altro caso in cui ho ricevuto questo errore è stato quando la mia query sul database ha restituito un valore nullo ma il mio tipo di modello utente / vista è stato impostato come non annullabile. Ad esempio, cambiando il mio campo UserModel da inta int?risolto.


0

Ciò accade anche quando Response-Type non è pubblico! Ho restituito una classe interna poiché ho utilizzato Visual Studio per generarmi il tipo.

internal class --> public class

0

Sebbene tutte queste risposte sopra siano corrette, si potrebbe voler controllare InnerException> ExceptionMessage .

Se dice qualcosa del genere "L ' istanza di ObjectContext è stata eliminata e non può più essere utilizzata per operazioni che richiedono una connessione. ". Questo potrebbe essere un problema a causa del comportamento predefinito dell'EF.

Assegnando LazyLoadingEnabled = false nel tuo costruttore DbContext farà il trucco.

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

Per una lettura più dettagliata sul comportamento di EagerLoading e LazyLoading di EF, fare riferimento a questo articolo MSDN .


0

Nel mio caso ho ricevuto un messaggio di errore simile:

Il tipo "ObjectContent" 1 "non è riuscito a serializzare il corpo della risposta per il tipo di contenuto" application / xml; charset = utf-8' .

Ma quando ho scavato più a fondo, il problema era:

Digitare "name.SomeSubRootType" con il nome del contratto dati "SomeSubRootType: //schemas.datacontract.org/2004/07/WhatEverService" non è previsto. Prendi in considerazione l'utilizzo di un DataContractResolver se stai usando DataContractSerializer o aggiungi qualsiasi tipo non noto staticamente all'elenco dei tipi noti, ad esempio usando l'attributo KnownTypeAttribute o aggiungendoli all'elenco dei tipi noti passati al serializzatore.

Il modo in cui ho risolto aggiungendo KnownType.

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

Questo è stato risolto ispirato da questa risposta .

Riferimento: https://msdn.microsoft.com/en-us/library/ms730167(v=vs.100).aspx


0

Visual Studio 2017 o 2019 è assolutamente impensabile su questo, perché Visual Studio stesso richiede che l'output sia in formato json , mentre il formato predefinito di Visual Studio è " XmlFormat" (config.Formatters.XmlFormatter) .

Visual Studio dovrebbe farlo automaticamente invece di dare tanti problemi agli sviluppatori.

Per correggere questo problema, vai al file WebApiConfig.cs e aggiungi

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

dopo " config.MapHttpAttributeRoutes (); " nel metodo Register (HttpConfiguration config) . Ciò consentirebbe al tuo progetto di produrre output json.


0

Nel mio caso ho risolto ricreando il database. Ho apportato alcune modifiche a un modello e lanciando Update-Database nella console di Gestione pacchetti ho ricevuto il seguente errore:

"L'istruzione ALTER TABLE era in conflitto con il vincolo FOREIGN KEY" FK_dbo.Activities_dbo.Projects_ProjectId ". Il conflitto si è verificato nel database" TrackEmAllContext-20190530144302 ", tabella" dbo.Projects ", colonna" Id "."


0

Nel caso: se l'aggiunta di codice a WebApiConfig.cs o Global.asax.cs non funziona per te:

.ToList();

Aggiungi la funzione .ToList ().

Ho provato ogni soluzione, ma per me ha funzionato:

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

Spero possa essere d'aiuto.


0

nel mio caso, è stato corretto quando ho rimosso la parola chiave virtuale prima delle mie proprietà di navigazione, intendo le tabelle di riferimento. quindi sono cambiato

public virtual MembershipType MembershipType { get; set; }

per:

public MembershipType MembershipType { get; set; }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.