2017-10-12 10 views
0

私はかなり複雑な親オブジェクトを操作するためにAngularJSを使用しています。子オブジェクトはサーバー側とはかなり異なる動作をする必要があります。非常に堅実に見えるthis answerに基づいて、以下のテストケースを作成しました。私が実行している問題は、CreateModel関数を入力するたびにbindingContext.ValueProvider.GetValue(key)への呼び出しがnullを返すことです。私はデバッガのすべての値をチェックしました。オブジェクトタイプがロードされているように見えますが、値はまだバインドされていません。ModelBindingContext.ValueProvider.GetValue(key)は常にnullを返します。

マイモデル:

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

マイスクリプト:私は

を試してみた

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

})(); 

物事私のように、単にクラス名を示すために、文字列を使用して試してみましたthis answerおよびthis oneである。ただし、bindingContext.ValueProvider.GetValue(key)の呼び出しはnullを返し、結果としてNullReferenceExceptionとなります。

また、モデルが正しくバインドされていることを確認しました。 CreatureModelBinderを次のように変更すると、すべてがうまくマッピングされます。しかし、各クリーチャーは継承されたタイプを失い、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." 

答えて

0

私は期待していたソリューションが存在していた:JSONデータを投稿するときは

を、すべてのキーが先頭に付加モデル名を持っていることが表示されます。前の回答

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

データ私のAngularJS機能ポストはできません(として「要求ペイロード」、とクロームによって標識されたこれは単純にCreateModelの最初の行を変更することで解決することができます私が知る限り)CreateModelにアクセスしてください。私が@ DarinDimitrovのソリューションを行ごとに実装すると、データはフォームデータとして投稿されました。これはCreateModelで利用できました。 AngularJSはapplication/json以外のcontent-typeでデータを送信できるようには見えませんでしたが、データ型の違いについてはもう少し詳しく知りました(here)。

次のように私は、しかし、BindModel機能で、私は私のオブジェクトのインスタンスにアクセスする(そして異なるタイプの新しいインスタンスを作成する)ことができることを知りました:

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." 

私が見ることができる最大の欠点をオブジェクトのすべてのプロパティを新しいインスタンスに手動でマップする必要があり、煩雑になり、追跡が困難なバグが発生する可能性があります。

これは私が今使っているものですが、誰かがより洗練されたソリューションを提供できるかどうかは私には分かりません。

:私は、アプリケーション/ JSONとして投稿するときに、モデルデータがこれを使用してアクセスすることができることを見出し

EDIT

関連する問題