Exposing Classic HTTP Endpoints with WCF in .NET 3.5
Introducing the New Web Programming Model for WCF
October 30, 2009
This monthly column explores the enhancements made to theWCF programming model in .NET 3.5 that specifically support Web programming. Ifyou have questions about migrating your existing ASMXor WSE Web services to WCF, or questions regarding WCF as it relates to Webservices, send them to [email protected]!
Before SOAP Web services were introduced in 1999, and fora period of time during the early adoption phase of Web services, mostapplications handled data-centric Web programming with the HTTP protocol. Thisusually meant standing up an endpoint, such as an ASP or JSP page (in thosedays), and processing requests to that endpoint using HTTP GET as the protocolfor data retrieval, and HTTP POST for sending complex types for processing. XMLschemas were usually used to define the payload of posted parameters, and ofresponse streams. The drawback of this approach, and part of the reason SOAPwas such an exciting technology, is that the list of HTTP-addressable URI wasnot automatically published (as in WSDL); the schemas defining the messagepayload for each GET and POST were not automatically published (again, as inWSDL); and client applications could not programmatically generate code tobuild the HTTP request to consume a collection of endpoints (as in proxygeneration).
Despite the fact that SOAP Web services and WS* provide arich programming experience to resolve the limitations of the HTTP programmingmodel, 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 HTTPendpoint was possible thanks to the highly extensible and flexible programmingmodel but it wasn t easy. With the release of .NET 3.5, new WCF features havebeen 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 theservice model. In this article, I ll introduce the new features that supportthese technologies, focusing specifically on POX to uncover core featurescommon to all Web programming approaches. I will cover JSON, REST, and RSS insubsequent articles.
Core Web Programming Features
The release of .NET 3.5 includes a new assemblySystem.ServiceModel.Web.dll which includes features for the Web programmingmodel for WCF. A combination of new contract attributes, bindings, messageencoders, behaviors, and service hosts are among the new types available tosupport this model. Figure 1 lists the core new types I ll be discussing inthis 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 | A new derivative of ChannelFactory 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: Coretypes supporting POX in the new Web programming model for WCF.
Now let s take a closer look at how you would use these featuresin practice.
Designing a POX Service Contract
To design a service contract with operations that can bereached via the HTTP protocol, a WebGetAttribute or WebInvokeAttribute must beadded to the operation. Figure 2 shows a service contract that leverages bothattributes appropriately, and its implementation on aservice type. For GET operations like GetItems and GetItem, WebGetAttribute isapplied; for POST operations like SaveItem or DeleteItems, WebInvokeAttributeis applied.
[ServiceContract(Namespace="http://www.thatindigogirl.com/ samples/VS2008")]public interface IQueryStringService{ [OperationContract] [WebGet(UriTemplate = "GetItems")] Dictionary 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 s_itemStorage = new Dictionary(); public Dictionary GetItems() { return s_itemStorage as Dictionary; } 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: Aservice contract and implementation exposing HTTP GET and POST operations.
The UriTemplate property for both attributes determinesthe format of the URI required to invoke an operation.Let s assume the base address of the service ishttp://localhost:8000/QueryStringService. In that case, the UriTemplate GetItemsrequires callers to use the URIhttp://localhost:8000/QueryStringService/GetItems to invoke the GetItemsmethod. The resulting HTTP request looks like this:
GET /QueryStringService/GetItems HTTP/1.1Content-Type: application/xml; charset=utf-8Host: localhost:8080
In this case, no query parameters are passed, but theoperation is responsible for returning content in the HTTP response. In thiscase, that content is a dictionary that serializes to the content in Figure 3in the response.
HTTP/1.1 200 OKContent-Length: 1001Content-Type: application/xml; charset=utf-8Server: Microsoft-HTTPAPI/2.0Date: Mon, 07 Jan 2008 00:09:22 GMT0Item 01Item 12Item 23Item 34Item 45Item 56Item 67Item 78Item 89Item 9
Figure 3: Adictionary that serializes content in the response.
The UriTemplate GetItem?id={key}requires callers to use the URIhttp://localhost:8000/QueryStringService/GetItem?id=0 to invoke GetItem andretrieve the first item in the dictionary. The query string parameter isspecified with a placeholder value in curly braces, in this case {key} andthis must match the operation parameter that receives the placeholder. Theresult is an HTTP GET that passed a query string such as:
GET /QueryStringService/GetItem?id=1 HTTP/1.1Content-Type: application/xml; charset=utf-8Host: localhost:8080
This time, the response returns only a string, and lookssomething like this:
HTTP/1.1 200 OKContent-Length: 91Content-Type: application/xml; charset=utf-8Server: Microsoft-HTTPAPI/2.0Date: Mon, 07 Jan 2008 00:09:22 GMTUpdated Item 1
The definition of SaveItem has two operation parameters:key and item. The first, key, is a query string parameter as specified by theUriTemplate. The second, item, will be populated with the data posted in thebody of the request. In this case, the WebInvokeAttribute is required tospecify the request as an HTTP POST. This is shown explicitly in the Methodparameter to the attribute, but the default verb is POST, thus these areequivalent:
[WebInvoke(UriTemplate = "SaveItem?id={key}", Method = "POST")][WebInvoke(UriTemplate = "SaveItem?id={key}")]
The Method parameter is only applicable toWebInvokeAttribute, and it can specify any valid or custom HTTP verb.
The HTTP POST content passed to the second parameter ofSaveItem looks like this on the wire:
POST /QueryStringService/SaveItem?id=0 HTTP/1.1Content-Type: application/xml; charset=utf-8Host: localhost:8080Content-Length: 83Expect: 100-continueConnection: Keep-AliveItem 0
The service model unpacks the posted content from its element and maps it to the second parameter.
Both the WebGetAttribute and WebInvokeAttribute supportadditional properties beyond UriTemplate and Method (for the latter); they are:RequestFormat, ResponseFormat, and BodyStyle. The following shows defaultsettings 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 messageencoder for the operation and can be one of WebMessageFormat.Xml orWebMessageFormat.Json. The default is Xml, but Json is useful in specifically supportingthe AJAX client programming model.BodyStyle can be one of the following: WebMessageBodyStyle.Bare,WebMessageBodyStyle.Wrapped, WebMessageBodyStyle.WrappedRequest, orWebMessageBodyStyle.WrappedResponse. Bare is the default setting, but this onlyallows you to post a single parameter to the operation. As soon as multipleparameters are supported, you must change this to WrappedRequest (at least forthe 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 therequest 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 wrapperaround the content by the name of the operation , with eachparameter embedded within, as follows:
Content-Type: application/xml; charset=utf-8Host: localhost:8080Content-Length: 105Expect: 100-continueConnection: Keep-Alive0Item 0
Once your service contract is defined to include theappropriate HTTP request and response definitions, you can host the serviceimplementation in any of the usual WCF hosting environments (IIS6, IIS7/WAS, orself-hosting) with some minor changes to support the invocation process ofthe 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 isWebHttpBinding. This binding is necessary to expose service endpointsaccessible using classic HTTP requests. In addition to the binding, a newendpoint behavior has been introduced, WebHttpBehavior. This behavior is alsonecessary for messages received by the channel dispatcher to map incoming HTTPrequests and associated query string parameters and content to the correctoperation and related parameters. Thus, to construct a ServiceHost instancethat utilizes these new features, you would add an endpoint usingWebHttpBinding 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 configuration. The configuration and associated ServiceHost code shown in Figure4 illustrate adding an endpoint that customizes the WebHttpBindingconfiguration by increasing various message size and reader quotas. Inaddition, you can see that the has a behavior associated thatenables the WebHttpBehavior with the element.
Figure 4: Servicemodel configuration and ServiceHost code to manually construct theWebHttpBinding endpoint with WebHttpBehavior enabled.
WebServiceHost
A new ServiceHost derivative has been introduced tospecifically support Web programming. When you construct a new WebServiceHostinstead of ServiceHost, by default it will add a WebHttpBinding endpoint forthe base address of the service. The WebServiceHost also adds theWebHttpBehavior to all endpoints. Thus, the hosting code would be reduced tothe following:
WebServiceHost host = new WebServiceHost(typeof( Services.QueryStringService), new Uri( "http://localhost:8000/QueryStringService"));host.Open();
The resulting URI to invoke GetItems would behttp://localhost:8000/QueryStringService/GetItems. A base address is requiredfor the WebServiceHost to add the endpoint for you; otherwise, an explicitendpoint must be provided in code or configuration.
If you specify an endpoint, then WebServiceHost will notadd additional endpoints; thus, this is only a shortcut for cases where nocustomization to the WebHttpBinding configuration is required. For example, ifthe default security settings (None) and the default message size and readerquotas are acceptable, the default WebHttpBinding should work fine.
If you decide to specify the endpoint explicitly, there isno need to add the WebHttpBehavior manually, as it will be added for you by theWebServiceHost. Thus, the configuration shown in Figure 4 would change to thatshown in Figure 5.
Figure 5: The newconfiguration, if you decide to specify the endpoint explicitly.
WebServiceHostFactory
To host your services as part of a Web application (hostedin IIS or file-based for testing), a .svc endpoint is necessary to constructthe ServiceHost as part of message-based activation. Thus, to host your POXservices in a Web application there must be a way to specify that you want theWebServiceHost instead of the vanilla ServiceHost. This is done by specifyingthe WebServiceHostFactory in the Factory property of the @ServiceHostdirective:
<%@ ServiceHost Service= "Services.QueryStringService" Factory= "System.ServiceModel.Activation.WebServiceHostFactory" %>
This tells the service model to construct a WebServiceHostinstance, which will automatically add the default WebHttpBinding endpointusing the .svc URI, and add the WebHttpBehavior to all endpoints. As before, ifyou specify an endpoint in the web.config, the WebServiceHost will honor thatconfiguration just be sure to use the WebHttpBinding.
I discussed why you might want to create a customServiceHost or ServiceHostFactory in ControllingServiceHost Initialization at Run Time). For POX services, you ll instead extendWebServiceHost or WebServiceHostFactory.
Consuming POX Services
Although you can enable the ServiceMetadataBehavior for aPOX service, and it will generate a form of WSDL that lists the operations forthe 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 useWebChannelFactory to invoke them. This implies you have access to thefollowing 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 willadd the WebHttpBehavior to the client endpoint so operation calls areappropriately mapped between operation parameters and query strings, contentparameters, or content results. The following code illustrates the client codenecessary to invoke all operations of the service type in Figure 2:
WebChannelFactory factory = new WebChannelFactory( 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 items = proxy.GetItems();proxy.SaveItem("1", "Updated Item 1");string item = proxy.GetItem("1");
WebOperationContext
Another new construct, WebOperationContext, has beenprovided to give you ready access to HTTP headers in lieu of the HttpContextyou would use when running services in ASP.NET compatibility mode. You canaccess the WebOperationContext via its static Current property, as shown herein 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 includeIncomingRequest, IncomingResponse, OutgoingRequest, and OutgoingResponse. Eachof these properties provides access to the associated HTTP headers. The precedingexample illustrates a case where you might set the ContentType header to bemore specific than application/octet-stream so that an Excel spreadsheet canbe presented in the browser properly.
Conclusion
This introduction to the new Web programming supported byWCF as of .NET 3.5 gives you the foundation of exposing service operations asURI accessible over HTTP. This model supports POX services where you can postand return XML without the SOAP programming model but you also can extendthis to JSON, REST, and RSS implementations with just a few more new featuresspecific to each. I will discuss those in the next series of articles for thiscolumn. Until then, enjoy the code samples accompanying this article!
Download the samplesfor this article at http://www.dasblonde.net/downloads/asppromar08.zip.
Read more about:
MicrosoftAbout the Author
You May Also Like