Exposing Classic HTTP Endpoints with WCF in .NET 3.5

Introducing the New Web Programming Model for WCF

This monthly column explores the enhancements made to theWCF programming model in .NET 3.5 that specifically support Web programming. If you have questions about migrating your existing ASMX or WSE Web services to WCF, or questions regarding WCF as it relates to Web services, send them to [email protected]!

Before SOAP Web services were introduced in 1999, and for a period of time during the early adoption phase of Web services, most applications handled data-centric Web programming with the HTTP protocol. This usually meant standing up an endpoint, such as an ASP or JSP page (in those days), and processing requests to that endpoint using HTTP GET as the protocol for data retrieval, and HTTP POST for sending complex types for processing. XML schemas were usually used to define the payload of posted parameters, and of response streams. The drawback of this approach, and part of the reason SOAP was such an exciting technology, is that the list of HTTP-addressable URI was not automatically published (as in WSDL); the schemas defining the message payload for each GET and POST were not automatically published (again, as in WSDL); and client applications could not programmatically generate code to build the HTTP request to consume a collection of endpoints (as in proxy generation).

Despite the fact that SOAP Web services and WS* provide a rich programming experience to resolve the limitations of the HTTP programming model, applications still may need to expose endpoints that support this model. When .NET 3.0 released with the first version of WCF, exposing a simple HTTP endpoint was possible thanks to the highly extensible and flexible programming model but it wasn t easy. With the release of .NET 3.5, new WCF features have been released to support this Web programming model to make plain-old-XML (POX), JavaScript Object Notation (JSON), Representational State Transfer (REST), and syndication services (such as RSS) first class citizens of the service model. In this article, I ll introduce the new features that support these technologies, focusing specifically on POX to uncover core features common to all Web programming approaches. I will cover JSON, REST, and RSS in subsequent articles.

 

Core Web Programming Features

The release of .NET 3.5 includes a new assembly System.ServiceModel.Web.dll which includes features for the Web programming model for WCF. A combination of new contract attributes, bindings, message encoders, behaviors, and service hosts are among the new types available to support this model. Figure 1 lists the core new types I ll be discussing in this article.

Type Name

Description

System.ServiceModel.Web.WebGetAttribute

Defines a service operation invoked by the HTTP GET protocol.

System.ServiceModel.Web.WebInvokeAttribute

Defines a service operation invoked by any HTTP protocol, including GET, POST, PUT, DELETE.

System.ServiceModel.WebHttpBinding

A new binding that exposes service endpoints reachable using the HTTP protocol rather than SOAP messaging.

System.ServiceModel.Description.WebHttpBehavior

A new endpoint behavior that supports dispatching messages to an operation based on a URI template. This makes it possible to customize URI and pass post parameters or query string parameters.

System.ServiceModel.Web.WebServiceHost

A new derivative of the ServiceHost type that ensures the correct binding and behavior for Web-style services.

System.ServiceModel.Activation.WebServiceHostFactory

A new derivative of the ServiceHostFactory type that associates the WebServiceHost with a .svc endpoint for IIS deployment.

System.ServiceModel.Web.WebChannelFactory<T>

A new derivative of ChannelFactory<T> that creates a client channel with the correct binding and behavior for Web-style services.

System.ServiceModel.Web.WebOperationContext

A new extension to the OperationContext that provides direct access to HTTP headers for a request or response.

Figure 1: Core types supporting POX in the new Web programming model for WCF.

Now let s take a closer look at how you would use these features in practice.

 

Designing a POX Service Contract

To design a service contract with operations that can be reached via the HTTP protocol, a WebGetAttribute or WebInvokeAttribute must be added to the operation. Figure 2 shows a service contract that leverages both attributes appropriately, and its implementation on a service type. For GET operations like GetItems and GetItem, WebGetAttribute is applied; for POST operations like SaveItem or DeleteItems, WebInvokeAttribute is applied.

