/// <summary> /// Registers the rule with the current <see cref="ModelContext"/>. /// </summary> public void Register(ModelType rootType = null) { // Determine if the rule is be registered for a specific root model type if (rootType != null) { if (this.rootType != null) { throw new InvalidOperationException("Rules cannot be explicitly registered for more than one model type."); } this.rootType = rootType; } // Raise the Initialization event the first time the rule is registered if (Initialize != null) { Initialize(this, EventArgs.Empty); Initialize = null; } // Default the invocation type to PropertyChanged if none were assigned if ((int)InvocationTypes == 1) { InvocationTypes = RuleInvocationType.PropertyChanged; } // Automatically detect predicates if none were specified if (Predicates == null && ((InvocationTypes & (RuleInvocationType.PropertyChanged | RuleInvocationType.PropertyGet)) > 0)) { SetPredicates(GetPredicates()); } // Track and validate uniqueness of condition types HashSet <ConditionType> conditionTypes = RootType.GetExtension <HashSet <ConditionType> >(); foreach (var conditionType in ConditionTypes) { if (conditionTypes.Contains(conditionType)) { throw new InvalidOperationException("Registered condition types must be unique for each model type: " + conditionType.Code); } conditionTypes.Add(conditionType); } // Track the rule registration for the root model type List <Rule> rules = RootType.GetExtension <List <Rule> >(); rules.Add(this); // Do not perform model type event registration if the rule is not supposed to execute on the server if ((ExecutionLocation & RuleExecutionLocation.Server) == 0) { return; } // Init Invocation if ((InvocationTypes & RuleInvocationType.InitExisting) == RuleInvocationType.InitExisting || (InvocationTypes & RuleInvocationType.InitNew) == RuleInvocationType.InitNew) { RootType.Init += (sender, e) => { if (((InvocationTypes & RuleInvocationType.InitExisting) == RuleInvocationType.InitExisting && !e.Instance.IsNew) || ((InvocationTypes & RuleInvocationType.InitNew) == RuleInvocationType.InitNew && e.Instance.IsNew)) { Invoke(e.Instance, e); } }; } // Property Get Invocation if ((InvocationTypes & RuleInvocationType.PropertyGet) == RuleInvocationType.PropertyGet) { // Subscribe to property get notifications for all return values RootType.PropertyGet += (sender, e) => { // Determine if the rule is responsible for calculating the property being accessed if (ReturnValues.Contains(e.Property.Name)) { // Get the rule manager for the current instance var manager = e.Instance.GetExtension <RuleManager>(); // Determine if the rule needs to be run if ((e.IsFirstAccess && (!e.Property.IsPersisted || e.Instance.IsNew || manager.IsPendingInvocation(this))) || manager.IsPendingInvocation(this)) { // Invoke the rule Invoke(e.Instance, e); // Mark the rule state as no longer requiring invocation manager.SetPendingInvocation(this, false); } } }; if (!InvocationTypes.HasFlag(RuleInvocationType.SuppressPropertyChanged)) { // Subscribe to property change notifications for all rule predicates foreach (string predicate in Predicates) { RootType.GetPath(predicate).Change += (sender, e) => { // Only invoke the rule if the instance is of the same type as the rule root type if (RootType.IsInstanceOfType(e.Instance)) { // Get the rule manager for the current instance var manager = e.Instance.GetExtension <RuleManager>(); // Mark the rule state as requiring invocation if (manager.SetPendingInvocation(this, true)) { // Raise property change notifications foreach (var property in ReturnValues) { e.Instance.Type.Properties[property].NotifyPathChange(e.Instance); } } } }; } } // Mark the properties as calculated to denote that they are return values of a rule foreach (var property in ReturnValues) { var prop = RootType.Properties[property]; if (prop != null) { prop.IsCalculated = true; } } } // Property Change Invocation if ((InvocationTypes & RuleInvocationType.PropertyChanged) == RuleInvocationType.PropertyChanged) { // Subscribe to property change notifications for all rule predicates foreach (string predicate in Predicates) { RootType.GetPath(predicate).Change += (sender, e) => { // Only invoke the rule if the instance is of the same type as the rule root type if (RootType.IsInstanceOfType(e.Instance)) { // Get the rule manager for the current instance var manager = e.Instance.GetExtension <RuleManager>(); // Register the rule to run if it is not already registered if (manager.SetPendingInvocation(this, true)) { // Invoke the rule when the last model event scope exits ModelEventScope.OnExit(() => { // Mark the rule state as no longer requiring invocation manager.SetPendingInvocation(this, false); // Invoke the rule Invoke(e.Instance, e); }); } } }; } } // Allow subclasses to perform additional registration logic OnRegister(); }