この記事では、ASP.NET Web API の JSON フォーマッタと XML フォーマッタについて説明します。
ASP.NET Web API では、 メディア型フォーマッタ は次のことが可能なオブジェクトです。
- HTTP メッセージ本文から CLR オブジェクトを読み取る
- HTTP メッセージ本文に CLR オブジェクトを書き込む
Web API は、JSON と XML の両方にメディア型フォーマッタを提供します。 フレームワークは、既定でこれらのフォーマッタをパイプラインに挿入します。 クライアントは、HTTP 要求の Accept ヘッダーで JSON または XML を要求できます。
内容
JSON メディアタイプ フォーマッタ
JSON 書式設定は、 JsonMediaTypeFormatter クラスによって提供されます。 既定では、 JsonMediaTypeFormatter は Json.NET ライブラリを使用してシリアル化を実行します。 Json.NET は、サードパーティ製のオープン ソース プロジェクトです。
必要に応じて、Json.NET の代わりに DataContractJsonSerializer を使用するように JsonMediaTypeFormatter クラスを構成できます。 これを行うには、 UseDataContractJsonSerializer プロパティを true に設定します。
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.UseDataContractJsonSerializer = true;
JSON シリアル化
このセクションでは、既定の Json.NET シリアライザーを使用した JSON フォーマッタの特定の動作について説明します。 これは、Json.NET ライブラリの包括的なドキュメントではありません。詳細については、 Json.NET ドキュメントを参照してください。
シリアル化される内容
既定では、すべてのパブリック プロパティとフィールドがシリアル化された JSON に含まれます。 プロパティまたはフィールドを省略するには、 JsonIgnore 属性で装飾します。
public class Product
{
public string Name { get; set; }
public decimal Price { get; set; }
[JsonIgnore]
public int ProductCode { get; set; } // omitted
}
"オプトイン" アプローチを使用する場合は、 DataContract 属性を使用してクラスを装飾します。 この属性が存在する場合、 DataMember がない限り、メンバーは無視されます。 DataMember を使用してプライベート メンバーをシリアル化することもできます。
[DataContract]
public class Product
{
[DataMember]
public string Name { get; set; }
[DataMember]
public decimal Price { get; set; }
public int ProductCode { get; set; } // omitted by default
}
読み取り専用プロパティ
既定では、読み取り専用プロパティがシリアル化されます。
日付
既定では、Json.NET は ISO 8601 形式で日付を書き込みます。 UTC (世界協定時刻) の日付は、"Z" サフィックスで書き込まれます。 現地時刻の日付には、タイム ゾーン オフセットが含まれます。 例えば次が挙げられます。
2012-07-27T18:51:45.53403Z // UTC
2012-07-27T11:51:45.53403-07:00 // Local
既定では、Json.NET はタイム ゾーンを保持します。 これをオーバーライドするには、DateTimeZoneHandling プロパティを設定します。
// Convert all dates to UTC
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.DateTimeZoneHandling = Newtonsoft.Json.DateTimeZoneHandling.Utc;
ISO 8601 ではなく Microsoft JSON 日付形式 ("\/Date(ticks)\/") を使用する場合は、シリアライザー設定で DateFormatHandling プロパティを設定します。
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.DateFormatHandling
= Newtonsoft.Json.DateFormatHandling.MicrosoftDateFormat;
インデント
インデントされた JSON を書き込むには、 書式設定設定をFormatting.Indented に設定します。
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.Formatting = Newtonsoft.Json.Formatting.Indented;
キャメル ケース
データ モデルを変更せずにキャメル ケースで JSON プロパティ名を書き込むには、シリアライザーで CamelCasePropertyNamesContractResolver を設定します。
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
匿名オブジェクトと弱い型付けオブジェクト
アクション メソッドは、匿名オブジェクトを返して JSON にシリアル化できます。 例えば次が挙げられます。
public object Get()
{
return new {
Name = "Alice",
Age = 23,
Pets = new List<string> { "Fido", "Polly", "Spot" }
};
}
応答メッセージの本文には、次の JSON が含まれます。
{"Name":"Alice","Age":23,"Pets":["Fido","Polly","Spot"]}
Web API がクライアントから緩やかに構造化された JSON オブジェクトを受け取る場合は、要求本文を Newtonsoft.Json.Linq.JObject 型に逆シリアル化できます。
public void Post(JObject person)
{
string name = person["Name"].ToString();
int age = person["Age"].ToObject<int>();
}
ただし、通常は、厳密に型指定されたデータ オブジェクトを使用することをお勧めします。 その後、データを自分で解析する必要がなく、モデル検証の利点が得られます。
XML シリアライザーは、匿名型または JObject インスタンスをサポートしていません。 JSON データにこれらの機能を使用する場合は、この記事で後述するように、パイプラインから XML フォーマッタを削除する必要があります。
XMLメディアタイプフォーマッタ
XML 書式設定は、 XmlMediaTypeFormatter クラスによって提供されます。 既定では、 XmlMediaTypeFormatter は DataContractSerializer クラスを使用してシリアル化を実行します。
必要に応じて、DataContractSerializer の代わりに XmlSerializer を使用するように XmlMediaTypeFormatter を構成できます。 これを行うには、 UseXmlSerializer プロパティを true に設定します。
var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
xml.UseXmlSerializer = true;
XmlSerializer クラスは、DataContractSerializer よりも狭い型セットをサポートしますが、結果の XML をより細かく制御できます。 既存の XML スキーマと一致させる必要がある場合は、 XmlSerializer の使用を検討してください。
XML シリアル化
このセクションでは、既定の DataContractSerializer を使用した XML フォーマッタの特定の動作について説明します。
既定では、DataContractSerializer は次のように動作します。
- すべてのパブリック読み取り/書き込みプロパティとフィールドがシリアル化されます。 プロパティまたはフィールドを省略するには、 IgnoreDataMember 属性で装飾します。
- プライベート メンバーと保護されたメンバーはシリアル化されません。
- 読み取り専用プロパティはシリアル化されません。 (ただし、読み取り専用コレクション プロパティの内容はシリアル化されます)。
- クラス名とメンバー名は、クラス宣言に表示されるとおりに XML で記述されます。
- 既定の XML 名前空間が使用されます。
シリアル化をより詳細に制御する必要がある場合は、 DataContract 属性を使用してクラスを装飾できます。 この属性が存在する場合、クラスは次のようにシリアル化されます。
- "オプトイン" アプローチ: プロパティとフィールドは、既定ではシリアル化されません。 プロパティまたはフィールドをシリアル化するには、 DataMember 属性で装飾します。
- プライベート メンバーまたは保護メンバーをシリアル化するには、 DataMember 属性で装飾します。
- 読み取り専用プロパティはシリアル化されません。
- XML でのクラス名の表示方法を変更するには、DataContract 属性で Name パラメーターを設定します。
- XML でのメンバー名の表示方法を変更するには、DataMember 属性で Name パラメーターを設定します。
- XML 名前空間を変更するには、DataContract クラスで Namespace パラメーターを設定します。
読み取り専用プロパティ
読み取り専用プロパティはシリアル化されません。 読み取り専用プロパティにバッキング プライベート フィールドがある場合は、プライベート フィールドを DataMember 属性でマークできます。 この方法では、クラスの DataContract 属性が必要です。
[DataContract]
public class Product
{
[DataMember]
private int pcode; // serialized
// Not serialized (read-only)
public int ProductCode { get { return pcode; } }
}
日付
日付は ISO 8601 形式で記述されます。 たとえば、"2012-05-23T20:21:37.9116538Z" などです。
インデント
インデントされた XML を書き込むには、 Indent プロパティを true に設定します。
var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
xml.Indent = true;
XML シリアライザーの型ごとの設定
CLR 型ごとに異なる XML シリアライザーを設定できます。 たとえば、下位互換性のために XmlSerializer を必要とする特定のデータ オブジェクトがあるとします。 このオブジェクトには XmlSerializer を使用し、他の型には DataContractSerializer を引き続き使用できます。
特定の型の XML シリアライザーを設定するには、 SetSerializer を呼び出します。
var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
// Use XmlSerializer for instances of type "Product":
xml.SetSerializer<Product>(new XmlSerializer(typeof(Product)));
XmlSerializer または XmlObjectSerializer から派生する任意のオブジェクトを指定できます。
JSON または XML フォーマッタの削除
JSON フォーマッタまたは XML フォーマッタを使用しない場合は、フォーマッタの一覧から削除できます。 これを行う主な理由は次のとおりです。
- Web API の応答を特定のメディアの種類に制限する。 たとえば、JSON 応答のみをサポートし、XML フォーマッタを削除することができます。
- 既定のフォーマッタをカスタム フォーマッタに置き換えるには たとえば、JSON フォーマッタを JSON フォーマッタの独自のカスタム実装に置き換えることができます。
次のコードは、既定のフォーマッタを削除する方法を示しています。 Global.asax で定義 されているApplication_Start メソッドからこれを呼び出します。
void ConfigureApi(HttpConfiguration config)
{
// Remove the JSON formatter
config.Formatters.Remove(config.Formatters.JsonFormatter);
// or
// Remove the XML formatter
config.Formatters.Remove(config.Formatters.XmlFormatter);
}
循環オブジェクト参照の処理
既定では、JSON フォーマッタと XML フォーマッタはすべてのオブジェクトを値として書き込みます。 2 つのプロパティが同じオブジェクトを参照している場合、または同じオブジェクトがコレクションに 2 回表示される場合、フォーマッタはオブジェクトを 2 回シリアル化します。 これは、オブジェクト グラフにサイクルが含まれている場合に特に問題になります。これは、シリアライザーがグラフ内のループを検出したときに例外をスローするためです。
次のオブジェクト モデルとコントローラーについて考えてみましょう。
public class Employee
{
public string Name { get; set; }
public Department Department { get; set; }
}
public class Department
{
public string Name { get; set; }
public Employee Manager { get; set; }
}
public class DepartmentsController : ApiController
{
public Department Get(int id)
{
Department sales = new Department() { Name = "Sales" };
Employee alice = new Employee() { Name = "Alice", Department = sales };
sales.Manager = alice;
return sales;
}
}
このアクションを呼び出すと、フォーマッタによって例外がスローされ、その結果としてクライアントには状態コード 500(内部サーバーエラー)の応答が返されます。
JSON でオブジェクト参照を保持するには、Global.asax ファイル 内のメソッドApplication_Start 次のコードを追加します。
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.PreserveReferencesHandling =
Newtonsoft.Json.PreserveReferencesHandling.All;
これで、コントローラー アクションは次のような JSON を返します。
{"$id":"1","Name":"Sales","Manager":{"$id":"2","Name":"Alice","Department":{"$ref":"1"}}}
シリアライザーが両方のオブジェクトに "$id" プロパティを追加していることに注意してください。 また、Employee.Department プロパティによってループが作成されることを検出し、値をオブジェクト参照 {"$ref":"1"} に置き換えます。
注
オブジェクト参照は JSON では標準ではありません。 この機能を使用する前に、クライアントが結果を解析できるかどうかを検討してください。 グラフからサイクルを削除するだけの方が良い場合があります。 たとえば、この例では、従業員から部署へのリンクは実際には必要ありません。
XML でオブジェクト参照を保持するには、2 つのオプションがあります。 より簡単なオプションは、モデル クラスに [DataContract(IsReference=true)] を追加することです。
IsReference パラメーターは、オブジェクト参照を有効にします。
DataContract ではシリアル化オプトインが行われるので、プロパティに DataMember 属性を追加する必要もあります。
[DataContract(IsReference=true)]
public class Department
{
[DataMember]
public string Name { get; set; }
[DataMember]
public Employee Manager { get; set; }
}
フォーマッタは、次のような XML を生成します。
<Department xmlns:i="http://www.w3.org/2001/XMLSchema-instance" z:Id="i1"
xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/"
xmlns="http://schemas.datacontract.org/2004/07/Models">
<Manager>
<Department z:Ref="i1" />
<Name>Alice</Name>
</Manager>
<Name>Sales</Name>
</Department>
モデル クラスの属性を回避する場合は、別のオプションがあります。新しい型固有 の DataContractSerializer インスタンスを作成し、コンストラクターで preserveObjectReferences を true に設定します。 次に、このインスタンスを XML メディア型フォーマッタの型ごとのシリアライザーとして設定します。 次のコードは、これを行う方法を示しています。
var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
var dcs = new DataContractSerializer(typeof(Department), null, int.MaxValue,
false, /* preserveObjectReferences: */ true, null);
xml.SetSerializer<Department>(dcs);
オブジェクトのシリアル化のテスト
Web API を設計するときは、データ オブジェクトのシリアル化方法をテストすると便利です。 これは、コントローラーを作成したり、コントローラー アクションを呼び出したりせずに行うことができます。
string Serialize<T>(MediaTypeFormatter formatter, T value)
{
// Create a dummy HTTP Content.
Stream stream = new MemoryStream();
var content = new StreamContent(stream);
/// Serialize the object.
formatter.WriteToStreamAsync(typeof(T), value, stream, content, null).Wait();
// Read the serialized string.
stream.Position = 0;
return content.ReadAsStringAsync().Result;
}
T Deserialize<T>(MediaTypeFormatter formatter, string str) where T : class
{
// Write the serialized string to a memory stream.
Stream stream = new MemoryStream();
StreamWriter writer = new StreamWriter(stream);
writer.Write(str);
writer.Flush();
stream.Position = 0;
// Deserialize to an object of type T
return formatter.ReadFromStreamAsync(typeof(T), stream, null, null).Result as T;
}
// Example of use
void TestSerialization()
{
var value = new Person() { Name = "Alice", Age = 23 };
var xml = new XmlMediaTypeFormatter();
string str = Serialize(xml, value);
var json = new JsonMediaTypeFormatter();
str = Serialize(json, value);
// Round trip
Person person2 = Deserialize<Person>(json, str);
}