2017-10-12 3 views
0

J'utilise AngularJS pour manipuler un objet parent assez complexe avec des enfants qui doivent se comporter différemment du côté serveur. Basé sur this answer, ce qui semble assez solide, j'ai créé le cas de test ci-dessous. Le problème que je rencontre est que chaque fois que j'entre la fonction CreateModel, tout appel à bindingContext.ValueProvider.GetValue(key) renvoie null. J'ai vérifié toutes les valeurs dans le débogueur. Le type d'objet semble être chargé, mais aucune valeur n'a encore été liée.ModelBindingContext.ValueProvider.GetValue (key) retourne toujours null

Mes modèles:

public class Menagerie 
{ 
    public Menagerie() 
    { 
     Critters = new List<Creature>(); 
    } 

    public string MakeNoise() 
    { 
     return String.Join(" ", Critters.Select(c => c.MakeNoise())); 
    } 

    public List<Creature> Critters { get; set; } 
} 

public class Tiger : Creature 
{ 
    public Tiger() { } 
    public override CreatureType Type => CreatureType.Tiger; 
    public override string Sound => "ROAR"; 
} 

public class Kitty : Creature 
{ 
    public Kitty() { } 
    public override CreatureType Type => CreatureType.Kitty; 
    public override string Sound => "meow"; 
} 

public class Creature 
{ 
    public Creature() { } 
    public string Name { get; set; } 
    public virtual CreatureType Type { get; set; } 
    public virtual string Sound { get; } 
    public string MakeNoise() 
    { 
     return $"{Name} says {Sound ?? "nothing"}."; 
    } 

    public static Type SelectFor(CreatureType type) 
    { 
     switch (type) 
     { 
      case CreatureType.Tiger: 
       return typeof(Tiger); 
      case CreatureType.Kitty: 
       return typeof(Kitty); 
      default: 
       throw new Exception(); 
     } 
    } 
} 

public enum CreatureType 
{ 
    Tiger, 
    Kitty, 
} 

public class CreatureModelBinder : DefaultModelBinder 
{ 
    protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType) 
    { 
     CreatureType creatureType = GetValue<CreatureType>(bindingContext, "Type"); 
     Type model = Creature.SelectFor(creatureType); 
     Creature instance = (Creature)base.CreateModel(controllerContext, bindingContext, model); 
     bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => instance, model); 
     return instance; 
    } 

    private T GetValue<T>(ModelBindingContext bindingContext, string key) 
    { 
     ValueProviderResult valueResult = bindingContext.ValueProvider.GetValue(key); // valueResult is null 
     bindingContext.ModelState.SetModelValue(key, valueResult); 
     return (T)valueResult.ConvertTo(typeof(T)); // NullReferenceException 
    } 
} 

Mon script:

(function() { 
    'use strict'; 

    angular.module('app', []).controller('CritterController', ['$scope', '$http', function ($scope, $http) { 

     $http.post('/a/admin/get-menagerie', { }).success(function (data) { 
      $scope.menagerie = data.menagerie; 
     }); 

     $scope.makeNoise = function() { 
      $http.post('/a/admin/make-noise', { menagerie: $scope.menagerie }).success(function (data) { 
       $scope.message = data.message; 
      }); 
     } 
    }]); 

})(); 

choses que j'ai essayé

J'ai essayé juste en utilisant une chaîne pour indiquer le nom de la classe, comme dans this answer et this one. Toutefois, l'appel à bindingContext.ValueProvider.GetValue(key) renvoie toujours null, ce qui entraîne un NullReferenceException.

J'ai également vérifié que le modèle est correctement lié. Quand je change mon CreatureModelBinder à ce qui suit, tout est bien mappé. Mais chaque créature perd son type hérité et devient un Creature.

public class CreatureModelBinder : DefaultModelBinder 
{ 
    protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType) 
    { 
     return base.CreateModel(controllerContext, bindingContext, modelType); 
    } 
} // MakeNoise returns: "Shere Khan says nothing. Mr. Boots says nothing. Dr. Evil says nothing." 

Répondre

0

La solution que j'espérais existait:

Lors de la publication des données JSON, il semble que toutes les clés ont le nom du modèle préfixé. Ceci peut être résolu en changeant simplement la première ligne de CreateModel à:

CreatureType creatureType = GetValue<CreatureType>(bindingContext, bindingContext.ModelName + ".Type"); 

précédente Réponse:

Les données mes AngularJS messages de fonction est marquée par Chrome comme « Demande Payload », qui ne peut pas (comme autant que je peux dire) être accessible en CreateModel. Lorsque j'ai implémenté les solutions @ DarinDimitrov line-for-line, les données ont été enregistrées en tant que "Données de formulaire", qui était disponible en CreateModel. J'ai trouvé un peu plus d'informations sur la différence entre les types de données here, bien que AngularJS ne semble pas capable d'envoyer des données avec un type de contenu autre que application/json sans quelques acrobaties fantaisistes.

Je ne trouve cependant que je peux accéder à une instance de mon objet (et de créer une nouvelle instance avec un autre type) dans la fonction BindModel comme suit:

public class CreatureModelBinder : DefaultModelBinder 
{ 
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) 
    { 
     Creature instance = (Creature)base.BindModel(controllerContext, bindingContext); 
     Creature newInstance = (Creature)Activator.CreateInstance(Creature.SelectFor((instance).Type)); 
     newInstance.Name = instance.Name; 

     return newInstance; 
    } 
} // MakeNoise() returns: "Shere Khan says ROAR. Mr. Boots says meow. Dr. Evil says meow." 

Le plus grand inconvénient que je peux voir est que chaque propriété dans l'objet doit être mappée manuellement à la nouvelle instance, ce qui peut devenir lourd et produire des bogues difficiles à suivre.

C'est ce que j'utilise pour le moment, même si je suis ouvert aux suggestions si quelqu'un peut fournir une solution plus élégante.

EDIT

J'ai trouvé que lors de la publication application/JSON, les données du modèle sont accessibles en utilisant ceci: