Не удалось сериализовать ответ в веб-API с помощью Json


109

Я работаю с ASP.NET MVC 5 Web Api. Я хочу проконсультироваться со всеми моими пользователями.

Я написал api/usersи получаю это:

«Типу 'ObjectContent`1' не удалось сериализовать тело ответа для типа контента 'application / json; charset = utf-8'»

В WebApiConfig я уже добавил эти строки:

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

Но все равно не работает.

Моя функция для возвращаемых данных такова:

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

Как выглядит объект значения, который вы пытаетесь передать потребителю?
mckeejm

Спасибо! Просто к сведению - я думаю, это следует читать: using (Database db = new Database ()) {List <UserModel> listOfUsers = new List <UserModel> (); foreach (пользователь var в db.Users) {UserModel userModel = new UserModel (); userModel.FirstName = user.FirstName; userModel.LastName = user.LastName; listOfUsers.Add (userModel); } IEnumerable <UserModel> users = listOfUsers; вернуть пользователей; } .. поскольку все результаты возвращали одинаковые значения.
Джаред Уиттингтон

Ответы:


76

Когда дело доходит до возврата данных потребителю из Web Api (или любой другой веб-службы, если на то пошло), я настоятельно рекомендую не передавать обратно объекты, поступающие из базы данных. Гораздо надежнее и удобнее использовать модели, в которых вы можете контролировать, как выглядят данные, а не базу данных. Таким образом, вам не придется так много возиться с форматерами в WebApiConfig. Вы можете просто создать UserModel с дочерними моделями в качестве свойств и избавиться от циклов ссылок в возвращаемых объектах. Это делает сериализатор намного счастливее.

Кроме того, обычно нет необходимости удалять средства форматирования или поддерживаемые типы мультимедиа, если вы просто указываете заголовок «Accepts» в запросе. Игра с этим иногда может запутать вас еще больше.

Пример:

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

Когда вы говорите о моделях, вы хотите сказать, что я делаю? Вернуть IEnumerable пользователей, которые являются моделью.
CampDev

5
Вы возвращаете объект. Сущность относится к объекту в БД, который можно получить по уникальному идентификатору. Вы возвращаете все объекты User из своей базы данных. Я предлагаю вам создать новый класс под названием «UserModel» и для каждого объекта User, который вы получаете из базы данных, создать новый экземпляр класса модели данных, заполненный необходимой информацией, которую вы хотите раскрыть. Возвращает IEnumerable объектов UserModel в отличие от сущностей User. Убедитесь, что свойства модели не относятся к экземплярам класса UserModel. Вот что втягивает вас в эту проблему.
jensendp

3
ncampuzano верен, вы не хотите возвращать автоматически сгенерированные объекты. Если бы вы использовали хранимые процедуры для доступа к базе данных, разделение было бы более четким. Вам нужно было бы создать объект значения C # и сопоставить значения из IDataReader с объектом значения. Поскольку вы используете EF, для вас создаются классы, но это специальные классы EF, которые знают больше, чем объекты значений. Вы должны возвращать своему клиенту только «тупые» объекты значений.
mckeejm

1
@Donny Если вы используете DBContext или репозиторий в своем контроллере, который возвращает объекты обратно из БД, вы можете просто сопоставить объекты с моделями (например, DTO) в контроллере ... но я предпочитаю иметь Контроллер вызывает службу, которая возвращает модель / DTO. Обратите внимание на AutoMapper - отличный инструмент для обработки карт.
Бен

1
@NH. Вы можете использовать вышеупомянутые махинации, но всему есть свое место. «Сущности», предоставляемые доступом к уровню данных, обычно должны оставаться на уровне данных. Все, что хочет использовать эти данные на бизнес-уровне приложения, обычно также будет использовать «сущности» в преобразованной форме (объекты домена). И тогда данные, которые возвращаются и вводятся пользователем, обычно также будут другой формой (модели). Согласны, что это может быть утомительно везде выполнять такие преобразования, но именно здесь такие инструменты, как AutoMapper, действительно пригодятся.
jensendp

147

Если вы работаете с EF, помимо добавления кода ниже на Global.asax

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

Не забудьте импортировать

using System.Data.Entity;

Затем вы можете вернуть свои собственные модели EF

Просто как тот!


Даже если это может помочь для EF, решение не относится к EF и работает также с другими типами моделей. Использование Global.asax не представляется необходимым. Это было предназначено для контроллеров?
Matthieu

16
Приветствуются некоторые объяснения того, что делает этот код и его последствия.
Джейкоб

1
Спасибо, что столкнулся с аналогичной проблемой, этот ответ помог мне решить проблему.
RK_Aus

Меня устраивает. Не нужно добавлять using System.Data.Entity; в global.asax. Спасибо.
Доктор МАФ

Оно работает. Просто добавьте код выше на Global.asax, вот и все, нет необходимости импортировать с помощью System.Data.Entity;
Хемант Рамфул 01

52

