Using a SAML token for the Service Bus Transport Client Credential

The Windows Azure AppFabric Service Bus uses a class called TransportClientEndpointBehavior to specify the credentials for a particular endpoint.  There are four options available to you: Unauthenticated, SimpleWebToken, SharedSecret, and SAML.  For details, take a look at the CredentialType member.  The first three are pretty well described and documented – in fact, if you’ve spent […]

BizTalk – List of Macros

BizTalk – List of Macros

I’m always forgetting the list of macros that I use, which leads me to always be looking for them, so here’s a list of send macros that you can use: Macro name Substitute value %datetime% Coordinated Universal Time (UTC) date time in the format YYYY-MM-DDThhmmss (for example, 1997-07-12T103508). %datetime_bts2000% UTC date time in the format […]

Streaming over HTTP with WCF

Recently I had a customer email me looking for information on how to send and receive large files with a WCF HTTP service. WCF supports streaming specifically for these types of scenarios.  Basically with streaming support you can create a service operation which receives a stream as it’s incoming parameter and returns a stream as it’s return value (a few other types like Message and anything implementing IXmlSerializable are also supported). MSDN describes how streaming in WCF works here, and how to implement it here. There’s a few gotchas however if you are dealing with sending large content with a service that is hosted in ASP.NET. If you scour the web you can find the answers, such as in the comments here.

In this post I’ll bring everything together and walk you through building a service exposed over HTTP and which uses WCF streaming. I’ll also touch on supporting file uploads with ASP.NET MVC, something I am sure many are familiar with. The sample which we will discuss requires .NET 4.0 and ASP.NET MVC 3 RC. If you don’t have MVC you can skip right to the section “Enabling streaming in WCF”. Also it’s very easy to adopt the code to work for web forms.

The scenario

For the sample we’re going to use a document store. To keep things simple and stay focused on the streaming, the store allows you to do two things, post documents and retrieve them through over HTTP. Exposing over HTTP means I can use multiple clients/devices to talk to the repo.  Here are more detailed requirements.

1. A user can POST documents to the repository with the uri indicating the location where the document will be stored using the uri format “http://localhost:8000/documents/{file}”. File in this case can include folder information, for example the following is a valid uri, “http://localhost:8000/documents/pictures/lighthouse.jpg”.

Below is what the full request looks like in Fiddler. Note: You’ll notice that the uri (and several below) has a “.” in it after localhost, this is a trick to get Fiddler to pick up the request as I am running in Cassini (Visual Studio’s web server).

And the response

2. On a POST, the server creates the necessary folder structure to support the POST. This means if the Pictures sub-folder does not exist it will get created.

3. A user can GET a document from the repository using the same uri format for the POST. Below is the request for retrieving the document we just posted.

And the response: (To keep thing simple I am not wrestling with setting the appropriate content type thus application/octet-stream is the default).

4. The last requirement was I needed a simple front end for uploading the file.  For this I decided to use ASP.NET MVC3 to create a really simple front end. That also gave me a chance to dip my feet in the new Razor syntax.

Creating the application

The first thing I did was create a new MVC 3 application in Visual Studio. I called the application “DocumentStore” and selected to create an empty application when prompted.

Next thing I did was was add a HomeController and a new view Index.cshtml which I put in the Views/Home folder. The view which is below allows me to upload a document and specify the folder. I did tell you it was simple right?

Next I added an Upload view also in the View/Home folder. That view show she result of the upload.

Then in the HomeController I implemented the Upload action which contains the logic for POSTing to the service. I used HttpClient form the Rest Starter Kit to do the actual post.

Here’s the flow

  • Grab the uploaded file and the path.
  • Create the uri for the resource I am going to post.
  • Post the resource using HttpClient.
  • Set the filename info to return in the view.

Up until this point I’ve simply created the front end for uploading the document. Now we can get to the streaming part.

Enabling streaming over HTTP

Streaming is enabled in WCF through the transferMode property on the binding. In this case I set transferMode to “Streamed” as I want to support it bi-directionally. The setting accepts other values which can be used to enable streaming on the request OR the response. I also set the maxReceivedMessageSize to the maximum size that I want to allow for transfers. Finally I also also set the maxBufferSize, though this is not required. WCF 4.0 introduced default bindings which greatly simplifies this configuration. In this case because I’m going to use WCF HTTP, I can use the standard webHttpBinding as is shown.

If you read the MSDN articles I cited above, you might think I’m done, however because I’m hosting our service on ASP.NET, there’s one more critical setting I need to mess with. This one definitely threw me for a loop.

ASP.NET doesn’t know about WCF and it has it’s own limits for the request size which I have now increased to match my limit. Without setting this the request will be killed if it exceeds the limits. This setting and the previous settings have to kept in sync. If not, either ASP.NET will kill the request, or WCF will return a Status 400: Invalid Request. Service tracing is definitely your friend in figuring out any issues on the WCF site.

Building the DocumentsResource

Now that streaming is enabled, I can build the resource. First I created a DocumentsResource class which I marked as a ServiceContract and configured to allow hosting in ASP.NET.

 

Registering the resource through routes (.NET 4.0 Goodness!)

Because I am using .NET 4.0, I can take advantage of the new ASP.NET routing integration to register my new resource without needing an .SVC file. To do this, I create a ServiceRoute in the Global.asax specifying the base uri and the service type (resource).

Document retrieval (GET)

Our document store has to allow retrieval of documents. Below is the implementation.

As I mentioned earlier, when you enable streaming your operations only work with a limited set of input / return values. In this case my GET method returns a stream which represents the document. Notice the method is has no parameters. When building a streamed service, the method can only have a single parameter which is either of type stream, message or which implements IXmlSerializable. This forces me to jump through a small hoop to get access to the path info for the document, but it’s manageable.

Here’s the logic in the method.

  • The method is annotated with a WebGetAttribute to indicate that it is a GET. The UriTemplate is set to “*” in order to match against any uri that matches the base uri for this service.
  • Next I grab all the uri segments and concatenate in order to create the relative file location. For example for the uri “http://localhost:8080/documents/pictures/lighthouse.jpg” this will result in “pictures\lighthouse.jpg” assuming the base address is “documents’”.
  • If a file path was actually passed in, I then create a the relative file path by concatenating the relative location with the server root and the relative path for the doc store.
  • Finally I create a stream pointing to the file on disk and I return it. The resulting stream is then returned back to the client.

Document upload (POST)

