Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#4.0

Enterprise Variables for WF4

4.95/5 (21 votes)
23 Sep 2010CPOL23 min read 60.3K   813  
This article describes the design, implementation and usage of the Enterprise Variables stored in the Repository.

 

Contents

 

Features

  • Logical Centralized in the Runtime Repository
  • Based on the VisualBasicValue expression text
  • Any variable type (Simple, Complex type, etc.)
  • Get/Set Value or ExpressionText
  • Variable can also be an Activity declared by type or xaml
  • Application and Environment scopes
  • Shareable across the Applications
  • Managing variables on the fly using Admin Tool
  • Enable decoupling a business model managed by Admin Tool 
  • Re-deployment not required for changes
  • Simple access via static methods
  • Transactional access
  • Easy extensibility for service access
  • .Net 4 Technologies

 

Introduction and Concept

This article is a continuation to my previously articles such as Manageable Services and Contract Model for Manageable Services. In these articles, I have described in detail about the model driven architecture, where the Logical model is centralized (stored) in the Repository and then decentralized (deployed) to the target for its runtime projecting. Basically, I have explained strategies of pushing and pulling metadata from/to Repository. Typical deployment scenario is pushing metadata in the Model-Repository-Deploy manner. Therefore, any change in the business/contract model will require recycling this pushing scenario and having some protection (for instance routing mechanisms) to avoid some runtime glitches during this process. On the other hand, the pulling scenario will allow bootstrapping metadata from the Repository and refreshing model based on the needs on the fly. Note, that the pulling scenario is not easy to implement and it will require building a bootstrap/loader components in the hosting infrastructure.

This article is focusing on the hybrid solution, where the model is pushed from the Repository to the Target and then the Target can pull-up metadata during RunTime from the Repository based on the requirements. The WF4 introduced a runtime Variable within the workflow for sharing state between the activities. Variables can be setup by Default value when the specific scope is scheduled. The Default Value can be represented explicitly or implicitly using an expression text.

Note, that workflow Variables are not directly accessible and shareable between the workflows. There are locally in the scope of the workflow instance. The following screen snippet shows an example of the workflow Variables:

Image 1

As we can see the above example, there are 3 variables declared in the Sequence scope included their Default value. Using non-declared variable in the specific scope will throw an exception during the DesignTime. The WF4 current version doesn't support something like VariableResolver, which will be a great extensibility point for resolving a variable based on the custom needs. It is similar to a scenario, when domain cannot load a particular assembly. However, I will show you another way how it can be accomplished; using a custom static API methods and custom sequence activity. The following picture shows this solution:

Image 2

As you can see, in the custom sequence activity such as EnterpriseVariableScope there are two kinds of variables such as Enterprise and Local Variables. The difference between the variables is based on the underscore (<code>_) character prompt. For variable with the underscore prompt, the Default value is generated automatically. This solution has a straightforward implementation and it can be easily migrated in the case when WF4 will support VariableResolver, for instance, having an outer scope for variable.

As you will see later, the Runtime Repository will have the same capability to declare a variable like it is done in the workflow. In additional there are more features related to enterprise scope such as environment, application, version, expression text, transactional changes, editing, etc. Note, that the variable value can be any type, for example: simple type, generic type or more complex type such as Activity, ServiceEndpoint, etc.

During the DesignTime, when we map the business model to the target runtime model represented by the metadata, we should know which variable will be in the enterprise or local scope:

Image 3

Encapsulating variables into two groups such as Local and Enterprise, we are enabling different manageability of the variable. From the abstraction point of the view, the Local Variables are located in the application domain, where a runtime projector created their based on the deployed metadata. In the case of Enterprise Variables, they are deployed (pushed) to the specific part of the repository such as Runtime Repository from where they can be pulled during the runtime:

Image 4

 

As the above picture shows, the Model is pushed from Repository to the Target, where the Local Variables are located. The Enterprise Variables located in the Runtime Repository are pulled from the Repository on the runtime, based on the deployed Model.

The following screen snippet shows a position of the Enterprise Variables in the Model-Repository-Deploy-Target-RuntimeRepository scenario:

Image 5

 

As the above picture shows, the Repository is divided into two parts. The first one is a centralized logical (enterprise) model where metadata is stored, the other one, is a Runtime Repository. The Runtime Repository is a storage of the Enterprise Variables for runtime usage in the respect of the environment and application scopes. The Enterprise Variable can be declared for specific application and environment, for instance only for Development, or it can be declared as neutral for any scope, etc. The Runtime Repository has its own editor, known as Admin Tool. Its purpose is to administrate variable from business and IT perspectives.

 

