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.DownloadableContentHandlers;
using Elsa.Http.FileCaches;
using Elsa.Http.Handlers;
using Elsa.Http.Options;
using Elsa.Http.Parsers;
using Elsa.Http.PortResolvers;
using Elsa.Http.Resilience;
using Elsa.Http.Selectors;
using Elsa.Http.Services;
using Elsa.Http.Tasks;
using Elsa.Http.UIHints;
using Elsa.Resilience.Extensions;
using Elsa.Resilience.Features;
using Elsa.Workflows;
using FluentStorage;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
namespace Elsa.Http.Features;
///
/// Installs services related to HTTP services and activities.
///
[DependsOn(typeof(HttpJavaScriptFeature))]
[DependsOn(typeof(ResilienceFeature))]
public class HttpFeature(IModule module) : FeatureBase(module)
{
private Func _httpEndpointRouteProvider = sp => sp.GetRequiredService();
private Func _httpEndpointBasePathProvider = sp => sp.GetRequiredService();
///
/// 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 HttpFeature WithHttpEndpointRoutesProvider() where T : IHttpEndpointRoutesProvider
{
return WithHttpEndpointRoutesProvider(sp => sp.GetRequiredService());
}
public HttpFeature WithHttpEndpointRoutesProvider(Func httpEndpointRouteProvider)
{
_httpEndpointRouteProvider = httpEndpointRouteProvider;
return this;
}
public HttpFeature WithHttpEndpointBasePathProvider() where T : class, IHttpEndpointBasePathProvider
{
Services.TryAddScoped();
return WithHttpEndpointBasePathProvider(sp => sp.GetRequiredService());
}
public HttpFeature WithHttpEndpointBasePathProvider(Func httpEndpointBasePathProvider)
{
_httpEndpointBasePathProvider = httpEndpointBasePathProvider;
return this;
}
///
public override void Configure()
{
Module.UseWorkflowManagement(management =>
{
management.AddVariableTypes([
typeof(HttpRouteData),
typeof(HttpRequest),
typeof(HttpResponse),
typeof(HttpResponseMessage),
typeof(HttpHeaders),
typeof(IFormFile),
typeof(HttpFile),
typeof(Downloadable)
], "HTTP");
management.AddActivitiesFrom();
});
Module.UseResilience(resilience => resilience.AddResilienceStrategyType());
}
///
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.
.AddNotificationHandler()
.AddNotificationHandler()
// Content parsers.
.AddSingleton()
.AddSingleton()
.AddSingleton()
.AddSingleton()
.AddSingleton()
// HTTP content factories.
.AddScoped()
.AddScoped()
.AddScoped()
.AddScoped()
// Activity property options providers.
.AddScoped()
.AddScoped()
.AddScoped(_httpEndpointBasePathProvider)
// Port resolvers.
.AddScoped()
// HTTP endpoint handlers.
.AddScoped()
.AddScoped()
.AddScoped()
.AddScoped()
.AddScoped()
.AddScoped(HttpEndpointWorkflowFaultHandler)
.AddScoped(HttpEndpointAuthorizationHandler)
.AddScoped(_httpEndpointRouteProvider)
// Startup tasks.
.AddStartupTask()
// 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();
// 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[]");
});
}
}