What is BFsharp?

It's a library which provides several services assosiated with entity management:

  • Rules - they allow to define reusable code which can be executed in response to property change (strategy pattern).
  • DynamicProperties - entity can be extended dynamically.
  • Formula DSL - textual language which can be used to write rules. Moreover it can be used without rules so as to customize other functionality.
  • IsDirty - marks entity as dirty in response to property change.
  • Entity management - groups entity into aggregates - concepts known from Domain Driven Design. It allows to manage entites in logical groups. Let's take invoice as an example. If any of InvoiceLines (child) is modified or validated the Invoice ( parent ) should be notified to react accordingly.

It's supported on the following platforms:

  • .Net
  • Silverlight
  • WP7 (under development)

Getting started

First of all you need to enable BFsharp support in your code. You can do this using NuGet or manually adding reference to BFsharp.dll and optionally BFsharp.AOP.dll. BFSharp supports .net 3.5, silverlight 4 (assemblies ending with .SL) and experimentally WP7 (assemblies ending with .WP7). Next you need to derive your class from EntityBase<T>, for example:

public class Product : EntityBase<Product>
{
    public string Name { get; set; }
    public decimal NetPrice { get; set; }
    public decimal Rate { get; set; }
    public decimal GrossPrice { get; set; }
    public decimal Tax { get; set; }
}

BFsharp relies heavily on INotifyPropertyChanged interface. You can implement it manually or use the aspect which will do everything for you.

[NotifyPropertyChanged]
public class Product : EntityBase<Product>
{
    public string Name { get; set; }
    public decimal NetPrice { get; set; }
    public decimal Rate { get; set; }
    public decimal GrossPrice { get; set; }
    public decimal Tax { get; set; }
}

If you can't derive your class from EntityBase because you need to use inheritance for some other purpose or simply you do not have access to the code ( for example compiled class ) there is a way. You have to register extensions for your object.

var c = new ClassWithoutSourceCode();
var e= EntityExtensions.RegisterTypedObject(e);
e.CreateValidationRule(x => x.Name.Length > 3)
    .Start();

Now, you can access BFsharp API from the Extensions property.

Rules

Rules are one of the many features you can find in BFsharp. They allow to define reusable code which can be executed in response to property change (strategy pattern). There're several types of rules:

  1. ValidationRule - checks for specified condition and if it's false creates a BrokenRule which can be later used to show error to the end-user in the UI. For example, to check that NetPrice must be greater than zero you could write:
    var p = new Product();
    p.Extensions.CreateValidationRule(x => x.NetPrice > 0)
                    .WithMessage("NetPrice must be greater than zero.")
                    .Start();
    
    Every time the NetPrice property is changed ( INotifyPropertyChanged ) this rule is evaluated. If predicate is false the rule is said to be broken and a BrokenRule class is created with information about the error. Moreover EntityBase implements several interfaces which are used in WPF and Silverlight to display errors automatically.
    p.Extensions.BrokenRules[0].Message.ShouldEqual("NetPrice must be greater than zero.");
    p.Extensions.BrokenRules[0].Severity.ShouldEqual(BrokenRuleSeverity.Error);
    
    Validation rules can also be specifed using attributes, for example:
    [NotifyPropertyChanged]
    public class Customer : EntityBase<Product>
    {
        [Required, Email, MaxLength(100)]
        public string Name { get; set; }
        [Range(0,100)]
        public decimal NetPrice { get; set; }
    }
    There are several predefinied rules and you can create your own:
    1. Required
    2. MaxLength
    3. Email
    4. Pattern
    5. Range
    6. ShouldBe
    They are created and started with InitializeRules method.
  2. BusinessRule - consists of func and target. In response to property changed the formula is recalculated and assigned to target. BusinessRule can also work as a ValidationRule. For example, to automatically calculate GrossPrice from Rate and NetPrice:
    p.Extensions.CreateBusinessRule(x => x.NetPrice * x.Rate, x => x.GrossPrice)
                    .Start();
    
    You can ask: "Why do I need this? I can encapsulate this forumla in GrossPrice property making it read-only". However, there are several drawbacks in this approach:
    1. This approach is not reusable.
    2. The rule formula cannot be changed without compilation.
    3. There is no support for notification for this property and it's impossible to bind this property to UI.
    4. Using this approach you cannot implement easily the following scenario: calculate GrossPrice when user changes NetPrice, calucalte NetPrice when user changed GrossPrice - so called rule cycle.
    Moreover BFsharp supports rules created from string (Formula language), you could write:
    p.Extensions.CreateBusinessRule("GrossPrice=NetPrice*Rate")
                    .Start();
    
    Using this functionality you can easily externalize rules and make them customizable for the end-user\system administrator.
  3. ActionRules - executes an action in response to a property change. For example you can use it to execute WebService and check the availability of the entered mail.
    var c = new Customer();
    c.Extensions.CreateActionRule(x => CheckIfEmailIsAvailable(x.Email))
                    .Start();
    

Rules are invoked only if one of the properties used in the rule definition is changed. If you want to specify the dependencies by hand you could write:

p.Extensions.CreateValidationRuleWithoutDependency(x => x.NetPrice > 0)
                .WithDependencies(x=>x.Tax) // Expression, stronly-typed, compile time name validation
                .WithDependencies("NetPrice", "GrossPrice") // String, usefull for tools and infrastructure code
                .Start();

Althought rules can be added from any place in code, there is a pattern to do it.

