Click here to Skip to main content
Click here to Skip to main content

Building RESTful Message Based Web Services with WCF

, 12 May 2014 MIT
Rate this:
Please Sign up or sign in to vote.
How to create RESTful message based web service with WCF

Introduction

I already wrote how to build SOAP Message Based Web Services with Windows Communication Foundation (WCF). I want to talk about designing and building RESTful message based web services with WCF. It's not a beginner tutorial, so you have to have the basic knowledge of what REST is and how to create RESTful web services with WCF. You can find more about RESTful service from A Guide to Designing and Building RESTful Web Services with WCF 3.5

In this article, I'll try to reveal and solve RESTful design problems. You'll know how to build RESTful web services with:

  • Stable and universal interface
  • Transfer data according to DTO pattern

Let's design a WCF web service for Santa Claus. Santa really like REST as an architectural style and doesn't like Open Data Protocol (OData), so he made following demands:

  • RESTful API
  • The service should able to do:
    • Save a present request
    • Update a present request
    • Get present requests by Status and Country
    • Delete present request by Id

Defining Basic Business Objects

Our goal is to design web service with RESTful style, so let's keep business objects as simple as possible. It may be a bit incorrect, but simple.

Here is a present request, this is a Domain-Driven Design aggregate and contains all necessary information about a wish.

I didn't add comments for the class, I hope it describes itself.

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

Here're Address and PresentRequestStatus:

public class Address
{
    public string Country { get; set; }
    public string Recipient { get; set; }
    public string StreetAddress { get; set; }
    public int ZipCode { get; set; }
}   
public enum PresentRequestStatus
{
    Pending,
    Accepted,
    Rejected,
    Completed
} 

Now, we have all the necessary stuff for the beginning.

RESTful Web Service with WCF: Design Issue

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

Save present request

The simplest implementation will look like that:

public void Save(PresentRequest request) 

Client fills all fields and sends the request to the web service. The Save returns void because we know, the service should be high loaded, so client should set a unique Id by himself.

According to RESTful design style, we have to mark Save method by WebInvoke attribute and set appropriate HTTP method.

Here is a cheat sheet for HTTP methods:

Operation

HTTP

Create

PUT / POST

Read

GET

Update

PUT / PATCH

Delete

DELETE

As a result, Save method, ServiceContract could look like this:

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

The Save method has both advantages and disadvantages.

Note: ServiceContract is the major part of the service, it should be stable and flexible. All clients depend from the ServiceContract. We should be very careful with any changes in the ServiceContract.

Advantages

  • Method signature is abstract, so we can easily add any fields in the PersentRequest without any breaking changes
  • Request is sent as an object, not as url params

Most of the developers know from The Mythical Man-Month book, you will take away first version of software. The same relates to the ServiceContract, so we have to try and create the ServiceContract to be as flexible as possible.

Disadvantages

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

Create, Update and Delete operations are similar, they have the same advantages and generally the same disadvantages. Get operation is different, as for me, it's the most unmaintainable method.

Get PresentRequests

For Get operation params are sent as query string. In our case, for Get present requests by Status and Country, we have to create something like this:

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

Advantages

  • Readable url, for instance http://SantaClaus.org/requests?country=sheldonopolis&status=pending

Before we list disadvantages, let's take a look only at the Get method.

For example, we use the same method internally in our application, without any WCF.

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

One of the biggest issues of this method is the method signature. We have to update service implementation after any changes in the method signature. This method is frangible and fragile and has a smell of a bad design. So, Get operation in RESTful style is unmaintainable by default.

Here is better solution, we can change query without any interface changes:

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

where PresentRequestQuery class contains all necessary fields:

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

Disadvantages

As we already saw, Get method has unmaintainable signature, so it's really difficult to extend functionality without breaking changes.

Get operation params are send as a query string with simple fields, the same fields are present in a Get method signature. Cohesion between params is absent, because WCF cannot create a request object from params.

Let's take a look at an example:

The url http://SantaClaus.org/requests?country=sheldonopolis&status=pending for getting PresentRequests by country and status.

Here is the apropriate WCF's method:

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

Cohesion between country and status is absent, as per method signature. In fact, we don't know what country and status mean, we can guess.

As for me, WCF should be able to create query string from a request object (serialize) and create the same request object from the query string (deserialize). So, for sending the following request object...

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

...should be serialised to country=sheldonopolis&status=pending after receiving the query string should be deserialized to an instance of PresentRequestQuery and Get method should look like:

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

We have to create as many Get methods as get requests we have, here is the code sample from WCF's Guide to Designing and Building RESTful Web Services.

