So a couple of years I started learning about the ESB and when I discovered that you can pass values into an orchestration through a resolver, I thought ‘this is awesome, now I can send custom values into an orchestration based on a particular itinerary.’
I struggled in actually doing this, and finally put values in the Static Resolver and did sneaky string parsing of the Resolver in an orchestration.
I spent today (yes, one day) and created a simple custom resolver. I read the huge volumes of documentation on how to do it:
- Create an assembly with a class that implements the IResolveProvider interface and contains a Resolve method that returns resolver facts as an instance of the Dictionary class.
- 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.
- (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”.
- (Optional) Generate a class from the new schema and save it in the custom resolver assembly. This exposes typed parameters in the custom resolver.
- Register the new assembly in the global assembly cache.
Not terribly helpful, so I wanted to create the simple steps I took to get it all working:
Resolver
- I created a schema that had two values I wanted to pass into the orchestration:
the actual code is:
<?xml version="1.0" encoding="utf-16"?> <xs:schema xmlns="http://schemas.microsoft.biztalk.practices.esb.com/itinerary" xmlns:b="http://schemas.microsoft.com/BizTalk/2003" elementFormDefault="qualified" targetNamespace="http://schemas.microsoft.biztalk.practices.esb.com/itinerary" id="HardCoded" xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:element name="HardCoded"> <xs:complexType> <xs:attribute name="value" type="xs:string" use="required" /> <xs:attribute name="enable" type="xs:boolean" use="required" /> </xs:complexType> </xs:element> </xs:schema> - I used xsd.exe and created the class for it. I then added decorations to the class:
using System.Xml.Serialization; using Microsoft.Practices.Modeling.ExtensionProvider.Metadata; using Microsoft.Practices.Services.ItineraryDsl; using System.ComponentModel; using Microsoft.Practices.Modeling.ExtensionProvider.Extension; namespace StottCreations.ESB.HardCodedResolver { [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.17929")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] [System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "http://schemas.microsoft.biztalk.practices.esb.com/itinerary")] [System.Xml.Serialization.XmlRootAttribute(Namespace = "http://schemas.microsoft.biztalk.practices.esb.com/itinerary", IsNullable = false)] [ObjectExtender(typeof(Resolver))] public partial class HardCoded { private string valueField; private bool enableField; /// <remarks/> [Category(HardedCodedProvider.ExtensionProviderPropertyCategory), Description("Value to be passed into the service"), DisplayName("Value"), ReadOnly(false), Browsable(true)] [System.Xml.Serialization.XmlAttributeAttribute()] public string value { get { return this.valueField; } set { this.valueField = value; } } /// <remarks/> [Category(HardedCodedProvider.ExtensionProviderPropertyCategory), Description("Enable Property"), DisplayName("Enable"), ReadOnly(false), Browsable(true)] [System.Xml.Serialization.XmlAttributeAttribute()] public bool enable { get { return this.enableField; } set { this.enableField = value; } } } [ExtensionProviderAttribute("E5D2F3B5-C792-4050-BF9F-61234B30A3AC", "HardCoded", "Hard Coded Resolver Extension", typeof(ItineraryDslDomainModel))] [ResolverExtensionProvider] public partial class HardedCodedProvider : ExtensionProviderBase { public const string ExtensionSourcePropertyCategory = "Source Settings"; public HardedCodedProvider() : base(typeof(HardCoded)) { } } }
- I included the .cs file as part of the project
- Build and place the assembly in the following folder: C:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\IDE\Extensions\Microsoft.Practices.Services.Itinerary.DslPackage\Lib
- Restart Visual Studio
Provider
- Created a new C# project, gave it a strong named key (because BizTalk will execute this in the orchestration)
- In the class I wrote the following code:
using System; using System.Collections.Generic; using Microsoft.Practices.ESB.Resolver; using System.Xml; using System.Reflection; using Microsoft.XLANGs.BaseTypes; using Microsoft.Practices.ESB.Exception.Management; using Microsoft.BizTalk.Message.Interop; using Microsoft.BizTalk.Component.Interop; using Microsoft.Practices.ESB.Resolver.Container; using Microsoft.Practices.Unity; namespace StottCreations.ESB.HardCodedResolverProvider { public class HardCodedResolverProvider : IResolveProvider, IResolveContainer { /// <summary> /// Resolve implementation for use with Resolver Web Service. This method is typically used with unit tests to test for the correct Resolved entities, such as itinerary, maps and endpoint addresses. /// This method invokes the Hard Coded Values that were configured through the Config and Resolver connection string values. /// </summary> /// <param name="config">string containing name, value property values</param> /// <param name="resolver">Resolver connection string</param> /// <param name="message">Xml document containing the message to pass to the resolver if configured properly</param> /// <returns>Resolver Dictionary Collection containing resolved entries, such as itinerary name, map name, and endpoint address resolution values</returns> Dictionary<string, string> IResolveProvider.Resolve(string config, string resolver, XmlDocument message) { throw new NotImplementedException(); } /// <summary> /// Resolve implementation for use within a Pipeline component. /// This method is typically used with one of the ESB Pipeline components such as the Itinerary Selector, or ESB Dispatcher to resolve entities, such as itinerary, maps and endpoint addresses. /// This method invokes the Values that were configured through the Config and Resolver connection string values. /// </summary> /// <param name="config">string containing name, value property values</param> /// <param name="resolver">Resolver connection string</param> /// <param name="message">BizTalk IBaseMessage class which is used to pass to the resolver if configured properly</param> /// <param name="pipelineContext">BizTalk Pipeline configuration</param> /// <returns>Resolver Dictionary Collection containing resolved entries, such as itinerary name, map name, and endpoint address resolution values</returns> Dictionary<string, string> IResolveProvider.Resolve(string config, string resolver, IBaseMessage message, IPipelineContext pipelineContext) { throw new NotImplementedException(); } /// <summary> /// Resolve implementation for use within an Orchestration. /// This method is typically used by creating an instance of the BRE Resolver Provider class inside an orchestration expression to resolve entities, such as itinerary, maps and endpoint addresses. /// This method invokes the BRE Policies that were configured through the Config and Resolver connection string values. /// </summary> /// <param name="resolverInfo">Resolver collection of entries</param> /// <param name="message">BizTalk XLangMessage class which is used to pass to the resolver if configured properly</param> /// <returns>Resolver Dictionary Collection containing resolved entries, such as itinerary name, map name, and endpoint address resolution values</returns> Dictionary<string, string> IResolveProvider.Resolve(ResolverInfo resolverInfo, XLANGMessage message) { Dictionary<string, string> dictionary; if (message == null) { throw new ArgumentNullException("message"); } Resolution resolution = new Resolution(); try { dictionary = ResolverMgr.GetFacts(resolverInfo.Config, resolverInfo.Resolver); } catch (Exception exception) { EventLogger.Write(MethodBase.GetCurrentMethod(), exception); throw; } finally { if (resolution != null) { resolution = null; } } return dictionary; } private static IUnityContainer container; public void Initialize(Microsoft.Practices.Unity.IUnityContainer container) { HardCodedResolverProvider.container = container; } } }
- Compile and place the code into the GAC
- In the esb.config, you need to tell the ESB how to look up this: so you add the following line to the configuration\esb\resolvers section (upper case of the class of the resolver (public partial class HardCoded))
<resolver name="HARDCODED" type="StottCreations.ESB.HardCodedResolverProvider.HardCodedResolverProvider, HardCodedProvider, Version=1.0.0.0, Culture=neutral, PublicKeyToken=41bb35272df047ea"/>
- In the orchestration, add the following code in an expression shape (after creating to string variables called Enable and Value)
Resolver=Resolvers.Current; ResolverDictionary = Microsoft.Practices.ESB.Resolver.ResolverMgr.Resolve(InMsg, Resolver); Enable = ResolverDictionary.Item("ENABLE"); Value = ResolverDictionary.Item("VALUE");
- Deploy the orchestration and add the reference to the newly deployed orchestration in the esb.config
- Create the itinerary that calls the orchestration you created in step 5
- Deploy the itinerary, setup the on ramp to call the itinerary
- Run a message through the itinerary
- I put a breakpoint as I ran it and here is the Orchestration debugger
Please don’t flame me, I know that there are a series of try/catch blocks I should be putting in this, but this is 15 steps on how to create a custom resolver that you can pass values directly into an orchestration (and do the same for a messaging based solution also).