63

モデルと照合(またはバインド)したいJSONを送信するWebAPIのメソッドを呼び出しています。

コントローラには、次のようなメソッドがあります。

public Result Post([ModelBinder(typeof(CustomModelBinder))]MyClass model);

パラメータとして指定される「MyClass」は抽象クラスです。渡されたjsonのタイプに応じて、正しい継承クラスがインスタンス化されるようにしたいと思います。

それを達成するために、私はカスタムバインダーを実装しようとしています。問題は、(非常に基本的なものかどうかはわかりませんが、何も見つかりません)リクエストに含まれる生のJSON(またはより良いのはある種のシリアル化)を取得する方法がわからないことです。

そうか:

  • actionContext.Request.Content

ただし、すべてのメソッドは非同期として公開されます。生成モデルをコントローラーメソッドに渡すことで、これが誰に適合するのかわかりません...

4

4 に答える 4

94

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

この他のSOを見てください:JSON.NETにカスタムJsonConverterを実装して、基本クラスオブジェクトのリストを逆シリアル化する方法は?

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

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

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/AllJotaBeが支持するものを使用することが常に理想的な解決策であるとは限らないと私は信じています。この場合はそうかもしれませんが、個人的には、次の場合を除いては行いません。

  • 私のAPIは私または私のチームによってのみ使用されます
  • デュアルXML互換エンドポイントを使用する必要はありません

Json.NetTypeNameHandling.AutoまたはAllを使用すると、Webサーバーはタイプ名をの形式で送信し始めますMyNamespace.MyType, MyAssemblyName

私はコメントで、これはセキュリティ上の懸念だと思います。これについては、Microsoftから読んだいくつかのドキュメントで言及されています。それはもう言及されていないようですが、それでも私はそれが正当な懸念であると感じています。名前空間で修飾された型名とアセンブリ名を外部に公開したくありませんそれは私の攻撃対象領域を増やしています。だから、はい、私はObject自分のAPIタイプのプロパティ/パラメーターを持つことはできませんが、私のサイトの残りの部分は完全に穴がないと誰が言いますか?将来のエンドポイントがタイプ名を悪用する機能を公開しないと誰が言いますか?簡単だからといって、なぜそのチャンスをつかむのですか?

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

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

于 2012-09-28T14:03:04.037 に答える
49

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

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

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

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

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

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

MyNamespace.MyTypeパラメータは、クラスのオブジェクトに逆シリアル化されます。

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

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

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

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

ノート

アトリビュートで何かを飾ったり、その他のカスタマイズを行う必要はありません。WebAPIコードを変更しなくても機能します。

重要な注意点

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

カスタムJsonConverter/JsonConverterAttributeとの比較

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

JsonConverter/を実装するにはJsonConverterAttribute

  • JsonConverterカスタム、およびカスタムを実装する必要がありますJsonConverterAttribute
  • パラメータをマークするには属性を使用する必要があります
  • パラメータに期待される可能なタイプを事前に知っておく必要があります
  • JsonConverterタイプやプロパティが変更されるたびに、実装するか、実装を変更する必要があります
  • 予想されるプロパティ名を示すために、マジックストリングのコードの臭いがあります
  • どのタイプでも使用できる一般的なものを実装していません
  • あなたは車輪の再発明をしている

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

そして、これらはJSON.NETネイティブソリューションの利点です。

  • 何も実装する必要はありません(TypeNameHandlingアプリで一度だけ構成する必要があります)
  • アクションパラメータで属性を使用する必要はありません
  • 可能なパラメーター型を事前に知る必要はありません。基本型を知って、それをパラメーターで指定する必要があります(ポリモーフィズムをより明確にするために、抽象型である可能性があります)
  • ソリューションはほとんどの場合(1)何も変更せずに機能します
  • このソリューションは広くテストされ、最適化されています
  • マジックストリングは必要ありません
  • 実装は汎用であり、任意の派生型を受け入れます

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

したがって、私は不利な点を見つけることができず、JSON.NETソリューションで多くの利点を見つけることができます。

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

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

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

于 2014-06-02T16:23:06.177 に答える
4

通常、非同期メソッドを呼び出すことができます。メソッドが返されるまで実行が一時停止され、標準的な方法でモデルを返すことができます。次のように電話をかけてください。

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

それはあなたに生のJSONを与えるでしょう。

于 2012-09-28T12:39:45.947 に答える
3

TypeNameHandling.Autoを使用したいが、セキュリティに関心がある場合、またはAPIコンシューマーがそのレベルの舞台裏の知識を必要としない場合は、$typeを処理して自分自身を逆シリアル化します。

public class InheritanceSerializationBinder : DefaultSerializationBinder
{
    public override Type BindToType(string assemblyName, string typeName)
    {
        switch (typeName)
        {
            case "parent[]": return typeof(Class1[]);
            case "parent": return typeof(Class1);
            case "child[]": return typeof(Class2[]);
            case "child": return typeof(Class2);
            default: return base.BindToType(assemblyName, typeName);
        }
    }
}

次に、これをglobal.asax.Application__Startに接続します

var config = GlobalConfiguration.Configuration;
        config.Formatters.JsonFormatter.SerializerSettings = new JsonSerializerSettings { Binder = new InheritanceSerializationBinder() };

最後に、実際のクラスを構成してオブジェクトを機能させることができなかったため、さまざまなタイプのオブジェクトを含むプロパティでラッパークラスと[JsonProperty(TypeNameHandling = TypeNameHandling.Auto)]を使用しました。

このアプローチにより、消費者は必要な情報を要求に含めることができ、許容値の文書化はプラットフォームに依存せず、変更が容易で、理解しやすいものになります。独自のコンバースターを作成する必要はありません。

そのフィールドプロパティのカスタムデシリアライザーを表示してくれたhttps://mallibone.com/post/serialize-object-inheritance-with-json.netのクレジット。

于 2018-03-08T14:00:13.143 に答える