C# XmlSerializerの使い方

XMLでリクエスト/レスポンスするAPIへアクセスする機会がありましたので、XmlSerializerの使い方を備忘録にしておきます。

XMLをシリアライズ/デシリアライズする

まずはC#オブジェクト(ここではBook)とXML形式の文字列でシリアライズ/デシリアライズさせる方法です。

Bookクラス用のXmlSerializer(System.Xml.Serialization名前空間)を定義して、Serializeメソッドでシリアライズできます。

  • publicのフィールドまたはプロパティがXML要素になりますが、XmlRootAttribute、XmlElementAttributeで要素名を指定しています
    • これらの属性が無い場合は、フィールド名・プロパティ名がそのまま要素名になります
  • XMLの繰り返し構造を定義することもでき、その場合はXmlArrayAttributeで親要素名、XmlArrayItemAttributeで子要素名を指定できます
  • クラスは入れ子構造にできます(Book内にListを定義しています)

デシリアライズは、同様にBookクラス用のXmlSerializerを定義し、Deserializeメソッドでデシリアライズします。

using System;
using System.Collections.Generic;
using System.IO;
using System.Xml.Serialization;

namespace ConsoleApplication
{
    [XmlRoot("book")]
    public class Book
    {
        [XmlElement("title")]
        public string Title { get; set; }

        [XmlElement("publised")]
        public DateTime Published { get; set; }

        [XmlArray("authors")]
        [XmlArrayItem("author")]
        public List<Author> Authors { get; set; }
    }

    public class Author
    {
        [XmlElement("authorName")]
        public string AuthorName { get; set; }
    }

    class Program
    {
        public static void Main(string[] args)
        {
            var book = new Book
            {
                Title =
                    "The Art of Readable Code: Simple and Practical Techniques for Writing Better Code (Theory in Practice)",
                Published = new DateTime(2011, 11, 3),
                Authors = new List<Author>
                {
                    new Author {AuthorName = "Dustin Boswell"},
                    new Author {AuthorName = "Trevor Foucher"}
                }
            };

            // シリアライズ
            var writer = new StringWriter(); // 出力先のWriterを定義
            var serializer = new XmlSerializer(typeof(Book)); // Bookクラスのシリアライザを定義
            serializer.Serialize(writer, book);

            var xml = writer.ToString(); 
            Console.WriteLine(xml);

            // デシリアライズ
            var deserializedBook = (Book)serializer.Deserialize(new StringReader(xml));

            Console.WriteLine($"Title: {deserializedBook.Title}");
            Console.WriteLine($"Published: {deserializedBook.Published}");
            foreach (var author in deserializedBook.Authors)
            {
                Console.WriteLine($"AuthorName: {author.AuthorName}");
            }
        }
    }
}

実行すると、以下のように出力されます。

  • ルート要素にデフォルトで名前空間(xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://ww w.w3.org/2001/XMLSchema")が設定されます
<?xml version="1.0" encoding="utf-16"?>
<book xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://ww
w.w3.org/2001/XMLSchema">
  <title>The Art of Readable Code: Simple and Practical Techniques for Writing B
etter Code (Theory in Practice)</title>
  <publised>2011-11-03T00:00:00</publised>
  <authors>
    <author>
      <authorName>Dustin Boswell</authorName>
    </author>
    <author>
      <authorName>Trevor Foucher</authorName>
    </author>
  </authors>
</book>
Title: The Art of Readable Code: Simple and Practical Techniques for Writing Bet
ter Code (Theory in Practice)
Published: 2011/11/03 0:00:00
AuthorName: Dustin Boswell
AuthorName: Trevor Foucher

大まかな使い方は以上で、以下はtipsです。

デフォルトの名前空間を削除する

Serializeメソッドでは、XmlSerializerNamespacesオブジェクトを引数にとることができ、任意のXML名前空間を定義できます。 先程のデフォルトの名前空間が不要の場合、空のXmlSerializerNamespacesを渡すことで削除できます。

var namespaces = new XmlSerializerNamespaces();
namespaces.Add(string.Empty, string.Empty);
serializer.Serialize(writer, book, namespaces);

book要素から名前空間が削除されていることが確認できます。

<?xml version="1.0" encoding="utf-16"?>
<book>
  <title>The Art of Readable Code: Simple and Practical Techniques for Writing B
etter Code (Theory in Practice)</title>
・・・

encoding="utf-8"にする

出力のXML宣言を見ますと、encoding="utf-16"となっています。 StringWriterのEncodingプロパティの値がEncoding.UTF16になっているためですが、このプロパティはsetアクセサを持たないため、直接書き換えることができません。

そのため、StringWriterを継承したクラスを新たに作り、Encodingプロパティをオーバライドすることで、Encoding.UTF8に変えることができます。

private sealed class Utf8StringWriter : StringWriter
{
    public override Encoding Encoding => Encoding.UTF8;
}

var writer = new Utf8StringWriter();
serializer.Serialize(writer, book, namespaces);

シリアライズされたXMLを見ると、encoding="utf-8"と出力されることが確認できます。

<?xml version="1.0" encoding="utf-8"?>
<book>
  <title>The Art of Readable Code: Simple and Practical Techniques for Writing B
etter Code (Theory in Practice)</title>
・・・

C# HttpClientでBasic認証する

今回はHttpClientでBasic認証を行います。

Basic認証するユーザ名とパスワードをコロン":“でつないでBase64形式にエンコーディングして、その値をAuthorizationヘッダに詰めればOKです。

using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;

// Basic認証するユーザ名とパスワード
var userName = "ohke";
var userPassword = "ohkepassword";

// リクエストの生成
var request = new HttpRequestMessage
{
    Method = HttpMethod.Post,
    RequestUri = new Uri("http://ohke.hateblo.jp/")
};

// Basic認証ヘッダを付与する
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue(
    "Basic",
    Convert.ToBase64String(Encoding.ASCII.GetBytes(string.Format("{0}:{1}", userName, userPassword))));

// リクエストの送信
using (var httpClient = new HttpClient())
{
    var response = await httpClient.SendAsync(request);
}

試しにこのブログにリクエストを飛ばして、wiresharkでリクエストパケットをキャプチャしてみますと、Authorizationに設定したユーザ名・パスワードが送られていることが確認できます。

f:id:ohke:20170520155308p:plain:w500

C# HttpClientでCookieを設定する

先週に引き続いて、HttpClientネタです。

WebサイトにHttpClientを使ってフォーム認証を行い、Cookieをリクエストに詰めて取り回す必要がありましたので、備忘録としておきます。

  1. フォームでユーザ名・パスワードの情報を詰めたリクエストをPOSTして、レスポンスヘッダのSet-Cookieの値を取得する
  2. 認証Cookieを使って更にPOSTリクエストする
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;

// 1. Cookieの取得
var client = new HttpClient();
var content = new FormUrlEncodedContent(new Dictionary<string, string>
{
    { "username", "ohke" },
    { "password", "ohkepassword" }
});
var loginResponse = await httpClient.PostAsync("https://hogehoge.xxx.com/login");
// HeadersプロパティからSet-Cookieの値を取得する
var cookie = loginResponse.Headers.GetValues("Set-Cookie").First();

// 2. Cookieの設定
content = new FormUrlEncodedContent(new Dictionary<string, string>
{
    { "key", "value" }
});
// リクエストのCookieヘッダに1.で取得した値を設定する
content.Headers.Add("Cookie", cookie);
var response = await httpClient.PostAsync("https://hogehoge.xxx.com/other", content);