65.9K
CodeProject 正在变化。 阅读更多。
Home

WF4 的企业变量

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.95/5 (21投票s)

2010年9月23日

CPOL

23分钟阅读

viewsIcon

62767

downloadIcon

838

本文描述了存储在存储库中的企业变量的设计、实现和使用。

 

目录

 

特点

  • 在运行时存储库中逻辑集中化
  • 基于VisualBasicValue表达式文本
  • 任何变量类型(简单类型、复杂类型等)
  • 获取/设置值或表达式文本
  • 变量也可以是按类型或 XAML 声明的活动
  • 应用程序和环境范围
  • 跨应用程序共享
  • 使用管理工具动态管理变量
  • 通过管理工具实现业务模型解耦
  • 更改无需重新部署
  • 通过静态方法轻松访问
  • 事务访问
  • 服务访问的易于扩展性
  • .Net 4 技术

 

引言与概念

本文是我之前文章的延续,例如可管理服务可管理服务的契约模型。在这些文章中,我详细描述了模型驱动架构,其中逻辑模型在存储库中集中化(存储),然后分散化(部署)到目标以进行运行时投影。基本上,我解释了从存储库推送和拉取元数据的策略。典型的部署场景是以模型-存储库-部署方式推送元数据。因此,业务/契约模型的任何更改都需要重新运行此推送场景,并采取一些保护措施(例如路由机制)以避免在此过程中出现一些运行时故障。另一方面,拉取场景将允许从存储库引导元数据并根据需要动态刷新模型。请注意,拉取场景不易实现,需要主机基础设施中构建引导/加载组件。

本文重点关注混合解决方案,其中模型从存储库推送到目标,然后目标可以在运行时根据要求从存储库拉取元数据。WF4 在工作流中引入了运行时变量,用于在活动之间共享状态。变量可以在特定范围被调度时通过默认值进行设置。默认值可以显式或隐式地使用表达式文本表示。

请注意,工作流变量不能直接访问和在工作流之间共享。它们在工作流实例的本地范围内。以下屏幕截图显示了工作流变量的示例

如我们从上面的示例中看到,在序列范围内声明了 3 个变量,包括它们的默认值。在特定范围内使用未声明的变量将在设计时抛出异常。WF4 当前版本不支持类似VariableResolver的东西,这将是根据自定义需求解析变量的一个很好的扩展点。这类似于域无法加载特定程序集的场景。但是,我将向您展示另一种实现方式;使用自定义静态 API 方法和自定义序列活动。下图显示了此解决方案

如您所见,在自定义序列活动(例如EnterpriseVariableScope)中,有两种变量:企业变量和局部变量。变量之间的区别基于下划线(_)字符提示。对于带下划线提示的变量,默认值会自动生成。此解决方案实现简单,并且在 WF4 将支持VariableResolver(例如,为变量提供外部作用域)的情况下,可以轻松迁移。

正如您稍后将看到的,运行时存储库将具有与工作流中相同的声明变量的能力。此外,还有更多与企业范围相关的功能,例如环境、应用程序、版本、表达式文本、事务性更改、编辑等。请注意,变量值可以是任何类型,例如:简单类型、泛型类型或更复杂的类型,例如活动、ServiceEndpoint 等。

设计时,当我们把业务模型映射到元数据表示的目标运行时模型时,我们应该知道哪个变量将是企业范围或本地范围的。

将变量封装为本地变量和企业变量两组,可以实现变量的不同管理方式。从抽象的角度来看,本地变量位于应用程序域中,运行时投影器根据已部署的元数据创建它们。对于企业变量,它们被部署(推送)到存储库的特定部分,例如运行时存储库,在那里它们可以在运行时被拉取。

 

如上图所示,模型从存储库推送到目标,其中局部变量位于其中。运行时存储库中的企业变量在运行时根据已部署的模型从存储库中拉取。

以下屏幕截图显示了企业变量在模型-存储库-部署-目标-运行时存储库场景中的位置

 

如上图所示,存储库分为两部分。第一个是集中式逻辑(企业)模型,其中存储元数据;另一个是运行时存储库。运行时存储库是企业变量的存储,用于在环境和应用程序范围内运行时使用。企业变量可以针对特定应用程序和环境声明,例如仅用于开发,或者可以声明为任何范围的中立变量等。运行时存储库有自己的编辑器,称为管理工具。其目的是从业务和 IT 角度管理变量。

 

