WCFでのRESTfulメッセージベースのWebサービスの構築

はじめに



WCFでSOAPメッセージベースのWebサービスを作成する方法についてはすでに書いています 。 そして今、WCFでのRESTfulメッセージベースのWebサービスの設計と構築についてお話したいと思います。 この記事を理解するには、 RESTの基本知識と、WCFでRESTful Webサービスを作成する方法が必要です。 RESTful Webサービスに慣れるには、「 WCF 3.5でRESTful Webサービスを設計および構築するためのガイド」をご覧ください。



この記事では、RESTfulな設計の問題を発見して解決しようとします。 以下のRESTful Webサービスを構築する方法を学習します。





サンタクロースのWCF Webサービスを設計しましょう。 サンタは、RESTアーキテクチャスタイルが大好きであり、 Open Data Protocol(OData)が嫌いなので、次の要件を提示しました。



コアビジネスオブジェクトの定義



私たちの目標は、RESTfulスタイルでWebサービスを設計することです。そのため、ビジネスオブジェクトをできるだけシンプルに保ちましょう。



ギフトリクエストクラス(以降、PresentRequest)を検討してください。 PresentRequestは集約であり、欲求に関するすべての必要な情報が含まれています。

PresentRequest
public class PresentRequest { public Address Address { get; set; } public Guid Id { get; set; } public PresentRequestStatus Status { get; set; } public string Wish { get; set; } }
      
      





住所
 public class Address { public string Country { get; set; } public string Recipient { get; set; } public string StreetAddress { get; set; } public int ZipCode { get; set; } }
      
      





PresentRequestStatus
 public enum PresentRequestStatus { Pending, Accepted, Rejected, Completed }
      
      





これで、開始する必要があるすべてのものができました。



WCFのRESTful Webサービス:設計上の問題



このステップでは、Webサービスインターフェイスを定義します。 Save



メソッドから始めましょう。



PresentRequestを保存しています


簡単な実装は次のようになります。

 public void Save(PresentRequest request)
      
      





クライアントはすべてのフィールドに入力し、Webサービスにリクエストを送信します。 Save



メソッドはvoid



返しvoid



。なぜなら サービスの負荷が大きくなることがわかっているため、一意のId



生成はクライアントの肩にかかっています。



RESTfulデザインスタイルに従って、 Save



メソッドをWebInvoke



属性で装飾し、適切なHTTPメソッドを指定する必要があります。 HTTPメソッドに関する小さなチートシートを次に示します。

運営

HTTP

作成する

PUT / POST

読む

ゲット

更新する

PUT / PATCH

削除する

削除

その結果、次のServiceContractを取得します。

 [ServiceContract] public interface IPresentRequestService { [WebInvoke(Method = "POST", UriTemplate = "requests")] [OperationContract] void Save(PresentRequest request); }
      
      





注:ServiceContractはサービスの主要部分であり、安定性と柔軟性が必要です。 すべての顧客はServiceContractに依存しているため、契約の変更には非常に注意する必要があります。



Saveメソッドには、長所と短所の両方があります。

長所



ほとんどの開発者は、 Mythical Man-Monthという本から、ソフトウェアの最初のバージョンが破棄されることを知っています。 同じことがServiceContractにも当てはまるので、可能な限り柔軟にする必要があります。

短所



KnownTypeAttributeについては知っていますが、逆シリアル化プロセスのためだけに役に立たないクラス階層を作成する必要があります。



Create



Update



およびDelete



操作には、同様の長所と短所があります。 操作Get-異なり、表示されます、私見、維持するのが最も難しい方法。



PresentRequestsを取得する


Get操作の場合、パラメーターはクエリ文字列で送信されます 。 私たちの場合、ステータスと国でPresentRequest



を取得するには、次のようなものを作成する必要があります

 [WebGet(UriTemplate = "requests?country={country}&status={status}")] [OperationContract] List<PresentRequest> Get(string country, string status);
      
      





長所



欠陥をリストする前に、 Get



メソッドを見てみましょう。 WCFを使用せずに、アプリケーション内でこのメソッドを使用するとします。

 public interface IPresentRequestService { List<PresentRequest> Get(string country, string status); }
      
      





この方法の最大の問題の1つは署名です。 メソッドのシグネチャを変更した後は、サービスの実装を更新する必要があります。 この方法は壊れやすく、臭いがあります。 したがって、デフォルトではRESTfulスタイルのGet



