public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { var httpMethod = context.HttpContext.Request.Method; var routeValues = context.RouteData.Values; var workflowTypeEntries = await _workflowTypeRouteEntries.GetWorkflowRouteEntriesAsync(httpMethod, routeValues); var workflowEntries = await _workflowRouteEntries.GetWorkflowRouteEntriesAsync(httpMethod, routeValues); if (workflowTypeEntries.Any()) { var workflowTypeIds = workflowTypeEntries.Select(x => Int32.Parse(x.WorkflowId)).ToList(); var workflowTypes = (await _workflowTypeStore.GetAsync(workflowTypeIds)).ToDictionary(x => x.Id); var correlationId = routeValues.GetValue <string>("correlationid"); foreach (var entry in workflowTypeEntries) { if (workflowTypes.TryGetValue(Int32.Parse(entry.WorkflowId), out var workflowType)) { var activity = workflowType.Activities.Single(x => x.ActivityId == entry.ActivityId); if (activity.IsStart) { // If a singleton, try to acquire a lock per workflow type. (var locker, var locked) = await _distributedLock.TryAcquireWorkflowTypeLockAsync(workflowType); if (!locked) { continue; } await using var acquiredLock = locker; // Check if this is a workflow singleton and there's already an halted instance on any activity. if (workflowType.IsSingleton && await _workflowStore.HasHaltedInstanceAsync(workflowType.WorkflowTypeId)) { continue; } await _workflowManager.StartWorkflowAsync(workflowType, activity, null, correlationId); } } } } if (workflowEntries.Any()) { var workflowIds = workflowEntries.Select(x => x.WorkflowId).ToList(); var workflows = (await _workflowStore.GetAsync(workflowIds)).ToDictionary(x => x.WorkflowId); var correlationId = routeValues.GetValue <string>("correlationid"); foreach (var entry in workflowEntries) { if (workflows.TryGetValue(entry.WorkflowId, out var workflow) && (String.IsNullOrWhiteSpace(correlationId) || workflow.CorrelationId == correlationId)) { // If atomic, try to acquire a lock per workflow instance. (var locker, var locked) = await _distributedLock.TryAcquireWorkflowLockAsync(workflow); if (!locked) { continue; } await using var acquiredLock = locker; // If atomic, check if the workflow still exists and is still correlated. var haltedWorkflow = workflow.IsAtomic ? await _workflowStore.GetAsync(workflow.Id) : workflow; if (haltedWorkflow == null || (!String.IsNullOrWhiteSpace(correlationId) && haltedWorkflow.CorrelationId != correlationId)) { continue; } // And if it is still halted on this activity. var blockingActivity = haltedWorkflow.BlockingActivities.SingleOrDefault(x => x.ActivityId == entry.ActivityId); if (blockingActivity != null) { await _workflowManager.ResumeWorkflowAsync(haltedWorkflow, blockingActivity); } } } } await next(); }
public async Task <IActionResult> Invoke(string token) { if (!_securityTokenService.TryDecryptToken <WorkflowPayload>(token, out var payload)) { _logger.LogWarning("Invalid SAS token provided"); return(NotFound()); } // Get the workflow type. var workflowType = await _workflowTypeStore.GetAsync(payload.WorkflowId); if (workflowType == null) { if (_logger.IsEnabled(LogLevel.Warning)) { _logger.LogWarning("The provided workflow with ID '{WorkflowTypeId}' could not be found.", payload.WorkflowId); } return(NotFound()); } if (!workflowType.IsEnabled) { if (_logger.IsEnabled(LogLevel.Warning)) { _logger.LogWarning("The provided workflow with ID '{WorkflowTypeId}' is not enabled.", payload.WorkflowId); } return(NotFound()); } // Get the activity record using the activity ID provided by the token. var startActivity = workflowType.Activities.FirstOrDefault(x => x.ActivityId == payload.ActivityId); if (startActivity == null) { if (_logger.IsEnabled(LogLevel.Warning)) { _logger.LogWarning("The provided activity with ID '{ActivityId}' could not be found.", payload.ActivityId); } return(NotFound()); } // Instantiate and bind an actual HttpRequestEvent object to check its settings. var httpRequestActivity = _activityLibrary.InstantiateActivity <HttpRequestEvent>(startActivity); if (httpRequestActivity == null) { if (_logger.IsEnabled(LogLevel.Warning)) { _logger.LogWarning("Activity with name '{ActivityName}' could not be found.", startActivity.Name); } return(NotFound()); } // Check if the HttpRequestEvent is configured to perform antiforgery token validation. If so, perform the validation. if (httpRequestActivity.ValidateAntiforgeryToken && (!await _antiforgery.IsRequestValidAsync(HttpContext))) { _logger.LogWarning("Antiforgery token validation failed."); return(BadRequest()); } // If the activity is a start activity, start a new workflow. if (startActivity.IsStart) { // If a singleton, try to acquire a lock per workflow type. (var locker, var locked) = await _distributedLock.TryAcquireWorkflowTypeLockAsync(workflowType); if (locked) { await using var acquiredLock = locker; // Check if this is not a workflow singleton or there's not already an halted instance on any activity. if (!workflowType.IsSingleton || !await _workflowStore.HasHaltedInstanceAsync(workflowType.WorkflowTypeId)) { if (_logger.IsEnabled(LogLevel.Debug)) { _logger.LogDebug("Invoking new workflow of type '{WorkflowTypeId}' with start activity '{ActivityId}'", workflowType.WorkflowTypeId, startActivity.ActivityId); } await _workflowManager.StartWorkflowAsync(workflowType, startActivity); } } } else { // Otherwise, we need to resume all halted workflows on this activity. var workflows = await _workflowStore.ListAsync(workflowType.WorkflowTypeId, new[] { startActivity.ActivityId }); if (!workflows.Any()) { if (_logger.IsEnabled(LogLevel.Warning)) { _logger.LogWarning("No workflow found that is blocked on activity '{ActivityId}'", startActivity.ActivityId); } return(NotFound()); } foreach (var workflow in workflows) { var blockingActivity = workflow.BlockingActivities.FirstOrDefault(x => x.ActivityId == startActivity.ActivityId); if (blockingActivity != null) { if (_logger.IsEnabled(LogLevel.Debug)) { _logger.LogDebug("Resuming workflow with ID '{WorkflowId}' on activity '{ActivityId}'", workflow.WorkflowId, blockingActivity.ActivityId); } // If atomic, try to acquire a lock per workflow instance. (var locker, var locked) = await _distributedLock.TryAcquireWorkflowLockAsync(workflow); if (!locked) { continue; } await using var acquiredLock = locker; // If atomic, check if the workflow still exists. var haltedWorkflow = workflow.IsAtomic ? await _workflowStore.GetAsync(workflow.WorkflowId) : workflow; if (haltedWorkflow == null) { continue; } // And if it is still halted on this activity. blockingActivity = haltedWorkflow.BlockingActivities.FirstOrDefault(x => x.ActivityId == startActivity.ActivityId); if (blockingActivity != null) { await _workflowManager.ResumeWorkflowAsync(haltedWorkflow, blockingActivity); } } } } return(GetWorkflowActionResult()); }
public async Task TriggerEventAsync(string name, IDictionary <string, object> input = null, string correlationId = null, bool isExclusive = false, bool isAlwaysCorrelated = false) { var activity = _activityLibrary.GetActivityByName(name); if (activity == null) { _logger.LogError("Activity '{ActivityName}' was not found", name); return; } // Resume workflow instances halted on this kind of activity for the specified target. var haltedWorkflows = await _workflowStore.ListByActivityNameAsync(name, correlationId, isAlwaysCorrelated); foreach (var workflow in haltedWorkflows) { // Don't allow scope recursion per workflow instance id. if (_recursions.TryGetValue(workflow.WorkflowId, out var count) && count > 0) { if (_logger.IsEnabled(LogLevel.Debug)) { _logger.LogDebug("Don't allow scope recursion per workflow instance id: '{Workflow}'.", workflow.WorkflowId); } continue; } // If atomic, try to acquire a lock per workflow instance. (var locker, var locked) = await _distributedLock.TryAcquireWorkflowLockAsync(workflow); if (!locked) { continue; } await using var acquiredLock = locker; // If atomic, check if the workflow still exists and is still correlated. var haltedWorkflow = workflow.IsAtomic ? await _workflowStore.GetAsync(workflow.Id) : workflow; if (haltedWorkflow == null || (!isAlwaysCorrelated && haltedWorkflow.CorrelationId != (correlationId ?? ""))) { continue; } // Check the max recursion depth of workflow executions. if (_currentRecursionDepth > MaxRecursionDepth) { _logger.LogError("The max recursion depth of 'Workflow' executions has been reached."); break; } var blockingActivities = haltedWorkflow.BlockingActivities.Where(x => x.Name == name).ToArray(); foreach (var blockingActivity in blockingActivities) { await ResumeWorkflowAsync(haltedWorkflow, blockingActivity, input); } } // Start new workflows whose types have a corresponding starting activity. var workflowTypesToStart = await _workflowTypeStore.GetByStartActivityAsync(name); foreach (var workflowType in workflowTypesToStart) { // Don't allow scope recursion per workflow type id. if (_recursions.TryGetValue(workflowType.WorkflowTypeId, out var count) && count > 0) { if (_logger.IsEnabled(LogLevel.Debug)) { _logger.LogDebug("Don't allow scope recursion per workflow type: '{WorkflowType}'.", workflowType.Name); } continue; } // If a singleton or the event is exclusive, try to acquire a lock per workflow type. (var locker, var locked) = await _distributedLock.TryAcquireWorkflowTypeLockAsync(workflowType, isExclusive); if (!locked) { continue; } await using var acquiredLock = locker; // Check if this is a workflow singleton and there's already an halted instance on any activity. if (workflowType.IsSingleton && await _workflowStore.HasHaltedInstanceAsync(workflowType.WorkflowTypeId)) { continue; } // Check if the event is exclusive and there's already a correlated instance halted on a starting activity of this type. if (isExclusive && (await _workflowStore.ListAsync(workflowType.WorkflowTypeId, name, correlationId, isAlwaysCorrelated)) .Any(x => x.BlockingActivities.Any(x => x.Name == name && x.IsStart))) { continue; } // Check the max recursion depth of workflow executions. if (_currentRecursionDepth > MaxRecursionDepth) { _logger.LogError("The max recursion depth of 'Workflow' executions has been reached."); break; } var startActivity = workflowType.Activities.First(x => x.IsStart && x.Name == name); await StartWorkflowAsync(workflowType, startActivity, input, correlationId); } }