Exploring WCF Web Services
LANGUAGES: C#
TECHNOLOGIES: .NET 3.5 | WCF
Designing WCF Services for Web 2.0 Clients
Consuming WCF Services from ASP.NET AJAX and Silverlight
By Michele Leroux Bustamante
This monthly column explores how to design your WCF services to support ASP.NET AJAX and Silverlight clients comparing SOAP, XML, and JSON approaches. 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]!
In last month s column, Exposing Classic HTTP Endpoints
with WCF in .NET 3.5, I introduced the underlying WCF features released with
.NET 3.5 that make it possible to support a Web programming model for plain-old
XML (POX), JavaScript Object Notation (JSON), Representational State Transfer
(REST), and syndication services such as RSS. The article focused on POX
services and introduced new WCF types specific to Web programming, such as
WebHttpBinding, WebHttpBehavior, WebServiceHostFactory, WebServiceHost, and
WebChannelFactory
Web 2.0 Clients
Traditional thick clients such as those created with Windows Forms or Windows Presentation Foundation (WPF) have an advantage over Web 2.0 clients in that they can work with a rich object model to communicate with WCF services. Client developers typically generate a proxy from the service WSDL or through metadata exchange, and use this and its related WCF client configuration to build a communication channel capable of calling a service endpoint using SOAP messaging. With this programming model, clients and services can work with all the rich protocols available with WCF, including those WS* protocols for security, reliable messaging, and transactions.
Web 2.0 clients typically rely on JavaScript to communicate with services remotely, thus they don t have access to this rich programming model, nor can they support the breadth of WS* protocols. Using JavaScript, you can call SOAP, XML, or JSON service endpoints, but the choice of protocol plays a role in the relative simplicity of the required client code to call the service. Because a typical service proxy cannot be used by JavaScript, alternate methods of invocation must be used, such as the classic XmlHttpRequest object or special JavaScript proxies that can automatically be generated in some cases.
Figure 1 illustrates a WCF service exposing endpoints for SOAP, XML, and JSON consumption by an ASP.NET page. In this example, the client uses XmlHttpRequest directly from page script to call each service. As I ll discuss, the coding effort for this model can be cumbersome.
Figure 1: XmlHttpRequest can be used
to call SOAP, XML, and JSON services.
Figure 2 illustrates an alternate approach for JSON endpoints where an ASP.NET AJAX-enabled page uses a JavaScript proxy to call the service. In this case, the call to XmlHttpRequest is handled by the underlying ASP.NET AJAX libraries, and the ASP.NET ScriptManager control handles generating the proxy dynamically for the client to invoke. The client code simply creates an instance of the proxy to make calls to the service.
Figure 2: JSON supports
automatically generated JavaScript proxies to call JSON services.
Silverlight clients are a special breed, in that they can use traditional JavaScript calls to invoke services, they can use the ScriptManager if you use ASP.NET 3.5 Futures, or they can use .NET code to invoke a service using a special object model that wraps the XmlHttpRequest. Figure 3 summarizes this list of options.
Client Technology |
Programming Model |
Protocol Support |
ASP.NET AJAX/JavaScript |
XmlHttpRequest |
SOAP/XML/JSON |
ASP.NET AJAX/ScriptManager |
Generated Proxy |
JSON |
Silverlight/JavaScript |
XmlHttpRequest |
SOAP/XML/JSON |
Silverlight/ScriptManager |
Generated Proxy |
JSON |
Silverlight/.NET Code |
BrowserHttpWebRequest |
SOAP/XML/JSON |
Figure 3: Web 2.0 client options to call WCF services.
In the rest of this article, I ll take you on a tour of these programming models as I describe how the WCF service should be designed and configured for each.
XmlHttpRequest and SOAP Services
Many services expose only SOAP endpoints, so it is important to be able to use SOAP protocol from any client environment, including Web 2.0 clients. Although cumbersome, it is possible to consume a SOAP service from JavaScript using the XmlHttpRequest object. Consider this simple WCF service contract:
[ServiceContract(Namespace = "thatindigogirl.com/samples")]
public interface ISOAPService
{
[OperationContract]
string EchoString(string s);
}
Assuming the service implementing this contract will be consumed by the XmlHttpRequest object, it would be necessary to provide an endpoint that doesn t rely on addressing any of the WS* protocols thus, BasicHttpBinding is the most appropriate choice, as shown here:
contract="ISOAPService" /> Figure 4 shows the code to invoke this service using the
XmlHttpRequest object. Figure 4: Using
XmlHttpRequest to call a SOAP service. The request posts the SOAP message to the target service
endpoint; in this case, SOAPService.svc. For the message to be well-formed, the
object must first be initialized with the correct headers, indicating the
correct content type and SOAP action. Furthermore, the entire In last month s column I explained how you can use the new
WCF features released with .NET 3.5 to expose POX endpoints. This changes your
service contracts in that they must apply the WebGetAttribute for operations
that respond to HTTP GET requests, or the WebInvokeAttribute for any HTTP verb
including GET, POST, PUT, or DELETE (POST is the default behavior). Using the
same attributes, you would also specify WebMessageFormat.Xml so the service
channel will process plain XML requests because the default for these
attributes initializes the runtime to use JSON. The following example shows how
the service operation from the SOAP example earlier would be configured to
support XML endpoints: [ServiceContract(Namespace
= "thatindigogirl.com/samples")] public interface IXmlService { [OperationContract] [WebInvoke(RequestFormat
= WebMessageFormat.Xml, ResponseFormat = WebMessageFormat.Xml, BodyStyle =
WebMessageBodyStyle.Wrapped)] string
EchoString(string s); // other operations } To support XML messaging, you must expose an endpoint
using WebHttpBinding and enable the WebHttpBehavior: contract="IXmlService" behaviorConfiguration="webHttpBehavior" /> ... WebHttpBinding with WebHttpBehavior enables support for
POX, REST, JSON, and other non-SOAP services. Recall from last month that you
also can enable a default endpoint and behavior by using the WebServiceHost
instead of the default ServiceHost type. This is done by associating the
WebServiceHostFactory with your .svc endpoints, as follows: <%@ ServiceHost Service="XmlService"
CodeBehind="~/ App_Code/XmlService.cs" Factory= "System.ServiceModel.Activation.WebServiceHostFactory"
%> You now have a service that can receive XML messages.
Calling an XML endpoint using XmlHttpRequest is slightly more straightforward
than with a SOAP endpoint, but the tedium of preparing the correct XML request
format and parsing the XML response still exists. Figure 5 shows the code to
invoke an XML service using XmlHttpRequest. Figure 5: Using
XmlHttpRequest to call an XML service. If the contract is configured to use
WebMessageBodyStyle.Wrapped, the request must be formatted to include the
correct namespace, a wrapper element named for the operation, and any nested
parameter elements. The request and response for EchoString then looks like
this: // request // response hello If you change the contract to use WebMessageBodyStyle.Bare,
a // request // response When you use the XmlHttpRequest object to call an XML
service, the code differs from a SOAP service in that the target URL includes
the operation name, the XML payload is simplified somewhat (no need to create a
compete SOAP message), and the content-type header is application/xml instead of
text/xml. The programming model is still cumbersome for both, and still
requires knowledge of the appropriate message payload. JSON provides many benefits to the Web 2.0 clients. The
message payload is greatly simplified, and there are many supporting features
of the client programming model that naturally work with JSON to streamline the
coding effort. Specifically, processing JSON responses from a service is super
easy. You can expose JSON endpoints for your WCF services if you
define a contract that supports JSON and enable the appropriate channel
behaviors to process JSON messages. As with POX services, JSON services are now
easily supported by WCF, thanks to the new Web programming features in .NET 3.5
that I discussed last month with a few slightly different switches. To design
a service contract supporting JSON messaging, first select
WebMessageFormat.Json for the request and reply format for each operation, as
shown here: [OperationContract] [WebInvoke(RequestFormat = WebMessageFormat.Json, ResponseFormat =
WebMessageFormat.Json, BodyStyle =
WebMessageBodyStyle.WrappedRequest)] string EchoString(string s); In fact, WebMessageFormat.Json is the default setting, but
it is always a good idea to explicitly declare your usage intentions to avoid
confusion. The WebScriptEnablingBehavior also requires
WebMessageBodyStyle.WrappedRequest. As with XML endpoints, you use WebHttpBinding with the
WebHttpBehavior for JSON endpoints to be consumed by XmlHttpRequest. However,
if you want to expose JSON services for streamlined consumption by ASP.NET AJAX
clients, you should use the WebScriptEnablingBehavior, shown here: contract="IJsonService" behaviorConfiguration="webScriptBehavior" /> ... This behavior enables a number of JSON-related channel
features, such as support for automatic proxy generation. You can automatically
enable this behavior by employing the WebScriptServiceHostFactory in your .svc
definition removing the need to explicitly associate the endpoint behavior: <%@ ServiceHost Service="JsonServiceScript" CodeBehind="~/App_Code/JsonServiceScript.cs"
Factory="System.ServiceModel.Activation.WebScriptServiceHostFactory"
%> If you expose a simple JSON endpoint, you can consume it
from any AJAX client using the
XmlHttpRequest object previously discussed for SOAP and XML. Once again, you
are then forced to write code to construct the request object, set the correct
URL and content-type, and figure out the correct parameters and resulting JSON
format for posting to the service operation. Considering the JSON service
contract in the previous section, a call to EchoString would look like this: request = new XMLHttpRequest( ); request.open ("POST",
"JsonServiceWeb.svc/EchoString", true); request.onreadystatechange = OnEchoStringComplete; var params= '{"s":"hello"}'; request.setRequestHeader("Content-type",
"application/json"); request.send(params); JSON is certainly much simpler, not to mention shorter,
than formatting SOAP or XML payloads but the effort to create the
XmlHttpRequest is still necessary to figure out operation types for parameters
and return values. Fortunately, you can reduce this code to a single line if
you leverage the WebScriptEnablingBehavior alongside the ASP.NET ScriptManager
control. After you enable the WebScriptEnablingBehavior, a magical
thing happens to your service endpoint: You now can browse to the endpoint,
append a /js suffix, and, presto, you have a JavaScript proxy that encapsulates
calls to XmlHttpRequest to reach the service endpoint. For example, if you
browse to the following JsonServiceWebScript.svc endpoint, you ll be prompted
to open the proxy file or save it to disk: http://localhost/... /JsonServiceWebScript.svc/js Save the file with the extension .js and you can inspect
it to see what the proxy looks like in JavaScript. The proxy exposes functions
that match the signature of your service operations. I m not going to dissect
the proxy here, but you ll need to know the namespace that was generated from
your service contract in order to use the proxy. For example, the service
contract above uses this namespace: [ServiceContract(Namespace =
"thatindigogirl.com/samples")] This results in the following JavaScript namespace and
proxy name (in the form of a JavaScript function): Type.registerNamespace('thatindigogirl.com.samples'); thatindigogirl.com.samples.IjsonServiceScript =function() {...} That means, to call EchoString using the proxy, you would
use this syntax: thatindigogirl.com.samples.IJsonServiceScript.EchoString( hello ); This proxy relies on the ASP.NET AJAX JavaScript libraries
to execute the XmlHttpRequest under the covers. Rather than saving the
generated proxy and including it in your page by hand, the ASP.NET
ScriptManager control handles including the necessary script libraries and
generating the proxy for any service references you provide it. For example, to
reference JsonServiceWebScript.svc, you would provide the following service
reference: When the page is parsed, the ScriptManager will inject a
script reference such as this into your page: Now when you write code inside the page to invoke service
operations, you can use the proxy directly, and you even get IntelliSense! This
makes it incredibly easy to invoke JSON services using a familiar object model
for each operation. The first parameters to each operation will be those
defined by the service. The proxy also adds three additional parameters that
you can pass to the call: For example, when you call EchoString, the code, and
handlers, might look like this: function OnEchoString() { thatindigogirl.com.samples.IJsonServiceScript.EchoString( "hello",
OnEchoStringComplete, OnError); } function OnEchoStringComplete() { $get("stringResult").innerText = "EchoString() called with result: " +
result; } function OnError(error, userContext, methodName) { $get("stringResult").innerText = "Error calling " + methodName + ": " +
error.get_message(); } Silverlight clients can call services in three ways: The last option implies that you create a Silverlight page
that uses code-behind instead of JavaScript. You can create new Silverlight
pages with code-behind from a Silverlight project (as opposed to a Silverlight Web
site). You can then reference the Silverlight project from a Web site using the
new Add Silverlight Link menu from the project node in Solution Explorer (see
Figure 6). This copies the XAML files from the referenced project, and copies
the binary (with the code-behind files) to the \ClientBin directory beneath the
Web site keeping those files up-to-date as you recompile the solution with
changes. In the code-behind you can write code to communicate with
services using the BrowserHttpWebRequest object. This represents a slightly
different object model to do the same thing you would with XmlHttpRequest
which means you could call SOAP, XML, or JSON services. JSON is the more likely
scenario for Web 2.0, so I ll use that as an example here. Figure 7 shows the required code to invoke a JSON service
from a Silverlight code-behind file using BrowserHttpWebRequest. This object
model isn t dissimilar to XmlHttpRequest, in that you must provide the Uri for
the service operation, set the content-type and length, and populate the
outgoing request stream with the JSON-formatted content. Likewise, to process
the response you grab the response stream and read the content to populate UI
if applicable. string serviceAddress = HtmlPage.DocumentUri.AbsoluteUri; System.Uri operationUri = new
System.Uri(serviceAddress.Substring(0, serviceAddress.IndexOf("/Default.aspx")) +
"/JsonService.svc/EchoString"); BrowserHttpWebRequest request = new
BrowserHttpWebRequest(operationUri); string content =
"{\"s\":\"hello\"}"; request.Method = "POST"; request.ContentType = "application/json"; request.ContentLength = content.Length; HttpWebResponse response = null; using (StreamWriter writer = new
StreamWriter(request.GetRequestStream())) { writer.Write(content); writer.Flush(); response =
request.GetResponse(); } if (response.StatusCode != HttpStatusCode.OK) { string s =
response.StatusDescription; } else { using (StreamReader
reader = new StreamReader(response.GetResponseStream())) { string s =
reader.ReadToEnd(); s = s; } } Figure 7: Using
BrowserHttpWebRequest to call a JSON service. This article provides a crash course in Web 2.0 options
for consuming your WCF services. Now you know which client technologies can
call SOAP, XML, or JSON services, which programming model you can use for each,
and how to configure your services to support each model. Clearly, the best
possible option is to use the automatically generated JavaScript proxy for
ASP.NET AJAX clients, including Silverlight, because it provides a friendly
object model with significantly less code required. The code samples associated
with this article illustrate each example and provide you with the remaining
context for each scenario so be sure to look at those as well! Enjoy! Download the samples
for this article at http://www.dasblonde.net/downloads/aspproapr08.zip. Michele Leroux
Bustamante is Chief Architect of IDesign Inc., Microsoft Regional Director
for San Diego, and Microsoft MVP for Connected Systems. At IDesign, Michele
provides training, mentoring, and high-end architecture consulting services
focusing on Web services, scalable and secure architecture design for .NET,
federated security scenarios, Web services, interoperability, and globalization
architecture. She is a member of the International .NET Speakers Association
(INETA), a frequent conference presenter, conference chair for SD West, and is
frequently published in several major technology journals. Michele also is on
the board of directors for IASA (International Association of Software
Architects), and a Program Advisor to UCSD Extension. Her latest book is Learning WCF (O Reilly, 2007); see her book
blog at mailto:www.thatindigogirl.com.
Reach her at mailto:[email protected], or
visit http://www.idesign.net and her main
blog, http://www.dasblonde.net. Learning WCF, O Reilly 2007: http://www.thatindigogirl.com (get
all the book code here!) Michele s blog: http://www.dasblonde.net IDesign: http://www.idesign.net WCF Home: http://wcf.netfx3.com XmlHttpRequest and XML Services
helloJSON-enabled Services
XmlHttpRequest and JSON Services
JSON Proxies
Silverlight Clients
Figure 6: From a Web site, adding a
link to a Silverlight project. Conclusion