操作を維持するのは困難です。

より良い解決策は次のとおりです。インターフェイスを変更せずにリクエストを変更できます。

 public interface IPresentRequestService { List<PresentRequest> Get(PresentRequestQuery query); }
      
      





必要なすべてのクエリデータには、 PresentRequestQuery



クラスが含まれます。

 public class PresentRequestQuery { public string Country { get; set; } public string Status { get; set; } }
      
      





短所

前述のように、 Get



メソッドには脆弱なシグネチャがあるため、変更を壊さずに機能を拡張することは非常に困難です。 Get操作パラメーターは、単純なフィールドを持つクエリ文字列として送信されます。これは、 Get



メソッドのシグネチャでも表されます。 パラメータ間の接続はありません WCFは、パラメーターに基づいてクエリオブジェクトを作成しません。

例を見てみましょう:URL SantaClaus.org/requests?country=sheldonopolis&status=pendingで国とステータス別にPresentReuqest



を取得します。

WCFサービスの対応するメソッドは次のとおりです。

 public List<PresentRequest> Get(string country, string status) { throw new NotImplementedException(); }
      
      





メソッドのシグネチャによると、国とステータスの間には関係がありません。 実際、 country



status



が何status



意味するのかはわかりません。推測することしかできません。 私の意見では、WCFはクエリオブジェクトに基づいてクエリの期限を作成(シリアル化)し、クエリ文字列に基づいてクエリオブジェクトを作成(逆シリアル化)できる必要があります。 したがって、次のリクエストオブジェクトを送信するには:

 public class PresentRequestQuery { public string Country { get; set; } public string Status { get; set; } }
      
      





country=sheldonopolis&status=pending



にシリアル化する必要があり、受信時にクエリ文字列をPresentRequestQuery



インスタンスに逆シリアル化する必要があり、 Get



メソッドは次のようになります。

 public List<PresentRequest> Get(PresentRequestQuery query) { throw new NotImplementedException(); }
      
      





クエリと同じ数のGetメソッドを作成する必要があります。 以下は、 WCFのRESTful Webサービスの設計および構築ガイドからのサンプルコードです

BookmarkService
 [ServiceContract] public partial class BookmarkService { [WebGet(UriTemplate = "?tag={tag}")] [OperationContract] Bookmarks GetPublicBookmarks(string tag) {...} [WebGet(UriTemplate = "{username}?tag={tag}")] [OperationContract] Bookmarks GetUserPublicBookmarks(string username, string tag) {...} [WebGet(UriTemplate = "users/{username}/bookmarks?tag={tag}")] [OperationContract] Bookmarks GetUserBookmarks(string username, string tag) {...} [WebGet(UriTemplate = "users/{username}/profile")] [OperationContract] UserProfile GetUserProfile(string username) {...} [WebGet(UriTemplate = "users/{username}")] [OperationContract] User GetUser(string username) {...} [WebGet(UriTemplate = "users/{username}/bookmarks/{bookmark_id}")] [OperationContract] Bookmark GetBookmark(string username, string bookmark_id) {...} ... }
      
      





WCFがクエリ文字列のシリアル化、つまりクエリ文字列からオブジェクトを作成することをサポートしていない理由がわかりません。 この簡単なトリックは、より安定したメソッドシグネチャを作成するのに役立ちます。 一方、Getメソッドにはこのような署名が含まれている場合があります。 そのため、メソッドの種類は再利用可能で多態的です。

 Message Get (Message request);
      
      





Get



操作の短所




WCF SOAPサービスにはポリモーフィズムがあり、より正確にはKnownTypeAttribute



を介して実装された特別なポリモーフィズム( アドホックポリモーフィズム )があることに注意してください。



おわりに



RESTfulフレームワークとしてのWCFには、再利用可能で安定したサービスの作成を困難にするいくつかのアーキテクチャ機能があります。 一方、WCFにはこれらの問題を解決するために必要なものがすべて揃っています。



WCF上のRESTful Webサービス:改善されたデザイン



まず、 Get



メソッドの欠点を修正しましょう。 シリアル化メッセージングのアプローチが役立つと思います。



URLのシリアル化と逆シリアル化



PresentRequestQuery



クラスはすでに見ましたが、今度はそれをシリアライズしましょう。

 public class PresentRequestQuery { public string Country { get; set; } public string Status { get; set; } }
      
      





知っているように、 Get



はパラメーターをクエリ文字列として送信するため、シリアル化メソッドは有効なクエリ文字列を作成する必要があります。 シリアル化から得られる理想的なクエリ文字列は次のようになりますcountry=sheldonopolis&status=pending



と似たようなものを作成したいと思います。 理想的なシリアル化の結果には1つの欠点があります。パラメーター間の通信がないため、URLを要求オブジェクトに逆シリアル化できません。 シリアル化メカニズムは、この問題も解決するはずです。



一般的に、クエリ文字列は異なるキーと値のペアのコレクションです: key1=value1&key2=value2&key3=value3





この場合、2つのキーがあります。

次のシリアル化アルゴリズムが表示されます。

  1. 要求タイプを定義する
  2. JSONでリクエストオブジェクトをシリアル化する
  3. JSONのエンコード


結果のクエリ文字列はマスクと一致する必要があります: type={request type}&data={request data}





要求オブジェクトのインスタンスは次のとおりです。

 var query = new PresentRequestQuery { Country = "sheldonopolis", Status = "pending" };
      
      





結果のクエリ文字列: type=PresentRequestQuery&data=%7B%22Country%22%3A%22sheldonopolis%22%2C%22Status%22%3A%22pending%22%7D





このクエリ文字列は、 PresentRequestQuery



インスタンスに簡単に逆シリアル化できます。 実装は非常に簡単です。

CreateQueryParams <T>(T値)
 private static NameValueCollection CreateQueryParams<T>(T value) { string data = JsonDataSerializer.ToString(value); var result = new NameValueCollection { { RestServiceMetadata.ParamName.Type, UrlEncode(typeof(T).Name) }, { RestServiceMetadata.ParamName.Data, UrlEncode(data) } }; return result; }
      
      



UrlEncode



UrlEncode



のみを呼び出し、 JsonDataContractSerializer



DataContractJsonSerializer



インスタンスです。

ToString <T>(T値)
 public static string ToString<T>(T value) { using (var stream = new MemoryStream()) { var serializer = new DataContractJsonSerializer(typeof(T)); serializer.WriteObject(stream, value); return Encoding.UTF8.GetString(stream.ToArray()); } }
      
      





これで、次のステップに進む準備ができました。 メッセージベースのアプローチを使用します 。 SOAPサービスでは、このコントラクトを使用しました。
ISoapService
SeriviceContract





 [ServiceContract] public interface ISoapService { [OperationContract(Action = ServiceMetadata.Action.Process)] void Process(Message message); [OperationContract(Action = ServiceMetadata.Action.ProcessWithResponse, ReplyAction = ServiceMetadata.Action.ProcessResponse)] Message ProcessWithResponse(Message message); }
      
      





RESTfulスタイルには、 Get, Post, Put, Delete



、およびServiceContract



少なくとも4つのメソッドが必要です。

IJsonService
 [ServiceContract] public interface IJsonService { [OperationContract] [WebInvoke(Method = OperationType.Delete, UriTemplate = RestServiceMetadata.Path.Delete, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)] void Delete(Message message); [OperationContract] [WebInvoke(Method = OperationType.Delete, UriTemplate = RestServiceMetadata.Path.DeleteWithResponse, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)] Message DeleteWithResponse(Message message); [OperationContract] [WebGet(UriTemplate = RestServiceMetadata.Path.Get, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)] void Get(Message message); [OperationContract] [WebGet(UriTemplate = RestServiceMetadata.Path.GetWithResponse, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)] Message GetWithResponse(Message message); [OperationContract] [WebInvoke(Method = OperationType.Post, UriTemplate = RestServiceMetadata.Path.Post, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)] void Post(Message message); [OperationContract] [WebInvoke(Method = OperationType.Post, UriTemplate = RestServiceMetadata.Path.PostWithResponse, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)] Message PostWithResponse(Message message); [OperationContract] [WebInvoke(Method = OperationType.Put, UriTemplate = RestServiceMetadata.Path.Put, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)] void Put(Message message); [OperationContract] [WebInvoke(Method = OperationType.Put, UriTemplate = RestServiceMetadata.Path.PutWithResponse, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)] Message PutWithResponse(Message message); }
      
      





IJsonService



は、柔軟性、安定性、およびメンテナンスの容易さがあります。 サービスはWCF( MSDN )の基本であるMessage



クラスのみに依存するため、任意のデータを転送できます。 もう1つの利点はCRUDです。 IJsonServiceとURLシリアル化を使用して、 パラメトリック多態性を持つ再利用可能なRESTfulサービスを作成できます。



RESTfulサービスの実装



ここではすべてのコードを提供しません。なぜなら、 それはすでに以前に引用されてます。 以下は、リクエストを作成、更新、受信、削除する方法の例です。

ClientProcessor
 public sealed class ClientProcessor : IPostWithResponse<CreateClientRequest>, IGetWithResponse<GetClientRequest>, IDelete<DeleteClientRequest>, IPutWithResponse<UpdateClientRequest> { private static List<Client> _clients = new List<Client>(); public void Delete(DeleteClientRequest request) { _clients = _clients.Where(x => x.Id != request.Id).ToList(); } public object GetWithResponse(GetClientRequest request) { Client client = _clients.Single(x => x.Id == request.Id); return new ClientResponse { Id = client.Id, Email = client.Email }; } public object PostWithResponse(CreateClientRequest request) { var client = new Client { Id = Guid.NewGuid(), Email = request.Email }; _clients.Add(client); return new ClientResponse { Id = client.Id, Email = client.Email }; } public object PutWithResponse(UpdateClientRequest request) { Client client = _clients.Single(x => x.Id == request.Id); client.Email = request.Email; return new ClientResponse { Id = client.Id, Email = client.Email }; } }
      
      





次のインターフェイスはCRUD操作を表します。

画像

ここで、要求を適切なCRUD操作に関連付ける必要があります。

ServiceProcessor
 public abstract class ServiceProcessor { internal static readonly RequestMetadataMap _requests = new RequestMetadataMap(); protected static readonly Configuration _configuration = new Configuration(); private static readonly RequestProcessorMap _requestProcessors = new RequestProcessorMap(); protected static void Process(RequestMetadata requestMetaData) { IRequestProcessor processor = _requestProcessors.Get(requestMetaData.Type); processor.Process(requestMetaData); } protected static Message ProcessWithResponse(RequestMetadata requestMetaData) { IRequestProcessor processor = _requestProcessors.Get(requestMetaData.Type); return processor.ProcessWithResponse(requestMetaData); } protected sealed class Configuration : IConfiguration { public void Bind<TRequest, TProcessor>(Func<TProcessor> creator) where TRequest : class where TProcessor : IRequestOperation { if (creator == null) { throw new ArgumentNullException("creator"); } _requestProcessors.Add<TRequest, TProcessor>(creator); _requests.Add<TRequest>(); } public void Bind<TRequest, TProcessor>() where TRequest : class where TProcessor : IRequestOperation, new() { Bind<TRequest, TProcessor>(() => new TProcessor()); } } }
      
      





特定のServiceProcessor



は、構成メソッドと処理メソッドのみがあります。

RestServiceProcessor
 public sealed class RestServiceProcessor : ServiceProcessor { private RestServiceProcessor() { } public static IConfiguration Configure(Action<IConfiguration> action) { action(_configuration); return _configuration; } public static void Process(Message message) { RequestMetadata metadata = _requests.FromRestMessage(message); Process(metadata); } public static Message ProcessWithResponse(Message message) { RequestMetadata metadata = _requests.FromRestMessage(message); return ProcessWithResponse(metadata); } }
      
      





RequestMetadataMap



Message



インスタンスから特定のリクエストを作成するために必要なリクエストタイプを保存するために使用されます。

RequestMetadataMap
 internal sealed class RequestMetadataMap { private readonly Dictionary<string, Type> _requestTypes = new Dictionary<string, Type>(); internal void Add<TRequest>() where TRequest : class { Type requestType = typeof(TRequest); _requestTypes[requestType.Name] = requestType; } internal RequestMetadata FromRestMessage(Message message) { UriTemplateMatch templateMatch = WebOperationContext.Current.IncomingRequest.UriTemplateMatch; NameValueCollection queryParams = templateMatch.QueryParameters; string typeName = UrlSerializer.FromQueryParams(queryParams).GetTypeValue(); Type targetType = GetRequestType(typeName); return RequestMetadata.FromRestMessage(message, targetType); } internal RequestMetadata FromSoapMessage(Message message) { string typeName = SoapContentTypeHeader.ReadHeader(message); Type targetType = GetRequestType(typeName); return RequestMetadata.FromSoapMessage(message, targetType); } private Type GetRequestType(string typeName) { Type result; if (_requestTypes.TryGetValue(typeName, out result)) { return result; } string errorMessage = string.Format( "Binding on {0} is absent. Use the Bind method on an appropriate ServiceProcessor", typeName); throw new InvalidOperationException(errorMessage); } }
      
      





IJsonService



の再利用可能な実装をIJsonService





JsonServicePerCall
 [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)] public sealed class JsonServicePerCall : IJsonService { public void Delete(Message message) { RestServiceProcessor.Process(message); } public Message DeleteWithResponse(Message message) { return RestServiceProcessor.ProcessWithResponse(message); } public void Get(Message message) { RestServiceProcessor.Process(message); } public Message GetWithResponse(Message message) { return RestServiceProcessor.ProcessWithResponse(message); } public void Post(Message message) { RestServiceProcessor.Process(message); } public Message PostWithResponse(Message message) { return RestServiceProcessor.ProcessWithResponse(message); } public void Put(Message message) { RestServiceProcessor.Process(message); } public Message PutWithResponse(Message message) { return RestServiceProcessor.ProcessWithResponse(message); } }
      
      





ご覧のとおり、RESTfulに完全に従って、必要なものを送信できます。

最も興味深いことは、URLから特定のリクエストを作成するのに役立つクラスであるRestRequestMetadata



で発生します。 RestRequestMetadata



の実装を見る前に、いくつかの説明をしたいと思います。 RestRequestMetadata



WebOperationContext



を使用してクエリ文字列を取得し、特定のクエリを作成します。 また、要求に基づいて応答メッセージを作成することもできます。

RestRequestMetadata
 internal sealed class RestRequestMetadata : RequestMetadata { private readonly object _request; private readonly WebOperationContext _webOperationContext; internal RestRequestMetadata(Message message, Type targetType) : base(targetType) { _webOperationContext = WebOperationContext.Current; OperationType = GetOperationType(message); _request = CreateRequest(message, targetType); } public override string OperationType { get; protected set; } public override Message CreateResponse(object response) { var serializer = new DataContractJsonSerializer(response.GetType()); return _webOperationContext.CreateJsonResponse(response, serializer); } public override TRequest GetRequest<TRequest>() { return (TRequest)_request; } private static object CreateRequestFromContent(Message message, Type targetType) { using (var stream = new MemoryStream()) { XmlDictionaryWriter writer = JsonReaderWriterFactory.CreateJsonWriter(stream); message.WriteMessage(writer); writer.Flush(); var serializer = new DataContractJsonSerializer(targetType); stream.Position = 0; return serializer.ReadObject(stream); } } private static string GetOperationType(Message message) { var httpReq = (HttpRequestMessageProperty)message.Properties[HttpRequestMessageProperty.Name]; return httpReq.Method; } private object CraeteRequestFromUrl(Type targetType) { UriTemplateMatch templateMatch = _webOperationContext.IncomingRequest.UriTemplateMatch; NameValueCollection queryParams = templateMatch.QueryParameters; return UrlSerializer.FromQueryParams(queryParams).GetRequestValue(targetType); } private object CreateRequest(Message message, Type targetType) { if (IsRequestByUrl()) { return CraeteRequestFromUrl(targetType); } return CreateRequestFromContent(message, targetType); } private bool IsRequestByUrl() { return OperationType == Operations.OperationType.Get || OperationType == Operations.OperationType.Delete; } }
      
      





特定のリクエストはすべて、RequestProcessorクラスによって処理されます。

RequestProcessor <TRequest、TProcessor>
 internal sealed class RequestProcessor<TRequest, TProcessor> : IRequestProcessor where TRequest : class where TProcessor : IRequestOperation { private readonly Func<TProcessor> _creator; public RequestProcessor(Func<TProcessor> creator) { _creator = creator; } public void Process(RequestMetadata metadata) { switch (metadata.OperationType) { case OperationType.Get: Get(metadata); break; case OperationType.Post: Post(metadata); break; case OperationType.Put: Put(metadata); break; case OperationType.Delete: Delete(metadata); break; default: string message = string.Format("Invalid operation type: {0}", metadata.OperationType); throw new InvalidOperationException(message); } } public Message ProcessWithResponse(RequestMetadata metadata) { switch (metadata.OperationType) { case OperationType.Get: return GetWithResponse(metadata); case OperationType.Post: return PostWithResponse(metadata); case OperationType.Put: return PutWithResponse(metadata); case OperationType.Delete: return DeleteWithResponse(metadata); default: string message = string.Format("Invalid operation type: {0}", metadata.OperationType); throw new InvalidOperationException(message); } } private void Delete(RequestMetadata metadata) { var service = (IDelete<TRequest>)_creator(); var request = metadata.GetRequest<TRequest>(); service.Delete(request); } private Message DeleteWithResponse(RequestMetadata metadata) { var service = (IDeleteWithResponse<TRequest>)_creator(); var request = metadata.GetRequest<TRequest>(); object result = service.DeleteWithResponse(request); return metadata.CreateResponse(result); } private void Get(RequestMetadata metadata) { var service = (IGet<TRequest>)_creator(); var request = metadata.GetRequest<TRequest>(); service.Get(request); } private Message GetWithResponse(RequestMetadata metadata) { var service = (IGetWithResponse<TRequest>)_creator(); var request = metadata.GetRequest<TRequest>(); object result = service.GetWithResponse(request); return metadata.CreateResponse(result); } private void Post(RequestMetadata metadata) { var service = (IPost<TRequest>)_creator(); var request = metadata.GetRequest<TRequest>(); service.Post(request); } private Message PostWithResponse(RequestMetadata metadata) { var service = (IPostWithResponse<TRequest>)_creator(); var request = metadata.GetRequest<TRequest>(); object result = service.PostWithResponse(request); return metadata.CreateResponse(result); } private void Put(RequestMetadata metadata) { var service = (IPut<TRequest>)_creator(); var request = metadata.GetRequest<TRequest>(); service.Put(request); } private Message PutWithResponse(RequestMetadata metadata) { var service = (IPutWithResponse<TRequest>)_creator(); var request = metadata.GetRequest<TRequest>(); object result = service.PutWithResponse(request); return metadata.CreateResponse(result); } }
      
      







RESTfulサービスクライアント



クライアントは非常にシンプルで、データをクエリ文字列にシリアル化し、サービスに送信するだけです。 クライアントはHttpClientに基づいています 。 クライアントメソッドは次のとおりです。

顧客の方法
 public void Delete<TRequest>(TRequest request) where TRequest : class public TResponse Delete<TRequest, TResponse>(TRequest request) where TRequest : class public Task DeleteAsync<TRequest>(TRequest request) where TRequest : class public Task<TResponse> DeleteAsync<TRequest, TResponse>(TRequest request) where TRequest : class public void Get<TRequest>(TRequest request) where TRequest : class public TResponse Get<TRequest, TResponse>(TRequest request) where TRequest : class public Task GetAsync<TRequest>(TRequest request) where TRequest : class public Task<TResponse> GetAsync<TRequest, TResponse>(TRequest request) where TRequest : class public void Post<TRequest>(TRequest request) where TRequest : class public TResponse Post<TRequest, TResponse>(TRequest request) where TRequest : class public Task<TResponse> PostAsync<TRequest, TResponse>(TRequest request) where TRequest : class public Task PostAsync<TRequest>(TRequest request) where TRequest : class public void Put<TRequest>(TRequest request) where TRequest : class public TResponse Put<TRequest, TResponse>(TRequest request) where TRequest : class public Task PutAsync<TRequest>(TRequest request) where TRequest : class public Task<TResponse> PutAsync<TRequest, TResponse>(TRequest request) where TRequest : class
      
      







サンタをメッセージベースのサービスであるRESTfulの幸せな所有者にしましょう。



RESTfulサービスの例



サンタは、フィルターを使用してギフトリクエストを保存および検索できるRESTfulサービスを期待しています。

サービス



構成ファイルが最も一般的です。



構成
 <?xml version="1.0" encoding="utf-8"?> <configuration> <system.serviceModel> <services> <service name="Nelibur.ServiceModel.Services.JsonServicePerCall"> <host> <baseAddresses> <add baseAddress="http://localhost:9090/requests" /> </baseAddresses> </host> <endpoint binding="webHttpBinding" contract="Nelibur.ServiceModel.Contracts.IJsonService" /> </service> </services> </system.serviceModel> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" /> </startup> </configuration>
      
      





JsonServicePerCall



IJsonService



はすでに上記で言及されています。



以下は、バインディングおよびその他の設定です。 バインディングは、 PresentRequestProcessor



PresentRequest



PresentRequestQuery



を処理すると言います。

スナップ設定
 private static void Main() { RestServiceProcessor.Configure(x => { x.Bind<PresentRequest, PresentRequestProcessor>(); x.Bind<PresentRequestQuery, PresentRequestProcessor>(); x.Bind<UpdatePresentRequestStatus, PresentRequestProcessor>(); x.Bind<DeletePresentRequestsByStatus, PresentRequestProcessor>(); }); using (var serviceHost = new WebServiceHost(typeof(JsonServicePerCall))) { serviceHost.Open(); Console.WriteLine("Santa Clause Service has started"); Console.ReadKey(); serviceHost.Close(); } }
      
      





最後に、 PresentRequestProcessor



は、ギフトリクエストの取得、投稿、書き込み、削除の方法を示します。

PresentRequestProcessor
 public sealed class PresentRequestProcessor : IPost<PresentRequest>, IPost<UpdatePresentRequestStatus>, IGetWithResponse<PresentRequestQuery>, IDelete<DeletePresentRequestsByStatus> { private static List<PresentRequest> _requests = new List<PresentRequest>(); public void Delete(DeletePresentRequestsByStatus request) { var status = (PresentRequestStatus)Enum.Parse(typeof(PresentRequestStatus), request.Status); _requests = _requests.Where(x => x.Status != status).ToList(); Console.WriteLine("Request list was updated, current count: {0}", _requests.Count); } public object GetWithResponse(PresentRequestQuery request) { Console.WriteLine("Get Present Requests by: {0}", request); var status = (PresentRequestStatus)Enum.Parse(typeof(PresentRequestStatus), request.Status); return _requests.Where(x => x.Status == status) .Where(x => x.Address.Country == request.Country) .ToList(); } public void Post(PresentRequest request) { request.Status = PresentRequestStatus.Pending; _requests.Add(request); Console.WriteLine("Request was added, Id: {0}", request.Id); } public void Post(UpdatePresentRequestStatus request) { Console.WriteLine("Update requests on status: {0}", request.Status); var status = (PresentRequestStatus)Enum.Parse(typeof(PresentRequestStatus), request.Status); _requests.ForEach(x => x.Status = status); } }
      
      







お客様



顧客コードの自己文書化:

お客様
 private static void Main() { var client = new JsonServiceClient("http://localhost:9090/requests"); var presentRequest = new PresentRequest { Id = Guid.NewGuid(), Address = new Address { Country = "sheldonopolis", }, Wish = "Could you please help developers to understand, " + "WCF is awesome only with Nelibur" }; client.Post(presentRequest); var requestQuery = new PresentRequestQuery { Country = "sheldonopolis", Status = PresentRequestStatus.Pending.ToString() }; List<PresentRequest> pendingRequests = client.Get<PresentRequestQuery, List<PresentRequest>>(requestQuery); Console.WriteLine("Pending present requests count: {0}", pendingRequests.Count); var updatePresentRequestStatus = new UpdatePresentRequestStatus { Status = PresentRequestStatus.Accepted.ToString() }; client.Post(updatePresentRequestStatus); var deleteByStatus = new DeletePresentRequestsByStatus { Status = PresentRequestStatus.Accepted.ToString() }; client.Delete(deleteByStatus); Console.WriteLine("Press any key for Exit"); Console.ReadKey(); }
      
      





実行結果:Fiddlerスクリーンショット

画像



終わり



メッセージベースのアプローチは、非常に強力なアーキテクチャスタイルです。安定した保守可能なインターフェースを備えたRESTfulサービスの作成に役立ちます。もちろん、サンタ自身もクリスマスプレゼントなどのRESTfulサービスを喜んで受け取ります。



ソースは、元の記事またはプロジェクトのWebサイトからダウンロードできますNugetパッケージ

利用できます



興味深い関連記事:メッセージベースのWebサービスの利点



All Articles