技術日記
ASP.NET MVC 3で複数フォーマット対応APIを書くEdit

APIコールしたときの結果として、パラメータとして?format=xmlと書いたらXML形式で、?format=jsonと書いたらJSON形式で結果を返すようなやつを、どう書けばいいのか。

もちろんそれぞれ別々にアクションメソッドを用意するとか、ビューだけを差し替えるとかいろいろ手があるけれども、JSONに関してはJsonResultという便利なクラスが用意されているんで、それを使いたい。 元となる結果データは、シリアライズされること前提でクラスで定義することにした。

[sourcecode language="c#"]
public class ApiResult
{
  public string StringResult;
  public int IntResult;
  public bool BoolResult;
  public StringDictionary parameters = new StringDictionary();
}
[/sourcecode]
こんな感じの結果クラスを用意しておいて、
[sourcecode language="c#"]
public  ActionResult SomeApiCall(string format)
{
  ApiResult result = new ApiResult();
  // resultにいろいろセットする
  switch (format) {
    case "json":
      return Json[result];
    case "xml":
      var serializer = new XmlSerializer(typeof(ApiResult));
      var stream = new TextWriter(new StringBuilder());
      serializer.Serialize(stream, result);
      var xmlResult = new ContentResult();
      xmlResult.Content = stream.ToString();
      xmlResult.ContentType = "text/xml";
      return xmlResult;
  }
}
[/sourcecode]
こんな感じでいけるかと思っていたんだけど、JSONは思い通りにできたのに、XMLの方が思ったよりも大変だった。というのは、XmlSerializerがDictionaryのたぐいをシリアライズしてくれないから。

で、[PC][C]C#::DictionaryをXMLSerializerでシリアライズしたいんですが?を見つけて、これでDictionaryをXMLシリアライズしようと思ったんだけど、これでシリアライズすると、

[sourcecode language="xml"] keyname value [/sourcecode]

みたいな感じのXMLとしてシリアライズされる。JSON相当に読みやすい表現の

[sourcecode language="xml"] value [/sourcecode]

になってくれないかなー。というわけで試しに作ってみた。

[sourcecode language="c#"]
    public class SerializableDictionary : Dictionary, IXmlSerializable
    {
        public System.Xml.Schema.XmlSchema GetSchema()
        {
            return null;
        }

    public void ReadXml(System.Xml.XmlReader reader)
    {
        string key = null;
        object value = null;
        while (reader.Read())
        {
            switch (reader.NodeType)
            {

                case System.Xml.XmlNodeType.Element:
                    key = reader.Name;
                    break;
                case System.Xml.XmlNodeType.Text:
                    value = reader.Value;
                    break;
                case System.Xml.XmlNodeType.EndElement:
                    if (!string.IsNullOrEmpty(key))
                    {
                        this[key] = value;
                    }
                    else
                    {
                        reader.Read();
                        return;
                    }
                    key = null;
                    value = null;
                    break;
            }
        }
    }

    public void WriteXml(System.Xml.XmlWriter writer)
    {
        foreach (var key in Keys)
        {
            writer.WriteStartElement(key.ToString());
            writer.WriteString(this[key].ToString());
            writer.WriteEndElement();
        }
    }
}

[/sourcecode]

汎用Dictionary<Tkey, Tvalue>の場合の扱い方がよくわからなかったんで、Dictionary<string, object>に限定。あと、valueがさらに階層化されている場合の処理の書き方もわからなかったんで、一次元のDictionaryにしか対応しない。あとNode名としての妥当性チェックとかもしていない。といろいろ課題はあるけど、動く範囲では動く。

これで、最初に書いたようにDictionaryを含むResultオブジェクトを生成しておいて、それをXMLなりJSONなりで出力するようなAPIコードが簡単に書けるようになった。

Published At2011-11-16 21:38Updated At2011-11-16 21:38