Ejemplo n.º 1
0
        /// <summary>
        /// Invokes the rule on the specified <see cref="ModelInstance"/> as a result
        /// of the specified triggering <see cref="ModelEvent"/>
        /// </summary>
        /// <param name="root"></param>
        /// <param name="modelEvent"></param>
        internal void Invoke(ModelInstance root, ModelEvent modelEvent)
        {
            try
            {
                if (BeforeInvoke != null)
                {
                    BeforeInvoke(this, EventArgs.Empty);
                }

                ModelEventScope.Perform(() => OnInvoke(root, modelEvent));
            }
            finally
            {
                if (AfterInvoke != null)
                {
                    AfterInvoke(this, EventArgs.Empty);
                }
            }
        }
Ejemplo n.º 2
0
        /// <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();
        }
Ejemplo n.º 3
0
        /// <summary>
        /// Generates a script tag to embed a model including instance data into a server-rendered view.
        /// Supports initial customization of the model via an initialization delegate.
        /// </summary>
        /// <param name="query"></param>
        /// <param name="init"></param>
        /// <returns></returns>
        static void Model(TextWriter writer, object query, Delegate init)
        {
            // Raise the begin model event
            if (BeginModel != null)
            {
                BeginModel(query, EventArgs.Empty);
            }

            // Get the roots for each query
            var roots = new List <KeyValuePair <string, ServiceRequest.Query> >();

            foreach (var prop in query.GetType().GetProperties())
            {
                // Get the value of the model property
                var m = prop.GetValue(query, null);
                if (m == null)
                {
                    continue;
                }

                // Convert the value into a query, if necessary
                var q = m as ServiceRequest.Query ?? Query(m);
                if (q != null)
                {
                    q.LoadRoots(null);
                    roots.Add(new KeyValuePair <string, ServiceRequest.Query>(prop.Name, q));
                }
            }

            // Create the request object
            ServiceRequest request = new ServiceRequest(roots.Select(r => r.Value).ToArray());

            // Synthesize init new changes for new query roots
            ModelTransaction initChanges = (ModelTransaction)request.Queries
                                           .SelectMany(q => q.Roots)
                                           .Where(i => i.IsNew)
                                           .Select(i => new ModelInitEvent.InitNew(i))
                                           .Cast <ModelEvent>()
                                           .ToList();

            // Perform the initialization action, if specified
            if (init != null)
            {
                // Determine matching parameters
                var parameters = new object[init.Method.GetParameters().Length];
                foreach (var p in init.Method.GetParameters())
                {
                    foreach (var root in roots)
                    {
                        if (p.Name.Equals(root.Key, StringComparison.InvariantCultureIgnoreCase))
                        {
                            // List parameter
                            if (p.ParameterType.IsArray)
                            {
                                parameters[p.Position] = root.Value.Roots.Select(r => r.Instance).ToArray();
                            }

                            // Instance parameter
                            else
                            {
                                parameters[p.Position] = root.Value.Roots.Select(r => r.Instance).FirstOrDefault();
                            }

                            break;
                        }
                    }

                    // Throw an exception if the model property value cannot be cast to the corresponding initialization parameter
                    if (parameters[p.Position] != null && !p.ParameterType.IsAssignableFrom(parameters[p.Position].GetType()))
                    {
                        throw new ArgumentException(String.Format("The model property '{0}' cannot be converted into the initialization parameter type of '{1}'.", p.Name, p.ParameterType.FullName));
                    }

                    // Throw an exception if a valid model parameter could not be found to pass to the initialization delegate
                    if (parameters[p.Position] == null && query.GetType().GetProperty(p.Name, BindingFlags.Public | BindingFlags.IgnoreCase | BindingFlags.Instance) == null)
                    {
                        throw new ArgumentException(String.Format("A model property could not be found to pass to the initialization parameter '{0}'.", p.Name));
                    }
                }

                // Perform initialization while capturing changes
                initChanges.Record(() =>
                {
                    // Prevent property change rules from running until after initialization
                    ModelEventScope.Perform(() => init.DynamicInvoke(parameters));
                });
            }

            // Invoke the request queries
            ServiceResponse response = request.Invoke(initChanges);

            response.Model = roots.ToDictionary(r => r.Key, r => r.Value);
            foreach (var q in response.Model.Values)
            {
                q.ReducePaths();
            }

            // Track the current model roots to support server template rendering
            foreach (var root in response.Model)
            {
                Templates.Page.Current.Model[root.Key] = root.Value.IsList ? (object)root.Value.Roots : (root.Value.Roots.Length == 1 ?root.Value.Roots[0] : null);
            }

            // Write the model embed script to the writer
            writer.Write("<script type=\"text/javascript\">$exoweb(");
            JsonUtility.Serialize(writer, response);
            writer.Write(");</script>");

            // Raise the end model event
            if (EndModel != null)
            {
                EndModel(query, EventArgs.Empty);
            }
        }