Announcement: WCF Web API is now ASP.NET Web API! ASP.NET Web API released with ASP.NET MVC 4 Beta. The WCF Web API and WCF support for jQuery content on this site wll removed by the end of 2012.

Getting Started: Web API Enhancements

Learning objectives

In this quick start you will learn how to leverage the WCF Web API Enhancements* to simplify the implementation of a Web API.

  • How to simplify adding routes and configuration for a Web API
  • How to use conventions to enable HTTP POST, PUT and DELETE methods on an API.
  • How to enable browser HTML forms to POST to an API.
  • How to implement asynchronous service operations using Task/Task<T>

*Note: The Web API Enhancements are a collection of prototype features that simplify and add on to the core Web API features. Your feedback on these enhancements is greatly appreciated!

Pre-requisites

  • Visual Studio 2010 / Visual Studio 2010 SP1
  • ASP.NET MVC 3**

**Note: Web API does not requires ASP.NET MVC, it supports supports multiple hosting configurations including self-host, IIS and ASP.NET (both MVC and Web Forms). This quick start however show show to host in ASP.NET MVC 3.

Scenario

For this quick start the scenario will be to simplify the implementation of an existing Web API using Web API Enhancements

1 – Extract the starter code

To begin the quick start first extract the starter code / end code available here. Once the starter code is extracted, open the solution in the “Start” folder.

2 – Use MapServiceRoute<T>

The implementation in the Start solution adds a ServiceRoute to the ASP.NET routes table to enable hosting of the Web API. The ServiceRoute must be constructed with the HttpServiceHostFactory and the Web API type to enable hosting of the Web API. Configuration data for the Web API is passed to the HttpServiceHostFactory.

Web API Enhancements adds a convenience MapServiceRoute<T> extension method that takes care of adding and configuring the HttpServiceHostFactory. By default MapServiceRoute<T> uses WebApiRoute instead of ServiceRoute, which enables action link generation from ASP.NET MVC controllers in the same application. MapServiceRoute<T> also provides a convenient generic type parameter for specifying the Web API type.

  • Double-click on the Global.asax file in the project
  • Modify the RegisterRoutes method to replace ServiceRoute code with the following call to MapServiceRoute<T>
routes.MapServiceRoute<ContactsApi>("api/contacts", config);
  • Build and run the solution (F5) to verify that the Web API is still functional

3 – Use WebApiConfiguration

The Web API is configured by passing in an HttpConfiguration instance when creating the service route. For example, the HttpConfiguration is used to enable the Web API test client. Web API Enhancements extends HttpConfiguration to enable simplifying conventions and to add support for additional formats, like form URL encoded data. The MapServiceRoute<T> extension method uses WebApiConfiguration by default, but we still want to enable the Web API test client. We will modify the code to use WebApiConfiguration instead of HttpConfiguration.

  • In the RegisterRoutes method change the type of the configuration instance to be WebApiConfiguration
var config = new WebApiConfiguration() { EnableTestClient = true };
  • Build and run the solution (F5)

4 – Use conventions to specify supported HTTP methods

The WebApiConfiguration enables a convention that allows you to specify the HTTP method for an operation using a prefix on the operation name (ex. PostXxx, PutXxx, DeleteXxx). Using WebApiConfiguration also changes the default UriTemplate to be “” instead of the name of the operation.

  • Open ContactsApi.cs
  • Modify the WebGetAttribute on the Get operation to remove the UriTemplate
[WebGet]
public IQueryable<Contact> Get()
{
    return repository.FindAll();
}

Since the default UriTemplate is now “” by default the UriTemplate property is no longer needed here.

  • Modify the WebInvokeAttribute on the Post operation to remove the UriTemplate and the Method properties
[WebInvoke]
public Contact Post(Contact contact)
{
    repository.Add(contact);
    return contact;
}

In addition to the UriTemplate being no longer needed in this case, the HTTP method is inferred from the method name, which in this case is POST.

  • Modify the WebInvokeAttributes on the Put and Delete operations to remove the Method properties
[WebInvoke(UriTemplate = "{id}")]
public Contact Put(Contact contact, int id)
{
    var existingContact = repository.Find(id);
    if (existingContact == null)
    {
        throw new HttpResponseException(HttpStatusCode.NotFound);
    }
    existingContact.Name = contact.Name;
    return existingContact;
}

