I wanted to create a DSL that would allow me to express rules in a compact, simple way. MicroRules parses and compiles a simple expression into an executable function at runtime. This is not interpreted, it is compiled to a function that is ran by the .NET runtime.
MicroRules is purposely small and simple, with only as many features as necessary for my use case.
Language examples:
Say we wanted to give a discount to users who have spent more than $100 on items.
SpendTotal > 100 ? 0.1 : 0
Let's get complicated. How about shipping costs?
MicroRules has a compact match expression for pattern matching. This can match a single value, multiple values, collection, and so on.
Comparison operators can be placed in the case arms. _ can be used as a wildcard that always matches.
(zone, weight, service, membership) -> {
(Domestic, <= 5, Express, Premium): baseCost * 0.8,
(International, _, _, Premium): baseCost * 1.2,
(_, > 20, Ground, _): baseCost + (weight - 20) * surchargeRate,
(Remote, _, _, _): baseCost * 1.5,
_: baseCost
}
Medication dosage.
This has a collection contains check([value]) as part of a case.
(patientAge, weight, condition, allergies) -> {
(>= 65, _, Hypertension, ['ace-inhibitor']): alternativeDose * 0.5,
(< 18, < 50, _, _): pediatricDose,
(_, >= 100, Diabetes, _): standardDose * 1.2,
_: standardDose
}
LINQ & lambda expressions are supported.
Block checkout if any item is unavailable:
cart.items.Any(i => i.inventory <= 0)
MicroRules compiles expressions into strongly-typed, executable functions:
// Custom delegate types (recommended approach)publicdelegateboolCustomerEligibility(Customercustomer);publicdelegatedoublePricingRule(Orderorder,doublebasePrice);vareligibilityRule=MicroRules.Compile<CustomerEligibility>("customer.Age >= 18 && customer.CreditScore > 600");booleligible=eligibilityRule(customer);varpricingRule=MicroRules.Compile<PricingRule>("order.Items.Any(i => i.Category == Premium) ? basePrice * 1.2 : basePrice");doublefinalPrice=pricingRule(order,basePrice);When you have a single parameter and want cleaner syntax, use SelfFunc<T, TResult>. The "self" parameter enables implicit property lookup:
// With SelfFunc, "Age" implicitly refers to customer.Agevarrule=MicroRules.Compile<SelfFunc<Customer,bool>>("Age >= 18 && CreditScore > 600");boolresult=rule(customer);// Equivalent to this custom delegate:publicdelegateboolCustomerRule(Customerself);varrule2=MicroRules.Compile<CustomerRule>("Age >= 18 && CreditScore > 600");boolresult2=rule2(customer);// Store and reuse compiled expressionsvardiscountRule=MicroRules.Compile<SelfFunc<Customer,double>>("SpendTotal > 100 ? 0.1 : 0");doublediscount1=discountRule(customer1);doublediscount2=discountRule(customer2);// Implicit conversion to delegate for LINQvarfilter=MicroRules.Compile<SelfFunc<Item,bool>>("Rarity == Legendary");varlegendaryItems=items.Where(filter);// Direct executionboolhasAccess=MicroRules.Compile<SelfFunc<User,bool>>("Permissions.Any(p => p.Active)")(user);