使用 Unity 容器创建自定义解析器

可以使用 Unity 应用程序块 (Unity )(Unity)(https://go.microsoft.com/fwlink/?LinkId=188286)创建自定义解析程序,以便对解析逻辑和元数据源进行运行时依赖项注入。

事实提供者

事实提供程序是实现 IFactProvider 接口的类的实例。 此接口公开名为 RegisterFact 的方法的三个不同的重载。 此方法接收消息、解析程序配置,在某些情况下接收管道上下文,并返回一个对象。 此对象可能是以某种方式从输入中提取的信息,可能是某种形式的计算,也可以从某些外部源查找。 事实提供程序返回的每个对象都可以称为事实,通常由解析容器添加到列表中,供事实翻译人员稍后使用。

Unity 解析程序可能具有零个或多个事实提供程序,这些提供程序可以随时通过单个配置更改添加或删除。

以下代码是事实提供程序中包含的逻辑示例。 也可以在 ESB.Resolver.Itinerary.Facts 项目的 ItineraryStaticFactProvider.cs 文件中找到此代码。 它是 ITINERARY-STATIC 解析程序中的组件,它从解析程序连接字符串中收集行程的名称和版本。

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;
}

事实翻译器

事实翻译器是实现 IFactTranslator 接口的类的实例。 此接口公开名为 TranslateFact 的单个方法。 此方法接受一个对象数组,该数组包含一个事实列表,以及稍后将由解析程序通过事实翻译器返回的解析器字典。 事实翻译器负责以有意义的方式处理事实提供程序提供的事实,然后填充解析程序字典。

Unity 解析程序可能具有零个或多个事实翻译器,可随时通过单个配置更改添加或删除事实翻译。

以下代码是事实翻译中包含的逻辑示例。 此代码还可以在 ESB.Resolver.Itinerary.Facts 项目的 ItineraryStaticFactTranslator.cs 文件中找到。 它是 ITINERARY-STATIC 解析程序中的组件,它执行数据库查询以按名称和(可选)按版本收集行程的行程 XML。

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

解析容器

解析容器是实现 IResolveContainer 接口的类。 通常,它还实现 IResolveProvider 接口。 IResolveContainer 接口公开一个名为 Initialize 的方法,该方法采用 IUnityContainer。 传递给此方法的容器将包含解析程序完成其处理所需的所有依赖项(即 IFactProviderIFactTranslator 类的实例)以及所需的任何其他类型。

以下代码是 IResolveContainer 接口实现的示例。 可以在 ESB.Resolver.Itinerary 项目的 StaticItineraryResolveContainer.cs 文件中找到此代码。

#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

在解析容器中,在 IResolveProvider 接口的 Resolve 方法的实现中,必须循环访问 Unity 容器中的所有事实提供程序和事实翻译器,以允许其中每个方法执行其处理。

以下代码是 Resolve 容器中包含的逻辑示例。 此代码也可以在 ESB.Resolver.Itinerary 项目的 StaticItineraryResolveContainer.cs 文件中找到。

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;
    }
}

配置自定义 Unity 冲突解决程序

若要配置自定义 Unity 解析程序,与创建自定义解析程序时相同的配置步骤适用;但是,必须包含一些附加配置才能正确注册构成解析程序的组件。

首先,在 Esb.config 文件中,在解析程序声明下,必须使用以下两个属性添加 resolverConfig 节点:

  • unitySectionName。 此属性包含配置文件中配置节的名称,其中包含 Unity 应用程序块的配置;默认情况下,此属性的值为 esb.resolver

  • unityContainerName。 此属性包含特定于自定义解析程序的 Unity 配置中定义的 Unity 容器的名称。

    以下 XML 是 解析程序 节点中所需的配置示例。

<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>

以下 XML 是 esb.resolver 节点下所需的配置示例。

<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>

有关 esb.resolvers 节点中必需的配置的详细信息,请参阅 MSDN 上的 Unity 应用程序块的源架构https://go.microsoft.com/fwlink/?LinkId=188288)。

创建自定义 Unity 解析程序

创建自定义 Unity 解析程序

  1. (可选)使用实现 IFactProvider 接口的类创建程序集或程序集,并包含一个 RegisterFact 方法,该方法提供解决所需的信息。

  2. (可选)使用实现 IFactTranslator 接口的类创建程序集或程序集,并包含 TranslateFact 方法,该方法将提供的事实转换为解析程序字典中的键/值对。

  3. 创建一个包含实现 IResolveContainerIResolveProvider 接口的类的程序集,该接口包含一个 Resolve 方法,用于验证解析程序配置、收集事实提供程序中的所有事实、执行任何专用处理、使用事实翻译器翻译这些事实,并将翻译的事实作为 Dictionary 类的实例返回。

  4. 使用一个包含根名字对象作为name属性和完全限定程序集名称作为type属性的<解析程序>元素,将解析程序添加到Esb.config配置文件中以注册解析程序。

  5. 请将 Unity 特定的配置添加到 Esb.config 文件以用于该解析器。

  6. (可选)创建定义根名字对象和查询参数的架构,然后将其保存在 ESB 中。Schemas.Resolvers 文件夹。 该名称应遵循现有的 ESB 命名约定;这意味着它应使用追加有“_Resolution.xsd”的根名字对象的名称。

  7. (可选)从新架构生成一个类,并将其保存在自定义解析程序程序集中。 这将在自定义解析程序中公开类型化参数。

  8. 在全局程序集缓存中注册所有程序集。