Neil Thompson

BizTalk | .NET | SQL |

  Home  |   Contact  |   Syndication    |   Login
  15 Posts | 20 Stories | 56 Comments | 55 Trackbacks

News

Article Categories

Archives

Post Categories

Fav BizTalk Blogs

This is pretty basic stuff, but it is a really common mistake.  Using .NET classes in the BTS RuleEngine (RE) is pretty simple until you actually try to run them.  The problem always seem to come down to creating an instance of the class that you are referencing in the Vocabulary/ Function etc.

When Testing in the Composer

If you're in the composer things are a little tricky.  You will need to create a “Fact Creator” to provide your testing process with an instance of the classes you need.  The implementation is simple enough once you get the hang of it, but it is not easily discoverable by trial and error.  First I'll give you the main outline, then I'll give you an example of a Fact creator

  1. Create the helper class with all the helper functions you need (Date comparison operations are a common case here for me)
  2. Use the Rules Composer to reference the properties/methods of the class that you will need to use in the Rules (I like to add them to the Vocabulary rather than predicates or functions)
  3. Create a fact retriever (i.e. copy and paste code you find and tweak it to return an instance of the helper class you created in step 1) [Also make sure you have a referece to the Microsoft.RulesEngine Assembly which you can find in the BizTalk installation folder]
  4. Install the Fact Retriever and the helper class into the GAC
  5. Click on the policy version, then click test
  6. Specify the xml instance file from the file system (careful, the engine can modify its contents)
  7. Specify the fact retriever you just created/installed
  8. click test and hope for the best

When Calling from Orchestration

Calling from an orchestration is a little different. The fact creator will do nothing to help you here (it is only for composer work).  You MUST create an instance of your helper class and pass it down to the rules engine in the Call Rules shape.

Caution: The Call Rules shape is not strict about the “signature“ for the rules engine call. It will allow you to omit the class instance and leave you wondering why your rules never fire.

  1. Open up the orchestration that is calling the rules
  2. Create a reference to the assembly that has your helper class
  3. Create an instance variable for your helper class in the variables of the orchestration
  4. Select that variable in the Call Rules shape.

 

Well, that's all for now. Hopefully this will help fill in the blanks that others have left.

___________________________________________________

Below is a sample fact creator

 

For this example to work you will need to create a reference to the Microsoft.RulesEngine assembly. Look for it in the BizTalk installation directory

using System;
using Microsoft.RuleEngine;
using  <Custom Assembly reference here,to the assembly with the helper class>

namespace BTSRulesEngineFactCreator

{

[Serializable]
public class MyFactCreator : IFactCreator

{

private object[] myFacts;
public MyFactCreator()

{

}

public object[] CreateFacts ( RuleSetInfo rulesetInfo )

{

myFacts = new object[1];

//This is where you add instance(s) of your helper class(es) to the myFacts Array

myFacts.SetValue(new VinProofFacts(),0);

return myFacts;

}

public Type[] GetFactTypes (RuleSetInfo rulesetInfo)

{

return null;

}

}

}

 

 

  • Share This Post:
  • Share on Twitter
  • Share on Facebook
  • Share on Technorati
posted on Wednesday, August 03, 2005 11:10 AM

Feedback

# re: Integrating .NET classes with the BizTalk Server 2004 Rules engine 1/23/2006 10:56 AM Muthu
Hi,

I tried calling rules from an Orchetration following the guidelines mentioned in your blog, for some reason the rules never gets fired.

If call my rules from Policy Tester and from a .NET Assembly it works fine.

Any sample Orchestration will help.




# re: Integrating .NET classes with the BizTalk Server 2004 Rules engine 1/23/2006 11:01 AM nsthompson
Are you sure that you are creating an instance of your .net class in your orchestration AND passing it down to the rules in the Call Rules shape?

# re: Integrating .NET classes with the BizTalk Server 2004 Rules engine 9/20/2006 9:35 AM Neo
I am having the below senario

The currentmileage is source from xml schema fact

If currentmileage is between 0 and 5000 Then
customer.set_NextIntervdue(5000)
Update (customer)

The customer.set_NextIntervdue is a dotnet componet setting value.
once the value is assigned I am trying to access the value in the next rule like