Правильный ответ - это один из возможных вариантов, однако это перебор, когда вы можете исправить это с помощью одной настройки конфигурации.

Лучше использовать его в конструкторе dbcontext

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

Ошибка веб-API Asp.Net: типу ObjectContent`1 не удалось сериализовать тело ответа для типа контента application / xml; charset = utf-8 '


ваш код будет удален, если мы обновим модель из базы данных.
Bimal Das

1
Вы можете легко разделить его, удалив файлы .tt и имея несвязанный контекст. Каждый раз, когда вы создаете модель, просто добавляйте новый класс на место. @Brimal: Вы можете следить за этим youtube.com/watch?v=yex0Z6qwe7A
Md. Alim Ul Karim

1
Чтобы избежать перезаписи, вы можете отключить отложенную загрузку в свойствах edmx. У меня это сработало.
Francisco G

@FranciscoG работает, но теряется, если мы удалим edmx и регенерируем.
Md. Alim Ul Karim

1
@BimalDas попробуйте это youtube.com/… . Он не удалится
М. Алим Уль Карим

37

Добавьте этот код global.asaxниже Application_Start:

Обновить с .Ignoreна .Serialize. Это должно сработать.

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

1
Отлично работает, спасибо! Не могли бы вы лучше объяснить, почему нам нужно удалить Xml Formatter, чтобы он работал?
Jav_1

Нет необходимости добавлять сериализатор json (по крайней мере, в моем случае), но необходимо было удалить форматирование Xml. Я предполагаю, что сериализатор xml не может сериализовать анонимные типы, и, удалив его, результат сериализуется как json. Если моя догадка верна, можно будет получить данные из контроллера, запросив тип 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();
    }
}

Вау, у меня это сработало. Но почему? Что делает свойство ProxyCreationEnabled?
jacktric

работал со мной, но что это за код? Я также отметил, что все подклассы извлекаются с нулевым значением !!
Waleed A. Elgalil

10

Мне не нравится этот код:

foreach(var user in db.Users)

В качестве альтернативы можно было бы сделать что-то вроде этого, что сработало для меня:

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

                         });

return listOfUsers.ToList();

Однако в итоге я использовал решение Лукаса Роселли.

Обновление: упрощено за счет возврата анонимного объекта:

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

return listOfUsers.ToList();

10

Я решил это, используя этот код в файле WebApiConfig.cs

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

большое спасибо. не знаю, что это делает с безопасностью.
Arun Prasad ES

6

Также существует сценарий, который генерирует такую ​​же ошибку:

В случае возврата List<dynamic> к методу веб-API

Пример:

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

Итак, для этого сценария используйте [KnownTypeAttribute] в возвращаемом классе (все они) следующим образом:

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

У меня это работает!


Что делать, если вы используете linq pivot с динамическими столбцами?
codegrid

[KnownTypeAttribute (typeof (TestClass))] решил мою проблему
Guillaume Raymond

6

Добавление этого в свой Application_Start()метод Global.asaxфайла должно решить проблему

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

МЕТОД 2: [Не рекомендуется]
Если вы работаете с EntityFramework, вы можете отключить прокси в конструкторе класса DbContext. ПРИМЕЧАНИЕ: этот код будет удален, если вы обновите модель.

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

1
Спасибо, Суман. У меня была такая же проблема. Я тестировал свой веб-API и столкнулся с этой проблемой. ваше решение решает проблему. большое спасибо.
TarakPrajapati

4

Мой личный фаворит: просто добавьте приведенный ниже код в App_Start/WebApiConfig.cs. Это вернет json вместо XML по умолчанию, а также предотвратит возникшую ошибку. Не нужно редактировать, Global.asaxчтобы удалить XmlFormatterи т. Д.

Типу 'ObjectContent`1' не удалось сериализовать тело ответа для типа содержимого 'application / xml; кодировка = UTF-8

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

2

Используйте AutoMapper ...

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

2

Используйте следующее пространство имен:

using System.Web.OData;

Вместо того :

using System.Web.Http.OData;

Это сработало для меня


2

Добавьте строку ниже

this.Configuration.ProxyCreationEnabled = false;

Два способа использования ProxyCreationEnabledas false.

  1. Добавьте его в DBContextконструктор

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

ИЛИ

  1. Добавьте строку внутри Getметода

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

2

Решение, которое сработало для меня:

  1. Используйте [DataContract]для класса и [DataMember]атрибутов для каждого сериализуемого свойства. Этого достаточно, чтобы получить результат Json (например, от скрипачей).

  2. Чтобы получить сериализацию xml, напишите в Global.asaxэтом коде:

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

  3. Прочтите эту статью, она помогла мне понять сериализацию: https://www.asp.net/web-api/overview/formats-and-model-binding/json-and-xml-serialization

1

Чтобы добавить к ответу jensendp:

Я бы передал объект в созданную пользователем модель и использовал значения из этого объекта для установки значений в вашей вновь созданной модели. Например:

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

