Practical .NET
WCF and Service-Oriented Architectures
Windows Communication Foundation has steadily evolved to better support SOA and Web services. Here's how to get the most out of your WCF-based services architecture.
If you want to exploit service-oriented architecture (SOA) and services, you're going to have to go beyond just Web services. Visual Studio 2010 and Windows Communication Foundation (WCF) in the Microsoft .NET Framework 4 provide a comprehensive set of tools that reduces the barriers to building all of the different kinds of services you need.
While people may argue over the definition of SOA, it's more than a random collection of Web services. A SOA not only needs to provide interoperability through Web services, but must also provide acceptable performance to create responsive applications, support AJAX for Web applications, and enable business processes that stretch over hours or even weeks.
Every version of WCF has gotten closer to meeting all of those needs, though configuring WCF often seemed beyond the capabilities of mere mortals. This article introduces you to how the .NET Framework 4 and Visual Studio 2010 support all of these scenarios, while reducing the complexity of configuring WCF to meet your needs.
It's Not About Web Services
For too many developers, "service" means "Web service." That's not true. The fundamental definition of "service" is that it's some piece of business logic that can be called at an endpoint (feel free to embellish that definition with any additional criteria). That includes, but is not limited to, Web services.
Web services deliver two primary benefits: first, because they're based on industry standards they ensure interoperability; and second, they're contract-based. Web services use Web Services Description Language (WSDL), which provides an unambiguous communication of the technical requirements for connecting to a service (for example, input message formats, output message formats and the service endpoint) to stakeholders.
But Web services are inherently inefficient -- when it comes to performance, the best you can hope for is that a Web service will be "fast enough." The message format (Unicode text and XML) ensures that messages are bulky and slow to parse. If there's a slower protocol to handle communication than HTTP, no one is using it. Additionally, to ensure interoperability, Web services have to be targeted at the lowest common denominator for functionality: moving data. If most of your services are consumed by internal clients in a homogeneous software environment, Web services may not even be a major part of your SOA -- or, at least, they shouldn't be.
If you don't need interoperability and do need performance (and who doesn't?) then you're better off creating your services using more efficient tools: TCP/IP and binary message formats. That choice will give you faster communication, smaller packages to transport and faster parsing at both ends. Avoiding Web services also means you can take advantage of specific .NET Framework features, enabling you, for instance, to move whole objects around rather than just their data.
The good news is that WCF doesn't make this an either/or situation. You can configure a service both as an interoperable Web service with adequate performance, and as a high-efficiency service that isn't interoperable. The only requirement is that your service have two endpoints: one to be used to call your service in an interoperable way and one to call the service in a .NET-only, high-efficiency way.
The bad news is that it requires a bewildering set of entries in your service config file set to create both endpoints. Something like Listing 1, for instance.
And there's the problem: Who's willing to try and write this mess of XML (and then debug it)? The tool you need here is the Visual Studio WCF configuration editor. It won't eliminate configuration issues when creating a service with multiple endpoints, but it does reduce them.
Using the Configuration Editor
To create an interoperable service, you start by picking the WCF Service Library template in the Visual Studio New Project dialog. After you rename the default IService and Service files (and write the code you need in the service methods), you'll have a service configured as an interoperable Web service. Unless this service is only going to be accessed from external, non-.NET clients, the right thing to do is to go on to configure the service so it can also be accessed as a faster, high-efficiency service.
This is where the Service Configuration Editor comes in. Just right-click on your application's config file and pick Edit WCF Configuration (Figure 1).
[Click on image for larger view.] |
Figure 1. The WCF Configuration Editor UI has been revamped for Visual Studio 2010, but the editor is available in earlier versions of Visual Studio also. |
In the configuration window on the left, you'll see your service under the Services node. Drilling down through the service and its Endpoints folder will show you all the current configurations. By default, you'll have two unnamed endpoints: one for clients to call operations on the service and one for clients to extract your service WSDL information. Your first step should be to assign names to these endpoints so that you can refer to them in your thoughts (if nowhere else). The endpoint whose Binding property is set to wsHttpBinding is the access point for your "service as a Web service" -- give it a name like WebService.
Now you're ready to add a high-efficiency endpoint to your service. Right-click on the endpoints folder and select New Service Endpoint to display a blank set of endpoint properties. Set the name to something like TcpService and select netTcpBinding from the drop-down list in the Binding property.
You must also set the Contract property to the interface defined for your service. The easiest way to do that is to switch back to the WebService endpoint, copy the Contract entry from that endpoint, and paste it into the Contract property of your new TcpService. You can also set the Contract by clicking on the builder button (the one with three dots) that appears when you click in the property. In the Contract Type Browser dialog that appears, browse to your service DLL in the project bin/Debug folder. When you double-click on the DLL you'll see your service interface: select that and click the Open button to update the Contract property.
In order to use a high-efficiency TCP service, you need to provide a TCP-compatible address for clients to use to contact the service. By default, the only address provided in your service config file is an HTTP endpoint that supports your interoperable Web service.
To add a new address, first click on the Host node under your service. In the BaseAddresses box on the left you'll see the default HTTP URL that's been provided for your WebService endpoint. Double-click on the address to open an edit dialog and copy the URL. Then close the dialog and click on the New button at the bottom of the dialog to add a new endpoint. Paste the copied endpoint into the edit dialog and, at the start of the URL, change "http" to "net.tcp."
It's not enough for the two endpoints to have different protocols: they must be different addresses. It makes sense to have all the endpoints for a service be very similar (usually just differing in port numbers), which is why I copy and paste the Web service endpoint. I usually just change the port number for the new address, typically by adding 1 to the port number used in the Web service endpoint.
After closing the editor, you'll now have a service configured for interoperability (the Web service endpoint), and also for performance (the TCP endpoint). If you're never going to have an external or non-.NET client accessing the service, you can consider deleting the original Web service endpoint. While having that endpoint won't cost you much in production, it's another exposed service that hackers could exploit.
On the other hand, if you're going to allow other programs on the same computer to access your service, you can consider adding a named pipe endpoint. The process is the same: just pick the netNamedPipeBinding and then, when adding the base address, set the protocol to net.pipe and remove the port number.
This process is facilitated in the .NET Framework 4 by the willingness of WCF to take reasonable defaults in your configuration file. In previous versions of WCF you had to make a lot more entries to implement even the most common scenarios. The defaults in the .NET Framework 4 reduce the number of required settings significantly.
REST, JSON and WCF Data Services
Both of these services offer contracts: a machine-readable description of the message formats and datatypes used by your service that developers can use to create clients. But all the services in your SOA don't have to provide a contract. If a single team is building both the service and the consumer, as is often the case in AJAX scenarios, a contract isn't necessary -- the team can work out their calling conventions by talking to each other.
In AJAX scenarios, for instance, you have no choice about your calling protocol: It's going to be HTTP, so there's no need for a contract that specifies that. Further, as Web applications move to the new paradigm where the browser is engaged in an ongoing conversation between JavaScript in the client and services on the server, reducing the overhead on each transmission is critical. REST and JSON provide lower-overhead ways to call services and move data around. REST can reduce the request message to just a URL. JSON replaces the flexibility of WSDL-based message formats with a set of industry-wide conventions that require a lot less infrastructure than an XML-based format.
The WCF short answer for building RESTful Web services (with or without a JSON response) is to use WCF Data Services. WCF Data Services establishes its own conventions for RESTful requests, while piggybacking on the Atom standard for defining response messages (adding some micro-formats for what Atom doesn't natively support).
I have some issues with WCF Data Services, primarily that it's very much an entity-based solution. By sending only discrete entities, WCF Data Services reflects an object-oriented (OO) approach that I'm not convinced is appropriate for the distributed nature of a SOA implementation.
Specifically, I'm concerned that entity-based services result in fine-grained, "chatty" service interfaces that require a large number of messages to support any business activity. The network latency involved in making a service call over HTTP makes me favor services with operations that will satisfy the need of the business application with a single request. My reference is for services that reflect business processes and, while they do their internal work using OO code, their external response messages should return a "business transaction's worth" of data. That response will typically contain a diverse set of data and, perhaps, more data than the request needs (the application can cache the "extra" data to eliminate subsequent requests to the service).
That means that, rather than return the entities that I use for a good OO design (the WCF Data Services default), I prefer to return data transfer objects (DTOs) that satisfy the needs of the business. As an example, if a sales-order management system requests some customer data, I'm inclined to create a DTO that returns a complete Customer object with all of the customer's SalesOrder objects. My assumption is that making a second trip to the service is always going to take twice as long as making just one trip. On the other hand, sending twice as much data on a single request will probably add almost nothing to the request's time. I also suspect that reducing the number of requests to the service might well give me a more scalable application than reducing the amount of data in each request.
Again, with WCF it's not an either/or situation. If your SOA includes entity-based services, WCF Data Services will give you that out of the box. But if you want to create RESTful services with custom DTOs, you can do that also.
AJAX in ASP.NET
If you don't want to use WCF Data Service but you do want to create an AJAX-based application that issues RESTful requests to a service that's part of the same application, then you should consider building the application in ASP.NET MVC. ASP.NET MVC uses REST-like URLs for all requests. Accepting and returning JSON results is also natively supported out of the box (and the ASP.NET MVC 3 support for JSON is simpler and cleaner than that of ASP.NET MVC 2). ASP.NET MVC will let you create either chatty interfaces that return entities or the chunky DTO interfaces that I prefer. However, if you're an ASP.NET shop, moving to ASP.NET MVC just to create AJAX applications is probably overkill.
More good news: the new Microsoft WCF/REST templates make it easy to create an ASP.NET application with REST-with-JSON Web services that support AJAX development. In Visual Studio you'll need to go to the Online Templates of the Add New Project dialog to get to the project template. There, under the WCF section, you'll find several templates for creating RESTful Web services. Select the WCF REST Service Template for your language and version of .NET (you may need to click through an annoying licensing dialog as part of downloading the template). After selecting the template, you'll end up with an ASP.NET project with a file called Service1. You don't get, or need, the .svc file that comes with an ordinary WCF service added to an ASP.NET project.
While that works when creating a new ASP.NET application, you can also add an AJAX-compatible REST-with-JSON Web service to an existing ASP.NET application. Unfortunately there's no "WCF REST Service" item template, so you have to do a little more work. First, you'll need to add these elements to your web.config file:
<system.serviceModel>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
<standardEndpoints>
<webHttpEndpoint>
<standardEndpoint name="" helpEnabled="true"
automaticFormatSelectionEnabled="true"/>
</webHttpEndpoint>
</standardEndpoints>
</system.serviceModel>
REST requests are tied to your service through URLs, so you'll need to add these tags inside your <system.webserver> element to enable support for routing incoming requests:
<modules runAllManagedModulesForAllRequests="true">
<add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule,
System.Web, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b03f5f7f11d50a3a" />
</modules>
Unlike a WCF service library, other than adding those entries, you can now leave your config file alone. You'll need to add references to the System.ServiceModel.Web and System.ServiceMo del.Activation libraries.
Now, to create a service, you just add a class file to your application. To save yourself some typing, add these imports to the top of the file:
Imports System.ServiceModel
Imports System.ServiceModel.Web
Imports System.ServiceModel.Activation
Your class will need these attributes added to it:
<ServiceContract()>
<Activation.AspNetCompatibilityRequirements(
RequirementsMode:=AspNetCompatibilityRequirementsMode.Allowed)>
<ServiceBehavior(InstanceContextMode:=InstanceContextMode.PerCall)>
Public Class MyRESTService
The final step (other than writing business code in your service methods) is to add code to your Global.asax file's Application_Startup event that defines a rule that routes RESTful requests to your service class. This example ties any REST request with a URL beginning with "TheService" to a class called MyRESTService:
RouteTable.Routes.Add(New ServiceRoute("TheService",
New WebServiceHostFactory(), GetType(MyRESTService)))
With these changes, you can now use a URL to call a method on your service. To call a method named GetCustomer in the MyRESTService class, for instance, you'd use a URL like this:
http://www.MyServer.com/TheService/GetCustomer
You can pass parameters to your method using the URL's querystring. For instance, to specify a parameter with a CustomerId of "A123," you'd use a URL like this:
http://www.MyServer.com/TheService/GetCustomer?CustomerId=A123
You now have all the infrastructure in place and can start adding methods to your service. For a method to be invoked by the URL just described, the method needs to be called GetCustomer, have the WebGet attribute on it and accept a parameter called "CustomerId," as this example does:
<WebGet()>
Public Function GetCustomer(CustomerId As String) As Customer
Dim cust As New Customer(CustomerId)
Return cust
End Function
Testing this method is easy: In the Web tab of the project properties, set the Specific Page option to TheService/GetCustomer?CustomerId=A123 and press F5. The result returned from the method will be displayed in the browser.
You'll notice, however, that the result will be wrapped in XML rather than being sent as a JSON object. There are any number of ways to ensure a JSON response, but you can force a JSON response with this code:
Dim woc As New WebOperationContext(OperationContext.Current)
Return woc.CreateJsonResponse(cust)
Of course, creating this service isn't much good unless you can call it from JavaScript running in a browser. This JavaScript/jQuery code in a page will, once the page is loaded, make a RESTful call to the service and receive the Customer object as JSON into a parameter called data. The code then inserts the object's FirstName property into the <div> element, as shown in Listing 2.
Long-Running Services
All of these solutions have assumed that the response to your service call will be measured in milliseconds or, at worst, in seconds (and, in the case of the AJAX calls from a Web page, in a very few seconds). However, that ignores a significant number of services that an organization provides that have response times measured in hours, days or weeks -- services like "getting vacation permission" or "hiring a temporary employee." Even in these long-running processes, there are components that, as part of a SOA, can be supported with code. Supporting these services requires another toolset that integrates with WCF: Windows Workflow Foundation (WF).
The WF engine inherently supports issuing requests and catching the results hours, days or even weeks later. WF also provides tools for integrating and coordinating services, making it an excellent tool for creating services by orchestrating other services.
It makes sense (to me, at least) to segregate your long-running services into a separate project -- which is what the WCF Workflow Service Application template in the New Project dialog will give you. However, you can also integrate workflow services into existing applications.
For instance, if you want to extend an ASP.NET Web site that includes WCF services, you can use the WCF Workflow Service template in the New Item dialog to add the necessary .xamlx file to your site. You'll also need to add an .svc file to act as the endpoint for your service. The easiest way to create that .svc file is to use the WCF Service template from the Add New Item dialog and then delete its codebehind file. Then, within the .svc file, replace the existing directive at the top of the file with a directive that references your .xamlx in its Service attribute and the WorkflowServiceHostFactory in its Factory attribute. This example provides the endpoint for a workflow in the JobReview.xamlx file:
<%@ ServiceHost Language="VB" Debug="true" Service="JobReview.xamlx"
Factory="System.ServiceModel.Activities.Activation.WorkflowServiceHostFactory" %>
When you add a service reference for this project to a client, you'll see two endpoints: one for the .xamlx file and one for the .svc file: pick the .svc file.
Some additional configuration is still needed in for the default ReceiveRequest that appears as the first activity in your workflow. Its CanCreateInstance property must be set to true and you should change the default ServiceContractName (with the default tempura.org namespace) to something meaningful. But, after you've done that, it's just a matter of creating the workflow that will meet your organization's needs.
That may not cover all the functionality that your SOA should support -- a complete SOA is multifaceted and satisfies many needs within an organization. What's remarkable about the range of the WCF toolset is that it can support so many of those needs.