Falha ao serializar a resposta na API da Web com Json

109

Estou trabalhando com ASP.NET MVC 5 Web Api. Quero consultar todos os meus usuários.

Escrevi api/userse recebo isto:

"O tipo 'ObjectContent`1' falhou ao serializar o corpo da resposta para o tipo de conteúdo 'application / json; charset = utf-8'"

No WebApiConfig, já adicionei estas linhas:

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

Mas ainda não funciona.

Minha função para dados de retorno é esta:

public IEnumerable<User> GetAll()
{
    using (Database db = new Database())
    {
        return db.Users.ToList();
    }
}
CampDev
fonte
Qual é a aparência do objeto de valor que você está tentando passar para o consumidor?
mckeejm
Muito obrigado! Apenas fyi - acho que deveria ler: using (Database db = new Database ()) {List <UserModel> listOfUsers = new List <UserModel> (); foreach (var usuário em db.Users) {UserModel userModel = new UserModel (); userModel.FirstName = user.FirstName; userModel.LastName = user.LastName; listOfUsers.Add (userModel); } IEnumerable <UserModel> users = listOfUsers; usuários de retorno; } .. como os resultados estavam retornando os mesmos valores.
Jared Whittington
1
Possível duplicata de falha ao serializar a resposta na API Web
Edvaldo Silva

Respostas:

76

Quando se trata de retornar dados ao consumidor da API Web (ou de qualquer outro serviço da Web), recomendo enfaticamente não transmitir entidades que vêm de um banco de dados. É muito mais confiável e sustentável usar modelos nos quais você tem controle da aparência dos dados e não do banco de dados. Assim, você não precisa mexer tanto com os formatadores no WebApiConfig. Você pode simplesmente criar um UserModel que tem modelos filhos como propriedades e se livrar dos loops de referência nos objetos de retorno. Isso deixa o serializador muito mais feliz.

Além disso, não é necessário remover formatadores ou tipos de mídia suportados normalmente se você estiver apenas especificando o cabeçalho "Aceita" na solicitação. Brincar com essas coisas às vezes pode tornar as coisas mais confusas.

Exemplo:

public class UserModel {
    public string Name {get;set;}
    public string Age {get;set;}
    // Other properties here that do not reference another UserModel class.
}
jensendp
fonte
Quando você se refere a modelos, quer dizer o que estou fazendo? Retorne IEnumerable of Users que é um modelo.
CampDev
5
Você está retornando uma Entidade. Uma entidade se refere a um objeto no banco de dados que pode ser recuperado por um id único. Você está retornando todas as entidades de usuário de seu banco de dados. Estou sugerindo que você crie uma nova classe chamada "UserModel" e para cada uma das entidades User que você obtém do banco de dados, crie uma nova instância da classe de modelo de dados preenchida com as informações necessárias que você deseja expor. O retorno de um IEnumerable de objetos UserModel em oposição a entidades de usuário. Certifique-se de que as Propriedades do modelo não se referem a instâncias da classe UserModel. É isso que está colocando você neste problema.
jensendp de
3
ncampuzano está correto, você não deseja retornar entidades geradas automaticamente. Se você estivesse usando procedimentos armazenados para acessar o banco de dados, a separação seria mais clara. Você precisaria ter gerado um objeto de valor C # e valores mapeados do IDataReader para o objeto de valor. Já que você está usando EF, há classes sendo geradas para você, mas essas são classes EF especiais que sabem mais do que objetos de valor. Você só deve retornar objetos de valor "burros" para o seu cliente.
mckeejm
1
@Donny Se você estiver usando DBContext ou um Repositório em seu controlador que está retornando entidades do banco de dados, então você pode apenas mapear os objetos para modelos (um DTO, por exemplo) no controlador ... mas eu prefiro ter o controlador chama um serviço que retorna o modelo / DTO. Confira AutoMapper - ótima ferramenta para manipulação de mapeamento.
ben
1
@NH. Você pode absolutamente usar as travessuras acima mencionadas, mas tudo tem seu lugar. As "entidades" fornecidas pelo acesso à camada de dados normalmente devem permanecer na camada de dados. Qualquer coisa que queira usar esses dados dentro da Camada de Negócios do aplicativo também usará as "entidades" em uma forma transformada (Objetos de Domínio). E então os dados que são retornados e inseridos pelo usuário normalmente serão de outra forma (Modelos). Concordamos que pode ser tedioso fazer esse tipo de transformação em qualquer lugar, mas é aí que ferramentas como o AutoMapper realmente são úteis.
jensendp
147