Where should the Variables be located ?

Basically, there are three major locations in the application model:

  • Configuration variables are centralized (located) in the section located in the config file. Variables can be pulled in any time from the config file or from its cache. Note, that any change in the config file will force a recycle process; therefore we should not use it for regular administration purposes. 
  • Local variables are declared in the model within the specific scopes. They are tightly coupled with the model, and their changes will require full redeployment scenario. For small solutions and not critical for recycling process glitches, this scenario is quite suitable and doesn't require a special admin tool. 
  • Enterprise variables are loosely decoupled model variables, logical centralized in the Runtime Repository, administrated by Admin Tool and pulled from repository based on the application and environment scopes. They are not depended from the Target runtime, therefore we can change them any time without impacting their consumers. For those features, the Enterprise variables are recommended in the enterprise applications.

The following picture shows some features of the Enterprise variables, where Target can pull specific variables from the Runtime Repository based on the machine and application names:

Image 6

Having an executable neutral (agile) model enables us to design model more robust and independent from the physical deployment. Based on the Deployment Model, our Application Logical model can be decentralized to individual Targets and deployed to the Runtime Repository. The Target can pull variables from the Runtime Repository based on the application and environment context such as application name, site name, variable scope name, machine name, etc. For instance, (like the above picture shows): Application A can have a different value for environment Dev and QA. Note, that this value can be administrated manually or by service during the runtime processing.

OK, if you correctly understand until now, in the model driven architecture (Model-Repository-Deploy), where we have a mechanism for model tuning and  re-deployment, we should focus on model agility, business encapsulation into small sub-models and loosely decoupled primitives and artifacts and locate them in Runtime Repository based on the needs. Simple said, the Logical model should have a "constant" part and "variable" part. That's why we have a Runtime Repository.

 

What should be stored in the Enterprise Variable?

Well, the short answer is; it can be any artifact type needed in our model. The typical type is a simple type such as String, Integer, Boolean, etc. These types have a simple implementation without any referencing or constraints to other ones. They are represented like literal text, for instance: "</span />1234"</span />"</span />True"</span />, etc. The administration of these simple types doesn't require any special editor; we can use (for instance) a standard notepad editor.

That's fine for this kind of business variables, where we need a simple value. The natural way to handle more complex type is to use xml formatted text which represent more complex object. We still have a variable with string type as a storage of the xml serialized complex object. The variable value can be edited by standard notepad or XmlNotepad editors. So far so good, but we still have many limitations such as referencing, executing, etc., therefore we need something more powerful for variables and arguments such as ExpressionTextBox (introduced in WF4). 

The ExpressionTextBox allows to put a Literal or VisualBasicValue into the text for its runtime compiling and executing. This is a very powerful feature for building an Enterprise Variables. Basically, we can declare a variable in the Runtime Repository the same way like in the workflow designer.

The following are some examples of the expression text in the Enterprise Variable:

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>

As you can see the above examples, there are many kinds of expressions, from simple to complex, using a custom static method, LINQ query with predicate function, etc. Also, we can have a configuration section for routing service and managing routing table on the fly (see later description). From the architectural point of the view, the EnterpriseVariable enables us to execute shareable complex type declared imperatively or declaratively. In other words, the Logical centralized model can be decentralized physically to the Targets and Runtime Repository.

Enterprise Variable/Activity

The business primitives, mediators which requested a runtime administration can be physically decomposed from the Target and handled in the Runtime Repository directly or via a service. For instance, the business mediator for transforming output message to the custom format, can be decoupled into the EnterpriseVariable with a xslt resource and managed by Admin Tool without re-deploying the scenario. We can use the built-in primitive activity such as System.Activities.Statements.InvokeMethod for invoking executable enterprise variable (activity). The following picture shows an example of invoking enterprise activity:

Image 7

 

In the above example, we have an EnterpriseVariableScope with one variable _act1 setting from the Runtime Repository for default value, for example,  new</span /> WriteLine with { .Text="</span />Hello World"</span /> }. The variable can be invoked by InvokeMethod activity passing its reference to the Parameters property.

As I mentioned earlier, we can declare any type (from simple to complex one)  in the Enterprise Variable. The Activity type declared by xaml stack is one of the complex types, where we can declaratively design some portion of the business mediation primitives. For this complexity, we should have the re-hosted workflow designer. The following picture shows the re-hosted designer which I built and included in this article:

 

Image 8

