ASP.NET
ASP.NET Web API を使ってハイパーメディア Web API を構築する
ハイパーメディアは HATEOAS (Hypermedia as the Engine of Application State) としても知られていますが、REST (Representational State Transfer) の主な制約の 1 つです。クライアントが一連の HTTP サービスを操作する方法を記述するために、ハイパーメディアのアーティファクト (リンクやフォームなど) を使用する、というのがその考え方です。進化可能な API のデザインを考案する際に、このハイパーメディアの考え方が興味深い概念としてすぐに取り入れられるようになりました。考え方は、通常 Web を操作する方法と変わりありません。通常は、Web サイトのホームページのエントリ ポイントまたは URL を 1 つ覚えておいて、リンクを使ってサイト内のさまざまなセクションに移動します。フォームを使用することもあります。フォームは、事前に定義済みの操作や、サイトがなんらかの操作を実行するために必要なデータを送信するための URL を含みます。
多くの開発者は、Web サービス記述言語 (WSDL) などの SOAP サービスの正式なコントラクトから、ハイパーメディア以外の Web API の簡単なドキュメントに至るまで、サービスでサポートするメソッドをすべて静的に記述しておこうとします。静的に API を記述する方法の大きな問題点は、クライアントとサーバーとの間に密接な結び付きが生じることです。そのため、API の記述に変更を加えると、既存のクライアントがすべて機能しなくなる可能性があるため、API の進化が妨げられます。
企業内であれば、クライアント アプリケーションの数が管理され事前に把握されているため、しばらくの間は問題になりません。ただし、潜在的なクライアント数が急激に増加した今日では、複数のデバイスで数千ものサードパーティ製アプリケーションが実行される可能性があるため、この方法は優れた考え方とはいえません。しかし、単純に SOAP サービスを HTTP サービスに移行しても、この問題が解決されるとは限りません。たとえば、URL を導き出すための知識を一部でもクライアントに持たせていれば、WSDL などの明示的なコントラクトがなくても問題は解決されません。あらゆるサーバーの変更からクライアントを守る機能が、ハイパーメディアです。
クライアントが次に行えることを決定するアプリケーションの状態ワークフローは、サーバー側に配置する必要があります。あるリソースでの操作が特定の状態でしか使用できないとすると、このワークフローのロジックをいずれかの API クライアントに配置しなければならないのでしょうか。そんなことはありません。サーバーは、常に、リソースで行えることを指示すべきです。たとえば、発注書 (PO) がキャンセルされている場合、クライアント アプリケーションではその PO の送信が許可されてはなりません。つまり、クライアントに送った応答では、PO を送信するリンクやフォームが利用可能にならないようにします。
ハイパーメディアの登場
リンクは、常に、REST アーキテクチャの重要なコンポーネントでした。当然、リンクはブラウザーなどの UI コンテキストでよく使用されます。たとえば、カタログで特定の製品の詳細を取得するための [詳細を表示] リンクなどが思い浮かびます。では、UI やユーザー操作がなく、コンピューターどうしでやり取りを行うシナリオの場合はどうでしょう。このようなシナリオでも、同様に、ハイパーメディアのアーティファクトを使用できます。
この新しい手法でサーバーが返すのはデータだけではありません。サーバーは、データとハイパーメディアのアーティファクトを返します。ハイパーメディアのアーティファクトは、サーバー アプリケーション ワークフローの状態を基に、特定の時点に実行可能な操作のセットを判断する手段をクライアントに提供します。
この手法が、多くの場合、通常の Web API と RESTful API を区別する 1 つの領域になりますが、ほかにも適用される制約があるため、API が RESTful かどうかを議論するのはほとんど意味がないでしょう。重要なのは、API では HTTP をアプリケーション プロトコルとして適切に使用し、可能な場面ではハイパーメディアを活用することです。ハイパーメディアを有効にすることで、自己検出可能な API を作成できます。これはドキュメントを用意しないことを正当化するのではなく、更新を可能にするという観点で、API により柔軟性を与えます。
使用できるハイパーメディアのアーティファクトは、主に選択したメディアの種類によって決まります。JSON や XML など、Web API を構築するために今日使用しているメディアの種類の多くは、HTML のような、リンクやフォームを表す組み込みの概念がありません。このようなメディアの種類でも、ハイパーメディアを公開する方法を定義すれば利用できますが、そのためにはクライアントがハイパーメディアのセマンティクスが定義される方法を把握している必要があります。これに対して、XHTML (application/xhtml+xml) や ATOM (application/atom+xml) などのメディアの種類は、リンクやフォームなどのハイパーメディアのアーティファクトのいくつかを既にサポートしています。
HTML の場合、リンクは URL を指す "href" 属性、リンクと現在のリソースとの関連性を表す "rel" 属性、および想定するメディアの種類を指定する "type" 属性の 3 つのコンポーネントで構成されます。たとえば、XHTML を使用してカタログで製品一覧を公開する場合、リソース ペイロードは図 1 のようになります。
図 1 XHTML を使った製品一覧の公開
<div id="products">
<ul class="all">
<li>
<span class="product-id">1</span>
<span class="product-name">Product 1</span>
<span class="product-price">5.34</span>
<a rel="add-cart" href="/cart" type="application/xml"/>
</li>
<li>
<span class="product-id">2</span>
<span class="product-name">Product 2</span>
<span class="product-price">10</span>
<a rel="add-cart" href="/cart" type="application/xml"/>
</li>
</ul>
</div>
この例の製品カタログは標準の HTML 要素で表されていますが、既存の XML ライブラリを使って解析するのが非常に容易なため XHTML を使用しています。ペイロードの一部として、アンカー (a) 要素も含まれ、現在のユーザー カートに項目を追加するリンクを表しています。このリンクを見ると、クライアントは rel 属性の用途 (新しい項目の追加) を推測でき、リソース (/cart) で命令を実行するために href を使用できます。重要なのは、ビジネス ワークフローに基づきサーバーによってリンクが生成されるため、クライアントは URL をハードコーディングしたり、規則を推測したりする必要がないことです。この方法では、既存のクライアントにまったく影響を与えることなく、実行中にワークフローを修正するチャンスも得られます。カタログで提供されている製品のいずれかが在庫切れであれば、サーバーは製品をカートに追加するためのリンクを単純に削除できます。クライアントにとっては、リンクが使用できないのでその製品を注文できません。サーバー側ではこのワークフローにもっと複雑な規則が関係しているかもしれませんが、クライアントではリンクが存在しないことだけが意味を持つため、そのような複雑な規則を意識することはありません。ハイパーメディアとリンクによって、クライアントがサーバー側のビジネス ワークフローと切り離されます。
さらに、API の設計を進化させる可能性も、ハイパーメディアとリンクによって向上します。サーバー上のビジネス ワークフローの進化に合わせて、新しい機能のリンクを追加できます。製品カタログの例では、次のように、製品をお気に入りとしてマークする新しいリンクをサーバーに含めることができます。
<li>
<span class="product-id">1</span>
<span class="product-name">Product 1</span>
<span class="product-price">5.34</span>
<a rel="add-cart" href="/cart/1" type="application/xml"/>
<a rel="favorite" href="/product_favorite/1"
type="application/xml"/>
</li>
既存のクライアントはこのリンクを無視して、新しい機能の効果を利用しないかもしれませんが、新しいクライアントはこの機能をすぐに使い始めることができます。この方法で、Web API 用に残りの機能を見つけるリンクを含むエントリ ポイントまたはルート URL を 1 つ用意することは不自然ではありません。たとえば、1 つの URL "/shopping_cart" を用意して、次の HTML 表現を返すことができます。
<div class="root">
<a rel="products" href="/products"/>
<a rel="cart" href="/cart"/>
<a rel="favorites" href="/product_favorite"/>
</div>
類似の機能は OData サービスにもあり、サポートするすべてのリソースのセットとそれに関連するデータを取得するリンクを含むサービス ドキュメントを 1 つルート URL で公開します。
リンクは、サーバーとクライアントを接続する優れた方法ですが、明らかな問題もあります。上記の製品カタログの例では、HTML のリンクは rel 属性、href 属性、および type 属性しか提供しません。つまり、href 属性で表現される URL で実行できることに関して、帯域外の知識がいくつか必要になることになります。クライアントは HTTP POST または HTTP GET を使うべきでしょうか。POST を使う場合、クライアントはどのデータを要求本文に含める必要があるでしょう。このような知識はすべてドキュメントのどこかに記載できますが、クライアントがこの機能を実際に見つけられたらすばらしいと思いませんか。このような疑問のすべてに答えるのが、HTML フォームの使用です。
実際のフォーム
ブラウザーを使用して Web を操作するときは、通常、フォームを使って操作を表現します。製品カタログの例では、[カートに追加](Add to Cart) リンクを押すと、HTTP GET がサーバーに送信され、製品をカートに追加するのに使用する HTML フォームを返します。このフォームには、URL を伴う "action" 属性、HTTP メソッドを表す "method" 属性、ユーザーから入力を必要とする入力フィールド、および次に進むための指示を含めることができます。
コンピューターどうしのシナリオでも同じことが可能です。人間がフォームを操作する代わりに、JavaScript または C# を実行するアプリケーションを用意します。製品カタログの最初の製品の "add-cart" リンクの HTTP GET は、XHTML で表された次のフォームを取得することになります。
<form action="/cart" method="POST">
<input type="hidden" id="product-id">1</input>
<input type="hidden" id="product-price">5.34</input>
<input type="hidden" id="product-quantity" class="required">1</input>
<input type="hidden" id="___forgeryToken">XXXXXXXX</input>
</form>
これで、クライアント アプリケーションは、製品をカートに追加することに関連する特定の詳細とは切り離されるようになります。HTTP POST を使用して、このフォームを action 属性で指定される URL に送信する必要があるだけです。サーバーはフォームに追加の情報、たとえば、クロスサイト リクエスト フォージェリ (CSRF) 攻撃を避けたり、サーバー用に事前設定されたデータに署名したりするための偽造トークンなどを含めることができます。
このモデルでは、ユーザーのアクセス許可やクライアントが使用するバージョンなど、さまざまな要素に基づいて新しいフォームを提供することで、Web API を自由に進化できるようにします。
XML および JSON 向けのハイパーメディア
前述のように、XML (application/xml) および JSON (application/json) 向けの汎用のメディアの種類には、ハイパーメディアもリンクやフォーム用の組み込みサポートがありません。このようなメディアの種類は、"application/vnd-shoppingcart+xml" などのドメイン固有のコンセプトを使って拡張できますが、これでは新しいクライアントが新しい種類で定義されたすべてのセマンティクスを理解する必要があるため (メディアの種類の増殖を生む場合もあるため)、一般に優れた考え方とは考えられていません。
このため、リンクのセマンティクスを使って XML と JSON を拡張する、HAL (Hypertext Application Language) という新しいメディアの種類が提案されています。XML と JSON を使用してハイパーリンクと埋め込みリソース (データ) を公開する標準の方法を単純に定義するドラフトは、stateless.co/hal_specification.html から入手できます。メディアの種類 HAL は、一連のプロパティ、リンク、および埋め込みリソースを含むリソースを定義します (図 2 参照)。
図 2 メディアの種類 HAL
図 3 は、XML 表現と JSON 表現の両方を使って HAL で製品カタログを表す方法の例を示しています。図 4 は、サンプル リソースの JSON 表現です。
図 3 HAL での製品カタログ
<resource href="/products">
<link rel="next" href="/products?page=2" />
<link rel="find" href="/products{?id}" templated="true" />
<resource rel="product" href="/products/1">
<link rel="add-cart" href="/cart/" />
<name>Product 1</name>
<price>5.34</price>
</resource>
<resource rel="product" href="/products/2">
<link rel="add-cart" href="/cart/" />
<name>Product 2</name>
<price>10</price>
</resource>
</resource>
図 4 サンプル リソースの JSON 表現
{
"_links": {
"self": { "href": "/products" },
"next": { "href": "/products?page=2" },
"find": { "href": "/products{?id}", "templated": true }
},
"_embedded": {
"products": [{
"_links": {
"self": { "href": "/products/1" },
"add-cart": { "href": "/cart/" },
},
"name": "Product 1",
"price": 5.34,
},{
"_links": {
"self": { "href": "/products/2" },
"add-cart": { "href": "/cart/" }
},
"name": "Product 2",
"price": 10
}]
}
}
ASP.NET Web API におけるハイパーメディアのサポート
ここまでは、Web API の設計におけるハイパーメディアの基になる理論をいくつか説明してきました。ここからは、このフレームワークが提供するすべての拡張ポイントと機能と ASP.NET Web API を使用して、実際にこの理論を実装する方法を見ていきましょう。
ASP.NET Web API では、その中核レベルでフォーマッタの考え方をサポートします。フォーマッタの実装では、特定のメディアの種類の処理方法、およびそれを具体的な .NET 型に対してシリアル化またはシリアル化解除する方法を認識します。以前は、ASP.NET MVC での新しいメディアの種類のサポートは大きな制限がありました。HTML と JSON のみが優先的に対応され、スタック全体で完全にサポートされていました。また、コンテンツ ネゴシエーションをサポートするための一貫したモデルはありませんでした。カスタムの ActionResult の実装を提供することで応答メッセージ用にさまざまなメディアの種類のフォーマットをサポートできましたが、新しいメディアの種類を導入して要求メッセージをシリアル化解除する方法は明らかではありませんでした。これは多くの場合、新しいモデル バインダーまたは値プロバイダーを使ってモデルバインディング インフラストラクチャを活用することで解決できました。さいわい、ASP.NET Web API にフォーマッタが導入されたことによりこの一貫性の欠如が解決されています。
すべてのフォーマッタは、基本クラス System.Net.Http.Formatting.MediaTypeFormatter から派生し、CanReadType/ReadFromStreamAsync メソッドをオーバーライドしてシリアル化解除をサポートし、CanWriteType/WriteToStreamAsync メソッドをオーバーライドして .NET 型を特定のメディアの種類のフォーマットにシリアル化するのをサポートします。
図 5 に、MediaTypeFormatter クラスの定義を示します。
図 5 MediaTypeFormatter クラス
public abstract class MediaTypeFormatter
{
public Collection<Encoding> SupportedEncodings { get; }
public Collection<MediaTypeHeaderValue> SupportedMediaTypes { get; }
public abstract bool CanReadType(Type type);
public abstract bool CanWriteType(Type type);
public virtual Task<object> ReadFromStreamAsync(Type type,
Stream readStream,
HttpContent content, IFormatterLogger formatterLogger);
public virtual Task WriteToStreamAsync(Type type, object value,
Stream writeStream, HttpContent content,
TransportContext transportContext);
}
フレームワークが要求メッセージの "Accept" ヘッダーと "Content-Type" ヘッダーで受け取った値に基づいて適切なフォーマッタを選択できるようになったため、フォーマッタは ASP.NET Web API でコンテンツ ネゴシエーションをサポートする際に非常に重要な役割を果たします。
ReadFromStreamAsync メソッドと WriteToStreamAsync メソッドは、非同期作業にタスク並列ライブラリ (TPL) を利用するため、Task インスタンスを返します。フォーマッタの実装を明示的に同期して作業させる必要がある場合は、基本クラスの BufferedMediaTypeFormatter がこれを内部で行います。この基本クラスの実装では、SaveToStream と ReadFromStream をオーバーライドできます。これらは、SaveToStreamAsync と ReadFromStreamAsync の同期バージョンです。
HAL 用の MediaTypeFormatter の開発
HAL は、リソースとリンクを表す特定のセマンティクスを使用するため、Web API の実装のモデルはどれも使用できません。このため、リソースを表す基本クラスとリソースのコレクションを表す別のクラスを使用して、フォーマッタの実装を非常にシンプルにします。
public abstract class LinkedResource
{
public List<Link> Links { get; set; }
public string HRef { get; set; }
}
public abstract class LinkedResourceCollection<T> : LinkedResource,
ICollection<T> where T : LinkedResource
{
// Rest of the collection implementation
}
Web API コントローラーが使用する実際のモデル クラスは、これら 2 つの基本クラスから派生できます。たとえば、製品または製品のコレクションは次のように実装できます。
public class Product : LinkedResource
{
public int Id { get; set; }
public string Name { get; set; }
public decimal UnitPrice { get; set; }
}
...
public class Products : LinkedResourceCollection<Product>
{
}
次に、HAL モデルを定義する標準の方法で、フォーマッタを実装します。新しいフォーマッタの実装に着手する最も簡単な方法は、MediaTypeFormatter 基本クラスまたは BufferedMediaTypeFormatter 基本クラスから派生する方法です。図 6 の例では、2 つ目の基本クラスを使用しています。
図 6 BufferedMediaTypeFormatter 基本クラス
public class HalXmlMediaTypeFormatter : BufferedMediaTypeFormatter
{
public HalXmlMediaTypeFormatter()
: base()
{
this.SupportedMediaTypes.Add(new MediaTypeHeaderValue(
"application/hal+xml"));
}
public override bool CanReadType(Type type)
{
return type.BaseType == typeof(LinkedResource) ||
type.BaseType.GetGenericTypeDefinition() ==
typeof(LinkedResourceCollection<>);
}
public override bool CanWriteType(Type type)
{
return type.BaseType == typeof(LinkedResource) ||
type.BaseType.GetGenericTypeDefinition() ==
typeof(LinkedResourceCollection<>);
}
...
}
このコードは、まずコンストラクタでこの実装向けにサポートするメディアの種類 ("application/hal+xml") を定義し、CanReadType メソッドと CanWriteType メソッドをオーバーライドして、サポートする .NET 型を指定します。これは、LinkedResource または LinkedResourceCollection から派生する必要があります。コンストラクタで定義を行ったため、この実装は HAL の XML バリアントしかサポートしません。JSON バリアントをサポートするために、必要に応じて別のフォーマッタを実装することができます。
実際の作業は WriteToStream メソッドと ReadFromStream メソッドで行い (図 7 参照)、それぞれ XmlWriter を使用してオブジェクトをストリームに書き込み、XmlReader を使用してストリームから読み取ります。
図 7 WriteToStream メソッドと ReadFromStream メソッド
public override void WriteToStream(Type type, object value,
System.IO.Stream writeStream, System.Net.Http.HttpContent content)
{
var encoding = base.SelectCharacterEncoding(content.Headers);
var settings = new XmlWriterSettings();
settings.Encoding = encoding;
var writer = XmlWriter.Create(writeStream, settings);
var resource = (LinkedResource)value;
if (resource is IEnumerable)
{
writer.WriteStartElement("resource");
writer.WriteAttributeString("href", resource.HRef);
foreach (LinkedResource innerResource in (IEnumerable)resource)
{
// Serializes the resource state and links recursively
SerializeInnerResource(writer, innerResource);
}
writer.WriteEndElement();
}
else
{
// Serializes a single linked resource
SerializeInnerResource(writer, resource);
}
writer.Flush();
writer.Close();
}
public override object ReadFromStream(Type type,
System.IO.Stream readStream, System.Net.Http.HttpContent content,
IFormatterLogger formatterLogger)
{
if (type != typeof(LinkedResource))
throw new ArgumentException(
"Only the LinkedResource type is supported", "type");
var value = (LinkedResource)Activator.CreateInstance(type);
var reader = XmlReader.Create(readStream);
if (value is IEnumerable)
{
var collection = (ILinkedResourceCollection)value;
reader.ReadStartElement("resource");
value.HRef = reader.GetAttribute("href");
var innerType = type.BaseType.GetGenericArguments().First();
while (reader.Read() && reader.LocalName == "resource")
{
// Deserializes a linked resource recursively
var innerResource = DeserializeInnerResource(reader, innerType);
collection.Add(innerResource);
}
}
else
{
// Deserializes a linked resource recursively
value = DeserializeInnerResource(reader, type);
}
reader.Close();
return value;
}
最後の手順では、フォーマッタの実装を Web API ホストの一部として構成します。この手順は、ASP.NET または ASP.NET Web API Self-Host とほぼ同じ方法で実現でき、必要な HttpConfiguration 実装に 1 つ違いがあるだけです。Self-Host が HttpSelfHostConfiguration インスタンスを使用するのに対し、ASP.NET は通常 System.Web.Http.GlobalConfiguration.Configuration でグローバルに使用可能な HttpConfiguration インスタンスを使用します。HttpConfiguration クラスは、Formatters コレクションを提供し、フォーマッタの独自の実装を挿入できるようにします。以下に、これを ASP.NET で行う方法を示します。
protected void Application_Start()
{
Register(GlobalConfiguration.Configuration);
}
public static void Register(HttpConfiguration config)
{
config.Formatters.Add(new HalXmlMediaTypeFormatter());
}
フォーマッタを ASP.NET Web API パイプラインで構成したら、すべてのコントローラーが単に LinkedResource から派生するモデル クラスを返して、HAL を使用するフォーマッタによってシリアル化するようにできます。製品カタログの例の場合、製品とカタログを表す製品のコレクションは、それぞれ LinkedResource と LinkedResourceCollection から派生することができます。
public class Product : LinkedResource
{
public int Id { get; set; }
public string Name { get; set; }
public decimal UnitPrice { get; set; }
}
public class Products : LinkedResourceCollection<Product>
{
}
図 8 に示すように、製品カタログ リソースに対するすべての要求を処理する ProductCatalogController コントローラーは、Get メソッドに Product インスタンスと Products インスタンスを返すようになります。
図 8 ProductCatalogController クラス
public class ProductCatalogController : ApiController
{
public static Products Products = new Products
{
new Product
{
Id = 1,
Name = "Product 1",
UnitPrice = 5.34M,
Links = new List<Link>
{
new Link { Rel = "add-cart", HRef = "/api/cart" },
new Link { Rel = "self", HRef = "/api/products/1" }
}
},
new Product
{
Id = 2,
Name = "Product 2",
UnitPrice = 10,
Links = new List<Link>
{
new Link { Rel = "add-cart", HRef = "/cart" },
new Link { Rel = "self", HRef = "/api/products/2" }
}
}
};
public Products Get()
{
return Products;
}
}
この例は HAL フォーマットを使用していますが、同様の方法で、モデルを XHTML にシリアル化するのに Razor とテンプレートを使用するフォーマッタを構築することもできます。Razor 用の MediaTypeFormatter の具体的な実装は RestBugs で見つかります。これは、ASP.NET Web API を使用してハイパーメディア Web API を作成する方法を示すために Howard Dierking が作成したサンプル アプリケーションです (github.com/howarddierking/RestBugs、英語)。
フォーマッタは、新しいメディアの種類に Web API を拡張するのを容易にします。
Web API コントローラーでのリンク サポートの向上
前の ProductCatalogController の例では、明らかに不適切なところがあります。リンクをすべてハードコーディングしているため、ルートが頻繁に変わる場合は厄介なことになります。優れている点は、フレームワークが System.Web.Http.Routing.UrlHelper というヘルパー クラスを提供し、ルーティング テーブルからリンクを自動的に推測することです。このクラスのインスタンスは Url プロパティを通して ApiController 基本クラスで使用できるので、あらゆるコントローラー メソッドで簡単に使用できます。次に、UrlHelper クラスの定義を示します。
public class UrlHelper
{
public string Link(string routeName,
IDictionary<string, object> routeValues);
public string Link(string routeName, object routeValues);
public string Route(string routeName,
IDictionary<string, object> routeValues);
public string Route(string routeName, object routeValues);
}
Route メソッドは特定のルートの相対 URL (/products/1 など) を返し、Link メソッドはハードコーディングを避けるためにモデルで使用できる絶対 URL を返します。Link メソッドは、ルート名と URL を構成する値の 2 つの引数を受け取ります。
図 9 は、前の製品カタログの例で、UrlHelper クラスを Get メソッドで使用する方法を示しています。
図 9 UrlHelper クラスを Get メソッドで使用する方法
public Products Get()
{
var products = GetProducts();
foreach (var product in products)
{
var selfLink = new Link
{
Rel = "self",
HRef = Url.Route("API Default",
new
{
controller = "ProductCatalog",
id = product.Id
})
};
product.Links.Add(selfLink);
if(product.IsAvailable)
{
var addCart = new Link
{
Rel = "add-cart",
HRef = Url.Route("API Default",
new
{
controller = "Cart"
})
};
product.Links.Add(addCart);
}
}
return Products;
}
製品の "self" リンクは、ProductCatalog というコントローラー名と製品 ID を使用して既定のルートから生成されます。カートに製品を追加するためのリンクも既定のルートから生成されますが、この場合は Cart というコントローラー名を使用します。図 9 に示すように、カートに製品を追加するためのリンクは、製品の在庫に基づく応答に関連付けられています (product.IsAvailable)。クライアントにリンクを提供するロジックは、通常コントローラーで適用されるビジネス ルールに多くの部分を依存しています。
まとめ
ハイパーメディアは、クライアントとサーバーを個別に進化できるようにする強力な機能です。リンクや、さまざまな段階にサーバーから提供されるフォームなどのハイパーメディアのアーティファクトを使用することで、操作を行うサーバー ビジネス ワークフローからクライアントが適切に切り離されます。
Pablo Cibraro は、マイクロソフト テクノロジによる広く普及しているシステムの設計と実装に 12 年以上の経験を持つ国際的に認められた専門家です。彼は、Connected Systems MVP です。過去 9 年間、彼は数多くのマイクロソフト チームがツールとフレームワークを開発し、Web サービス、Windows Communication Foundation、ASP.NET、および Windows Azure のサービス指向アプリケーションを構築するのをサポートしてきました。彼のブログは weblogs.asp.net/cibrax (英語) で、Twitter は twitter.com/cibrax (英語) でフォローできます。
この記事のレビューに協力してくれた技術スタッフの Daniel Roth に心より感謝いたします。