by community-syndication | Aug 30, 2006 | BizTalk Community Blogs via Syndication
Ok, so this isn’t just limited to Windows Workflow. It is relevant to
any piece of code that will end up on the Visual Studio toolbox.
I am building a number of custom activities and I got tired of seeing the
standard cog graphic in the toolbar for each one. Within seconds all of my
project tasks were put on hold and changing that image was placed to the top of
my priority list. Little did I know that it would be 1 1/2 hours later
before I could set my gaze on all of those 16×16 pixel graphic images.
Why did it take so long? Well, it turns out that the code behind the
ToolboxBitmap attribute code attempts to dynamically create a path to point to
the embedded resource file. There are times when the path created does not
point to a valid location.
So when I first started down this path I followed the typical route.
This typical path is to add a graphic as a resource of the assembly with the
graphic marked as an embedded resource on the property page. Then add the
ToolboxBitmap attribute to your class providing the type of the object and the
location for the graphic. Compile and add your item to the toolbar and you
should see the entry along with your graphic image.
As I was researching this I found that there are two things that can mess
this process up. The first is to make sure that you know the fully
qualified path of graphic, including if the graphic is in a folder in the
solution and second is around the namespace of the assembly.
If the default namespace has been changed from the original settings you will
need to add that to the beginning of the string along with the the folder tree
scheme. Therefore, if I have my graphic image in a folder called Resources
and my default namespace is BlogActivity then my resource address is
BlogActivity.Resources.MyGraphic.png. A problem occurs if we change the
default namespace. Lets say that I change it to BlogActivityTest.
This change would have my resource address as
BlogActivityTest.Resources.MyGraphic.png. This in an of itself is not a
bad thing but in order for the ToolboxBitmap attribute to find the graphic the
attribute code needs to find them in the context of their own namespace.
There are two attribute constructors that are relevant to this (there are three
constructors but the third loads the graphic from a location on the file system
and that is not what we want). These two constructors are:
TooboxBitMap(typeof(<control>), "<assembly>.<resource>")
ToolboxBitMap(typeof(<control>))
The second constructor is interesting if you name the graphic the same name
as the control. If they are the same then the attribute code will match
the graphic with the resource automatically.
It is the first constructor that led me in circles. As it turns out
there is a little bug in the GetImageFromResource method. If you have a
namespace that is different from the assembly name (as I am sure happens in most
scenarios) then the GetImageFromResource ends up putting the assembly name in
front of the resource address. In my example, my resource address would
look something like BlogActivity.BlogActivity.Resources.MyGraphic.png and this
location does not exist.
Now that I understood what was happening I wanted to see what my Visual
Studio project thought the address was. Place the code below in the
constructor of your object. The output of this code will show you
the full path to your embedded resource that the runtime will use. This
output should match the string you are using for the second parameter.
This is only to loop through all of the embedded resource for debugging and is
not meant to remain in your code.
public
BlogItem()
{
InitializeComponent();
//Remove this code after debugging
string[] rn = this.GetType().Assembly.GetManifestResourceNames();
foreach (string
s in
rn)
{
System.Diagnostics.Trace.WriteLine(s);
}
}
After verifying and testing this path and things still don’t work then follow
these steps. We need to tinker with the internal dynamic path creation
mechanism.
First, create an internal class that is outside of the
root namespace. This class can be called whatever you want (in my example
I called it EmbeddedResourceFinder)
Next, use this class name in the ToolboxBitmap attribute
instead of your control name
Lastly, change the resource location argument to match
"<default namespace>.<resourcename>" in order to locate the resource.
The code will look like this:
using
System.Workflow.Activities;
//this internal class is needed to fool the
ToolboxBitMap attribute so that it can actually build the
//assembly/namespace string correctly for it to find
the resource.
internal
class
EmbeddedResourceFinder
{
}
namespace
BlogItem.Workflow.Activities
{
[ToolboxBitmap(typeof(EmbeddedResourceFinder),
"BlogActivity.MyGraphic.png")]
[Designer(typeof(PerformanceMonitorDesigner))]
[ActivityValidator(typeof(PerformanceMonitorValidator))]
public
partial class
BlogActivity : SequenceActivity
{
……….
Now that you are using the empty class and modified attribute you will find
that the next time you add your control to the toolbox you will see your
picture.
by community-syndication | Aug 29, 2006 | BizTalk Community Blogs via Syndication
Here are some of the changes to BRE in BizTalk 2006.
Summary:
Support for generic types and generic methods
Support for nullable types 2
Accessing nested members of a class 4
Type Casting support 5
Invoking Static Members of a Class 6
Overriding registry key setting with application configuration file.
GetDeploymentDriver method added to Configuration class
Clear method added to the Policy class
IFactRemover interface added.
SqlTimeOut registry key added.
Arithmetic and logical operators support double promotion.
Attachment: Word document with the following information
The rule engine supports using specialized generic types and specialized generic methods in a rule. It does not support using generic types and generic methods themselves in a rule. For example, in a business rule you can use List<int>, but not List<T> (from the System.Collections.Generic namespace in the .NET class library). Currently, the Business Rule Composer tool does not support creating rules by using specialized generic types and specialized generic methods. You must create the rules programmatically by using the rule engine object model. The following sample code demonstrates how to use the List generic class in a business rule:
// Create the condition list IF 1 == 1
Equal eq = new Equal(new Constant(1), new Constant(1));
// Create the action list
ActionCollection ac = new ActionCollection();
//Create class binding and class member bindings
ClassBinding lstClass = new ClassBinding(typeof(System.Collections.Generic.List<int>));
ArgumentCollection argc = new ArgumentCollection();
argc.Add(new Constant(3));
ClassMemberBinding add = new ClassMemberBinding(“Add”, lstClass, argc);
// Wrapping the .NET binding as a user function
UserFunction uf = new UserFunction(add);
ac.Add(uf);
// Create the rule
Rule rl = new Rule(“AddToList”, eq, ac);
// Create the policy
RuleSet rs = new RuleSet(“GenericTest”);
rs.Rules.Add(rl);
// Create the .NET List object
List<int> lst = new List<int>();
lst.Add(1);
lst.Add(2);
// Print the list before executing the policy
Console.WriteLine(“Contents of the lists before executing the policy”);
foreach (int i in lst)
Console.WriteLine(i);
PolicyTester pt = new PolicyTester(rs);
pt.Execute(lst);
// Print the list after executing the policy
Console.WriteLine(“Contents of the lists before executing the policy”);
foreach (int i in lst)
Console.WriteLine(i);
The rule engine supports using nullable types in a business rule. You can use nullable types in .NET class bindings, XML bindings, and database bindings. Currently, the Business Rule Composer tool does not support using nullable types in a business rule. You can use the nullable types when creating rules programmatically.
You can create a class member binding to a property or a field whose type is a nullable type. You can also create a class member binding to a method that takes a parameter of nullable type and/or returns a value of nullable type. The following sample code demonstrates how to access a nullable field, and how to access a return value of nullable type from a method in a business rule. If you execute a console application with the following code as it is, you will see that the value of the prop field is set to 5. If you do not initialize the prop field in the class or initialize it to null and run the code, you will see that the value of the prop field is set to 1.
using Microsoft.RuleEngine;
namespace UseNullableAsm
{
class Program
{
public class Class1
{
public int? prop = 1;
private int? prop2 = 4;
public int? GetProp2()
{
return prop2;
}
}
static void Main(string[] args)
{
//Create the class binding for the Class1 class
ClassBinding cbCls1 = new ClassBinding(typeof(Class1));
//Create the class member binding to the GetProp2 method of Cls1 class
ClassMemberBinding cmGetProp2 = new ClassMemberBinding(“GetProp2”, cbCls1);
//Create the class member binding to the to GET the value of prop
ClassMemberBinding cmGetProp = new ClassMemberBinding(“prop”, cbCls1);
//Create arguments for the prop1 field, which is prop1 + GetProp2()
UserFunction ufGetProp = new UserFunction(cmGetProp);
Add addArg = new Add(ufGetProp, new UserFunction(cmGetProp2));
ArgumentCollection al1 = new ArgumentCollection();
al1.Add(addArg);
//Set the value of prop to prop1 + cmGetPro2()
ClassMemberBinding cmProp = new ClassMemberBinding(“prop”, cbCls1, al1);
//Create a userfunction based on cmProp and add to the action collection
UserFunction ufProp = new UserFunction(cmProp);
ActionCollection ac = new ActionCollection();
ac.Add(ufProp);
//Create the condition list IF prop == 1
Equal eq = new Equal(ufGetProp, new Constant(1));
//Create the rule
// If (prop == 1)
// Then prop = prop + GetProp2()
Rule rl = new Rule(“NullableTestRule”, eq, ac);
//Create the condition list IF prop != 1
NotEqual neq = new NotEqual(ufGetProp, new Constant(1));
//Set the value of prop to prop to 1
Constant ct = new Constant(1);
ArgumentCollection al2 = new ArgumentCollection();
al2.Add(ct);
//Create class member binding to prop field with argument value 1
ClassMemberBinding cmSetProp = new ClassMemberBinding(“prop”, cbCls1, al2);
//Create a userfunction based on cmSetProp and add to the action collection
UserFunction ufSetProp = new UserFunction(cmSetProp);
ActionCollection ac2 = new ActionCollection();
ac2.Add(ufSetProp);
//Create the second rule
// If (prop != 1)
// Then prop = 1
Rule rl2 = new Rule(“NullableTestRule2”, neq, ac2);
//Create the policy and add both the rules to the policy
RuleSet rs = new RuleSet(“NulableTestPolicy”);
rs.Rules.Add(rl);
rs.Rules.Add(rl2);
//Create the .NET object fact
Class1 cls1Obj = new Class1();
//Print the value of the field prop before executing the policy
Console.WriteLine(“Value of the prop field is ” + cls1Obj.prop);
//Execute the policy
PolicyTester pt = new PolicyTester(rs);
pt.Execute(cls1Obj);
//Print the value of the field prop after executing the policy
Console.WriteLine(“Value of the prop field is ” + cls1Obj.prop);
}
}
}
You can also use nullable types in database bindings. The following sample code fragment shows you how to use a nullable type in database bindings.
DataColumnBinding dcBinding = new DataColumnBinding(“col”, typeof(int?), dbBinding);
Similarly, you can use nullable types in XML bindings. The following sample code fragment shows how to use a nullable type in XML bindings.
XMLDocumentFieldBinding xfb1 = new XMLDocumentFieldBinding(typeof(int?),”ID”,xdb);
The rule engine allows you to use a nested property or method of an object in a rule. For example, suppose you have a class named AClass, which has a property named B of type BClass, which has a field named C. The rule engine allows you to build rules accessing the field C by using the A.B.C syntax. However, it is possible to use this syntax only when building the rules programmatically, not when using the Business Rule Composer tool. The following sample code demonstrates how to use a property of an object, which is a property of another object:
// Create the condition list IF 1 == 1
Equal eq = new Equal(new Constant(1), new Constant(1));
// Create the action collection
ActionCollection ac = new ActionCollection();
// Create class binding and class member binding to cField
// Set the value of cField to “Changed”
Constant chg = new Constant(“Changed”);
ArgumentCollection argCol = new ArgumentCollection();
argCol.Add(chg);
ClassBinding lstClass = new ClassBinding(typeof(AClass));
ClassMemberBinding bBinding = new ClassMemberBinding(“bObj”, lstClass);
ClassMemberBinding CBinding = new ClassMemberBinding(“cField”, bBinding,argCol);
UserFunction uf_C = new UserFunction(CBinding);
ac.Add(uf_C);
// Create the rule
Rule rl = new Rule(“ChangeCField”, eq, ac);
// Create the policy
RuleSet rs = new RuleSet(“NestedNodeTest”);
rs.Rules.Add(rl);
//Create the facts
AClass aObj = new AClass();
Console.WriteLine(“The value of aObj.bObj.cField BEFORE executing the policy”);
Console.WriteLine(aObj.bObj.cField);
//Execute the policy
PolicyTester tester = new PolicyTester(rs);
tester.Execute(aObj);
Console.WriteLine(“The value of aObj.bObj.cField AFTER executing the policy”);
Console.WriteLine(aObj.bObj.cField);
You can use the Cast method of the ClassMemberBinding class to convert an object of one type to an object of another compatible type. Currently, the Business Rule Composer tool does not support creating rules by using the Cast method. You must create the rules programmatically by using the rule engine object model to take advantage of this feature. The following sample code demonstrates how to use the Cast method to convert an instance of the System.Object class to an instance of the Cls2 class.
using Microsoft.RuleEngine;
namespace RuleTypeCasting
{
class Cls1
{
//Note that return type is ‘object’, not Cls2
public object GetCls2Obj()
{
return new Cls2();
}
}
class Cls2
{
public void Log()
{
Console.WriteLine(“In Cls2.Log method”);
}
}
class Program
{
static void Main(string[] args)
{
//Create the condition list IF 1 == 1
Equal eq = new Equal(new Constant(1), new Constant(1));
//Create the action collection
ActionCollection ac = new ActionCollection();
//Create the class binding for the Cls1 class
ClassBinding cbCls1 = new ClassBinding(typeof(Cls1));
//Create the class member binding for the GetCls2Obj method in the Cls1 class
ClassMemberBinding cmGetCls2Obj = new ClassMemberBinding(“GetCls2Obj”, cbCls1);
//Type casting the return value of GetCls2Obj method (object) to Cls2 type
cmGetCls2Obj.Cast(typeof(Cls2));
//Create the class member binding to the Log method of Cls2 type
ClassMemberBinding cmLog = new ClassMemberBinding(“Log”, cmGetCls2Obj);
//Create a user function based on cmLog and add it to the action collection
UserFunction ufLog = new UserFunction(cmLog);
ac.Add(ufLog);
// Create the rule
Rule rl = new Rule(“InvokeLogRule”, eq, ac);
// Create the rule set or policy
RuleSet rs = new RuleSet(“InvokeLogPolicy”);
rs.Rules.Add(rl);
//Create the facts
Cls1 Cls1Obj = new Cls1();
//Execute the policy
PolicyTester tester = new PolicyTester(rs);
tester.Execute(Cls1Obj);
}
}
}
By default, the rule engine requires you to assert an instance of a .NET class to execute a policy that invokes a static member of the .NET class. You can modify this behavior by changing the value of the StaticSupport registry key under HKEY_LOCAL_MACHINE\Software\Microsoft\BusinessRules\3.0 to one of the values in the following table.
StaticSupport registry value |
Rule engine behavior |
0 |
Default value. The rule engine follows the BizTalk Server 2004 model, where the static method is called only when an instance of the .NET class is asserted. |
1 |
An object instance is not required. The static method is called when the rule is evaluated or executed. |
2 |
An object instance is not required. The static method is called at the policy translation time if all parameters are constant. This is a performance optimization because the static method is called only once even though it is used in multiple rules in conditions. Note that static methods used as actions will not be executed at the translation time, but static methods used as parameters may be executed. |
If you do not see the StaticSupport registry key under HKEY_LOCAL_MACHINE\Software\Microsoft\BusinessRules\3.0, you should add it by performing the following steps.
To add the StaticSupport registry key
1. Click Start; click Run, type RegEdit, and then click OK.
2. Expand HKEY_LOCAL_MACHINE, expand Software, expand Microsoft, expand BusinessRules, and then select 3.0.
3. In the right pane, right-click, point to New, and then click DWORD value.
4. For Name, type StaticSupport.
If the StaticSupport registry key already exists, and you need to change its value, perform the following steps.
To change the value of the StaticSupport registry key
1. Click Start, click Run, type RegEdit, and then click OK.
2. Expand HKEY_LOCAL_MACHINE, expand Software, expand Microsoft, expand BusinessRules, and then expand 3.0.
3. Double-click the StaticSupport registry key, or right-click it and then click Modify.
Overriding registry key setting with application configuration file
The registry entries described in the topic “Rule Engine Configuration and Tuning Parameters” on MSDN ( http://msdn.microsoft.com/library/default.asp?url=/library/en-us/sdk/htm/ebiz_prog_rules_yhjc.asp ) can be overriden by using an application configuration file.
The registry settings are global for all applications that host a rule engine instance. You can override these registry settings at an application level by using the application configuration file. For BizTalk Server applications, the host application is the BTSNTSvc.exe and the configuration file is the BTSNTSvc.exe.config, which you can find in the BizTalk Server installation directory. You need to specify the values for the configuration parameters that you want to override in the application configuration file as show below:
<configuration>
<configSections>
<section name=”Microsoft.RuleEngine” type=”System.Configuration.SingleTagSectionHandler” />
</configSections>
<Microsoft.RuleEngine
UpdateServiceHost=”localhost”
UpdateServicePort=”3132″
UpdateServiceName=”RemoteUpdateService”
CacheEntries=”32″
CacheTimeout=”3600″
PollingInterval=”60″
TranslationTimeout=”3600″
CachePruneInterval=”60″
DatabaseServer=”(localhost)”
DatabaseName=”BizTalkRuleEngineDb”
SqlTimeout=”-1″
StaticSupport=”1″
/>
</configuration>
You can deploy policies programmatically by using the RuleSetDeploymentDriver class in the Microsoft.RuleEngine.RuleEngineExtensions namespace. The following sample code demonstrates how to use the RuleSetDeploymentDriver class to deploy a policy named LoanProcessing:
string policyName = “LoanProcessing”;
int majorRev = Convert.ToInt16(args[1]);
int minorRev = Convert.ToInt16(args[2]);
RuleSetInfo rsi = new RuleSetInfo(policyName,majorRev,minorRev);
Microsoft.BizTalk.RuleEngineExtensions.RuleSetDeploymentDriver dd;
dd = new Microsoft.BizTalk.RuleEngineExtensions.RuleSetDeploymentDriver();
dd.Deploy(rs);
The overloaded constructors of the RuleSetDeploymentDriver class take the names of the rule store database as a parameter. This allows you to deploy policies to a database that your BizTalk Server environment is not configured to use.
If you are deploying policies to the database that your BizTalk Server environment is configured to use, you do not have to create the RuleSetDeploymentDriver object in the code. Instead, you can request the rule engine to create a RuleSetDeploymentDriver object for you by invoking the GetDeploymentDriver method of the Configuration class in the System.RuleEngine namespace. The following sample code demonstrates how to invoke the GetDeploymentDriver method:
Microsoft.BizTalk.RuleEngineExtensions.RuleSetDeploymentDriver dd;
dd = new Microsoft.RuleEngine.Configuration.GetDeploymentDriver();
The GetDeploymentDriver method retrieves the values of the DeploymentDriverAssembly and DeploymentDriverClass registry keys under HKEY_LOCAL_MACHINE\Software\Microsoft\BusinessRules\3.0, and creates an instance of DeploymentDriverClass. The following table shows the default values of these two registry keys.
Registry key |
Value |
DeploymentDriverAssembly |
Microsoft.BizTalk.RuleEngineExtensions |
DeploymentDriverClass |
Microsoft.BizTalk.RuleEngineExtensions.RuleSetDeploymentDriver |
The RuleSetDeploymentDriver class implements the IRuleSetDeploymentDriver interface. You can develop your own policy deployment driver by creating a class that implements the IRuleSetDeploymentDriver interface and change the values for the registry keys described above as appropriate.
Clear method is added to the Policy class. Here are the important methods of the Policy class and their descriptions.
Method |
Description |
Execute |
Adds the specified short-term facts into the rule engine’s working memory and executes the policy using Match-Conflict Resolution-Action algorithm. For more information on Match-Conflict Resolution-Action algorithm, see Condition Evaluation and Action Execution . |
Dispose |
Releases the resources used by the rule engine for executing the policy. |
Clear |
Clears or resets the working memory and the agenda of the rule engine instance created for executing the policy. |
You can optionally implement the IFactRemover interface on a fact retriever component. The rule engine invokes the UpdateFactsAfterExecution method of the IFactRemover interface when the policy is disposed. This provides an opportunity to you to do any post-execution work such as committing any database changes or retracting any object instances from the rule engine’s working memory.
SqlTimeOut Registry key is added under HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\BusinessRules\3.0.
Name |
Description |
Default Value |
SqlTimeout |
Timeout value for SQL commands that access the SQL rule store. The value for this key is interpreted as follows:
< 0 – Uses the .NET default value (30 seconds)
= 0 – Unlimited timeout
> 0 – Maximum time for a query before it times out |
-1 |
Arithmetic operators such as addition, subtraction, multiplication, and division automatically promote a smaller operand type to a larger operand type if the types are different. The rule engine also supports double promotion if both the operands can be promoted to a common type.
For logical operators, when operands are of different types, the rule engine converts type of one of the parameters to match the type of the other parameter or converts types of both the parameters to a common type before evaluating the expression.