Обнаружен цикл саморегуляции - Возврат данных из WebApi в браузер
Я использую Entity Framework и имею проблему с получением родительских и дочерних данных в браузере. Вот мои классы:
public class Question
{
public int QuestionId { get; set; }
public string Title { get; set; }
public virtual ICollection<Answer> Answers { get; set; }
}
public class Answer
{
public int AnswerId { get; set; }
public string Text { get; set; }
public int QuestionId { get; set; }
public virtual Question Question { get; set; }
}
Я использую следующий код для возврата данных вопросов и ответов:
public IList<Question> GetQuestions(int subTopicId, int questionStatusId)
{
var questions = _questionsRepository.GetAll()
.Where(a => a.SubTopicId == subTopicId &&
(questionStatusId == 99 ||
a.QuestionStatusId == questionStatusId))
.Include(a => a.Answers)
.ToList();
return questions;
}
На стороне С# это, похоже, работает, однако я замечаю, что объекты ответа ссылаются на вопрос. Когда я использую WebAPI для получения данных в браузере, я получаю следующее сообщение:
Тип ObjectContent`1 не смог сериализовать тело ответа для типа контента 'application/json; кодировка = UTF-8'.
Ядро референтных ссылок обнаружено для свойства "вопрос" с типом "Models.Core.Question".
Это потому, что у Вопроса есть ответы и ответы есть ссылка на Вопрос? Все места, которые я искал, предлагают иметь ссылку на родителя в ребенке, поэтому я не уверен, что делать. Может кто-нибудь дать мне несколько советов по этому поводу.
Ответы
Ответ 1
Это потому, что у Вопроса есть ответы и ответы ссылаться на вопрос?
Да. Он не может быть сериализован.
EDIT: см. комментарий Tallmaris и комментарий OttO, поскольку он проще и может быть установлен глобально.
GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
Старый ответ:
Проецируйте объект EF Question
на свой собственный промежуточный или DataTransferObject. Это Dto может быть успешно сериализовано.
public class QuestionDto
{
public QuestionDto()
{
this.Answers = new List<Answer>();
}
public int QuestionId { get; set; }
...
...
public string Title { get; set; }
public List<Answer> Answers { get; set; }
}
Что-то вроде:
public IList<QuestionDto> GetQuestions(int subTopicId, int questionStatusId)
{
var questions = _questionsRepository.GetAll()
.Where(a => a.SubTopicId == subTopicId &&
(questionStatusId == 99 ||
a.QuestionStatusId == questionStatusId))
.Include(a => a.Answers)
.ToList();
var dto = questions.Select(x => new QuestionDto { Title = x.Title ... } );
return dto;
}
Ответ 2
Вы также можете попробовать это в вашем Application_Start()
:
GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Serialize;
Это должно исправить вашу проблему, не проходя через много обручей.
РЕДАКТИРОВАТЬ: В соответствии с комментарием OttO ниже, используйте: ReferenceLoopHandling.Ignore
вместо. GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
Ответ 3
В ядре ASP.NET исправление выглядит следующим образом:
services
.AddMvc()
.AddJsonOptions(x => x.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore);
Ответ 4
Если вы используете OWIN, помните, больше не GlobalSettings для вас! Вы должны изменить этот же параметр в объекте HttpConfiguration, который передается функции IAppBuilder UseWebApi (или любой другой платформы обслуживания, на которой вы находитесь)
Будет выглядеть примерно так.
public void Configuration(IAppBuilder app)
{
//auth config, service registration, etc
var config = new HttpConfiguration();
config.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
//other config settings, dependency injection/resolver settings, etc
app.UseWebApi(config);
}
Ответ 5
Если используется DNX/MVC 6/ASP.NET vNext blah blah, даже HttpConfiguration
отсутствует. Вы должны конфигурировать форматировщики, используя следующие коды в вашем файле Startup.cs
.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().Configure<MvcOptions>(option =>
{
//Clear all existing output formatters
option.OutputFormatters.Clear();
var jsonOutputFormatter = new JsonOutputFormatter();
//Set ReferenceLoopHandling
jsonOutputFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
//Insert above jsonOutputFormatter as the first formatter, you can insert other formatters.
option.OutputFormatters.Insert(0, jsonOutputFormatter);
});
}
Ответ 6
Веб-API ASP.NET Core (.NET Core 2.0):
// Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.Configure<MvcJsonOptions>(config =>
{
config.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
});
}
Ответ 7
Используя это:
GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore
не работал у меня. Вместо этого я создал новую, упрощенную версию моего модельного класса, просто чтобы проверить, и это вернулось. Эта статья затрагивает некоторые проблемы, которые я испытывал в своей модели, которая отлично поработала для EF, но не была сериализована:
http://www.asp.net/web-api/overview/data/using-web-api-with-entity-framework/part-4
Ответ 8
ReferenceLoopHandling.Ignore не работает для меня. Единственный способ, которым я мог обойти это, - удалить с помощью кода ссылки обратно родителям, которых я не хотел, и сохранить те, которые я сделал.
parent.Child.Parent = null;
Ответ 9
Из-за ленивой загрузки вы получаете эту ошибку. Поэтому мое предложение состоит в том, чтобы удалить виртуальный ключ из свойства. Если вы работаете с API, тогда ленивая загрузка не подходит для вашего здоровья API.
Не нужно добавлять дополнительную строку в свой файл конфигурации.
public class Question
{
public int QuestionId { get; set; }
public string Title { get; set; }
public ICollection<Answer> Answers { get; set; }
}
public class Answer
{
public int AnswerId { get; set; }
public string Text { get; set; }
public int QuestionId { get; set; }
public Question Question { get; set; }
}
Ответ 10
Я обнаружил, что эта ошибка возникает, когда я создал edmx (XML файл, который определяет концептуальную модель) существующей базы данных, и имел свойства навигации как для родительской, так и для дочерних таблиц. Я удалил все ссылки навигации к родительским объектам, так как я только хотел перейти к детям, и проблема была решена.
Ответ 11
Объекты db = новые объекты()
db.Configuration.ProxyCreationEnabled = false;
db.Configuration.LazyLoadingEnabled = false;
Ответ 12
Для нового веб-приложения Asp.Net с использованием .Net Framework 4.5:
Web Api: Goto App_Start → WebApiConfig.cs:
Должен выглядеть примерно так:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Configure Web API to use only bearer token authentication.
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
// ReferenceLoopHandling.Ignore will solve the Self referencing loop detected error
config.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
//Will serve json as default instead of XML
config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html"));
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
Ответ 13
Вы можете динамически создавать новую дочернюю коллекцию, чтобы легко обойти эту проблему.
public IList<Question> GetQuestions(int subTopicId, int questionStatusId)
{
var questions = _questionsRepository.GetAll()
.Where(a => a.SubTopicId == subTopicId &&
(questionStatusId == 99 ||
a.QuestionStatusId == questionStatusId))
.Include(a => a.Answers).Select(b=> new {
b.QuestionId,
b.Title
Answers = b.Answers.Select(c=> new {
c.AnswerId,
c.Text,
c.QuestionId }))
.ToList();
return questions;
}
Ответ 14
Ни одна из конфигураций в ответах выше не работала для меня в ASP.NET Core 2.2.
У меня было добавление атрибутов JsonIgnore
в моих свойствах виртуальной навигации.
public class Question
{
public int QuestionId { get; set; }
public string Title { get; set; }
[JsonIgnore]
public virtual ICollection<Answer> Answers { get; set; }
}