Не вдалося серіалізувати відповідь у веб-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

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

Відповіді:


76

Що стосується повернення даних споживачеві з веб-сервісу Api (або будь-якого іншого веб-сервісу з цього приводу), я настійно рекомендую не передавати назад об’єкти, що надходять із бази даних. Набагато надійніше та рентабельніше використовувати Моделі, в яких ви маєте контроль над тим, як виглядають дані, а не з базою даних. Таким чином, вам не доведеться сильно возитися з форматерами в WebApiConfig. Ви можете просто створити UserModel, який має дочірні Моделі як властивості, і позбутися від циклів посилань у об'єктах, що повертаються. Це робить серіалізатор набагато щасливішим.

Крім того, не потрібно видаляти форматери або підтримувані типи носіїв, як правило, ви просто вказуєте заголовок "Приймає". Граючи з цими речами, іноді можуть зробити речі більш заплутаними.

Приклад:

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

Коли ви посилаєтесь на Моделі, ви хочете сказати, що я роблю? Повертайте IE чимало користувачів, які є моделлю.
CampDev

5
Ви повертаєте сутність. Суб'єкт відноситься до об'єкта в БД, який можна отримати за допомогою унікального ідентифікатора. Ви повертаєте всі об’єкти Користувача зі своєї бази даних. Я пропоную вам створити новий клас під назвою "UserModel" і для кожного з об'єктів Користувача, який ви отримаєте з бази даних, створити новий екземпляр класу моделі даних, заповненого необхідною інформацією, яку ви хочете викрити. Повернення IE численних об'єктів UserModel на відміну від об'єктів Користувача. Переконайтесь, що властивості моделі не посилаються на екземпляри класу 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. Це було призначено для контролерів?
Матьє

16
Деякі пояснення щодо того, що робить цей код, та його наслідки будуть вітатися.
Яків

1
Дякую, що я зіткнувся з подібною проблемою, ця відповідь допомогла мені вирішити питання.
RK_Aus

Це працює для мене. Не потрібно додавати за допомогою System.Data.Entity; до global.asax. Дякую.
Д-р МАФ

Це працює. Простий додавання коду вище на Global.asax, це все, Не потрібно імпортувати за допомогою System.Data.Entity;
Хемант Рамфул

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 '


ваш код буде видалено, якщо ми оновимо модель з бази даних.
Бімал Дас

1
Ви можете легко відокремити його, видаливши файли .tt і маючи непересічний контекст. Кожен раз, коли ви генеруєте модель, просто додайте новий клас на місце. @Brimal: Ви можете слідкувати за цим youtube.com/watch?v=yex0Z6qwe7A
. Алім Уль Карим

1
Щоб уникнути перезапису, ви можете відключити ліниве навантаження від властивостей edmx. Це працювало для мене.
Francisco G

@FranciscoG працює, але він втрачається, якщо видалити edmx та відновити.
Пані Алім Уль Карім

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".
ЛосМанос

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

Отже, для цього сценарію використовуйте [K knownTypeAttribute] у класі повернення (усі вони) таким чином:

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

Це працює для мене!


Що робити, якщо ви використовуєте шарнір linq з динамічними стовпцями?
codegrid

[K PoznaTypeAttribute (typeof (TestClass))] вирішив мою проблему
Гійом Реймонд

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; charset = utf-8

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

2

Використовувати Автоматичне складання ...

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;

Два способи використання ProxyCreationEnabledяк false.

  1. Додайте його всередину DBContextConstructor

    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 (наприклад, від fiddler).

  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

Це моя помилка

Я в основному додаю один рядок, який вони є

  • Субстанції.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

Вам потрібно буде визначити формат серіалізатора в WebApiConfig.cs, доступному в App_Start Folder, як

Додавання 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

Інший випадок, коли я отримав цю помилку, коли запит на базу даних повернув нульове значення, але тип моделі користувача / перегляду був встановлений як нерегульований. Наприклад, змінюючи поле мого UserModel від intдо int?вирішена.


0

Це також відбувається, коли тип відповіді не є загальнодоступним! Я повернув внутрішній клас, коли використовував Visual Studio, щоб створити мені тип.

internal class --> public class

0

Хоча всі ці відповіді вище правильні, можна перевірити InnerException> ExceptionMessage .

Якщо це говорить щось подібне, "T екземпляр ObjectContext був розміщений і більше не може використовуватися для операцій, які потребують з'єднання. " Це може бути проблемою через поведінку EF за замовчуванням.

Призначаючи LazyLoadingEnabled = false у вашому конструкторі 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 або додайте до списку відомих типів будь-які типи, невідомі статично - наприклад, за допомогою атрибута KknownTypeAttribute або додавши їх до списку відомих типів, переданих у серіалізатор.

Те, як я вирішив, додавши 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 (); " у методі Регістр (HttpConfiguration config) . Це дозволить вашому проекту отримати вихід json.


0

У моєму випадку я вирішив відтворити базу даних. Я вніс деякі зміни в модель і запустив Update-Database в Console Package Manager, я отримав таку помилку:

"Оператор 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

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

public virtual MembershipType MembershipType { get; set; }

до:

public MembershipType MembershipType { get; set; }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.