モデルと一致(またはバインド)したいJSONを送信するWebAPIのメソッドを呼び出しています。
コントローラーには、次のようなメソッドがあります。
public Result Post([ModelBinder(typeof(CustomModelBinder))]MyClass model);
'MyClass'、これはパラメータとして抽象クラスです。渡されたJSONのタイプに応じて、正しい継承クラスがインスタンス化されるようにしたいと思います。
それを達成するために、カスタムバインダーを実装しようとしています。問題は(それが非常に基本的なものかどうかはわかりませんが、何も見つかりません)リクエストに含まれる生のJson(またはもっと良いのは、何らかのシリアル化)を取得する方法がわかりません。
そうですか:
ただし、すべてのメソッドは非同期として公開されます。生成モデルをコントローラーメソッドに渡すのにこれが誰に当てはまるかわかりません...
どうもありがとう!
カスタムモデルバインダーは必要ありません。また、リクエストパイプラインをいじる必要もありません。
この他のSOをご覧ください。 JSON.NETでカスタムJsonConverterを実装して、基本クラスオブジェクトのリストを逆シリアル化する方法 。
私はこれを、同じ問題に対する私自身の解決策の基礎として使用しました。
その中で参照される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
よりもこの方法を好む理由私は、JotaBeが支持するTypeNameHandling.Auto/All
を使用することが常に理想的なソリューションであるとは考えていません。この場合はそうかもしれません-しかし、個人的に私はそれをしない:
Json.Net TypeNameHandling.Auto
またはAll
が使用されると、WebサーバーはMyNamespace.MyType, MyAssemblyName
形式で型名の送信を開始します。
これはセキュリティ上の懸念だと思うとコメントで述べました。これについては、Microsoftから読んだいくつかのドキュメントで言及されています。それはもう言及されていないようですが、それは妥当な懸念だとまだ感じています。名前空間で修飾された型名とアセンブリ名を外部に公開したくないever 。攻撃対象が増えています。それで、はい、私のAPIタイプのObject
プロパティ/パラメーターを持つことはできませんが、私のサイトの残りの部分は完全に穴がないと言えますか?将来のエンドポイントが型名を悪用する機能を公開しないと言うのは誰ですか?簡単だからといって、なぜそのチャンスをつかむのでしょうか?
また、「適切な」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リリース以降で機能します 。
[〜#〜] note [〜#〜]
属性で何かを装飾したり、他のカスタマイズを行う必要はありません。 Web APIコードを変更しなくても機能します。
重要な注意事項
$ typeはJSONシリアル化オブジェクトの最初のプロパティである必要があります 。そうでない場合、無視されます。
カスタムJsonConverter/JsonConverterAttributeとの比較
私はネイティブソリューションを比較しています この答えと 。
JsonConverter
/JsonConverterAttribute
を実装するには:
JsonConverter
とカスタムJsonConverterAttribute
を実装する必要がありますJsonConverter
の実装を実装または変更する必要があります回答の著者には、セキュリティに関するコメントがあります。何か間違ったことをしない限り(Object
のようにパラメーターに対して汎用的な型を受け入れるなど)、間違った型のインスタンスを取得するリスクはありません。JSON.NETネイティブソリューションは、パラメーターの型のオブジェクトのみをインスタンス化します、またはそれから派生したタイプ(そうでない場合は、null
を取得します)。
そして、これらはJSON.NETネイティブソリューションの利点です。
TypeNameHandling
を一度設定するだけです)(1):同じ基本型から継承しないパラメーター値を受け取りたい場合、これは機能しませんが、そうすることには意味がありません
だから私は短所を見つけることができず、JSON.NETソリューションで多くの利点を見つけます。
カスタムJsonConverter/JsonConverterAttributeを使用する理由
これは、カスタマイズを可能にする優れた実用的なソリューションであり、特定のケースに合わせて変更または拡張することができます。
型名のカスタマイズ、使用可能なプロパティ名に基づいたパラメーターの型の推測など、ネイティブソリューションではできないことをしたい場合は、独自のケースに合わせてこのソリューションを使用してください。もう1つはカスタマイズできず、ニーズに合わせて機能しません。
通常は非同期メソッドを呼び出すことができ、メソッドが戻るまで実行は一時停止され、標準的な方法でモデルを返すことができます。次のような電話をかけるだけです。
string jsonContent = await actionContext.Request.Content.ReadAsStringAsync();
生のJSONが得られます。
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 そのフィールドプロパティのカスタムデシリアライザーを見せてくれた。