Understanding Code Effects FlexSource
FlexSource is a feature of the stand-alone Rule Editor and Rule Engine. The Rule Repository does not support it (see FlexSource and the Rule Repository).
This article walks you through a minimal, production-ready implementation of FlexSource — a technology that lets the Rule Engine evaluate rules against runtime-defined data (no compile-time POCOs, no code generation).
Why FlexSource Exists
Most .NET applications rely on strongly typed classes to define the “shape” of data used by business rules.
But in many systems - such as multi-tenant, configurable, or metadata-driven applications - the data shape isn’t fixed until runtime.
FlexSource solves this by allowing Code Effects to interact with dynamic data models.
You can define and evaluate rules against data whose fields and methods are defined at runtime, without recompiling or generating code.
Why use FlexSource instead of Adaptive Source?
The FlexSource option is ideal when you want to define the Rule Editor's source object entirely in code at runtime without introducing the additional infrastructure required by Adaptive Source. It allows your application to construct fields, methods, actions, and other source elements programmatically using familiar .NET patterns, making it a natural choice when the available rule elements depend on runtime conditions such as user roles, tenant configuration, feature flags, database settings, or other application-specific logic.
Like Source XML, FlexSource determines the editor's available elements before rule authoring begins. Once the source has been built and the editor initialized, the entire generated source remains available throughout the authoring session. This provides a straightforward way to create dynamic editor experiences while retaining a relatively simple implementation model.
The trade-off is that FlexSource does not continuously adapt as the rule itself evolves. Unlike Adaptive Source, it cannot refine menus and available options based on the rule's current authoring context. Consequently, features that rely on real-time context awareness during rule construction, such as Context Resetting, are not available when using the FlexSource option.
What is FlexSource?
FlexSource creates a bridge between the Rule Editor and the Rule Engine when no static C# type exists.
| Role |
Used by |
Purpose |
| FlexSource Type (derives from System.Type) |
Rule Editor |
Describes what fields, properties, and methods exist in your dynamic object so rule authors can use them. |
| Flex Data Provider (implements IFlexDataProvider) |
Rule Engine |
Provides property values and executes methods when evaluating a rule at runtime. |
Instead of referencing a static C# class, you provide these two lightweight components that tell Code Effects how to discover, display, and execute your dynamic object’s members.
Key Concept: “Mocking a Type”
Think of the System.Type - derived class as a mock type.
The Rule Editor reflects over it as if it were a real class.
You decide what GetProperties() and GetMethods() should return — based on your runtime metadata.
Meanwhile, the IFlexDataProvider tells the Rule Engine how to actually get, set, or invoke those members on live data when the rule executes.
What the Rule Editor and Engine Expect
At design time (Rule Editor) — You must declare a class (the FlexSource) that derives from System.Type and returns your runtime members when the editor reflects over it. Pass an instance of it to the SourceType property of the Control. The editor queries it for all public fields, properties, and methods that are not decorated with the ExcludeFromEvaluation attribute.
The goal is to give the editor a Type that represents your dynamic structure.
Deriving directly from System.Type means overriding dozens of abstract members. The simplest way is to derive from System.Reflection.TypeDelegator instead, which implements them all by delegating to another type, and to override only these members:
using System;
using System.Reflection;
string Name { get; }
string FullName { get; }
string AssemblyQualifiedName { get; }
PropertyInfo[] GetProperties(BindingFlags bindingAttr);
MethodInfo[] GetMethods(BindingFlags bindingAttr);
FieldInfo[] GetFields(BindingFlags bindingAttr);
Three details decide whether the editor accepts your type:
- Return your own
PropertyInfo and MethodInfo objects. If you return the members of a real class, the editor also reflects the members that class inherits from object. The sample below implements small PropertyInfo, MethodInfo, and ParameterInfo classes whose names and types come from your metadata.
- Return an empty array of the requested type from
GetCustomAttributes(Type, bool), not null, and return a getter and a setter method from every property.
- Set
AssemblyQualifiedName to typeof(object).AssemblyQualifiedName. The editor writes this name into the type attribute of every rule it saves, and the Rule Engine looks that type up when it loads the rule. A name that no assembly can load makes the engine skip the rule and report that the ruleset contains no rules of the source type.
At evaluation time (Rule Engine) — The evaluator only needs a small set of methods to get or set property values and invoke methods on your runtime data. Your data class must implement the IFlexDataProvider interface:
using System;
object GetProperty(object target, string name);
object CallMethod(object target, string name, params object[] args);
void SetProperty(object target, string name, object value);
Type GetPropertyType(string propertyName);
Type GetMethodReturnType(string methodName);
You can combine both roles in one class (derive from System.Type and implement IFlexDataProvider at the same time), but most real-world mission-critical systems keep them separate because these data-oriented classes typically have different implementations, expose different sets of members, and are used in different places: the type by the editor, usually in a web application, and the provider by the engine.
Tip: When combining both roles, ensure your System.Type overrides are safe for reflection and your IFlexDataProvider calls are deterministic to maintain consistent evaluations.
When to Use FlexSource
Use FlexSource when:
- Your data model is defined at runtime (for example, metadata tables or configurable schemas).
- You need to author rules before the final data shape is known.
- You want to evaluate and optionally execute rules without compiling classes.
- You use a stand-alone Rule Editor. FlexSource is not available in the Rule Repository.
Avoid FlexSource when:
FlexSource and the Rule Repository
The Rule Repository (RR) converts every source to a Source XML document, stores it, and versions it, and it identifies each source by an alias. A FlexSource has none of those: it is an object that exists only in your process, with a type name that is not a real type, and the engine evaluates it through a data provider that RR does not know about. So:
- A FlexSource cannot be synced.
SourceSettings.SourceTypes takes real .NET types, and a mock type is not one. RR does not test or audit FlexSource.
- To keep the runtime-defined shape and use RR, describe it as Source XML. Generate the document from your metadata (see Source XML) and add it to
SourceSettings.SourceXmls, or write an Adaptive Source provider whose GetSource() returns the shape. Both give RR the same fields and methods that a FlexSource would give a stand-alone editor.
- Evaluation of such rules still needs an
IFlexDataProvider if there is no .NET class to evaluate them against. RR only stores and manages the rules.
Mental Model
- Design time (Editor) — The editor reflects over your
System.Type-derived class to list fields and methods. Authors build rules visually.
- Runtime (Engine) — The engine asks your
IFlexDataProvider to read or write those values on real data objects when rules are evaluated.
Step-by-Step Example
The sample below compiles and runs. Its rule editor lists the five fields and the method of the shape, and a rule that the editor saves evaluates against a FlexRecord.
Shape — the runtime metadata
public static class Shape
{
public static readonly Dictionary<string, Type> Properties = new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase)
{
["Id"] = typeof(int), ["Name"] = typeof(string), ["Segment"] = typeof(string), ["IsActive"] = typeof(bool), ["Balance"] = typeof(decimal)
};
public static readonly Dictionary<string, (Type Return, (string Name, Type Type)[] Parameters)> Methods = new Dictionary<string, (Type, (string, Type)[])>(StringComparer.OrdinalIgnoreCase)
{
["InSegment"] = (typeof(bool), new[] { ("segment", typeof(string)) })
};
}
FlexType — the mock type that the editor reflects over, and the reflection classes it returns
public sealed class FlexType : TypeDelegator
{
public FlexType() : base(typeof(FlexRecord)) { }
public override string Name => "FlexRecord";
public override string FullName => "DynamicSources.FlexRecord";
public override string AssemblyQualifiedName => typeof(object).AssemblyQualifiedName;
public override PropertyInfo[] GetProperties(BindingFlags bindingAttr) => Shape.Properties.Select(kv => (PropertyInfo)new FlexProperty(this, kv.Key, kv.Value)).ToArray();
public override MethodInfo[] GetMethods(BindingFlags bindingAttr) => Shape.Methods.Select(kv => (MethodInfo)new FlexMethod(this, kv.Key, kv.Value.Return, kv.Value.Parameters)).ToArray();
public override FieldInfo[] GetFields(BindingFlags bindingAttr) => Array.Empty<FieldInfo>();
}
public sealed class FlexProperty : PropertyInfo
{
readonly Type owner; readonly string name; readonly Type type;
public FlexProperty(Type owner, string name, Type type) { this.owner = owner; this.name = name; this.type = type; }
public override string Name => name;
public override Type PropertyType => type;
public override Type DeclaringType => owner;
public override Type ReflectedType => owner;
public override PropertyAttributes Attributes => PropertyAttributes.None;
public override bool CanRead => true;
public override bool CanWrite => true;
public override MethodInfo[] GetAccessors(bool nonPublic) => Array.Empty<MethodInfo>();
public override MethodInfo GetGetMethod(bool nonPublic) => new FlexMethod(owner, "get_" + name, type, Array.Empty<(string, Type)>());
public override MethodInfo GetSetMethod(bool nonPublic) => new FlexMethod(owner, "set_" + name, typeof(void), new[] { ("value", type) });
public override ParameterInfo[] GetIndexParameters() => Array.Empty<ParameterInfo>();
public override object GetValue(object obj, BindingFlags invokeAttr, Binder binder, object[] index, CultureInfo culture) => throw new NotSupportedException();
public override void SetValue(object obj, object value, BindingFlags invokeAttr, Binder binder, object[] index, CultureInfo culture) => throw new NotSupportedException();
public override object[] GetCustomAttributes(bool inherit) => Array.Empty<object>();
public override object[] GetCustomAttributes(Type attributeType, bool inherit) => (object[])Array.CreateInstance(attributeType, 0);
public override bool IsDefined(Type attributeType, bool inherit) => false;
}
public sealed class FlexParameter : ParameterInfo
{
public FlexParameter(string name, Type type, int position, MemberInfo member) { NameImpl = name; ClassImpl = type; PositionImpl = position; MemberImpl = member; AttrsImpl = ParameterAttributes.None; }
}
public sealed class FlexMethod : MethodInfo
{
readonly Type owner; readonly string name; readonly Type returnType; readonly ParameterInfo[] pars;
public FlexMethod(Type owner, string name, Type returnType, (string Name, Type Type)[] parameters)
{
this.owner = owner; this.name = name; this.returnType = returnType;
pars = parameters.Select((p, i) => (ParameterInfo)new FlexParameter(p.Name, p.Type, i, this)).ToArray();
}
public override string Name => name;
public override Type ReturnType => returnType;
public override Type DeclaringType => owner;
public override Type ReflectedType => owner;
public override MethodAttributes Attributes => MethodAttributes.Public;
public override RuntimeMethodHandle MethodHandle => throw new NotSupportedException();
public override ICustomAttributeProvider ReturnTypeCustomAttributes => throw new NotSupportedException();
public override ParameterInfo ReturnParameter => new FlexParameter(null, returnType, -1, this);
public override MethodInfo GetBaseDefinition() => this;
public override ParameterInfo[] GetParameters() => pars;
public override MethodImplAttributes GetMethodImplementationFlags() => MethodImplAttributes.IL;
public override object Invoke(object obj, BindingFlags invokeAttr, Binder binder, object[] parameters, CultureInfo culture) => throw new NotSupportedException();
public override object[] GetCustomAttributes(bool inherit) => Array.Empty<object>();
public override object[] GetCustomAttributes(Type attributeType, bool inherit) => (object[])Array.CreateInstance(attributeType, 0);
public override bool IsDefined(Type attributeType, bool inherit) => false;
}
FlexRecord — the runtime data, and the IFlexDataProvider implementation
public sealed class FlexRecord
{
readonly Dictionary<string, object> values = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
public FlexRecord(IDictionary<string, object> seed) { foreach (var kv in seed) values[kv.Key] = kv.Value; }
public object this[string key] { get => values.TryGetValue(key, out var v) ? v : null; set => values[key] = value; }
public bool InSegment(string segment) => string.Equals(Convert.ToString(this["Segment"]), segment, StringComparison.OrdinalIgnoreCase);
}
public sealed class FlexProvider : IFlexDataProvider
{
public object GetProperty(object target, string name) => ((FlexRecord)target)[name];
public void SetProperty(object target, string name, object value) { var t = Shape.Properties[name]; ((FlexRecord)target)[name] = value == null ? null : Convert.ChangeType(value, Nullable.GetUnderlyingType(t) ?? t, CultureInfo.InvariantCulture); }
public object CallMethod(object target, string name, params object[] args) => typeof(FlexRecord).GetMethod(name).Invoke(target, args);
public Type GetPropertyType(string propertyName) => Shape.Properties.TryGetValue(propertyName, out var t) ? t : typeof(object);
public Type GetMethodReturnType(string methodName) => Shape.Methods.TryGetValue(methodName, out var m) ? m.Return : typeof(void);
}
Wiring It Up
using CodeEffects.Rule.Common.Models;
using CodeEffects.Rule.Editor;
using CodeEffects.Rule.Engine;
// Design-time
var editor = new Control("divRuleEditor")
{
EvaluationMode = EvaluationMode.Execution,
SourceType = new FlexType()
};
// Run-time
var target = new FlexRecord(new Dictionary<string, object>
{
["Id"] = 123,
["Name"] = "Acme",
["Segment"] = "Enterprise",
["IsActive"] = true,
["Balance"] = 2500.00m
});
var provider = new FlexProvider();
string ruleXml = /* load from storage */;
var evaluator = new Evaluator(ruleXml, provider);
bool success = evaluator.Evaluate(target);
Practical Tips
- Method whitelisting — Only expose deterministic, safe methods. The engine's assembly whitelist (
EvaluationParameters.AssemblyWhitelist) is ignored for a FlexSource, so your provider is the only gate.
- Type coercion — Always convert inputs to their declared property or parameter types.
- Centralized metadata — Keep your property/method catalog centralized.
- Null handling — Decide whether missing fields are treated as
null or as errors.
- Diagnostics — Log missing members or type mismatches.
- Performance — Cache reflection results when safe.
FlexSource doesn’t store your data — it teaches Code Effects how to pretend your runtime-defined object is a real .NET class.