C# Advent Calendar 2013 - Adventar の7日目のエントリです。
クラウド型ビジネスチャットツール|チャットワーク (ChatWork) というチャットサービスがあるのですが、このサービスのAPIが先日限定公開されました。参照: チャットワークAPIを限定プレビュー公開します! | ChatWorkブログ 管理系の機能が足りないので、APIさえあれば管理ツール作れると思った待っていたところでした。というわけで、C# のクライアントをPCLで作ってみました。α版ではありますが、Nugetにも放流しています。当然ながら、限定公開のAPIキーを取得していないと利用できません。
ソースはGitHubで公開しています
使い方はソースにあるConsoleAppを見ていただくのがわかりやすいかと。
制限事項としては、
- APIが正式公開されて、正式版を出すまではAPIの仕様を大幅に変えるおそれがあります
- とくに、プロパティや引数の名前などはPascal形式やCamel形式に変える予定です
- APIで返ってくるクラスも正式版で整理します
- OAuth対応が予定されているため、クライアントクラス作成部分も変更の可能性があります
- HttpClient まわりの設定はのちのち追加していきます
- CancellationTokenやHttpCompletionOptionを含んだオーバーロードまわり
- API仕様で列挙値になっているものも単なる文字列型にしています
- 引数やデータのValidationはしていません
などがあります。
さて、今日はこのクライアントを作ったときの話をしたいと思います。
Chatwork APIはREST StyleなAPIなのですが、今のところWCF Web Serviceのクライアント参照のようなC#のクライアントコードの自動生成は Visual Studio含めなさそうです。ただ、REST APIの定義そのものは RAML というモデリング言語が登場しており、Chatwork APIもRAMLを公開しています。将来的には、RAMLからC#へ自動生成するツールを作ってやったほうがいいかなあと思っています。今回手動で作ったコードだと、工数の問題で対応していないこととかありますので。ただ、とにかく使えるコードがほしかったのでまずは手動でクライアントライブラリを作ることにしました。
Portable Class Library (PCL)
これまでPCLなクラスライブラリを作成したことがなかったので、今回PCLで作成することにしました。プラットフォームごとのSDKの違いをこまやかに吸収するのであれば、プラットフォームごとにクラスライブラリを作って、Nugetで配布するという方法がありますが、今回はそこまでやる予定がないのでPCLでお手軽にマルチプラットフォーム対応することにしました。 対応プラットフォームは
- .NET 4.5以上
- Silverligh5
- Windows Store Apps 8 以上
- Windows Phone 8 以上
にしました。基本的にはasyncが使えるプラットフォームで、というイメージです。
PCLプロジェクトの作成
Visual Studio Professional以上であればPCL用のプロジェクトテンプレートが含まれているのですぐに作成することができます。
対象とするプラットフォームとバージョンも次ような画面で選べます。
あとは、Visual Studioが使えるAPIだけをIntelliSenseに出してくれるので、それに従い開発していきます。
PCLプロジェクトでのNugetの使用
他のライブラリをNugetから使う場合、そのライブラリが選択対象のプラットフォームをサポートしていれば使えます。 今回は
- Http Client Library
- Microsoft Bcl Async (Silverligh5でasync/await使うのに必要)
- JSON.NET
を使っています。参照する方法は通常のプロジェクトと同様です。
クラスライブラリ設計
インターフェースの明示的実装による分離
当初は、RESTのAPI1つに1つのメソッドを対応させて、すべてをChatworkClientの中にフラットに並べました。が、さすがに1つのクラスにメソッドがずらっと並ぶと使いづらいという指摘をいただきまして、インターフェースの明示的に実装という方法を使って、REST APIの最初のPath単位で分離しました。
何をやっているかといいますと、
- /my GET
- /contact GET
という2つのAPIで説明してみます。
public interface IMy { Task<string> GetAsync(); } public interface IContact { Task<string> GetAsync(); } public partial class IketeruClient : IMy, IContact { public IMy My { get { return this; } } public IContact Contact { get { return this; } } } public partial class IketeruClient // IMy { Task<string> IMy.GetAsync() { throw new NotImplementedException(); } } public partial class IketeruClient // IContact { Task<string> IContact.GetAsync() { throw new NotImplementedException(); } } //usage public class Program { public static void Main(string[] args) { var client = new IketeruClient(); var my = client.My.GetAsync().Result; var contact = client.Contact.GetAsync().Result; } }
このような感じで整理することができます。
JSONデータを使ったモデルクラスの作成
これはもうVS2012のころから(拡張機能経由で?)つかえていた機能です。Chatwork APIのドキュメントにはJSONのサンプルデータついていたので、これをコピーしてC#のクラスとして貼り付けていきます。
ただ、異なるAPIの返り値で同じ構造や似た構造のものがある場合のモデルクラスの整理についは今後の課題です。
UnixTimeの変換とJSON.NET でのConverterクラスの作成
Chatwork APIでは時間情報はUnixTimeなinteger値で表現するとあります(海外拠点への展開する場合、TimeZoneの情報どこで持つんだろう...?)。ところがC#では、標準でUnixTimeを扱うことができません。これについては、 C#でUNIX時刻を取得する - 酢ろぐ! を参考にしました。
また、JSON.NET ではDateTimeの変換処理のふるまいを外部から定義できます。そのためには、Newtonsoft.Json.Converters.DateTimeConverterBase クラスを継承して変換クラスを定義します。
あとは、JSONに対応するC#のクラスでこんな風に属性を定義すればOKです。
public class MessageModel { public int message_id { get; set; } public AccountModel account { get; set; } public string body { get; set; } [JsonProperty] [JsonConverter(typeof(UnixDateTimeConverter))] public DateTime send_time { get; set; } [JsonProperty] [JsonConverter(typeof(UnixDateTimeConverter))] public DateTime update_time { get; set; } }
Nugetによる公開
こうやってクラスライブラリが作れたらあとはNugetパッケージを作って公開します。PCLの場合はlibフォルダの下に対応するプラットフォームの名前をつけたフォルダを作ります。このあたりの操作は、
NuGet Package Explorer - Home を使うのが簡単です(最近プロジェクトのHPが変更になっています)。
libフォルダを作って、右クリックすると「Add Portable Library Folder」というメニューを選択し、対応するプラットフォームを選べばOKです。
あとは、参照したNugetの依存関係を追加して、パッケージの情報を追加すれば完成です。
まとめ
というわけで、Chatwork API for C#の紹介がてら、PCLによるクラスライブラリの作成とNugetによる公開を紹介してみました。Visual Studio Professional以上が必須ですが、かなりお手軽に作れます(クラスライブラリがPCLの範囲で作れれば)。
あと、半分趣味タスクで作っているこういうクラスライブラリの設計にもCTOをはじめとする同僚からコメントもらえる環境が弊社にあります。以上、ステマでした。