Se você está trabalhando com EF, além de adicionar o código abaixo em Global.asax

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

Não se esqueça de importar

using System.Data.Entity;

Então você pode devolver seus próprios modelos EF

Simples assim!

Lucas roselli
fonte
Mesmo que ajude para a EF, a solução não é específica da EF, e funciona também com outros tipos de modelos. O uso não parece ser necessário em Global.asax. Foi planejado para os controladores?
Matthieu
16
Algumas explicações sobre o que este código faz e suas implicações seriam bem-vindas.
Jacob
1
Obrigado, eu estava enfrentando um problema semelhante, essa resposta me ajudou a resolver o problema.
RK_Aus
Funciona para mim. Não há necessidade de adicionar usando System.Data.Entity; para global.asax. Obrigado.
Dr. MAF
Funciona. Simplesmente adicione o código acima em Global.asax, isso é tudo, não há necessidade de importar usando System.Data.Entity;
Hemant Ramphul
52

Dada a resposta certa é um caminho a percorrer, no entanto, é um exagero quando você pode corrigi-lo por meio de uma configuração.

Melhor usá-lo no construtor dbcontext

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

Erro de API Web Asp.Net: O tipo 'ObjectContent`1' falhou ao serializar o corpo de resposta para o tipo de conteúdo 'application / xml; charset = utf-8 '

Md. Alim Ul Karim
fonte
seu código será removido se atualizarmos o modelo do banco de dados.
Bimal Das
1
Você pode separá-lo facilmente removendo os arquivos .tt e ter um contexto separado. Cada vez que você gerar o modelo basta adicionar uma nova classe no local. @Brimal: Você pode acompanhar youtube.com/watch?v=yex0Z6qwe7A
Md. Alim Ul Karim
1
Para evitar a substituição, você pode desativar o carregamento lento nas propriedades do edmx. Funcionou para mim
Francisco G
@FranciscoG funciona, mas se perde se removermos o edmx e o regenerarmos.
Md. Alim Ul Karim
1
@BimalDas, tente este youtube.com/… . Não será removido
Md. Alim Ul Karim
37

Adicione este código global.asaxabaixo em Application_Start:

Atualizar de .Ignorea .Serialize. Deve funcionar.

GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Serialize;
            GlobalConfiguration.Configuration.Formatters.Remove(GlobalConfiguration.Configuration.Formatters.XmlFormatter);
Bimal Das
fonte
1
Funciona muito bem, obrigado! Você poderia explicar melhor por que temos que remover o formatador Xml para que ele funcione?
Jav_1
Não há necessidade de adicionar o serializador json (pelo menos no meu caso), mas foi necessário remover a formatação Xml. Eu acho que o serialiser xml não pode serialise tipos anônimos e removendo-o resultado é serializada como JSON. Se meu palpite estiver correto, seria possível obter dados do controlador solicitando o 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();
    }
}
Aykut
fonte
Uau, funcionou para mim. Mas por que? O que a propriedade ProxyCreationEnabled faz?
jacktric
funcionou comigo mas qual é esse código? Observei também que todas as sub classes recuperadas com null !!
Waleed A. Elgalil
10

Eu não gosto deste código:

foreach(var user in db.Users)

Como alternativa, pode-se fazer algo assim, que funcionou para mim:

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

                         });

return listOfUsers.ToList();

Porém, acabei usando a solução de Lucas Roselli.

Atualização: simplificado pelo retorno de um objeto anônimo:

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

return listOfUsers.ToList();
Kasper Halvas Jensen
fonte
10

Resolvi isso usando este código para o arquivo WebApiConfig.cs

var json = config.Formatters.JsonFormatter;
json.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.Objects; 
config.Formatters.Remove(config.Formatters.XmlFormatter);
Aemre
fonte
Muito obrigado. não sei o que isso faz com a segurança.
Arun Prasad ES
6

Também existe este cenário que gera o mesmo erro:

No caso de o retorno ser um List<dynamic>método de API da web

Exemplo:

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

Portanto, para este cenário, use o [KnownTypeAttribute] na classe de retorno (todos eles) assim:

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

Isso funciona para mim!

Alex
fonte
E se você estiver usando o linq pivot com colunas dinâmicas?
codegrid de
[KnownTypeAttribute (typeof (TestClass))] resolveu meu problema
Guillaume Raymond
6

Adicionar isso ao seu Application_Start()método de Global.asaxarquivo deve resolver o problema

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

MÉTODO 2: [Não recomendado]
Se você estiver trabalhando com EntityFramework, você pode desativar o proxy em seu construtor de classe DbContext. NOTA: este código será removido se você atualizar o modelo

public class MyDbContext : DbContext
{
  public MyDbContext()
  {
    this.Configuration.ProxyCreationEnabled = false;
  }
}
Er Suman G
fonte
1
Obrigado Suman. Eu estava tendo o mesmo problema. Estava testando minha API da web e fiquei com esse problema. sua solução resolve o problema. Muito obrigado.
TarakPrajapati
4

Meu favorito pessoal: Basta adicionar o código abaixo para App_Start/WebApiConfig.cs. Isso retornará json em vez de XML por padrão e também evitará o erro que você teve. Não há necessidade de editar Global.asaxpara remover XmlFormatteretc.

O tipo 'ObjectContent`1' falhou ao serializar o corpo da resposta para o tipo de conteúdo 'application / xml; charset = utf-8

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

Usar AutoMapper ...

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

Use o seguinte namespace:

using System.Web.OData;

Ao invés de :

using System.Web.Http.OData;

Funcionou para mim

Harish K
fonte
2

Adicione a linha abaixo

this.Configuration.ProxyCreationEnabled = false;

Duas maneiras de usar ProxyCreationEnabledcomo false.

  1. Adicione-o dentro do DBContextConstrutor

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

OU

  1. Adicione a linha dentro do Getmétodo

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

Solução que funcionou para mim:

  1. Use [DataContract]para a classe e os [DataMember]atributos de cada propriedade a ser serializada. Isso é o suficiente para obter o resultado Json (por exemplo, do violinista).

  2. Para obter a serialização xml, escreva Global.asaxneste código:

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

  3. Leia este artigo, ele me ajudou a entender a serialização: https://www.asp.net/web-api/overview/formats-and-model-binding/json-and-xml-serialization
Lapenkov Vladimir
fonte
1

Para adicionar à resposta de jensendp:

Gostaria de passar a entidade para um modelo criado pelo usuário e usar os valores dessa entidade para definir os valores em seu modelo recém-criado. Por exemplo:

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

Em seguida, altere seu tipo de retorno para: IEnumerable<UserInformation>

Greg A
fonte
1
existem maneiras mais elegantes de lidar com a tradução para você, para que você não tenha que manter todas as propriedades. AutoMapper e ValueInjecter são 2 notáveis
Sonic Soul
1

Este é o meu erro

Eu basicamente adiciono uma linha que eles são

  • entidades.Configuration.ProxyCreationEnabled = false;

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

Aqui está minha saída:

GUL EDA AYDEMİR
fonte
1

Use [Serializable] para a classe:

Exemplo:

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

Funcionou para mim!

Msxmania
fonte
1
Frase assim, quase se poderia pensar que cada um que respondeu a esta postagem antes era burro;) ... Bem, eu duvido seriamente que fossem, então isso provavelmente significa que essa solução simplesmente não era aplicável quando a pergunta foi feita, em 2015 ... Não sei muito sobre essa sintaxe, mas tenho a sensação de que é relativamente nova ou pode haver algumas desvantagens que a tornam inutilizável em certos casos de uso. Tenho a sensação de que sua solução pode ser útil para futuros leitores que estão chegando a esta questão, mas certamente ajudaria se você esclarecesse suas limitações.
jwatkins de
1

Você terá que definir o Serializer Formatter dentro de WebApiConfig.cs disponível na pasta App_Start como

Adicionando config.Formatters.Remove (config.Formatters.XmlFormatter); // que fornecerá dados no formato JSON

Adicionando config.Formatters.Remove (config.Formatters.JsonFormatter); // que fornecerá dados em formato XML

Yogesh Dangre
fonte
Você merece uma medalha.
TheKrogrammer
1

Basta colocar as seguintes linhas em global.asax:

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

Importar

using System.Data.Entity;
Samu Abbasi
fonte
0

Outro caso em que recebi esse erro foi quando minha consulta de banco de dados retornou um valor nulo, mas meu tipo de modelo de usuário / visualização foi definido como não anulável. Por exemplo, alterando meu campo UserModel de intpara int?resolvido.

marechal
fonte
0

Isso também acontece quando o Tipo de resposta não é público! Retornei uma classe interna, pois usei o Visual Studio para gerar o tipo.

internal class --> public class
Vanice
fonte
0

Embora todas as respostas acima estejam corretas, pode-se querer verificar a InnerException> ExceptionMessage .

Se disser algo como "A instância ObjectContext foi descartada e não pode mais ser usada para operações que requerem uma conexão. ". Isso pode ser um problema devido ao comportamento padrão do EF.

Ao atribuir LazyLoadingEnabled = false em seu construtor DbContext irá fazer o truque.

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

Para uma leitura mais detalhada sobre o comportamento de EagerLoading e LazyLoading da EF, consulte este artigo MSDN .

Bhramar
fonte
0

No meu caso, recebi uma mensagem de erro semelhante:

O tipo 'ObjectContent`1' falhou ao serializar o corpo da resposta para o tipo de conteúdo 'application / xml; charset = utf-8 '.

Mas quando vou mais fundo nisso, o problema era:

O tipo 'name.SomeSubRootType' com o nome do contrato de dados 'SomeSubRootType: //schemas.datacontract.org/2004/07/WhatEverService' não é esperado. Considere usar um DataContractResolver se estiver usando DataContractSerializer ou adicione quaisquer tipos não conhecidos estaticamente à lista de tipos conhecidos - por exemplo, usando o atributo KnownTypeAttribute ou adicionando-os à lista de tipos conhecidos passados ​​para o serializador.

A maneira como resolvi adicionando KnownType.

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

Isso foi resolvido inspirado nesta resposta .

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

maytham-ɯɐɥʇʎɐɯ
fonte
0

O Visual Studio 2017 ou 2019 é totalmente impensado nisso, porque o próprio Visual Studio requer que a saída esteja no formato json , enquanto o formato padrão do Visual Studio é " XmlFormat" (config.Formatters.XmlFormatter) .

O Visual Studio deve fazer isso automaticamente, em vez de dar tantos problemas aos desenvolvedores.

Para corrigir esse problema, vá para o arquivo WebApiConfig.cs e adicione

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

após " config.MapHttpAttributeRoutes (); " no método Register (HttpConfiguration config) . Isso permitiria que seu projeto produzisse saída json.

William Hou
fonte
0

No meu caso, resolvi recriar o banco de dados. Fiz algumas alterações em um modelo e, ao iniciar o Update-Database no Package Manager Console, obtive o seguinte erro:

"A instrução ALTER TABLE entrou em conflito com a restrição FOREIGN KEY" FK_dbo.Activities_dbo.Projects_ProjectId ". O conflito ocorreu no banco de dados" TrackEmAllContext-20190530144302 ", tabela" dbo.Projects ", coluna 'Id'."

Manuel Sansone
fonte
0

Caso: Se adicionar código a WebApiConfig.cs ou Global.asax.cs não funcionar para você:

.ToList();

Adicione a função .ToList ().

Tentei todas as soluções, mas as seguintes funcionaram para mim:

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

Espero que ajude.

Catalisador
fonte
0

no meu caso, foi corrigido quando removi a palavra-chave virtual antes das minhas propriedades de navegação, ou seja, as tabelas de referência. então eu mudei

public virtual MembershipType MembershipType { get; set; }

para:

public MembershipType MembershipType { get; set; }
Hadi Rezaee
fonte