The ChannelAdam SOAP Library

Overview

This is an open source (Apache License 2.0) .NET Standard 2.0 code library that provides a fluent API to make it as easy as possible to create a SOAP 1.1 or SOAP 1.2 message.

Among other things, it is perfect for building SOAP envelopes to use in lightweight web frameworks such as Nancy.

Getting Started

NuGet Package Installation

To install the ChannelAdam.Soap NuGet package run the following command in the Package Manager Console:

1PM> Install-Package ChannelAdam.Soap

Usage

There are plenty of overloads and other methods for more advanced usage - see the reference in the next section.

SOAP Envelope with a Header Action and Body

Here is a simple example of creating a SOAP 1.1 envelope with a header action and body payload.

1string envelopeXml = SoapBuilder.CreateSoap11Envelope()
2    .WithHeader.AddAction("http://my.action/here")
3    .WithBody.AddEntry("<myXml>hello from the body</myXml>")
4    .Build()
5    .ToString();

And for SOAP 1.2:

1string envelopeXml = SoapBuilder.CreateSoap12Envelope()
2    .WithHeader.AddAction("http://my.action/here")
3    .WithBody.AddEntry("<myXml>hello from the body</myXml>")
4    .Build()
5    .ToString();

There is even an overload to create the body entry from .NET objects. For instance:

 1public class MyModel
 2{
 3    public string Value { get; set; } 
 4}
 5
 6...
 7
 8var model = new MyModel { Value = "hello!" };
 9
10string envelopeXml = SoapBuilder.CreateSoap12Envelope()
11    .WithHeader.AddAction("http://my.action/here")
12    .WithBody.AddEntry(model)
13    .Build()
14    .ToString();

Easy!

SOAP Faults

To create a SOAP 1.1 fault:

1string envelopeXml = SoapBuilder.CreateSoap11Envelope()
2    .WithBody.SetFault(Soap11FaultCode.Client, "SOAP 1.1 fault string here")
3    .Build()
4    .ToString();

And for a SOAP 1.2 fault:

1string envelopeXml = SoapBuilder.CreateSoap11Envelope()
2    .WithBody.SetFault(Soap12FaultCode.Sender, "SOAP 1.2 fault reason here")
3    .Build()
4    .ToString();

There are overloads to set other SOAP fault value properties as well.

Reference

This reference assumes that the reader is familiar with SOAP terminology. For more information see the following SOAP specifications:

SoapBuilder

The SoapBuilder static class is the fluent starting point for building a SOAP 1.1 or SOAP 1.2 envelope.

The SoapBuilder static class has two methods:

  • ISoap11EnvelopeBuilder CreateSoap11Envelope() - to start the process of creating a SOAP 1.1 envelope

  • ISoap12EnvelopeBuilder CreateSoap12Envelope() - to start the process of creating a SOAP 1.2 envelope

The ISoap11EnvelopeBuilder interface has two properties and one method:

  • ISoap11BodyBuilder WithBody { get; } - to specify the SOAP body or a fault

  • ISoap11HeaderBuilder WithHeader { get; } - to specify the SOAP header

  • XContainer Build() - to finally build the SOAP envelope as specified

The ISoap12EnvelopeBuilder interface is similar:

  • ISoap12BodyBuilder WithBody { get; }

  • ISoap12HeaderBuilder WithHeader { get; }

  • XContainer Build()

Usage of SoapBuilder

The general procedure for usage is:

  1. Call one of the SoapBuilder.CreateSoap??Envelope() methods.

  2. If necessary, add a header via the WithHeader property.

  3. Add a body or fault message via the WithBody property.

  4. Once all the details have been specified, simply call the Build() method which will return an XContainer.

  5. If there is something that you need to do to the contents of the SOAP envelope that is not currently allowed by the API, then you can always directly modify the elements in the XContainer and do magical things with LINQ.

  6. A simple .ToString() on the XContainer returned from Build() will then give you the XML string of the SOAP envelope/payload - by default formatted with indentation. There is also the overload .ToString(System.Xml.Linq.SaveOptions options) that allows you to change the formatting of whitespace and indentation.

Specify the SOAP Header

The SOAP header can be specified via the WithHeader property.

For SOAP 1.1, WithHeader chains the following methods:

1ISoap11EnvelopeBuilder AddAction(string action);
2
3ISoap11EnvelopeBuilder AddBlock(string headerXml);
4ISoap11EnvelopeBuilder AddBlock(XContainer headerBlock);
5ISoap11EnvelopeBuilder AddBlock(object toSerialise);
6ISoap11EnvelopeBuilder AddBlock(object toSerialise, string toElementName, string toElementNamespace);
7
8ISoap11EnvelopeBuilder SetStandardSoapEncoding();
9ISoap11EnvelopeBuilder SetCustomSoapEncoding(string soapEncodingNamespace);

Notes:

  • AddAction(string action) adds an action header block with the specified action.
  • AddBlock(XContainer headerBlock) adds any arbitrary header block - e.g. from a System.Xml.Linq.XElement.
  • AddBlock(object toSerialise, string toElementName, string toElementNamespace) serialises the given object and during serialisation overrides the XML element name and namespace with those specified.
  • ISoap11EnvelopeBuilder SetStandardSoapEncoding() sets the SoapEncoding attribute to the standard encoding namespace
  • ISoap11EnvelopeBuilder SetCustomSoapEncoding(string soapEncodingNamespace) sets the SoapEncoding attribute to your custom namespace

For SOAP 1.2, WithHeader is nearly identical to SOAP 1.1, except an ISoap12EnvelopeBuilder is returned instead of a ISoap11EnvelopeBuilder.

Specify the SOAP Body Payload or Fault

The SOAP body payload or fault can be specified via the WithBody property which provides the following methods.

For SOAP 1.1

 1ISoap11EnvelopeBuilder AddEntry(string bodyXml);
 2ISoap11EnvelopeBuilder AddEntry(XContainer fromNode);
 3ISoap11EnvelopeBuilder AddEntry(object toSerialise);
 4ISoap11EnvelopeBuilder AddEntry(object toSerialise, string toElementName, string toElementNamespace);
 5
 6ISoap11EnvelopeBuilder SetFault(Soap11FaultCode code, string faultString);
 7ISoap11EnvelopeBuilder SetFault(Soap11FaultCode code, string faultString, string faultActor);
 8ISoap11EnvelopeBuilder SetFault(Soap11FaultCode code, string faultString, string faultActor, IEnumerable<XContainer> detailEntries);
 9
10ISoap11EnvelopeBuilder SetStandardSoapEncoding();
11ISoap11EnvelopeBuilder SetCustomSoapEncoding(string soapEncodingNamespace);

Notes:

  • AddEntry(object toSerialise, string toElementName, string toElementNamespace) serialises the given object and during serialisation overrides the XML element name and namespace with those specified.

SOAP 1.2 Faults are more descriptive than SOAP 1.1, hence the overloads of SetFault with more parameters.

For SOAP 1.2

 1ISoap12EnvelopeBuilder AddEntry(string bodyXml);
 2ISoap12EnvelopeBuilder AddEntry(XContainer fromNode);
 3ISoap12EnvelopeBuilder AddEntry(object toSerialise);
 4ISoap12EnvelopeBuilder AddEntry(object toSerialise, string toElementName, string toElementNamespace);
 5
 6ISoap12EnvelopeBuilder SetFault(Soap12FaultCode code, string reason);
 7ISoap12EnvelopeBuilder SetFault(Soap12FaultCode code, string subCode, string reason);
 8ISoap12EnvelopeBuilder SetFault(Soap12FaultCode code, XNamespace subCodeNamespace, string subCode, string reason);
 9ISoap12EnvelopeBuilder SetFault(Soap12FaultCode code, XNamespace subCodeNamespace, string subCode, string reason, string reasonXmlLanguage);