Whereas in the Get method we return a stream, in the POST case we simply accept a stream. In this case we need to create the directory if it does not exist and write the document to disk so there’s a bit more work.

Here’s the logic:

  • The method is annotated with WebInvoke with the method set to POST. Similar to the GET, the UriTemplate is set to “*” to match everything.
  • Next I grab on to the uri of the request so that I can return it later as part of the Location header in the response. This is appropriate according to the HTTP spec. It’s important that I grab it before I actually stream the file as I found if I wait till after, the request is disposed and I get an exception.
  • Similar to the GET I also grab the path segments to create the file name and create the local path.
  • Next I check to see if the directory that was requested actually exists. If it does not, I create it.
  • I create a stream to write the file to disc and use the convenient CopyTo method to copy the incoming stream to it.
  • Finally I set the status code to Created and set the location header.

Testing out the app

That’s it, the app is done. Now all there is to do is launch it and see the streaming in action as well.

Like I said, simple UI. . Go press “Browse” and then grab an image or a video. In this case I’ll grab the sample wildlife.wmv included in win7.

Next enter “Videos” for the folder. You can also put a deep hierarchy if you want like Foo/Bar/Baz.

Select Upload and you will see the following once it completes.

Now that the file is uploaded, it can be retrieved from the browser.

Which in this case will cause Windows media player to launch and show our video,.w00t!

Now that our document store is in place, we can use different clients to access it. In this example we used a browser / MVC application to keep things simple. The same store can also be accessed from a mobile device line an IPad,, a Silverlight application, or from a desktop application. All it needs is a way to talk HTTP.

Why not just MVC?

I know some folks are going to see this and say “why didn’t you just use MVC?’”.  I could have accomplished the upload / retrieval completely through MVC. The main point of this post however was to show how you can use streaming with WCF. Let’s move on

Get the code here: http://cid-f8b2fd72406fb218.office.live.com/self.aspx/blog/DocumentStore.zip

FREE 30-day Azure developer accounts! Hurry!

If you’ve worked with Azure you know that the SDK provides a simulation environment that lets you do a lot of local Azure development without even needing an Azure account. You can even do hybrid (something I like to do) where you’re running code locally but using Azure storage, queues, SQL Azure.

However, wouldn’t it be nice if you had a credit-card free way that you could get a developer account to really push stuff up to Azure, for FREE? Now you can! I’m not sure how long this will last, or if there’s a limit on how many will be issued, but right now, you can get (FOR FREE):

The Windows Azure platform 30 day pass includes the following resources:

  • Windows Azure
    • 4 small compute instances
    • 3GB of storage
    • 250,000 storage transactions

  • SQL Azure
    • Two 1GB Web Edition database

  • AppFabric
    • 100,000 Access Control transactions
    • 2 Service Bus connections

  • Data Transfers (per region)
    • 3 GB in
    • 3 GB out

Important: At the end of the 30 days all of your data will be erased. Please ensure you move your application and data to another Windows Azure Platform offer.

You may review the Windows Azure platform 30 day pass privacy policy here.

 

Sign up at:

http://www.windowsazurepass.com/?campid=C4624604-86EE-DF11-B2EA-001F29C6FB82

promo code DP001

 

Enjoy!

ACSUG December Meeting: Microsoft BizTalk Server 2010 Demo with Thiago Almeida

ACSUG December Meeting: Microsoft BizTalk Server 2010 Demo with Thiago Almeida

The next Auckland Connected Systems User Group meeting is set for Thursday the 2nd of December at 5:30pm. Presentation starts at 6:00pm. It’s free and we will have the usual pizza, drinks and giveaways. Register here. Microsoft BizTalk Server 2010 Demo Presentation BizTalk Server is Microsoft’s premier server for building business process and integration solutions. […]

Getting started with WCF Discovery

One of the cool new features in WCF 4 is the support for WS-Discovery.

 

So what makes WCF Discovery so cool?

Normally when a client application wants to connect to a WCF service it has to know several thing about the service like the operations it supports and where it is located. Knowing the operations at design time makes sense, after all this is functionality you are going to call. But the address makes less sense, that is a deployment things and something we don’t really want to be concerned with at design time. So we can specify the service address in the configuration file of the client app and we can change this as needed. However that does mean that whenever we move the WCF service to a different location, for example a new server, we need to go into each clients configuration file and update it with the new address. Not so nice and error prone as we are manually editing a bunch of XML

Decoupling the client from the actual address of the WCF service is what WCF Discovery does meaning we can move our WCF service to a new machine and each client will automatically start using the new address. Now how cool is that?

 

The two modes of WCF Discovery

There are two different ways of using WCF Discovery. The first is add-hoc and is real simple because there is no central server involved and the second being managed which is more capable but also depends on some central service being available. In his post I will be taking a closer look at the simple add-hoc mode.

 

The beauty of add-hoc WCF Discovery

The real nice thing about add-hoc WCF Discovery is that a service can just add a discovery endpoint and the client can check for this whenever it needs to call the service. This is done using the UDP protocol through a broadcast message meaning that a service will just discoverable as long as it is on the same subnet. This means that on lots of cases where a service is used on a LAN this will just work without any extra configuration of a central discovery service. Nice and simple, just the way I like it

 

Adding add-hoc WCF Discovery to a service

To demonstrate how to use add-hoc discovery I wrote a real simple service and self host it using the following code:

using System;
using System.ServiceModel;
 
namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var host = new ServiceHost(typeof(MyService)))
            {
                host.Open();
                Console.WriteLine("The host is listening at:");
                foreach (var item in host.Description.Endpoints)
                {
                    Console.WriteLine("{0}\n\t{1}", item.Address, item.Binding                        );
                }
                Console.WriteLine();
                Console.ReadLine();
            }
        }
    }
 
    [ServiceContract]
    interface IMyService
    {
        [OperationContract]
        string GetData(int value);
    }
 
 
    class MyService : IMyService
    {
        public string GetData(int value)
        {
            var result = string.Format("You entered \"{0}\" via \"{1}\"", 
                value, 
                OperationContext.Current.RequestContext.RequestMessage.Headers.To);
            
            Console.WriteLine(result);
            return result;
        }
    }
}

Note there is nothing about WCF Discovery here at all, it is just a standard WCF service.

 

This WCF service has a configuration file that is almost standard. It exposes the service using three different endpoints, each with a different binding. The WCF Discovery extensions are added by adding an extra endpoint of kind udpDiscoveryEndpoint and the <serviceDiscovery/> element in the service behavior section.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <services>
      <service name="ConsoleApplication1.MyService">
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:9000"/>
            <add baseAddress="net.tcp://localhost:9001/"/>
          </baseAddresses>
        </host>
        <endpoint address="basic"
                  binding="basicHttpBinding"
                  contract="ConsoleApplication1.IMyService" />
        <endpoint address="ws"
                  binding="wsHttpBinding"
                  contract="ConsoleApplication1.IMyService" />
        <endpoint address="tcp"
                  binding="netTcpBinding"
                  contract="ConsoleApplication1.IMyService" />
        <endpoint kind="udpDiscoveryEndpoint" />
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <serviceDiscovery/>
          <serviceMetadata httpGetEnabled="true"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>

 

If we run this service it prints a message to the console for each endpoint it is listening on.

Most of these endpoints are very standard, it’s the last one urn:docs-oasis-open-org:ws-dd:ns:discovery:2009:01 that stands out and is the UDP discovery endpoint.

 

Using this discovery enabled WCF service from a client application

The first option on the client is to use the DiscoveryClient and have it determine all possible endpoints for the service. This type lives in the System.ServiceModel.Discovery.dll assembly so you will need to add a reference to that first. Using the DiscoveryClient a FindCriteria we can now search for published endpoints and use one like this:

var client = new DiscoveryClient(new UdpDiscoveryEndpoint());
var criteria = new FindCriteria(typeof(IMyService));
var findResult = client.Find(criteria);
 
foreach (var item in findResult.Endpoints)
{
    Console.WriteLine(item.Address);
}
Console.WriteLine();
var address = findResult.Endpoints.First(ep => ep.Address.Uri.Scheme == "http").Address;
Console.WriteLine(address);
var factory = new ChannelFactory<IMyService>(new BasicHttpBinding(), address);
var proxy = factory.CreateChannel();
Console.WriteLine(proxy.GetData(42));
((ICommunicationObject)proxy).Close();

This code will work just fine but there are two problems with it. First of all the client.Find() call will take 20 seconds. The reason is it wants to find as many endpoints as it can and the default time allowed is 20 seconds. You can set this to a shorter time but in that case you run the risk of making the timeout too short and the service not being discovered. The other option is to set the criteria.MaxResults to one so the client.Find() call return as soon as single endpoint is found.

The bigger problem here is the binding. I am exposing a BasicHttpBinding, a WSHttpBinding and a NetTcpBinding from my service and the DiscoveryClient doesn’t tell me which binding needs to be used with which address.

 

The DynamicEndpoint to the rescue

There is a different way of using WCF Discovery from the client that is far easier if all we want to do is call a specific service and we don’t really care about all addresses exposed and that is the DynamicEndpoint. Using the DynamicEndpoint we can specify which service we want to call and which binding we want to use and it will just do it for us if there is an endpoint for that binding. The following code calls the service with each of the three bindings supported.

foreach (var binding in new Binding[] { new BasicHttpBinding(), new WSHttpBinding(), new NetTcpBinding() })
{
    var endpoint = new DynamicEndpoint(ContractDescription.GetContract(typeof(IMyService)), binding);
    var factory = new ChannelFactory<IMyService>(endpoint);
    var proxy = factory.CreateChannel();
    Console.WriteLine(proxy.GetData(Environment.TickCount));
    ((ICommunicationObject)proxy).Close();
}

This results in the following output where we can see the different endpoints being used.

 

What happens if there is no endpoint with the required binding varies depending on the missing binding. If I remove the BasicHttpBinding from the service and run the client it will find the WSHttpBinding instead and try to call that resulting in a System.ServiceModel.ProtocolException error with message “Content Type text/xml; charset=utf-8 was not supported by service http://localhost:9000/ws.  The client and service bindings may be mismatched.”. The fact that DynamicEndpoint tries to use the address with a different HTTP binding sounds like a bug in WCF Discovery to me. On the other hand removing the WSHttpBinding from the service and running the client produces a much better System.ServiceModel.EndpointNotFoundException error with the message “2 endpoint(s) were discovered, but the client could not create or open the channel with any of the discovered endpoints.”.

 

Conclusion

WCF Discovery is a nice way to decouple clients from the physical address of the service giving us an extra bit of flexibility when deploying our WCF services.

 

Enjoy!

www.TheProblemSolver.nl

Wiki.WindowsWorkflowFoundation.eu

How using Duplex MEP to communicate with BizTalk from a .NET application or a WF workflow running inside AppFabric Part 1

Introduction

Some time ago, I was asked by a customer whether or not BizTalk WCF Adapters support the Duplex Message Exchange Pattern. The latter is characterized by the ability of both the service and the client to send messages to each other independently either using one-way or request/reply messaging. This form of bi-directional communication is useful for services that must communicate directly to the client or for providing an asynchronous experience to either side of a message exchange, including event-like behavior. The BizTalk WCF Adapters support this message exchange pattern, but unfortunately this feature is undocumented. Therefore, I decided to create a full demo where a client .NET application exchange messages with an Orchestration via a two-way Request-Response WCF Receive Location that supports callbacks and duplex communication. Subsequently, I decided to extend this demo and introduce a WCF Workflow Service between the WinForm client application and the underlying Orchestration to implement a more complex scenario The final objective was to investigate how exploiting the Correlation mechanisms provided by WF 4.0 to get a workflow running within IIS/AppFabric to exchange messages with a downstream orchestration in an asynchronous mode.

This article will be composed of 3 parts:

Why Duplex Message Exchange Pattern?

The Duplex Message Exchange Pattern is fully supported by WCF and is extensively documented on MSDN. As I already mentioned in the introduction, the Duplex pattern allows two applications to act both as service endpoints and send messages to each other independently in an asynchronous way. There are many reasons for using the Duplex Message Exchange Pattern when communicating with a BizTalk solution:

  • If an orchestration invoked by the consumer application takes seconds to minutes to complete.
  • If you are invoking an orchestration within an ASP.NET page, use asynchronous pages.
  • If you are invoking an orchestration from a single threaded application, such as a Windows Forms or Windows Presentation Foundation (WPF) application. When using the event-based asynchronous calling model, the result event is raised on the UI thread, adding responsiveness to the application without requiring you to handle multiple threads yourself.

In general, if you have a choice between a synchronous and asynchronous call, you should choose the latter approach. In fact, a synchronous call typically blocks the client thread till the operation completes, whereas an asynchronous call is non-blocking and only initiates the operation. This way the consumer application can continue its execution without waiting for the call to complete and get notified by the service when the result of its request is ready.

For more information on how implementing the Duplex Message Exchange Pattern with WCF, you can read the following articles:

Using a Duplex Channel to communicate with a BizTalk Application

The following picture represents the architecture of the first use case. The idea behind the application is quite straightforward: a Windows Forms application submits a question to an orchestration called SyncMagic8Ball via a WCF-NetTcp Receive Location. The orchestration is a BizTalk version of the notorious Magic 8 Ball toy and it randomly returns one of 20 standardized answers. 

Message Flow
  1. The  Windows Forms Client Application enables a user to specify a question and a delay in seconds. When the user presses the Ask button, a new request message containing the question and the delay is created and sent to a Two-Way WCF-NetTcp Receive Location.
  2. The XmlReceive pipeline promotes the MessageType context property.
  3. The Message Agent submits the request message to the MessageBox (BizTalkMsgBoxDb).
  4. A new instance of the SyncMagic8Ball orchestration receives the request message via a two-way logical port and uses a custom helper component called XPathHelper to read the value of the Question and Delay elements from the inbound message.
  5. The SyncMagic8Ball orchestration invokes the SetResponse static method exposed by the ResponseHelper class to build the response message containing the answer to this question contained in the request message. The response message is then published to the MessageBox (BizTalkMsgBoxDb) by the Message Agent.
  6. The response message is retrieved by the WCF-NetTcp Receive Location.
  7. The PassThruTransmit send pipeline is executed by the WCF-NetTcp Receive Location.
  8. The response message is finally returned to the original caller.

WCF Receive Locations

In order to establish an asynchronous communication, the client application and service, in our case BizTalk Server, have to meet the following conditions:

  1. They need to be online simultaneously.
  2. They need to use the same WCF binding.
  3. The selected binding must support sessions and duplex contracts. WCF provides a rich set of built-in bindings that support the Duplex MEP. For more information, you can read “System-Provided Bindings” topic on MSDN.
  4. The client application needs to implement a Service Contract to submit the request message to BizTalk and expose a Callback Contract to receive the response message from BizTalk Server asynchronously.

To satisfy the second and third conditions, I have created 3 different Two-Way Request-Response Receive Locations with the following characteristics:

  • The 3 Receive Locations are children of the same Receive Port called DuplexMEP.Sync.ReceivePort.
  • They use the XmlReceive pipeline to process the inbound message and the PassThruTrasmit pipeline to process the outbound message.
  • The DuplexMEP.Sync.WCF-NetTcp.ReceiveLocation uses the WCF-NetTcp Adapter and is hosted by the BizTalkServerReceiveHost64 in-process host.
  • The DuplexMEP.Sync.WCF-NetNamedPipe.ReceiveLocation uses the WCF-NetNamedPipe Adapter and is hosted by the BizTalkServerReceiveHost64 in-process host.
  • The DuplexMEP.Sync.WCF-Custom.WsDualHttpBinding.ReceiveLocation uses the WCF-CustomIsolated Adapter in conjunction with the WsDualHttpBinding and is hosted by the BizTalkServerIsolatedHost.

The following picture shows the 3 Receive Locations inside the BizTalk Administration Console:

 Note
You should select the most appropriate WCF Adapter and binding based on your needs:

  • The WsDualHttpBinding binding is designed for interoperability with advanced web services that need to support WS-* standards. It uses the HTTP protocol for the transport and the text message encoding.
  • NetTcpBinding and NetNamedPipeBinding, on the other hand, are designed for efficient and performant communication with other WCF applications across machines or on the same machine respectively. They both use the binary message encoding that guarantees better performance than the text message encoding.

Bindings have different characteristics in terms of response time and throughput, so the general advice to increase performance is using the NetTcpBinding and NetNamedPipeBinding whenever possible.

Schemas

The request messages sent by the Windows Forms Client Application have the following format:

Request Message

<Request xmlns="http://microsoft.appfabric.cat/10/samples/duplexmep">
    <Id>9aff8596-ec87-494d-801e-30f286d449a4</Id>
    <Question>Will the world end in 2012?</Question>
    <Delay>0</Delay>
</Request>

The corresponding response messages returned by the SyncMagic8Ball orchestration have the following format:

Response Message

<Response xmlns="http://microsoft.appfabric.cat/10/samples/duplexmep">
    <Id>9aff8596-ec87-494d-801e-30f286d449a4</Id>
    <Answer>Most likely</Answer>
</Response>

Both message types are defined by an XML Schema contained in the Schemas project that you can find in the companion code for this article.

Orchestration

The following picture shows the structure of the SyncMagic8Ball orchestration.

As you can easily notice, the orchestration uses the Two-Way Request-Response Logical Port to receive the inbound request message and to return the corresponding response message. The Trace Request Expression Shape contains the following code to extract the information from the request message. The namespace of the LogHelper and XPathHelper static classes have been eliminated for ease of reading.

LogHelper.WriteLine(System.String.Format("[SyncMagic8Ball] Transport: {0}", 
                                         RequestMessage(BTS.InboundTransportType))); id = XPathHelper.GetValue(RequestMessage, 0, "Id Element XPath Expression"); if (!System.Int32.TryParse(XPathHelper.GetValue(RequestMessage, 0, "Delay Element XPath Expression"),
                           out delayInSeconds)) { delayInSeconds = 0; } LogHelper.WriteLine(System.String.Format("[SyncMagic8Ball] Id: {0}", id)); LogHelper.WriteLine(System.String.Format("[SyncMagic8Ball] Question: {0}",
                                         XPathHelper.GetValue(RequestMessage, 0,
                                                              "Question Element XPath Expression"))); LogHelper.WriteLine(System.String.Format("[SyncMagic8Ball] Delay: {0}", delayInSeconds));

You can use DebugView to monitor the trace produced by the orchestration and helper components.

 Note 
My LogHelper class traces messages to the standard output using the capability supplied by the Trace class. This component is primarily intended to be used for debugging a BizTalk application in a test environment, rather than to be used in a production environment. If you are looking for a tracing framework which combines the high performance and flexibility provided by the Event Tracing for Windows (ETW) infrastructure, you can review the following articles by Valery Mizonov:

  • Streamlining Custom Pipeline Component Development Using Base Class Library.
  • Best Practices for Instrumenting High Performance BizTalk Solutions.
  • How to Support Component-Level Instrumentation Using BizTalk CAT Instrumentation Framework.
  • Management Tasks for BizTalk CAT Instrumentation Framework – Fully Explained.

The value of the Delay Shape is defined as follows:

new System.TimeSpan(0, 0, delayInSeconds);

Therefore, the orchestration waits for the time interval in seconds specified in the request message before returning the response message to the caller.

 Note

To extract the value of individual fields from the inbound document, I could have used Distinguished Fields defined on the XML Schema for the request message. These are context properties whose name is defined by the XPath Expression used to retrieve data from an input XML document. If the XML Schema of the document is quite complex, the XPath Expression in question can be very long and therefore the corresponding Distinguished Field can occupy a significant amount of space in the message context. Therefore, I recommend to implement the following best practices in any BizTalk application: 

  • Eliminate unnecessary and unused Distinguished Fields.
  • Use a different technique to access the value of individual elements and attributes within an Xml document.

When the document in question is extremely small, you can also load the message into an XmlDocument object and use the SelectSingleNode method to extract the information you need using an XPath Expression. However, the general advice is to minimize the usage of XmlDocument variables in orchestrations and in .NET code. Loading a message into an XmlDocument variable has significant overhead, especially for large messages.  This overhead is in terms of memory usage and system resources required to build the in-memory structures.  The use of an XmlDocument instance forces the message content to be entirely loaded into memory in order to build the object graph for the DOM.  The total amount of memory used by an instance of this class can be around 10 times the actual message size. For more information and evidence, see the following articles I wrote on this topic:

  • ”4 Different ways to process an XLANGMessage within an helper component invoked by an orchestration Part 1” on the AppFabric CAT blog.
  • ”4 Different ways to process an XLANGMessage within an helper component invoked by an orchestration Part 2” on the AppFabric CAT blog.

The request message in my demo is relatively small; nevertheless,  I decided to use an helper class that adopts a streaming approach to extract data from the XLANGMessage passed in as argument:

public class XPathHelper
{
    #region Private Constants 
    private const string MessageCannotBeNull = "[XPathReader] The message cannot be null.";
    #endregion
        
    #region Public Static Methods
    public static string GetValue(XLANGMessage message, int partIndex, string xpath)
    {
        try
        {
            if (message == null)
            {
                throw new ApplicationException(MessageCannotBeNull);
            }
            using (Stream stream = message[partIndex].RetrieveAs(typeof(Stream)) as Stream)
            {
                XmlTextReader xmlTextReader = new XmlTextReader(stream);
                XPathCollection xPathCollection = new XPathCollection();
                XPathReader xPathReader = new XPathReader(xmlTextReader, xPathCollection);
                xPathCollection.Add(xpath);
                bool ok = false;
                while (xPathReader.Read() && !ok)
                {
                    if (xPathReader.HasAttributes)
                    {
                        for (int i = 0; i < xPathReader.AttributeCount; i++)
                        {
                            xPathReader.MoveToAttribute(i);
                            if (xPathReader.Match(xPathCollection[0]))
                            {
                                return xPathReader.GetAttribute(i);
                            }
                        }
                    }
                    if (xPathReader.Match(xPathCollection[0]) && !ok)
                    {
                        return xPathReader.ReadString();
                    }
                }
            }
        }
        finally
        {
            message.Dispose();
        }
        return string.Empty;
    }
    #endregion
}

The Set Response Message Assignment shape invokes the SetResponse static method exposed by the ResponseHelper class to generate the response message:

ResponseHelper Class

public class ResponseHelper
{
    #region Private Constants
    private const string MessageFormat = "[ResponseManager] {0}";
    private const string ResponseNamespace = "http://microsoft.appfabric.cat/10/samples/duplexmep";
    private const string Response = "Response";
    private const string Id = "Id";
    private const string Answer = "Answer";
    #endregion

    #region Static Constructor
    private static string[] answers = null;
    #endregion

    #region Static Constructor
    static ResponseHelper()
    {
        answers = new string[]{"As I see it, yes",
                                "It is certain",
                                "It is decidedly so",
                                "Most likely",
                                "Outlook good",
                                "Signs point to yes",
                                "Without a doubt",
                                "Yes",
                                "Yes - definitely",
                                "You may rely on it",
                                "Reply hazy, try again",
                                "Ask again later",
                                "Better not tell you now",
                                "Cannot predict now",
                                "Concentrate and ask again",
                                "Don't count on it",
                                "My reply is no",
                                "My sources say no",
                                "Outlook not so good",
                                "Very doubtful"};
    }
    #endregion

    #region Public Static Methods
    public static void SetResponse(XLANGMessage message, string id)
    {
        try
        {
            VirtualStream stream = new VirtualStream();
            using (XmlWriter writer = XmlWriter.Create(stream))
            {
                writer.WriteStartDocument();
                writer.WriteStartElement(Response, ResponseNamespace);
                writer.WriteStartElement(Id, ResponseNamespace);
                writer.WriteString(id);
                writer.WriteEndElement();
                writer.WriteStartElement(Answer, ResponseNamespace);
                Random random = new Random(unchecked((int)DateTime.Now.Ticks));
                writer.WriteString(answers[random.Next(0, 20)]);
                writer.WriteEndElement();
                writer.WriteEndElement();
            }
            stream.Seek(0, SeekOrigin.Begin);
            message[0].LoadFrom(stream);
        }
        catch (Exception ex)
        {
            LogHelper.WriteLine(ex.Message);
        }
        finally
        {
            message.Dispose();
        }
    }
    #endregion
}

As you can see, the ResponseHelper class uses a VirtualStream object an XmlWriter class instance to initialize the content of the response message. Once again, the response message in this case is so small that I could have used an XmlDocument object to load the content of the response message. However, my intention was to show you how to take advantage of the streaming approach to read and write the content of an XLANGMessage object within an orchestration.

Let’s take a look at the code used by the Windows Forms Client Application to send a request message and receive the corresponding response using a Callback contract.

Data and Message Contracts

I started defining the Data and Message Contracts for the request and response messages. These 2 types of contracts have different roles in WCF:

  • Data Contracts provide a mechanism to map .NET CLR types that are defined in code and XML Schemas (XSD) defined by the W3C organization (www.w3c.org). Data contracts are published in the service’s metadata, allowing clients to convert the neutral, technology-agnostic representation of the data types to their native representations.
  • Message Contracts describe the structure of SOAP messages sent to and from a service and enable you to inspect and control most of the details in the SOAP header and body. Whereas data contracts enable interoperability through the XML Schema Definition (XSD) standard, message contracts enable you to interoperate with any system that communicates through SOAP. Using message contracts gives you complete control over the SOAP message sent to and from a service by providing access to the SOAP headers and bodies directly. This allows the use of simple or complex types to define the exact content of the SOAP parts.

For more information on Data and Message Contracts, you can read the following articles:

In my solution, I created 2 separate projects called DataContracts and MessageContracts respectively. For your convenience, I included below the code of the 4 classes used to define the Data and Message Contract of the request and response messages.

BizTalkRequest Class

[DataContract(Name="Request", Namespace="http://microsoft.appfabric.cat/10/samples/duplexmep")]
public partial class BizTalkRequest : IExtensibleDataObject
{
    #region Private Fields
    private ExtensionDataObject extensionData;
    private string id;
    private string question;
    private int delay;
    #endregion

    #region Public Constructors
    public BizTalkRequest()
    {
        this.id = Guid.NewGuid().ToString();
        this.question = null;
        this.delay = 0;
    }

    public BizTalkRequest(string question, int delay)
    {
        this.id = Guid.NewGuid().ToString();
        this.question = question;
        this.delay = delay;
    }
    #endregion

    #region Public Properties
    public ExtensionDataObject ExtensionData
    {
        get
        {
            return this.extensionData;
        }
        set
        {
            this.extensionData = value;
        }
    }

    [DataMemberAttribute(IsRequired = true, Order = 1)]
    public string Id
    {
        get
        {
            return this.id;
        }
        set
        {
            this.id = value;
        }
    }

    [DataMemberAttribute(IsRequired = true, Order = 2)]
    public string Question
    {
        get
        {
            return this.question;
        }
        set
        {
            this.question = value;
        }
    }

    [DataMemberAttribute(IsRequired = true, Order = 3)]
    public int Delay
    {
        get
        {
            return this.delay;
        }
        set
        {
            this.delay = value;
        }
    } 
    #endregion
}

BizTalkResponse Class

[DataContract(Name = "Response", Namespace = "http://microsoft.appfabric.cat/10/samples/duplexmep")]
public partial class BizTalkResponse : IExtensibleDataObject
{
    #region Private Fields
    private ExtensionDataObject extensionData;
    private string id;
    private string answer;
    #endregion

    #region Public Properties
    public ExtensionDataObject ExtensionData
    {
        get
        {
            return this.extensionData;
        }
        set
        {
            this.extensionData = value;
        }
    }

    [DataMemberAttribute(IsRequired = true, Order = 1)]
    public string Id
    {
        get
        {
            return this.id;
        }
        set
        {
            this.id = value;
        }
    }

    [DataMemberAttribute(IsRequired = true, Order = 2)]
    public string Answer
    {
        get
        {
            return this.answer;
        }
        set
        {
            this.answer = value;
        }
    }
    #endregion
}

BizTalkRequestMessage Class

[MessageContract(IsWrapped=false)]
public class BizTalkRequestMessage
{
    #region Private Fields
    private BizTalkRequest request;
    #endregion

    #region Public Constructors
    public BizTalkRequestMessage()
    {
        this.request = null;
    }

    public BizTalkRequestMessage(BizTalkRequest request)
    {
        this.request = request;
    }
    #endregion

    #region Public Properties
    [MessageBodyMember(Namespace = "http://microsoft.appfabric.cat/10/samples/duplexmep")]
    public BizTalkRequest Request
    {
        get
        {
            return this.request;
        }
        set
        {
            this.request = value;
        }
    } 
    #endregion
}

BizTalkResponseMessage Class

[MessageContract(IsWrapped = false)]
public class BizTalkResponseMessage
{
    #region Private Fields
    private BizTalkResponse response;
    #endregion

    #region Public Constructors
    public BizTalkResponseMessage()
    {
        this.response = null;
    }

    public BizTalkResponseMessage(BizTalkResponse response)
    {
        this.response = response;
    }
    #endregion

    #region Public Properties
    [MessageBodyMember(Namespace = "http://microsoft.appfabric.cat/10/samples/duplexmep")]
    public BizTalkResponse Response
    {
        get
        {
            return this.response;
        }
        set
        {
            this.response = value;
        }
    }
    #endregion
}

Indeed, I could have used just Data Contracts to model messages as Message Contracts add a degree of complexity. So why I decided to use Message Contracts? Well, the reason is quite straightforward. By assigning false to the IsWrapped property exposed by the MessageContractAttribute, you specify that the message body won’t be contained in a wrapper element. Typically, the wrapper element of a request message is the name of the operation invoked and it’s defined in the WSDL. Setting the value of the IsWrapped property to false, you can simply select the Body option in both the Inbound BizTalk message body and Outbound WCF message body sections on the Messages tab when configuring a WCF Receive Location. On the contrary, you should define a Path in the Inbound BizTalk message body section to extract the payload from the inbound message, and specify a template in the Outbound WCF message body section to include the outgoing response message within a wrapper element.

 Note 
The namespace of the Message and Data Contract classes match those defined by the XML Schemas that model the request and response messages in the BizTalk Server application.

Service Contracts

The next step was to define the Service Contracts used by the client application to exchange messages with BizTalk Server. To this purpose, I created a new project in my solution called ServiceContracts, and then I started off by defining the Service Contract Interface that models the server side of the duplex contract. I declared the signature of a One-Way method called AskQuestion. I specified a parameter of type BizTalkRequestMessage (see the Data and Message Contracts section) and void as return type. Then I decorated the method with the XmlSerializerFormatAttribute to indicate to the WCF Runtime to use the XmlSerializer instead of the DataContractSerializer. Please note that the WCF Adapters and BizTalk in general use the XmlSerializer, thus I had to explicitly set the correct serializer in the contract definition. Next, I decorated the method with the OperationContractAttribute to indicate that the method is one way and to specify the WS-Addressing Action of the request message. Finally I decorated the interface with the ServiceContractAttribute.

IMagic8BallBizTalk Interface

[ServiceContract(Namespace = http://microsoft.appfabric.cat/10/samples/duplexmep,
                 SessionMode = SessionMode.Required, CallbackContract = typeof(IMagic8BallBizTalkCallback), ConfigurationName = "IMagic8BallBizTalk")] public interface IMagic8BallBizTalk { [XmlSerializerFormat] [OperationContract(Action = "AskQuestion", IsOneWay = true)] void AskQuestion(BizTalkRequestMessage requestMessage); }

Then I defined the the callback interface as follows:

IMagic8BallBizTalkCallback Interface

[ServiceContract(Namespace = http://microsoft.appfabric.cat/10/samples/duplexmep, 
                 ConfigurationName = "IMagic8BallBizTalkCallback")] public interface IMagic8BallBizTalkCallback { [XmlSerializerFormat] [OperationContract(Action = "AskQuestionResponse", IsOneWay = true)] void AskQuestionResponse(BizTalkResponseMessage responseMessage); }

Finally, I linked the two interfaces into a Duplex Contract by assigning the type of the callback interface to the CallbackContract property in the ServiceContractAttribute that decorates the IMagic8BallBizTalk interface.

Callback Contract Implementation

The next step was to implement the IMagic8BallBizTalkCallback callback interface in order to receive response messages from BizTalk Server. To this purpose  I created a new class in the Client project called Magic8BallBizTalkCallback that implements the IMagic8BallBizTalkCallback callback interface.

Magic8BallBizTalkCallback Class

public class Magic8BallBizTalkCallback : IMagic8BallBizTalkCallback { #region Private Constants private const string ResponseFormat = "Response:\n\tId: {0}\n\tAnswer: {1}"; #endregion

 


#region Private Fields private MainForm form; #endregion #region Public Constructors public Magic8BallBizTalkCallback() { this.form = null; } public Magic8BallBizTalkCallback(MainForm form) { this.form = form; } #endregion #region IMagic8BallCallback Members public void AskQuestionResponse(BizTalkResponseMessage responseMessage) { if (responseMessage != null && responseMessage.Response != null && !string.IsNullOrEmpty(responseMessage.Response.Id) && !string.IsNullOrEmpty(responseMessage.Response.Answer)) { form.WriteToLog(string.Format(CultureInfo.CurrentCulture,
                                          ResponseFormat,
responseMessage.Response.Id,
responseMessage.Response.Answer)); } } #endregion } public partial class MainForm : Form { ... public void WriteToLog(string message) { if (InvokeRequired) { Invoke(new Action<string>(InternalWriteToLog), new object[] { message }); } else { InternalWriteToLog(message); } } ... }

In particular, when the WCF Receive Location invokes the AskQuestionResponse method to return a response message, the callback is handled by a ThreadPool thread other than the main thread running the client application. Since the InternalWriteToLog method is used to write the answer on a ListBox control owned by the main thread, the WriteToLog method exposed by the MainForm class uses the InvokeRequired property to check whether it’s necessary to use the Invoke method because the caller is on a different thread than the main thread.

Invoking BizTalk Server

The next step was to write the code to invoke one of the 3 WCF Receive Locations exposed by by BizTalk application.

btnAsk_Click Method

private void btnAsk_Click(object sender, EventArgs e)
{
    try
    {
        int delay = 0;
        if (string.IsNullOrEmpty(txtQuestion.Text))
        {
            WriteToLog(QuestionCannotBeNull);
            txtQuestion.Focus();
            return;
        }
        if (string.IsNullOrEmpty(txtDelay.Text) ||
            !int.TryParse(txtDelay.Text, out delay))
        {
            WriteToLog(DelayMustBeANumber);
            txtDelay.Focus();
            return;
        }

        if (string.IsNullOrEmpty(cboEndpoint.Text))
        {
            WriteToLog(NoEndpointsFound);
        }

        DuplexChannelFactory<IMagic8BallBizTalk> channelFactory = null;

        try
        {
            BizTalkRequest request = new BizTalkRequest(txtQuestion.Text, delay);
            BizTalkRequestMessage requestMessage = new BizTalkRequestMessage(request);
            WriteToLog(string.Format(CultureInfo.CurrentCulture, 
                                     RequestFormat,
                                     cboEndpoint.Text,
                                     request.Id,
                                     request.Question)); InstanceContext context = new InstanceContext(new Magic8BallBizTalkCallback(this)); channelFactory = new DuplexChannelFactory<IMagic8BallBizTalk>(context, cboEndpoint.Text); IMagic8BallBizTalk channel = channelFactory.CreateChannel(); channel.AskQuestion(requestMessage); } catch (FaultException ex) { WriteToLog(ex.Message); if (channelFactory != null) { channelFactory.Abort(); } } catch (CommunicationException ex) { WriteToLog(ex.Message); if (channelFactory != null) { channelFactory.Abort(); } } catch (TimeoutException ex) { WriteToLog(ex.Message); if (channelFactory != null) { channelFactory.Abort(); } } catch (Exception ex) { WriteToLog(ex.Message); if (channelFactory != null) { channelFactory.Abort(); } } } catch (Exception ex) { WriteToLog(ex.Message); } }

As you may have noticed, the method btnAsk_Click performs the following actions in order:

  • Creates a data contract object (BizTalkRequest)
  • Creates a message contract object (BizTalkRequestMessage).
  • Creates a new instance of the Magic8BallBizTalkCallback type to handle callbacks.
  • Creates a new InstanceContext object. In particular, the Magic8BallBizTalkCallback object is passed in as argument to the constructor of the InstanceContext object.
  • Instantiates a DuplexChannelFactory to create a duplex channel of type IMagic8BallBizTalk.  In particular, the first argument specifies the InstanceContext that the client uses to listen for messages from the connected service, in our case BizTalk Server.
  • Creates a new duplex channel.
  • Invokes the AskQuestion method.

Configuration File

The final step was to define the client endpoints in the client configuration file in order to invoke the 3 WCF Receive Locations exposed by the BizTalk application.

Configuration File

<?xml version="1.0"?>
<configuration>
  <system.serviceModel>
    <bindings>
      <netNamedPipeBinding>
        <binding name="netNamedPipeBinding" />
      </netNamedPipeBinding>
      <netTcpBinding>
        <binding name="netTcpBinding">
          <security mode="Transport">
            <transport protectionLevel="None" />
          </security>
        </binding>
      </netTcpBinding>
      <wsDualHttpBinding>
        <binding name="wsDualHttpBinding" />
      </wsDualHttpBinding>
    </bindings>
    <client>
      <clear />
      <endpoint address="net.tcp://localhost:7171/magic8ballbiztalk/sync"
                binding="netTcpBinding" 
                bindingConfiguration="netTcpBinding"
                contract="IMagic8BallBizTalk"
                name="NetTcpEndpointBizTalk" />
      <endpoint address="net.pipe://localhost/magic8ballbiztalk/sync" 
                binding="netNamedPipeBinding"
                bindingConfiguration="netNamedPipeBinding" 
                contract="IMagic8BallBizTalk"
                name="NetNamedPipeEndpointBizTalk" />
      <endpoint address="http://localhost/magic8ballbiztalk/syncmagic8ball.svc"
                binding="wsDualHttpBinding" 
                bindingConfiguration="wsDualHttpBinding"
                contract="IMagic8BallBizTalk"
                name="WsDualHttpEndpointBizTalk" />
    </client>
  </system.serviceModel>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
  </startup>
</configuration>

We are now ready to test the solution.

Testing the Solution

To test the application, you can proceed as follows:

  • Configure and start the DuplexMEP BizTalk application.
  • Open a new instance of the Client Application, as indicated in the picture below.
  • Enter an existential question like "Why am I here?", "What’s the meaning of like?" or "Will the world end in 2012?" in the Question textbox.
  • Select one of the following endpoints:
    • NetTcpEndpointBizTalk to invoke the DuplexMEP.Sync.WCF-NetTcp.ReceiveLocation.
    • NetNamedPipeEndpointBizTalk to invoke the DuplexMEP.Sync.WCF-NetNamedPipe.ReceiveLocation.
    • WsDualHttpEndpointBiztalk to invoke the DuplexMEP.Sync.WCF-Custom.WsDualHttpBinding.ReceiveLocation.
  • Specify a Delay in seconds in the corresponding textbox.
  • Press the Ask button.

Now, if you press the Ask button multiple times in a row, you can easily notice that the client application is called back by BizTalk in an asynchronous way. Therefore, the client application doesn’t need to wait for the response to the previous question before posing a new one.

In order for the demo to work, it’s important that the Delay in seconds that you specify in the client application is less than the ReceiveTimeout configured in the WCF Receive Location. When using a WCF Adapter other than the WCF-Custom and WCF-CustomIsolated, you cannot specify a custom ReceiveTimeout. The following picture shows the Binding tab of a WCF-NetTcp Receive Location.

As a consequence, the default 10 minutes timeout will be used. Therefore, if you need to increase the ReceiveTimeout beyond 10 minutes, you can replace the WCF Adapter in question with the WCF-Custom and WCF-CustomIsolated adapter and select the same binding. This way, you can specify a custom value for the ReceiveTimeout property exposed by the binding, as shown in the following picture:

Conclusions

In this article we have seen how to exchange messages with an orchestration via a two-way WCF Receive Location using the Duplex Message Exchange Pattern. In the next article of the series, we’ll see how to implement an asynchronous communication between our client application and a WCF Workflow Service running within IIS\AppFabric Hosting Services using the Durable Duplex Correlation. In the meantime, here you can download the companion code for this article. As always, you feedbacks are more than welcome!

Acknowledge review and comments from Valery Mizonov. Thanks mate!

Calling Workflow Services without Add Service Reference

Sometimes you just don’t want to do an Add Service reference in the client application but still be able to to call a WF4 workflow service. The good thing is that a WF4 workflow service is just another WCF service from the client perspective so almost everything you can do with a regular WCF service you can also do with a workflow service. And calling the service without doing an Add Service Reference first is one of those things.

In these examples I am going to use the default workflow service template just to make it easy to get started. Adjust the client code depending on your service contract.

 

The first option is to hand craft the service contract on the client and use the ChannelFactory<T>

This option is very simple and straightforward. Basically you are handcrafting a ServiceContract and request/response MessageContract’s to fit the service.

The ServiceContract is nice and simple and looks like this:

[ServiceContract(Name = "IService")]
interface IMyService
{
    [OperationContract]
    GetDataResponse GetData(GetDataRequest request);
}

 

The request and response messages are defined using a MessageContract like this:

[MessageContract(IsWrapped = false)]
class GetDataRequest
{
    [MessageBodyMember(Name = "int", 
        Namespace = "http://schemas.microsoft.com/2003/10/Serialization/")]
    public int Value { get; set; }
}
 
[MessageContract(IsWrapped = false)]
class GetDataResponse
{
    [MessageBodyMember(Name = "string", 
        Namespace = "http://schemas.microsoft.com/2003/10/Serialization/")]
    public string Value { get; set; }
}

 

With these pieces in place calling the workflow service is simple using the following code:

static void Main(string[] args)
{
 
    var factory = new ChannelFactory<IMyService>(new BasicHttpBinding(), new EndpointAddress("http://localhost:9199/Service1.xamlx"));
    var proxy = factory.CreateChannel();
    var response = proxy.GetData(new GetDataRequest() { Value = 42 });
    Console.WriteLine(response.Value);
    Console.ReadLine();
}

 

Note the only thing that varies here is the endpoint address.

 

Getting rid if the ServiceContract

If you don’t want to use a fixed ServiceContract you can use the low level WCF Message API. In this case we use a very simple service contract with the action name of “*” and request/response of type Message. This means we can send any request to any request/response service through this contract. The contract is very simple:

[ServiceContract]
interface IGenericService
{
    [OperationContract(Action = "*")]
    Message GetData(Message request);
}

 

 

The complexity now is in constructing the messages to send but even that isn’t to bad. There are may ways to do this, I chose the XElement API here but you are free to do it any way you like.

static void Main(string[] args)
{
    XNamespace ns = "http://schemas.microsoft.com/2003/10/Serialization/";
 
    var factory = new ChannelFactory<IGenericService>(new BasicHttpBinding(), new EndpointAddress("http://localhost:9199/Service1.xamlx"));
    var proxy = factory.CreateChannel();
    var request = Message.CreateMessage(MessageVersion.Soap11, "http://tempuri.org/IService/GetData", new XElement(ns + "int", 42));
    
    var response = proxy.GetData(request);
 
    var xml = (XElement)XElement.ReadFrom(response.GetReaderAtBodyContents());
    var message = xml.Value;
    Console.WriteLine("Response is: {0}", message);
 
    Console.ReadLine();
}

 

With this in place you can create a generic client that can send messages to any WCF service, workflow service or plain old WCF, all you need to do is store the endpoint, operation name and message formats somewhere. I have to say I really like this power and have used it in several applications where flexibility and configurability was important.

 

Enjoy!

www.TheProblemSolver.nl

Wiki.WindowsWorkflowFoundation.eu