When I try to the following LINQ Filter
var productsInfo = from product in productsElement.Descendants("product").Filter(rule)
from photo in product.Descendants("photo")
from parameter in product.Descendants("parameter")
let id = product.Attribute("id")
let addr = photo.Attribute("addr")
let name = parameter.Attribute("name")
select new { ID = id.Value, Addr = addr.Value, Name = name.Value };
I get the following error:
The given ruleset does not contain any rules with type
System.Xml.Linq.XElement, System.Xml.Linq, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089 (Error E106)
My rule:
<?xml version="1.0" encoding="utf-8"?><codeeffects xmlns="http://codeeffects.com/schemas/rule/41" xmlns:ui="http://codeeffects.com/schemas/ui/4"><rule id="09973a56-3d6a-4616-ae1c-40d0d17e95b9" webrule="4.3.6.7" utc="2017-07-24T10:07:08.6346" type="testSlimWebRule.products, testSlimWebRule, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" eval="true"><definition><condition type="equal"><property name="AllProducts.product.id" /><value type="numeric">1</value></condition></definition><format><lines /></format></rule></codeeffects>
The XML:
XDocument productsElement = XDocument.Parse(@"<products>
<AllProducts>
<product id='1'>
<photo addr='1.jpg'/>
<parameter name='name'/>
</product>
<product id='2'>
<photo addr='2.jpg'/>
<parameter name='Kenneth'/>
</product>
</AllProducts>
</products> ");
The products class generated using Visual Studio "Insert as XML class":
namespace testSlimWebRule
{
/// <remarks/>
[System.SerializableAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
[System.Xml.Serialization.XmlRootAttribute(Namespace = "", IsNullable = false)]
public partial class products
{
private productsAllProducts allProductsField;
/// <remarks/>
public productsAllProducts AllProducts
{
get
{
return this.allProductsField;
}
set
{
this.allProductsField = value;
}
}
}
/// <remarks/>
[System.SerializableAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
public partial class productsAllProducts
{
private productsAllProductsProduct productField;
/// <remarks/>
public productsAllProductsProduct product
{
get
{
return this.productField;
}
set
{
this.productField = value;
}
}
}
/// <remarks/>
[System.SerializableAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
public partial class productsAllProductsProduct
{
private productsAllProductsProductPhoto photoField;
private productsAllProductsProductParameter parameterField;
private byte idField;
/// <remarks/>
public productsAllProductsProductPhoto photo
{
get
{
return this.photoField;
}
set
{
this.photoField = value;
}
}
/// <remarks/>
public productsAllProductsProductParameter parameter
{
get
{
return this.parameterField;
}
set
{
this.parameterField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public byte id
{
get
{
return this.idField;
}
set
{
this.idField = value;
}
}
}
/// <remarks/>
[System.SerializableAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
public partial class productsAllProductsProductPhoto
{
private string addrField;
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public string addr
{
get
{
return this.addrField;
}
set
{
this.addrField = value;
}
}
}
/// <remarks/>
[System.SerializableAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
public partial class productsAllProductsProductParameter
{
private string nameField;
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public string name
{
get
{
return this.nameField;
}
set
{
this.nameField = value;
}
}
}
}
The ASP.net part:
<rule:RuleEditor ID="ruleEditor" runat="server"
Mode="Filter"
ShowToolBar="false"
SourceAssembly="testSlimWebRule"
SourceType="testSlimWebRule.products" />
I have tried several combinations of setting the filter, but just can't find a solution.
What am I missing?
The Filter() extension takes a collection of objects and evaluates them against a given rule individually. The rule engine takes a type of an element of the collection and uses it to compile a lambda with a parameter of that type.
List<MyClass> list = new List<MyClass>();
list.Filter(rule);
In this example the rule is of type MyClass and it gets applied to each object in the list similar to:
Evaluator<MyClass> ev = new Evaluator<MyClass>(rule);
foreach (var item in list)
{
ev.Evaluate(item);
}
You may read more on CodeEffects documentation page: Rule-Based Data Filtering Using LINQ to Object Provider.
In your example you made few mistakes:
The example below will demonstrate four possible options, with the first one being the best.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Linq;
using System.Xml.Serialization;
using CodeEffects.Rule.Core;
namespace testSlimWebRule
{
class Program
{
/* Output:
* Option A: 1, Bob, 1.jpg
* Option B: 1, Bob, 1.jpg
* Option C: 1, Bob, 1.jpg
* Option D: 2, Kenneth, 2.jpg
*/
static void Main(string[] args)
{
string xml =
@"<products>
<AllProducts>
<product id='1'>
<photo addr='1.jpg'/>
<parameter name='Bob'/>
</product>
<product id='2'>
<photo addr='2.jpg'/>
<parameter name='Kenneth'/>
</product>
</AllProducts>
</products>";
XDocument dom = XDocument.Parse(xml);
products products;
//You need to load auto-generated classes. I prefer serialization. You may parse XML elements individually.
using (var xmlReader = dom.CreateReader())
{
var serializer = new XmlSerializer(typeof(products));
products = (products)serializer.Deserialize(xmlReader);
}
string productsProductRule = File.ReadAllText("rule1.xml");
//A: Filter productsProduct[]; result is IEnumerable<productsProduct>.
//This rule evaluates objects of the productsProduct type.
var filteredProducts = products.AllProducts.Filter(productsProductRule);
foreach (var product in filteredProducts)
Console.WriteLine("Option A: {0}, {1}, {2}", product.id, product.parameter.name, product.photo.addr);
string xElementRule = File.ReadAllText("rule2.xml");
//B: Filter IEnumerable<XElement>; result is IEnumerable<XElement>.
//This rule evaluates objects of the XElement type.
var filteredElements = dom.Descendants("product").Filter(xElementRule);
foreach (var element in filteredElements)
Console.WriteLine("Option B: {0}, {1}, {2}", element.Attribute("id").Value, element.Element("parameter").Attribute("name").Value, element.Element("photo").Attribute("addr").Value);
//C: Filter IEnumerable<XElement>; result is IEnumerable<'a> (anonymous)
//This rule also evaluates objects of the XElement type.
var productsInfo = from product in dom.Descendants("product").Filter(xElementRule)
from photo in product.Descendants("photo")
from parameter in product.Descendants("parameter")
let id = product.Attribute("id")
let addr = photo.Attribute("addr")
let name = parameter.Attribute("name")
select new
{
ID = id.Value,
Addr = addr.Value,
Name = name.Value
};
foreach (var info in productsInfo)
Console.WriteLine("Option C: {0}, {1}, {2}", info.ID, info.Name, info.Addr);
string anonymousRule = File.ReadAllText("rule3.xml");
//D: Filter IEnumerable<'a>; result is IEnumerable<'a>
//This rule evaluates objects of the anonymous type 'a with properties ID, Addr, and Name.
var productsInfo2 = (from product in dom.Descendants("product")
from photo in product.Descendants("photo")
from parameter in product.Descendants("parameter")
let id = product.Attribute("id")
let addr = photo.Attribute("addr")
let name = parameter.Attribute("name")
select new
{
ID = id.Value,
Addr = addr.Value,
Name = name.Value
})
.Filter(anonymousRule);
foreach (var info in productsInfo2)
Console.WriteLine("Option D: {0}, {1}, {2}", info.ID, info.Name, info.Addr);
}
}
}
You need re-paste your XML example to generate proper array fields. The one you have was generated with XML sample that only had one record. However to filter you need a collection.
using System;
namespace testSlimWebRule
{
/// <remarks/>
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
[System.Xml.Serialization.XmlRootAttribute(Namespace = "", IsNullable = false)]
public partial class products
{
private productsProduct[] allProductsField;
/// <remarks/>
[System.Xml.Serialization.XmlArrayItemAttribute("product", IsNullable = false)]
public productsProduct[] AllProducts
{
get
{
return this.allProductsField;
}
set
{
this.allProductsField = value;
}
}
}
/// <remarks/>
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
public partial class productsProduct
{
private productsProductPhoto photoField;
private productsProductParameter parameterField;
private byte idField;
/// <remarks/>
public productsProductPhoto photo
{
get
{
return this.photoField;
}
set
{
this.photoField = value;
}
}
/// <remarks/>
public productsProductParameter parameter
{
get
{
return this.parameterField;
}
set
{
this.parameterField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public byte id
{
get
{
return this.idField;
}
set
{
this.idField = value;
}
}
}
/// <remarks/>
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
public partial class productsProductPhoto
{
private string addrField;
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public string addr
{
get
{
return this.addrField;
}
set
{
this.addrField = value;
}
}
}
/// <remarks/>
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
public partial class productsProductParameter
{
private string nameField;
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public string name
{
get
{
return this.nameField;
}
set
{
this.nameField = value;
}
}
}
}
This rule has proper type testSlimWebRule.productsProduct. It gets evaluated against each element in the auto-generated array of testSlimWebRule.productsProduct.
<?xml version="1.0" encoding="utf-8"?>
<codeeffects xmlns="http://codeeffects.com/schemas/rule/41" xmlns:ui="http://codeeffects.com/schemas/ui/4">
<rule id="09973a56-3d6a-4616-ae1c-40d0d17e95b9" webrule="4.3.6.7" utc="2017-07-24T10:07:08.6346" type="testSlimWebRule.productsProduct, testSlimWebRule, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" eval="true">
<definition>
<condition type="equal">
<property name="id" />
<value type="numeric">1</value>
</condition>
</definition>
</rule>
</codeeffects>
This rule is the way you would have. It is applied to objects of type System.Xml.Linq.XElement. As such it can only operate on properties and methods of that type, i.e. you don't get your custom fields id, addr, name, etc.
<?xml version="1.0" encoding="utf-8"?>
<codeeffects xmlns="http://codeeffects.com/schemas/rule/41" xmlns:ui="http://codeeffects.com/schemas/ui/4">
<rule id="e38da351-1190-47fb-b99b-d06787c9a459" webrule="4.3.6.7" utc="2017-07-24T10:07:08.6346" type="System.Xml.Linq.XElement, System.Xml.Linq, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" eval="true">
<definition>
<condition type="equal">
<property name="FirstAttribute.Value" />
<value>1</value>
</condition>
</definition>
</rule>
</codeeffects>
This rule does not have any type. Instead it receives whatever type it is evaluated against. In the example it gets anonymous type 'a, so it can operate on properties ID, Name, and Addr.
<?xml version="1.0" encoding="utf-8"?>
<codeeffects xmlns="http://codeeffects.com/schemas/rule/41" xmlns:ui="http://codeeffects.com/schemas/ui/4">
<rule id="7d72463f-5ae2-4617-a2bf-fd605fcb4f54" webrule="4.3.6.7" utc="2017-07-24T10:07:08.6346" type="" eval="true">
<definition>
<condition type="startsWith">
<property name="Addr" />
<value>2</value>
</condition>
</definition>
</rule>
</codeeffects>