Watch this now – XBOX 360!
http://www.neowin.net/news/gamers/09/06/01/microsoft-introduces-controller-free-gaming-project-natal
http://www.neowin.net/news/gamers/09/06/01/microsoft-introduces-controller-free-gaming-project-natal
Register by going to http://www.microsoft.com/sqlserver/2008/en/us/r2.aspx and we will let you know when the CTP ships.
It’s a busy month 😉
"Oslo" is the code name for Microsoft’s next generation modeling platform. This CTP includes:
New in this CTP:
http://www.microsoft.com/downloads/details.aspx?FamilyID=827122a5-3ca0-4389-a79e-87af37cbf60d&displaylang=en
Just noticed that we had released to web the BizTalk 2009 guide for Hyper V. Get it here:
http://www.microsoft.com/downloads/details.aspx?displaylang=en&FamilyID=0582bc67-0bef-4a0a-99cf-4408a111c4e3
Let me know what you think.
[Source: http://geekswithblogs.net/EltonStoneman]
The sample BizTalk application provided with the BizTalk Cache Adapter on CodePlex illustrates three approaches for using the cache:
All the samples use FILE locations for the originating request and ultimate response. The expected locations will be created in the install, with sample request files in the receive locations. Enable the relevant receive location to run the sample, then copy the request file to the receive location again to re-run it – from the second run onwards, the responses should be the cached versions.
Installation
To use the samples, you’ll need to install and configure a cache provider, the cache adapter, cache adapter sample, sample service, and the cache viewer (if you want to see the contents of the cache).
Both the Cache Adapter and Sample BizTalk MSIs use PowerShell scripts for their post-processing steps. If you want to install from the MSIs, you’ll need to have PowerShell installed and set up to enable script execution (Set-ExecutionPolicy RemoteSigned is fine).
1. Cache Provider.
Download and install NCache Express (this is a free edition, you’ll have a product key emailed to you), and configure a cache instance for the samples by modifying the configuration file (config.ncconf – by default in C:\Program Files\NCache Express\config):
<configuration>
<cache-config name=“Sixeyed.CacheAdapter.NCacheExpressProvider.Tests“ inproc=“false“>
<cleanup interval=“1sec“/>
<log trace-errors=“true“ trace-debug=“false“ enabled=“true“/>
<storage cache-size=“5mb“/>
<eviction-policy default-priority=“normal“ eviction-ratio=“10%“ eviction-enabled=“true“/>
<perf-counters enabled=“false“/>
</cache-config>
</configuration>
This creates a single-node cache on the local machine, limited to 5Mb of system memory. Start the cache by running the startcache tool (C:\Program Files\NCache Express\bin\tools):
startcache Sixeyed.CacheAdapter.NCacheExpressProvider.Tests
– and verify it’s there with listcaches. You should see the cache listed as Running:
2. Cache Adapter.
Sixeyed.CacheAdapter.msi is the BizTalk MSI which installs the adapter and artifacts. Import the application into BizTalk and run the installer. Verify the Sixeyed.CacheAdapter application is listed, and add the adapter in the Administration Console through Platform Settings…Adapters…New.
Note: the installer adds the necessary adapter files to the GAC and sets up the registry keys. If you don’t use the MSI, manual installation steps are: add the files from the Binaries release to a local directory and to the GAC; modify run the .REG command to specify assembly paths and import the .REG file; add the BizTalk artifact resources to your own application.
2. Cache Adapter Sample.
Sixeyed.CacheAdapterSample.msi is the BizTalk MSI which installs the sample application and artifacts, and configures the ports. Import the application into BizTalk and run the installer. Verify the Sixeyed.CacheAdapterSample application is listed, and that the message types are configured in SSO (check the contents of SSO application Sixeyed.CacheAdapter).
Note: the installer creates FILE send and receive locations, copies sample messages, and sets up SSO configuration for the cached message types. If you don’t use the MSI, manual installation should follow the steps in: Source\Sixeyed.CacheAdapterSample\Deployment\Sixeyed.CacheAdapterSample.Install.ps1.
4. Sample Service.
Sixeyed.CacheAdapterSample.Service.msi installs a WCF service for the samples to call. Verify the service has deployed correctly by navigating to http://localhost/Sixeyed.CacheAdapterSample/SampleService.svc?wsdl.
5. Cache Viewer.
Sixeyed.CacheAdapter.CacheViewer.msi installs a Winforms app for viewing cache contents. Run the application and configure it to use the running cache instance, by navigating to the NCacheExpressProvider assembly and selecting the NCacheExpressProvider:
– and specifying the ID of the cache instance:
(This is the same management UI used for configuring cache adapter ports in the BizTalk Administration Console. For the samples the Cache Id is Sixeyed.CacheAdapter.NCacheExpressProvider.Tests).
Simple Messaging
Enable the receive location ReceiveRequest_SimpleMessaging.FILE and you should see the following workflow:
Check the cache viewer and you will see a byte array cached with a GUID key:
The GUID is the hash of the request message, and the byte array is the response message. Subsequent drops of the same request into the Requests folder will find the cached response at step 2, and the response will be the cached message.
This workflow is suitable for any simple messaging solution, where static ports are used and where the original request and ultimate response can be auctioned by different ports. Configuration of the cache instance is contained in the send ports.
Orchestration
The orchestration sample follows the same workflow as the Simple Messaging sample, and uses the same CacheXMLReceive pipeline to add the caching properties to the incoming message. The orchestration sample makes the cache access with explicit send and receives, and configuration of the cache instance is still contained in the send ports.
Enable the receive location ReceiveRequest_Orchestration.FILE to run the sample. This is suitable for solutions where finer grained control is needed over the caching mechanism, and an orchestration is a viable option.
Complex Messaging
Both the simple implementations use the cache adapter as a passthrough when there is no matching response in the cache – the incoming message is returned by the cache adapter, and a send port picks it up and routes it on the service. This is not suitable for more complex solutions:
For these scenarios, the cache checking can be moved forward into the originating receive, so the cache adapter is only called if the response is known to exist in the cache. Added complexity here is that the receive pipeline needs to have the cache instance configuration, along with the send ports. This is shown in the Complex Messaging sample – run by enabling the receive location ReceiveRequest_ComplexMessaging.FILE:
Note that the cache instance is configured in the pipeline. Using a custom pipeline means you can configure the instance using the Cache Adapter UI in Visual Studio, which is easier and applies validation. While you could use a generic pipeline and modify the configuration per-instance on receive locations, that means manually hacking the XML;
There is a risk in this scenario that the object will exist in cache at the point of checking, but been removed by the time the cache is actually read. In this case the response from step 2 will be a passthrough of the original request – in the sample, this means the originating request will be copied to the output directory. Suspending the message may be a better option, but depending on the solution you may be able to cope with this differently.
[Source: http://geekswithblogs.net/EltonStoneman]
In a recent project we had a requirement for a configurable cache, residing on a BizTalk host and storing responses from WCF services which the BizTalk app had brokered. Producing this as a generic cache adapter was the preferred option, but project timescales didn’t allow for it – instead I’ve written the adapter as an open source component which is available on CodePlex: BizTalkCacheAdapter, and which we’re now making use of in the project.
It’s a simple design suitable for any situation where BizTalk is brokering services to consumers. The incoming request from the consumer is hashed to generate a cache key, and the response is stored in the cache against the request key. When future requests exactly match, they will receive the cached response; if the response doesn’t exist in the cache, the service provider is invoked and the response is added to the cache. The adapter is independent of the cache store being used – currently the only option provided is NCache Express, but extending it to use memcached or Velocity will be straightforward.
Overview
The adapter uses context properties to store cache configuration, so it’s suitable for orchestration or messaging solutions. Alongside the adapter on CodePlex is a sample project which demonstrates the approaches. The workflow is the same in both cases:
With the messaging solution, the same workflow is carried out using subscriptions to the promoted cache configuration properties, and the various send ports. This workflow may not be suitable for more complex messaging solutions, where services are invoked through dynamic send ports – there is an alternative workflow which I’ll cover in a separate post.
The only real complexity in the adapter is in putting the service response into the cache – the outgoing service request message has the cache configuration properties promoted, but the incoming response does not. In orchestration solutions this isn’t a problem, as the cache properties can be stored as state in the orchestration instance. In messaging solutions, the cache properties are retained by temporarily adding them to the cache, keyed by the interchange ID of the service instance, and removing them when the response is added to the cache.
Configuration
The cache adapter can be used with messages of any type, but with XML messages it can employ different cache configurations for different message types – e.g. a service to get current tax rates may configured to live for several days in the cache, while a service to get current exchange rates may use the same cache store, but be configured to expire after an hour. The CacheReceive and CacheReceiveXml pipelines contain properties for configuring the cache – it can be enabled/disabled for all messages, or (for the XML pipeline) only for configured message types:
Message-level configuration uses an SSO application store, which holds a MessageCacheConfigurationCollection object in XML:
<messageCacheConfigurationCollection xmlns=“http://schemas.sixeyed.com/CacheAdapter/2009“>
<messageCacheConfigurations>
<messageCacheConfiguration>
<messageType>http://schemas.sixeyed.com/CacheAdapterSample/2009#GetServerDateTime</messageType>
</messageCacheConfiguration>
<messageCacheConfiguration>
<messageType>http://schemas.xyz.com/2009#GetXYZ</messageType>
<cacheLifespan>PT10M</cacheLifespan>
</messageCacheConfiguration>
…
Caching is enabled for a message if:
The lifespan of the object put into cache will be either the cacheLifespan specified in the MessageCacheConfiguration in SSO, or the defaultLifespan specified in the cache send port:
(The point at which the object is removed from cache will depend on the provider, some will remove it when its lifespan expires, others only when its lifespan expires and the cache is full or the cleanup schedule runs).
Installation
For version 1.0.0.0, the CodePlex project comes with three project releases, which can be used independently or together:
– a documentation release:
– and two sample releases which should be deployed together:
Pre-requisite for the initial release is the non-commercial NCache Express to use as the cache provider.
To add the sample message-level cache configuration to SSO, the SSO Config Tool can import the provided .ssoconfig file into SSO.
I’ll update the project to add a memcached provider, and write an updated post when it’s available – and the same when Velocity is released. One advantage in having multiple providers would be in evaluating their performance against your expected caching load, so I’ll look at extending the sample solution and adding some LoadGen scripts.
And as Michael Stephenson commented, a nice alternative would be to move the caching up to the WCF layer as a behavior, so WCF requests would check the cache first. Two options for this – it can either be a client behaviour using a local cache store (which could be more performant and would have less impact on server resources, but requires the cache provider to be deployed client-side), or as an operation behaviour using a shared server-side cache (which could be more performant, depending on the type of messages being cached, would impact on server and network resource, but would make the caching transparent to consumers). The complexity is on caching the response message if not already cached – the cache key from the original message will have been lost, so there needs to be a different approach for maintaining state to correlate the WCF service request and response.
This month’s chat in my ongoing series of discussions with “connected systems” thought leaders is with Charles Young. Charles is a steady blogger, Microsoft MVP, consultant for Solidsoft Ltd, and all-around exceptional technologist.
Those of you who read Charles’ blog regularly know that he is famous for his articles of staggering depth which leave the […]
Ok, so I’ve been really late to this particular party, but I gotta say, I’m absolutely thrilled with the effect that changing the color schemes of VS has on improving my coding morale!! I’ve been using several schemes from Tomas Restrepo’s collectionand its done wonders for me (specifically Ragnorak Blue, Grey and Moria Alternate). Since […]
Here is the latest in my link-listing series. Also check out my ASP.NET Tips, Tricks and Tutorials page and Silverlight Tutorials page for links to popular articles I’ve done myself in the past.
You can also now follow me on twitter (@realscottgu) where I also post links and small posts.
Using ASP.NET 3.5’s ListView and DataPager Controls to Delete Data: Scott Mitchell continues his excellent tutorial series on the ASP.NET ListView control. In this article he discusses how to handle deleting data with it.
ASP.NET ListView: Displaying Hierarchical Data: Adam Pooler writes about how to use the ASP.NET ListView control to display hierarchical data within a web page.
ELMAH: Error Logging Module and Handlers for ASP.NET: ELMAH is a really cool open source error logging module for ASP.NET that can help you figure out what is going wrong with a site in production (and it enables you to diagnose things remotely in a browser). This post from Scott Hanselman nicely summarizes some of the things you can do with it. Visit the ELMAH home page to learn more and download it. Using ELMAH with ASP.NET MVC describes how to use it within ASP.NET MVC applications.
Examining ASP.NET 2.0’s Membership, Roles and Profile API Part 14: Scott Mitchell continues his excellent series on ASP.NET’s security features with an article that discusses how to create a page that permits users to update their security question and answer settings to reset passwords.
ASP.NET Tip/Trick: Use a Base Page Class for All Application Pages: A nice article that discusses a good best practice with ASP.NET applications – which is to create a helper base class that encapsulates common functionality that you can use across pages within your applications.
Setting the Default Input Focus and Default Button with jQuery: Chris Love has an nice post on how to improve the user experience of a page by setting the default focus and default button of a web form control using jQuery.
Automatically Minify and Combine JavaScript in Visual Studio: Dave Ward has a great article that describes how you can add a build command to Visual Studio that enables you to automatically compress and combine client-side JavaScript files. This makes your pages load faster on the client and improves the perceived performance of your sites.
Using Complex Types to Make Calling Services lessComplex: Dave Ward has a great post that discusses strategies on how to pass complex types to the server from client-side JavaScript.
Client-side Data Binding in ASP.NET AJAX 4.0: Fritz Onion has a great article about the new client-side templating features of ASP.NET AJAX 4.0 (which you can download and use today in .NET 3.5 projects). This enables powerful client data-binding scenarios against JSON based data. Also check out Politian’s Blog to find some great tutorials on how to use it.
jQuery Auto-Complete Text Box with ASP.NET MVC: Ben Scheirman has a really nice tutorial (taken from his upcoming ASP.NET MVC in Action book) that describes how to implement an auto-complete textbox using jQuery and ASP.NET MVC.
Using the jQuery Grid with ASP.NET MVC: Phil Haack has a nice post that describes how to use the jQuery Grid plugin with ASP.NET MVC to build an AJAX-enabled DataGrid.
ASP.NET MVC Forms Authentication with Active Directory: Mike has a nice post that shows how to setup an ASP.NET MVC application with Forms Authentication that uses Active Directory as the username/password credential store instead of a database. Also check out his post on ASP.NET MVC Forms Authentication with SQL Membership to learn more about how to setup forms authentication using a SQL Server database (instead of the default SQL Express one).
Visual Studio NUnit Templates for ASP.NET MVC: The VS Web Tools team has released updated NUnit templates that work with ASP.NET MVC 1.0. This enables you to automatically create a test project that uses NUnit instead of MSTest when you do a File->New Project and select the ASP.NET MVC 1.0 Project item.
13 ASP.NET MVC Extensibility Points You Have to Know: Simone Chiaretta has a post that nicely summarizes 13 extensibility points in ASP.NET MVC and how you can use them to customize your applications. Also check out the free chapter of his new ASP.NET MVC book.
Custom Route Constraints in ASP.NET MVC: Keyvan Nayyeriu has a nice post that discusses how to create a custom route constraint in ASP.NET MVC (one of the extensibility points in Simone’s list above). You can use these to control whether a route rule is used or not, and they can enable some pretty rich routing scenarios. Note that in addition to creating route constraint classes, ASP.NET MVC also supports using Regular Expressions and HTTP Method filters to constrain routes as well. Keyvan is the co-author with Simone of the Beginning ASP.NET MVC Book (free chapter available).
Tip: How to insert quotes automatically while typing attributes in the Visual Studio HTML editor: A useful tip that demonstrates how to configure Visual Studio and Visual Web Developer express to automatically add quotes around attributes when in the HTML source editor.
Hope this helps,
Scott
Since the WCF Publishing Wizard in BizTalk does not support adding custom headers defined at the server, we need to programmatically modify what gets created by the wizard to add custom headers. However, from the client you have the option to pass in header values at will. If you are passing in headers generated at the client BizTalk will take them and map them to the context. However, they show up as an XML fragment and not as individual data items. It becomes annoying to constantly parse the fragment each and every time you want to get to the data.
What we are really interested in is the ability to expose the end point with the header values already defined, accept the header values from the client and either promote or write the values to the context and lastly, be able to create a behavior that you can attach to your WCF endpoint that exposes the properties through configuration to let you dynamically, per end point, set the header items and what you want to do with them as they are submitted. This will be a three part posting with a post covering each of these features.
For this first post, we will focus on the ability to expose the end point with the header values already defined. What makes this even more interesting is that there is no WSDL file as this gets generated dynamically when you access the SVC file. If you wish you can create a static WSDL file and then use the externalMetadataLocation attribute of the element in the Web.config file that the wizard generates to specify the location of the WSDL file. Then the static WSDL file will be sent to the user in response to WSDL and metadata exchange (MEX) requests instead of the auto-generated WSDL.
In our solution, we did not want to have to create WSDL files for each of our endpoints, nor did we want to maintain them. We needed a way to hook in to the dynamic WSDL creation process.
There are a number of posts out there that talk about this but after reviewing them I found that none of them gave the whole picture. They were all very good and they provided enough information to fill in many missing pieces but there was enough missing that I though it warranted looking at the whole picture.
We are going to start by creating our own EndPointBehavior. The EndPointBehavior allows us to inject custom functionality in the WCF execution pipeline.
To create the EndPointBehavior we need to create a solution that references System.ServiceModel.dll and includes a class that derives from BehaviorExtensionElement, IWsdlExportExtension and IEndpointBehavior. We need the functionality of the BehaviorExtensionElement to implement the configuration of the behavior, the functionality of the IWsdlExportExtension to change the generated WSDL and the functionality of the IEndPointBehavior to define the endpoint and its behavior.
Lets add a class file to our solution called SoapHeaderEndpointBehavior. After we create the class and inherit from our objects we need to add the following line of code to the ExportEndPoint method
SoapHeaderWsdlExport.ExportEndpoint(exporter,context);
and we need to add the following two lines of code to the ApplyDispatchBehavior method.
SoapHeaderMessageInspector headerInspector = new SoapHeaderMessageInspector();
endpointDispatcher.DispatchRuntime.MessageInspectors.Add(headerInspector);
Our code should look like this:
using System;
using System.Collections.Generic;
using System.Collections;
using System.Configuration;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Configuration;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using System.Text;
using System.Xml;
using System.Xml.Schema;
using System.Web.Services;
using System.Web.Services.Description;
using WsdlDescription = System.Web.Services.Description.ServiceDescription;
namespace Services.WCF.ServiceBehavior
{
public class SoapHeaderEndpointBehavior : BehaviorExtensionElement, IWsdlExportExtension, IEndpointBehavior
{
#region BehaviorExtensionElement Members
public override Type BehaviorType
{
get
{
return typeof(SoapHeaderEndpointBehavior);
}
}
protected override object CreateBehavior()
{
return new SoapHeaderEndpointBehavior();
}
#endregion
#region IWsdlExportExtension Members
public void ExportContract(WsdlExporter exporter, WsdlContractConversionContext context)
{
//throw new NotImplementedException();
}
public void ExportEndpoint(WsdlExporter exporter, WsdlEndpointConversionContext context)
{
SoapHeaderWsdlExport.ExportEndpoint(exporter,context);
}
#endregion
#region IEndpointBehavior Members
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
//throw new NotImplementedException();
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
//throw new NotImplementedException();
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
SoapHeaderMessageInspector headerInspector = new SoapHeaderMessageInspector();
endpointDispatcher.DispatchRuntime.MessageInspectors.Add(headerInspector);
}
public void Validate(ServiceEndpoint endpoint)
{
//throw new NotImplementedException();
}
#endregion
}
}
When we added code to the ExportEndpoint method, we utilized a custom object. Let’s add another class to our solution to implement the SoapHeaderWsdlExport. This class will add a header schema and its namespace, create and add a header message description and finally add the header to the operation.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Schema;
using System.Reflection;
using System.Web.Services.Description;
using System.Xml;
using System.ServiceModel.Description;
using WsdlDescription = System.Web.Services.Description.ServiceDescription;
namespace Services.WCF.ServiceBehavior
{
class SoapHeaderWsdlExport
{
public static void ExportEndpoint(WsdlExporter exporter, WsdlEndpointConversionContext context)
{
// Read the schema of the custom header message
XmlSchema customSoapHeaderSchema = XmlSchema.Read(Assembly.GetExecutingAssembly().GetManifestResourceStream(“Services.WCF.ServiceBehavior.SoapHeader.xsd”),
new ValidationEventHandler (SoapHeaderWsdlExport.ValidationCallBack));
// Create the HeaderMessage to add to wsdl:message AND to refer to from wsdl:operation
System.Web.Services.Description.Message headerMessage = CreateHeaderMessage();
foreach (WsdlDescription wsdl in exporter.GeneratedWsdlDocuments)
{
// Add the schema of the CustomSoapHeader to the types AND add the namespace to the list of namespaces
wsdl.Types.Schemas.Add(customSoapHeaderSchema);
wsdl.Namespaces.Add(“sh”, SoapHeaderNames.SoapHeaderNamespace);
// The actual adding of the message to the list of messages
wsdl.Messages.Add(headerMessage);
}
addHeaderToOperations(headerMessage, context);
}
private static System.Web.Services.Description.Message CreateHeaderMessage()
{
// Create Message
System.Web.Services.Description.Message headerMessage = new System.Web.Services.Description.Message();
// Set the name of the header message
headerMessage.Name = SoapHeaderNames.SoapHeaderName;
// Create the messagepart and add to the header message
MessagePart part = new MessagePart();
part.Name = “Header”;
part.Element = new XmlQualifiedName(SoapHeaderNames.SoapHeaderName, SoapHeaderNames.SoapHeaderNamespace);
headerMessage.Parts.Add(part);
return headerMessage;
}
private static void addHeaderToOperations(System.Web.Services.Description.Message headerMessage, WsdlEndpointConversionContext context)
{
// Create a XmlQualifiedName based on the header message, this will be used for binding the header message and the SoapHeaderBinding
XmlQualifiedName header = new XmlQualifiedName(headerMessage.Name, headerMessage.ServiceDescription.TargetNamespace);
foreach (OperationBinding operation in context.WsdlBinding.Operations)
{
// Add the SoapHeaderBinding to the MessageBinding
ExportMessageHeaderBinding(operation.Input, context, header, false);
ExportMessageHeaderBinding(operation.Output, context, header, false);
}
}
private static void ExportMessageHeaderBinding(MessageBinding messageBinding, WsdlEndpointConversionContext context, XmlQualifiedName header, bool isEncoded)
{
// For brevity, assume Soap12HeaderBinding for Soap 1.2
SoapHeaderBinding binding = new Soap12HeaderBinding();
binding.Part = “Header”;
binding.Message = header;
binding.Use = isEncoded ? SoapBindingUse.Encoded : SoapBindingUse.Literal;
messageBinding.Extensions.Add(binding);
}
private static void ValidationCallBack(object sender, ValidationEventArgs args)
{
if (args.Severity == XmlSeverityType.Warning)
Console.WriteLine(“\tWarning: Matching schema not found. No validation occurred.” + args.Message);
else
Console.WriteLine(“\tValidation error: ” + args.Message);
}
}
}
In the addHeaderToOperations method there are two calls to the ExportMessageHeaderBinding method. The second call passes the operation.Output parameter which will pass the header back to the calling application with the response message. This also also means that the method signature at the client will be to pass in the header object by Ref. Since we needed the client to pass the header data into BizTalk we didn’t need to echo the header back to the client so we deleted this line. If you want to echo it back then keep this line (as shown in the code above).
Also, in the code above, in the ExportEndpoint and CreateHeaderMessage methods there was another custom class called SoapHeaderNames. This class contained the values that we wanted to place in the custom header. By creating a class for this data we could limit the location of this information to one location. The code for the SoapHeaderNames class looks like this:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Services.WCF.ServiceBehavior
{
public static class SoapHeaderNames
{
public const String SoapHeaderName = “SoapHeader”;
public const String AppName = “App”;
public const String UserName = “User”;
public const String SoapHeaderNamespace = http://servicebehavior.mycompany.com;
}
}
Way back at the top, in the ApplyDispatchBehavior method of the SoapHeaderEndpointBehavior class we have a custom object called SoapHeaderMessageInspector. Therefore, let’s add another class for the SoapHeaderMessageInspector. There are two methods that we must implement on the IDispatchMessageInspector and they are the AfterReceiveRequest and the BeforeReceiveRequest. Since we are interested in applying the headers in the dynamic WSDL we will only need code in the BeforeReceiveRequest. The code will look like this:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel.Dispatcher;
using System.ServiceModel;
using System.Xml;
namespace Services.WCF.ServiceBehavior
{
class SoapHeaderMessageInspector: IDispatchMessageInspector
{
#region IDispatchMessageInspector Members
public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel, System.ServiceModel.InstanceContext instanceContext)
{
return null;
}
public void BeforeSendReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
{
// Look for my custom header in the request
Int32 headerPosition = OperationContext.Current.IncomingMessageHeaders.FindHeader(SoapHeaderNames.SoapHeaderName, SoapHeaderNames.SoapHeaderNamespace);
// Get an XmlDictionaryReader to read the header content
XmlDictionaryReader reader = OperationContext.Current.IncomingMessageHeaders.GetReaderAtHeader(headerPosition);
// Read through its static method ReadHeader
SoapHeader header = SoapHeader.ReadHeader(reader);
if (header != null)
{
// Add the header from the request
reply.Headers.Add(header);
}
}
#endregion
}
}
This code grabs the header section and will inject the header elements. In the BeforeSendReply method you will see we are using a SoapHeader object. This object contains the properties and methods to deal with the elements that we will be reading and writing to the header.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.ServiceModel.Channels;
namespace Services.WCF.ServiceBehavior
{
[Serializable]
public class SoapHeader: MessageHeader
{
private string _app;
private string _user;
public string App
{
get
{
return (this._app);
}
set
{
this._app = value;
}
}
public string User
{
get
{
return (this._user);
}
set
{
this._user = value;
}
}
public SoapHeader()
{
}
public SoapHeader(string app, string user)
{
this._app = app;
this._user = user;
}
public override string Name
{
get { return (SoapHeaderNames.SoapHeaderName); }
}
public override string Namespace
{
get { return (SoapHeaderNames.SoapHeaderNamespace); }
}
protected override void OnWriteHeaderContents(System.Xml.XmlDictionaryWriter writer, MessageVersion messageVersion)
{
// Write the content of the header directly using the XmlDictionaryWriter
writer.WriteElementString(SoapHeaderNames.AppName, this.App);
writer.WriteElementString(SoapHeaderNames.UserName, this.User);
}
public static SoapHeader ReadHeader(XmlDictionaryReader reader)
{
String app = null;
String user = null;
// Read the header content (key) using the XmlDictionaryReader
if (reader.ReadToDescendant(SoapHeaderNames.AppName, SoapHeaderNames.SoapHeaderNamespace))
{
app = reader.ReadElementString();
}
if (reader.ReadToDescendant(SoapHeaderNames.UserName, SoapHeaderNames.SoapHeaderNamespace))
{
user = reader.ReadElementString();
}
if (!String.IsNullOrEmpty(app) && !String.IsNullOrEmpty(user))
{
return new SoapHeader(app, user);
}
else
{
return null;
}
}
}
}
There is also a schema, called SoapHeader.xsd, that needs to be added to the project as well. This schema defines the message contract for the header and is referenced and used in the ExportEndpoint method of the SoapHeaderWsdlExport class and looks like this:
<?xml version=”1.0″ encoding=”utf-16″?>
<xs:schema xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance” xmlns:b=”http://schemas.microsoft.com/BizTalk/2003″ xmlns=”http://servicebehavior.mycompany.com” attributeFormDefault=”unqualified” elementFormDefault=”qualified” targetNamespace=”http://servicebehavior.mycompany.com” xmlns:xs=”http://www.w3.org/2001/XMLSchema”>
<xs:element name=”SoapHeader”>
<xs:complexType>
<xs:sequence>
<xs:element name=”App” type=”xs:string” />
<xs:element name=”User” type=”xs:string” />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
At this point we have all the code that is part of our project. Once this is compiled, we need to add the assembly to the machine config.
There is an easy way to add entries by using the SvcConfigEditor.exe tool. This tool is part of the Windows SDK and, if installed, can be found in the \Program Files\Microsoft SDKs\Windows\v6.0A\Bin directory.
Once this utility is open you can click on the File ->Open->Config File menu item. Open the machine.config file. At the bottom of the tree view on the left side you will see an Advanced folder. Expand that node and expand the the Extensions folder. Click on the the ‘behavior element extensions’ node. At the bottom right, click on the new button. This will bring up the Extension Configuration Element Editor dialog box. Enter the name you wish to give to your extension and then click on the ellipses next to type. This will bring up the Type Browser dialog box. Browse to your component (also note that you can select assemblies already placed in the GAC). Once selected, your fully qualified assembly name will be entered. Select Save under the File menu. You are now ready to start using the new behavior.
Up to this point everything we have done has been specifically WCF functionality. The next paragraph will outline how we can utilized this behavior in a BizTalk WCF endpoint.
Create a WCF endpoint in BizTalk (if you need more information on creating a WCF endpoint check out the docs on MSDN (Insert Link)). One thing to keep in mind is that when you create a WCF end point in BizTalk using one of the standard bindings you will not have the option to specify a behavior. In order to specify a behavior you need to specify either the WCF-Custom or WCF-CustomIsolated binding. Once you select the binding type, click on the Configure button and the Transport Properties dialog will appear. Select the Behavior tab and then right click on the the Endpoint Behavior node. Once the popup menu appears, select Add extension. Select your behavior from the Select Behavior Extensions dialog box and click OK. Enter the rest of the specific information you need for the end point and click OK to save your endpoint.
We have now done everything that is needed to create a custom behavior including the ability to link into the dynamic WSDL creation process at run time, register the behavior and finally to use the behavior.
Now, when you create a client against the end point you will see that there will be two parameters for the web method call. The first will be the custom header and the second will be the message body. When we look in the object browser for the header object we will see that the two items appear that we defined in our behavior.
As I said at the beginning of this post, this will be a three part series. What we have not covered is the ability to accept these header values from the client and promote or write the values to the context (part 2) and we have not covered the ability to create a behavior that exposes the properties through configuration to let you dynamically, per end point, set the header items (part 3).