/// <summary> /// Validates the inputs. Returns true if all of the UI controls contain valid values. Otherwise, returns false. /// </summary> /// <returns>true if all of the UI controls contain valid values. Otherwise, false.</returns> public override bool ValidateInputs() { Logger.Instance.WriteMethodEntry(); try { // Use the controller to validate standard activity controls // and return false if a problem was identified if (!this.controller.ValidateInputs()) { return false; } try { // Verify that the target lookup is valid LookupEvaluator targetLookup = new LookupEvaluator(this.publicationTarget.Value); if (!targetLookup.IsValidTarget) { this.controller.ValidationError = string.Format(CultureInfo.CurrentUICulture, ActivitySettings.TargetLookupValidationError, this.publicationTarget.Value); return false; } } catch (WorkflowActivityLibraryException ex) { // If an exception was thrown while attempting to parse lookups, report the error this.controller.ValidationError = ex.Message; return false; } // Verify that the supplied conflict filter is a valid XPath query if (!ExpressionEvaluator.IsXPath(this.conflictFilter.Value)) { this.controller.ValidationError = ActivitySettings.ConflictFilterValidationError; return false; } // Verify that the supplied conflict filter contains the [//Value] lookup if (!this.conflictFilter.Value.ToUpperInvariant().Contains("[//VALUE]")) { this.controller.ValidationError = ActivitySettings.ConflictFilterValueLookupValidationError; return false; } ExpressionEvaluator evaluator = new ExpressionEvaluator(); if (this.queryLdap.Value) { // Loop through all active query listings and make sure they are valid foreach (DefinitionListing query in this.ldapQueries.DefinitionListings.Where(query => query.Active)) { // If a value is missing for key or query, the definition // will be null and the listing fails validation if (query.Definition == null) { this.controller.ValidationError = ActivitySettings.LdapQueryDefinitionValidationError; return false; } // Make sure that the specified XPath filter is properly formatted if (!query.Definition.Right.ToUpperInvariant().Contains("[//VALUE]")) { this.controller.ValidationError = ActivitySettings.LdapQueryDefinitionValueLookupValidationError; return false; } // If it's an expression, make sure it's properly formatted if (ExpressionEvaluator.IsExpression(query.Definition.Left)) { try { evaluator.ParseExpression(query.Definition.Left); } catch (WorkflowActivityLibraryException ex) { this.controller.ValidationError = ex.Message; return false; } } } } // Count the active value expressions int count = this.valueExpressions.DefinitionListings.Count(valueExpression => valueExpression.Active); // Loop through all active update listings and make sure they are valid int i = 1; foreach (DefinitionListing valueExpression in this.valueExpressions.DefinitionListings.Where(valueExpression => valueExpression.Active)) { if (string.IsNullOrEmpty(valueExpression.State.Left)) { // If a value is missing for the expression, fail validation this.controller.ValidationError = ActivitySettings.ValueExpressionValidationError; return false; } // Attempt to parse the value expression try { evaluator.ParseExpression(valueExpression.State.Left); } catch (WorkflowActivityLibraryException ex) { this.controller.ValidationError = ex.Message; return false; } // Verify that the [//UniquenessKey] lookup is only present in the last value expression bool containsKey = valueExpression.State.Left.ToUpperInvariant().Contains("[//UNIQUENESSKEY]"); if (i < count && containsKey) { this.controller.ValidationError = ActivitySettings.ValueExpressionUniquenessKeyValidationError; return false; } if (i == count && !containsKey) { this.controller.ValidationError = ActivitySettings.ValueExpressionMissingUniquenessKeyValidationError; return false; } i += 1; } try { if (!string.IsNullOrEmpty(this.activityExecutionCondition.Value)) { evaluator.ParseExpression(this.activityExecutionCondition.Value); // Verify that the activity execution condition resolves to a Boolean value if (!evaluator.IsBooleanExpression(this.activityExecutionCondition.Value)) { this.controller.ValidationError = ActivitySettings.ActivityExecutionConditionValidationError; return false; } } } catch (WorkflowActivityLibraryException ex) { this.controller.ValidationError = ex.Message; return false; } // If no errors were found, clear any validation error and return true this.controller.ValidationError = string.Empty; return true; } catch (Exception e) { Logger.Instance.ReportError(e); throw; } finally { Logger.Instance.WriteMethodExit(); } }
/// <summary> /// Handles the ExecuteCode event of the Prepare CodeActivity. /// </summary> /// <param name="sender">The source of the event.</param> /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param> private void Prepare_ExecuteCode(object sender, EventArgs e) { Logger.Instance.WriteMethodEntry(EventIdentifier.ResolveLookupsPrepareExecuteCode); try { // Get the parent workflow so we can use it to pull request target, requestor, etc. // and access the workflow dictionary SequentialWorkflow parentWorkflow; if (!SequentialWorkflow.TryGetContainingWorkflow(this, out parentWorkflow)) { throw Logger.Instance.ReportError(EventIdentifier.ResolveLookupsPrepareExecuteCodeUnableToGetParentWorkflowError, new InvalidOperationException(Messages.UnableToGetParentWorkflowError)); } // Determine if the parent workflow is an authorization workflow running against a create request // In this situation, lookups against target will fail because the resource does not yet exist. // We will need to convert [//Target/...] to [//Delta/...] to facilitate resolution // This behavior replicates FIM's native behavior if (this.Request.CurrentRequest.Operation == OperationType.Create && this.Request.CurrentRequest.AuthorizationWorkflowInstances != null) { foreach (UniqueIdentifier ui in this.Request.CurrentRequest.AuthorizationWorkflowInstances) { Guid authZWorkflowInstanceId; if (ui.TryGetGuid(out authZWorkflowInstanceId) && authZWorkflowInstanceId == this.WorkflowInstanceId) { this.authZCreate = true; break; } } } foreach (string s in this.Lookups.Keys) { Logger.Instance.WriteVerbose(EventIdentifier.ResolveLookupsPrepareExecuteCode, "Constructing lookup evaluator for lookup: '{0}'.", s); LookupEvaluator originalLookup = new LookupEvaluator(s); LookupEvaluator resolvableLookup = new LookupEvaluator(s); // There are certain conditions which will prompt the activity to dynamically replace the original lookup with an alternate // This is done at the paramater, swapping [//Effective/...] with [//Delta/...] or [//Target/...], for example if (this.authZCreate && (originalLookup.Parameter == LookupParameter.Target || originalLookup.Parameter == LookupParameter.Effective)) { Logger.Instance.WriteVerbose(EventIdentifier.ResolveLookupsPrepareExecuteCode, "The lookup '{0}' will be processed as [//Delta] lookup.", s); // When resolving a [//Target/...] or [//Effective/...] lookup during authZ on a create request, // we will always pull the value from the request delta because the target resource will not yet be available for read string lookupString = s.Replace(string.Format(CultureInfo.InvariantCulture, "[//{0}/", s.Substring(3, originalLookup.Parameter.ToString().Length)), string.Format(CultureInfo.InvariantCulture, "[//{0}/", LookupParameter.Delta)); resolvableLookup = new LookupEvaluator(lookupString); } else if (originalLookup.Parameter == LookupParameter.Effective) { // When resolving a [//Effective/...] lookup, we need to determine if we should resolve against the request delta or the target resource // If the attribute tied to the Effective parameter exists in the request parameters, the associated value will always supersede the // attribute value on the target resource bool useDelta = false; if (originalLookup.Components.Count > 1) { ReadOnlyCollection<CreateRequestParameter> requestParameters = this.Request.CurrentRequest.ParseParameters<CreateRequestParameter>(); var query = from p in requestParameters where p.PropertyName.Equals(originalLookup.Components[1], StringComparison.OrdinalIgnoreCase) select p.PropertyName; if (query.Any()) { useDelta = true; Logger.Instance.WriteVerbose(EventIdentifier.ResolveLookupsPrepareExecuteCode, "The [//Effective] lookup '{0}' will be processed as [//Delta] lookup.", s); } } // Either replace [//Effective/...] with [//Delta/...] or [//Target/...] to facilitate resolution // The activity will look for this lookup when resolving the original effective lookup string lookupString = s.Replace(string.Format(CultureInfo.InvariantCulture, "[//{0}/", s.Substring(3, originalLookup.Parameter.ToString().Length)), useDelta ? string.Format(CultureInfo.InvariantCulture, "[//{0}/", LookupParameter.Delta) : string.Format(CultureInfo.InvariantCulture, "[//{0}/", LookupParameter.Target)); resolvableLookup = new LookupEvaluator(lookupString); } // Add the lookup parameter to the list of parameters for the activity so we know which resolutions are required // Also establish a new entry in the values dictionary // If the resolvable lookup is different than the original, add the pair to the alternates dictionary so we can find it later during resolution if (!this.parameters.Contains(resolvableLookup.Parameter)) { this.parameters.Add(resolvableLookup.Parameter); } if (!this.values.ContainsKey(resolvableLookup.Lookup)) { this.values.Add(resolvableLookup.Lookup, new List<object>()); } if (originalLookup.Lookup != resolvableLookup.Lookup) { this.alternateLookups.Add(originalLookup.Lookup, resolvableLookup.Lookup); } // Add each of the lookup's reads to the read dictionary which represents all lookups // This single dictionary will ensure the minimum number of reads are performed foreach (string key in resolvableLookup.Reads.Keys) { if (!this.reads.ContainsKey(key)) { this.reads.Add(key, new List<string> { resolvableLookup.Reads[key] }); } else if (!this.reads[key].Contains(resolvableLookup.Reads[key])) { this.reads[key].Add(resolvableLookup.Reads[key]); } } // If resolving a [//WorkflowData/...] lookup, publish the value of the specified // workflow dictionary key to the resources or values dictionary // Make sure we only do this once for each workflow dictionary key because the publish method will stack values if it's executed more than once if (resolvableLookup.Parameter == LookupParameter.WorkflowData && resolvableLookup.Components.Count > 1 && parentWorkflow.WorkflowDictionary.ContainsKey(resolvableLookup.Components[1]) && parentWorkflow.WorkflowDictionary[resolvableLookup.Components[1]] != null && !this.values.ContainsKey(string.Format(CultureInfo.InvariantCulture, "{0}/{1}", LookupParameter.WorkflowData, resolvableLookup.Components[1]))) { this.Publish(string.Format(CultureInfo.InvariantCulture, "{0}/{1}", LookupParameter.WorkflowData, resolvableLookup.Components[1]), parentWorkflow.WorkflowDictionary[resolvableLookup.Components[1]]); } } // If we have reads, add the keys of the read dictionary to the for each loop which will perform them if (this.reads.Count > 0) { this.ForEachRead.InitialChildData = this.reads.Keys.ToArray(); } // Publish Request, Requestor, and Target to the resource and values dictionary // regardless of lookup parameter this.Publish(LookupParameter.Request.ToString(), parentWorkflow.RequestId); this.Publish(LookupParameter.Requestor.ToString(), parentWorkflow.ActorId); if (this.parameters.Contains(LookupParameter.Target)) { this.Publish(LookupParameter.Target.ToString(), parentWorkflow.TargetId); } // Publish the supplied value for [//Value] if (this.parameters.Contains(LookupParameter.Value)) { this.Publish(LookupParameter.Value.ToString(), this.Value); } // Resolve lookups associated with the [//Delta/...] parameter if (this.parameters.Contains(LookupParameter.Delta)) { if (this.Request.CurrentRequest.Operation != OperationType.SystemEvent) { this.PublishRequestDelta(this.Request.CurrentRequest, false); } } // If necessary, add the supplied query results to the resource dictionary // to support resolution of [//Queries/...] lookups if (this.parameters.Contains(LookupParameter.Queries)) { if (this.QueryResults != null) { foreach (string query in this.QueryResults.Keys) { this.Publish(string.Format(CultureInfo.InvariantCulture, "{0}/{1}", LookupParameter.Queries, query), this.QueryResults[query]); } } else { Logger.Instance.WriteVerbose(EventIdentifier.ResolveLookupsPrepareExecuteCode, "The [//Queries/...] lookup(s) don't have any supplied results."); } } // Preemptively build the XPath filter which identifies approvals associated with the request // The activity will only be used if we are resolving a [//Approvers/...] lookup this.FindApprovals.XPathFilter = string.Format(CultureInfo.InvariantCulture, "/Approval[Request = '{0}']", parentWorkflow.RequestId); } finally { Logger.Instance.WriteMethodExit(EventIdentifier.ResolveLookupsPrepareExecuteCode); } }
/// <summary> /// Validates the inputs. Returns true if all of the UI controls contain valid values. Otherwise, returns false. /// </summary> /// <returns>true if all of the UI controls contain valid values. Otherwise, false.</returns> public override bool ValidateInputs() { Logger.Instance.WriteMethodEntry(); try { // Use the controller to validate standard activity controls // and return false if a problem was identified if (!this.controller.ValidateInputs()) { return false; } ExpressionEvaluator evaluator = new ExpressionEvaluator(); // Verify that the resource type is valid if (!Regex.Match(this.resourceType.Value, RegexPattern).Success) { this.controller.ValidationError = string.Format(CultureInfo.CurrentUICulture, ActivitySettings.ResourceTypeValidationError, this.resourceType.Value); return false; } if (this.advanced.Value) { if (this.queryResources.Value) { // Loop through all active query listings and make sure they are valid foreach (DefinitionListing query in this.queries.DefinitionListings) { if (query.Active) { // If a value is missing for key or query, the definition // will be null and the listing fails validation if (query.Definition == null) { this.controller.ValidationError = ActivitySettings.QueryDefinitionValidationError; return false; } // Make sure that the specified query key is properly formatted if (!Regex.Match(query.Definition.Left, RegexPattern).Success) { this.controller.ValidationError = string.Format(CultureInfo.CurrentUICulture, ActivitySettings.QueryDefintionLeftValidationError, query.Definition.Left); return false; } // Make sure that the specified XPath filter is properly formatted if (!ExpressionEvaluator.IsXPath(query.Definition.Right)) { this.controller.ValidationError = string.Format(CultureInfo.CurrentUICulture, ActivitySettings.QueryDefintionRightValidationError, query.Definition.Left); return false; } } } } try { if (!string.IsNullOrEmpty(this.activityExecutionCondition.Value)) { evaluator.ParseExpression(this.activityExecutionCondition.Value); // Verify that the activity execution condition resolves to a Boolean value if (!evaluator.IsBooleanExpression(this.activityExecutionCondition.Value)) { this.controller.ValidationError = ActivitySettings.ActivityExecutionConditionValidationError; return false; } } if (!string.IsNullOrEmpty(this.iteration.Value)) { evaluator.ParseExpression(this.iteration.Value); } if (GetActorType(this.actorType.Value) == ActorType.Resolve) { evaluator.ParseExpression(this.actorString.Value); } // Verify that the supplied conflict filter is a valid XPath query, if necessary if (this.checkForConflict.Value && !ExpressionEvaluator.IsXPath(this.conflictFilter.Value)) { this.controller.ValidationError = ActivitySettings.ConflictResourceSearchFilterValidationError; return false; } // If necessary, parse the created resource ID target lookup to ensure its validity if (!string.IsNullOrEmpty(this.createdResourceIdTarget.Value)) { LookupEvaluator createdTargetLookup = new LookupEvaluator(this.createdResourceIdTarget.Value); if (!createdTargetLookup.IsValidTarget) { this.controller.ValidationError = string.Format(CultureInfo.CurrentUICulture, ActivitySettings.TargetLookupValidationError, this.createdResourceIdTarget.Value); return false; } } // If necessary, parse the conflicting resource ID target lookup to ensure its validity if (this.checkForConflict.Value && !string.IsNullOrEmpty(this.conflictingResourceIdTarget.Value)) { LookupEvaluator conflictingTargetLookup = new LookupEvaluator(this.conflictingResourceIdTarget.Value); if (!conflictingTargetLookup.IsValidTarget) { this.controller.ValidationError = string.Format(CultureInfo.CurrentUICulture, ActivitySettings.TargetLookupValidationError, this.conflictingResourceIdTarget.Value); return false; } } } catch (WorkflowActivityLibraryException ex) { // If an exception was thrown while attempting to parse lookups, report the error this.controller.ValidationError = ex.Message; return false; } if (this.applyAuthorizationPolicy.Value && GetActorType(this.actorType.Value) == ActorType.Service) { this.controller.ValidationError = ActivitySettings.RequestActorValidationError; return false; } } // Loop through all active definition listings and make sure they are valid foreach (DefinitionListing listing in this.attributes.DefinitionListings) { if (!listing.Active) { continue; } if (listing.Definition == null) { // If a value is missing for source or target, the definition // will be null and the listing fails validation // Because the activity allows for the creation of a resource without any attributes, // we need to check if this is the only active listing for the form and, if so, // verify that all fields have been left blank before failing validation int countActive = this.attributes.DefinitionListings.Count(l => l.Active); if (countActive != 1 || !string.IsNullOrEmpty(listing.State.Left) || !string.IsNullOrEmpty(listing.State.Right)) { this.controller.ValidationError = ActivitySettings.AttributeDefinitionValidationError; return false; } } else { // Attempt to parse the source expression and fail validation // if an exception is thrown by the expression evaluator try { evaluator.ParseExpression(listing.Definition.Left); } catch (WorkflowActivityLibraryException ex) { this.controller.ValidationError = ex.Message; return false; } ParameterType targetType = ParameterType.String; try { targetType = ExpressionEvaluator.DetermineParameterType(listing.Definition.Right, true); } catch (WorkflowActivityLibraryException) { } // Target variables are valid // For anything else, make sure that the target attribute matches the regular // expression for FIM attributes if (targetType != ParameterType.Variable) { if (!Regex.Match(listing.Definition.Right, RegexPattern).Success) { this.controller.ValidationError = string.Format(CultureInfo.CurrentUICulture, ActivitySettings.TargetValidationError, listing.Definition.Right); return false; } } } } // Verify that no [//Query/...] or [//Value/...] expressions exist // if the query resources or iteration options are not enabled, respectively bool containsQueryExpressions = false; bool containsValueExpressions = false; foreach (LookupEvaluator lookup in evaluator.LookupCache.Keys.Select(key => new LookupEvaluator(key))) { if (lookup.Parameter == LookupParameter.Queries) { containsQueryExpressions = true; } if (lookup.Parameter == LookupParameter.Value) { containsValueExpressions = true; } } if (!this.queryResources.Value && containsQueryExpressions) { this.controller.ValidationError = ActivitySettings.QueryResourcesValidationError; return false; } if (string.IsNullOrEmpty(this.iteration.Value) && containsValueExpressions) { this.controller.ValidationError = ActivitySettings.IterationValidationError; return false; } // If no errors were found, clear any validation error and return true this.controller.ValidationError = string.Empty; return true; } catch (Exception e) { Logger.Instance.ReportError(e); throw; } finally { Logger.Instance.WriteMethodExit(); } }
/// <summary> /// Validates the inputs. Returns true if all of the UI controls contain valid values. Otherwise, returns false. /// </summary> /// <returns>true if all of the UI controls contain valid values. Otherwise, false.</returns> public override bool ValidateInputs() { Logger.Instance.WriteMethodEntry(); try { // Use the controller to validate standard activity controls // and return false if a problem was identified if (!this.controller.ValidateInputs()) { return false; } ExpressionEvaluator evaluator = new ExpressionEvaluator(); if (this.advanced.Value) { if (this.queryResources.Value) { // Loop through all active query listings and make sure they are valid foreach (DefinitionListing query in this.queries.DefinitionListings.Where(query => query.Active)) { // If a value is missing for key or query, the definition // will be null and the listing fails validation if (query.Definition == null) { this.controller.ValidationError = ActivitySettings.QueryDefinitionValidationError; return false; } // Make sure that the specified query key is properly formatted if (!Regex.Match(query.Definition.Left, RegexPattern).Success) { this.controller.ValidationError = string.Format(CultureInfo.CurrentUICulture, ActivitySettings.QueryDefintionLeftValidationError, query.Definition.Left); return false; } // Make sure that the specified XPath filter is properly formatted if (!ExpressionEvaluator.IsXPath(query.Definition.Right)) { this.controller.ValidationError = string.Format(CultureInfo.CurrentUICulture, ActivitySettings.QueryDefintionRightValidationError, query.Definition.Left); return false; } } } try { if (!string.IsNullOrEmpty(this.activityExecutionCondition.Value)) { evaluator.ParseExpression(this.activityExecutionCondition.Value); // Verify that the activity execution condition resolves to a Boolean value if (!evaluator.IsBooleanExpression(this.activityExecutionCondition.Value)) { this.controller.ValidationError = ActivitySettings.ActivityExecutionConditionValidationError; return false; } } if (!string.IsNullOrEmpty(this.iteration.Value)) { evaluator.ParseExpression(this.iteration.Value); } if (GetActorType(this.actorType.Value) == ActorType.Resolve) { evaluator.ParseExpression(this.actorString.Value); } } catch (WorkflowActivityLibraryException ex) { this.controller.ValidationError = ex.Message; return false; } if (this.applyAuthorizationPolicy.Value && GetActorType(this.actorType.Value) == ActorType.Service) { this.controller.ValidationError = ActivitySettings.RequestActorValidationError; return false; } } // Loop through all active update listings and make sure they are valid foreach (DefinitionListing update in this.updates.DefinitionListings.Where(update => update.Active)) { if (update.Definition == null) { // If a value is missing for source or target, the definition // will be null and the listing fails validation this.controller.ValidationError = ActivitySettings.UpdateDefinitionValidationError; return false; } // Attempt to parse the source expression and target lookup or variable // Fail validation if an exception is thrown for either try { evaluator.ParseExpression(update.Definition.Left); ParameterType targetType = ParameterType.Lookup; try { targetType = ExpressionEvaluator.DetermineParameterType(update.Definition.Right); } catch (WorkflowActivityLibraryException) { } // Target variables are valid // Target lookups require further evaluation to determine if they represent a valid target if (targetType != ParameterType.Variable) { LookupEvaluator lookup = new LookupEvaluator(update.Definition.Right); if (!lookup.IsValidTarget) { this.controller.ValidationError = string.Format(CultureInfo.CurrentUICulture, ActivitySettings.TargetLookupValidationError, update.Definition.Right); return false; } } } catch (WorkflowActivityLibraryException ex) { this.controller.ValidationError = ex.Message; return false; } } // Verify that no [//Query/...] or [//Value/...] expressions exist // if the query resources or iteration options are not enabled, respectively bool containsQueryExpressions = false; bool containsValueExpressions = false; foreach (LookupEvaluator lookup in evaluator.LookupCache.Keys.Select(key => new LookupEvaluator(key))) { if (lookup.Parameter == LookupParameter.Queries) { containsQueryExpressions = true; } if (lookup.Parameter == LookupParameter.Value) { containsValueExpressions = true; } } if (!this.queryResources.Value && containsQueryExpressions) { this.controller.ValidationError = ActivitySettings.QueryResourcesValidationError; return false; } if (string.IsNullOrEmpty(this.iteration.Value) && containsValueExpressions) { this.controller.ValidationError = ActivitySettings.IterationValidationError; return false; } // If no errors were found, clear any validation error and return true this.controller.ValidationError = string.Empty; return true; } catch (Exception e) { Logger.Instance.ReportError(e); throw; } finally { Logger.Instance.WriteMethodExit(); } }
/// <summary> /// Handles the ExecuteCode event of the BuildRequests CodeActivity. /// </summary> /// <param name="sender">The source of the event.</param> /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param> private void BuildRequests_ExecuteCode(object sender, EventArgs e) { Logger.Instance.WriteMethodEntry(EventIdentifier.UpdateLookupsBuildRequestsExecuteCode); try { foreach (UpdateLookupDefinition update in this.UpdateLookupDefinitions) { LookupEvaluator lookup = new LookupEvaluator(update.TargetLookup); if (lookup.TargetIsWorkflowDictionary) { // Get the parent workflow to facilitate access to the workflow dictionary SequentialWorkflow parentWorkflow; SequentialWorkflow.TryGetContainingWorkflow(this, out parentWorkflow); // For simplicity, start by adding the key to the parent workflow dictionary so we can assume that its there if (!parentWorkflow.WorkflowDictionary.ContainsKey(lookup.TargetAttribute)) { parentWorkflow.WorkflowDictionary.Add(lookup.TargetAttribute, null); } if (update.Mode == UpdateMode.Modify) { parentWorkflow.WorkflowDictionary[lookup.TargetAttribute] = update.Value; } else if (update.Value != null) { // Use reflection to determine the expected List<> type based on the value // Also get the Add and Remove methods for the list Type listType = typeof(List<>).MakeGenericType(new Type[] { update.Value.GetType() }); MethodInfo add = listType.GetMethod("Add"); MethodInfo remove = listType.GetMethod("Remove"); switch (update.Mode) { case UpdateMode.Insert: if (parentWorkflow.WorkflowDictionary[lookup.TargetAttribute] == null) { parentWorkflow.WorkflowDictionary[lookup.TargetAttribute] = update.Value; } else if (parentWorkflow.WorkflowDictionary[lookup.TargetAttribute].GetType() == update.Value.GetType()) { // Single value, create a new instance of the appropriate List<> type // and add both values: existing and new object existingValue = parentWorkflow.WorkflowDictionary[lookup.TargetAttribute]; parentWorkflow.WorkflowDictionary[lookup.TargetAttribute] = Activator.CreateInstance(listType); add.Invoke(parentWorkflow.WorkflowDictionary[lookup.TargetAttribute], new object[] { existingValue }); add.Invoke(parentWorkflow.WorkflowDictionary[lookup.TargetAttribute], new object[] { update.Value }); } else if (parentWorkflow.WorkflowDictionary[lookup.TargetAttribute].GetType() == listType) { // The dictionary key is a list of the expected type, add the value add.Invoke(parentWorkflow.WorkflowDictionary[lookup.TargetAttribute], new object[] { update.Value }); } else { // We have a problem and need to report an error throw Logger.Instance.ReportError(new WorkflowActivityLibraryException(Messages.UpdateLookup_InsertVariableError, update.Value.GetType(), lookup.TargetAttribute, parentWorkflow.WorkflowDictionary[lookup.TargetAttribute].GetType())); } break; case UpdateMode.Remove: if (parentWorkflow.WorkflowDictionary[lookup.TargetAttribute] == null) { // Do nothing } else if (parentWorkflow.WorkflowDictionary[lookup.TargetAttribute].Equals(update.Value)) { // A single matching value exists, clear the variable parentWorkflow.WorkflowDictionary[lookup.TargetAttribute] = null; } else if (parentWorkflow.WorkflowDictionary[lookup.TargetAttribute].GetType() == listType) { // The variable is a list of the expected type, attempt to remove the value remove.Invoke(parentWorkflow.WorkflowDictionary[lookup.TargetAttribute], new object[] { update.Value }); // Check the count on the list to determine if we are down to a single value or have no value // If so, adjust the value of the variable accordingly to eliminate the list object listValue = null; int i = 0; foreach (object o in (IEnumerable)parentWorkflow.WorkflowDictionary[lookup.TargetAttribute]) { i += 1; listValue = o; } if (i <= 1) { parentWorkflow.WorkflowDictionary[lookup.TargetAttribute] = listValue; } } break; } } // If we ended up with a null value in the workflow dictionary key, // remove the key to cleanup after ourselves if (parentWorkflow.WorkflowDictionary[lookup.TargetAttribute] == null) { parentWorkflow.WorkflowDictionary.Remove(lookup.TargetAttribute); } } else if (!string.IsNullOrEmpty(lookup.TargetResourceLookup) && this.TargetLookups.ContainsKey(lookup.TargetResourceLookup) && this.TargetLookups[lookup.TargetResourceLookup] != null) { // Based on the type of the resolved target lookup (should be Guid or List<Guid>) // build the list of target resources for the update List<Guid> targets = new List<Guid>(); if (this.TargetLookups[lookup.TargetResourceLookup] is Guid) { targets.Add((Guid)this.TargetLookups[lookup.TargetResourceLookup]); } else if (this.TargetLookups[lookup.TargetResourceLookup].GetType() == typeof(List<Guid>)) { targets.AddRange((List<Guid>)this.TargetLookups[lookup.TargetResourceLookup]); } foreach (Guid target in targets) { // Add the target to the update requests dictionary, if it doesn't already exist, // and add the new update request parameter if (!this.PendingRequests.ContainsKey(target)) { this.PendingRequests.Add(target, new List<UpdateRequestParameter>()); } this.PendingRequests[target].Add(new UpdateRequestParameter(lookup.TargetAttribute, update.Mode, update.Value)); } } } // If there are requests that need to be submitted to fulfill the updates, // assign the list of targets to the for each loop which will evaluate each change // to determine if they will result in changes if (this.PendingRequests.Count > 0) { this.ForEachPending.InitialChildData = this.PendingRequests.Keys.ToList(); } Logger.Instance.WriteVerbose(EventIdentifier.UpdateLookupsBuildRequestsExecuteCode, "The number of requests that need to be submitted to fulfill the updates: '{0}'.", this.PendingRequests.Count); } finally { Logger.Instance.WriteMethodExit(EventIdentifier.UpdateLookupsBuildRequestsExecuteCode); } }