83 votes

Détection d'une boucle d'auto-référencement - Récupération des données de la WebApi vers le navigateur

J'utilise Entity Framework et j'ai un problème avec l'envoi des données parent et enfant au navigateur. Voici mes classes :

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

J'utilise le code suivant pour renvoyer les données de la question et de la réponse :

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

Du côté C#, cela semble fonctionner, mais je remarque que les objets de réponse ont des références à la question. Lorsque j'utilise l'interface WebAPI pour obtenir les données dans le navigateur, j'obtiens le message suivant :

Le type 'ObjectContent`1' n'a pas réussi à sérialiser le corps de la réponse pour le type de contenu 'application/json ; charset=utf-8'.

Une boucle d'auto-référencement a été détectée pour la propriété 'question' de type 'Models.Core.Question'.

Est-ce parce que la question comporte des réponses et que les réponses renvoient à la question ? Tous les sites que j'ai consultés suggèrent d'avoir une référence au parent dans l'enfant, donc je ne sais pas trop quoi faire. Quelqu'un peut-il me donner des conseils à ce sujet ?

74voto

Sam Leach Points 5358

Est-ce parce que la question a des réponses ? à la question ?

Oui, il ne peut pas être sérialisé.

EDIT : Voir la réponse de Tallmaris et le commentaire d'OttO car c'est plus simple et peut être défini globalement.

GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.Re‌​ferenceLoopHandling = ReferenceLoopHandling.Ignore;

Ancienne réponse :

Projeter l'objet EF Question à votre propre objet intermédiaire ou DataTransferObject. Ce Dto peut alors être sérialisé avec succès.

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

Quelque chose comme :

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

56voto

Tallmaris Points 4185

Vous pouvez également l'essayer dans votre Application_Start() :

GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Serialize;

Cela devrait résoudre votre problème sans avoir à passer par de nombreuses étapes.


EDIT : Conformément au commentaire d'OttO ci-dessous, utiliser : ReferenceLoopHandling.Ignore au lieu de cela.

GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;

22voto

Mohsen Afshin Points 3643

Dans ASP.NET Core, la solution est la suivante :

services
.AddMvc()
.AddJsonOptions(x => x.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore);

21voto

klatzib Points 126

Si vous utilisez OWIN, n'oubliez pas qu'il n'y a plus de GlobalSettings pour vous ! Vous devez modifier ce même paramètre dans un objet HttpConfiguration qui est transmis à la fonction UseWebApi de IAppBuilder (ou quelle que soit la plateforme de service sur laquelle vous vous trouvez).

Cela ressemblerait à quelque chose comme ceci.

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

5voto

René Schindhelm Points 537

ASP.NET Core Web-API (.NET Core 2.0) :

// Startup.cs
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
    services.Configure<MvcJsonOptions>(config =>
    {
        config.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
    });
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X