Asp.Net Web API で JSON を派生型に逆シリアル化する

カスタム モデル バインダーは必要ありません。また、リクエスト パイプラインをいじる必要もありません。

この別の SO を見てみましょう:How to implement custom JsonConverter in JSON.NET to deserialize a List of base class objects?.

これを、同じ問題に対する独自の解決策の基礎として使用しました。

JsonCreationConverter<T> から始めます その SO で参照されている (応答の型のシリアル化に関する問題を修正するためにわずかに変更されています):

public abstract class JsonCreationConverter<T> : JsonConverter
{
    /// <summary>
    /// this is very important, otherwise serialization breaks!
    /// </summary>
    public override bool CanWrite
    {
        get
        {
            return false;
        }
    }
    /// <summary> 
    /// Create an instance of objectType, based properties in the JSON object 
    /// </summary> 
    /// <param name="objectType">type of object expected</param> 
    /// <param name="jObject">contents of JSON object that will be 
    /// deserialized</param> 
    /// <returns></returns> 
    protected abstract T Create(Type objectType, JObject jObject);

    public override bool CanConvert(Type objectType)
    {
        return typeof(T).IsAssignableFrom(objectType);
    }

    public override object ReadJson(JsonReader reader, Type objectType,
      object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return null;
        // Load JObject from stream 
        JObject jObject = JObject.Load(reader);

        // Create target object based on JObject 
        T target = Create(objectType, jObject);

        // Populate the object properties 
        serializer.Populate(jObject.CreateReader(), target);

        return target;
    }

    public override void WriteJson(JsonWriter writer, object value, 
      JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
} 

これで、型に JsonConverterAttribute で注釈を付けることができます 、Json.Net をカスタム コンバーターにポイント:

[JsonConverter(typeof(MyCustomConverter))]
public abstract class BaseClass{
  private class MyCustomConverter : JsonCreationConverter<BaseClass>
  {
     protected override BaseClass Create(Type objectType, 
       Newtonsoft.Json.Linq.JObject jObject)
     {
       //TODO: read the raw JSON object through jObject to identify the type
       //e.g. here I'm reading a 'typename' property:

       if("DerivedType".Equals(jObject.Value<string>("typename")))
       {
         return new DerivedClass();
       }
       return new DefaultClass();

       //now the base class' code will populate the returned object.
     }
  }
}

public class DerivedClass : BaseClass {
  public string DerivedProperty { get; set; }
}

public class DefaultClass : BaseClass {
  public string DefaultProperty { get; set; }
}

これで基本型をパラメーターとして使用できます:

public Result Post(BaseClass arg) {

}

投稿する場合:

{ typename: 'DerivedType', DerivedProperty: 'hello' }

次に arg DerivedClass のインスタンスになります 、ただし投稿した場合:

{ DefaultProperty: 'world' }

次に、 DefaultClass のインスタンスを取得します .

編集 - TypeNameHandling.Auto/All よりもこの方法を好む理由

TypeNameHandling.Auto/All を使用していると思います JotaBe の支持は必ずしも理想的なソリューションではありません。この場合はそうかもしれませんが、個人的には次の場合を除き、それを行いません:

  • 私の API は唯一 私または私のチームが使用する
  • 二重の XML 互換エンドポイントを持つことは気にしません

Json.Net TypeNameHandling.Auto の場合 または All が使用されると、Web サーバーは MyNamespace.MyType, MyAssemblyName の形式で型名を送信し始めます .

これはセキュリティ上の問題だと思うとコメントで述べました。これについては、マイクロソフトから読んだいくつかのドキュメントで言及されていました。もう言及されていないようですが、それでも有効な懸念事項だと思います。私はしたことがない 名前空間で修飾された型名とアセンブリ名を外部に公開したい。それは私の攻撃面を増やしています。だから、はい、私は Object を持つことはできません プロパティ/パラメータは API タイプですが、サイトの残りの部分には完全に穴がないと誰が言えますか?将来のエンドポイントでは、型名を悪用する機能が公開されないと誰が言えますか?簡単だからという理由だけで、なぜそのチャンスをつかむのですか?

また、「適切な」API を作成している場合、つまり、自分自身のためだけでなく、サード パーティが使用するためのものであり、Web API を使用している場合は、JSON/XML コンテンツ タイプを活用する可能性が最も高いでしょう。取り扱い(最低限)。 XML 形式と JSON 形式ですべての API タイプを異なる方法で参照する、使いやすいドキュメントをどこまで書こうとしているかを確認してください。

JSON.Net が型名を理解する方法をオーバーライドすることで、この 2 つを一致させることができます。型名がどちらか一方のほうが覚えやすいという理由ではなく、純粋に好みに基づいて、呼び出し元に対して XML と JSON のどちらかを選択できます。


自分で実装する必要はありません。 JSON.NET はそれをネイティブでサポートしています。

次のように、JSON フォーマッタに目的の TypeNameHandling オプションを指定する必要があります (global.asax 内)。 アプリケーション開始イベント):