if customer.get_NextIntervdue is equal to 5000 Then
Reminder="5k"

The reminder is xml fact.

This works fine if I am having single node in the xml fact.

If I am having multiple nodes this conditions the action is wrong.
It's the the Last conditions value is updated to the all other nodes to the source
xml fact.

Please help me to resolve this.

Thanks in advance.

Neo

# re: Integrating .NET classes with the BizTalk Server 2004 Rules engine 9/20/2006 10:11 AM nsthompson
I'm not entirely sure I understand the scenario that you are describing. It sounds like you have a problem with the multiple node scenario but without seeing the xml structure I can't offer you meaningful advice.

Please post a sample of an xml instance that works, and another example of an xml instance that does not work.

# re: Integrating .NET classes with the BizTalk Server 2004 Rules engine 9/20/2006 10:42 AM Neo
Source schema for single customer element

<Customers>
<Customer>
<Name>Glavin</Name>
<DeliveryDate>3/1/2004</DeliveryDate>
<MileageatDelivery>30</MileageatDelivery>
<PriorRODate>3/1/2005</PriorRODate>
<PriorROMileage>16000</PriorROMileage>
<MostRecentRODate>8/1/2005</MostRecentRODate>
<MostRecentROMileage>21000</MostRecentROMileage>
<DaysSinceLastTwoKnownMileagePoints>150</DaysSinceLastTwoKnownMileagePoints>
<MileageDrivenSinceLastMileageReprot>5000</MileageDrivenSinceLastMileageReprot>
<AverageMilesDriverPerDay>33.3</AverageMilesDriverPerDay>
<DaysSinceLastKnownMileagePoint>76.0</DaysSinceLastKnownMileagePoint>
<EstimatedCurrentMileage>23533</EstimatedCurrentMileage>
<NextIntervalDue></NextIntervalDue>
<MilesUntilNextDueInterval>1467</MilesUntilNextDueInterval>
<DaysUntilNextDueInterval>44</DaysUntilNextDueInterval>
<NextServiceDueDate>11/30/2005</NextServiceDueDate>
<ServiceReminderProcesswindow>11/2/2005</ServiceReminderProcesswindow>
<ReminderType></ReminderType>
</Customer>
</ns0:Customers>

Destination

<Customers>
<Customer>
<Name>Glavin</Name>
<DeliveryDate>3/1/2004</DeliveryDate>
<MileageatDelivery>30</MileageatDelivery>
<PriorRODate>3/1/2005</PriorRODate>
<PriorROMileage>16000</PriorROMileage>
<MostRecentRODate>8/1/2005</MostRecentRODate>
<MostRecentROMileage>21000</MostRecentROMileage>
<DaysSinceLastTwoKnownMileagePoints>150</DaysSinceLastTwoKnownMileagePoints>
<MileageDrivenSinceLastMileageReprot>5000</MileageDrivenSinceLastMileageReprot>
<AverageMilesDriverPerDay>33.3</AverageMilesDriverPerDay>
<DaysSinceLastKnownMileagePoint>76.0</DaysSinceLastKnownMileagePoint>
<EstimatedCurrentMileage>23533</EstimatedCurrentMileage>
<NextIntervalDue>25k</NextIntervalDue>
<MilesUntilNextDueInterval>1467</MilesUntilNextDueInterval>
<DaysUntilNextDueInterval>44</DaysUntilNextDueInterval>
<NextServiceDueDate>11/30/2005</NextServiceDueDate>
<ServiceReminderProcesswindow>11/2/2005</ServiceReminderProcesswindow>
<ReminderType>25K</ReminderType>
</Customer>
</ns0:Customers>

# re: Integrating .NET classes with the BizTalk Server 2004 Rules engine 9/20/2006 10:52 AM Neo
I have using the dotnet code as given below
public class Customer
{
private string customertype;

public string CustomerType
{
get
{
return customertype;
}
set
{
customertype = value;
}
}
}

# re: Integrating .NET classes with the BizTalk Server 2004 Rules engine 9/20/2006 11:20 AM nsthompson
It looks as though it is always returning the estimatedmileage from the same instance of customer, as such, every customer will ge the same reminder type.