10ISoap12EnvelopeBuilder SetFault(Soap12FaultCode code, XNamespace subCodeNamespace, string subCode, string reason, string reasonXmlLanguage, string node);
11ISoap12EnvelopeBuilder SetFault(Soap12FaultCode code, XNamespace subCodeNamespace, string subCode, string reason, string reasonXmlLanguage, string node, string role);
12ISoap12EnvelopeBuilder SetFault(Soap12FaultCode code, XNamespace subCodeNamespace, string subCode, string reason, IEnumerable<XContainer> detailEntries);
13ISoap12EnvelopeBuilder SetFault(Soap12FaultCode code, XNamespace subCodeNamespace, string subCode, string reason, string reasonXmlLanguage, IEnumerable<XContainer> detailEntries);
14ISoap12EnvelopeBuilder SetFault(Soap12FaultCode code, XNamespace subCodeNamespace, string subCode, string reason, string reasonXmlLanguage, string node, string role, IEnumerable<XContainer> detailEntries);
15
16ISoap12EnvelopeBuilder SetStandardSoapEncoding();
17ISoap12EnvelopeBuilder SetCustomSoapEncoding(string soapEncodingNamespace);

The ChannelAdam Core Library

IMPORTANT: DEPRECATION NOTICE

This full .NET Framework library has been deprecated.

The functionality has been refactored out into the following ChannelAdam .NET Standard libraries.

Overview

The open source, full .NET Framework, ChannelAdam Core Library provides some helpful .NET functionality - such as the implementation of various design patterns. This functionality also just happens to be used in the other ChannelAdam code libraries…

Below are the main features of the library. See the linked code for details.

Please contact me if you have any questions.

Getting Started

NuGet Package Installation

To install the ChannelAdam.Core NuGet package run the following command in the Package Manager Console:

1PM> Install-Package ChannelAdam.Core

Features

Disposable Pattern

Have you ever thought that the disposable pattern was complicated to implement or understand? Inherit this and it’s easy!

Command Pattern

Have you ever needed to implement a Command Pattern?

Command Action

A Command Action has an Execute method with a void return parameter.

Command Function

A Command Function has an Execute method with a specified return parameter.

Reversible Commands

Need to execute some commands and be able to undo them? This Reversible Command implementation is for you!

Use the Reversible Command Manager to manage the stack of commands that can be reversed.

Set Property - Command Implementation

A command implementation for setting a property on an object.

Weak Event Pattern

Need a Weak Event in specific circumstances to prevent memory leaks in your event listeners?

Simple Logger

A very simple interface for logging or writing out to the console.

Embedded Resources

Easily retreive embedded resources from their manifest resource stream.

Real Proxies

Getting low-level with a Real Proxy? These are disposable and have retry capability!

Retry Policies (for Transient Fault Handling)

Some handy interfaces to describe retry policies.

XML

XML validation, serialisation and type conversion made easy.

The ChannelAdam Test Framework Libraries

IMPORTANT: DEPRECATION NOTICE

This documention is for Version 1 of these full .NET Framework libraries and it has been deprecated.

Please see the [Version 2] documentation for the following ChannelAdam .NET Standard libraries.

Overview

The open source ChannelAdam Test Framework Libraries are a set of automated testing libraries to make testing your .NET code easier.

There are currently 3 main libraries:

  • The ChannelAdam Test Framework - the common test framework library without a dependency on any other test runner or library
  • The ChannelAdam Test Framework for MSTest - containing MSTest-specific implementations of interfaces in the common library
  • The ChannelAdam Test Framework for BizTalk - containing functionality specifically for testing Microsoft BizTalk artifacts.

The ChannelAdam Test Framework Libraries are fully compatible with MSTest and .NET Framework 4.0 and above.

Note: I highly recommend using these libraries with Behaviour-Driven Development practices and the usage of SpecFlow.

Getting Started

NuGet Package Installation

The easiest way to get started is to install the library that suits your favourite test runner or test library. Currently however there is only the MSTest implementation…

To install the ChannelAdam.TestFramework.MSTest NuGet package run the following command in the Package Manager Console:

1PM> Install-Package ChannelAdam.TestFramework.MSTest

Features

Logger

Logging useful information to the output of a test is extremely important.

The ISimpleLogger interface in the ChannelAdam Core Library provides a very simple interface with a simple Log method that is intended for usage in scenarios such writing test output.

The SimpleConsoleLogger class implements ISimpleLogger and is a thin wrapper around Console.WriteLine().

Log and then Assert

I cannot emphasise enough how important it is to log useful to the output of a test. Developers and analysts almost should be able to read the output of a test like a story. Analysts especially should review the test outputs and be satisfied that the test was implemented correctly - almost as if they performed the test themselves. Therefore, when an assertion is performed, we want the test output to actually tell us that an assertion is taking place and provide a information about what is being asserted.

The Log Asserter interface provides all the usual assertion methods that you are familiar with, and the first parameter of all of the methods is a string that will be outputted to an ISimpleLogger (provided in the constructor) before performing the actual assertion.

This LogAssert class is the MSTest implementation of the Log Asserter interface.

Test Easy Base Class for Tests

Logging, assertions and testing for exceptions are 3 of some fundamental features common to most automated test suites. Wouldn’t it be nice to be able to perform that functionality easily and keep your test code to a minimum?

The TestEasy base class for tests provides those abilities.

This class provides the following helpful properties.

  • ISimpleLogger Logger - for logging to the test output

  • ILogAsserter LogAssert - your handy-dandy log asserter

  • ExpectedExceptionDescriptor ExpectedException - describes the exception that is expected to occur for a specific test case

  • Exception ActualException - for storing the actual exception that occurred

When the ActualException property is set, the details are automatically logged to the test output.

TestEasy also contains the following helpful methods:

  • void Try(Action action) - performs the given action and catches exceptions into ActualException

  • void Try(Func<Task> action) - performs the given asynchronous action that returns a Task, waits for it finish, and catches exceptions into ActualException

  • void AssertNoExceptionOccurred() - asserts that no ActualException exists/occurred

  • void AssertExpectedException(Type expectedExceptionType) - asserts that type of the value of ActualException is the given expected type

  • void AssertExpectedException() - asserts that the ActualException is equivalent to that described by the ExpectedException descriptor. When the ActualException is an System.AggregateException, the assertion will pass if there is at least one inner exception equivalent to the ExpectedException descriptor.

Expected Exception Descriptor

The ExpectedException property is implemented by the ExpectedExceptionDescriptor class. This class describes the exception that is expected to occur.

There are 2 properties in ExpectedExceptionDescriptor that are specifically used in the AssertExpectedException() method:

  • string MessageShouldContainText - which describes the substring that the ActualException.Message property should contain

  • Type ExpectedType - which describes the System.Type that ActualException should be.

Either or both of these properties can be set. When set, the details are automatically logged to the test output.

Moq Test Fixture Base Class for Tests

MoqTestFixture inherits all the goodies from TestEasy and also provides a MyMockRepositoy property to access the Moq MockRepository.

The default constructor automatically creates a Moq “loose” MockRepository, but that can be specified in an overload of the constructor.

Text Tester

The TextTester class is used in tests for asserting whether actual text is similar enough to the given expected text.

Under the covers, it uses the DiffPlex open source library to identify differences.

The TextTester class has the following properties:

  • string ActualText

  • string ExpectedText

  • DiffPaneModel Differences

  • Action<DiffPaneModel> TextDifferenceFilter - a filter for the diff (see below)

And the following methods:

  • void ArrangeActualText(Assembly assembly, string resourceName) - to arrange the actual text from an embedded resource in the given assembly

  • void ArrangeActualText(string text) - to arrange the actual text from the given string

  • void ArrangeExpectedText(Assembly assembly, string resourceName) - to arrange the expected text from an embedded resource in the given assembly

  • void ArrangeExpectedText(string text) - to arrange the expected text from the given string

  • virtual void AssertActualTextEqualsExpectedText() - to assert the actual text against the expected text

  • bool IsEqual() - determines if the given actual and expected text is equivalent

  • virtual bool IsEqual(string expected, string actual) - determines if the given actual and expected text is equivalent

Filtering Text Differences

The TextTester class provides 3 mechanisms for hooking into the DiffPlex DiffPaneModel difference result, allowing you to manipulate it before a final decision is made as
to whether or not the actual text is similar enough to the expected text.

  • An event that is raised with the differences
1    /// <summary>
2    /// Occurs when a text difference is detected, allowing a listener to filter the differences and
3    /// change the Line[x].Type to ChangeType.UnChanged so that the difference is no longer treated as a difference.
4    /// </summary>
5    /// <remarks>
6    /// This event or the TextDifferenceFilter property can be used for this purpose.
7    /// </remarks>
8    public event EventHandler<TextDifferenceDetectedEventArgs> TextDifferenceDetectedEvent;
  • An action delegate method that is called with the differences
1    /// <summary>
2    /// Gets or sets the Action delegate to be invoked when a text difference is detected, allowing differences to be filtered out by
3    /// changing the Line[x].Type to ChangeType.UnChanged - so that a difference is no longer treated as a difference.
4    /// </summary>
5    /// <value>The Action delegate.</value>
6    /// <remarks>
7    /// This property or TextDifferenceDetectedEvent can be used for this purpose.
8    /// </remarks>
9    public Action<DiffPaneModel> TextDifferenceFilter { get; set; }
  • A virtual method that allows you to inherit from the tester class and override the differences when they are detected.
1    protected virtual void OnTextDifferenceDetected(DiffPaneModel differences)

For a quick example, please see the Behaviour Specification for the TextTester class and the associated
text tester code

XML Tester

The XmlTester class is used in tests for asserting whether the actual XML is similar enough to the given expected XML.

Under the covers, it uses the XMLUnit.NET open source library to identify differences.

The XMLTester class has the following properties:

  • XElement ActualXml

  • XElement ExpectedXml

  • Diff Differences

And the following methods:

  • void ArrangeActualXml(Assembly assembly, string resourceName) - to arrange the actual XML from an embedded resource in the given assembly

  • void ArrangeActualXml(XElement xmlElement) - to arrange the actual XML from a given XElement

  • void ArrangeActualXml(object valueToSerialise) - to arrange the actual XML by serialising the given object

  • void ArrangeActualXml(object valueToSerialise, XmlRootAttribute xmlRootAttribute) - to arrange the actual XML by serialising the given object and applying the given XmlRootAttribute during the serialisation

  • void ArrangeActualXml(object valueToSerialise, XmlAttributeOverrides xmlAttributeOverrides) - to arrange the actual XML by serialising the given object and applying the given XmlAttributeOverrides during the serialisation

  • void ArrangeActualXml(string xmlValue) - to arrange the actual XML from the given XML string

  • void ArrangeExpectedXml(Assembly assembly, string resourceName) - to arrange the expected XML from an embedded resource in the given assembly

  • void ArrangeExpectedXml(XElement xmlElement) - to arrange the expected XML from a given XElement

  • void ArrangeExpectedXml(object valueToSerialise) - to arrange the expected XML by serialising the given object

  • void ArrangeExpectedXml(object valueToSerialise, XmlRootAttribute xmlRootAttribute) - to arrange the expected XML by serialising the given object and applying the given XmlRootAttribute during the serialisation

  • void ArrangeExpectedXml(object valueToSerialise, XmlAttributeOverrides xmlAttributeOverrides) - to arrange the expected XML by serialising the given object and applying the given XmlAttributeOverrides during the serialisation

  • void ArrangeExpectedXml(string xmlValue) - to arrange the expected XML from the given XML string

  • virtual void AssertActualXmlEqualsExpectedXml() - to assert the actual XML against the expected XML

  • virtual void AssertActualXmlEqualsExpectedXml(IXmlFilter xmlFilter) - to assert the actual XML against the expected XML, ignoring items listed in the given IXmlFilter (see below)

  • bool IsEqual() - determines if the given actual and expected XML is equivalent

  • bool IsEqual(XNode expected, XNode actual) - determines if the given actual and expected XNode XML is equivalent

  • virtual bool IsEqual(XmlNode expected, XmlNode actual) - determines if the given actual and expected XmlNode XML is equivalent

Ignoring / Filtering XML Elements

The AssertActualXmlEqualsExpectedXml(IXmlFilter xmlFilter) override allows you to specify an IXmlFilter interface which allows you to specify a list of the local names of XML elements to ignore in comparisons, and/or a list of XPath expressions to ignore in comparisons.

Examples

For a quick example, please see the Behaviour Specification for the XmlTester class and the XML tester code

XML Filter

The XmlFilter class allows you to specify a list of the local names of XML elements to ignore in comparisons, and/or a list of XPath expressions to ignore in comparisons.

Two constructor methods allow you to easily specify these lists.

  • XmlFilter(IList<string> elementLocalNamesToIgnore)

  • XmlFilter(IList<string> elementLocalNamesToIgnore, IList<string> xpathsToIgnore)

XML Asserter

The XmlAsserter class provides utility methods for performing assertions on XML values. It has the following methods:

  • void XPathValueEquals(string description, string xpath, XNode rootElement, string expected) - to assert that the given XPath of the provided XNode has the given expected string value

  • void XPathValueEquals(string description, string xpath, XNode rootElement, XmlNamespaceManager namespaceManager, string expected) - uses the provided namespace manager to assert that the given XPath of the provided XNode has the given expected string value

  • void XPathValuesAreEqual(string description, string xpath, XNode expectedElements, XNode actualElements) - to assert that the value of the XPath in the given actual XNode equals the corresponding value in the given expected XNode

  • void XPathValuesAreEqual(string description, string expectedXpath, XNode expectedElements, string actualXpath, XNode actualElements) - to assert that the value of the actual XPath in the actual XNode is the same as the value of the expected XPath in the expected XNode

  • void XPathValuesAreEqual(string description, string expectedXpath, XNode expectedElements, XmlNamespaceManager expectedNamespaceManager, string actualXpath, XNode actualElements, XmlNamespaceManager actualNamespaceManager) - to assert that the value of the actual XPath in the actual XNode is the same as the value of the expected XPath in the expected XNode, using the provided XML namespace managers

  • void AreEqual(XElement expectedXml, XElement actualXml) - to assert that the given expected XElement is equivalent to the actual XElement

  • void AreEqual(XElement expectedXml, XElement actualXml, IXmlFilter xmlFilter) - to assert that the given expected XElement is equivalent to the actual XElement - using the given IXmlFilter

Mapping Testers

There are 4 map tester classes to be used depending on the type of input and output of your map:

The MappingFromXmlToXmlTester class has the following method overrides for arranging the input XML.

  • void ArrangeInputXml(Assembly assembly, string resourceName) - to arrange the input XML from an embedded resource in the given assembly

  • void ArrangeInputXml(XElement xmlElement) - to arrange the input XML from a given XElement

  • void ArrangeInputXml(object valueToSerialise) - to arrange the input XML by serialising the given object

  • void ArrangeInputXml(object valueToSerialise, XmlRootAttribute xmlRootAttribute) - to arrange the input XML by serialising the given object and applying the given XmlRootAttribute during the serialisation

  • void ArrangeInputXml(object valueToSerialise, XmlAttributeOverrides xmlAttributeOverrides) - to arrange the input XML by serialising the given object and applying the given XmlAttributeOverrides during the serialisation

  • void ArrangeInputXml(string xmlValue) - to arrange the input XML from the given XML string

  • void ArrangeExpectedOutputXml(Assembly assembly, string resourceName) - to arrange the expected output XML from an embedded resource in the given assembly

  • void ArrangeExpectedOutputXml(XElement xmlElement) - to arrange the expected output XML from a given XElement

  • void ArrangeExpectedOutputXml(object valueToSerialise) - to arrange the expected output XML by serialising the given object

  • void ArrangeExpectedOutputXml(object valueToSerialise, XmlRootAttribute xmlRootAttribute) - to arrange the expected output XML by serialising the given object and applying the given XmlRootAttribute during the serialisation

  • void ArrangeExpectedOutputXml(object valueToSerialise, XmlAttributeOverrides xmlAttributeOverrides) - to arrange the expected output XML by serialising the given object and applying the given XmlAttributeOverrides during the serialisation

  • void ArrangeExpectedOutputXml(string xmlValue) - to arrange the expected output XML from the given XML string

Input can be arranged from an embedded resource, XElement, object that is XML serialisable (for which you can also override the root XML namespace when serialised) or simply a string.

This same pattern is followed for arranging the expected output of the map, and for arranging the contents of flat files with the other map tester classes.

Assert - Compare the Actual Output Against the Expected Output

The MappingFromXmlToXmlTester class has the following method overrides for asserting the actual output from the map against the expected output that was arranged.

  • void AssertActualOutputXmlEqualsExpectedOutputXml()

  • AssertActualOutputXmlEqualsExpectedOutputXml(IXmlFilter xmlFilter) (see below)

All the comparison and logging and formatting of any differences is done for you. Easy!

Ignoring / Filtering XML Elements

The AssertActualOutputXmlEqualsExpectedOutputXml(IXmlFilter xmlFilter) override allows you to specify an IXmlFilter which allows you to specify a list of the local names of XML elements to ignore in comparisons, and/or a list of XPath expressions to ignore in comparisons.

Two constructor methods on XmlFilter allow you to easily specify these lists.

  • XmlFilter(IList<string> elementLocalNamesToIgnore)

  • XmlFilter(IList<string> elementLocalNamesToIgnore, IList<string> xpathsToIgnore)

Ignoring / Filtering Flat File Differences

Ignoring differences in flat files is a little more tricky, but not too difficult. The ChannelAdam library uses the DiffPlex library for performing text differences.

3 mechanisms on the flat file tester classes are provided for hooking into the DiffPaneModel difference result and manipulating it before a final decision is made as to whether or not the actual output is similar enough to the expected output.

  • An event that is raised with the differences
1    /// <summary>
2    /// Occurs when a text difference is detected, allowing a listener to filter the differences and
3    /// change the Line[x].Type to ChangeType.UnChanged so that the difference is no longer treated as a difference.
4    /// </summary>
5    /// <remarks>
6    /// This event or the TextDifferenceFilter property can be used for this purpose.
7    /// </remarks>
8    public event EventHandler<TextDifferenceDetectedEventArgs> TextDifferenceDetectedEvent;
  • An action delegate method that is called with the differences
1    /// <summary>
2    /// Gets or sets the Action delegate to be invoked when a text difference is detected, allowing differences to be filtered out by
3    /// changing the Line[x].Type to ChangeType.UnChanged - so that a difference is no longer treated as a difference.
4    /// </summary>
5    /// <value>The Action delegate.</value>
6    /// <remarks>
7    /// This property or TextDifferenceDetectedEvent can be used for this purpose.
8    /// </remarks>
9    public Action<DiffPaneModel> TextDifferenceFilter { get; set; }
  • A virtual method that allows you to inherit from the tester class and override the differences when they are detected.
1    protected virtual void OnTextDifferenceDetected(DiffPaneModel differences)

The ChannelAdam Test Framework Library for BizTalk

Overview

This is an open source .NET Framework 4.0, 4.5, 4.6 library providing functionality for the automated testing of BizTalk 2010/2013/2016 artifacts.

It provides you with an enhanced ability to use modern standard industry testing practices to test BizTalk Maps.

This library is compatible with the legacy, full .NET Framework 4.0 ChannelAdam Test Framework Libraries (version 1) only.

Please see the article Modern Testing of BizTalk Maps for background information on the issues with using Microsoft’s out-of-the-box functionality for testing Microsoft BizTalk Maps.

This library provides the following benefits and features:

  • Works with BizTalk 2010 / 2013 / 2016
  • Never again set the project property “Enable Unit Testing” to “True”!
  • BizTalk Map unit tests can be executed against a release build
  • The provided map tester classes allow you to easily unit test both XML and flat file BizTalk Maps
  • The design of tester classes follow the clean Arrange-Act-Assert (AAA) testing pattern
  • Support for arranging the input and expected output from a string, XElement, an XML serialisable object or loading from an embedded resource
  • Performs schema validation of both the input and output by default (easily overridable)
  • Support for mocking External Assembly class methods
  • Support for filtering assertions of the expected output to allow for dynamic data (such as timestamps or GUIDs)
  • Detailed information is provided allowing you to see the differences between the expected output and the actual output of the map.

The ChannelAdam Test Framework Libraries are fully compatible with MSTest (v1) and .NET Framework 4.0, 4.5, 4.6.

Note: I highly recommend using Behaviour-Driven Development practices and the usage of SpecFlow in conjunction with this library.

Getting Started

NuGet Package Installation

To install the ChannelAdam.TestFramework.BizTalk NuGet package run the following command in the Package Manager Console:

1PM> Install-Package ChannelAdam.TestFramework.BizTalk

Usage

The Basics

There are 4 map tester classes to be used depending on the type of input and output of your map:

  • BizTalkXmlToXmlMapTester - for testing a map that maps from XML to XML
  • BizTalkXmlToFlatFileMapTester - for testing a map that maps from XML to a flat file
  • BizTalkFlatFileToFlatFileMapTester - for testing a map that maps from a flat file to a flat file
  • BizTalkFlatFileToXmlMapTester - for testing a map that maps from a flat file to XML

Below is some sample MSTest test method code (in a very raw form) for testing a map that maps from XML to XML.

 1// ==================
 2// Variables
 3// ==================
 4
 5// Define some helpful variables for our testing artifacts
 6static Assembly thisAssembly = this.GetType().Assembly;
 7static string inputXmlTestDataEmbeddedResourceName          = thisAssembly.GetName().Name + ".InputXmlTestDataEmbeddedResource.xml"
 8static string expectedOutputXmlTestDataEmbeddedResourceName = thisAssembly.GetName().Name + ".ExpectedOutputXmlTestDataEmbeddedResource.xml"
 9
10// Create the MSTest compatible logging assertion class
11var logAssert = new ChannelAdam.TestFramework.MSTest.LogAssert();
12
13// Create the map tester - in this case for a map from XML to XML
14var xmlToXmlMapTester = new BizTalkXmlToXmlMapTester(logAssert);
15
16// ==================
17// Arrange
18// ==================
19
20// Arrange the input XML and expected output XML from embedded resources
21xmlToXmlMapTester.ArrangeInputXml(thisAssembly, inputXmlTestDataEmbeddedResourceName);
22xmlToXmlMapTester.ArrangeExpectedOutputXml(thisAssembly, expectedOutputXmlTestDataEmbeddedResourceName);
23
24// ==================
25// Act
26// ==================
27xmlToXmlMapTester.TestMap(new MySpiffyMapFromXmlToXml());
28
29// ==================
30// Assert
31// ==================
32xmlToXmlMapTester.AssertActualOutputXmlEqualsExpectedOutputXml();

Note: I highly recommend using the MoqTestFixture as a base class to your test class as it removes the need for you to be concerned with instantiating the LogAssert class and provdes a number of base methods to make testing easier. Please see the ChannelAdam Test Framework Libraries documentation for more information.

The test code above results in the following test output.

 110/10/16 10:00:00 PM - The input XML for the map is: 
 2<ns0:Request xmlns:ns0="http://SampleBizTalkMaps.Schemas.XmlRequest">
 3  <Key>Key_0</Key>
 4  <StringValue>StringValue_0</StringValue>
 5  <DateTimeValue>1999-05-31T13:20:00.000-05:00</DateTimeValue>
 6  <IntegerValue>100</IntegerValue>
 7  <DecimalValue>10.4</DecimalValue>
 8</ns0:Request>
 9
1010/10/16 10:00:00 PM - The expected output XML of the map is: 
11<ns0:Response xmlns:ns0="http://SampleBizTalkMaps.Schemas.XmlResponse">
12  <Key>Key_0</Key>
13  <StringValue>StringValue_0FAKE_GUID</StringValue>
14  <DateTimeValue>1999-05-31T13:20:00.000-05:00</DateTimeValue>
15  <IntegerValue>100</IntegerValue>
16  <DecimalValue>10.4</DecimalValue>
17</ns0:Response>
18
1910/10/16 10:00:00 PM - Validating the input XML for the BizTalk map
2010/10/16 10:00:00 PM - Executing the BizTalk map (XML to XML) MySpiffyMapFromXmlToXml
2110/10/16 10:00:00 PM - Asserting XML output from the BizTalk map exists is True
22
2310/10/16 10:00:00 PM - BizTalk map (XML to XML) execution completed
24
2510/10/16 10:00:00 PM - The actual output XML from the map is: 
26<ns0:Response xmlns:ns0="http://SampleBizTalkMaps.Schemas.XmlResponse">
27  <Key>Key_0</Key>
28  <StringValue>StringValue_0FAKE_GUID</StringValue>
29  <DateTimeValue>1999-05-31T13:20:00.000-05:00</DateTimeValue>
30  <IntegerValue>100</IntegerValue>
31  <DecimalValue>10.4</DecimalValue>
32</ns0:Response>
3310/10/16 10:00:00 PM - Validating the output XML from the BizTalk map
34
3510/10/16 10:00:00 PM - Asserting actual and expected XML are equal
3610/10/16 10:00:00 PM - Asserting The XML is as expected is True
3710/10/16 10:00:00 PM - The XML is as expected

And the test passed successfully!

Advanced Usage

More advanced functionality can be performed with the overloads on the methods used in the above test code.

Arrange - Input and Expected Output

The BizTalkXmlToXmlMapTester has the following method overrides for arranging the input XML.

  • void ArrangeInputXml(Assembly assembly, string resourceName) - to arrange the input XML from an embedded resource in the given assembly
  • void ArrangeInputXml(XElement xmlElement) - to arrange the input XML from a given XElement
  • void ArrangeInputXml(object valueToSerialise) - to arrange the input XML by serialising the given object
  • void ArrangeInputXml(object valueToSerialise, XmlRootAttribute xmlRootAttribute) - to arrange the input XML by serialising the given object and applying the given XmlRootAttribute during the serialisation
  • void ArrangeInputXml(object valueToSerialise, XmlAttributeOverrides xmlAttributeOverrides) - to arrange the input XML by serialising the given object and applying the given XmlAttributeOverrides during the serialisation
  • void ArrangeInputXml(string xmlValue) - to arrange the input XML from the given XML string

Input can be arranged from an embedded resource, XElement, object that is XML serialisable (for which you can also override the root XML namespace when serialised) or simply a string.

This same pattern is followed for arranging the expected output of the map, and for arranging the contents of flat files with the other map tester classes.

Act - Test the Map

The BizTalkXmlToXmlMapTester has the following method overrides for testing your BizTalk Map.

  • void TestMap(TransformBase map)
  • void TestMap(TransformBase map, IEnumerable<XsltExtensionObjectDescriptor> xsltExtensionObjectOverrides) (see below - Mocking External Assemblies)
  • void TestMap(TransformBase map, bool validateInputXml, bool validateOutputXml)
  • void TestMap(TransformBase map, IEnumerable<XsltExtensionObjectDescriptor> xsltExtensionObjectOverrides, bool validateInput, bool validateOutputXml)

The validateInputXml and validateOutputXml parameters are self-explanatory.

Mocking External Assemblies

The xsltExtensionObjectOverrides enumerable is the secret to mocking External Assembly class methods. To mock a class, create an Interface that define the method used in the map, and then create and set up a mock object from the Interface. Then use the following code to create the xsltExtensionObjectOverrides, populated with the mock object.

1// Mock the external assembly 'MyExternalAssemblyClassUsedInTheMap' class used in the map ;)
2var mocksOfExternalAssemblyClassesUsedInMap = new List<XsltExtensionObjectDescriptor>
3{
4    new XsltExtensionObjectDescriptor(typeof(MyExternalAssemblyClassUsedInTheMap), myMoqMockForThatClass.Object)
5};  

And test the map, providing the xsltExtensionObjectOverrides parameter.

1// Test the map which will execute methods on the mock objects
2xmlToXmlMapTester.TestMap(new MySpiffyMapFromXmlToXml(), mocksOfExternalAssemblyClassesUsedInMap);

For Other examples, including how to mock an External Assembly, please refer to this example BDD feature file and the implementation of the scenario steps.

Assert - Compare the Actual Output Against the Expected Output

The BizTalkXmlToXmlMapTester has the following method overrides for asserting the actual output from the map against the expected output that was arranged.

  • void AssertActualOutputXmlEqualsExpectedOutputXml()
  • AssertActualOutputXmlEqualsExpectedOutputXml(IXmlFilter xmlFilter)

All the comparison and logging and formatting of any differences is done for you. Easy!

Ignoring / Filtering XML Elements

The override allows you to specify an IXmlFilter which allows you to specify a list of the local names of XML elements to ignore in comparisons, and/or a list of XPath expressions to ignore in comparisons.

Two constructor methods on XmlFilter allow you to easily specify these lists.

  • XmlFilter(IList<string> elementLocalNamesToIgnore)
  • XmlFilter(IList<string> elementLocalNamesToIgnore, IList<string> xpathsToIgnore)

Ignoring / Filtering Flat File Differences

Ignoring differences in flat files is a little more tricky, but not too difficult. The ChannelAdam library uses the DiffPlex library for performing text differences.

3 mechanisms on the flat file tester classes are provided for hooking into the DiffPaneModel difference result and manipulating it before a final decision is made as to whether or not the actual output is similar enough to the expected output.

  • An event that is raised with the differences
1    /// <summary>
2    /// Occurs when a text difference is detected, allowing a listener to filter the differences and
3    /// change the Line[x].Type to ChangeType.UnChanged so that the difference is no longer treated as a difference.
4    /// </summary>
5    /// <remarks>
6    /// This event or the TextDifferenceFilter property can be used for this purpose.
7    /// </remarks>
8    public event EventHandler<TextDifferenceDetectedEventArgs> TextDifferenceDetectedEvent;
  • An action delegate method that is called with the differences
1    /// <summary>
2    /// Gets or sets the Action delegate to be invoked when a text difference is detected, allowing differences to be filtered out by
3    /// changing the Line[x].Type to ChangeType.UnChanged - so that a difference is no longer treated as a difference.
4    /// </summary>
5    /// <value>The Action delegate.</value>
6    /// <remarks>
7    /// This property or TextDifferenceDetectedEvent can be used for this purpose.
8    /// </remarks>
9    public Action<DiffPaneModel> TextDifferenceFilter { get; set; }
  • A virtual method that allows you to inherit from the tester class and override the differences when they are detected.
1    protected virtual void OnTextDifferenceDetected(DiffPaneModel differences)

Background

ChannelAdam WCF Library — Version 1 Documentation

Background

The Issues

The issues faced by .NET developers are explained in depth in the article How To Call WCF Services Properly, where I researched and highlighted the typical problems with using WCF clients (such as memory and connection leaks), and concluded that the most correct solution for consuming WCF services would:

  • Perform the Close/Abort pattern without a race condition;
  • Handle the situation when the service operation throws exceptions;
  • Handle the situations when both the Close and Abort methods throw exceptions; and
  • Handle asynchronous exceptions such as the ThreadAbortException.

Below is the verbose sample code that is required for correctly using a .NET WCF client, performing exception handling and correctly performing the Close/Abort pattern. Unfortunately, due to the way Microsoft designed WCF, if you want to use a WCF service client cleanly without connection or memory leaks, this is the type of extensive code that must be written for every service operation call.

  1SampleServiceClient client = null;
  2
  3try
  4{
  5    client = new SampleServiceClient();
  6
  7    var response = client.SampleOperation(1234);
  8
  9    // Do some business logic
 10}
 11catch (FaultException<MyCustomException>)
 12{
 13    // Do some business logic for this SOAP Fault Exception
 14}
 15catch (FaultException)
 16{
 17    // Do some business logic for this SOAP Fault Exception
 18}
 19catch (CommunicationException)
 20{
 21    // Catch this expected exception so it is not propagated further.
 22    // Perhaps write this exception out to log file for gathering statistics...
 23}
 24catch (TimeoutException)
 25{
 26    // Catch this expected exception so it is not propagated further.
 27    // Perhaps write this exception out to log file for gathering statistics...
 28}
 29catch (Exception)
 30{
 31    // An unexpected exception that we don't know how to handle.
 32    // Perhaps write this exception out to log file for support purposes...
 33    throw;
 34}
 35finally
 36{
 37    // This will:
 38    // - be executed if any exception was thrown above in the 'try' (including ThreadAbortException); and
 39    // - ensure that CloseOrAbortServiceChannel() itself will not be interrupted by a ThreadAbortException
 40    //   (since it is executing from within a 'finally' block)
 41    CloseOrAbortServiceChannel(client);
 42
 43    // Unreference the client
 44    client = null;
 45}
 46
 47
 48private void CloseOrAbortServiceChannel(ICommunicationObject communicationObject)
 49{
 50    bool isClosed = false;
 51
 52    if (communicationObject == null || communicationObject.State == CommunicationState.Closed)
 53    {
 54        return;
 55    }
 56
 57    try 
 58    {
 59        if (communicationObject.State != CommunicationState.Faulted)
 60        {
 61            communicationObject.Close();
 62            isClosed = true;
 63        }
 64    }
 65    catch (CommunicationException)
 66    {
 67        // Catch this expected exception so it is not propagated further.
 68        // Perhaps write this exception out to log file for gathering statistics...
 69    }
 70    catch (TimeoutException)
 71    {
 72        // Catch this expected exception so it is not propagated further.
 73        // Perhaps write this exception out to log file for gathering statistics...
 74    }
 75    catch (Exception)
 76    {
 77        // An unexpected exception that we don't know how to handle.
 78        // Perhaps write this exception out to log file for support purposes...
 79        throw;
 80    }
 81    finally
 82    {
 83        // If State was Faulted or any exception occurred while doing the Close(), then do an Abort()
 84        if (!isClosed)
 85        {
 86            AbortServiceChannel(communicationObject);
 87        }
 88    }
 89}
 90
 91
 92private static void AbortServiceChannel(ICommunicationObject communicationObject)
 93{
 94    try
 95    {
 96        communicationObject.Abort();
 97    }
 98    catch (Exception)
 99    {
100        // An unexpected exception that we don't know how to handle.
101        // If we are in this situation:
102        // - we should NOT retry the Abort() because it has already failed and there is nothing to suggest it could be successful next time
103        // - the abort may have partially succeeded
104        // - the actual service call may have been successful
105        //
106        // The only thing we can do is hope that the channel's resources have been released.
107        // Do not rethrow this exception because the actual service operation call might have succeeded
108        // and an exception closing the channel should not stop the client doing whatever it does next.
109        //
110        // Perhaps write this exception out to log file for gathering statistics and support purposes...
111    }
112}

ChannelAdam WCF Library Makes It Easy

The ChannelAdam WCF Library is an API for working with WCF clients, that significantly reduces the amount of that code that developers have to write. It allows you to forget about the complex subtleties of WCF clients, channels and communication states, and lets you just use your service client in a way that is very similar to how you probably are doing it now - but without the leaks!

When you use the ChannelAdam WCF Library’s ServiceConsumer, you know that you can keep calling service operations through the ServiceConsumer and it will ‘just work’ and not leak connections or memory - no matter what happens!

The ChannelAdam WCF Library’s ServiceConsumer hides the inner complexity of service channels. For example, if a service channel / communication object / service client moves into a Fault state, the ServiceConsumer will correctly perform the Close/Abort pattern and dispose of the channel. The next time a request is made through the ServiceConsumer, a new channel is created under the covers to seamlessly process the request.

Background

ChannelAdam WCF Library — Version 2 Documentation

Background

The Issues

The issues faced by .NET developers are explained in depth in the article How To Call WCF Services Properly, where I researched and highlighted the typical problems with using WCF clients (such as memory and connection leaks), and concluded that the most correct solution for consuming WCF services would:

  • Perform the Close/Abort pattern without a race condition;
  • Handle the situation when the service operation throws exceptions;
  • Handle the situations when both the Close and Abort methods throw exceptions; and
  • Handle asynchronous exceptions such as the ThreadAbortException.

Below is the verbose sample code that is required for correctly using a .NET WCF client, performing exception handling and correctly performing the Close/Abort pattern. Unfortunately, due to the way Microsoft designed WCF, if you want to use a WCF service client cleanly without connection or memory leaks, this is the type of extensive code that must be written for every service operation call.

  1SampleServiceClient client = null;
  2
  3try
  4{
  5    client = new SampleServiceClient();
  6
  7    var response = client.SampleOperation(1234);
  8
  9    // Do some business logic
 10}
 11catch (FaultException<MyCustomException>)
 12{
 13    // Do some business logic for this SOAP Fault Exception
 14}
 15catch (FaultException)
 16{
 17    // Do some business logic for this SOAP Fault Exception
 18}
 19catch (CommunicationException)
 20{
 21    // Catch this expected exception so it is not propagated further.
 22    // Perhaps write this exception out to log file for gathering statistics...
 23}
 24catch (TimeoutException)
 25{
 26    // Catch this expected exception so it is not propagated further.
 27    // Perhaps write this exception out to log file for gathering statistics...
 28}
 29catch (Exception)
 30{
 31    // An unexpected exception that we don't know how to handle.
 32    // Perhaps write this exception out to log file for support purposes...
 33    throw;
 34}
 35finally
 36{
 37    // This will:
 38    // - be executed if any exception was thrown above in the 'try' (including ThreadAbortException); and
 39    // - ensure that CloseOrAbortServiceChannel() itself will not be interrupted by a ThreadAbortException
 40    //   (since it is executing from within a 'finally' block)
 41    CloseOrAbortServiceChannel(client);
 42
 43    // Unreference the client
 44    client = null;
 45}
 46
 47
 48private void CloseOrAbortServiceChannel(ICommunicationObject communicationObject)
 49{
 50    bool isClosed = false;
 51
 52    if (communicationObject == null || communicationObject.State == CommunicationState.Closed)
 53    {
 54        return;
 55    }
 56
 57    try 
 58    {
 59        if (communicationObject.State != CommunicationState.Faulted)
 60        {
 61            communicationObject.Close();
 62            isClosed = true;
 63        }
 64    }
 65    catch (CommunicationException)
 66    {
 67        // Catch this expected exception so it is not propagated further.
 68        // Perhaps write this exception out to log file for gathering statistics...
 69    }
 70    catch (TimeoutException)
 71    {
 72        // Catch this expected exception so it is not propagated further.
 73        // Perhaps write this exception out to log file for gathering statistics...
 74    }
 75    catch (Exception)
 76    {
 77        // An unexpected exception that we don't know how to handle.
 78        // Perhaps write this exception out to log file for support purposes...
 79        throw;
 80    }
 81    finally
 82    {
 83        // If State was Faulted or any exception occurred while doing the Close(), then do an Abort()
 84        if (!isClosed)
 85        {
 86            AbortServiceChannel(communicationObject);
 87        }
 88    }
 89}
 90
 91
 92private static void AbortServiceChannel(ICommunicationObject communicationObject)
 93{
 94    try
 95    {
 96        communicationObject.Abort();
 97    }
 98    catch (Exception)
 99    {
100        // An unexpected exception that we don't know how to handle.
101        // If we are in this situation:
102        // - we should NOT retry the Abort() because it has already failed and there is nothing to suggest it could be successful next time
103        // - the abort may have partially succeeded
104        // - the actual service call may have been successful
105        //
106        // The only thing we can do is hope that the channel's resources have been released.
107        // Do not rethrow this exception because the actual service operation call might have succeeded
108        // and an exception closing the channel should not stop the client doing whatever it does next.
109        //
110        // Perhaps write this exception out to log file for gathering statistics and support purposes...
111    }
112}

ChannelAdam WCF Library Makes It Easy

The ChannelAdam WCF Library is an API for working with WCF clients, that significantly reduces the amount of that code that developers have to write. It allows you to forget about the complex subtleties of WCF clients, channels and communication states, and lets you just use your service client in a way that is very similar to how you probably are doing it now - but without the leaks!

When you use the ChannelAdam WCF Library’s ServiceConsumer, you know that you can keep calling service operations through the ServiceConsumer and it will ‘just work’ and not leak connections or memory - no matter what happens!

The ChannelAdam WCF Library’s ServiceConsumer hides the inner complexity of service channels. For example, if a service channel / communication object / service client moves into a Fault state, the ServiceConsumer will correctly perform the Close/Abort pattern and dispose of the channel. The next time a request is made through the ServiceConsumer, a new channel is created under the covers to seamlessly process the request.

Basic Usage

ChannelAdam WCF Library — Version 1 Documentation

Basic Usage

Create a ServiceConsumer with the ServiceConsumerFactory

The first thing you need to understand is that you consume your service operations through the usage of the ChannelAdam.ServiceModel.ServiceConsumer class.

The second thing you need to understand is that the best way to create the ServiceConsumer is by using the Create method on the ChannelAdam.ServiceModel.ServiceConsumerFactory.

In order to use the ServiceConsumerFactory.Create method, simply pass in as a parameter a factory method for creating the service client.

For example:

1IServiceConsumer<IFakeService> service = ServiceConsumerFactory.Create<IFakeService>(() => new FakeServiceClient()));

The ServiceConsumerFactory.Create method returns a IServiceConsumer<TServiceInterface> type. In all further examples, I will use the var keyword instead - such as:

1var service = ServiceConsumerFactory.Create<IFakeService>(() => new FakeServiceClient()));

Alternatively, you can pass in as a string parameter the endpoint configuration name from the service client section of your application’s .config file. For example:

1var service = ServiceConsumerFactory.Create<IFakeService>("BasicHttpBinding_IFakeService")); 

Four Ways to Clean Up the ServiceConsumer

There are four ways in which the ServiceConsumer can be cleaned up:

  1. Implicitly via the Using statement;
  2. Explicitly with the Close method;
  3. Explicitly with the Dispose method; or
  4. Automatically by the Garbage Collector.

1. The Using Statement

The recommended and easiest way to use the ServiceConsumer is with the using statement - because it is a well behaved implementation of IDisposable (unlike Microsoft’s service clients!). The service channel will automatically be correctly disposed and closed or aborted, no matter what happens.

1using (var service = ServiceConsumerFactory.Create<IFakeService>(() => new FakeServiceClient())))
2{
3    ...
4}

2. The Close Method

The ServiceConsumer and underlying channel can be closed immediately by calling the Close method on the ServiceConsumer instance.

1service.Close();

If you want to, you still can immediately call another service operation via that same ServiceConsumer instance, as under the covers, a new service channel will be created seamlessly and automatically.

3. The Dispose Method

The ServiceConsumer and underlying channel can be disposed of immediately by calling the Dispose method on the ServiceConsumer instance.

1service.Dispose();

As per standard IDisposable concepts, you should not attempt to reuse the ServiceConsumer after disposing it.

4. Garbage Collection

If you happen to accidently forget to wrap your ServiceConsumer instance in a Using statement or call the Close method, rest assured that the ServiceConsumer will still be cleaned up! When the ServiceConsumer instance goes out of scope, the .NET Garbage Collector will [eventually] kick in and call its destructor/finalise method, thus correctly cleaning up the service channel and preventing any leaks.

Since there is an indeterminate amount of time before the Garbage Collector kicks in, I would recommend the Using statement approach, as it minimises the number of open channels and other resources currently in use, over the lifetime of your application.

How To Call a Service Operation

There are two ways in which to call a service operation via the ServiceConsumer:

  1. Via the Operations property; or
  2. Via the Consume method.

The Operations Property

The Operations property exposes the interface of your service. It can be used just like the normal service client that you already would be familiar with.

Below is an example of how to call the AddIntegers operation via the Operations property.

1using (var service = ServiceConsumerFactory.Create<IFakeService>(() => new FakeServiceClient())))
2{
3    try
4    {
5        int actualValue = service.Operations.AddIntegers(1, 1); 
6        ...
7    }
8    ...
9}

And below is an example of how to do some exception handling with it.

 1using (var service = ServiceConsumerFactory.Create<IFakeService>(() => new FakeServiceClient())))
 2{
 3    try
 4    {
 5        int actualValue = service.Operations.AddIntegers(1, 1); 
 6        ...
 7    }
 8    catch (FaultException<MyCustomException> fe)
 9    {
10        Console.WriteLine("Service operation threw a custom fault: " + fe.ToString());
11    }
12    catch (FaultException fe)
13    {
14        Console.WriteLine("Service operation threw a fault: " + fe.ToString());
15    }
16    catch (Exception ex)
17    {
18        Console.WriteLine("An unexpected error occurred while calling the service operation: " + ex.ToString());
19    }
20}

There is no need to catch a CommunicationException or TimeOutException and try to perform the Close/Abort pattern, as the underlying channel is dealt with automatically for you by the ServiceConsumer.

The Consume Method

As an alternative to the Operations property, you can call a service operation via the Consume method on the ServiceConsumer instance.

The Consume method returns an instance of IOperationResult<TReturnTypeOfYourOperation> which wraps the result of the operation. This interface has the following helpful properties:

  • TReturnTypeOfYourOperation Value - which gives you access to the return value from the operation;
  • bool HasException - whether there was an exception thrown by the service operation;
  • bool HasNoException - whether there was not an exception thrown by the service operation;
  • bool HasFaultException - whether an exception of type FaultException was thrown by the service operation;
  • bool HasFaultException<OfThisType> - whether an exception of FaultException<OfThisType> was thrown by the service operation; and
  • Exception Exception - the exception that was thrown by the service operation.

Below is an example of how to call the AddIntegers operation via the Consume method.

 1using (var service = ServiceConsumerFactory.Create<IFakeService>(() => new FakeServiceClient())))
 2{
 3	IOperationResult<int> result = service.Consume(operation => operation.AddIntegers(1, 1));
 4
 5    if (result.HasNoException)
 6    {
 7        Console.WriteLine("Result was: " + result.Value);
 8        ...
 9    }
10    ...
11}

And below is an example of how to do some exception handling with IOperationResult.

 1using (var service = ServiceConsumerFactory.Create<IFakeService>(() => new FakeServiceClient())))
 2{
 3    var result = service.Consume(operation => operation.AddIntegers(1, 1));
 4
 5    if (result.HasNoException)
 6    {
 7        Console.WriteLine("Result was: " + result.Value);
 8    }
 9    else
10    {
11        if (result.HasFaultExceptionOfType<MyCustomException>())
12        {
13            Console.WriteLine("Service operation threw a custom fault: " + result.Exception.ToString());
14        }
15        else if (result.HasFaultException)
16        {
17            Console.WriteLine("Service operation threw a fault: " + result.Exception.ToString());
18        }
19        else
20        {
21            Console.WriteLine("An unexpected error occurred while calling the service operation: " + result.Exception.ToString());
22        }
23    }
24}

Basic Usage

ChannelAdam WCF Library — Version 2 Documentation

Basic Usage

Create a ServiceConsumer with the ServiceConsumerFactory

The first thing you need to understand is that you consume your service operations through the usage of the ChannelAdam.ServiceModel.ServiceConsumer class.

The second thing you need to understand is that the best way to create the ServiceConsumer is by using the Create method on the ChannelAdam.ServiceModel.ServiceConsumerFactory.

In order to use the ServiceConsumerFactory.Create method, simply pass in as a parameter a factory method for creating the service client.

For example:

1IServiceConsumer<IFakeService> service = ServiceConsumerFactory.Create<IFakeService>(() => new FakeServiceClient()));

The ServiceConsumerFactory.Create method returns a IServiceConsumer<TServiceInterface> type. In all further examples, I will use the var keyword instead - such as:

1var service = ServiceConsumerFactory.Create<IFakeService>(() => new FakeServiceClient()));

Alternatively, you can pass in as a string parameter the endpoint configuration name from the service client section of your application’s .config file. For example:

1var service = ServiceConsumerFactory.Create<IFakeService>("BasicHttpBinding_IFakeService")); 

Four Ways to Clean Up the ServiceConsumer

There are four ways in which the ServiceConsumer can be cleaned up:

  1. Implicitly via the Using statement;
  2. Explicitly with the Close method;
  3. Explicitly with the Dispose method; or
  4. Automatically by the Garbage Collector.

1. The Using Statement

The recommended and easiest way to use the ServiceConsumer is with the using statement - because it is a well behaved implementation of IDisposable (unlike Microsoft’s service clients!). The service channel will automatically be correctly disposed and closed or aborted, no matter what happens.

1using (var service = ServiceConsumerFactory.Create<IFakeService>(() => new FakeServiceClient())))
2{
3    ...
4}

2. The Close Method

The ServiceConsumer and underlying channel can be closed immediately by calling the Close method on the ServiceConsumer instance.

1service.Close();

If you want to, you still can immediately call another service operation via that same ServiceConsumer instance, as under the covers, a new service channel will be created seamlessly and automatically.

3. The Dispose Method

The ServiceConsumer and underlying channel can be disposed of immediately by calling the Dispose method on the ServiceConsumer instance.

1service.Dispose();

As per standard IDisposable concepts, you should not attempt to reuse the ServiceConsumer after disposing it.

4. Garbage Collection

If you happen to accidently forget to wrap your ServiceConsumer instance in a Using statement or call the Close method, rest assured that the ServiceConsumer will still be cleaned up! When the ServiceConsumer instance goes out of scope, the .NET Garbage Collector will [eventually] kick in and call its destructor/finalise method, thus correctly cleaning up the service channel and preventing any leaks.

Since there is an indeterminate amount of time before the Garbage Collector kicks in, I would recommend the Using statement approach, as it minimises the number of open channels and other resources currently in use, over the lifetime of your application.

How To Call a Service Operation

There are two ways in which to call a service operation via the ServiceConsumer:

  1. Via the Operations property; or
  2. Via the Consume method.

The Operations Property

The Operations property exposes the interface of your service. It can be used just like the normal service client that you already would be familiar with.

Below is an example of how to call the AddIntegers operation via the Operations property.

1using (var service = ServiceConsumerFactory.Create<IFakeService>(() => new FakeServiceClient())))
2{
3    try
4    {
5        int actualValue = service.Operations.AddIntegers(1, 1); 
6        ...
7    }
8    ...
9}

And below is an example of how to do some exception handling with it.

 1using (var service = ServiceConsumerFactory.Create<IFakeService>(() => new FakeServiceClient())))
 2{
 3    try
 4    {
 5        int actualValue = service.Operations.AddIntegers(1, 1); 
 6        ...
 7    }
 8    catch (FaultException<MyCustomException> fe)
 9    {
10        Console.WriteLine("Service operation threw a custom fault: " + fe.ToString());
11    }
12    catch (FaultException fe)
13    {
14        Console.WriteLine("Service operation threw a fault: " + fe.ToString());
15    }
16    catch (Exception ex)
17    {
18        Console.WriteLine("An unexpected error occurred while calling the service operation: " + ex.ToString());
19    }
20}

There is no need to catch a CommunicationException or TimeOutException and try to perform the Close/Abort pattern, as the underlying channel is dealt with automatically for you by the ServiceConsumer.

The Consume Method

As an alternative to the Operations property, you can call a service operation via the Consume method on the ServiceConsumer instance.

The Consume method returns an instance of IOperationResult<TReturnTypeOfYourOperation> which wraps the result of the operation. This interface has the following helpful properties:

  • TReturnTypeOfYourOperation Value - which gives you access to the return value from the operation;
  • bool HasException - whether there was an exception thrown by the service operation;
  • bool HasNoException - whether there was not an exception thrown by the service operation;
  • bool HasFaultException - whether an exception of type FaultException was thrown by the service operation;
  • bool HasFaultException<OfThisType> - whether an exception of FaultException<OfThisType> was thrown by the service operation; and
  • Exception Exception - the exception that was thrown by the service operation.

Below is an example of how to call the AddIntegers operation via the Consume method.

 1using (var service = ServiceConsumerFactory.Create<IFakeService>(() => new FakeServiceClient())))
 2{
 3	IOperationResult<int> result = service.Consume(operation => operation.AddIntegers(1, 1));
 4
 5    if (result.HasNoException)
 6    {
 7        Console.WriteLine("Result was: " + result.Value);
 8        ...
 9    }
10    ...
11}

And below is an example of how to do some exception handling with IOperationResult.

 1using (var service = ServiceConsumerFactory.Create<IFakeService>(() => new FakeServiceClient())))
 2{
 3    var result = service.Consume(operation => operation.AddIntegers(1, 1));
 4
 5    if (result.HasNoException)
 6    {
 7        Console.WriteLine("Result was: " + result.Value);
 8    }
 9    else
10    {
11        if (result.HasFaultExceptionOfType<MyCustomException>())
12        {
13            Console.WriteLine("Service operation threw a custom fault: " + result.Exception.ToString());
14        }
15        else if (result.HasFaultException)
16        {
17            Console.WriteLine("Service operation threw a fault: " + result.Exception.ToString());
18        }
19        else
20        {
21            Console.WriteLine("An unexpected error occurred while calling the service operation: " + result.Exception.ToString());
22        }
23    }
24}

If you generated async Task methods with your service client, and if you call those async Task methods with Consume(), the task will be executed immediately/synchronously.

The ConsumeAsync Method

Use ConsumeAsync() to asynchronously execute async Task methods on your service client. The syntax is the same as the Consume() example above.

Sample Usage Code

For more sample code, please see SampleUsage.cs.

Channel Close Trigger Strategies

ChannelAdam WCF Library — Version 1 Documentation

Channel Close Trigger Strategies

Overview

When an exception occurs while calling the service operation, the ServiceConsumer follows a specific strategy to determine whether the exception and/or current channel state is severe enough to necessitate and trigger the closing/aborting of the service channel.

These “Service Channel Close Trigger Strategies” are an implementation of the interface IServiceChannelCloseTriggerStrategy. There is one method in this interface:

1bool ShouldCloseChannel(ICommunicationObject channel, Exception exception);

If the method ShouldCloseChannel returns true, then the ServiceConsumer will perform the Close/Abort pattern.

The Out-Of-The-Box Strategy

Out of the box, the DefaultServiceChannelCloseTriggerStrategy is used. This strategy is very simple, effective and safe. It triggers a close to occur if the exception is anything but a FaultException, regardless of the state of the service channel. It is highly probable that the channel is no longer usable in the case where there was any other type of exception.

Use Your Own

If you find that this strategy does not suit your need, you can create your own class that implements that interface and specify your class as one of the overloads of the Create method on the ServiceConsumerFactory. Alternatively, you can directly set the property ChannelCloseTriggerStrategy on the ServiceConsumer instance itself.

Channel Close Trigger Strategies

ChannelAdam WCF Library — Version 2 Documentation

Channel Close Trigger Strategies

Overview

When an exception occurs while calling the service operation, the ServiceConsumer follows a specific strategy to determine whether the exception and/or current channel state is severe enough to necessitate and trigger the closing/aborting of the service channel.

These “Service Channel Close Trigger Strategies” are an implementation of the interface IServiceChannelCloseTriggerStrategy. There is one method in this interface:

1bool ShouldCloseChannel(ICommunicationObject channel, Exception exception);

If the method ShouldCloseChannel returns true, then the ServiceConsumer will perform the Close/Abort pattern.

The Out-Of-The-Box Strategy

Out of the box, the DefaultServiceChannelCloseTriggerStrategy is used. This strategy is very simple, effective and safe. It triggers a close to occur if the exception is anything but a FaultException, regardless of the state of the service channel. It is highly probable that the channel is no longer usable in the case where there was any other type of exception.

Use Your Own

If you find that this strategy does not suit your need, you can create your own class that implements that interface and specify your class as one of the overloads of the Create method on the ServiceConsumerFactory. Alternatively, you can directly set the property ChannelCloseTriggerStrategy on the ServiceConsumer instance itself.