<ph2>
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 PresentRequest
s 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 <code>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:
- Get property names thru
<a href="http://msdn.microsoft.com/en-us/library/system.reflection.emit.dynamicmethod.aspx">DynamicMethod</a>
- 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:
- Create an instance object the
DynamicMethod
- 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 Request
s 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 <code>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.
="1.0"="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<code>
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)
<code><code>