I would look at your .net class to see if it is just returning the *first* item in a list of values when it reads the mileage off the xmlsheet.


Enumerating over multiple records in a single rules call is something that i tend to avoid as a rule. If i were in your position (and if performance allows) i would try to call the rules in a loop that always passes down a single customer.

If that is not feasible then i would take a long hard look at your xpath and see what values it pulls back in the multiple customer case. I would also look at the .net class/fact creator to be sure it is not just returning the first value in a list of values.

# re: Integrating .NET classes with the BizTalk Server 2004 Rules engine 9/20/2006 11:33 AM Neo
Single customer can't be passed the reason is we have millions of customer to apply this rules.

Is it ok if I use policy chaining, for this senario?

# re: Integrating .NET classes with the BizTalk Server 2004 Rules engine 9/20/2006 11:37 AM nsthompson
How do you plan to use policy chaining to fix your situation?


# re: Integrating .NET classes with the BizTalk Server 2004 Rules engine 9/20/2006 11:53 AM Neo
in first policy I will update the NextIntervalDue in the xml document
I will call second policy that access NextInterdue from the xml instance
and update ReminderType

# re: Integrating .NET classes with the BizTalk Server 2004 Rules engine 9/21/2006 2:57 PM David
Hi,

Can you please provide a sample for this example

Rule 1
If A.amount > $0
Then B.total = B.total + A.amount
Rule 2
If B.total > $50 & < $100
Then B.discount = 10%
Rule 3
If B.total >= $100
Then B.discount = 15%

(
B.total = B.total + A.amount
B.discount = 10%
)

Thanks
David

# re: Integrating .NET classes with the BizTalk Server 2004 Rules engine 9/26/2006 9:12 AM vinod
I am very new to buisness rule engine. I am trying to do this using biztalk
rules engine. Can you please help me how to do this.

RULE1
-----

if age is range between 50 to 100 then
Action Score=4
if age is range between 40 to 50 then
Action Score=3
if age is range between 30 to 40 then
Action Score=2
if age is range between 20 to 30 then
Action Score=1


RULE2
-----
if AnnualIncome is range between 500000 to 1000000 then
Action Score=Score+4
if AnnualIncome is range between 400000 to 500000 then
Action Score=Score+3
if AnnualIncome is range between 300000 to 400000 then
Action Score=Score+2
if AnnualIncome is range between 200000 to 300000 then
Action Score=Score+1

RULE3
-----

if NumTimesBankrup is range between 0 to 1 then
Action Score=Score+4
if NumTimesBankrup is range between 1 to 2 then
Action Score=Score+3
if NumTimesBankrup is range between 3 to 4 then
Action Score=Score+2
if NumTimesBankrup is range between 5 to 6 then
Action Score=Score+1



XML Instance
-------------

<ns0:ApplicantDetails>
<ns0:ApplicationID>10</ns0:ApplicationID>
<ns0:Name>John Dangerlover</ns0:Name>
<ns0:Age>99</ns0:Age>
<ns0:CurrentHealth>Poor</ns0:CurrentHealth>
<ns0:NumTimesBankrupt>0</ns0:NumTimesBankrupt>
<ns0:AnnualIncome>1000</ns0:AnnualIncome>
<ns0:AnnualExpenditure>10000</ns0:AnnualExpenditure>
<ns0:OwnProperty>false</ns0:OwnProperty>
<ns0:Score></Score>
</ns0:FinancialDetails>
</ns0:ApplicantDetails>



# re: Integrating .NET classes with the BizTalk Server 2004 Rules engine 9/26/2006 9:43 AM nsthompson
vinod send me the xsd schema that defnes your xml instance document (including any namespaces) and I'll see what I can do to help.

# re: Integrating .NET classes with the BizTalk Server 2004 Rules engine 9/26/2006 9:48 AM nsthompson
Also, vinod, if you have never used the business rules engine before, please go to http://msdn.microsoft.com/virtuallabs/biztalk/ and run through the lab for Business Rules integration. It is technically for BizTalk 2006 but it is substantially identical to BizTalk 2004. That may give you all the info you need. If you complete that and are still unsure what to do, send me the xsd and we'll see what we can do.