Затем измените тип возвращаемого значения на: IEnumerable<UserInformation>


1
есть более элегантные способы сделать перевод для вас, так что вам не нужно поддерживать каждое свойство .. AutoMapper и ValueInjecter - два примечательные из них
Sonic Soul

1

Это моя ошибка

Я в основном добавляю одну строчку, в которой они

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

Вот мой результат:


1

Используйте [Serializable] для класса:

Пример:

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

У меня это сработало!


1
Выражаясь таким образом, можно было почти подумать, что каждый, кто раньше отвечал на этот пост, был тупым;)… Ну, я серьезно сомневаюсь, что да, так что, вероятно, это означает, что это решение было просто неприменимо, когда был задан вопрос, еще в 2015 году ... Я сам мало что знаю об этом синтаксисе, но мне кажется, что либо он относительно новый, либо в нем могут быть некоторые недостатки, которые делают его непригодным для использования в определенных случаях использования. У меня такое чувство, что ваше решение может быть полезно для будущих читателей, задающих этот вопрос, но оно, безусловно, поможет, если вы проясните его ограничения.
jwatkins

1

Вам нужно будет определить Serializer Formatter в WebApiConfig.cs, доступном в папке App_Start, например

Добавление config.Formatters.Remove (config.Formatters.XmlFormatter); // который предоставит вам данные в формате JSON

Добавление config.Formatters.Remove (config.Formatters.JsonFormatter); // который предоставит вам данные в формате XML


Вы заслуживаете медали.
TheKrogrammer

1

Просто введите следующие строки в global.asax:

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

Импортировать

using System.Data.Entity;

0

Еще один случай, когда я получил эту ошибку, - это когда мой запрос к базе данных возвратил нулевое значение, но мой тип модели пользователь / представление был установлен как не допускающий значения NULL. Например, изменение моего поля UserModel с intна int?разрешенное.


0

Это также происходит, когда Response-Type не является общедоступным! Я вернул внутренний класс, поскольку использовал Visual Studio для создания типа.

internal class --> public class

0

Хотя все приведенные выше ответы верны, можно проверить InnerException> ExceptionMessage .

Если он говорит что-то вроде этого: « Экземпляр ObjectContext удален и больше не может использоваться для операций, требующих подключения. ». Это может быть проблемой из-за поведения EF по умолчанию.

Назначая LazyLoadingEnabled = ложь в конструкторе DbContext будет делать трюк.

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

Для получения более подробной информации о поведении EagerLoading и LazyLoading EF обратитесь к этой статье MSDN .


0

В моем случае у меня было аналогичное сообщение об ошибке:

Типу 'ObjectContent`1' не удалось сериализовать тело ответа для типа содержимого 'application / xml; charset = utf-8 '.

Но когда я копнул глубже, проблема была в следующем:

Введите name.SomeSubRootType с именем контракта данных SomeSubRootType: //schemas.datacontract.org/2004/07/WhatEverService не ожидается. Рассмотрите возможность использования DataContractResolver, если вы используете DataContractSerializer, или добавьте какие-либо типы, не известные статически, в список известных типов - например, с помощью атрибута KnownTypeAttribute или путем добавления их в список известных типов, передаваемых в сериализатор.

Способ, которым я решил, добавив KnownType.

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

Это было решено на основе этого ответа .

Ссылка: https://msdn.microsoft.com/en-us/library/ms730167(v=vs.100).aspx


0

Visual Studio 2017 или 2019 совершенно не думают об этом, потому что сама Visual Studio требует, чтобы вывод был в формате json , в то время как формат Visual Studio по умолчанию - « XmlFormat» (config.Formatters.XmlFormatter) .

Visual Studio должна делать это автоматически, чтобы не доставлять разработчикам столько хлопот.

Чтобы исправить эту проблему, перейдите в файл WebApiConfig.cs и добавьте

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

после " config.MapHttpAttributeRoutes (); " в методе Register (конфигурация HttpConfiguration) . Это позволит вашему проекту производить вывод в формате json.


0

В моем случае я решил воссоздать базу данных. Я внес некоторые изменения в модель и, запустив Update-Database в консоли диспетчера пакетов, получил следующую ошибку:

«Оператор ALTER TABLE конфликтует с ограничением FOREIGN KEY« FK_dbo.Activities_dbo.Projects_ProjectId ». Конфликт произошел в базе данных« TrackEmAllContext-20190530144302 », таблица« dbo.Projects », столбец« Id »».


0

В случае: если добавление кода в WebApiConfig.cs или Global.asax.cs у вас не работает:

.ToList();

Добавить функцию .ToList ().

Я опробовал все решения, но у меня сработало следующее:

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

Я надеюсь, что это помогает.


0

в моем случае это было исправлено, когда я удалил ключевое слово virtual перед своими свойствами навигации, я имею в виду справочные таблицы. поэтому я изменил

public virtual MembershipType MembershipType { get; set; }

кому:

public MembershipType MembershipType { get; set; }
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.