[ServiceContract(Namespace="http://www.thatindigogirl.com/

 samples/VS2008")]

public interface IQueryStringService

{

  [OperationContract]

  [WebGet(UriTemplate = "GetItems")]

 Dictionary<string, string> GetItems();

    [OperationContract]

    [WebGet(UriTemplate="GetItem?id={key}")]

   string GetItem(string key);

  [OperationContract]

  [WebInvoke(UriTemplate = "SaveItem?id={key}",

   Method = "POST")]

 void SaveItem(string key, string item);

  [OperationContract]

  [WebInvoke(UriTemplate = "DeleteItems", Method = "POST")]

 void DeleteItems();

}

public class QueryStringService : IQueryStringService

{

 static IDictionary<string, string> s_itemStorage =

   new Dictionary<string, string>();

 public Dictionary<string, string> GetItems()

 {

   return s_itemStorage as Dictionary<string, string>;

 }

 public string GetItem(string key)

 {

   return s_itemStorage[key];

 }

 public void SaveItem(string key, string item)

 {

   if (s_itemStorage.ContainsKey(key))

     s_itemStorage[key] = item;

   else

     s_itemStorage.Add(key, item);

 }

 public void DeleteItems()

 {

   s_itemStorage.Clear();

 }

}

Figure 2: A service contract and implementation exposing HTTP GET and POST operations.

The UriTemplate property for both attributes determines the format of the URI required to invoke an operation. Let s assume the base address of the service is http://localhost:8000/QueryStringService. In that case, the UriTemplate GetItems requires callers to use the URI http://localhost:8000/QueryStringService/GetItems to invoke the GetItems method. The resulting HTTP request looks like this:

GET /QueryStringService/GetItems HTTP/1.1

Content-Type: application/xml; charset=utf-8

Host: localhost:8080

In this case, no query parameters are passed, but the operation is responsible for returning content in the HTTP response. In this case, that content is a dictionary that serializes to the content in Figure 3 in the response.

HTTP/1.1 200 OK

Content-Length: 1001

Content-Type: application/xml; charset=utf-8

Server: Microsoft-HTTPAPI/2.0

Date: Mon, 07 Jan 2008 00:09:22 GMT

<ArrayOfKeyValueOfstringstring xmlns="http://schemas.microsoft.com/2003/10/

 Serialization/Arrays" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">

<KeyValueOfstringstring><Key>0</Key><Value>Item 0</Value></KeyValueOfstringstring>

<KeyValueOfstringstring><Key>1</Key><Value>Item 1</Value></KeyValueOfstringstring>

<KeyValueOfstringstring><Key>2</Key><Value>Item 2</Value></KeyValueOfstringstring>

<KeyValueOfstringstring><Key>3</Key><Value>Item 3</Value></KeyValueOfstringstring>

<KeyValueOfstringstring><Key>4</Key><Value>Item 4</Value></KeyValueOfstringstring>

<KeyValueOfstringstring><Key>5</Key><Value>Item 5</Value></KeyValueOfstringstring>

<KeyValueOfstringstring><Key>6</Key><Value>Item 6</Value></KeyValueOfstringstring>

<KeyValueOfstringstring><Key>7</Key><Value>Item 7</Value></KeyValueOfstringstring>

<KeyValueOfstringstring><Key>8</Key><Value>Item 8</Value></KeyValueOfstringstring>

<KeyValueOfstringstring><Key>9</Key><Value>Item 9</Value></KeyValueOfstringstring>

</ArrayOfKeyValueOfstringstring> 

Figure 3: A dictionary that serializes content in the response.

The UriTemplate GetItem?id={key} requires callers to use the URI http://localhost:8000/QueryStringService/GetItem?id=0 to invoke GetItem and retrieve the first item in the dictionary. The query string parameter is specified with a placeholder value in curly braces, in this case {key} and this must match the operation parameter that receives the placeholder. The result is an HTTP GET that passed a query string such as:

GET /QueryStringService/GetItem?id=1 HTTP/1.1

Content-Type: application/xml; charset=utf-8

Host: localhost:8080

This time, the response returns only a string, and looks something like this:

HTTP/1.1 200 OK

Content-Length: 91

Content-Type: application/xml; charset=utf-8

Server: Microsoft-HTTPAPI/2.0

Date: Mon, 07 Jan 2008 00:09:22 GMT

<string xmlns="http://schemas.microsoft.com/2003/10/

 Serialization/">Updated Item 1</string>

The definition of SaveItem has two operation parameters: key and item. The first, key, is a query string parameter as specified by the UriTemplate. The second, item, will be populated with the data posted in the body of the request. In this case, the WebInvokeAttribute is required to specify the request as an HTTP POST. This is shown explicitly in the Method parameter to the attribute, but the default verb is POST, thus these are equivalent:

[WebInvoke(UriTemplate = "SaveItem?id={key}",

 Method = "POST")]

[WebInvoke(UriTemplate = "SaveItem?id={key}")]

 

The Method parameter is only applicable to WebInvokeAttribute, and it can specify any valid or custom HTTP verb.

The HTTP POST content passed to the second parameter of SaveItem looks like this on the wire:

POST /QueryStringService/SaveItem?id=0 HTTP/1.1

Content-Type: application/xml; charset=utf-8

Host: localhost:8080

Content-Length: 83

Expect: 100-continue

Connection: Keep-Alive

<string xmlns="http://schemas.microsoft.com/2003/10/

 Serialization/">Item 0</string>

The service model unpacks the posted content from its <string> element and maps it to the second parameter.

Both the WebGetAttribute and WebInvokeAttribute support additional properties beyond UriTemplate and Method (for the latter); they are: RequestFormat, ResponseFormat, and BodyStyle. The following shows default settings for each:

 [OperationContract]

[WebInvoke(BodyStyle = WebMessageBodyStyle.Bare,

 RequestFormat = WebMessageFormat.Xml, ResponseFormat =

 WebMessageFormat.Xml, UriTemplate = "SaveItem?id={key}",

 Method = "POST")]

void SaveItem(string key, string item);

 

RequestFormat and ResponseFormat control the message encoder for the operation and can be one of WebMessageFormat.Xml or WebMessageFormat.Json. The default is Xml, but Json is useful in specifically supporting the AJAX client programming model. BodyStyle can be one of the following: WebMessageBodyStyle.Bare, WebMessageBodyStyle.Wrapped, WebMessageBodyStyle.WrappedRequest, or WebMessageBodyStyle.WrappedResponse. Bare is the default setting, but this only allows you to post a single parameter to the operation. As soon as multiple parameters are supported, you must change this to WrappedRequest (at least for the request case).

If the SaveItem operation did not support query strings, the first and second parameter would both have to be passed as part of the request content. The new operation definition would look as follows:

[OperationContract]

[WebInvoke(BodyStyle = WebMessageBodyStyle.WrappedRequest,

 UriTemplate = SaveItem )]

void SaveItem(string key, string item);

The resulting HTTP POST message would include a wrapper around the content by the name of the operation <SaveItem>, with each parameter embedded within, as follows:

Content-Type: application/xml; charset=utf-8

Host: localhost:8080

Content-Length: 105

Expect: 100-continue

Connection: Keep-Alive

<SaveItem xmlns="http://www.thatindigogirl.com/

 samples/VS2008">

<key>0</key>

<item>Item 0</item>

</SaveItem>

Once your service contract is defined to include the appropriate HTTP request and response definitions, you can host the service implementation in any of the usual WCF hosting environments (IIS6, IIS7/WAS, or self-hosting) with some minor changes to support the invocation process of the Web programming model.

 

WebHttpBinding and WebHttpBehavior

Several new standard bindings for WCF were introduced with .NET 3.5 but the binding that is useful for Web programming is WebHttpBinding. This binding is necessary to expose service endpoints accessible using classic HTTP requests. In addition to the binding, a new endpoint behavior has been introduced, WebHttpBehavior. This behavior is also necessary for messages received by the channel dispatcher to map incoming HTTP requests and associated query string parameters and content to the correct operation and related parameters. Thus, to construct a ServiceHost instance that utilizes these new features, you would add an endpoint using WebHttpBinding and add a WebHttpBehavior to the endpoint:

ServiceHost host = new ServiceHost(typeof(Services.

 QueryStringService), new Uri("http://localhost:8000/

 QueryStringService"));

ServiceEndpoint ep = host.AddServiceEndpoint(typeof(

 Contracts.IQueryStringService), new WebHttpBinding(), "");

ep.Behaviors.Add(new WebHttpBehavior());

host.Open();

You also can add the endpoint using the traditional <system.serviceModel> configuration. The configuration and associated ServiceHost code shown in Figure 4 illustrate adding an endpoint that customizes the WebHttpBinding configuration by increasing various message size and reader quotas. In addition, you can see that the <endpoint> has a behavior associated that enables the WebHttpBehavior with the <webHttp> element.

<system.serviceModel>

  <services>

    <service name="Services.QueryStringService" >

      <endpoint address="" binding="webHttpBinding"

       bindingConfiguration="webHttp" contract="Contracts.

       IQueryStringService"

       behaviorConfiguration="webBehavior"/>

      <host>

        <baseAddresses>

          <add baseAddress="http://localhost:8000/

           QueryStringService"/>

         </baseAddresses>

      </host>

    </service>

  </services>

  <bindings>

    <webHttpBinding>

      <binding name="webHttp" maxReceivedMessageSize=

       "1000000" maxBufferSize="1000000" >

        <readerQuotas maxArrayLength=

         "1000000" maxStringContentLength="1000000" />

      </binding>

    </webHttpBinding>

  </bindings>

  <behaviors>

    <endpointBehaviors>

      <behavior name="webBehavior">

        <webHttp />

      </behavior>

    </endpointBehaviors>

  </behaviors>

 </system.serviceModel>

Figure 4: Service model configuration and ServiceHost code to manually construct the WebHttpBinding endpoint with WebHttpBehavior enabled.

 

WebServiceHost

A new ServiceHost derivative has been introduced to specifically support Web programming. When you construct a new WebServiceHost instead of ServiceHost, by default it will add a WebHttpBinding endpoint for the base address of the service. The WebServiceHost also adds the WebHttpBehavior to all endpoints. Thus, the hosting code would be reduced to the following:

WebServiceHost host = new WebServiceHost(typeof(

 Services.QueryStringService), new Uri(

 "http://localhost:8000/QueryStringService"));

host.Open();

The resulting URI to invoke GetItems would be http://localhost:8000/QueryStringService/GetItems. A base address is required for the WebServiceHost to add the endpoint for you; otherwise, an explicit endpoint must be provided in code or configuration.

If you specify an endpoint, then WebServiceHost will not add additional endpoints; thus, this is only a shortcut for cases where no customization to the WebHttpBinding configuration is required. For example, if the default security settings (None) and the default message size and reader quotas are acceptable, the default WebHttpBinding should work fine.

If you decide to specify the endpoint explicitly, there is no need to add the WebHttpBehavior manually, as it will be added for you by the WebServiceHost. Thus, the configuration shown in Figure 4 would change to that shown in Figure 5.

<system.serviceModel>

 <services>

   <service name="Services.QueryStringService" >

     <endpoint address="http://localhost:8000/QueryStringService"

      binding="webHttpBinding" bindingConfiguration="webHttp"

      contract="Contracts.IQueryStringService"  />

   </service>

 </services>

 <bindings>

   <webHttpBinding>

     <binding name="webHttp" maxReceivedMessageSize=

      "1000000" maxBufferSize="1000000" >

       <readerQuotas maxArrayLength=

        "1000000" maxStringContentLength="1000000" />

     </binding>

   </webHttpBinding>

 </bindings>

</system.serviceModel>

Figure 5: The new configuration, if you decide to specify the endpoint explicitly.

 

WebServiceHostFactory

To host your services as part of a Web application (hosted in IIS or file-based for testing), a .svc endpoint is necessary to construct the ServiceHost as part of message-based activation. Thus, to host your POX services in a Web application there must be a way to specify that you want the WebServiceHost instead of the vanilla ServiceHost. This is done by specifying the WebServiceHostFactory in the Factory property of the @ServiceHost directive:

<%@ ServiceHost Service=

 "Services.QueryStringService" Factory=

 "System.ServiceModel.Activation.WebServiceHostFactory" %>

This tells the service model to construct a WebServiceHost instance, which will automatically add the default WebHttpBinding endpoint using the .svc URI, and add the WebHttpBehavior to all endpoints. As before, if you specify an endpoint in the web.config, the WebServiceHost will honor that configuration just be sure to use the WebHttpBinding.

I discussed why you might want to create a custom ServiceHost or ServiceHostFactory in Controlling ServiceHost Initialization at Run Time). For POX services, you ll instead extend WebServiceHost or WebServiceHostFactory.

 

Consuming POX Services

Although you can enable the ServiceMetadataBehavior for a POX service, and it will generate a form of WSDL that lists the operations for the service, this cannot be used to generate a proxy to invoke POX services. Instead of adding a service reference to your client applications, you ll use WebChannelFactory<T> to invoke them. This implies you have access to the following information at the client:

  • The address of the service (base address).
  • The service contract (the metadata), which includes the URI model supported for each operation (UriTemplate).
  • The binding requirements (WebHttpBinding, plus any customizations to the binding configuration).

Like the WebServiceHost, WebChannelFactory<T> will add the WebHttpBehavior to the client endpoint so operation calls are appropriately mapped between operation parameters and query strings, content parameters, or content results. The following code illustrates the client code necessary to invoke all operations of the service type in Figure 2:

WebChannelFactory<IQueryStringService> factory =

 new WebChannelFactory<IQueryStringService>(

 new Uri("http://localhost:8000/QueryStringService"));

 IQueryStringService proxy = factory.CreateChannel();

for (int i = 0; i<10; i++)

 proxy.SaveItem(i.ToString(), "Item " + i.ToString());

Dictionary<string, string> items = proxy.GetItems();

proxy.SaveItem("1", "Updated Item 1");

string item = proxy.GetItem("1");

 

WebOperationContext

Another new construct, WebOperationContext, has been provided to give you ready access to HTTP headers in lieu of the HttpContext you would use when running services in ASP.NET compatibility mode. You can access the WebOperationContext via its static Current property, as shown here in the context of a service operation that returns an Excel spreadsheet (.xls):

public Stream GetExcelFile()

{

 FileStream excelFile = GetExcelFile();

 WebOperationContext.Current.OutgoingResponse.ContentType =

    "application/vnd.ms-excel";

 return excelFile;

}

Properties of the WebOperationContext include IncomingRequest, IncomingResponse, OutgoingRequest, and OutgoingResponse. Each of these properties provides access to the associated HTTP headers. The preceding example illustrates a case where you might set the ContentType header to be more specific than application/octet-stream so that an Excel spreadsheet can be presented in the browser properly.

 

Conclusion

This introduction to the new Web programming supported by WCF as of .NET 3.5 gives you the foundation of exposing service operations as URI accessible over HTTP. This model supports POX services where you can post and return XML without the SOAP programming model but you also can extend this to JSON, REST, and RSS implementations with just a few more new features specific to each. I will discuss those in the next series of articles for this column. Until then, enjoy the code samples accompanying this article!

Download the samples for this article at http://www.dasblonde.net/downloads/asppromar08.zip.

 

Hide comments

Comments

  • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Publish