# re: Integrating .NET classes with the BizTalk Server 2004 Rules engine 9/26/2006 10:07 AM nsthompson
Another good resource for general BRE info is http://blogs.msdn.com/biztalkbre/default.aspx

# re: Integrating .NET classes with the BizTalk Server 2004 Rules engine 9/26/2006 10:38 AM vinod
Thanks for giving me the site.
I gone through that application but i found
method.
Update(StatusObj)
but he is not again using this method for processing next business rule.

my concern more is like if passed multiple records in the xml file the result of outcome that is the score value may be wrong because I m not sure whether biztalk apply rules fetching record by record or one time itself on all records.

if it applies one time on records then how it going to keep track of the value score for each record.
I am assuming the value may come wrong.

# re: Integrating .NET classes with the BizTalk Server 2004 Rules engine 9/26/2006 11:52 AM nsthompson
Ok a couple of cleanup items before I start the answer:

1) I don't like using byte so I change the schemas to be ints or floats for simplicity
2) You have rules with multiple possible output values for Score. Each possible output value (i.e. 4,5,0 etc) means that you should have a separate rule in my opinion. What you have expressed as "Rule 1" is actually 4 different rules in the policy
3) I believe that ranges are inclusive so you should not be using 40-50, then 50-60. You should be using 40-49,50-59 etc. Otherwise you risk having two rules fire when you only want one.


Now my attempt at answering your issue. I did a quick lab experiment on this and it seemed to work fine. The score was incremented as expected and I tried it with multiple records and it kept them separate as desired.

1) define standard vocab to read your age,numBankrupt etc feilds
2) define also a set element for Score (GetScore)
3) define a get element for Score (SetScore)
4) Set the priority on your rules to order when they will fire (actually you don't strictly have to do this because in your case you are only adding values so it doesn't matter the order)
5) In your action element set the score using the add function with something like this
Score(ScoreRead + <some number here>)

I can't spend to much time on this but I think it may do the trick for you. Try not to think of it as passing data from one rule to the next as this is not OOP, or even procedural. The forward inferencing rules engine is a wierd model. You may want to look into the Fact Asserting and Retracting behaviours to gain insight as to how data gets shared among rules.


The policy xml file is as follows ( I will try to post the files later from home as i can not transmit files presently)

