elsa-core/src/activities/Elsa.Activities.Http/Middleware/HttpEndpointMiddleware.cs
Sipke Schoorstra 4932dbec2a Update activity property evaluation to store computed value as part of workflow instance state
This allows bookmarking to be optimized and fixes the issue when the system needs to access an activity's property from a workflow isntance without having to re-evaluate the property value provider which may be impossible to do since there is no context.
A potential downside is increased workflow instance size, but this needs to be resolved as part of #898
2021-05-28 13:07:44 +02:00

99 lines
5.3 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Elsa.Activities.Http.Bookmarks;
using Elsa.Activities.Http.Extensions;
using Elsa.Activities.Http.Models;
using Elsa.Activities.Http.Services;
using Elsa.Persistence;
using Elsa.Persistence.Specifications.WorkflowInstances;
using Elsa.Services;
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;
using Open.Linq.AsyncExtensions;
namespace Elsa.Activities.Http.Middleware
{
public class HttpEndpointMiddleware
{
// TODO: Design multi-tenancy.
private const string? TenantId = default;
private readonly RequestDelegate _next;
public HttpEndpointMiddleware(RequestDelegate next) => _next = next;
public async Task InvokeAsync(
HttpContext httpContext,
IWorkflowLaunchpad workflowLaunchpad,
IWorkflowInstanceStore workflowInstanceStore,
IWorkflowRegistry workflowRegistry,
IWorkflowBlueprintReflector workflowBlueprintReflector,
IEnumerable<IHttpRequestBodyParser> contentParsers)
{
var cancellationToken = CancellationToken.None; // Prevent half-way request abortion (which also happens when WriteHttpResponse writes to the response).
var path = httpContext.Request.Path.Value.ToLowerInvariant();
var method = httpContext.Request.Method!.ToLowerInvariant();
var request = httpContext.Request;
request.TryGetCorrelationId(out var correlationId);
var useDispatch = httpContext.Request.GetUseDispatch();
const string activityType = nameof(HttpEndpoint);
var trigger = new HttpEndpointBookmark(path, method, null);
var bookmark = new HttpEndpointBookmark(path, method, correlationId?.ToLowerInvariant());
var collectWorkflowsContext = new CollectWorkflowsContext(activityType, bookmark, trigger, correlationId, default, default, TenantId);
var pendingWorkflows = await workflowLaunchpad.CollectWorkflowsAsync(collectWorkflowsContext, cancellationToken).ToList();
var pendingWorkflowInstanceIds = pendingWorkflows.Select(x => x.WorkflowInstanceId).Distinct();
var pendingWorkflowInstances = (await workflowInstanceStore.FindManyAsync(new WorkflowInstanceIdsSpecification(pendingWorkflowInstanceIds), cancellationToken: cancellationToken)).ToDictionary(x => x.Id);
var workflowDefinitionIds = pendingWorkflowInstances.Values.Select(x => x.DefinitionId).Distinct().ToHashSet();
var workflowBlueprints = (await workflowRegistry.FindManyAsync(x => x.IsPublished && workflowDefinitionIds.Contains(x.Id), cancellationToken)).ToDictionary(x => x.Id);
var serviceProvider = httpContext.RequestServices;
var workflowBlueprintWrappers = (await Task.WhenAll(workflowBlueprints.Values.Select(async x => await workflowBlueprintReflector.ReflectAsync(serviceProvider, x, cancellationToken)))).ToDictionary(x => x.WorkflowBlueprint.Id);
var commonInputModel = new HttpRequestModel(
new Uri(request.Path.ToString(), UriKind.Relative),
request.Method,
request.Query.ToDictionary(x => x.Key, x => x.Value.ToString()),
request.Headers.ToDictionary(x => x.Key, x => x.Value.ToString())
);
var orderedContentParsers = contentParsers.OrderByDescending(x => x.Priority).ToList();
var simpleContentType = request.ContentType?.Split(';').First();
var contentParser = orderedContentParsers.FirstOrDefault(x => x.SupportedContentTypes.Contains(simpleContentType, StringComparer.OrdinalIgnoreCase)) ?? orderedContentParsers.Last();
foreach (var pendingWorkflow in pendingWorkflows)
{
var pendingWorkflowInstance = pendingWorkflowInstances[pendingWorkflow.WorkflowInstanceId];
var workflowBlueprintWrapper = workflowBlueprintWrappers[pendingWorkflowInstance.DefinitionId];
var activityWrapper = workflowBlueprintWrapper.GetActivity<HttpEndpoint>(pendingWorkflow.ActivityId!);
var readContent = await activityWrapper!.EvaluatePropertyValueAsync(x => x.ReadContent, cancellationToken);
var inputModel = commonInputModel;
if (readContent)
{
var targetType = await activityWrapper.EvaluatePropertyValueAsync(x => x.TargetType, cancellationToken);
inputModel = inputModel with { Body = await contentParser.ParseAsync(request, targetType, cancellationToken) };
}
if (useDispatch)
await workflowLaunchpad.DispatchPendingWorkflowAsync(pendingWorkflow, inputModel, cancellationToken);
else
await workflowLaunchpad.ExecutePendingWorkflowsAsync(pendingWorkflows, inputModel, cancellationToken);
}
if (pendingWorkflows.Count > 0)
{
if (useDispatch)
{
httpContext.Response.ContentType = "application/json";
await httpContext.Response.WriteAsync(JsonConvert.SerializeObject(pendingWorkflows), cancellationToken);
}
return;
}
await _next(httpContext);
}
}
}