[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 don't understand why WCF doesn't support query string serialization, i.e., creating an object from query string. This simple trick could help create more stable method signature. Another point, Get method could have the following signature. This kind of method is reusable and polymorphic.

Message Get (Message request); 

Disadvantages for Get operation:

  • Methods are unmaintainable
  • Have to create too many Get methods
  • Cohesion between query params is absent
  • Polymorphism is absent

Please note, WCF SOAP service has polymorphism, actualy it has ad hoc polymorphism by KnownTypeAttribute but as for me, it should be parametric polymorphism.

Conclusion

WCF as a RESTfull framework has some architectural issues which complicates creating reusable and stable services, on the other hand it has all the necessary stuff for solving these problems.

RESTful Web Service with WCF: Better Design

First of all, let's solve Get method disadvantages, I think message based approach with url serialization can help us.

URL Serialization and Deserialization

We already saw 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 params as a query sting, so our serialization method should create a valid query string. Here is our ideal serialization country=sheldonopolis&status=pending and we want to create something like this. The ideal serialization result has one issue, it doesn't have cohesion between params, that's why we could not deserialize the url to an object request. Our seralization mechanism should solve this problem too.

Generally speaking, query string is a collection of different key value pairs: key1=value1&key2=value2&key3=value3 etc.

In our case, we have two keys:

  • Request type
  • Request data, object fields

I see the following serialization algorithm:

  1. Get property names thru DynamicMethod
  2. Create query string like: Property1=Value1&Property2=Value2&etc

Here is an instance of a request object:

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

The result query string: type=PresentRequestQuery&Country=sheldonopolis&Status=Pending

This query string should be easily deserialized to an instance object of PresentRequestQuery.

Here's the approach:

  1. Create an instance object the DynamicMethod
  2. Set property values thru compiled Expression
public static ObjectActivator CreateCtor(Type type)
{
    ConstructorInfo emptyConstructor = type.GetConstructor(Type.EmptyTypes);
    var dynamicMethod = new DynamicMethod("CreateInstance", type, Type.EmptyTypes, true);
    ILGenerator ilGenerator = dynamicMethod.GetILGenerator();
    ilGenerator.Emit(OpCodes.Nop);
    ilGenerator.Emit(OpCodes.Newobj, emptyConstructor);
    ilGenerator.Emit(OpCodes.Ret);
    return (ObjectActivator)dynamicMethod.CreateDelegate(typeof(ObjectActivator));
} 

Where ObjectActivator is a delegate which returns an object. So, we've an instance object and have to set properties.

public static PropertySetter CreatePropertySetter(PropertyInfo property)
{
    ParameterExpression target = Expression.Parameter(typeof(object), "target");
    ParameterExpression valueParameter = Expression.Parameter(typeof(object), "value");
    MemberExpression member = Expression.Property(Expression.Convert(target, property.DeclaringType), property);
    MethodInfo convertTo = typeof(DelegateFactory).GetMethod("ConvertTo", BindingFlags.NonPublic | BindingFlags.Static);
    MethodInfo genericConvertTo = convertTo.MakeGenericMethod(property.PropertyType);
    BinaryExpression assignExpression = Expression.Assign(member, Expression.Call(genericConvertTo, valueParameter));
    Expression<PropertySetter> lambda = Expression.Lambda<PropertySetter>(assignExpression, target, valueParameter);
    return lambda.Compile();
}

private static T ConvertTo<T>(object value)
{
    TypeConverter converter = TypeDescriptor.GetConverter(typeof(T));
    return (T)converter.ConvertFrom(value);
} 

Where PropertySetter is a delgate which has following signature

public delegate void PropertySetter(object target, object value); 

Now we're ready for the next step, I've already written about message based approach, for SOAP service we used this ServiceContract:

[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 style requires four methods at least: Get, Post, Put, Delete and ServiceContract could be like this:

[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);
} 

The IJsonService is flexible, stable and maintainable, we can transfer any data, because the service contract depends only from WCF's Message class.

"The Message class is fundamental to Windows Communication Foundation (WCF). All communication between clients and services ultimately results in Message instances being sent and received." (MSDN)

Another advantage is CRUD. Based on IJsonService and url serialization, we can create reusable RESTful service with parametric polymorphism.

RESTful Service Implementation

I'll not show all the code here, because I used the same appoarch for creating SOAP message based web service.

This example shows how to Create, Update, Get and Delete requests.

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:

As for SOAP service, we have to bind request object with appropriate CRUD operation.

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());
        }
    }
}  

