Advanced Web Programming

Download sample

This sample demonstrates some of the more advanced capabilities of the Windows Communication Foundation (WCF) Web programming model. The sample presents the following concepts:

  • URI Template Dispatch – allows service operations to be bound to external URIs that match a specified pattern.

  • HTTP Methods – service operations can be invoked by arbitrary HTTP methods, including GET, PUT, POST, and DELETE.

  • HTTP Headers – services can manipulate the contents of HTTP headers using WebOperationContext.

Note

This sample requires that .NET Framework version 3.5 is installed to build and run. Visual Studio 2008 is required to open the project and solution files.

The sample implements a basic collection of customer data that is stored in memory. It supports basic Create, Read, Update, and Delete operations that are exposed externally using URIs and HTTP methods.

Service Contract and Implementation

In this sample, the service implements a single endpoint that listens on a base URI address (https://localhost:8000/Customers). The service handles requests for URIs underneath that prefix in the following ways:

  • GET requests to https://localhost:8000/Customers are routed to GetCustomers().

  • Each customer in the system has a unique identifier, which is carried in the URI. GET requests for these URIs (for example, https://localhost:8000/Customers/1) are mapped to GetCustomer().

  • Individual customers can be updated by issuing an HTTP PUT request to the customer’s URI.

  • A customer can be removed from the system by issuing an HTTP DELETE to the customer’s URI.

  • New data can be added to the system by issuing an HTTP POST to the base URI.

[ServiceContract]
public interface ICustomerCollection
{
    [OperationContract]
    [WebInvoke(Method = "POST", UriTemplate = "")]
    Customer AddCustomer(Customer customer);

    [OperationContract]
    [WebInvoke(Method = "DELETE", UriTemplate = "{id}")]
    void DeleteCustomer(string id);

    [OperationContract]
    [WebGet(UriTemplate = "{id}")]
    Customer GetCustomer(string id);

    [OperationContract]
    [WebGet(UriTemplate = "")]
    List<Customer> GetCustomers();

    [OperationContract]
    [WebInvoke(Method = "PUT", UriTemplate = "{id}")]
    Customer UpdateCustomer(string id, Customer newCustomer);
}

The service contract is implemented by the Service class, which uses WCF singleton instancing. All requests received are routed to the same object instance on the server, allowing the customer's hash table to be shared across requests.

[ServiceBehavior( InstanceContextMode = InstanceContextMode.Single )]
public class Service : ICustomerCollection
{
   Hashtable customers = new Hashtable();
   ...
}        

The GetCustomers() operation can be invoked by issuing an HTTP GET request to the base URI of the service (for example, https://localhost:8000/Customers). The Customer type is serialized with the Data Contract Serializer.

public List<Customer> GetCustomers()
{
    List<Customer> list = new List<Customer>();

    foreach (Customer c in this.customers.Values)
    {
        list.Add(c);
    }

    return list;
}

Individual customers can be retrieved by issuing a GET request to the customer’s unique URI. For example, the customer with ID 1 can be retrieved at https://localhost:8000/Customers/1. Similarly, customer 2 can be accessed at http:/localhost:8000/Customers/2. Both URIs are dispatched to the GetCustomer(string id) method on the server. This mapping is created by the [WebGet( UriTemplate="{id}")] attribute that is applied to the GetCustomer() operation contract. The UriTemplate is a pattern that describes the set of URIs that the GetCustomer() operation handles. In this case, the pattern describes all URIs that have exactly one segment following the base address of the endpoint. The contents of that segment is matched to the {id} template variable and passed by the WCF dispatcher into the method parameter id.

//[WebGet( UriTemplate=”{id}” )]
public Customer GetCustomer(string id)
{
    Customer c = this.customers[id] as Customer;
      
    if (c == null)
    {
        WebOperationContext.Current.OutgoingResponse.SetStatusAsNotFound();
        return null;
    }

    return c;
}

The implementation of GetCustomer()looks up the customer ID (provided in the URI) and returns the associated customer. If no customer is found, the operation returns an HTTP 404 response by using the WebOperationContext method to set the response status code.

Note that the UpdateCustomer() and DeleteCustomer() methods are also associated with the same URI template as the GetCustomer() method. The WCF Web programming model permits multiple operations to be bound to the same URI template as long as they are each associated with a different HTTP method. In this case, the UpdateCustomer() method is bound to the HTTP PUT method, while the DeleteCustomer() operation is bound to the HTTP DELETE method.

[OperationContract]
[WebInvoke( Method="PUT", UriTemplate="{id}")]
Customer UpdateCustomer(string id, Customer newCustomer);

[OperationContract]
[WebInvoke( Method="DELETE", UriTemplate="{id}" )]
void DeleteCustomer(string id);

The AddCustomer() method shows how to use the new UriTemplate class to create new URIs that follow a specific pattern. The WebOperationContext() method is used to return an HTTP CREATED response with the location header set to the URI of the newly created customer.

public Customer AddCustomer(Customer customer)
{
    lock (writeLock)
    {
        counter++;
        UriTemplateMatch match = WebOperationContext.Current.IncomingRequest.UriTemplateMatch;

        UriTemplate template = new UriTemplate("{id}");
        customer.Uri = template.BindByPosition(match.BaseUri, counter.ToString());

        customers[counter.ToString()] = customer;
        WebOperationContext.Current.OutgoingResponse.SetStatusAsCreated(customer.Uri);
    }

    return customer;
}

Service Hosting

The service is hosted using WebServiceHost() and one endpoint is exposed using WebHttpBinding().

using (WebServiceHost host = new WebServiceHost(typeof(Service), new Uri("https://localhost:8000/Customers")))
{
    //WebServiceHost will automatically create a default endpoint at the base address using the WebHttpBinding
    //and the WebHttpBehavior, so there's no need to set it up explicitly
    host.Open();
    ...
}

Client

The client is created using the WebChannelFactory method to create a channel to the remote service.

using (WebChannelFactory<ICustomerCollection> cf = new WebChannelFactory<ICustomerCollection>( baseAddress ))
{
    //WebChannelFactory will default to using the WebHttpBinding with the WebHttpBehavior,
    //so there's no need to set up the endpoint explicitly
    ICustomerCollection channel = cf.CreateChannel();
    ...
}

The client that uses the channel issues a series of requests to the server, which manipulates the state of the customer collection.

Output

Adding some customers with POST:

Alice 123 Pike Place https://localhost:8000/Customers/1
Bob 2323 Lake Shore Drive https://localhost:8000/Customers/2

Using PUT to update a customer:

Charlie 123 Pike Place https://localhost:8000/Customers/1

Using GET to retrieve the list of customers:

Charlie 123 Pike Place https://localhost:8000/Customers/1
Bob 2323 Lake Shore Drive https://localhost:8000/Customers/2

Using DELETE to delete a customer:

Final list of customers:

Charlie 123 Pike Place https://localhost:8000/Customers/1

Once the interaction is complete, the program waits for a key press and then terminates.

To set up, build, and run the sample

  1. Ensure that you have performed the One-Time Set Up Procedure for the Windows Communication Foundation Samples.

  2. To build the C# or Visual Basic .NET edition of the solution, follow the instructions in Building the Windows Communication Foundation Samples.

  3. To run the sample in a single- or cross-machine configuration, follow the instructions in Running the Windows Communication Foundation Samples.

See Also

Other Resources

Basic Web Programming Model Sample

© 2007 Microsoft Corporation. All rights reserved.