Using this re-hosted workflow designer/editor, we can rapidly design any activity including its testing in the self-hosted console program before check-in the variable into the Runtime Repository. Note, that this enterprise activity can also declare another custom activity, invoking enterprise variables, activities, etc. without any restrictions, the same way like any workflow activity.

I think this is enough for introduction of the EnterpriseVariables and showing its major features and differences from the Local Variables. The following picture shows this major difference, where multiple applications can share variables with a different types, logical and physical centralized in the Runtime Repository:

Image 9

 

One more note, the title of the article shows usage for WF4, but it can be used directly by any .Net 4 Technologies such as WPF, WinForm, Console, WCF, etc. and via a service connectivity also for legacy and non .net applications.  

Let's get started with Design and Implementation of the Enterprise Variables.

 

Design and Implementation

The concept and design of the Enterprise Variables is based on getting a value from the compiling and executing expression text stored in the external database. For this concept, we can use new .Net Framework 4 very powerful classes such as <a href="</span>http://msdn.microsoft.com/en-us/library/dd780657.aspx">>Microsoft.VisualBasic.Activities.VisualBasicValue<</span />TResult></span /></a> and <a href="</span>http://msdn.microsoft.com/en-us/library/system.activities.workflowinvoker.aspx">>System.Activities.WorkflowInvoker</a>. Note, that a VisualBasicValue class is derived from the System.Activities.Activity which is a base abstract class of the WF4 paradigm, therefore we can use a WorkflowInvoker for invoking an expression text via VisualBasicValue.

Before we get into the implementation details and design, let's look at where Enterprise Variables are stored.

Runtime Repository

The Runtime Repository is a storage of the Enterprise Variables and Targets where an application package was deployed. The Targets entities are needed for scoping purposes to identify an Environment Scope based on the Netbios name. The following picture is a schema for Runtime Repository. There are scripts in the Project Solution included in this article for creating, loading and deleting a Runtime Repository in the SQL Database.

 

Image 10

The following tables are preloaded by the scripts:

Image 11

The above schema is very simple and it can be extended based on additional needs such as versioning, history, rollback, etc. There are records about your deployment (target machine) and variables. Every variable can have its own variable type, scopes, etc. They are grouped by 3 built-in categories such as Variables/Activity/Router. Also, there is a very basic level of the check-out/check-in support, where specific editor can edit checked-out variable only. The variable has two values, the first one is used by Admin Tool and the other one such as runtimeValue is used by Target (application).

OK, back to the design implementation.

The following code snippet shows a static generic method to invoke an expression text:

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

    Type type = GetFullType(typeName);

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

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

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

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

As you can see, this is very straightforward implementation. Basically, the instance of the VisualBasicValue is created based on the generic type. Then we need to import some common namespaces and assemblies and finally this object (Activity) is invoked by WorkflowInvoker.Invoke static method.

For special case, when an expressionText represents an Activity declared by xaml formatted text, the following static methods have been design and implemented:

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

OK, now the next level is a layer for handling access to the storage of the expression text:

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

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

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

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

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

The above class, ThreadStatic attributed properties is used for passing a context of the call such as application and environment scopes, etc. Based on the property IsDesignTime, the value of the variable will be evaluated.

The last level of this module is an API layer, which is a layer called by the application, for instance, by workflow. There are two ways to call the interface; by instance or static methods. The top class for this interface is a EVar object.

The following code snippet shows an example of the both implementations such as class Get<T> and static GetValue<T>:

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

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

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

I think the above implementation is very straightforward. In the prologue of the call, we need to setup the scope context, based on the machine name, application, connectionString, etc. In this step we need to call the Targets table to obtain an environment scope for our machine and application names.

OK, the best explanation of this layer is to show some examples. The following are examples of the getting value using an instance class 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")

 

The following table shows some examples of using the static methods:

 Example of Static Methods/Properties Description
 EVar.GetValue("variableName") Get string represented value of the variable.
 EVar.GetValue(Of List(Of Int32)("variableName") Get value of the variable for declared type System.Int32
 EVar.GetItem(Of String)("variableName", "keyOrIndex") Get value of the Dictionary/List item for specified variable
 EVar.Exec("variableName", inputs) Execute variable/Activity declared by xaml
 EVar.SetValue(Of DateTime)("variableName", "DateTime.Now") Set expression text of the variable for DateTime type.
 This.Variables Runtime Repository Table 'Variables'
 This.Targets Runtime Repository Table 'Targets'
 This.ConnectionString Thread safe static property for connection string
  EVar.Inputs Empty Dictionary(Of String, Object) object.

 

EnterpriseVariableScope Activity

The EnterpriseVariableScope is a custom sequence activity for declaration the Local and Enterprise variables. The usage of this custom activity is the same like built-in Sequence activity for designing root activities.

Image 12

As you can see in the above picture, the variable name has a prefix with underscore character. This character differentiates variables between a local (regular) and an enterprise variable. It will be nice to use it for declaration a Scope column and defining an external enterprise scope, but there is no simple interceptor for handling external scope, like there is no handler for VariableResolver, as well.

EnterpriseVariableScope has a built-in a mechanism for generating a Default value expression based on the name and variable type. For this expression text, we use a EVar.GetValue<T> static method. Note, that the variable name (in this method) can be manually change, or we can use a full variable name including its scope.

Design and implementation of the EnterpriseVariableScope is very straightforward and lightweight. Thanks for Reflector, it will allow me to use cut/paste mechanism to create a custom activity (Note, that the Sequence activity is a sealed class). Based on this boilerplate, the following part has been added in the Variable property:

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

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

The above code snippet shows the logic of creating a Default value for enterprise variable. Once the value has been created, it can be edited manually. In the case of the changing the variable type, the easy way is to clean-up a Default expression text for its re-generating again.

The EnterpriseVariableScope activity enables us to load values from the Runtime Repository when the activity is scheduled in the transparently manner. After that, the variable is used like other local variables.

 

Assign2EV<T> activity

The purpose of this custom activity is assigning a value or expression text to the enterprise variable. It's based on the Assign<T> built-in activity. The following picture shows the designer and properties of the Assign2EV<T> custom activity:

Image 13

Property To represents an expression text of the variable. The underscore character is used as a prefix for the enterprise variable. As you can see, there is a new enum property such as StoreAs with two selectable options that will be stored in the RuntimeRepository. The first (default) Value is a regular option like it is being used by Assign<T> activity. In this case, the value of the expression text will be store it. In our above picture it is an integer number. The second enum choice is ExpressionText. In this case, the Value property of the expression text is going to be stored.

Note, that the local copy of the enterprise variable stored in the Repository will be updated in the transactional manner. The following code snippet shows the logic in the execute method:

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

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

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


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

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

As we can see, the above implementation uses reflection to handle generic method programmatically based on the variable type. First of all, we have to figure out the name of the variable, than make a decision for Literal<> or VisualBasicValue<T> and finally call the magic static method EVar.SetValue for updating a runtineValue of the variable in the Repository table.

 

Invoking variable as Activity

Enterprise Variable can be declared for any type, therefore it is natural for WF4 have an enterprise variable declared as Activity by xaml expression text. This feature enables us to decouple part of the business model  into small shareable xaml declared Activities centralized in the RuntimeRepository for their administration. The workflow can consume these kinds of variables/activities using the built-in InvokeMethod activity.

The following picture is example of how to use InvokeMethod for calling enterprise variable:

Image 14

The EVar.Exec static method allows to pass the inputs to the activity via a Dictionary object. We can also obtain the result from the activity via its returned Dictionary object. This is a great feature of the enterprise variable, when the data can be passed for variable processing; modify the data and then return the data back to the invoker.

That's all for this topic.

 

Additional features

As I mentioned earlier, this article version has some limitations. For full production version, the following features should be extended:

  • Adding service for decoupling a compiling and executing expression text of variable from the application. In some cases, it is better to isolate evaluation of the variable from the application layer.
  • Caching and invalidating variables for improving the performance. This is an optional feature and it should be carefully selected for getting/setting variables on the fly. The variables can be changed any time by application or Admin Tool, therefore having a caching mechanism with invalidating its value will cause processing overhead in the case of frequent get/set usage. Some compromising can be used, when cache and invalidating logic is within the service layer.

 

Ok, now it is time to demonstrate the usage of the Enterprise Variables.

 

Usage and Test

The Enterprise Variables features and usage can be demonstrated in the following test solution. The solution consists of Runtime Library, Repository, tiny Admin Tool and self-hosted service HelloWorld. The following picture shows this package:

Image 15

The Lib folder contains two projects. Their assemblies must be included in your application for usage of the Enterprise Variables. Before that, we have to create a Repository on the SQL Server. There is a setup.bat file in the Repository folder. Please run this batch file under Admin account to create the database. The name of the database is RuntimeRepository. Repository is pre-loaded with few variables for testing purposes.

OK, now, lets describe the next folder such as Tools. Note, that this Admin Tool has been created for test purposes to demonstrate enterprise variables on the fly.

Admin Tool

The first step is to login to the RuntimeRepository. The following prompt login dialog will show up before the EnterpriseVariablesEditor:

Image 16

 

Note, that all configurations in the Repository, Tools and Usage folders are hardcoded for RuntimeRepository database name, therefore press the OK button to accept login for local SQLServer access.

After that, the winform will show up on the screen. Please, change the tab for Targets. The following screen snippet shows that there is one pre-register target. It is my target for testing variables in my environment. Please, add your Netbios name for your test purposes:

Image 17

 

OK, now, switch back to the Variables tab:

Image 18

 

This Variables tab allows you to add and modify variable in the RuntimeRepository. Note, that the variable can be modified only in the check-out state. The built-in editor (based on the category) can be used for editing the default variable value. The Rich Text Editor is used for editing variables (typically an expression text). For more complex objects, such as Activity category, the Workflow Designer Editor is included.

Please, select a variable a1 and click for Editor. The following picture will show-up:

Image 19

As you can see, this is a re-hosted WorkflowDesigner and embedded XmlNotepad2007 in the user controls. You can find the source code for this re-hosting in the solution package. Additionally, there is a button Run for self-hosted workflow in the console program for test purposes.

Please, press the button Run and the following console program will show-up:

Image 20

This is our first test, you can see, the value of the counter variable is 123</span />, loaded from the RuntimeRepository to the EnterpriseVariableScope (Sequence block). This is a cool feature of this editor allowing to troubleshoot, debug, run, etc. my activity before checking-in a variable to the runtimeValue.

Now, back to the Variables and select a variable r1 and click for Editor. The following editor (for designing a Routing Table in the config file) will show up. This is an example of another kind of editor for generating a variable value.

 

Image 21

 

Image 22

This Router Editor was built for Router Manager. It is described in my previously article.

That's all for the Admin Tool. Let's make some simple runtime test. For this purpose, the solution includes a self-hosted console program declared by xaml. This test workflow program will show counter value periodically each delay time. In other word, the program will use a two enterprise variables such as counter and delay.

Please, launch the HelloWorld console program.

Image 23

The console screen will show new line text with a counter value every 3 seconds. These two variables are pulled from the Runtime Repository - see the following picture:

 

Image 24

You can change their value any time and see changes on the console program. Also, try to launch another instance of the HelloWorld console program to see them work simultaneously.

Let's make some advanced example of the Enterprise Variables usage, such as nested invoking variable represented Activity. As you will see, variables can be declared as strong type and declaratively by xaml.

Advanced Example

Our first step is to create a new enterprise variable a2 for Activity category. Selecting this category, the variable will be predefined for type, scopes, etc. Please, type the name a2 and select tab Editor for creating the following Sequence a2:

 

Image 25

You can test this activity by pressing button Run.

Please go back to the Variables tab, and select editor for variable a1. We want to modify this variable for more advanced features of the Enterprise Variable. Please modify this Activity to match with the following picture. Note, there is a new local variable output in the Sequence scope:

Image 26

 

The following picture shows details of the new properties such as Assign2EV and Assign:

Image 27

The first property is for a custom activity to assign value for enterprise variable To. The second one, is a regular Assign activity. The interesting part is that the value is assigned from the enterprise variable a2, which in this case is activity. 

Now, we can make perform test by pressing the button Rum. You should get the same result like it is shown in the following picture:

Image 28

Let's make more simultaneous testing. Launch the console program HelloWorld. The following picture shows the result. As you can see, the counter has a random value:

Image 29

 

So far so good, but let's make one more funny step, we are going to invoke an enterprise variable type Activity by InvokeMehod activity.

The first step of this test is creating a new enterprise variable a3. The following picture shows this variable in the Repository:

Image 30

The next step is to modify our activity a2 for invoking the above variable. The following picture shows this change:

Image 31

The above picture also shows value of the InvokeMethod properties. Note, that the value of the enterprise variable is passed to the InvokeMethod via the Parameters.

OK, now the last step is to run the finally test. Back to the Variables and please select the variable a1 and Editor. Pressing button Run, the activity a1 will produce the following result:

Image 32

That's all, I hope this test shown you interesting features of the enterprise variables.

 

Conclusion

In conclusion, this article described a solution for enterprise variables centralized in the Runtime Repository in the WF4. Their usage in the workflow model is fully transparent like local variables. In addition, the Enterprise Variables give you a new dimension for your application on the Enterprise level such as managing variables on the runtime, sharing variables across the applications and environments, centralized runtime metadata and primitives, etc.    

 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)