You can expose the service in two different endpoints.
the SOAP one can use the binding that support SOAP e.g. basicHttpBinding, the RESTful one can use the webHttpBinding. I assume your REST service will be in JSON, in that case, you need to configure the two endpoints with the following behaviour configuration
<endpointBehaviors>
<behavior name="jsonBehavior">
<enableWebScript/>
</behavior>
</endpointBehaviors>
An example of endpoint configuration in your scenario is
<services>
<service name="TestService">
<endpoint address="soap" binding="basicHttpBinding" contract="ITestService"/>
<endpoint address="json" binding="webHttpBinding" behaviorConfiguration="jsonBehavior" contract="ITestService"/>
</service>
</services>
so, the service will be available at
Apply [WebGet] to the operation contract to make it RESTful.
e.g.
public interface ITestService
{
[OperationContract]
[WebGet]
string HelloWorld(string text)
}
Note, if the REST service is not in JSON, parameters of the operations can not contain complex type.
Reply to the post for SOAP and RESTful POX(XML)
For plain old XML as return format, this is an example that would work both for SOAP and XML.
[ServiceContract(Namespace = "http://test")]
public interface ITestService
{
[OperationContract]
[WebGet(UriTemplate = "accounts/{id}")]
Account[] GetAccount(string id);
}
POX behavior for REST Plain Old XML
<behavior name="poxBehavior">
<webHttp/>
</behavior>
Endpoints
<services>
<service name="TestService">
<endpoint address="soap" binding="basicHttpBinding" contract="ITestService"/>
<endpoint address="xml" binding="webHttpBinding" behaviorConfiguration="poxBehavior" contract="ITestService"/>
</service>
</services>
Service will be available at
REST request
try it in browser,
http://www.example.com/xml/accounts/A123
SOAP request
client endpoint configuration for SOAP service after adding the service reference,
<client>
<endpoint address="http://www.example.com/soap" binding="basicHttpBinding"
contract="ITestService" name="BasicHttpBinding_ITestService" />
</client>
in C#
TestServiceClient client = new TestServiceClient();
client.GetAccount("A123");
Another way of doing it is to expose two different service contract and each one with specific configuration. This may generate some duplicates at code level, however at the end of the day, you want to make it working.
I noticed this question a couple of days late, but I feel that I can add some insight. I hope this can be helpful towards your RESTful venture.
Point 1: Am I understanding it right?
You understood right. That is a correct representation of a RESTful architecture. You may find the following matrix from Wikipedia very helpful in defining your nouns and verbs:
When dealing with a Collection URI like: http://example.com/resources/
GET: List the members of the collection, complete with their member URIs for further navigation. For example, list all the cars for sale.
PUT: Meaning defined as "replace the entire collection with another collection".
POST: Create a new entry in the collection where the ID is assigned automatically by the collection. The ID created is usually included as part of the data returned by this operation.
DELETE: Meaning defined as "delete the entire collection".
When dealing with a Member URI like: http://example.com/resources/7HOU57Y
GET: Retrieve a representation of the addressed member of the collection expressed in an appropriate MIME type.
PUT: Update the addressed member of the collection or create it with the specified ID.
POST: Treats the addressed member as a collection in its own right and creates a new subordinate of it.
DELETE: Delete the addressed member of the collection.
Point 2: I need more verbs
In general, when you think you need more verbs, it may actually mean that your resources need to be re-identified. Remember that in REST you are always acting on a resource, or on a collection of resources. What you choose as the resource is quite important for your API definition.
Activate/Deactivate Login: If you are creating a new session, then you may want to consider "the session" as the resource. To create a new session, use POST to http://example.com/sessions/
with the credentials in the body. To expire it use PUT or a DELETE (maybe depending on whether you intend to keep a session history) to http://example.com/sessions/SESSION_ID
.
Change Password: This time the resource is "the user". You would need a PUT to http://example.com/users/USER_ID
with the old and new passwords in the body. You are acting on "the user" resource, and a change password is simply an update request. It's quite similar to the UPDATE statement in a relational database.
My instinct would be to do a GET call
to a URL like
/api/users/1/activate_login
This goes against a very core REST principle: The correct usage of HTTP verbs. Any GET request should never leave any side effect.
For example, a GET request should never create a session on the database, return a cookie with a new Session ID, or leave any residue on the server. The GET verb is like the SELECT statement in a database engine. Remember that the response to any request with the GET verb should be cache-able when requested with the same parameters, just like when you request a static web page.
Point 3: How to return error messages and codes
Consider the 4xx or 5xx HTTP status codes as error categories. You can elaborate the error in the body.
Failed to Connect to Database: / Incorrect Database Login: In general you should use a 500 error for these types of errors. This is a server-side error. The client did nothing wrong. 500 errors are normally considered "retryable". i.e. the client can retry the same exact request, and expect it to succeed once the server's troubles are resolved. Specify the details in the body, so that the client will be able to provide some context to us humans.
The other category of errors would be the 4xx family, which in general indicate that the client did something wrong. In particular, this category of errors normally indicate to the client that there is no need to retry the request as it is, because it will continue to fail permanently. i.e. the client needs to change something before retrying this request. For example, "Resource not found" (HTTP 404) or "Malformed Request" (HTTP 400) errors would fall in this category.
Point 4: How to do authentication
As pointed out in point 1, instead of authenticating a user, you may want to think about creating a session. You will be returned a new "Session ID", along with the appropriate HTTP status code (200: Access Granted or 403: Access Denied).
You will then be asking your RESTful server: "Can you GET me the resource for this Session ID?".
There is no authenticated mode - REST is stateless: You create a session, you ask the server to give you resources using this Session ID as a parameter, and on logout you drop or expire the session.
Best Answer
Unfortunately I have determined (by analysing the WCF reference source code and the help of the Fiddler tool for HTTP session sniffing) that this is a bug in the WCF stack.
Using Fiddler, I noticed that my WCF service was behaving unlike any other web site which uses Basic authentication.
To be clear, this is what SHOULD happen:
GET
request with no knowledge that a password is even needed.401 Unauthorized
status and includes aWWW-Authenticate
header containing information about acceptable authentication methods.GET
request and includes appropriateAuthentication
header with the credentials.200 OK
and the web page. If the credentials were wrong, the web server responds with401 Unauthorized
and includes the sameWWW-Authenticate
header that it did in Step #2.What was ACTUALLY happening with my WCF service was this:
GET
request with no knowledge that a password is even needed.Authentication
header in the request and blindly rejects request with a401 Unauthorized
status and includes aWWW-Authenticate
header. All normal so far.GET
request including the appropriateAuthentication
header.200 OK
. All is fine. If the credentials were wrong however, WCF responds with403 Forbidden
and does not include any additional headers such asWWW-Authenticate
.When the browser gets the
403 Forbidden
status it does not perceive this to be a failed authentication attempt. This status code is intended to inform the browser that the URL it tried to access is off limits. It doesn't relate to authentication in any way. This has the terrible side affect that when the user types their username/password incorrectly (and the server rejects with 403) then the web browser doesn't reprompt the user to type their credentials again. In fact the web browser believes authentication has succeeded and so stores those credentials for the rest of the session!With this in mind, I sought clarification:
The RFC 2617 (http://www.faqs.org/rfcs/rfc2617.html#ixzz0eboUfnrl) does not mention anywhere the use of the
403 Forbidden
status code. In fact, what it actually has to say on the matter is the following:WCF does neither of these. It neither correctly sends an
401 Unauthorized
status code. Nor does it include aWWW-Authenticate
header.Now to find the smoking gun within the WCF source code:
I discovered that in the
HttpRequestContext
class is a method calledProcessAuthentication
, which contains the following (excerpt):I defend Microsoft on a lot of things but this is indefensible.
Fortunately, I have got it working to an "acceptable" level. It just means that if the user accidently enters their username/password incorrectly then the only way to get another attempt is to fully close their web browser and restart it to retry. All because WCF is not responding to the failed authentication attempt with a
401 Unauthorized
and aWWW-Authenticate
header as per the specification.