Creating a Custom Resolver with a Unity Container

You can create a custom resolver using the Unity Application Block (Unity) (https://go.microsoft.com/fwlink/?LinkId=188286) for run-time dependency injection of resolution logic and metadata sources.

Fact Providers

Fact providers are instances of classes that implement the IFactProvider interface. This interface exposes three different overloads of a method named RegisterFact. This method takes in the message, the resolver configuration, and, in some cases, the pipeline context, and returns an object. This object may be information extracted from the inputs in some way, it may be a calculation of some form, or it may be looked up from some external source. Each object returned by a fact provider can be referred to as a fact and is typically added to a list by the resolve container for later use by a fact translator.

A Unity resolver may have zero or more fact providers that can be added or removed at any time with a single configuration change.

The following code is an example of the logic contained in a fact provider. This code can also be found in the ItineraryStaticFactProvider.cs file of the ESB.Resolver.Itinerary.Facts project. It is a component in the ITINERARY-STATIC resolver that gathers the name and version of an itinerary from the resolver connection string.

public object RegisterFact(IBaseMessage message, IPipelineContext pipelineContext, Dictionary\<string, string\> resolverContents)
{
    return GetItineraryFactFromResolver(resolverContents);
}

private static object GetItineraryFactFromResolver(Dictionary\<string, string\> resolverContents)
{
    if (resolverContents == null)
        throw new ArgumentNullException("resolverContents");

    ItineraryFact itineraryFact = new ItineraryFact();
    itineraryFact.Name = ResolverMgr.GetConfigValue(resolverContents, true, "name");
    string version = ResolverMgr.GetConfigValue(resolverContents, false, "version");
    if (!string.IsNullOrEmpty(version))
    {
        EventLogger.Write(string.Format("Version set with value {0}.", version));
        itineraryFact.SetVersion(version);
    }
    return itineraryFact;
}

Fact Translators

Fact translators are instances of classes that implement the IFactTranslator interface. This interface exposes a single method named TranslateFact. This method takes in an object array that contains a list of facts and the resolver dictionary that will later be returned by the resolver using the fact translator. A fact translator is responsible for processing the facts provided by the fact providers in a meaningful way and then populating the resolver dictionary.

A Unity resolver may have zero or more fact translators that can be added or removed at any time with a single configuration change.

The following code is an example of the logic contained in a fact translator. This code can also be found in the ItineraryStaticFactTranslator.cs file of the ESB.Resolver.Itinerary.Facts project. It is a component in the ITINERARY-STATIC resolver that performs a database query to gather the itinerary XML for an itinerary by name and, optionally, by version.

public void TranslateFact(object[] facts, Dictionary\<string, string\> factDictionary)
{
    #region Argument Check
    if (null == facts) throw new ArgumentNullException("fact");
    if (null == factDictionary) throw new ArgumentNullException("factDictionary");
    #endregion

    #region Local Variables
    Version version = new Version(1, 0);
    #endregion
    try
    {
        foreach (object fact in facts)
        {
            if (fact is ItineraryFact)
            {
                ItineraryFact targetFact = (ItineraryFact)fact;
                factDictionary.Add(_FactKeyItineraryName, targetFact.Name ?? string.Empty);
                if (null != targetFact.Version)
                {
                    factDictionary.Add(_FactKeyItineraryVersion, targetFact.Version.ToString(2) ?? string.Empty);
                    version = targetFact.Version;
                }

                string xml = null;

                if (targetFact.Version != null)
                {
                    xml = _repositoryProvider.GetItinerary(targetFact.Name, version.Major, version.Minor);
                }
                else
                {
                    xml = _repositoryProvider.GetItinerary(targetFact.Name);
                }
                if (!string.IsNullOrEmpty(xml))
                {
                    factDictionary.Add(_FactKeyItinerary, xml);
                }

                break;
            }
        }
    }
    #region Exception Handling
    catch (System.Exception)
    {
        throw;
    }
    #endregion
}
#endregion

Resolve Containers

A resolve container is a class that implements the IResolveContainer interface. Typically, it also implements the IResolveProvider interface. The IResolveContainer interface exposes a single method named Initialize that takes in an IUnityContainer. The container passed to this method will contain all of the dependencies (that is, instances of the classes IFactProvider and IFactTranslator, and any other types required) necessary for the resolver to complete its processing.

The following code is an example of an implementation of the IResolveContainer interface. This code can also be found in the StaticItineraryResolveContainer.cs file of the ESB.Resolver.Itinerary project.

#region IResolveContainer Members

private static IUnityContainer Container;

// <summary>
// This is used by the Unity resolver to initialize the current
// object with the Unity container.
// </summary>
// <param name="container">Unity container used for dependency
// injection.</param>
// <remarks>
// The container used is the ITINERARY container, although this is
// configurable in Esb.config.
// </remarks>

public void Initialize(IUnityContainer container)
{
  if (container == null)
    throw new ArgumentNullException("container");

  Container = container;
}
#endregion

In a resolve container, in the implementations of the Resolve methods from the IResolveProvider interface, it is necessary to iterate through all the fact providers and fact translators in the Unity container to allow each of them to perform their processing.

The following code is an example of the logic contained in a Resolve container. This code can also be found in the StaticItineraryResolveContainer.cs file of the ESB.Resolver.Itinerary project.

public Dictionary\<string, string\> Resolve(ResolverInfo resolverInfo,
    XLANGMessage message)
{
    #region Argument Check
    if (String.IsNullOrEmpty(resolverInfo.Config))
        throw new ArgumentNullException("resolverInfo.Config");
    if (String.IsNullOrEmpty(resolverInfo.Resolver))
        throw new ArgumentNullException("resolverInfo.Resolver");
    if (null == message)
        throw new ArgumentNullException("message");
    #endregion Argument Check

    return ResolveStatic(resolverInfo.Config, resolverInfo.Resolver,
        (factProvider, resolverContent) =>
            factProvider.RegisterFact(message, resolverContent)
     );
}

private Dictionary\<string, string\> ResolveStatic(string config, string resolver,
    Func<IFactProvider, Dictionary\<string, string\>, object> RegisterFact)
{
    try
    {
        EventLogger.Write(string.Format("Received {0} value in ITINERARY STATIC resolver.", config));

        Dictionary\<string, string\> queryParams =
                ResolverMgr.GetFacts(config, resolver);

        List<object> facts = new List<object>();

        foreach (IFactProvider factProvider in Container.ResolveAll<IFactProvider>())
        {
            facts.Add(RegisterFact(factProvider, queryParams));
        }

        Dictionary\<string, string\> resolverDictionary = new Dictionary\<string, string\>();

        object[] convertedFacts = facts.ToArray();

        foreach (IFactTranslator translator in Container.ResolveAll<IFactTranslator>())
        {
            translator.TranslateFact(convertedFacts, resolverDictionary);
        }

        return resolverDictionary;
    }
    catch (System.Exception ex)
    {
        EventLogger.Write(MethodInfo.GetCurrentMethod(), ex);
        throw;
    }
}

Configuring a Custom Unity Resolver

To configure a custom Unity resolver, the same configuration steps apply as when creating a custom resolver; however, there is some additional configuration that must be included to properly register the components that make up the resolver.

First, in the Esb.config file, under the resolver declaration, a resolverConfig node must be added with the following two properties:

  • unitySectionName. This property contains the name of the configuration section in the configuration file that contains the configuration for the Unity Application Block; by default, the value for this property is esb.resolver.

  • unityContainerName. This property contains the name of the Unity container defined in the Unity configuration specific to your custom resolver.

    The following XML is an example of the configuration necessary in the resolvers node.

<resolver name="ITINERARY-STATIC" type="Microsoft.Practices.ESB.Resolver.Unity.ResolveProvider, Microsoft.Practices.ESB.Resolver.Unity, Version=2.0.0.0, Culture=neutral, PublicKeyToken=c62dd63c784d6e22">
     <resolverConfig>
          <add name="unitySectionName" value="esb.resolver" />
          <add name="unityContainerName" value="ITINERARY" />
     </resolverConfig>
</resolver>

The following XML is an example of the configuration necessary under the esb.resolver node.

<typeAliases>
     <!-- Lifetime manager types -->
     <typeAlias alias="singleton" type="Microsoft.Practices.Unity.ContainerControlledLifetimeManager, Microsoft.Practices.Unity, Version=1.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
     <!-- std type providers -->
     <typeAlias alias="string" type="System.String, mscorlib"/>
     <typeAlias alias="int" type="System.Int32, mscorlib"/>
     <!-- repository providers -->
     <typeAlias alias="IRepositoryProvider" type="Microsoft.Practices.ESB.Resolver.Itinerary.Facts.Repository.IRepositoryProvider, Microsoft.Practices.ESB.Resolver.Itinerary.Facts, Version=2.0.0.0, Culture=neutral, PublicKeyToken=c62dd63c784d6e22"/>
     <typeAlias alias="SqlRepositoryProvider" type="Microsoft.Practices.ESB.Resolver.Itinerary.DataAccess.SqlRepositoryProvider, Microsoft.Practices.ESB.Resolver.Itinerary.DataAccess, Version=2.0.0.0, Culture=neutral, PublicKeyToken=c62dd63c784d6e22"/>
     <!-- fact providers -->
     <typeAlias alias="IFactProvider" type="Microsoft.Practices.ESB.Resolver.Facts.IFactProvider, Microsoft.Practices.ESB.Resolver.Facts, Version=2.0.0.0, Culture=neutral, PublicKeyToken=c62dd63c784d6e22"/>
     <typeAlias alias="IFactTranslator" type="Microsoft.Practices.ESB.Resolver.Facts.IFactTranslator, Microsoft.Practices.ESB.Resolver.Facts, Version=2.0.0.0, Culture=neutral, PublicKeyToken=c62dd63c784d6e22"/>
     <typeAlias alias="ItineraryFactProvider" type="Microsoft.Practices.ESB.Resolver.Itinerary.Facts.ItineraryFactProvider, Microsoft.Practices.ESB.Resolver.Itinerary.Facts, Version=2.0.0.0, Culture=neutral, PublicKeyToken=c62dd63c784d6e22"/>
     <typeAlias alias="ItineraryStaticFactProvider" type="Microsoft.Practices.ESB.Resolver.Itinerary.Facts.ItineraryStaticFactProvider, Microsoft.Practices.ESB.Resolver.Itinerary.Facts, Version=2.0.0.0, Culture=neutral, PublicKeyToken=c62dd63c784d6e22"/>
     <typeAlias alias="ItineraryHeaderFactProvider" type="Microsoft.Practices.ESB.Resolver.Itinerary.Facts.ItineraryHeaderFactProvider, Microsoft.Practices.ESB.Resolver.Itinerary.Facts, Version=2.0.0.0, Culture=neutral, PublicKeyToken=c62dd63c784d6e22"/>
     <typeAlias alias="ResolutionFactProvider" type="Microsoft.Practices.ESB.Resolver.Itinerary.Facts.ResolutionFactProvider, Microsoft.Practices.ESB.Resolver.Itinerary.Facts, Version=2.0.0.0, Culture=neutral, PublicKeyToken=c62dd63c784d6e22"/>
     <typeAlias alias="DefaultFactTranslator" type="Microsoft.Practices.ESB.Resolver.Facts.DefaultFactTranslator, Microsoft.Practices.ESB.Resolver.Facts, Version=2.0.0.0, Culture=neutral, PublicKeyToken=c62dd63c784d6e22"/>
     <typeAlias alias="ItineraryFactTranslator" type="Microsoft.Practices.ESB.Resolver.Itinerary.Facts.ItineraryFactTranslator, Microsoft.Practices.ESB.Resolver.Itinerary.Facts, Version=2.0.0.0, Culture=neutral, PublicKeyToken=c62dd63c784d6e22"/>
     <!-- resolve providers -->
     <typeAlias alias="IResolveProvider" type="Microsoft.Practices.ESB.Resolver.IResolveProvider, Microsoft.Practices.ESB.Resolver, Version=2.0.0.0, Culture=neutral, PublicKeyToken=c62dd63c784d6e22"/>
     <typeAlias alias="ItineraryResolveProvider" type="Microsoft.Practices.ESB.Resolver.Itinerary.BREItineraryResolverContainer,Microsoft.Practices.ESB.Resolver.Itinerary, Version=2.0.0.0, Culture=neutral, PublicKeyToken=c62dd63c784d6e22 "/>
     <typeAlias alias="StaticItineraryResolveProvider" type="Microsoft.Practices.ESB.Resolver.Itinerary.StaticItineraryResolveContainer,Microsoft.Practices.ESB.Resolver.Itinerary, Version=2.0.0.0, Culture=neutral, PublicKeyToken=c62dd63c784d6e22 "/>
</typeAliases>
<containers>
     <container name="ITINERARY">
          <types>
               <type type="IResolveProvider" mapTo="StaticItineraryResolveProvider" />
               <type type="IRepositoryProvider" mapTo="SqlRepositoryProvider" name="CurrentRepositoryProvider">
                    <lifetime type="singleton" />
                    <typeConfig extensionType="Microsoft.Practices.Unity.Configuration.TypeInjectionElement,Microsoft.Practices.Unity.Configuration, Version=1.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
                         <constructor>
                              <param name="connectionStringName" parameterType="string">
                                   <value value="ItineraryDb"/>
                              </param>
                              <param name="cacheManagerName" parameterType="string">
                                   <value value="Itinerary Cache Manager"/>
                              </param>
                              <param name="cacheTimeout" parameterType="string">
                                   <value value="120" />
                              </param>
                         </constructor>
                    </typeConfig>
               </type>
               <type type="IFactProvider" mapTo="ResolutionFactProvider" name="ResolutionFactProvider"  />
               <type type="IFactProvider" mapTo="ItineraryHeaderFactProvider" name="HeaderFactProvider"  />
               <type type="IFactProvider" mapTo="ItineraryStaticFactProvider" name="StaticFactProvider"  />
               <type type="IFactTranslator" mapTo="DefaultFactTranslator" name="DefaultFactTranslator">
                    <lifetime type="singleton" />
               </type>
               <type type="IFactTranslator" mapTo="ItineraryFactTranslator" name="ItineraryFactTranslator">
                    <lifetime type="singleton" />
                    <typeConfig extensionType="Microsoft.Practices.Unity.Configuration.TypeInjectionElement,Microsoft.Practices.Unity.Configuration, Version=1.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
                         <constructor>
                              <param name="repositoryProvider" parameterType="IRepositoryProvider">
                                   <dependency name="CurrentRepositoryProvider"/>
                              </param>
                         </constructor>
                    </typeConfig>
               </type>
          </types>
     </container>
</containers>

For detailed information about the configuration that is necessary in the esb.resolvers node, see Source Schema for the Unity Application Block (https://go.microsoft.com/fwlink/?LinkId=188288) on MSDN.

Creating a Custom Unity Resolver

To create a custom Unity resolver

  1. (Optional) Create an assembly or assemblies with a class that implements the IFactProvider interface and contains a RegisterFact method that provides information necessary for resolution to occur.

  2. (Optional) Create an assembly or assemblies with a class that implements the IFactTranslator interface and contains a TranslateFact method that translates the provided facts to key/value pairs within the resolver dictionary.

  3. Create an assembly with a class that implements the IResolveContainer and IResolveProvider interface and that contains a Resolve method that validates the resolver configuration, gathers all the facts from the fact providers, performs any specialized processing, translates them using the fact translators, and returns the translated facts as an instance of the Dictionary class.

  4. Register the resolver by adding it to the Esb.config configuration file using a <resolver> element that contains the root moniker as the name attribute and the fully qualified assembly name as the type attribute.

  5. Add the Unity-specific configuration to the Esb.config file for this resolver.

  6. (Optional) Create a schema that defines the root moniker and the query parameters, and then save it in the ESB.Schemas.Resolvers folder. The name should follow existing ESB naming conventions; this means it should use the name of the root moniker appended with "_Resolution.xsd".

  7. (Optional) Generate a class from the new schema and save it in the custom resolver assembly. This will expose typed parameters in the custom resolver.

  8. Register all the assemblies in the global assembly cache.