SharePoint アドイン製品一覧
SharePoint 2010 開発のステップ・バイ・ステップ
Windows Azure 入門
Windows Azure How-To 集
WCF / WF 入門
(2012/02 : サンプル コードを、WCF Web Api から ASP.NET Web Api に変更)
環境 :Visual Studio 2010ASP.NET Web API Beta (ASP.NET MVC 4 Beta)WCF Web API Preview5
REST サービス / Web Api の実践
ここでは、応用的なテーマをとりあげます。基本的な構築手順については、「REST サービスの作成」 (WCF の場合)、または 「Getting Started with ASP.NET Web Api」 (ASP.NET の場合) を参照してください。(2012/02 変更 : 「WCF Web Api」は、今後、「ASP.NET Web API」としてリリース予定です。)
こんにちは。
今回は、RESTful な Web Api で、さまざまな MIME タイプのデータを扱う方法について説明します。
.NET による Web Api 構築では、既定で、Plain XML、JSON などのフォーマットを扱えるようになっており、プログラム側で扱うオブジェクトも、これらの一般的なフォーマットに自然な形で (プログラマーがシリアライズ方法などを意識せずに) シリアライズできます。ASP.NET Web API WCF Web API を使用すると、実は、こうした一般的なフォーマット以外の独自のフォーマット (MIME Type) を扱えるように拡張できます。例えば、オブジェクトをイメージとして返したり、業界固有のフォーマット (MIME Type) なども扱うことができます。
補足 : なお、.NET Framework 4 の WCF REST サービス (WCF Web API を使用しない REST サービス) でも、Stream 型を使うことで、任意のデータ型を扱うことができます。(ただし、下記で述べるようなフォーマット拡張用のフレームワークはありません。)
プロジェクトの準備
準備として、ASP.NET Web API WCF Web API を使用した基本的な REST サービスを構築し、その後で、このサービスを拡張して行きましょう。
まず、Visual Studio を開き、ASP.NET MVC 4 Web Application のプロジェクトを新規作成します。(Web Api のプロジェクトを作成します。)
プロジェクトに、コントローラーを追加します。(今回、この名前を SchedulerController.cs とします。) そして、SchedulerController.cs に、以下の通りコードを記述します。
. . .using System;using System.Collections.Generic;using System.Linq;using System.Net;using System.Net.Http;using System.Web.Http;. . .public class SchedulerController : ApiController{ // GET /api/scheduler/5 public HttpResponseMessage Get(int id) { DateTime now = TimeZoneInfo.ConvertTimeBySystemTimeZoneId( DateTime.UtcNow, "Tokyo Standard Time"); // please search, here . . . // (this api returns dummy data . . .) SchedulerEvent resObj = new SchedulerEvent { Title = "Test Event " + id, StartTime = now, EndTime = now.AddHours(1) }; HttpResponseMessage<SchedulerEvent> resMsg = new HttpResponseMessage<SchedulerEvent>(resObj, HttpStatusCode.OK); return resMsg; }}public class SchedulerEvent{ public string Title; public DateTime StartTime; public DateTime EndTime;}. . .
なお、Global.asax.cs には、あらかじめ、以下の通り設定されているため、特に Routing の設定の必要はありません。(異なる形式の Uri を使用する場合は、独自に Routes を登録します。)
. . .public static void RegisterRoutes(RouteCollection routes){ routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); . . .
いったんビルドして動作確認をおこなうと、既定で Json (application/json) が使用され、結果が返ってくるのがわかります。(下記)
GET http://localhost/api/scheduler/3 HTTP/1.1User-Agent: FiddlerHost: localhost
HTTP/1.1 200 OKServer: ASP.NET Development Server/10.0.0.0Date: Fri, 02 Mar 2012 08:50:07 GMTX-AspNet-Version: 4.0.30319Cache-Control: no-cachePragma: no-cacheExpires: -1Content-Type: application/json; charset=utf-8Connection: CloseContent-Length: 108{ "EndTime":"\/Date(1330681807094+0900)\/", "StartTime":"\/Date(1330678207094+0900)\/", "Title":"Test Event 3"}
また、下記の通り Accept ヘッダーを指定することで、Plain XML フォーマット (application/json) でデータを受け取ることもできます。
GET http://localhost/api/scheduler/3 HTTP/1.1User-Agent: FiddlerHost: localhostAccept: application/xml
HTTP/1.1 200 OKServer: ASP.NET Development Server/10.0.0.0Date: Fri, 02 Mar 2012 08:57:45 GMTX-AspNet-Version: 4.0.30319Cache-Control: no-cachePragma: no-cacheExpires: -1Content-Type: application/xml; charset=utf-8Connection: CloseContent-Length: 293<?xml version="1.0" encoding="utf-8"?><SchedulerEvent xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <Title>Test Event 3</Title> <StartTime>2012-03-02T17:57:45.6603939</StartTime> <EndTime>2012-03-02T18:57:45.6603939</EndTime></SchedulerEvent>
Custom MIME Type によるシリアライズ
以上で、準備はできました。では、上記のサービスを拡張し、カスタムのフォーマット (MIME Type) で結果を返してみましょう。
今回は、上記の SchedulerEvent オブジェクトを、Outlook の予定表ファイル (.ics) の形式として受け取るサンプル コードを作成してみましょう。Outlook の予定表データ (.ics ファイル) は、下記のフォーマットを持つ単純なテキスト ファイルです。(下記は、「テストの予定」というサブジェクトで、開始時間が 2011/10/27 10:00、終了時間が 2011/10/27 10:30、15 分前にアラーム通知をおこなう場合の例です。)
BEGIN:VCALENDARPRODID:-//TESTApp/VERSION:2.0BEGIN:VTIMEZONETZID:Tokyo Standard TimeEND:VTIMEZONEBEGIN:VEVENTDTSTART;TZID="Tokyo Standard Time":20111027T100000DTEND;TZID="Tokyo Standard Time":20111027T103000SUMMARY;LANGUAGE=ja:テストの予定BEGIN:VALARMTRIGGER:-PT15MACTION:DISPLAYDESCRIPTION:ReminderEND:VALARMEND:VEVENTEND:VCALENDAR
この拡張をおこなう場合、ASP.NET Web API WCF Web API では、MediaTypeFormatter 継承クラスを作成して、独自のフォーマットを定義します。
まず、上記のプロジェクトに、クラスを追加します。今回、名前を AppointmentFormatter.cs とします。新規作成された AppointmentFormatter.cs に、下記の通り、BufferedMediaTypeFormatter (MediaTypeFormatter から継承されています) を継承してコードを記述します。
補足 : 今回、BufferedMediaTypeFormatter を使用していますが、非同期で処理したい場合は、継承元の MediaTypeFormatter を使用します。
. . .using System.Net;using System.Net.Http.Formatting;using System.Net.Http.Headers;using System.Threading.Tasks;. . .public class AppointmentFormatter : BufferedMediaTypeFormatter{ public AppointmentFormatter() { } protected override object OnReadFromStream(Type type, Stream stream, HttpContentHeaders contentHeaders, FormatterContext formatterContext) { } protected override void OnWriteToStream(Type type, object value, Stream stream, HttpContentHeaders contentHeaders, FormatterContext formatterContext, TransportContext transportContext) { } protected override bool CanReadType(Type type) { } protected override bool CanWriteType(Type type) { }}. . .
カスタムの Formatter では、上記の通り、4 つの override メソッドを実装すれば OK です。OnWriteToStream メソッドは、オブジェクトを MIME コンテンツのストリームに変換 (シリアライズ) するメソッドで、今回は、このメソッドを実装します。なお、OnReadFromStream メソッドは、逆に、コンテンツのストリームを .NET オブジェクトに逆シリアライズするメソッドで、このメソッドは、あとで実装します。CanReadType / CanWriteType メソッドは、それぞれ、特定の型のオブジェクト (クラス) を扱うことが可能か否かを bool 値で指定します。(フレームワークは、実際のシリアライズ、逆シリアライズの前に、これらのメソッドを使って確認をおこないます。)また、上記のコンストラクター (AppointmentFormatter() メソッド) を使って、インスタンスの作成時に、この MediaTypeFormatter がサポートしている MIME タイプを登録しておきます。(今回は、MIME タイプを text/calendar とします。)
以下の通り実装します。
. . .using System.IO;. . .public class AppointmentFormatter : BufferedMediaTypeFormatter{ public AppointmentFormatter() { // register custom MIME type this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/calendar")); } protected override object OnReadFromStream(Type type, Stream stream, HttpContentHeaders contentHeaders, FormatterContext formatterContext) { // we implement later ... throw new NotImplementedException(); } protected override void OnWriteToStream(Type type, object value, Stream stream, HttpContentHeaders contentHeaders, FormatterContext formatterContext, TransportContext transportContext) { // serialize .NET object to .ics text format SchedulerEvent item = value as SchedulerEvent; StreamWriter writer = new StreamWriter(stream); writer.WriteLine("BEGIN:VCALENDAR"); writer.WriteLine("PRODID:-//TESTApp/"); writer.WriteLine("VERSION:2.0"); writer.WriteLine("BEGIN:VTIMEZONE"); writer.WriteLine("TZID:Tokyo Standard Time"); writer.WriteLine("END:VTIMEZONE"); writer.WriteLine("BEGIN:VEVENT"); writer.WriteLine( string.Format("DTSTART;TZID=\"Tokyo Standard Time\":{0}", item.StartTime.ToString("yyyyMMddTHHmmss"))); writer.WriteLine( string.Format("DTEND;TZID=\"Tokyo Standard Time\":{0}", item.EndTime.ToString("yyyyMMddTHHmmss"))); writer.WriteLine(string.Format("SUMMARY;LANGUAGE=ja:{0}", item.Title)); writer.WriteLine("BEGIN:VALARM"); writer.WriteLine("TRIGGER:-PT15M"); writer.WriteLine("ACTION:DISPLAY"); writer.WriteLine("DESCRIPTION:Reminder"); writer.WriteLine("END:VALARM"); writer.WriteLine("END:VEVENT"); writer.WriteLine("END:VCALENDAR"); writer.Flush(); } protected override bool CanReadType(Type type) { return false; } protected override bool CanWriteType(Type type) { return (type == typeof(SchedulerEvent)); }}. . .
さいごに、上記のカスタムの Media Type Format (AppointmentFormatter) を ASP.NET Web API WCF Web API のフレームワークに登録します。Glabal.asax.cs を開き、下記 太字の通りコードを追加します。
. . .public static void RegisterRoutes(RouteCollection routes){ routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); HttpConfiguration conf = GlobalConfiguration.Configuration; conf.Formatters.Add(new AppointmentFormatter()); routes.MapHttpRoute( . . .
補足 : MediaTypeFormatter の登録には、優先順位があります。(順位の高いものから先に評価されます。) MediaTypeFormatter を先頭に登録するには (評価を優先するには)、Insert メソッドを使用します。
補足 : WCF Web Api の Test Client を使用する場合も、上記の WebApiConfiguration を使って下記の通り構成します。(Test Client の URL は、http://localhost/schedules/test/home のように「/test/home」を付与します。)conf.EnableTestClient = true;
以上で完了です。
Accept ヘッダーに text/calendar を指定してリクエストすると、下記の通り、.ics 形式の予定表のデータとして返ってくるようになります。
GET http://localhost/api/scheduler/3 HTTP/1.1User-Agent: FiddlerHost: localhostAccept: text/calendar
HTTP/1.1 200 OKServer: ASP.NET Development Server/10.0.0.0Date: Fri, 02 Mar 2012 09:23:05 GMTX-AspNet-Version: 4.0.30319Cache-Control: no-cachePragma: no-cacheExpires: -1Content-Type: text/calendarConnection: CloseContent-Length: 365BEGIN:VCALENDARPRODID:-//TESTApp/VERSION:2.0BEGIN:VTIMEZONETZID:Tokyo Standard TimeEND:VTIMEZONEBEGIN:VEVENTDTSTART;TZID="Tokyo Standard Time":20120302T182305DTEND;TZID="Tokyo Standard Time":20120302T192305SUMMARY;LANGUAGE=ja:Test Event 3BEGIN:VALARMTRIGGER:-PT15MACTION:DISPLAYDESCRIPTION:ReminderEND:VALARMEND:VEVENTEND:VCALENDAR
また、サービス側のコードに、下記 太字の通りコンテンツ タイプを指定すると、ブラウザーを使って http://localhost/api/scheduler/3 と入力した時にも、必ず、text/calendar の内容 (すなわち、上記の .ics の内容) が返ってくるようになります。
. . .using System.Net.Http.Formatting;. . .public HttpResponseMessage Get(int id){ DateTime now = TimeZoneInfo.ConvertTimeBySystemTimeZoneId( DateTime.UtcNow, "Tokyo Standard Time"); // please search, here . . . // (this api returns dummy data . . .) SchedulerEvent resObj = new SchedulerEvent { Title = "Test Event " + id, StartTime = now, EndTime = now.AddHours(1) }; HttpResponseMessage<SchedulerEvent> resMsg = new HttpResponseMessage<SchedulerEvent>(resObj, HttpStatusCode.OK, new[] { new AppointmentFormatter() }); return resMsg;}
このため、Outlook のインストールされたクライアント (PC) のブラウザーから この URL に接続すると、下図のように、Outlook の予定追加の画面が (ブラウザーから) 起動します。
補足 : 上記では、ContentType を強制的に上書きしていますが、現実の開発では、他のフォーマット (Json など) もそのまま使用できるようにカスタマイズしておくと良いでしょう。例えば、ASP.NET Web API WCF Web Api の DelegatingHandler を使って、Path から Uri Mapping に変換するカスタム ハンドラーを登録する方法があります。(例えば、http://localhost/api/scheduler/3/ics のような URL を入力することで、ブラウザーを使って .ics フォーマットのファイルをダウンロードできます。)
このように、ASP.NET Web API WCF Web Api を使用すると、HTTP 固有のフォーマット処理など システム的な処理 (関心事) をメインのロジックと分離し、かつ、再利用して構築できます。
Custom MIME Type による逆シリアライズ
上記では、.NET のオブジェクト (SchedulerEvent) からカスタム MIME のコンテンツに変換しましたが、逆に、HttpClient などを使って、受信した MIME のコンテンツ (HTTP の Headers、Body) を .NET オブジェクトに逆シリアライズできます。
上記とは逆に、今後は、MediaTypeFormatter (BufferedMediaTypeFormatter) の OnReadFromStream メソッドを下記 (太字) の通り実装してみます。
. . .public class AppointmentFormatter : BufferedMediaTypeFormatter{ . . . protected override object OnReadFromStream(Type type, Stream stream, HttpContentHeaders contentHeaders, FormatterContext formatterContext) { object result = null; if (string.Equals(contentHeaders.ContentType.MediaType, "text/calendar")) { SchedulerEvent resItem = new SchedulerEvent(); StreamReader reader = new StreamReader(stream); while (!reader.EndOfStream) { string contentsLine = reader.ReadLine(); string[] contentsVals = contentsLine.Split(':'); if (contentsVals[0].StartsWith("SUMMARY;")) resItem.Title = contentsVals[1]; else if (contentsVals[0].StartsWith("DTSTART;")) resItem.StartTime = DateTime.ParseExact(contentsVals[1], "yyyyMMddTHHmmss", null); else if (contentsVals[0].StartsWith("DTEND;")) resItem.EndTime = DateTime.ParseExact(contentsVals[1], "yyyyMMddTHHmmss", null); } result = resItem; } return result; } . . . protected override bool CanReadType(Type type) { return (type == typeof(SchedulerEvent)); } . . .
以上で完了です。では、逆シリアライズの実験をしてみましょう。かなり強引ですが、上記の ASP.NET MVC のプロジェクト (コントローラー) から、下記の通り、HttpClient を使用して GetEvent を呼び出してみます。この際、Accept ヘッダーにより text/calendar の MIME フォーマットでデータを受信しますが、最終的に、下記の item には、SchedulerEvent オブジェクト (.NET オブジェクト) として結果を取得できます。(以降は、.NET のプログラムから、オブジェクトとしてこのコンテンツの内容を確認できます。)
. . .using System.Net.Http;using System.Net.Http.Headers;. . .public class HomeController : Controller{ public ActionResult Test() { string serviceUrl = string.Format("{0}://{1}{2}", HttpContext.Request.Url.Scheme, HttpContext.Request.Url.Authority, Url.Content("~/api/scheduler/3")); HttpClient cl = new HttpClient(); cl.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("text/calendar")); HttpResponseMessage res = cl.GetAsync(serviceUrl).Result; string contentBody = res.Content.ReadAsStringAsync().Result; SchedulerEvent item = res.Content.ReadAsAsync<SchedulerEvent>(new[] { new AppointmentFormatter() }).Result; . . . return View(); } . . .
補足 : 実際の開発では、カスタムの MediaTypeFormatter クラスをライブラリーとして作成するなどして、クライアント側でもこのクラスを使用 (再利用) できるようにしておくと良いでしょう。ここでは、単純に、同じ ASP.NET MVC プロジェクトのコントローラー コードでテストしています。(真似しないでください。。。)
なお、現在の Beta 版には、MediaTypeFormatter から継承された標準のフォーマッターとして、JsonMediaTypeFormatter、XmlMediaTypeFormatter、FormUrlEncodedMediaTypeFormatter などが提供されています。