<?xml version="1.0" encoding="utf-8"?>
<brl xmlns="http://schemas.microsoft.com/businessruleslanguage/2002">
<vocabulary id="c89c74f6-0b7d-4ac4-be4a-91469f89fa43" name="vinod" uri="" description="">
<version major="1" minor="0" description="" modifiedby="MAPLE-5LFF4EB3R\Administrator" date="2006-09-26T14:23:17.9639104-04:00" />
<vocabularydefinition id="edd6b042-11a4-4c09-8ed8-e370164391a0" name="Score" description="">
<bindingdefinition>
<documentelementbindingdefinition field="*[local-name()='Score' and namespace-uri()='']" fieldalias="Score" type="int">
<documentinfo schema="C:\Project\BRE\BRE\ApplicantDetails.xsd" documenttype="BRE.ApplicantDetails" selector="/*[local-name()='ApplicantDetails' and namespace-uri()='']/*[local-name()='personaldetails' and namespace-uri()='']" selectoralias="/*[local-name()='ApplicantDetails' and namespace-uri()='']/*[local-name()='personaldetails' and namespace-uri()='']" instance="0" />
<argument position="0">
<valuedefinitionliteral type="int">
<int>0</int>
</valuedefinitionliteral>
</argument>
</documentelementbindingdefinition>
</bindingdefinition>
<formatstring language="en-US" string="Score {0}" delimiter="{[0-9]+}">
<argument position="0">
<valuedefinitionliteral type="int">
<int>0</int>
</valuedefinitionliteral>
</argument>
</formatstring>
</vocabularydefinition>
<vocabularydefinition id="517d5df2-2ff0-46fc-a14b-26a9500f5a5b" name="ScoreRead" description="">
<bindingdefinition>
<documentelementbindingdefinition field="*[local-name()='Score' and namespace-uri()='']" fieldalias="Score" type="int">
<documentinfo schema="C:\Project\BRE\BRE\ApplicantDetails.xsd" documenttype="BRE.ApplicantDetails" selector="/*[local-name()='ApplicantDetails' and namespace-uri()='']/*[local-name()='personaldetails' and namespace-uri()='']" selectoralias="/*[local-name()='ApplicantDetails' and namespace-uri()='']/*[local-name()='personaldetails' and namespace-uri()='']" instance="0" />
</documentelementbindingdefinition>
</bindingdefinition>
<formatstring language="en-US" string="ScoreRead" />
</vocabularydefinition>
<vocabularydefinition id="2fcb2ab7-32bf-4d59-ab3b-7d90c427d4ae" name="NumTimesBankrupt" description="">
<bindingdefinition>
<documentelementbindingdefinition field="*[local-name()='NumTimesBankrupt' and namespace-uri()='']" fieldalias="NumTimesBankrupt" type="int">
<documentinfo schema="C:\Project\BRE\BRE\ApplicantDetails.xsd" documenttype="BRE.ApplicantDetails" selector="/*[local-name()='ApplicantDetails' and namespace-uri()='']/*[local-name()='personaldetails' and namespace-uri()='']" selectoralias="/*[local-name()='ApplicantDetails' and namespace-uri()='']/*[local-name()='personaldetails' and namespace-uri()='']" instance="0" />
</documentelementbindingdefinition>
</bindingdefinition>
<formatstring language="en-US" string="NumTimesBankrupt" />
</vocabularydefinition>
<vocabularydefinition id="c2146256-fefd-42e7-8e84-f1e6ffa758c6" name="age" description="">
<bindingdefinition>
<documentelementbindingdefinition field="*[local-name()='Age' and namespace-uri()='']" fieldalias="Age" type="int">
<documentinfo schema="C:\Project\BRE\BRE\ApplicantDetails.xsd" documenttype="BRE.ApplicantDetails" selector="/*[local-name()='ApplicantDetails' and namespace-uri()='']/*[local-name()='personaldetails' and namespace-uri()='']" selectoralias="/*[local-name()='ApplicantDetails' and namespace-uri()='']/*[local-name()='personaldetails' and namespace-uri()='']" instance="0" />
</documentelementbindingdefinition>
</bindingdefinition>
<formatstring language="en-US" string="age" />
</vocabularydefinition>
<vocabularydefinition id="a96c24d1-ea58-41ef-8ade-283364c3ffda" name="annualIncome" description="">
<bindingdefinition>
<documentelementbindingdefinition field="*[local-name()='AnnualIncome' and namespace-uri()='']" fieldalias="AnnualIncome" type="ulong">
<documentinfo schema="C:\Project\BRE\BRE\ApplicantDetails.xsd" documenttype="BRE.ApplicantDetails" selector="/*[local-name()='ApplicantDetails' and namespace-uri()='']/*[local-name()='personaldetails' and namespace-uri()='']" selectoralias="/*[local-name()='ApplicantDetails' and namespace-uri()='']/*[local-name()='personaldetails' and namespace-uri()='']" instance="0" />
</documentelementbindingdefinition>
</bindingdefinition>
<formatstring language="en-US" string="annualIncome" />
</vocabularydefinition>
</vocabulary>
</brl>

# re: Integrating .NET classes with the BizTalk Server 2004 Rules engine 9/26/2006 2:13 PM vinod
Hi,
As per your guide lines I tried to create .NET component with the following code