变量应该放在哪里?

基本上,应用程序模型中有三个主要位置

  • 配置变量集中(位于)配置文件中的部分。变量可以随时从配置文件或其缓存中拉取。请注意,配置文件中的任何更改都会强制回收过程;因此,我们不应将其用于常规管理目的。
  • 局部变量在模型中特定作用域内声明。它们与模型紧密耦合,其更改将需要完整的重新部署场景。对于小型解决方案且对回收过程故障不关键,此场景相当适用且不需要特殊的管理工具。
  • 企业变量是松散解耦的模型变量,逻辑上集中在运行时存储库中,由管理工具管理,并根据应用程序和环境范围从存储库中拉取。它们不依赖于目标运行时,因此我们可以随时更改它们而不会影响其使用者。由于这些特性,企业变量在企业应用程序中得到推荐。

下图显示了企业变量的一些特性,其中目标可以根据机器和应用程序名称从运行时存储库中拉取特定变量

拥有可执行的独立(敏捷)模型使我们能够设计更健壮且独立于物理部署的模型。基于部署模型,我们的应用程序逻辑模型可以分散到各个目标并部署到运行时存储库。目标可以根据应用程序和环境上下文(例如应用程序名称、站点名称、变量作用域名称、机器名称等)从运行时存储库中拉取变量。例如(如上图所示):应用程序A在环境DevQA中可以有不同的值。请注意,此值可以在运行时处理期间手动或通过服务进行管理。

好的,如果您目前为止理解正确,在模型驱动架构(模型-存储库-部署)中,我们有一种用于模型调整和重新部署的机制,我们应该关注模型的敏捷性、将业务封装到小的子模型中以及松散解耦的基元和工件,并根据需要将它们放置在运行时存储库中。简单地说,逻辑模型应该有一个“常量”部分和一个“变量”部分。这就是我们拥有运行时存储库的原因。

 

企业变量中应该存储什么?

简短的回答是:它可以是我们模型中所需的任何工件类型。典型的类型是简单类型,例如字符串、整数、布尔值等。这些类型实现简单,没有任何对其他类型的引用或约束。它们表示为字面文本,例如:“1234”、“True”等。这些简单类型的管理不需要任何特殊的编辑器;我们可以使用(例如)标准记事本编辑器。

对于需要简单值的这类业务变量来说,这很好。处理更复杂类型的自然方式是使用 XML 格式的文本来表示更复杂的对象。我们仍然有一个字符串类型的变量作为 XML 序列化复杂对象的存储。变量值可以使用标准记事本或 XmlNotepad 编辑器进行编辑。到目前为止一切顺利,但我们仍然有许多限制,例如引用、执行等,因此我们需要更强大的变量和参数(例如 WF4 中引入的ExpressionTextBox)。

ExpressionTextBox允许在文本中放置LiteralVisualBasicValue以进行运行时编译和执行。这是构建企业变量的一个非常强大的功能。基本上,我们可以在运行时存储库中声明变量,方式与在工作流设计器中声明变量相同。

以下是企业变量中表达式文本的一些示例

100 + DateTime.Now.Second
New List(Of Int32) From { 0, 1, 2, 3, 4, 50, 6, 7, 8, 9 }
This.Variables.FirstOrDefault(Function(v) v.name = "a1").defaultValue
(From v In This.Variables Where v.applicationScope.Equals("*") Select v.name).ToList()
New DataContext(This.ConnectionString).ExecuteQuery(of int32)("select count(*) from Targets")
EVar.GetValue(Of List(Of Int32))("aa3").FindAll( Function(i) i > 2 and i < 6)
WorkflowInvoker.Invoke(new WriteLine with {.Text="Hello from workflow invoker"})
New WriteLine With {.Text="Hello World"}
<configuration><system.serviceModel><services><service name= .... /configuration>
<Activity xmlns="http://schemas.microsoft.com/netfx/2009/xaml/activities" ... /Activity>

如您从上述示例中看到,有多种表达式,从简单到复杂,使用自定义静态方法、带谓词函数的 LINQ 查询等。此外,我们还可以拥有一个用于路由服务和动态管理路由表的配置部分(详见后续描述)。从架构的角度来看,EnterpriseVariable使我们能够执行以命令式或声明式声明的可共享复杂类型。换句话说,逻辑集中模型可以物理地分散到目标和运行时存储库中。

企业变量/活动

业务基元、请求运行时管理的调解器可以从目标中物理分解,并直接或通过服务在运行时存储库中处理。例如,用于将输出消息转换为自定义格式的业务调解器可以解耦到带有 XSLT 资源的EnterpriseVariable中,并通过管理工具进行管理,而无需重新部署场景。我们可以使用内置的基元活动,例如System.Activities.Statements.InvokeMethod来调用可执行的企业变量(活动)。下图显示了调用企业活动的示例

 

在上面的例子中,我们有一个EnterpriseVariableScope,其中包含一个变量_act1,它从运行时存储库中设置了默认值,例如new WriteLine with { .Text="Hello World" }。该变量可以通过InvokeMethod活动调用,将其引用传递给Parameters属性。

正如我之前提到的,我们可以在企业变量中声明任何类型(从简单到复杂)。通过 XAML 栈声明的活动类型是复杂类型之一,我们可以在其中声明性地设计业务中介基元的一部分。对于这种复杂性,我们应该有一个重新托管的工作流设计器。下图显示了我构建并包含在本文中的重新托管设计器

 

使用此重新托管的工作流设计器/编辑器,我们可以快速设计任何活动,包括在将变量签入运行时存储库之前在自托管控制台程序中进行测试。请注意,此企业活动还可以声明另一个自定义活动,调用企业变量、活动等,没有任何限制,就像任何工作流活动一样。

我想这足以介绍EnterpriseVariables并展示其主要特性以及与局部变量的区别。下图显示了这一主要区别,其中多个应用程序可以共享不同类型的变量,这些变量在运行时存储库中逻辑和物理集中化。

 

再提一点,文章标题显示了 WF4 的用法,但它可以直接用于任何 .Net 4 技术,例如 WPF、WinForm、Console、WCF 等,并且通过服务连接也可以用于遗留和非 .net 应用程序。

让我们开始企业变量的设计和实现。

 

设计与实现

企业变量的概念和设计基于从存储在外部数据库中的编译和执行表达式文本中获取值。对于这个概念,我们可以使用 .Net Framework 4 中非常强大的新类,例如>Microsoft.VisualBasic.Activities.VisualBasicValue<TResult>>System.Activities.WorkflowInvoker。请注意,VisualBasicValue类派生自System.Activities.Activity,后者是 WF4 范例的基抽象类,因此我们可以使用 WorkflowInvoker 通过VisualBasicValue调用表达式文本。

在我们深入了解实现细节和设计之前,让我们先看看企业变量存储在哪里。

运行时存储库

运行时存储库是企业变量和目标应用程序包部署的存储位置。目标实体用于限定范围,以根据 Netbios 名称识别环境范围。下图是运行时存储库的架构。本文中包含在项目解决方案中的脚本用于在 SQL 数据库中创建、加载和删除运行时存储库。

 

以下表格由脚本预加载

上面的架构非常简单,可以根据版本控制、历史记录、回滚等额外需求进行扩展。有关于您的部署(目标机器)和变量的记录。每个变量都可以有自己的变量类型、作用域等。它们按 3 个内置类别分组,例如变量/活动/路由器。此外,还有一个非常基本的签出/签入支持,其中特定编辑器只能编辑已签出的变量。变量有两个值,第一个由管理工具使用,另一个(例如 runtimeValue)由目标(应用程序)使用。

好的,回到设计实现。

以下代码片段展示了一个调用表达式文本的静态泛型方法

public static dynamic Evaluation(string expressionText, string variableType, IDictionary<string, object> inputs)
{
    string typeName = variableType.IndexOf(".") > 0 ? variableType : "System." + variableType;

    Type type = GetFullType(typeName);

    Type typeVB = typeof(Microsoft.VisualBasic.Activities.VisualBasicValue<>).MakeGenericType(new Type[] { type });

    var activity = (Activity)Activator.CreateInstance(typeVB, new object[] { expressionText });

    var setting = new VisualBasicSettings();
    setting.ImportReferences.Add(new VisualBasicImportReference { Assembly = "mscorlib", Import = "System" });
    
    // ... more common imports
   
    VisualBasic.SetSettings(activity, setting);

    return inputs == null ? 
    	WorkflowInvoker.Invoke(activity)["Result"] : 
    	WorkflowInvoker.Invoke(activity, inputs)["Result"];
}

如您所见,这是一个非常直接的实现。基本上,VisualBasicValue的实例是根据泛型类型创建的。然后我们需要导入一些常见的命名空间和程序集,最后这个对象(Activity)通过WorkflowInvoker.Invoke静态方法调用。

对于特殊情况,当 expressionText 表示由 xaml 格式文本声明的 Activity 时,已设计和实现了以下静态方法

public static IDictionary<string, object> Execute(string xamlText, IDictionary<string, object> inputs)
{
  using (MemoryStream ms = new MemoryStream(Encoding.ASCII.GetBytes(xamlText)))
  {
    Activity activity = XamlIntegration.ActivityXamlServices.Load(ms);
    
    var retVal = inputs == null ? 
    	WorkflowInvoker.Invoke(activity, TimeSpan.FromSeconds(60)) : 
    	WorkflowInvoker.Invoke(activity, inputs, TimeSpan.FromSeconds(60));
    	
    return retVal;
  }
}

好的,现在下一层是处理对表达式文本存储的访问的层

public class This
{
  [ThreadStatic]
  public static bool IsDesignTime;
  
  [ThreadStaticAttribute]
  public static string ApplicationScope;
  
  [ThreadStatic]
  public static int EnvironmentScope;
  
  [ThreadStatic]
  public static string ConnectionString;

  public static System.Data.Linq.Table<Variable> Variables
  {
    get { return new System.Data.Linq.DataContext(ConnectionString).GetTable<Variable>(); }
  }

  internal static T Get<T>(string variableName)
  {
    string cs = ConnectionString;
    string ascope = ApplicationScope ?? "*";
    int escope = EnvironmentScope;

    using (var repository = new RepositoryDataContext(cs))
    {
      var variable = repository.Variables.FirstOrDefault(v => 
         string.Compare(v.name, variableName, true) == 0 && 
        (v.applicationScope.Equals(ascope) || ascope == "*" || v.applicationScope == null) && 
        (v.environmentScope == escope || v.environmentScope == 0));
      
      if (variable == null)
        throw new Exception(" ..."));

      return (T)VariablesHelper.Evaluation(IsDesignTime ? 
         variable.defaultValue : 
         variable.runtimeValue, variable.variableType);
    }
  }
  
  // ...
}

上述类,ThreadStatic属性用于传递调用的上下文,例如应用程序和环境作用域等。根据属性IsDesignTime,变量的值将被评估。

这个模块的最后一层是API层,它是由应用程序(例如,由工作流)调用的层。有两种方式调用接口;通过实例或静态方法。此接口的顶层类是EVar对象。

以下代码片段显示了类 Get<T> 和静态 GetValue<T> 两种实现的示例

public class EVar
{
  public class Get<T>
  {
    public string ConnectionString
    {
      get { return This.ConnectionString; }
      set { This.ConnectionString = value; }
    }
    public string Name { get; set; }
    public Dictionary<string, object> Inputs { private get; set; }

    public T Value
    {
      get
      {
         string traceText = VariablesHelper.SetScope();
         using (TransactionScope ts = new TransactionScope())
         {
           T variable = Inputs == null ? 
             This.Get(this.Name) : This.Exec(this.Name, Inputs);
           
           ts.Complete();
           return variable;
         }
      }
      private set
      {
         throw new NotSupportedException();
      }
    } 
    
    // ...
  } 
  public static T GetValue<T>(string variableName)
  {
    VariablesHelper.SetScope();

    using (TransactionScope ts = new TransactionScope())
    {
      T variable = This.Get<T>(variableName);
      ts.Complete();
      return variable;
    }
  }
  
  // ...
}

我认为上述实现非常直观。在调用序言中,我们需要根据机器名、应用程序、连接字符串等设置作用域上下文。在此步骤中,我们需要调用 Targets 表以获取我们机器和应用程序名的环境作用域。

好的,解释这一层最好的方式就是展示一些示例。以下是使用实例类 EVar.Get<T> 获取值的示例

new EVar.Get(Of System.DateTime) With { .Name="varName", .ConnectionString ="myDbConnection"}.Value

new EVar.Get(Of String) With { .Name="varName"}.Item(5)

new EVar.Get(Of String) With { .Name="varName", .ConnectionString ="myDbConnection"}.ExpressionText

new EVar.Get(Of String) With { .Name="varName"}.Variable.VariableType

new EVar.Get(Of MyObject) With { .Name="varName", .Inputs=EVar.Inputs}.Item("result")

 

下表显示了使用静态方法的一些示例

 静态方法/属性示例  描述
 EVar.GetValue("variableName")  获取变量的字符串表示值。
 EVar.GetValue(Of List(Of Int32)("variableName")  获取声明类型 System.Int32 的变量值
 EVar.GetItem(Of String)("variableName", "keyOrIndex")  获取指定变量的 Dictionary/List 项的值
 EVar.Exec("variableName", inputs)  执行由 xaml 声明的变量/活动
 EVar.SetValue(Of DateTime)("variableName", "DateTime.Now")  设置 DateTime 类型的变量的表达式文本。
 This.Variables  运行时存储库表 'Variables'
 This.Targets  运行时存储库表 'Targets'
 This.ConnectionString  连接字符串的线程安全静态属性
  EVar.Inputs  空 Dictionary(Of String, Object) 对象。

 

EnterpriseVariableScope 活动

EnterpriseVariableScope是一个自定义序列活动,用于声明局部变量和企业变量。此自定义活动的使用方式与内置Sequence活动用于设计根活动的方式相同。

如上图所示,变量名称带有下划线前缀。此字符区分本地(常规)变量和企业变量。如果能用它来声明一个范围列并定义一个外部企业范围会很好,但没有简单的拦截器来处理外部范围,就像也没有VariableResolver的处理器一样。

EnterpriseVariableScope 内置了根据名称和变量类型生成默认值表达式的机制。对于此表达式文本,我们使用 EVar.GetValue<T> 静态方法。请注意,变量名(在此方法中)可以手动更改,或者我们可以使用包含其作用域的完整变量名。

EnterpriseVariableScope的设计和实现非常直接和轻量级。感谢 Reflector,它让我可以使用剪切/粘贴机制来创建自定义活动(注意,Sequence 活动是一个密封类)。基于这个样板,以下部分已添加到 Variable 属性中

OnAddValidationCallback = delegate(Variable item)
{
  if (item == null)
  {
    throw new Exception("item");
  }
  if (item.Name.StartsWith("_") && item.Default == null)
  {
    string expText = string.Format("EVar.GetValue(Of {0})(\"{1}{2}\")", 
    	item.Type, this.VariableFullName ? this.DisplayName + "." : "", item.Name.Substring(1));
    
    expText = expText.Replace("`1[", "(Of ")
    	.Replace("`2[", "(Of ")
    	.Replace("`3[", "(Of ").Replace(']', ')');

    string typeName = item.Type.FullName.IndexOf(".") > 0 ? 
    	item.Type.FullName : "System." + item.Type.FullName;
    	
    Type type = RKiss.EnterpriseVariables.VariablesHelper.GetFullType(typeName);
    Type typeVB = typeof(Microsoft.VisualBasic.Activities.VisualBasicValue<>).MakeGenericType(new Type[]{ type });
    var activity = (ActivityWithResult)Activator.CreateInstance(typeVB, new object[] { expText });
    item.Default = activity;
  }
}

上述代码片段展示了为企业变量创建默认值的逻辑。一旦值被创建,它就可以手动编辑。在更改变量类型的情况下,简单的方法是清除默认表达式文本以重新生成它。

当活动以透明方式调度时,EnterpriseVariableScope活动使我们能够从运行时存储库加载值。之后,该变量像其他局部变量一样使用。

 

Assign2EV<T> 活动

此自定义活动的目的是为企业变量赋值或表达式文本。它基于内置的 Assign<T> 活动。下图显示了 Assign2EV<T> 自定义活动的设计器和属性

属性To表示变量的表达式文本。下划线字符用作企业变量的前缀。如您所见,有一个新的枚举属性StoreAs,它有两个可选选项,将存储在 RuntimeRepository 中。第一个(默认)Value是一个常规选项,就像 Assign<T> 活动使用的那样。在这种情况下,表达式文本的值将被存储。在我们上图中它是一个整数。第二个枚举选项是ExpressionText。在这种情况下,表达式文本的 Value 属性将被存储。

请注意,存储在存储库中的企业变量的本地副本将以事务方式更新。以下代码片段显示了 execute 方法中的逻辑

protected override void Execute(CodeActivityContext context)
{
  // name of the variable
  Type typeVBR = typeof(Microsoft.VisualBasic.Activities.VisualBasicReference<>).MakeGenericType(new Type[] { this.To.ArgumentType }); 
  string name = (string)typeVBR.GetProperty("ExpressionText").GetValue(this.To.Expression, null);

  if (name.StartsWith("_"))
  {
    Type typeL = typeof(System.Activities.Expressions.Literal<>).MakeGenericType(new Type[] { this.To.ArgumentType });

    MethodInfo mi = typeof(RKiss.EnterpriseVariables.EVar).GetMethod("SetValue", System.Reflection.BindingFlags.Static | BindingFlags.Public);
    MethodInfo gmi = mi.MakeGenericMethod(new Type[] { this.To.ArgumentType });


    if (this.StoreAs == StoreAsTypes.ExpressionText)
    {
      string value = null;
      if (this.Value.Expression.GetType() == typeL)
      {
        value = typeL.GetProperty("Value").GetValue(this.Value.Expression, null).ToString();
      }
      else
      {
        Type typeVBV = typeof(Microsoft.VisualBasic.Activities.VisualBasicValue<>).MakeGenericType(new Type[] { this.To.ArgumentType });
        value = (string)typeVBV.GetProperty("ExpressionText").GetValue(this.Value.Expression, null);
      }

      using (TransactionScope ts = new TransactionScope())
      {
        gmi.Invoke(null, new object[] { name.Substring(1), value });
        context.SetValue<T>(this.To, this.Value.Get(context));
        ts.Complete();
      }
    }
    else
    {
      using (TransactionScope ts = new TransactionScope())
      {
        gmi.Invoke(null, new object[] { name.Substring(1), this.Value.Get(context).ToString() });
        context.SetValue<T>(this.To, this.Value.Get(context));
        ts.Complete();
      }
    }
  }
  else
  {
    context.SetValue<T>(this.To, this.Value.Get(context));
  }
}

如我们所见,上述实现使用反射根据变量类型以编程方式处理泛型方法。首先,我们必须弄清楚变量的名称,然后决定是 Literal<> 还是 VisualBasicValue<T>,最后调用神奇的静态方法 EVar.SetValue 来更新存储库表中变量的 runtimeValue。

 

将变量作为活动调用

企业变量可以声明为任何类型,因此对于 WF4 来说,将企业变量声明为通过 XAML 表达式文本的活动是很自然的。此功能使我们能够将业务模型的一部分解耦为集中在 RuntimeRepository 中以进行管理的小型可共享 XAML 声明的活动。工作流可以使用内置的InvokeMethod活动来使用这些类型的变量/活动。

下图显示了如何使用 InvokeMethod 调用企业变量的示例

EVar.Exec静态方法允许通过 Dictionary 对象将输入传递给活动。我们还可以通过活动返回的 Dictionary 对象获取结果。这是企业变量的一个强大功能,当数据可以传递给变量进行处理;修改数据,然后将数据返回给调用者。

本次主题到此结束。

 

附加功能

如前所述,本文版本存在一些局限性。对于完整生产版本,应扩展以下功能

  • 添加服务以将变量的编译和执行表达式文本与应用程序解耦。在某些情况下,最好将变量的评估与应用程序层隔离开来。
  • 缓存和失效变量以提高性能。这是一个可选功能,应仔细选择以动态获取/设置变量。变量可以随时由应用程序或管理工具更改,因此,具有缓存机制并使其值失效会在频繁使用获取/设置的情况下导致处理开销。当缓存和失效逻辑在服务层内时,可以使用一些折衷方案。

 

好的,现在是演示企业变量用法的时候了。

 

用法与测试

企业变量的特性和用法可以通过以下测试解决方案进行演示。该解决方案包括运行时库、存储库、小型管理工具和自托管服务 HelloWorld。下图显示了此包

Lib 文件夹包含两个项目。其程序集必须包含在您的应用程序中才能使用企业变量。在此之前,我们必须在 SQL Server 上创建存储库。Repository 文件夹中有一个setup.bat文件。请以管理员帐户运行此批处理文件以创建数据库。数据库名称为RuntimeRepository。Repository 预加载了一些变量用于测试目的。

好的,现在,让我们描述下一个文件夹,例如 Tools。请注意,此管理工具是为测试目的而创建的,用于动态演示企业变量。

管理工具

第一步是登录到 RuntimeRepository。在 EnterpriseVariablesEditor 之前将显示以下提示登录对话框

 

请注意,Repository、Tools 和 Usage 文件夹中的所有配置都硬编码为RuntimeRepository数据库名称,因此请按 OK 按钮接受本地 SQLServer 访问的登录。

之后,winform 将显示在屏幕上。请切换到 Targets 选项卡。以下屏幕截图显示有一个预注册的目标。这是我的目标,用于在我环境中测试变量。请添加您的 Netbios 名称以进行测试。

 

好的,现在,切换回变量选项卡

 

此“变量”选项卡允许您在 RuntimeRepository 中添加和修改变量。请注意,变量只能在签出状态下修改。内置编辑器(根据类别)可用于编辑默认变量值。富文本编辑器用于编辑变量(通常是表达式文本)。对于更复杂的对象,例如“活动”类别,包含工作流设计器编辑器。

请选择变量 a1 并点击编辑器。将显示以下图片

如您所见,这是一个重新托管的工作流设计器和嵌入在用户控件中的 XmlNotepad2007。您可以在解决方案包中找到此重新托管的源代码。此外,还有一个“运行”按钮,用于在控制台程序中自托管工作流以进行测试。

请按下“运行”按钮,将显示以下控制台程序

这是我们的第一个测试,您可以看到,counter变量的值是123,从 RuntimeRepository 加载到 EnterpriseVariableScope(Sequence 块)中。这是此编辑器的一个很酷的功能,允许在将变量签入 runtimeValue 之前对其活动进行故障排除、调试、运行等。

现在,回到“变量”并选择变量r1,然后单击“编辑器”。将显示以下编辑器(用于设计配置文件中的路由表)。这是另一种用于生成变量值的编辑器的示例。

 

 

这个路由器编辑器是为路由器管理器构建的。它在我的前一篇文章中有所描述。

管理工具到此为止。让我们做一些简单的运行时测试。为此,解决方案包含一个用 XAML 声明的自托管控制台程序。这个测试工作流程序将每隔延迟时间定期显示计数器值。换句话说,该程序将使用两个企业变量,即counterdelay

请启动 HelloWorld 控制台程序。

控制台屏幕将每3秒显示一行带有计数器值的新文本。这两个变量是从运行时存储库中拉取的——请参阅下图

 

您可以随时更改它们的值并在控制台程序上查看更改。此外,尝试启动另一个 HelloWorld 控制台程序实例以查看它们同时工作。

让我们看一个更高级的企业变量用法示例,例如嵌套调用表示活动的变量。如您所见,变量可以声明为强类型,也可以通过 XAML 声明。

高级示例

我们的第一步是为 Activity 类别创建一个新的企业变量a2。选择此类别后,变量将预定义类型、范围等。请键入名称a2并选择“编辑器”选项卡以创建以下Sequence a2

 

您可以通过按“运行”按钮来测试此活动。

请回到“变量”选项卡,并选择变量 a1 的编辑器。我们想修改此变量以获得企业变量更高级的功能。请修改此活动以匹配下图。请注意,序列作用域中有一个新的局部变量output

 

下图显示了 Assign2EV 和 Assign 等新属性的详细信息

第一个属性用于自定义活动,为企业变量To赋值。第二个是常规的 Assign 活动。有趣的部分是,值是从企业变量a2分配的,在本例中是一个活动。

现在,我们可以通过按“运行”按钮进行测试。您应该获得与下图所示相同的结果。

让我们进行更多同时测试。启动控制台程序 HelloWorld。下图显示了结果。如您所见,计数器具有随机值。

 

到目前为止一切顺利,但让我们再做一步有趣的,我们将通过 InvokeMehod 活动调用一个企业变量类型Activity

本次测试的第一步是创建一个新的企业变量a3。下图显示了存储库中的此变量

下一步是修改我们的活动 a2,以调用上述变量。下图显示了此更改

上图还显示了InvokeMethod属性的值。请注意,企业变量的值通过 Parameters 传递给 InvokeMethod。

好的,现在最后一步是运行最终测试。回到“变量”并选择变量a1和“编辑器”。按下“运行”按钮,活动 a1 将产生以下结果

到此为止,我希望这个测试向您展示了企业变量的有趣功能。

 

结论

总而言之,本文描述了一种在 WF4 中将企业变量集中存储在运行时存储库中的解决方案。它们在工作流模型中的使用就像局部变量一样完全透明。此外,企业变量为您的应用程序在企业级别提供了新的维度,例如在运行时管理变量、跨应用程序和环境共享变量、集中式运行时元数据和基元等。

 

© . All rights reserved.