using Elsa.Common.Multitenancy;
using Elsa.Expressions.Options;
using Elsa.Extensions;
using Elsa.Features.Abstractions;
using Elsa.Features.Attributes;
using Elsa.Features.Services;
using Elsa.Http.ContentWriters;
using Elsa.Http.Contracts;
using Elsa.Http.DownloadableContentHandlers;
using Elsa.Http.FileCaches;
using Elsa.Http.Handlers;
using Elsa.Http.HostedServices;
using Elsa.Http.Models;
using Elsa.Http.MultiTenancy;
using Elsa.Http.Options;
using Elsa.Http.Parsers;
using Elsa.Http.PortResolvers;
using Elsa.Http.Selectors;
using Elsa.Http.Services;
using Elsa.Http.UIHints;
using Elsa.Workflows.Contracts;
using Elsa.Workflows.Management.Requests;
using Elsa.Workflows.Management.Responses;
using FluentStorage;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
namespace Elsa.Http.Features;
///
/// Installs services related to HTTP services and activities.
///
[DependsOn(typeof(HttpJavaScriptFeature))]
public class HttpFeature : FeatureBase
{
///
public HttpFeature(IModule module) : base(module)
{
}
///
/// A delegate to configure .
///
public Action? ConfigureHttpOptions { get; set; }
///
/// A delegate to configure .
///
public Action? ConfigureHttpFileCacheOptions { get; set; }
///
/// A delegate that is invoked when authorizing an inbound HTTP request.
///
public Func HttpEndpointAuthorizationHandler { get; set; } = sp => sp.GetRequiredService();
///
/// A delegate that is invoked when an HTTP workflow faults.
///
public Func HttpEndpointWorkflowFaultHandler { get; set; } = sp => sp.GetRequiredService();
///
/// A delegate to configure the .
///
public Func ContentTypeProvider { get; set; } = _ => new FileExtensionContentTypeProvider();
///
/// A delegate to configure the .
///
public Func FileCache { get; set; } = sp =>
{
var options = sp.GetRequiredService>().Value;
var blobStorage = StorageFactory.Blobs.DirectoryFiles(options.LocalCacheDirectory);
return new BlobFileCacheStorageProvider(blobStorage);
};
///
/// A delegate to configure the used when by the activity.
///
public Action HttpClient { get; set; } = (_, _) => { };
///
/// A delegate to configure the for .
///
public Action HttpClientBuilder { get; set; } = _ => { };
///
/// A list of types to register with the service collection.
///
public ICollection HttpCorrelationIdSelectorTypes { get; } = new List
{
typeof(HeaderHttpCorrelationIdSelector),
typeof(QueryStringHttpCorrelationIdSelector)
};
///
/// A list of types to register with the service collection.
///
public ICollection HttpWorkflowInstanceIdSelectorTypes { get; } = new List
{
typeof(HeaderHttpWorkflowInstanceIdSelector),
typeof(QueryStringHttpWorkflowInstanceIdSelector)
};
///
public override void Configure()
{
Module.UseWorkflowManagement(management =>
{
management.AddVariableTypes(new[]
{
typeof(RouteData),
typeof(HttpRequest),
typeof(HttpResponse),
typeof(HttpResponseMessage),
typeof(HttpHeaders),
typeof(IFormFile),
typeof(HttpFile),
typeof(Downloadable)
}, "HTTP");
management.AddActivitiesFrom();
});
}
///
public override void ConfigureHostedServices()
{
ConfigureHostedService();
}
///
public override void Apply()
{
var configureOptions = ConfigureHttpOptions ?? (options =>
{
options.BasePath = "/workflows";
options.BaseUrl = new Uri("http://localhost");
});
var configureFileCacheOptions = ConfigureHttpFileCacheOptions ?? (options => { options.TimeToLive = TimeSpan.FromDays(7); });
Services.Configure(configureOptions);
Services.Configure(configureFileCacheOptions);
var httpClientBuilder = Services.AddHttpClient(HttpClient);
HttpClientBuilder(httpClientBuilder);
Services
.AddScoped()
.AddScoped()
.AddScoped()
.AddScoped()
.AddScoped()
.AddScoped(ContentTypeProvider)
.AddHttpContextAccessor()
// Handlers.
.AddRequestHandler()
.AddNotificationHandler()
// Content parsers.
.AddSingleton()
.AddSingleton()
.AddSingleton()
.AddSingleton()
.AddSingleton()
// HTTP content factories.
.AddScoped()
.AddScoped()
.AddScoped()
.AddScoped()
// Activity property options providers.
.AddScoped()
// Port resolvers.
.AddScoped()
// HTTP endpoint handlers.
.AddScoped()
.AddScoped()
.AddScoped()
.AddScoped(HttpEndpointWorkflowFaultHandler)
.AddScoped(HttpEndpointAuthorizationHandler)
// Downloadable content handlers.
.AddScoped()
.AddScoped()
.AddScoped()
.AddScoped()
.AddScoped()
.AddScoped()
.AddScoped()
.AddScoped()
.AddScoped()
// File caches.
.AddScoped(FileCache)
.AddScoped()
// AuthenticationBasedHttpEndpointAuthorizationHandler requires Authorization services.
// We could consider creating a separate module for installing authorization services.
.AddAuthorization();
// HTTP clients.
Services.AddHttpClient();
// Tenant resolvers.
Services.AddScoped();
Services.AddScoped();
// Add selectors.
foreach (var httpCorrelationIdSelectorType in HttpCorrelationIdSelectorTypes)
Services.AddScoped(typeof(IHttpCorrelationIdSelector), httpCorrelationIdSelectorType);
foreach (var httpWorkflowInstanceIdSelectorType in HttpWorkflowInstanceIdSelectorTypes)
Services.AddScoped(typeof(IHttpWorkflowInstanceIdSelector), httpWorkflowInstanceIdSelectorType);
Services.Configure(options =>
{
options.AddTypeAlias("HttpRequest");
options.AddTypeAlias("HttpResponse");
options.AddTypeAlias("HttpResponseMessage");
options.AddTypeAlias("HttpHeaders");
options.AddTypeAlias("RouteData");
options.AddTypeAlias("FormFile");
options.AddTypeAlias("FormFile[]");
options.AddTypeAlias("HttpFile");
options.AddTypeAlias("HttpFile[]");
options.AddTypeAlias("Downloadable");
options.AddTypeAlias("Downloadable[]");
});
}
}