Validation

Extensions object has several member which are used to interact with the validation system:

  1. IsValid - this property tells whether the entity is in a valid state. If rules with automatic dependency are used it's updated in real-time in response to property changed notification. Moreover the property throws INotifyPropertyChanged hence it can be binded to UI easily.
  2. Validate - the method allows to validate the entire entity on demand. It's useful when an entity doesn't support INotifyPropertyChange, rules doesn't use automatic dependency or the are started without validation.
  3. Rules - a collection of rules associated with the current entity.
  4. BrokenRules - a collection of objects which represents erorrs and warnings associated with the current entity.

Rule options

Rules have several options which can be accessed using properties or Fluent Interface:

  1. Rule Name - rules can be named. It can be useful for identifying rules or getting them by name.
    rule.WithName("NetPriceCalculation");
    
  2. DebugString - rules can have debug string which can be useful during debugging. If rule is created using Expression contains the textual body representation.
    var r = e.Extensions.CreateBusinessRule(en => en.Number2 + 5, en => en.Number)
                    .Start();
    
    // Now r.DebugString is equal to en.Number = (en.Number2 + 5)
    
    rule.WithDebugString("Some debug string");
    
  3. Rule modes - each rule has one or mode modes which control when they are evaluated\validated or invoked.
    1. ValidationRule
      • StartupMode - controls whether the rule is automatically validated during startup. The default is Validate.
    2. BusinessRule
      • StartupMode - controls what happens when the rule is started. Available options are: none, validate, evaluate.
      • Mode - in response to dependency changed BusinessRule can be evaluated or validated.
      • TargetChangeAction - when the target property is changed BF can take the following action: None, Override or Validate. Validate option will evaluate function and then compares it with target, when they are different a broken rule is created. Override strategy simply overrides the property with function evaluation.
      • DisableValidation - by default BusinessRule is validated during its lifetime, that means if target is different from function it creates broken rule. You can disable this behavior with this switch.
    3. ActionRule
      • StartupMode - controls whether the rule is automatically invoked during startup. The default is none.
    There are defined several convinient Fluent Interface methods which control rule modes, the all start with With.
  4. Tags - every rule has a property named Tag. It can be used to store some user-defined data. There are several helper methods which can interact with rules and tag.
    1. EnableByTag - enables all rules with the specified tag
    2. SwitchRulesByTag - enables all rules with the specified tag and disables the rest
    3. ReevaluateRulesByTag - invoke ( ctionRule) or evaluate (BusinessRule) with the specified tag
  5. Exception Filter - sometimes rules can throw exceptions. They can be caught by BFsharp infrastructure and processed as a BrokenRule. For example let's take division by zero.
    p.Extensions.CreateBusinessRule(x => x.GrossPrice / x.NetPrice-1, x => x.Rate)
                    .WithException<DivideByZeroException>("DivisionByZeroMessage", BrokenRuleSeverity.Error)
                    .Start();
    
    p.NetPrices = 0; // In this line a new BrokenRule is created in response to 
                     // DivisionByZeroException which is thrown from the rule
    
  6. Rule Prototypes - rules can be copied. This enables some interesting scenarios, namely rule prototypes. Rules definition can be stored in some kind of repository. At the start of the application it is read and rules are instantiated. Then these prototype rules are copied and added to every entity which is created. Prototype rules are faster to create because they do not need to analyze dependencies. It's as fast as memory copy.
    var factory = new RuleFactory≶Entity>();
    var rule = factory.CreateValidationRule(en => en.Number > 5).Start();
    
    var e = new Entity();
    e.Extensions.AddRuleFromPrototype(rule).Start();
    
  7. Rule Suppression - rules can fire each other. You can disable rule evaluation by other rules using several methods:
    1. Suppresses
    2. MutuallySuppressedBy
    3. DisableRecursion
  8. Rule Priority - if you have two rules which are fired in response to the same property change you can specify which should run first.
    e.Extensions.CreateActionRuleWithoutDependency(en => lastRule="one")
                    .WithDependencies(en=>en.Number)
                    .Start();
    
    e.Extensions.CreateActionRuleWithoutDependency(en => lastRule = "two")
                    .WithDependencies(en => en.Number)
                    .WithHighPriority()
                    .Start();
    
    Other: WithLowPriority, WithPriority(int priority), WithPriority(RulePriority priority). These methods set an int property Rule.Priority. The greater number the higher priority it represents. You could also use RulePriority enum which defines several priority levels:
    public enum RulePriority
    {
        VeryLow = -20,
        Low = -10,
        Normal = 0,
        High = 10,
        VeryHigh = 20
    }
    

Dynamic Properties

Rules provide a way to extend behavior. Dynamic properties extend the data model.

var e = new Entity();
e.Extensions.AddProperty<int>("name");

e.Extensions.DynamicProperties["name"] = 4;

Dynamic Properties support databinding and other technology based on reflection. There is a special property called TypedProxy which provides dynamic class generated based on the definition. It supports INotifyPropertyChanged.

object typedProxy = e.Extensions.DynamicProperties.TypedProxy;

IsDirty

It'a mechanism which tells if the entity is edited. It can be used in the user interface to show a modification marker.

var p = new Product();
p.Extensions.StartDirtyTracking();
p.Name = "a";
// p.Extensions.IsDirty is true now

Last edited Jul 27, 2011 at 8:58 AM by michaelmac, version 5

Comments

No comments yet.