[WebInvoke(UriTemplate = "{id}")]
public Contact Delete(int id)
{
    var contact = repository.Find(id);
    if (contact == null)
    {
        throw new HttpResponseException(HttpStatusCode.NotFound);
    }
    repository.Remove(id);
    return contact;
}

As with the Post operation the HTTP method for the Put and Delete operations will be inferred from the operation names (PUT and DELETE respectively).

  • Build and run the solution (F5)
  • Browse to the Web API test client and verify that the operations still function as originally implemented in the previous Quick Start.

5 - Posting from a simple HTML Form

Web API contains out of the box support for posting content from HTML forms. Below you will add a ContactsController and corresponding view which will use a simple HTML form to POST a new contact.

  • Right click on the project and select Add->Controller.
  • Enter ContactsController for the name and click the “Add" button.
  • Copy the Create method below into the controller.
public ActionResult Create()
{
    return View();
}
  • Right click on the “Views” folder and select Add->New Folder. Rename the new folder to “Contacts”
  • Right click on the new “Contacts” folder and select Add->View.
  • Enter “Create” for the view name and click “Enter”
  • Open “Create.cshtml” and paste the code below.
@{
    Layout = null;
}
<!DOCTYPE html>
<html>
<head>
    <title>Create new Contact</title>
</head>
<body>
    <h1>Create new Contact</h1>
    <form method="post" action="/api/contacts" id="addContact" 
        enctype="application/x-www-form-urlencoded">
    <table>
        <tr>
            <td>Name</td>
            <td><input type="text" name="Name" /></td>
        </tr>
        <tr>
            <td colspan="2" align="center"><input type="submit" value="Add" /></td>
        </tr>
    </table>
    </form>
</body>
</html>

  • Build and run the solution.
  • When the page loads, enter “http://localhost:9000/contacts/create” to load the create contact page.

image

  • Enter “New Contact3” for the contact name and press "Add”

image

The created contact is returned.

6 – Implement asynchronous operations using Task/Task<T>

Using WebApiConfiguration also enables support for implementing asynchronous operations using Task or Task<T>. Here we will add an asynchronous operation to our Web API that calls a another Web API asynchronously and then processes and returns the result. We will add a new Web API for getting status messages for contacts and then add an operation to the ContactsApi that aggregates the status for all added contacts.

  • right click on the Resources folder and choose Add->Class. Enter Status as the class name
  • Copy the code below into the new class
namespace ContactManager.Resources
{
    public class Status
    {
        public int ContactId { get; set; }
        public string Message { get; set; }
    }
}
  • Right click on the APIs folder and choose Add->Class. Enter “StatusApi” as the class name.
  • Copy the code below into the new class
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Collections.Generic;
using ContactManager.Resources;
using System.Linq;
using Microsoft.ApplicationServer.Http.Dispatcher;
using System.Net;

namespace ContactManager.APIs
{
    [ServiceContract]
    public class StatusApi
    {
        static IDictionary<int, Status> repository = new Dictionary<int, Status>();

        [WebGet]
        public IQueryable<Status> Get()
        {
            return repository.Values.AsQueryable();
        }
        
        [WebGet(UriTemplate="{contactId}")]
        public Status GetStatus(int contactId)
        {
            Status status;
            repository.TryGetValue(contactId, out status);
            if (status == null) 
            {
                throw new HttpResponseException(HttpStatusCode.NotFound);
            }
            return status;
        }

        [WebInvoke]
        public Status PostStatus(Status status)
        {
            repository[status.ContactId] = status;
            return status;
        }
    }
}

The StatusApi maintains a single Status instance per contact indexed by the Contact ID. The StatusApi provides operations for getting the all of the posted status information, getting the status for a specific contact, and posting a new status for a given contact.

  • Double-click on the global.asax file
  • In the RegisterRoutes method specify the configuration instance as the default configuration for all Web APIs in this application using the SetDefaultHttpConfiguration extension method
routes.SetDefaultHttpConfiguration(new WebApiConfiguration() { EnableTestClient = true });
  • Add a route below the ContactsApi route for hosting the StatusApi at “api/status”