namespace EmployeeScore
{
public class EmpScore
{
private int customerscore;

public int CustomerScore
{
get
{
return customerscore;
}
set
{
customerscore = value;
}
}

public int AddScore(int score)
{
customerscore= CustomerScore + score;
return customerscore;
}
}

it's working fine for single node in the xml file. if i am testing with multiple nodes the results is wrong

# re: Integrating .NET classes with the BizTalk Server 2004 Rules engine 9/26/2006 3:34 PM nsthompson
Hold on a sec there. The class definition came from Neo and it doesn't appear to be correct.

Using only the xmlfact types and standard functions I implemented a sample of your rules (with different numbers) and I got it to sequence, tally score, and work on multiple instances/records no problem

Go to http://www.filedropr.com/download/MjU3
to download the sample and try it out. You should be able to follow this pattern (and not use the .net class). Please take some time to import those items into the rules engine, test from within the rules engine, and see the results.

Unless I'm missing something (which is always possible) this sample should give you everything you need.

# re: Integrating .NET classes with the BizTalk Server 2004 Rules engine 9/27/2006 7:35 AM vinod
Hi Neil,

Thank you so much for giving me the solution and sparing time for me.
I got excatly the correct result using your results.
I really appreciate your patience and guiding me.


# re: Integrating .NET classes with the BizTalk Server 2004 Rules engine 9/27/2006 2:20 PM vinod
Hi Neil,

I need once again your attention.
Sorry to trouble you.
I followed the same steps how you done previous example like

1) define standard vocab to read your age,Agestatus,Agestatusread etc feilds
2) define also a set element for Agestatus (GetAge)
3) define a get element for Agestatus (Agestatus)


I am trying to execute a buisness rule based on a small condition

Policy

Rule1

if Age is between 50 and 90 then

Action Agestatus="SeniorCitizens"


Rule2

if Agestatus=SeniorCitizens then

Action policy="Pension"


I am trying do this using the below given schema

When I am trying to test in rules engine I found that Rule 2 is firing first even though I set the proirity to execute Rule 1. I didn't undestood,why it's firing Rule 2 first. Could you please let me know where I am doing wrong.

<?xml version="1.0" encoding="utf-16"?>
<xs:schema xmlns:b="2003" xmlns="Schema1" targetNamespace="Schema1" xmlns:xs="XMLSchema">
<xs:element name="Employees">
<xs:complexType>
<xs:sequence>
<xs:element name="Employee">
<xs:complexType>
<xs:sequence>
<xs:element name="Name" type="xs:string" />
<xs:element name="Age" type="xs:integer" />
<xs:element name="AgeStatus" type="xs:string" />
<xs:element name="Policy" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>

# re: Integrating .NET classes with the BizTalk Server 2004 Rules engine 9/28/2006 6:51 AM nsthompson
There are two possibilities that jump out at me.

1) be sure the priority setting is correct. the higher the number the sooner the rule executes.
2) be sure that all of the elements that rule1 uses are present in the xml instance that you are running. If it is missing vocab feilds (facts) it will skip execution until it has all of its facts populated by other rules.

If Rule1 truly has a higher priority than rule2, rule2 should be triggered to fire again after rule1 has fired (because of rule2's dependency on agestatus). Look for this to happen in the testing window of the rules composer.

try setting rule1 priority at 500 and rule2 priority at 250. see what happens.

Also, are these the only rules that are firing as part of this policy?



# re: Integrating .NET classes with the BizTalk Server 2004 Rules engine 9/28/2006 8:43 AM vinod
Priority is set for Rule1-500
for Rule 2 -250

All values are present to execute in the rule 1 even though the Rule 2 is firing.

I am not able to trace it out.
I tried to update the Agestatus fact even though biztalk rules engine firing Rule 2, instead of Rule1

# re: Integrating .NET classes with the BizTalk Server 2004 Rules engine 9/28/2006 8:57 AM nsthompson
Your rule priorities are positive numbers right?

The "-" character is just notation in your post?

If so, recreate rule1 in another policy on its own and try again, you may have an error in your vocab definition.

# re: Integrating .NET classes with the BizTalk Server 2004 Rules engine 9/28/2006 9:24 AM vinod
Yes my rule priorites are Positive numbers
I have not used "-" .

As per your suggestion I have removed the policy and created the new policy.
Here the problem is Rule 1 is executing and the Agestatus is getting updated to set variable.
When it comes to Rule2 the
if Agestatus=SeniorCitizens then
Action policy="Pension"

The Leftoperand is still Null. so the Rule 2 not statisfying the condition.

For this purpose I have used Update Fact but that also not helped me.


# re: Integrating .NET classes with the BizTalk Server 2004 Rules engine 2/16/2011 12:29 AM Abhishek
is it possible to debug the fact retriever assembly.

Post A Comment
Title:
Name:
Email:
Website:
Comment:
Verification: