Building RESTful Message Based Web Services on WCF

Original author: Sergey Morenko
  • Transfer
  • Tutorial

Introduction


I already wrote about how to make a SOAP Message Based web service on WCF . And now I want to talk about designing and building 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 check out: A Guide to Designing and Building RESTful Web Services with WCF 3.5 .

In this article I will try to reveal and solve RESTful design problems. You will learn how to build a RESTful web service that:
  • It has a stable and universal interface.
  • Transmits data according to the DTO pattern .


Let's design WCF web service for Santa Claus. Santa is very fond of the REST architectural style and completely dislikes Open Data Protocol (OData) , so he put forward the following requirements:
  • The service must have a RESTful API
  • The service should have the following functionality:
    • Saving a gift request.
    • Update gift request.
    • Receiving a gift request by Status and Country.
    • Delete gift request by Id.

Definition of core business objects


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

Consider the Gift Request class (hereinafter PresentRequest). PresentRequest is an aggregate and contains all the necessary information about the 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 we need to start.

RESTful web service on WCF: design problem


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

Saving PresentRequest

A simple implementation would look like this:
public void Save(PresentRequest request) 

The client fills in all the fields and sends a request to the web service. The method Savereturns voidsince We know that the service will be highly loaded, so the generation of a unique one Id falls on the shoulders of the client.

In accordance with the RESTful design style, we must decorate the method with an Saveattribute WebInvokeand specify a suitable HTTP method. Here is a small cheat sheet on 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 must be stable and flexible. All customers depend on ServiceContract, so we must be very careful with any changes to the contract.

The Save method has both pros and cons.
Pros :
  • The method is abstract, so we can easily add fields to PresentRequest
  • The request is sent as an object, not as URL parameters

Most developers know from the book “Mythical Man-Month” that the first version of the software will be thrown out. The same applies to ServiceContract , so we should try to make it as flexible as possible.
Cons :
  • We should have as many methods Saveas PresentRequest we will have different descendant objects . But what about OOP ?

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

Operations Create, Update and Delete have similar pros and cons. The Get operation is different and is, IMHO, the most difficult to maintain method.

Getting PresentRequests

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

Pros :

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

One of the biggest problems with this method is the signature. We will have to update the service implementation after any changes in the method signature. This method is fragile and has a smell. Thus, Geta RESTful-style 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 Get(PresentRequestQuery query);
}

The class contains all the necessary request data PresentRequestQuery:
public class PresentRequestQuery
{
    public string Country { get; set; }
    public string Status { get; set; }
} 

Cons :
As mentioned above, the method Gethas a fragile signature, so expanding functionality without breaking changes is really difficult. The parameters of the Get operation are sent as a query string with simple fields, which are also represented in the method signature Get. There is no connection between the parameters, because WCF does not create a query object based on parameters.
Let's take a look at an example: URL SantaClaus.org/requests?country=sheldonopolis&status=pending to get PresentReuqests by country and status.
Here is the corresponding method in the WCF service:
public List 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 countryand means status, we can only speculate. In my opinion, WCF should be able to create a query deadline based on a query object (serialize), and also create a query object based on a query string (deserialization). Thus, to send the following request object:
public class PresentRequestQuery
{
    public string Country { get; set; }
    public string Status { get; set; }
}

should be serialized in country=sheldonopolis&status=pending, and upon receipt, the query string should be deserialized into an instance PresentRequestQueryand the method Getshould look like this:
public List Get(PresentRequestQuery query)
{
    throw new NotImplementedException();
} 

We need to create as many Get methods as we have queries. 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 query string serialization, i.e. creating an object from a query string. This simple trick could help create a more stable method signature. On the other hand, the Get method may have such a signature. So the kind of method is reusable and polymorphic .
Message Get (Message request);

Cons of the operationGet :
  • Methods are hard to follow
  • Too many methods to create Get
  • Missing connectivity between query parameters
  • No polymorphism

Please keep in mind that the WCF SOAP service has polymorphism, more precisely it has a special polymorphism ( ad hoc polymorphism ) implemented through KnownTypeAttribute, but, in my opinion, WCF should 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 method Get. I think a serialization messaging approach might help us.

URL Serialization and Deserialization


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

As we know, it Getsends 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=pendingand we want to create something similar. The ideal serialization result has one drawback: the lack of communication between the parameters, so we cannot deserialize the URL into a request object. Our serialization engine should solve this problem as well.

Generally speaking, the query string - a collection of different pairs of "key-value": key1=value1&key2=value2&key3=value3 .
In our case, we have two keys:
  • Request type
  • Request data, object fields

I see the following serialization algorithm:
  1. Define request type
  2. Serialize the request object in JSON
  3. Encode json

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

Resulting 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 an instance PresentRequestQuery. The implementation is very simple:
CreateQueryParams(T value)
private static NameValueCollection CreateQueryParams(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 it UrlEncodecalls only Uri.EscapeDataStringand JsonDataContractSerializer- this is an instance DataContractJsonSerializer.
Tostring(T value)
public static string ToString(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);
} 

A RESTful style requires at least four methods: Get, Post, Put, Deleteand ServiceContractcan be something 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);
} 

IJsonServiceIt has flexibility, stability and ease of maintenance. We can transfer any data, since the service depends only on the class Message, 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 it has already been given earlier . The following is an example of how to Create, Update, Receive, and Delete Requests.
ClientProcessor
public sealed class ClientProcessor : IPostWithResponse,
                                      IGetWithResponse,
                                      IDelete,
                                      IPutWithResponse
{
    private static List _clients = new List();
    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 the requests with the appropriate 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(Func creator)
            where TRequest : class
            where TProcessor : IRequestOperation
        {
            if (creator == null)
            {
                throw new ArgumentNullException("creator");
            }
            _requestProcessors.Add(creator);
            _requests.Add();
        }
        public void Bind()
            where TRequest : class
            where TProcessor : IRequestOperation, new()
        {
            Bind(() => new TProcessor());
        }
    }
}

A specific one ServiceProcessorhas only configuration and processing methods.
RestServiceProcessor
public sealed class RestServiceProcessor : ServiceProcessor
{
    private RestServiceProcessor()
    {
    }
    public static IConfiguration Configure(Action 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);
    }
}

RequestMetadataMapused to store the types of queries that are needed to create specific queries from instances Message.
RequestMetadataMap
internal sealed class RequestMetadataMap
{
    private readonly Dictionary _requestTypes =
        new Dictionary();
    internal void Add()
        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);
    }
}

Let's look at a reusable implementation 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 you want and completely in accordance with RESTful.
The most interesting thing happens in a RestRequestMetadataclass that helps create a specific request from a URL. Before looking at the implementation RestRequestMetadata, I want to give some explanations. RestRequestMetadatauses WebOperationContextto 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()
    {
        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
internal sealed class RequestProcessor : IRequestProcessor
    where TRequest : class
    where TProcessor : IRequestOperation
{
    private readonly Func _creator;
    public RequestProcessor(Func 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)_creator();
        var request = metadata.GetRequest();
        service.Delete(request);
    }
    private Message DeleteWithResponse(RequestMetadata metadata)
    {
        var service = (IDeleteWithResponse)_creator();
        var request = metadata.GetRequest();
        object result = service.DeleteWithResponse(request);
        return metadata.CreateResponse(result);
    }
    private void Get(RequestMetadata metadata)
    {
        var service = (IGet)_creator();
        var request = metadata.GetRequest();
        service.Get(request);
    }
    private Message GetWithResponse(RequestMetadata metadata)
    {
        var service = (IGetWithResponse)_creator();
        var request = metadata.GetRequest();
        object result = service.GetWithResponse(request);
        return metadata.CreateResponse(result);
    }
    private void Post(RequestMetadata metadata)
    {
        var service = (IPost)_creator();
        var request = metadata.GetRequest();
        service.Post(request);
    }
    private Message PostWithResponse(RequestMetadata metadata)
    {
        var service = (IPostWithResponse)_creator();
        var request = metadata.GetRequest();
        object result = service.PostWithResponse(request);
        return metadata.CreateResponse(result);
    }
    private void Put(RequestMetadata metadata)
    {
        var service = (IPut)_creator();
        var request = metadata.GetRequest();
        service.Put(request);
    }
    private Message PutWithResponse(RequestMetadata metadata)
    {
        var service = (IPutWithResponse)_creator();
        var request = metadata.GetRequest();
        object result = service.PutWithResponse(request);
        return metadata.CreateResponse(result);
    }
}


RESTful service client


The client is quite simple, it simply serializes the data into a query string and sends it to the service. The client is based on HttpClient . The following are client methods:
Customer Methods
public void Delete(TRequest request)
    where TRequest : class
public TResponse Delete(TRequest request)
    where TRequest : class
public Task DeleteAsync(TRequest request)
    where TRequest : class
public Task DeleteAsync(TRequest request)
    where TRequest : class
public void Get(TRequest request)
    where TRequest : class
public TResponse Get(TRequest request)
    where TRequest : class
public Task GetAsync(TRequest request)
    where TRequest : class
public Task GetAsync(TRequest request)
    where TRequest : class
public void Post(TRequest request)
    where TRequest : class
public TResponse Post(TRequest request)
    where TRequest : class
public Task PostAsync(TRequest request)
    where TRequest : class
public Task PostAsync(TRequest request)
    where TRequest : class
public void Put(TRequest request)
    where TRequest : class
public TResponse Put(TRequest request)
    where TRequest : class
public Task PutAsync(TRequest request)
    where TRequest : class
public Task PutAsync(TRequest request)
    where TRequest : class


Now let's make Santa the happy owner of RESTful, a message-based service.

RESTful service example


Santa still expects a RESTful service that can save and search for gift requests by filter.

Service


The configuration file is the most common:

Configuration

JsonServicePerCalland IJsonServicealready mentioned above.

Below is the binding and other settings. Binding says it PresentRequestProcessorwill handle PresentRequestand PresentRequestQuery.
Snap Setting
private static void Main()
{
    RestServiceProcessor.Configure(x =>
    {
        x.Bind();
        x.Bind();
        x.Bind();
        x.Bind();
    });
    using (var serviceHost = new WebServiceHost(typeof(JsonServicePerCall)))
    {
        serviceHost.Open();
        Console.WriteLine("Santa Clause Service has started");
        Console.ReadKey();
        serviceHost.Close();
    }
}

Finally, it PresentRequestProcessorshows how Get, Post, Put and Delete requests for gifts:
PresentRequestProcessor
public sealed class PresentRequestProcessor : IPost,
                                              IPost,
                                              IGetWithResponse,
                                              IDelete
{
    private static List _requests = new List();
    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);
    }
}


Client


Customer Code Self-documenting:
Client
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 pendingRequests = client.Get>(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();
}

Execution Results: Fiddler Screenshot :
image

the end


A message-based approach is a mega-powerful architectural style. It 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 website . Nuget package is
also available .

Interesting related article: Advantages of message based web services .

Also popular now: