Using the WCF federation bindings with the WCF Adapter in Microsoft biztalk server
Even though the WCF Federation bindings aren’t yet officially supported for use with the WCF Adapter, for most purposes, you can get your scenario to work (and in case your scenario does not work as expected, you should contact Microsoft Support so that we can investigate the issue - we of course might not fix it since we dont support this binding, but then again - we might fix the bug if we have available resources).
In this post, I’m going to port over a MSDN sample which uses the Ws2007FederationHttpBinding to BizTalk. You will need Microosft BizTalk Server 2009 R2 in order to get things to work correctly.
Firstly, you need the WCF samples from MSDN. You can get them from here. The MSDN documentation for these is available here. This is the sample which we will be using. Make sure that everything is working fine in the sample, and that you’re able to use the client WinForms application to successfully browse the books list, and are able to buy the 2nd and 3rd books (if you attempt to buy the 1st book, you should get back an error message complaining about the price being too high).
We’ll now attempt to bring BizTalk in the picture. First, we’ll make BizTalk act as a client and send a request to the server for purchasing a book. Next, we’ll make BizTalk act as the server and then use the same WinForms client application (present in the MSDN sample) to connect to BizTalk and purchase a book. NOTE that I won’t act BizTalk act as a client and send a request to the server for browsing the books list; since that uses the WsHttpBinding, and it is pretty straightforward to get that part working once you work on the second part of making BizTalk act as a client and purchase a book.
NOTE – when going forward, the assumption is that you are familiar with the MSDN sample – the code, and how it functions.
BizTalk as the Client – purchasing a book
We need to do two things before we start with the BizTalk side of things.
1. Run “certmgr”. Find the certificate issued to "Root Agency" (under Intermediate Certification Authorities\Certificates). Right Click, choose Cut. Paste it under Trusted Root Certificatioin Authorities\Certificates. (You can always revert this later).
The reason we have to do this – when you set up the MSDN sample, three certificates are created for use with the sample. These certificates are issued by “Root Agency”, which is a dummy CA for testing purposes, and it is not trusted. The WCF Adapter in BizTalk only accepts certificates whose CA is trusted; hence without this step, you’d get an Invalid Certificate exception.
2. In the zip file, you’ll find an endpoint behavior. You need to register it in machine.config, and add the assembly to the GAC. For registration in machine.config, add the XML below to the <system.serviceModel>/<extensions>/<behaviorExtensions> node.
<add name="federationSampleEndpointBehavior" type="FederationSample_EndpointBehavior.FS_EndpointBehavior_ExtensionElement, FederationSample_EndpointBehavior, Version=1.0.0.0, Culture=neutral, PublicKeyToken=fe278db62e2dcf8b" />
The reason we need this endpoint behavior: the way the WCF sample is written, the book which you want to buy – the name of that book must appear within the header of the message. The way the sample client does this is it sets the header on the endpoint – thus, all messages flowing out of that client endpoint will have the header applied to them. If you want to do this the same way in BizTalk, a custom endpoint behavior fits perfectly, allowing you to set the header on the endpoint which BTS will use.
NOTE – the code in the WCF sample client is as follows:
EndpointAddressBuilder myEndpointAddressBuilder = new EndpointAddressBuilder(myBuyBookClient.Endpoint.Address);
myEndpointAddressBuilder.Headers.Add(AddressHeader.CreateAddressHeader(Constants.BookNameHeaderName, Constants.BookNameHeaderNamespace, bookName));
myBuyBookClient.Endpoint.Address = myEndpointAddressBuilder.ToEndpointAddress();
and the custom endpoint behavior does the same thing.
Creating the Send Port in BizTalk
- Create an Application in BizTalk (from the BizTalk Server Administration console) named "FederationSampleApplication".
- Create a 2-way Send Port named "BuyBook_SendPort", choose WCF-Custom as the adapter. Click "Configure"
- In the Import/Export tab, choose Import, and browse to the app.config for the client WinForms application (for example, you would import the BookStoreClient.exe.config file from "C:\WCF_WF_Samples\WCF\Scenario\Federation\CS\BookStoreClient\bin" if you installed the MSDN samples to C:\WCF_WF_Samples).
- In the dialog for choosing an endpoint, choose WSFederationHttpBinding_IBuyBook.
- In the General tab, set the Action to https://tempuri.org/IBuyBook/BuyBook.
- In the Behavior tab, right click on BuyBookClientBehavior, and choose Add Extension. Add the federationSampleEndpointBehavior. Set the BookName property to "Book Title Two". (We’ll attempt to purchase this book).
- The port is now configured.
- Create a 1-way Receive Port named "FileIn", add a 1-way FILE Receive Location. Assume that the input folder is C:\IN.
- Create a 1-way Send Port named "FileOut" with the FILE Adapter. Assume that the output folder is C:\OUT.
- Setup a filter at the BuyBook_SendPort send port, with the condition being BTS.ReceivePortName == FileIn
- Setup a filter at the FileOut send port, with the condition being BTS.SPName == BuyBook_SendPort
- Start BizTalk and start/enlist/enable the artifacts. Drop the file BuyBook_Input.xml (this is present in the zip file) in the folder pointed to by FileIn; you should see the output in the folder pointed to by FileOut
One thing you must have noticed is that all messages sent out via the Send Port can only purchase a single book. In the MSDN sample, you were able to specify which book you wanted; and it worked there because for every request you made, you were recreating the client endpoint, and so setting the header on the endpoint worked. In BizTalk on the other hand, we don’t re-create the client endpoint for every call.
So, how do you work around it? Here’s one way – create your own schema containing three values: the emailAddress, the shipToAddress, and the bookName. Drop in an input message conforming to this schema. Use a map within an orchestration to transform it to another message containing only the emailAddress and the shipToAddress – since the sample service only expects these two to be present in the message body. Then, you can use an expression shape in the orchestration to set the value of the WCF.OutboundCustomHeaders context property to any custom header which you want to set – the bookName in this case. And of course, if you did this, you wouldn’t require the custom endpoint behavior anymore which we added earlier to the Send Port.
BizTalk as the Service
You must have seen that the sample consists of 3 services – the BookStore Service (which hosts the Browse and Buy endpoints), the BookStore STS, and the HomeRealm STS. I’m only going to host the Buy endpoint of the BookStore service in BizTalk, the Browse endpoint as well as the token services will yet continue to be hosted in IIS. Once you go through the rest of this port, it should be easy to figure out how the remaining endpoints / services can be “migrated” to BizTalk.
The .zip file contains a class library which houses two things – the code for the authorization manager, and a service behavior. The authorization manager code is more or less identical to what was there in the MSDN sample. But here’s why we also need another behavior: if you look at the BuyBook implementation in the sample, the code attempts to extract a specific claim from the incoming message and then use that for further processing. In BizTalk, if you want to access the WCF Message directly, you’d have to use a MessageInspector; and so we’re inserting a MessageInspector into the WCF Dispatch Runtime via a service behavior. You’ll need to register this service behavior extension in machine.config, have a look at the comment in the “FederationSample_ServiceBehavior.cs” file - this contains the machine.config entry. Compile and GAC this dll.
Create a 2-way Receive Port + Receive Location in BTS. In the Receive Location, import the web.config from the BookStoreService IIS virtual directory (C:\Inetpub\wwwroot\FederationSample\BookStoreService in my case). Select the Buy endpoint as the endpoint to import. Then, in the behaviors tab, you need to make the following changes:
- Change the authorization manager type to “FederationSampleServiceHelper.BuyAuthorizationManager, FederationSampleServiceHelper, Version=1.0.0.0, Culture=neutral, PublicKeyToken=fe278db62e2dcf8b”. (this is from our dll which we GACed above).
- Set ServiceCredentials -> IssuedTokenAuthentication -> CertificateValidation to PeerOrChainTrust (in the MSDN sample, this is being done in code in the BookStoreServiceHost constructor)
- Add the federationSampleServiceBehavior extension (so that our MessageInspector gets injected at runtime).
In the General tab, you also need to set a valid URI (by default, it gets set to “buy” which was the relative address used in the endpoint configuration when hosting in IIS).
Also, build the orchestration and deploy it. In the MSDN sample, three things were used from the incoming message to construct the response: the BookName header (present in the WCF headers), the name of the caller (present as a claim), and the email address (present in the WCF Message body). In my orchestration, I’m using a construct message shape to extract the first two (the last one is pretty straightforward – we just need to use XmlDocument to read the message body and then use an XPATH to get the email address out). The headers present in an incoming WCF Message are present in the InboundHeaders context property (the WCF Adapter sets this), and so the BookName header is readily obtained. Also, our message inspector code (when it ran during the message receive) did this – searched the claims in the message for the caller’s name (using the same code as there in the MSDN sample in the contract implementation) and put that claim in the message header; and so we are now able to extract this also from the InboundHeaders context property. The code in the expression box looks like:
inboundHeaders = Message_1(WCF.InboundHeaders);
xmlDom = new System.Xml.XmlDocument();
xmlDom.LoadXml(inboundHeaders);
nsmgr = new System.Xml.XmlNamespaceManager(xmlDom.NameTable);
nsmgr.AddNamespace("ab", " https://tempuri.org/ ");
n = xmlDom.SelectSingleNode("/headers/ab:CallerName", nsmgr);
callerName = n.InnerXml;
n = xmlDom.SelectSingleNode("/headers/ab:BookName", nsmgr);
bookName = n.InnerXml;
str = System.String.Format("Hello {0}, we will ship {1} to you.", callerName, bookName);
str = "<BuyBookResponse xmlns='https://tempuri.org/'><BuyBookResult>" + str + "</BuyBookResult></BuyBookResponse>";
xmlDom = new System.Xml.XmlDocument();
xmlDom.LoadXml(str);
Message_2 = xmlDom;
The last portion above is constructing the response message which the client will see. I’ve intentionally made the text different from what the MSDN sample returned so that when we test it, we can be sure that the response is coming from BTS.
To test it out, use the BookStoreClient in the MSDN sample. You just need to change the endpoint address for the “WSFederationHttpBinding_IBuyBook” endpoint to the address specified when configuring the RL.