J'ai expérimenté la création d'un site Web qui exploite MVC avec JSON pour ma couche de présentation et le cadre d'entité pour le modèle de données/base de données. Mon problème entre en jeu avec la sérialisation de mes objets Model en JSON.
J'utilise la méthode code first pour créer ma base de données. Lors de la première méthode de code, une relation un à plusieurs (parent/enfant) nécessite que l'enfant ait une référence au parent. (Exemple de code mon être une faute de frappe, mais vous obtenez l'image)
class parent
{
public List<child> Children{get;set;}
public int Id{get;set;}
}
class child
{
public int ParentId{get;set;}
[ForeignKey("ParentId")]
public parent MyParent{get;set;}
public string name{get;set;}
}
Lors du retour d'un objet "parent" via un JsonResult, une erreur de référence circulaire est levée car "enfant" a une propriété de classe parent.
J'ai essayé l'attribut ScriptIgnore mais je perds la possibilité de regarder les objets enfants. J'aurai besoin d'afficher des informations dans une vue enfant parent à un moment donné.
J'ai essayé de créer des classes de base pour les parents et les enfants qui n'ont pas de référence circulaire. Malheureusement, lorsque j'essaie d'envoyer les basesParent et baseChild, celles-ci sont lues par l'analyseur JSON comme leurs classes dérivées (je suis presque sûr que ce concept m'échappe).
Base.baseParent basep = (Base.baseParent)parent;
return Json(basep, JsonRequestBehavior.AllowGet);
La seule solution que j'ai trouvée est de créer des modèles "View". Je crée des versions simples des modèles de base de données qui n'incluent pas la référence à la classe parent. Ces modèles de vue ont chacun une méthode pour renvoyer la version de la base de données et un constructeur qui prend le modèle de base de données comme paramètre (viewmodel.name = databasemodel.name). Cette méthode semble forcée bien qu'elle fonctionne.
REMARQUE: je poste ici parce que je pense que cette discussion mérite d'être discutée. Je pourrais tirer parti d'un modèle de conception différent pour surmonter ce problème ou cela pourrait être aussi simple que d'utiliser un attribut différent sur mon modèle. Dans ma recherche, je n'ai pas vu de bonne méthode pour surmonter ce problème.
Mon objectif final serait d'avoir une application Nice MVC qui exploite fortement JSON pour communiquer avec le serveur et afficher les données. Tout en conservant un modèle cohérent entre les couches (ou du mieux que je peux trouver).
Je vois deux sujets distincts dans votre question:
Concernant les références circulaires, je suis désolé de dire qu'il n'y a pas de solution simple. D'abord parce que JSON ne peut pas être utilisé pour représenter des références circulaires, le code suivant:
var aParent = {Children : []}, aChild = {Parent : aParent};
aParent.Children.Push(aChild);
JSON.stringify(aParent);
Résulte en: TypeError: Converting circular structure to JSON
Le seul choix que vous avez est de ne garder que le composant composite -> composant de la composition et de supprimer le composant "navigation arrière" -> composite, ainsi dans votre exemple:
class parent
{
public List<child> Children{get;set;}
public int Id{get;set;}
}
class child
{
public int ParentId{get;set;}
[ForeignKey("ParentId"), ScriptIgnore]
public parent MyParent{get;set;}
public string name{get;set;}
}
Rien ne vous empêche de recomposer cette propriété de navigation côté client, ici en utilisant jQuery:
$.each(parent.Children, function(i, child) {
child.Parent = parent;
})
Mais alors vous devrez le rejeter à nouveau avant de le renvoyer au serveur, car JSON.stringify ne pourra pas sérialiser la référence circulaire:
$.each(parent.Children, function(i, child) {
delete child.Parent;
})
Il y a maintenant le problème de l'utilisation des entités EF comme entités de modèle de vue.
Tout d'abord, EF est susceptible d'utiliser des proxys dynamiques de votre classe pour implémenter des comportements tels que la détection des modifications ou le chargement paresseux, vous devez les désactiver si vous souhaitez sérialiser les entités EF.
De plus, l'utilisation d'entités EF dans l'interface utilisateur peut être à risque car tout le classeur par défaut mappera chaque champ de la demande aux champs d'entités, y compris ceux que vous ne vouliez pas que l'utilisateur définisse.
Ainsi, si vous souhaitez que votre application MVC soit correctement conçue, je recommanderais d'utiliser un modèle de vue dédié pour éviter que les "entrailles" de votre modèle d'entreprise interne ne soient exposées au client, je vous recommanderais donc un modèle de vue spécifique.
Une alternative plus simple à la tentative de sérialisation des objets serait de désactiver la sérialisation des objets parent/enfant. Au lieu de cela, vous pouvez effectuer un appel distinct pour récupérer les objets parent/enfant associés au fur et à mesure de vos besoins. Ce n'est peut-être pas idéal pour votre application, mais c'est une option.
Pour ce faire, vous pouvez configurer un DataContractSerializer et définir la propriété DataContractSerializer.PreserveObjectReferences sur 'false' dans le constructeur de votre classe de modèle de données. Cela spécifie que les références d'objet ne doivent pas être conservées lors de la sérialisation des réponses HTTP.
Exemples:
Format Json:
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.PreserveReferencesHandling =
Newtonsoft.Json.PreserveReferencesHandling.None;
Format XML:
var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
var dcs = new DataContractSerializer(typeof(Employee), null, int.MaxValue,
false, /* preserveObjectReferences: */ false, null);
xml.SetSerializer<Employee>(dcs);
Cela signifie que si vous récupérez un élément dont les objets enfants sont référencés, les objets enfants ne seront pas sérialisés.
Voir aussi la classe DataContractsSerializer .
Voici un exemple de Jackson JSONSerializer
personnalisé qui traite des références circulaires en sérialisant la première occurrence et en stockant un * reference
à la première occurrence sur toutes les occurrences suivantes.
Traitement des références circulaires lors de la sérialisation d'objets avec Jackson
private final Set<ObjectName> seen;
/**
* Serialize an ObjectName with all its attributes or only its String representation if it is a circular reference.
* @param on ObjectName to serialize
* @param jgen JsonGenerator to build the output
* @param provider SerializerProvider
* @throws IOException
* @throws JsonProcessingException
*/
@Override
public void serialize(@Nonnull final ObjectName on, @Nonnull final JsonGenerator jgen, @Nonnull final SerializerProvider provider) throws IOException, JsonProcessingException
{
if (this.seen.contains(on))
{
jgen.writeString(on.toString());
}
else
{
this.seen.add(on);
jgen.writeStartObject();
final List<MBeanAttributeInfo> ais = this.getAttributeInfos(on);
for (final MBeanAttributeInfo ai : ais)
{
final Object attribute = this.getAttribute(on, ai.getName());
jgen.writeObjectField(ai.getName(), attribute);
}
jgen.writeEndObject();
}
}
La seule solution que j'ai trouvée est de créer des modèles "View". Je crée des versions simples des modèles de base de données qui n'incluent pas la référence à la classe parent. Ces modèles de vue ont chacun une méthode pour renvoyer la version de la base de données et un constructeur qui prend le modèle de base de données comme paramètre (viewmodel.name = databasemodel.name). Cette méthode semble forcée bien qu'elle fonctionne.
L'envoi du strict minimum de données est la seule bonne réponse. Lorsque vous envoyez des données à partir de la base de données, il n'est généralement pas judicieux d'envoyer chaque colonne avec toutes les associations. Les consommateurs ne devraient pas avoir à gérer les associations et les structures de bases de données, c'est-à-dire les bases de données. Non seulement cela économisera de la bande passante, mais il est également beaucoup plus facile à entretenir, à lire et à consommer. Recherchez les données, puis modélisez-les pour ce dont vous avez réellement besoin pour envoyer l'eq. le strict minimum.