Concrete ServiceProcessor has only processing and configuration methods

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 is used for storing Requests type which is required for creating a concrete Request from a Message.

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);
    }
} 

As you can see RequestMetadataMap supports SOAP and REST services and the same RequestProcessorMap is used for binding Request's type with request processor.

Here is the reusable implementation:

[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 absolutely anything and it's absolutely RESTful.

The most interesting thing happens in the RestRequestMetadata, this class helps to create concrete request from url. Before looking at the RestRequestMetadata implementation, I want to give some explanation. The RestRequestMetadata uses WebOperationContext for getting query string and creating concrete request. Additionally, the class can create response message from a response.

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 concrete requests are processed by RequestProcessor the same class which is used for SOAP requests.

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);
    }
} 

As you can notice, most of the code for SOAP message based service and RESTful message based service is the same, the difference is only in serialization process, but benefits are the same.

RESTful Service Client

The client is really simple, just serialize data to an appropriate query string and send it to the service. The client is based on HttpClient. Here are all the client's methods:

public void Delete(object request)
 
public TResponse Delete<TResponse>(object request)
 
public Task DeleteAsync(object request)
 
public Task<TResponse> DeleteAsync<TResponse>(object request)
 
public void Get(object request)
 
public TResponse Get<TResponse>(object request)
 
public Task GetAsync(object request)
 
public Task<TResponse> GetAsync<TResponse>(object request)
 
public void Post(object request)
 
public TResponse Post<TResponse>(object request)
 
public Task<TResponse> PostAsync<TResponse>(object request)
 
public Task PostAsync(object request)
 
public void Put(object request)
 
public TResponse Put<TResponse>(object request)
 
public Task PutAsync(object request)
 
public Task<TResponse> PutAsync<TResponse>(object request)
 

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

RESTful Service Sample

Santa is still waiting for a RESTful service which will be able to Save and Find present requests by filter.

The Service

Config file is the same as usual.

<?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 already mentioned above.

Here's the binding and other configurations. Binding says , PresentRequestProcessor will process PresentRequest and 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();
    }
} 

and finally PresentRequestProcessor shows how to Get, Post, Put and Delete present requests:

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);
    }
} 

The Client

Client's code is self describing.

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<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();
} 

Execution results, this is screenshot from Fiddler.

That's All Folks

Message based approach is an extremely powerful architectural style, it could help to create RESTful web service with stable, maintainable interface and of course Santa will be glad to receive this kind of RESTful service for Christmas himself.

I hope you enjoyed it, please take the time to post a comment. Thanks for reading the article.

History

  • 3 Feb 2014: Initial version
  • 22 Apr 2014
    • Changed url serialization. Urls are readable
  • 10 May 2014
    • Changed client's methods signature. Example: client.Get<GetClientRequest, ClientResponse>(request) => client.Get<ClientResponse>(request)

License

This article, along with any associated source code and files, is licensed under The MIT License

Share

About the Author

Sergey Morenko
Software Developer (Senior)
Russian Federation Russian Federation
B.Sc. in Computer Science.
Follow on   Twitter   LinkedIn

Comments and Discussions

 
QuestionParameter List<int> in GET [modified] PinmemberM_Menon8-Sep-14 3:06 
AnswerRe: Parameter List<int> in GET PinmemberM_Menon8-Sep-14 4:03 
GeneralRe: Parameter List<int> in GET PinprofessionalSergey Morenko8-Sep-14 5:47 
QuestionVery good article! PinprofessionalVolynsky Alex18-Feb-14 6:24 
AnswerRe: Very good article! PinprofessionalSergey Morenko18-Feb-14 11:37 
GeneralRe: Very good article! PinprofessionalVolynsky Alex18-Feb-14 11:38 
QuestionHTTP method get/put/post/delete...? PinmemberSwab.Jat5-Feb-14 23:32 
AnswerRe: HTTP method get/put/post/delete...? PinprofessionalSergey Morenko6-Feb-14 5:53 
GeneralRe: HTTP method get/put/post/delete...? PinmemberSwab.Jat6-Feb-14 13:39 
GeneralRe: HTTP method get/put/post/delete...? PinprofessionalSergey Morenko6-Feb-14 18:00 
GeneralRe: HTTP method get/put/post/delete...? PinmemberSwab.Jat6-Feb-14 19:09 
GeneralMy vote of 5 Pinmemberfonarshik3-Feb-14 3:57 
GeneralRe: My vote of 5 PinprofessionalSergey Morenko3-Feb-14 7:41 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.1411022.1 | Last Updated 12 May 2014
Article Copyright 2014 by Sergey Morenko
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid