はじめに
WCFでSOAPメッセージベースのWebサービスを作成する方法についてはすでに書いています 。 そして今、WCFでのRESTfulメッセージベースのWebサービスの設計と構築についてお話したいと思います。 この記事を理解するには、 RESTの基本知識と、WCFでRESTful Webサービスを作成する方法が必要です。 RESTful Webサービスに慣れるには、「 WCF 3.5でRESTful Webサービスを設計および構築するためのガイド」をご覧ください。
この記事では、RESTfulな設計の問題を発見して解決しようとします。 以下のRESTful Webサービスを構築する方法を学習します。
- 安定したユニバーサルインターフェイスを備えています。
- DTOパターンに従ってデータを送信します。
サンタクロースのWCF Webサービスを設計しましょう。 サンタは、RESTアーキテクチャスタイルが大好きであり、 Open Data Protocol(OData)が嫌いなので、次の要件を提示しました。
- サービスにはRESTful APIが必要です
- サービスには次の機能が必要です。
- ギフトリクエストを保存しています。
- ギフトリクエストを更新します。
- ステータスおよび国別のギフトリクエストの受信。
- IDによるギフトリクエストを削除します。
コアビジネスオブジェクトの定義
私たちの目標は、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] public interface IPresentRequestService { [WebInvoke(Method = "POST", UriTemplate = "requests")] [OperationContract] void Save(PresentRequest request); }
注:ServiceContractはサービスの主要部分であり、安定性と柔軟性が必要です。 すべての顧客はServiceContractに依存しているため、契約の変更には非常に注意する必要があります。
Saveメソッドには、長所と短所の両方があります。
長所 :
- メソッドは抽象なので、
PresentRequest
フィールドを簡単に追加できます。 - 要求は、URLパラメーターとしてではなく、オブジェクトとして送信されます
ほとんどの開発者は、 Mythical Man-Monthという本から、ソフトウェアの最初のバージョンが破棄されることを知っています。 同じことがServiceContractにも当てはまるので、可能な限り柔軟にする必要があります。
短所 :
- 異なる
PresentRequest
オブジェクトと同数のSave
メソッドが必要です。 しかし、 OOPはどうですか?
KnownTypeAttributeについては知っていますが、逆シリアル化プロセスのためだけに役に立たないクラス階層を作成する必要があります。
Create
、
Update
および
Delete
操作には、同様の長所と短所があります。 操作Get-異なり、表示されます、私見、維持するのが最も難しい方法。
PresentRequestsを取得する
Get操作の場合、パラメーターはクエリ文字列で送信されます 。 私たちの場合、ステータスと国で
PresentRequest
を取得するには、次のようなものを作成する必要があります
[WebGet(UriTemplate = "requests?country={country}&status={status}")] [OperationContract] List<PresentRequest> Get(string country, string status);
長所 :
- 読み取り可能なURL(例: SantaClaus.org/requests?country=sheldonopolis&status=pending) 。
欠陥をリストする前に、
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
操作の短所 :
- 方法を理解するのは難しい
- 必要な
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つのキーがあります。
- リクエストタイプ
- リクエストデータ、オブジェクトフィールド
次のシリアル化アルゴリズムが表示されます。
- 要求タイプを定義する
- JSONでリクエストオブジェクトをシリアル化する
- 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操作を表します。
![画像](https://habrastorage.org/getpro/habr/post_images/9c2/df1/289/9c2df128928166f4efee9d2abec3b120.jpg)
ここで、要求を適切な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スクリーンショット:
![画像](https://habrastorage.org/getpro/habr/post_images/53f/ebf/986/53febf9862733ac39b5db8b009251171.png)
終わり
メッセージベースのアプローチは、非常に強力なアーキテクチャスタイルです。安定した保守可能なインターフェースを備えたRESTfulサービスの作成に役立ちます。もちろん、サンタ自身もクリスマスプレゼントなどのRESTfulサービスを喜んで受け取ります。
ソースは、元の記事またはプロジェクトのWebサイトからダウンロードできます。Nugetパッケージ
も利用できます。
興味深い関連記事:メッセージベースのWebサービスの利点。