routes.MapServiceRoute<StatusApi>("api/status");
  • Open ContactApi.cs
  • Add the following using statements
using System.Net.Http;
using System;
using System.Threading.Tasks;
  • Add an asynchronous operation for getting the status for each contact by copying the following code into the ContactApi class
[WebGet(UriTemplate="status")]
Task<IQueryable<Status>> GetStatusAsync()
{
    HttpClient client = new HttpClient();
    client.BaseAddress = new Uri("http://localhost:9000/api/");
    
    IEnumerable<Task<HttpResponseMessage>> tasks = 
        from c in repository.FindAll() select client.GetAsync("status/" + c.ContactId);
    Task<HttpResponseMessage>[] requestTasks = tasks.ToArray<Task<HttpResponseMessage>>();

    return Task.Factory.ContinueWhenAll(requestTasks, (completedTasks) =>
        {
            IEnumerable<Status> status = 
                from task in completedTasks 
                where task.Result.IsSuccessStatusCode 
                select task.Result.Content.ReadAs<Status>();
            return status.AsQueryable();
        });
}

The GetStatusAsync operation uses the HttpClient to asynchronously request status data from the status Web API for each contact in the repository. The ContinueWhenAll method Creates a continuation Task that will be started upon the completion of the requests.  The continuation code filters the responses to just the ones that completed successfully. Because the response type is Task<IQueryable<T>> the GetStatusAsync operation supports OData queries.

  • Build and run the solution (F5)
  • Browse to the Web API test client for the status Web API by browsing to http://localhost:9000/api/status/test
  • Click the  status resource in the Resources view
  • Change the HTTP method to be POST
  • Select the JSON tab and add the following content to the Request body
{ "ContactId": 1, "Message": "Falling in love with WCF Web API!" }
  • Click the Send button

image

The status for contact 1 was successfully added.

  • Post a few more status updates for other contacts
{ "ContactId": 2, "Message": "There's a gorilla in my bed!" }
{ "ContactId": 3, "Message": "I think I'll move to Australia" }
  • Browse to http://localhost:9000/api/contacts/status to retrieve the status updates for all of the contacts

image

The status updates for the contacts were successfully retrieved asynchronously

Summary

In this quick start we learned how to use Web API Enhancements to do the following:

  • How to simplify adding routes and configuration for a Web API
  • How to use conventions to enable HTTP POST, PUT and DELETE methods on an API.
  • How to enable browser HTML forms to POST to an API.
  • How to implement asynchronous service operations using Task/Task<T>

Last edited Feb 17, 2012 at 7:34 PM by danroth27, version 5

Comments

gblock Dec 21, 2011 at 1:09 AM 
Alan, it's not part of WebInvokeAttribute so no. If you look in WebApiConfiguration we take advantage of an extensibility hook in the config class to set the Method property based on convention.

jlnelson Dec 5, 2011 at 5:31 AM 
OK. I may have answered my own question.

The return statement of the GetStatusAsync() method shown in the walkthrough should be updated to this (if using Version 0.6 of the WebApi bits):

return Task.Factory.ContinueWhenAll(requestTasks, (completedTasks) =
{
IEnumerable<Status> status =
from task in completedTasks
where task.Result.IsSuccessStatusCode
select task.Result.Content.ReadAsAsync<Status>().Result;
return status.AsQueryable();
});

or do it the even easier way like this:


return Task.Factory.ContinueWhenAll(requestTasks, (completedTasks) =>
{
return completedTasks.Where(a => a.Result.IsSuccessStatusCode == true).Select(a => a.Result.Content.ReadAsAsync<Status>().Result).AsQueryable();
});


If anyone sees anything wrong with my revisions please post a response.

jlnelson Dec 4, 2011 at 6:17 AM 
Looks like version 0.6 broke the GetStatusAsync() method in step 6 of this walkthrough. I'm guessing the sync methods being removed from HttpClient in 0.6 has something to do with it, but I'm not sure. Anyone out there have any idea how to update GetStatusAsync() in the sample to make it play nicely with 0.6?

alanstevens Nov 15, 2011 at 9:33 PM 
Can you guys get this bit added to the MSDN documentation for the WebInvokeAttribute? "HTTP method is inferred from the method name"

++Alan