Development best practices using the WCF LOB Adapter SDK

You can use the best practices in this topic to improve your applications and adapters.

Call Abort Before Close on Channel Exception

When you write an application that uses the WCF channel model, you should call IRequestChannel.Abort before calling ChannelFactory.Close. If you do not, ChannelFactory.Close throws an exception.

In the following example, channel operations are attempted within a try/catch block. If an exception occurs, the channel is aborted.

ChannelFactory<IRequestChannel> cf = new  ChannelFactory<IRequestChannel>();  
IRequestChannel channel = null;  
try  
{  
  cf.Open();  
  channel = cf.CreateChannel();  
  channel.Open();  
  channel.Request();// This causes the channel to go into a faulted state.  
  channel.Close();  
}  
catch (Exception e)  
{  
  // Abort the channel if we have one  
  if(channel != null)  
    channel.Abort();  
}  
finally  
{  
  if (cf.State == CommunicationState.Opened)  
  {  
    cf.Close(); // It throws an exception that the channel is in a faulted state.  
  }  
}  

Implement Both Asynchronous and Synchronous Handlers

If possible, implement both asynchronous and synchronous handlers in your adapter. If your adapter only implements synchronous calls, you may run into blocking issues when processing a large volume of messages or when the adapter is used in a multithreaded environment.

Use Connection Pooling

The WCF LOB Adapter SDK supports connection pooling by default. However, it is up to the adapter developer to determine which connection pooling properties to expose as binding properties. The available Connection Pool settings are defined within Microsoft.ServiceModel.Channels.Common.ConnectionPoolSettings.

There are no options within the Consume Adapter Service Add-in to easily expose these properties as adapter connection properties. The adapter developer must manually define the properties in the adapter implementation.

public CustomAdapter(): base()  
{  
   this.Settings.ConnectionPool.EnablePooling = true;  
   this.Settings.ConnectionPool.HandlersShareSameConnection = true;  
   this.Settings.ConnectionPool.MaxConnectionsPerSystem = 50;  
   this.Settings.ConnectionPool.MaxAvailableConnections = 5;  
}  

Ensure That the Adapter Supports Two-Way Operations

If your adapter is called from BizTalk Server, it must support two-way operations, even if the return value is void. This is because BizTalk Server expects a response returned from any outgoing request, and throws an exception if your adapter only implements one-way operations.

Here is an example of a request-response contract that returns void.

[ServiceContract(Namespace=”Http:Microsoft.BizTalk.Samples.WCFAdapterSample”)]  
public interface ICalculator  
{  
   [OperationContract]  
   void Add(double n1, double n2);  
}  

Implement Tracing

During the development cycle, it might not seem important to add tracing to your adapter, since you can step through the code and debug any problems. However, once the adapter is installed in a production environment, you might not be able to use run-time debugging to isolate problems. If you have enabled tracing throughout your adapter, it can be used to isolate where failures are occurring.

See Trace an adapter with the WCF LOB Adapter SDK for further details.

Use URI Properties for Frequently Changed Settings

When deciding whether to expose a custom property as a binding or URI property, it is recommended to use a URI property if the value changes often. Binding properties should be reserved for values that rarely change.

An example binding property would be a database server name that is used by all connections. An example URI property would be a specific table or stored procedure to be used by that specific connection.

Do Not Pass User Name or Password Values in the URI

If your adapter requires the caller’s credentials, it is recommended to use the ClientCredentials class to retrieve credential values instead of passing client credentials as part of the URI. The ClientCredentials class is a standard feature of WCF that is intended for passing credential information from the client to the service in a more secure manner. Passing the user information as part of the URI string may expose the user information while in transit.

The recommended methods of passing credentials are shown in the following table.

Method Description
Design time When using the Add Adapter Service Reference Plug-in, you can specify the client credential types that the adapter supports.
Run time When using a generated .NET CLR proxy, you can programmatically set the client credentials.

static void Main(string[] args) { EchoServiceClient client = new EchoServiceClient(); client.ClientCredentials.UserName.UserName = "TestUser"; client.ClientCredentials.UserName.Password = "TestPassword"; string response=client.EchoString("Test String"); }