JsonSerializerSettings serializerSettings = GlobalConfiguration.Configuration
   .Formatters.JsonFormatter.SerializerSettings;
serializerSettings.TypeNameHandling = TypeNameHandling.Auto;

Autoを指定した場合 、上記のサンプルのように、パラメーターは $type で指定された型に逆シリアル化されます オブジェクトのプロパティ。 $type の場合 プロパティが見つからない場合、パラメーターの型に逆シリアル化されます。したがって、派生型のパラメーターを渡すときにのみ型を指定する必要があります。 (これが最も柔軟なオプションです)。

たとえば、このパラメーターを Web API アクションに渡す場合:

var param = {
    $type: 'MyNamespace.MyType, MyAssemblyName', // .NET fully qualified name
    ... // object properties
};

パラメータは MyNamespace.MyType のオブジェクトにデシリアライズされます クラス。

これはサブプロパティに対しても機能します。つまり、内部プロパティが特定のタイプであることを指定する、このようなオブジェクトを持つことができます

var param = { 
   myTypedProperty: {
      $type: `...`
      ...
};

ここでは、TypeNameHandling.Auto の JSON.NET ドキュメントのサンプルを参照できます。

これは、少なくとも JSON.NET 4 リリース以降は機能します。

注意

属性で何かを装飾したり、その他のカスタマイズを行う必要はありません。 Web API コードを変更しなくても機能します。

重要な注意

$type は、JSON シリアル化されたオブジェクトの最初のプロパティである必要があります。そうでない場合は無視されます。

カスタム JsonConverter/JsonConverterAttribute との比較

ネイティブ ソリューションとこの回答を比較しています。

JsonConverter を実装するには /JsonConverterAttribute :

  • カスタム JsonConverter を実装する必要があります 、およびカスタム JsonConverterAttribute
  • 属性を使用してパラメータをマークする必要があります
  • パラメータに期待される型を事前に知っておく必要があります
  • JsonConverter を実装するか、実装を変更する必要があります タイプまたはプロパティが変更されたとき
  • 予想されるプロパティ名を示すために、魔法の文字列のコードの匂いがします
  • どの型でも使用できる汎用的なものを実装していません
  • 車輪の再発明

回答の著者には、セキュリティに関するコメントがあります。何か間違ったことをしない限り (Object のように、パラメーターにあまりにも一般的な型を受け入れるなど) ) 間違った型のインスタンスを取得するリスクはありません。JSON.NET ネイティブ ソリューションは、パラメーターの型またはそれから派生した型のオブジェクトのみをインスタンス化します (そうでない場合は、null を取得します)。 ).

JSON.NET ネイティブ ソリューションの利点は次のとおりです。

  • 何も実装する必要はありません (TypeNameHandling を構成するだけで済みます) アプリで一度)
  • アクション パラメータで属性を使用する必要はありません
  • 可能性のあるパラメータの型を事前に知る必要はありません。単純に基本型を把握し、それをパラメータで指定する必要があります (ポリモーフィズムをより明確にするために、抽象型にすることもできます)
  • この解決策はほとんどの場合に有効です(1) 何も変更せずに
  • このソリューションは広くテストされ、最適化されています
  • 魔法の糸は必要ありません
  • 実装はジェネリックであり、あらゆる派生型を受け入れます

(1):同じ基本型から継承されていないパラメーター値を受け取りたい場合、これは機能しませんが、そうすることには意味がありません

したがって、JSON.NET ソリューションには欠点がなく、多くの利点があります。

カスタム JsonConverter/JsonConverterAttribute を使用する理由

これは、カスタマイズが可能で、特定のケースに合わせて変更または拡張できる優れた実用的なソリューションです。

型名をカスタマイズしたり、使用可能なプロパティ名に基づいてパラメーターの型を推測したりするなど、ネイティブ ソリューションではできないことを行いたい場合は、独自のケースに合わせてこのソリューションを使用してください。もう 1 つはカスタマイズできず、必要に応じて機能しません。


通常どおり非同期メソッドを呼び出すことができます。実行はメソッドが戻るまで中断され、標準的な方法でモデルを返すことができます。次のように電話をかけるだけです:

string jsonContent = await actionContext.Request.Content.ReadAsStringAsync();

生の JSON が得られます。