Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a good way to extend a WCF service using basicHttpBinding to also allow REST service communicating with JSON?

We have a web service up and running built in VS2010.

Several of the operational contracts looks like this:

    [OperationContract]
    ITicket Login(string userName, byte[] passwordHash, string softwareVersion);

I.e. they booth have complex arguments and complex return types, or even multiple returns.

We have recently started an outsourced iPhone project and are letting them use this service to communicate with our server. From what I have learnt from them I understood that this is not a good practice for communicating to the iPhone (lack of good ways to consume the WSDL for example). And therefore I have started to look at the possibility to expose the service as a REST service communicating with JSON.

I have added a new endpoint, using webHttpBinding, decorated the contracts like this:

    [OperationContract]
    [WebGet(UriTemplate = "/login?username={userName}&password={password}&softwareVersion={softwareVersion}", ResponseFormat=WebMessageFormat.Json)]
    ITicket Login(string userName, string password, string softwareVersion);

This method now works as intended.

I then tried to decorate another method as this:

    [OperationContract]
    [WebGet(UriTemplate = "/GetMetaData?ticket={ticket}",RequestFormat=WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
    IMetaData GetMetaData(ITicket ticket);

When I now try to access this I receive the following error:

Server Error in '/Jetas5MobileService' Application. Operation 'GetMetaData' in contract 'IJetas5MobileService2' has a query variable named 'ticket' of type 'Jetas.MobileService.DataContracts.ITicket', but type 'Jetas.MobileService.DataContracts.ITicket' is not convertible by 'QueryStringConverter'. Variables for UriTemplate query values must have types that can be converted by 'QueryStringConverter'.

I have manage to build a OperationContract that only takes a string as argument and then parses thin in the back end by using DataContractJsonSerializer, but that feels more like a ugly hack.

Is there any way to solve this in a better manner? I am beginner when it comes to WCF and REST so do not be afraid to point me to any beginner tutorials that there might be out there. I have tried to search for them but the vast amount of sources makes it hard to find the good ones.

like image 548
nj. Avatar asked Nov 17 '11 13:11

nj.


2 Answers

From what I have learnt from them I understood that this is not a good practice for communicating to the iPhone (lack of good ways to consume the WSDL for example).

The biggest problem is not the lack of good "tools" but the lack of understanding what WSDL is and how web services work. All these tools generating service stubs for developers caused that developers don't understand what is under the hood. It works for basic scenarios where all magic is done for you but once developers have to track any problem or extend the "tool" with additional features they have big problems (and it usually result in bad solution). To be honest SW development is not about basic scenarios.

REST place a big challenge for developers because it doesn't provide any "magic" tools. REST is about correct usage of HTTP protocol and it takes full advantage of existing HTTP infrastracture. Without understanding basics of HTTP protocol you will not create good REST service. Thats where you should begin.

Here is some example of incorrect usage:

[OperationContract]
[WebGet(UriTemplate = "/login?username={userName}&password={password}&softwareVersion={softwareVersion}", ResponseFormat=WebMessageFormat.Json)]
ITicket Login(string userName, string password, string softwareVersion);

Login method is obviously something that perform some action - I guess it creates ticket. It is absolutely unsuitable for GET HTTP request. This should definitely be a POST request to Login resource returning new ITicket representation for each call. Why? Because GET requests are supposed to be safe and idempotent.

  • Safe: the request should not cause any side effects = it should not do any changes to the resource but in your case it most probably creates a new resource.
  • Idempotent: this one is not so important for the example because you have already violated the Safe rule but it means that request to the resource should be repeatable. It means that first request with same user name, password and version can create new resource but when the request is executed again it should not create a new resource but return already created one. This makes more sense when the resource is persisted / maintained on the server.

Because HTTP GET request is by HTTP infrastructure considered as safe and idempotent it is handled in different way. For example GET requests can be cached redirected etc. When the request is not safe and idempotent it should use POST method. So the correct definition is:

[OperationContract]
[WebInvoke(UriTemplate = "/login?username={userName}&password={password}&softwareVersion={softwareVersion}", ResponseFormat=WebMessageFormat.Json)]
ITicket Login(string userName, string password, string softwareVersion);

because WebInvoke defaults to POST method. That is also reason why all protocol tunneling (for example SOAP) use usually POST HTTP methods for all requests.

The other problem in former example can again be REST approach = taking full advantage of HTTP infrastrucuture. It should use HTTP based authentication (login) = Basic, Digest, OAuth, etc. It doesn't mean that you cannot have similar resource but you should first consider using standard HTTP way.

Your second example is actually much better but it has problem with WCF limitation. WCF can read from URL only basic types (btw. how do you want to pass object in URL?). Any other parameter type needs custom WCF behavior. If you need to expose method which accepts data contract you must again use the HTTP method which accepts parameters in body - again use POST and place JSON serialized ticket to the body of the request:

[OperationContract]
[WebInvoke(UriTemplate = "/GetMetaData",RequestFormat=WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
IMetaData GetMetaData(ITicket ticket);
like image 116
Ladislav Mrnka Avatar answered Sep 23 '22 20:09

Ladislav Mrnka


I faced the similar problem using WCF Rest Starter Kit.

If I remember correctly, UriTemplate variables in the path always resolve to strings when using WebGet or WebInvoke. You can only bind UriTemplate variables to int, long, etc. when they are in the query portion of the UriTemplate. So there is no means to pass a complex object in.

I think there is no clean way to do it. I just used the parsing solution as you do.

Now, you can check out the new stack for doing REST with WCF called WCF Web Api. It deals very nicely with complex types as method parameters.

like image 36
Tomasz Jaskuλa Avatar answered Sep 25 '22 20:09

Tomasz Jaskuλa