📜 ⬆️ ⬇️

Building RESTful Message Based Web Services on WCF

Introduction


I already wrote about how to make a SOAP Message Based web service on WCF . And now I want to talk about the design and construction of RESTful Message Based web services on WCF. Understanding this article requires basic knowledge of REST and how to create RESTful web services on WCF. To familiarize yourself with RESTful web services you can find: A Guide to Designing and Building RESTful Web Services with WCF 3.5 .

In the article I will try to uncover and solve the problems of RESTful design. You will learn how to build a RESTful web service that:


Let's design a WCF web service for Santa Claus. Santa loves the architectural style of REST and does not like the Open Data Protocol (OData) at all , so he put forward the following requirements:

Identify key business objects


Our goal is to design a RESTful web service, so let's keep business objects as simple as possible.

Consider the class Request for a gift (hereinafter PresentRequest). PresentRequest is an aggregate and contains all the necessary information about desire.
PresentRequest
public class PresentRequest { public Address Address { get; set; } public Guid Id { get; set; } public PresentRequestStatus Status { get; set; } public string Wish { get; set; } } 

Address
 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 } 

Now we have everything you need to start.
')

RESTful WCF Web Service: The Problem of Design


In this step, we define the web service interface. Let's start with the Save method.

Preservation PresentRequest

The simplest implementation will look like this:
 public void Save(PresentRequest request) 

The client fills in all the fields and sends a request for a web service. The Save method returns void , since we know that the service will be heavily loaded, so the generation of a unique Id falls on the client’s shoulders.

In accordance with the RESTful design style, we must decorate the Save method with the WebInvoke attribute and specify the appropriate HTTP method. Here is a small cheat sheet for HTTP methods:
Operation
HTTP
Create
PUT / POST
Read
Get
Update
PUT / PATCH
Delete
DELETE
As a result, we get the following ServiceContract :
 [ServiceContract] public interface IPresentRequestService { [WebInvoke(Method = "POST", UriTemplate = "requests")] [OperationContract] void Save(PresentRequest request); } 

Note: ServiceContract is the main part of the service, which should have stability and flexibility. All customers are dependent on ServiceContract, so we must be very careful with any changes in the contract.

The Save method has both advantages and disadvantages.
Pros :

Most developers know from the book Mythical Man-Month that the first version of the software will be thrown away. The same applies to ServiceContract , so we must try to make it as flexible as possible.
Cons :

I know about KnownTypeAttribute , but we will have to create a useless class hierarchy only for the deserialization process.

The Create , Update and Delete operations have similar pros and cons. The Get operation is different and appears, in my opinion, the most difficult to follow method.

Getting PresentRequests

For the Get operation, the parameters are sent in the query string . In our case, to get the PresentRequest by status and country, we need to create something like
 [WebGet(UriTemplate = "requests?country={country}&status={status}")] [OperationContract] List<PresentRequest> Get(string country, string status); 

Pros :

Before listing the deficiencies, let's take a look at the Get method. Imagine that we use this method inside our application, without WCF.
 public interface IPresentRequestService { List<PresentRequest> Get(string country, string status); } 

One of the biggest problems with this method is the signature. We will need to update the service implementation after any changes in the method signature. This method is fragile and has a smell. Thus, a RESTful Get operation is difficult to maintain by default.
Here is a better solution, we can change the request without changing the interface:
 public interface IPresentRequestService { List<PresentRequest> Get(PresentRequestQuery query); } 

All the necessary query data contains the PresentRequestQuery class:
 public class PresentRequestQuery { public string Country { get; set; } public string Status { get; set; } } 

Cons :
As mentioned above, the Get method has a fragile signature, so extending the functionality without breaking changes is really difficult. The parameters of a Get operation are sent as a query string with simple fields that are also represented in the signature of the Get method. There is no connectivity between the parameters, since WCF does not create a request object based on parameters.
Let's take a look at an example: SantaClaus.org/requests?country=sheldonopolis&status=pending URL to get PresentReuqest s by country and status.
Here is the corresponding method in the WCF service:
 public List<PresentRequest> Get(string country, string status) { throw new NotImplementedException(); } 

According to the method signature, there is no connection between country and status. In fact, we do not know what country and status means, we can only guess. In my opinion, WCF should be able to create a request date based on the request object (serialize), and also create a request object based on the request string (deserialization). Thus, to send the following request object:
 public class PresentRequestQuery { public string Country { get; set; } public string Status { get; set; } } 

must be serialized to country=sheldonopolis&status=pending , and after receiving the query string must be deserialized into the PresentRequestQuery instance and the Get method should look like this:
 public List<PresentRequest> Get(PresentRequestQuery query) { throw new NotImplementedException(); } 

We have to create as many Get methods as we have requests. Here is a sample code from WCF's Guide to Designing and Building RESTful Web Services :
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) {...} ... } 

I do not understand why WCF does not support the serialization of the query string, that is, the creation of an object from the query string. This simple trick could help create a more stable method signature. On the other hand, a Get method can have such a signature. So the kind of method is reusable and polymorphic .
 Message Get (Message request); 

Cons of Get :

Please keep in mind that WCF SOAP service has polymorphism, or rather has a special polymorphism ( ad hoc polymorphism ), implemented through KnownTypeAttribute , but, in my opinion, WCF must support parametric polymorphism .

Conclusion


WCF as a RESTful framework has several architectural features that make it difficult to create reusable and stable services. On the other hand, WCF has everything you need to solve these problems.

RESTful Web Service on WCF: improved design


First of all, let's eliminate the disadvantages of the Get method. I think an approach based on serialization messages can help us.

URL serialization and deserialization


We have already seen the PresentRequestQuery class, but now let's serialize it.
 public class PresentRequestQuery { public string Country { get; set; } public string Status { get; set; } } 

As we know, Get sends the parameters as a query string, so our serialization method should create a valid query string. The ideal query string resulting from serialization should look like this: country=sheldonopolis&status=pending and we want to create something similar. The ideal result of serialization has one drawback: the lack of a connection between the parameters, so we cannot deserialize the URL into the request object. Our serialization mechanism should also solve this problem.

Generally speaking, a query string is a collection of different key-value pairs: key1=value1&key2=value2&key3=value3 .
In our case, we have two keys:
I see the following serialization algorithm:
  1. Determine the type of request
  2. Serialize request object in JSON
  3. Encode json

The resulting query string must match the mask: type={request type}&data={request data}
Here is an instance of the request object:
 var query = new PresentRequestQuery { Country = "sheldonopolis", Status = "pending" }; 

Result query string: type=PresentRequestQuery&data=%7B%22Country%22%3A%22sheldonopolis%22%2C%22Status%22%3A%22pending%22%7D
This query string can be easily deserialized into a PresentRequestQuery instance. The implementation is very simple:
CreateQueryParams <T> (T value)
 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; } 
where UrlEncode calls only Uri.EscapeDataString and JsonDataContractSerializer is an instance of the DataContractJsonSerializer .
ToString <T> (T value)
 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()); } } 

Now we are ready for the next step — using a message-based approach . For the SOAP service we used this contract:
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); } 

The RESTful style requires at least four methods: Get, Post, Put, Delete and ServiceContract can be like this:
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 has the flexibility, stability and ease of maintenance. We can transfer any data, because the service depends only on the Message class, which is fundamental for WCF ( MSDN ). Another advantage is CRUD. Using IJsonService and URL serialization, we can create reusable RESTful services with parametric polymorphism .

Implementing a RESTful service


I will not give all the code here, because He already cited previously . The following is an example of how to Create, Update, Receive and Delete requests.
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 }; } } 

The following interfaces represent CRUD operations:
image
Now we need to associate requests with suitable CRUD operations.
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()); } } } 

A specific ServiceProcessor has only configuration and processing methods.
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 used to store the types of requests that are needed to create specific requests from Message instances.
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); } } 

Look at the reusable implementation of the 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); } } 

As you can see, you can send anything and everything according to RESTful.
The most interesting thing happens in RestRequestMetadata , a class that helps create a specific request from a URL. Before looking at the implementation of RestRequestMetadata , I want to give some explanations. RestRequestMetadata uses WebOperationContext to get the query string and create a specific query. It can also create a response message based on the request.
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; } } 

All specific requests are processed by the RequestProcessor class.
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 service client


The client is quite simple, just serializes the data into the query string and sends it to the service. Client is based on HttpClient . The following are client methods:
Client methods
 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 


And now let's make Santa happy owner of a RESTful service based on messages.

RESTful service example


Santa is still waiting for a RESTful service that can save and search for requests for gifts by filter.

Service


The most common configuration file:

Configuration
 <?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 and IJsonService have already been mentioned above.

Below is the binding and other settings. Binding says that the PresentRequestProcessor will handle the PresentRequest and the PresentRequestQuery .
Binding Setup
 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(); } } 

Finally, PresentRequestProcessor shows Get, Post, Put and Delete requests for gifts:
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); } } 


Customer


Customer code self-documenting:
Customer
 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(); } 

Results of execution: screenshot of the Fiddler program :
image

the end


The message-based approach is a mega powerful architectural style. He can help create a RESTful service with a stable, maintainable interface, and of course Santa himself will be pleased to receive just such a RESTful service as a Christmas present.

Sources can be downloaded from the original article or from the project site . Nuget package is
also available .

An interesting article on the topic: Advantages of message based web services .

Source: https://habr.com/ru/post/218149/


All Articles