internal ServiceRequestEventArgs(ServiceRequest request, ServiceResponse response) { this.Request = request; this.Response = response; }
/// <summary> /// Outputs the JSON for the specified instance to the response stream. /// </summary> /// <param name="response"></param> internal ServiceResponse Invoke(ModelTransaction initChanges) { // Raise the begin request event ExoWeb.OnBeginRequest(this); // Create a response for the request ServiceResponse response = new ServiceResponse(); response.ServerInfo = new ServerInformation(); response.Changes = initChanges; try { // Set the types to return from the request response.Types = Types; // Apply view initialization changes if (Changes != null && Changes.Length > 0 && Changes[0].Source == ChangeSource.Init) { response.Changes = Changes[0].Changes; response.Changes.Perform(); } // Load root instances if (Queries != null) { foreach (var query in Queries) { query.Prepare(response); query.LoadRoots(response.Changes); } } // Preload the scope of work before applying changes PerformQueries(response, false, initChanges != null); // Apply additional changes and raise domain events ApplyChanges(response); // Load instances specified by load queries PerformQueries(response, true, initChanges != null); // Condense the transaction log if (response.Changes != null) { response.Changes.Condense(); } // Send conditions for instances loaded in the request if (response.Instances != null || response.Changes != null) { // Add instances created during the request if (response.Changes != null) { foreach (var instance in response.Changes.OfType <ModelInitEvent.InitNew>().Select(modelEvent => modelEvent.Instance)) { response.inScopeInstances.Add(instance); } } // Ensure conditions are evaluated before extracting them ExoWeb.OnEnsureConditions(response, response.inScopeInstances); // Extract conditions for all instances involved in the request Dictionary <string, List <Condition> > conditionsByType = new Dictionary <string, List <Condition> >(); foreach (var condition in response.inScopeInstances.SelectMany(instance => Condition.GetConditions(instance))) { List <Condition> conditions; if (!conditionsByType.TryGetValue(condition.Type.Code, out conditions)) { conditionsByType[condition.Type.Code] = conditions = new List <Condition>(); } if (!conditions.Contains(condition)) { conditions.Add(condition); } } response.Conditions = conditionsByType; } } finally { // Raise the end request event ExoWeb.OnEndRequest(this, response); } // Return the response return(response); }
/// <summary> /// Processes static property paths in order to determine the information to serialize. /// </summary> /// <param name="path"></param> internal static void PrepareStaticPath(string path, ServiceResponse response) { string type = null; string property = null; try { if (path.IndexOf('.') < 0) { throw new ArgumentException("'" + path + "' is not a valid static property path."); } // Split the static property reference int propertyIndex = path.LastIndexOf('.'); type = path.Substring(0, propertyIndex); property = path.Substring(propertyIndex + 1); // Get the model type ModelType modelType = ModelContext.Current.GetModelType(type); if (modelType == null) { throw new ArgumentException("'" + type + "' is not a valid model type for the static property path of '" + path + "'."); } // Get the model property ModelProperty modelProperty = modelType.Properties[property]; if (modelProperty == null || !modelProperty.IsStatic) { throw new ArgumentException("'" + property + "' is not a valid property for the static property path of '" + path + "'."); } // Add the property to the set of static properties to serialize response.GetModelTypeInfo(modelType).StaticProperties.Add(modelProperty); // Register instances for static reference properties to be serialized ModelReferenceProperty reference = modelProperty as ModelReferenceProperty; if (reference != null) { // Get the cached set of instances to be serialized for the property type ModelTypeInfo propertyTypeInfo = response.GetModelTypeInfo(reference.PropertyType); // Static lists if (reference.IsList) { foreach (ModelInstance instance in modelType.GetList(reference)) { ModelTypeInfo typeInfo = instance.Type == reference.PropertyType ? propertyTypeInfo : response.GetModelTypeInfo(instance.Type); if (!typeInfo.Instances.ContainsKey(instance.Id)) { typeInfo.Instances.Add(instance.Id, new ModelInstanceInfo(instance)); } } } // Static references else { ModelInstance instance = modelType.GetReference(reference); if (instance != null) { ModelTypeInfo typeInfo = instance.Type == reference.PropertyType ? propertyTypeInfo : response.GetModelTypeInfo(instance.Type); if (!typeInfo.Instances.ContainsKey(instance.Id)) { typeInfo.Instances.Add(instance.Id, new ModelInstanceInfo(instance)); } } } } } catch (Exception ex) { throw new ApplicationException(string.Format("Error preparing static path '{0}'{1}{2}: [{3}]", path, type == null ? string.Empty : (" for type '" + type + "'"), property == null ? string.Empty : (" and property '" + property + "'"), ex.Message), ex); } }
/// <summary> /// Prepares the query by parsing instance and static paths to determine /// what information is being requested by the query. /// </summary> /// <param name="response"></param> internal void Prepare(ServiceResponse response) { if (Include != null && Include.Length > 0) { string paths = "{"; foreach (var p in Include) { var path = p.Replace(" ", ""); if (path.StartsWith("this.")) path = path.Substring(5); else if (path.StartsWith("this{")) path = path.Substring(4); ModelPath instancePath; if (From.TryGetPath(path, out instancePath)) paths += path + ","; else PrepareStaticPath(path, response); } if (paths.Length > 1) Path = From.GetPath(paths.Substring(0, paths.Length - 1) + "}"); } }
/// <summary> /// Recursively builds up a list of instances to serialize. /// </summary> /// <param name="instance"></param> /// <param name="instances"></param> /// <param name="paths"></param> /// <param name="path"></param> static void ProcessInstance(ModelInstance instance, ModelStepList steps, bool includeInResponse, bool inScope, bool forLoad, ServiceResponse response) { // Avoid processing cached instances not included in the response if (instance.IsCached && !includeInResponse) { return; } ModelInstanceInfo instanceInfo = null; // Track the instance if the query represents a load request if (includeInResponse) { // Fetch or initialize the dictionary of instances for the type of the current instance ModelTypeInfo typeInfo = response.GetModelTypeInfo(instance.Type); // Add the current instance to the dictionary if it is not already there if (!typeInfo.Instances.TryGetValue(instance.Id, out instanceInfo)) { typeInfo.Instances[instance.Id] = instanceInfo = new ModelInstanceInfo(instance); } // Track in scope instances to limit conditions if (inScope && !instance.IsCached) { response.inScopeInstances.Add(instance); } } // Exit immediately if there are no child steps to process if (steps == null) { return; } // Process query steps for the current instance foreach (var step in steps) { // Recursively process child instances foreach (var childInstance in step.GetInstances(instance)) { ProcessInstance(childInstance, step.NextSteps, includeInResponse, inScope, forLoad, response); } // Mark value lists to be included during serialization if (step.Property.IsList && includeInResponse) { instanceInfo.IncludeList(step.Property); } } // Run all property get rules on the instance if (inScope) { if (forLoad) { instance.RunPendingPropertyGetRules(p => p is ModelValueProperty || steps.Any(s => s.Property == p)); } else { instance.RunPendingPropertyGetRules(p => p is ModelValueProperty); } } }
/// <summary> /// Raises domain events. /// </summary> void RaiseEvents(ServiceResponse response, ModelTransaction transaction) { // Process each event in the request if (Events != null) { response.Events = Events .Select((domainEvent) => { // Restore the instance to be a valid model instance if (domainEvent.Instance != null) { if (transaction != null) domainEvent.Instance = transaction.GetInstance(domainEvent.Instance.Type, domainEvent.Instance.Id); else domainEvent.Instance = domainEvent.Instance.Type.Create(domainEvent.Instance.Id); } var result = domainEvent.Raise(transaction); if (result == null) return null; ModelType type; ModelInstance[] roots; bool isList; if (ExoWeb.TryConvertQueryInstance(result, out type, out roots, out isList)) { Query newQuery = new Query(type, roots, true, isList, domainEvent.Include); newQuery.Prepare(response); Query[] newQueries = new Query[] { newQuery }; Queries = Queries == null ? newQueries : Queries.Union(newQueries).ToArray(); return isList ? (object)roots : (object)roots[0]; } else if (domainEvent.Include != null && domainEvent.Include.Length > 0) { Query newQuery = new Query(domainEvent.Instance.Type, new[] { domainEvent.Instance }, true, false, domainEvent.Include); newQuery.Prepare(response); Query[] newQueries = new Query[] { newQuery }; Queries = Queries == null ? newQueries : Queries.Union(newQueries).ToArray(); return result; } else return result; }) .ToArray(); } }
/// <summary> /// Processes static property paths in order to determine the information to serialize. /// </summary> /// <param name="path"></param> internal static void PrepareStaticPath(string path, ServiceResponse response) { string type = null; string property = null; try { if (path.IndexOf('.') < 0) throw new ArgumentException("'" + path + "' is not a valid static property path."); // Split the static property reference int propertyIndex = path.LastIndexOf('.'); type = path.Substring(0, propertyIndex); property = path.Substring(propertyIndex + 1); // Get the model type ModelType modelType = ModelContext.Current.GetModelType(type); if (modelType == null) throw new ArgumentException("'" + type + "' is not a valid model type for the static property path of '" + path + "'."); // Get the model property ModelProperty modelProperty = modelType.Properties[property]; if (modelProperty == null || !modelProperty.IsStatic) throw new ArgumentException("'" + property + "' is not a valid property for the static property path of '" + path + "'."); // Add the property to the set of static properties to serialize response.GetModelTypeInfo(modelType).StaticProperties.Add(modelProperty); // Register instances for static reference properties to be serialized ModelReferenceProperty reference = modelProperty as ModelReferenceProperty; if (reference != null) { // Get the cached set of instances to be serialized for the property type ModelTypeInfo propertyTypeInfo = response.GetModelTypeInfo(reference.PropertyType); // Static lists if (reference.IsList) { foreach (ModelInstance instance in modelType.GetList(reference)) { ModelTypeInfo typeInfo = instance.Type == reference.PropertyType ? propertyTypeInfo : response.GetModelTypeInfo(instance.Type); if (!typeInfo.Instances.ContainsKey(instance.Id)) typeInfo.Instances.Add(instance.Id, new ModelInstanceInfo(instance)); } } // Static references else { ModelInstance instance = modelType.GetReference(reference); if (instance != null) { ModelTypeInfo typeInfo = instance.Type == reference.PropertyType ? propertyTypeInfo : response.GetModelTypeInfo(instance.Type); if (!typeInfo.Instances.ContainsKey(instance.Id)) typeInfo.Instances.Add(instance.Id, new ModelInstanceInfo(instance)); } } } } catch (Exception ex) { throw new ApplicationException(string.Format("Error preparing static path '{0}'{1}{2}: [{3}]", path, type == null ? string.Empty : (" for type '" + type + "'"), property == null ? string.Empty : (" and property '" + property + "'"), ex.Message), ex); } }
private void ProcessQueryInstances(ServiceResponse response, bool forLoad) { // Recursively build up the list of instances to serialize foreach (Query query in Queries) { if ((forLoad && query.ForLoad) || query.InScope) foreach (ModelInstance root in query.Roots) ProcessInstance(root, query.Path != null ? query.Path.FirstSteps : null, forLoad && query.ForLoad, query.InScope, forLoad, response); } }
/// <summary> /// Performs the queries for the current request, either to prepare the model before /// applying changes (forLoad = false), or to actually load the model and transmit the /// requested instances to the client. /// </summary> /// <param name="response"></param> /// <param name="forLoad"></param> void PerformQueries(ServiceResponse response, bool forLoad, bool forInit) { // Load data based on the specified queries if (Queries != null) { // Record changes while processing queries if (forInit) { response.Changes.Record(() => ProcessQueryInstances(response, forLoad), evt => { if (evt.Instance.IsNew) return true; var refChange = evt as ModelReferenceChangeEvent; if (refChange != null) { return (refChange.OldValue != null && refChange.OldValue.IsNew) || (refChange.NewValue != null && refChange.NewValue.IsNew); } var listChange = evt as ModelListChangeEvent; if (listChange != null) { return (listChange.Added != null && listChange.Added.Any(i => i.IsNew)) || (listChange.Removed != null && listChange.Removed.Any(i => i.IsNew)); } return false; }); } else if (forLoad) (response.Changes ?? new ModelTransaction()).Record(() => ProcessQueryInstances(response, forLoad)); else ProcessQueryInstances(response, forLoad); } }
/// <summary> /// Apply changes and raises domain events. /// </summary> void ApplyChanges(ServiceResponse response) { // Consolidate previous changes ModelTransaction transaction = ModelTransaction.Combine((Changes ?? new ChangeSet[0]) .Where(cs => cs.Source != ChangeSource.Init) .Select(cs => cs.Changes)); // Chain the transactions about to be applied to any previously applied initialization changes if (response.Changes != null && transaction != null) response.Changes.Chain(transaction); // Apply changes and raise domain events if (transaction != null) response.Changes = transaction.Perform(() => RaiseEvents(response, transaction), MaxKnownId); // Otherwise, just raise events else response.Changes = (response.Changes ?? new ModelTransaction()).Record(() => RaiseEvents(response, null), MaxKnownId); }
/// <summary> /// Recursively builds up a list of instances to serialize. /// </summary> /// <param name="instance"></param> /// <param name="instances"></param> /// <param name="paths"></param> /// <param name="path"></param> static void ProcessInstance(ModelInstance instance, ModelStepList steps, bool includeInResponse, bool inScope, bool forLoad, ServiceResponse response) { // Avoid processing cached instances not included in the response if (instance.IsCached && !includeInResponse) return; ModelInstanceInfo instanceInfo = null; // Track the instance if the query represents a load request if (includeInResponse) { // Fetch or initialize the dictionary of instances for the type of the current instance ModelTypeInfo typeInfo = response.GetModelTypeInfo(instance.Type); // Add the current instance to the dictionary if it is not already there if (!typeInfo.Instances.TryGetValue(instance.Id, out instanceInfo)) typeInfo.Instances[instance.Id] = instanceInfo = new ModelInstanceInfo(instance); // Track in scope instances to limit conditions if (inScope && !instance.IsCached) response.inScopeInstances.Add(instance); } // Exit immediately if there are no child steps to process if (steps == null) return; // Process query steps for the current instance foreach (var step in steps) { // Recursively process child instances foreach (var childInstance in step.GetInstances(instance)) ProcessInstance(childInstance, step.NextSteps, includeInResponse, inScope, forLoad, response); // Mark value lists to be included during serialization if (step.Property.IsList && includeInResponse) instanceInfo.IncludeList(step.Property); } // Run all property get rules on the instance if (inScope) { if (forLoad) instance.RunPendingPropertyGetRules(p => p is ModelValueProperty || steps.Any(s => s.Property == p)); else instance.RunPendingPropertyGetRules(p => p is ModelValueProperty); } }
/// <summary> /// Outputs the JSON for the specified instance to the response stream. /// </summary> /// <param name="response"></param> internal ServiceResponse Invoke(ModelTransaction initChanges) { // Raise the begin request event ExoWeb.OnBeginRequest(this); // Create a response for the request ServiceResponse response = new ServiceResponse(); response.ServerInfo = new ServerInformation(); response.Changes = initChanges; try { // Set the types to return from the request response.Types = Types; // Apply view initialization changes if (Changes != null && Changes.Length > 0 && Changes[0].Source == ChangeSource.Init) { response.Changes = Changes[0].Changes; response.Changes.Perform(); } // Load root instances if (Queries != null) { foreach (var query in Queries) { query.Prepare(response); query.LoadRoots(response.Changes); } } // Preload the scope of work before applying changes PerformQueries(response, false, initChanges != null); // Apply additional changes and raise domain events ApplyChanges(response); // Load instances specified by load queries PerformQueries(response, true, initChanges != null); // Condense the transaction log if (response.Changes != null) response.Changes.Condense(); // Send conditions for instances loaded in the request if (response.Instances != null || response.Changes != null) { // Add instances created during the request if (response.Changes != null) { foreach (var instance in response.Changes.OfType<ModelInitEvent.InitNew>().Select(modelEvent => modelEvent.Instance)) response.inScopeInstances.Add(instance); } // Ensure conditions are evaluated before extracting them ExoWeb.OnEnsureConditions(response, response.inScopeInstances); // Extract conditions for all instances involved in the request Dictionary<string, List<Condition>> conditionsByType = new Dictionary<string, List<Condition>>(); foreach (var condition in response.inScopeInstances.SelectMany(instance => Condition.GetConditions(instance))) { List<Condition> conditions; if (!conditionsByType.TryGetValue(condition.Type.Code, out conditions)) conditionsByType[condition.Type.Code] = conditions = new List<Condition>(); if (!conditions.Contains(condition)) conditions.Add(condition); } response.Conditions = conditionsByType; } } finally { // Raise the end request event ExoWeb.OnEndRequest(this, response); } // Return the response return response; }