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

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

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

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!


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 '


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

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

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();

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

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!


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

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"));

2

Usar AutoMapper ...

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

2

Use o seguinte namespace:

using System.Web.OData;

Ao invés de :

using System.Web.Http.OData;

Funcionou para mim


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

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

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>


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:


1

Use [Serializable] para a classe:

Exemplo:

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

Funcionou para mim!


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


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;

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.


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

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 .


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


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.


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'."


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.


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; }
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.