using System.Net; using Elsa.Extensions; using Elsa.Features.Abstractions; using Elsa.Features.Attributes; using Elsa.Features.Services; using Elsa.Http.Bookmarks; 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.TriggerPayloadValidators; using Elsa.Http.UIHints; using Elsa.Resilience.Extensions; using Elsa.Resilience.Features; using Elsa.Workflows; using Elsa.Workflows.Options; using FluentStorage; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.StaticFiles; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; using Elsa.Common.Serialization; 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 and activities. /// 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() // Graceful shutdown: register HTTP ingress for diagnostic visibility in the runtime's IIngressSourceRegistry. // The middleware short-circuits to 503 when paused (FR-006). .AddSingleton() // 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() //Trigger payload validators. .AddTriggerPayloadValidator() // 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.RegisterTypeAlias(typeof(HttpRequest), "HttpRequest"); options.RegisterTypeAlias(typeof(HttpResponse), "HttpResponse"); options.RegisterTypeAlias(typeof(HttpResponseMessage), "HttpResponseMessage"); options.RegisterTypeAlias(typeof(HttpHeaders), "HttpHeaders"); options.RegisterTypeAlias(typeof(HttpRouteData), "RouteData"); options.RegisterTypeAlias(typeof(IFormFile), "FormFile"); options.RegisterTypeAlias(typeof(IFormFile[]), "FormFile[]"); options.RegisterTypeAlias(typeof(HttpFile), "HttpFile"); options.RegisterTypeAlias(typeof(HttpFile[]), "HttpFile[]"); options.RegisterTypeAlias(typeof(Downloadable), "Downloadable"); options.RegisterTypeAlias(typeof(Downloadable[]), "Downloadable[]"); options.RegisterTypeAlias(typeof(HttpStatusCode), nameof(HttpStatusCode)); options.RegisterTypeAlias(typeof(HttpRequestException), nameof(HttpRequestException)); options.RegisterTypeAlias(typeof(HttpEndpointBookmarkPayload), nameof(HttpEndpointBookmarkPayload)); }); } }