Chapter 4. Programming Read/Write Services
In the previous chapter, you were introduced to the WCF 3.5 web programming model and the major pieces of its infrastructure. You used the programming model to write a read-only RESTful service, and used the infrastructure to deploy and expose it.
While it could be that some of the services you build will be
read-only, it is more likely that your services will include other parts of
the uniform interface in addition to GET
. In this
chapter, I’ll show you how to put the WCF 3.5 web programming model to work
in building a read/write service that allows user agents to create, modify,
and delete resources.
POST, PUT, and DELETE
Chapter 1 discussed REST and the architectural constraints of the uniform interface. See Figure 4-1 to refresh your memory about the uniform interface and how it should work.
Recall from Chapter 3 that
WCF enables a RESTful programming model by allowing annotation on methods
via attributes. These attributes specify which method should be invoked
for each URI, and which part of the uniform interface each method
implements. For example, WebGetAttribute
will implement
GET
, and its UriTemplate
property
value specifies the URI to which the method will respond.
All other verbs in the uniform interface (POST
,
PUT
, and DELETE
) are implemented
using the WebInvokeAttribute
.
WebInvokeAttribute
also allows you to customize the URI
that the method will respond to through its own
UriTemplate
property. It also allows you to set the
HTTP verb so that you can not only implement the remainder of the uniform
interface, but you can implement HTTP verbs that are not part of the
uniform interface. Because we already examined GET
and
WebGetAttribute
at length in Chapter 3, this chapter will focus on
WebInvokeAttribute
.
Using WebInvokeAttribute
For this discussion, we will revisit the user/membership system example from Chapter 1 by creating the code to implement that service using WCF, instead of just discussing it in the abstract. The service from the last chapter is really a read-only service for the most part (although not being overly familiar with the biological taxonomy system perhaps there are more changes than I am aware of), so I think this example is better for a read/write service. To refresh your memory, the sample service is a membership system that stores information about users. First, let’s walk through the RESTful design steps for this service in a slightly abbreviated fashion.
Resources
Our example service will expose the following resources:
All users
A particular user delineated by the user’s unique identifier
URIs and Uniform Interface
Table 4-1 shows the URIs and the parts of the uniform interface that we will implement for each URI in our example.
It is important to reiterate here that you don’t have to implement
the entire uniform interface for all resources; the same service may
expose read-only resources alongside read/write resources. For example,
the /users resource is read-only and does not
implement the DELETE
method, because even if there
aren’t any users, we still want that resource to be there.
Representations
For this particular application, let’s use a custom XML format. The following
code defines two .NET classes you’ll use to represent the data:
User
represents each user, and
Users
represents the collection of users.
[CollectionDataContract(Name = "users", Namespace = "")] public class Users : List<User> { } [DataContract(Name = "user", Namespace = "")] public class User { [DataMember(Name="id",Order=1)] public string UserId; [DataMember(Name = "firstname", Order = 2)] public string FirstName; [DataMember(Name = "lastname", Order = 3)] public string LastName; [DataMember(Name = "email", Order = 4)] public string Email; }
Implementation
Next, we need a class annotated with the ServiceContractAttribute
that has
the ability to keep track of users. For this example we’ll use a static
data member with the list of users:
[ServiceContract] public class UserService { static Users _users = new Users(); //rest of the implementation to follow }
Yes, this implementation flies in the face of the concept of statelessness in REST because the service holds onto state (the list of users). Right now, however, we need to focus on the semantics of writing the service infrastructure pieces of code with WCF. Instead of this stateful implementation, imagine instead that we are storing the list of users in a backend database and that the service implementation is totally stateless.
POST
POST
is the part of the uniform interface that is typically used
to create a new resource. To implement POST
as part
of our RESTful WCF service, we annotate the CLR method with the
WebInvokeAttribute
. We set the
WebInvokeAttribute.Method
property to the string “POST” (although POST
is
the default, being explicit is usually a better policy) and then we
set the UriTemplate
property to the template we
want this method to respond to. This will cause WCF to call the method
on our service instance when a request comes to the endpoint with a
URI that matches the template when the HTTP verb used in the request
is POST
.
The UriTemplate
follows the same rules on the
WebInvokeAttribute
as it does
on WebGetAttribute
. In fact,
all of the UriTemplate
definitions from all methods
are parsed and added to the endpoint’s
UriTemplateTable
in exactly the same way. When
matching against the UriTemplateTable
, not only
does the WCF web-dispatching infrastructure look at the URI, it also
looks at the HTTP verb from the request. This is why the
UriTemplate
value can be the same for multiple
service methods, as long as each accepted HTTP verb is different.
Example 4-1 shows the first part
of the UserService
definition,
which relates to the top-level URI
("/users"
).
[WebGet(UriTemplate = "/users")] [OperationContract] public Users GetAllUsers() { return _users; } [WebInvoke(UriTemplate = "/users", Method = "POST")] [OperationContract] public User AddNewUser(User u) { u.UserId = Guid.NewGuid().ToString(); _users.Add(u); return u; } [WebGet(UriTemplate = "/users/{user_id}")] [OperationContract] public User GetUser(string user_id) { User u = FindUser(user_id); return u; }
Note that UriTemplate
value is the same for
both GetAllUsers
and AddNewUser
,
but one uses the WebGetAttribute
and one uses
WebInvokeAttribute
. The Method
property of WebInvokeAttribute
on
AddNewUser
is POST
, which is the
method we’re most interested in at the moment. The WCF web-dispatching
infrastructure will use the HTTP verb to differentiate requests to
this URI and will route them to the appropriate method.
Note
The code in Example 4-1
sets WebInvokeAttribute.Method
to
POST
, even though POST
is the
default. Explicitly defining default values (which would be used
even if you left them out) makes it easier to scan a contract for
the uniform interface.
Another interesting thing about this design is that it does not
allow the service client to set the resource identifier (in this case
User.UserId
). This is why the URI for both getting
all users and creating a new user is the same. In this case, the
"/users"
resource acts like a factory when
POST
is used, and even if the
UserId
property is set, it will be
overwritten.
Your own design might end up being different in this regard. If
you decide to allow users to select the identifier for the resource,
the URI for creating the new resource will include the identifier and
will thus be different from the collection URI. If the design used
here followed that pattern, the UriTemplate
for the
AddNewUser
method would be
"/users/
{user_id}
"
.
It would also be expected that the HTTP verb would be
PUT
instead of POST
to create
the user resource. In most cases, it is difficult to design a service
that allows clients to decide on the identifiers because each
identifier must be unique.
Because of this choice, the AddNewUser
method
in Example 4-1 doesn’t have a
UriTemplate
-based parameter
for user_id
, but it does have a parameter:
the complex User
type, which we defined earlier.
It’s expected that when you implement POST
and
PUT
from the uniform interface you will accept a
request body as part of the incoming HTTP request message
(DELETE
on the other hand isn’t expected to have a
request body). When you use the WebInvokeAttribute
on a method, the first parameter(s) of the method are expected to be
the template matches from the UriTemplate
definition if there are any. The last parameter is expected to be
deserialized from the incoming HTTP message body.
In Chapter 1, we looked at hypothetical images of what the requests and responses to this service would look like. Let’s now look at images of actual interactions between a user agent and the service, using a special user agent called Fiddler.
Fiddler is an incredibly useful tool for carrying out complex interactions between websites and web services. Not only can it spy on requests going from a user agent to a server, it can also allow you to build arbitrary HTTP requests using its Request Builder functionality. See http://www.fiddlertool.com/ for more information about this useful tool.
Note
The Fiddler Request Builder tab allows you to create arbitrary HTTP requests using different HTTP verbs and different representations as the request body. It also allows you to see the response that the service returned from those requests. This is an invaluable tool when building services of any kind, but with RESTful services it can become the first path testing client.
In Chapter 10, I’ll show you how to implement clients using WCF. For now, we will concentrate on the service syntax and programming model using the Fiddler tool.
The first thing we will do is view the current collection of
users by passing a GET
request to the root URI.
Figure 4-2 shows the Request
Builder HTTP GET
request, and Figure 4-3 shows the Session
Inspector tab view for the same request.
Note
In this case we are hosting the WCF RESTful service inside of IIS, using the .svc file capabilities instead of self-hosting, primarily for ease of deployment and because using Fiddler is slightly easier when using port 80 for HTTP requests. See Chapter 5 for more details about hosting options.
In Figure 4-3,
you can see that the response to the HTTP GET
request is an empty collection. Our next step, then, is to add a user
to the collection using a POST
request to the same
URI, including an entity body of the right media type. Figure 4-4 shows this
request, and Figure 4-5 shows the
response.
One important thing to note in Figure 4-4 is the Content-Type header. The Content-Type header is
essential when using RESTful services in general, but is especially
important when making requests
to RESTful services. If you don’t have a Content-Type header in your
HTTP request to a WCF service, you’ll always get a “415 Missing
Content Type” status code. You don’t need the Content-Type header when
making a GET
request, since there isn’t any entity
body when making a GET
request.
In Figure 4-5 you can see the Fiddler Session Inspector tab. The raw option is selected, and the service responded with a user resource (as shown by the new unique value in the id element).
If we make another GET
request to the root
URI, we will see the newly added member. Notice that in Figure 4-6 in the response
content body that the id element now has a value, and the unique
identifier that is now part of the newly created user resource.
We can now use this identifier as part of a
GET
request for that particular resource (Figure 4-7).
PUT
At this point, the service has one user resource, created
via a POST
request. Let’s now turn our attention to
using PUT
.
Example 4-2 shows the
service method that implements PUT
and modifies a
specific user resource.
[WebInvoke(UriTemplate = "/users/{user_id}", Method = "PUT")] [OperationContract] public User UpdateUser(string user_id,User update) { User u = FindUser(user_id); UpdateUserInternal(u, update); return u; }
The details of the UpdateUserInternal
aren’t
very important as it’s just a simple copy of fields from the new user
resource into the old, except for the UserId
field.
The more interesting bit of code from Example 4-2 is that the
UriTemplate
value of this WebInvokeAttribute
is the same as the
UriTemplate
value on the single user resource
GET
method (the GetUser
method
from Example 4-1). Remember, you
can have multiple methods with the same UriTemplate
value, as long as the HTTP verb is different. The
GetUser
method uses the
WebGetAttribute
; its method will inherently
implement GET
, so requests to a particular user’s
URI will be routed to the GetUser
method when the
HTTP verb in the request is GET
. The
UpdateUser
method will be called when a request
arrives for a specific user’s URI when the HTTP verb is
PUT
because the WebInvokeAttribute.Method
on the
UpdateUser
method is set to
PUT
.
To modify a resource, we can make a PUT
request to the user’s URI, passing the correct user resource
representation (which in this case is XML). On success, the service
returns the same resource as the body of its response. You can see the
request in Figure 4-8
and the response in Figure 4-9.
You can see in Figure 4-8 that we changed the resource by modifying the email address. The service returns a “200 OK” response code and returns the newly modified resource as the response body.
DELETE
At this point, you probably have a pretty good idea of how
the implementation of DELETE
is going to progress.
Example 4-3 shows the code used to
implement DELETE
.
[WebInvoke(UriTemplate = "/users/{user_id}", Method = "DELETE")] [OperationContract] public void DeleteUser(string user_id) { User u = FindUser(user_id); _users.Remove(u); }
You can see the interaction between the client and the service
using DELETE
in Figures 4-10
and 4-11.
RESTful convention dictates that DELETE
will
not accept or return a representation. There really isn’t anything
else special about implementing DELETE
, other than
making sure to set the WebInvokeAttribute.Method
property appropriately.
Full service implementation
Example 4-4 shows the entire service implementation from top to bottom.
[ServiceContract] public class UserService { static Users _users = new Users(); [WebGet(UriTemplate = "/users")] [OperationContract] public Users GetAllUsers() { return _users; } [WebInvoke(UriTemplate = "/users", Method = "POST")] [OperationContract] public User AddNewUser(User u) { u.UserId = Guid.NewGuid().ToString(); _users.Add(u); return u; } [WebGet(UriTemplate = "/users/{user_id}")] [OperationContract] public User GetUser(string user_id) { User u = FindUser(user_id); return u; } User FindUser(string user_id) { User ret = null; var result = (from u in _users where u.UserId == user_id select u).Single(); if (result != null) ret = result; else ret = new User(); return ret; } [WebInvoke(UriTemplate = "/users/{user_id}", Method = "PUT")] [OperationContract] public User UpdateUser(string user_id, User update) { User u = FindUser(user_id); UpdateUserInternal(u, update); return u; } private void UpdateUserInternal(User u, User update) { u.Email = update.Email; u.FirstName = update.FirstName; u.LastName = update.LastName; } [WebInvoke(UriTemplate = "/users/{user_id}", Method = "DELETE")] [OperationContract] public void DeleteUser(string user_id) { User u = FindUser(user_id); _users.Remove(u); } }
Summary
In this chapter, you learned how to finish implementing the
uniform interface using WCF. Use WebInvokeAttribute
to
implement any HTTP method other than GET
, and use the
WebInvokeAttribute.Method
property to specify which
HTTP method the CLR method should respond to. You can customize the URI
using the WebInvokeAttribute.UriTemplate
property.
WCF routes messages to the methods on instances of your service type by
looking for a match based on the URI and the HTTP verb.
There is still more that you can do to make your services compliant with the constraints of REST. Chapter 11 has more information about extending beyond the basic infrastructure of the WCF web-programming model and using the full breadth of HTTP in your service.
Get RESTful .NET now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.