Alternatively, if you need to interact with the channel directly, you can use the WCF channel model to specify the client credentials when creating a channel factory.

EchoAdapterBinding binding = new EchoAdapterBinding(); binding.Count = 3; ClientCredentials clientCredentials = new ClientCredentials(); clientCredentials.UserName.UserName = "TestUser"; clientCredentials.UserName.Password = "TestPassword"; BindingParameterCollection bindingParms = new BindingParameterCollection(); bindingParms.Add(clientCredentials); EndpointAddress address = new EndpointAddress("echo://"); IChannelFactory<IRequestChannel> requestChannelFactory = binding.BuildChannelFactory<IRequestChannel>(bindingParms); requestChannelFactory.Open();
WCF configuration In the client configuration file, add an <endpointBehaviors> element that includes <clientCredentials>.

<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0"> <system.serviceModel> . . . . . <behaviors> <endpointBehaviors> <behavior name="clientEndpointCredential"> <clientCredentials> <windows allowNtlm="false" allowedImpersonationLevel="Delegation" /> </clientCredentials> </behavior> </endpointBehaviors> </behaviors> </system.serviceModel> </configuration>
Using BizTalk When using the WCF adapter to consume your adapter, you can add the clientCredentials behavior extension on the Behavior tab. Once this has been added, you can set the desired client credentials in the endpoint behavior.

Do Not Return Both StrongDataSetType and WeakDataSetType

If your adapter returns a DataSet, use either Microsoft.ServiceModel.Channels.Common.QualifiedType.StrongDataSetType%2A or Microsoft.ServiceModel.Channels.Common.QualifiedType.WeakDataSetType%2A, but do not use both at the same time. The root node name and namespace produced by both types are identical, and cannot exist simultaneously in a WSDL.

While WeakDataSetType and StrongDataSetType both represent a System.Data.DataSet, StrongDataSetType is easier to use in .NET applications, since the proxy generated appears as System.Data.Dataset. The proxy generated by WeakDataSetType is XmlElement[], which is more difficult to use in a .NET application. BizTalk Server cannot consume the schema returned from StrongDataSet, but is able to consume WeakDataSetType.

Note

StrongDataSetType and WeakDataSetType only control how the client application interprets the XML messages passed by the adapter. The XML message is the same regardless of which type is specified.

Note

When returning StrongDataSetType, you must set Microsoft.ServiceModel.Channels.Common.MetadataSettings.CompileWsdl%2A to false. When set to true, the default, XmlSchemaSet::Compile is called within the adapter to ensure there are no errors in the WSDL, however the schema produced by StrongDataSetType generates an exception in XmlSchemaSet.

Setting CompileWsdl to false bypasses WSDL schema validation within the adapter and validation occurs during proxy generation. Utilities such as svcutil.exe are able to generate a proxy for both StrongDataSetType and WeakDataSetType.

To work with both BizTalk and .NET environments, consider implementing a binding property that allows switching between the two return types as dictated by the environment.

internal static QualifiedType GetDataSetQualifiedType(MyAdapterBindingProperties bindingProperties)  
{  
   if (bindingProperties.EnableBizTalkCompatibility)  
      return QualifiedType.WeakDataSetType;  
   else  
      return QualifiedType.StrongDataSetType;  
}  

Create Meaningful XSD Schema Names in BizTalk Server

When using the Consume Adapter Service BizTalk Project Add-in design-time tool, the name of the XSD schema generated in your BizTalk project is created using the DefaultXsdFileNamePrefix property, the fileNameHint annotation in the WSDL and, if required, a unique integer value.

For example, if DefaultXsdFileNamePrefix is set to “MyAdapter” and the fileNameHint annotation is set to “Stream”, the XSD schema created is named MyAdapterStream.xsd.

<xs:schema elementFormDefault='qualified' targetNamespace='http://schemas.microsoft.com/Message' xmlns:xs='http://www.w3.org/2001/XMLSchema' xmlns:tns='http://schemas.microsoft.com/Message'>  
<xs:annotation>  
<xs:appinfo>  
<fileNameHint xmlns='http://schemas.microsoft.com/servicemodel/adapters/metadata/xsd'>Stream</fileNameHint>  
</xs:appinfo>  
</xs:annotation>  
<xs:simpleType name='StreamBody'>  
<xs:restriction base='xs:base64Binary' />  
</xs:simpleType>  
</xs:schema>  

Note

The default value of DefaultXsdFileNamePrefix is the name of your binding. To specify a different value, override DefaultXsdFileNamePrefix in your Adapter class that is derived from Microsoft.ServiceModel.Channels.Common.AdapterBinding.

There are two possible methods to add the fileNameHint annotation to the schema: override the Export…Schema methods on OperationMetadata\TypeMetadata or override the IWsdlRetrieval implementation of the adapter. For either method, you can call the base implementation and then add the annotation to the schemas in the schema collection.

Note

When overriding the Export…Schema methods, there may be multiple operation/type definitions in the same schema; the adapter should make sure that multiple occurrences of the fileNameHints annotation in the same schema do not conflict. The Consume Adapter Service Add-in uses the first occurrence of fileNameHint if it occurs multiple times within a schema.

In the following example, IWsdlRetrieval is used to add the fileNameHint annotation to the WSDL.

sealed class MyAdapterWsdlRetrieval : IWsdlRetrieval  
{  
   IWsdlRetrieval mBaseWsdlRetrieval;  
   public MyAdapterWsdlRetrieval(IWsdlRetrieval baseWsdlRetrieval)  
   {  
      mBaseWsdlRetrieval = baseWsdlRetrieval;  
   }  

   ServiceDescription IWsdlRetrieval.GetWsdl(Microsoft.ServiceModel.Channels.MetadataRetrievalNode[] nodes, Uri uri, TimeSpan timeout)  
   {  
      ServiceDescription baseDesc = mBaseWsdlRetrieval.GetWsdl(nodes, uri, timeout);  
      foreach (XmlSchema schema in baseDesc.Types.Schemas)  
      {  
         CreateFileNameHint(schema);  
      }  
      return baseDesc;  
   }  

   void CreateFileNameHint(XmlSchema schema)  
   {  
      string targetNamespace = schema.TargetNamespace;  
      if (string.IsNullOrEmpty(targetNamespace))  
         return;  
      string fileNameHint = null;  
      //determine the filename based on namespace  
      if (targetNamespace.StartsWith("myadapter:// adapters.samples.myadaptpter/HelloWorld"))  
      {  
         fileNameHint = "HelloWorld";  
      }  
      if (targetNamespace.StartsWith("myadapter:// adapters.samples.myadapter/Hello"))  
      {  
         fileNameHint = "Hello";  
      }  
      //create the annotation and populate it with fileNameHint  
      XmlSchemaAnnotation annotation = new XmlSchemaAnnotation();  
      XmlSchemaAppInfo appInfo = new XmlSchemaAppInfo();  
      XmlDocument doc = new XmlDocument();  
      XmlNode[] fileNameHintNodes = new XmlNode[1];  
      fileNameHintNodes[0] = doc.CreateElement(null, "fileNameHint", "http://schemas.microsoft.com/servicemodel/adapters/metadata/xsd");  
      fileNameHintNodes[0].AppendChild(doc.CreateTextNode(fileNameHint));  
      appInfo.Markup = fileNameHintNodes;  
      annotation.Items.Add(appInfo);  
      schema.Items.Insert(0, annotation);  
   }  

Do Not Modify AdapterEnvironmentSettings During Processing

The adapter should only set the AdapterEnvironmentSettings, ConnectionPoolManager, ConnectionPool, and CommonCacheSize during adapter initialization and should not attempt to modify the values for a running instance.

If these settings are modified on a currently running adapter instance, this may result in new connections overwriting configuration settings for currently executing connections.

See Also

Development best practices using the WCF LOB Adapter SDK