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. 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. 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. 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: 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. 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: This time, the response returns only a string, and looks
something like this: 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: 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: 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: 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: 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: 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. 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: 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. Figure 4: Service
model configuration and ServiceHost code to manually construct the
WebHttpBinding endpoint with WebHttpBehavior enabled. 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: 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. Figure 5: The new
configuration, if you decide to specify the endpoint explicitly. 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: 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. 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: 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: 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): 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. 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. Core Web Programming Features
Designing a POX Service Contract
[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();
}
}
GET /QueryStringService/GetItems HTTP/1.1
Content-Type: application/xml; charset=utf-8
Host: localhost:8080
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>
GET /QueryStringService/GetItem?id=1 HTTP/1.1
Content-Type: application/xml; charset=utf-8
Host: localhost:8080
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>
[WebInvoke(UriTemplate = "SaveItem?id={key}",
Method = "POST")]
[WebInvoke(UriTemplate = "SaveItem?id={key}")]
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>
[OperationContract]
[WebInvoke(BodyStyle = WebMessageBodyStyle.Bare,
RequestFormat = WebMessageFormat.Xml, ResponseFormat =
WebMessageFormat.Xml, UriTemplate = "SaveItem?id={key}",
Method = "POST")]
void SaveItem(string key, string item);
[OperationContract]
[WebInvoke(BodyStyle = WebMessageBodyStyle.WrappedRequest,
UriTemplate = SaveItem )]
void SaveItem(string key, string item);
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>
WebHttpBinding and WebHttpBehavior
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();
<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>
WebServiceHost
WebServiceHost host = new WebServiceHost(typeof(
Services.QueryStringService), new Uri(
"http://localhost:8000/QueryStringService"));
host.Open();
<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>
WebServiceHostFactory
<%@ ServiceHost Service=
"Services.QueryStringService" Factory=
"System.ServiceModel.Activation.WebServiceHostFactory" %>
Consuming POX Services
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
public Stream GetExcelFile()
{
FileStream excelFile = GetExcelFile();
WebOperationContext.Current.OutgoingResponse.ContentType =
"application/vnd.ms-excel";
return excelFile;
}
Conclusion
Exposing Classic HTTP Endpoints with WCF in .NET 3.5
Introducing the New Web Programming Model for WCF
0 comments
Hide comments