Implement implicit FastEndpoint assembly collection for discovery
This commit is contained in:
parent
29a35e1cb1
commit
a4011278cc
|
|
@ -1,8 +1,8 @@
|
|||
using Elsa.Extensions;
|
||||
using Elsa.Features.Extensions;
|
||||
using Elsa.Http;
|
||||
using Elsa.Http.Extensions;
|
||||
using Elsa.Identity.Features;
|
||||
using Elsa.Identity;
|
||||
using Elsa.Identity.Extensions;
|
||||
using Elsa.Identity.Options;
|
||||
using Elsa.JavaScript.Activities;
|
||||
using Elsa.JavaScript.Extensions;
|
||||
|
|
@ -27,20 +27,16 @@ using Elsa.Scheduling.Extensions;
|
|||
using Elsa.WorkflowContexts.Extensions;
|
||||
using Elsa.Workflows.Api.Extensions;
|
||||
using Elsa.Workflows.Core.Activities;
|
||||
using Elsa.Workflows.Core.Activities.Flowchart.Activities;
|
||||
using Elsa.Workflows.Core.Middleware.Workflows;
|
||||
using Elsa.Workflows.Management.Extensions;
|
||||
using Elsa.Workflows.Management.Services;
|
||||
using Elsa.Workflows.Runtime.Extensions;
|
||||
using Elsa.Workflows.Runtime.Implementations;
|
||||
using Elsa.WorkflowServer.Web.Activities;
|
||||
using Elsa.WorkflowServer.Web.Jobs;
|
||||
using FastEndpoints;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Proto.Persistence.Sqlite;
|
||||
using Event = Elsa.Workflows.Core.Activities.Event;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
var services = builder.Services;
|
||||
|
|
@ -61,7 +57,7 @@ services
|
|||
.AddActivitiesFrom<RunJavaScript>()
|
||||
.AddActivitiesFrom<Program>()
|
||||
)
|
||||
.Use<IdentityFeature>(identity =>
|
||||
.UseIdentity(identity =>
|
||||
{
|
||||
identity.CreateDefaultUser = true;
|
||||
identity.IdentityOptions = options => identitySection.Bind(options);
|
||||
|
|
@ -81,9 +77,8 @@ services
|
|||
.UseJavaScript()
|
||||
.UseLiquid()
|
||||
.UseHttp()
|
||||
);
|
||||
).AddFastEndpointsFromModule();
|
||||
|
||||
services.AddFastEndpoints();
|
||||
services.AddHealthChecks();
|
||||
services.AddCors(cors => cors.AddDefaultPolicy(policy => policy.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin()));
|
||||
|
||||
|
|
@ -94,7 +89,7 @@ services
|
|||
|
||||
services.AddHttpContextAccessor();
|
||||
services.AddSingleton<IAuthorizationHandler, LocalHostRequirementHandler>();
|
||||
services.AddAuthorization(options => options.AddPolicy("SecurityRoot", policy => policy.AddRequirements(new LocalHostRequirement())));
|
||||
services.AddAuthorization(options => options.AddPolicy(IdentityPolicyNames.SecurityRoot, policy => policy.AddRequirements(new LocalHostRequirement())));
|
||||
|
||||
// Configure middleware pipeline.
|
||||
var app = builder.Build();
|
||||
|
|
|
|||
|
|
@ -13,12 +13,12 @@ public static class DependencyInjectionExtensions
|
|||
/// <summary>
|
||||
/// Creates a new Elsa module and adds the <see cref="ElsaFeature"/> to it.
|
||||
/// </summary>
|
||||
public static IServiceCollection AddElsa(this IServiceCollection services, Action<IModule>? configure = default)
|
||||
public static IModule AddElsa(this IServiceCollection services, Action<IModule>? configure = default)
|
||||
{
|
||||
var module = services.CreateModule();
|
||||
module.Configure<ElsaFeature>();
|
||||
configure?.Invoke(module);
|
||||
module.Apply();
|
||||
return services;
|
||||
return module;
|
||||
}
|
||||
}
|
||||
53
src/common/Elsa.Api.Common/Extensions/ModuleExtensions.cs
Normal file
53
src/common/Elsa.Api.Common/Extensions/ModuleExtensions.cs
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
using System.Reflection;
|
||||
using Elsa.Features.Services;
|
||||
using FastEndpoints;
|
||||
|
||||
namespace Elsa.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Provides extensions to <see cref="IModule"/>.
|
||||
/// </summary>
|
||||
public static class ModuleExtensions
|
||||
{
|
||||
private static readonly object FastEndpointsAssembliesKey = new();
|
||||
|
||||
/// <summary>
|
||||
/// Registers the specified assembly for FastEndpoint assembly discovery.
|
||||
/// </summary>
|
||||
public static void AddFastEndpointsAssembly(this IModule module, Assembly assembly)
|
||||
{
|
||||
var assemblies = module.Properties.GetOrAdd(FastEndpointsAssembliesKey, () => new HashSet<Assembly>());
|
||||
assemblies.Add(assembly);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers the assembly for FastEndpoint assembly discovery using the specified marker type.
|
||||
/// </summary>
|
||||
public static void AddFastEndpointsAssembly<T>(this IModule module) => module.AddFastEndpointsAssembly(typeof(T));
|
||||
|
||||
/// <summary>
|
||||
/// Registers the assembly for FastEndpoint assembly discovery using the specified marker type.
|
||||
/// </summary>
|
||||
public static void AddFastEndpointsAssembly(this IModule module, Type markerType) => module.AddFastEndpointsAssembly(markerType.Assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Returns all collected assemblies for discovery of endpoints.
|
||||
/// </summary>
|
||||
public static IEnumerable<Assembly> GetFastEndpointsAssembliesFromModule(this IModule module) => module.Properties.GetOrAdd(FastEndpointsAssembliesKey, () => new HashSet<Assembly>());
|
||||
|
||||
/// <summary>
|
||||
/// Adds FastEndpoints to the service container and registers all collected assemblies for discovery of endpoints.
|
||||
/// </summary>
|
||||
public static IModule AddFastEndpointsFromModule(this IModule module)
|
||||
{
|
||||
var assemblies = module.GetFastEndpointsAssembliesFromModule().ToList();
|
||||
|
||||
module.Services.AddFastEndpoints(options =>
|
||||
{
|
||||
options.DisableAutoDiscovery = true;
|
||||
options.Assemblies = assemblies;
|
||||
});
|
||||
|
||||
return module;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
namespace Elsa.Extensions;
|
||||
|
||||
static class ObjectDictionaryExtensions
|
||||
{
|
||||
public static T GetOrAdd<T>(this IDictionary<object, object> dictionary, object key, Func<T> valueFactory)
|
||||
{
|
||||
if (dictionary.TryGetValue(key, out var value)) return (T)value;
|
||||
value = valueFactory();
|
||||
dictionary.Add(key, value!);
|
||||
return (T)value!;
|
||||
}
|
||||
|
||||
public static T Get<T>(this IDictionary<object, object> dictionary, object key) => (T)dictionary[key];
|
||||
}
|
||||
|
|
@ -19,17 +19,15 @@ public static class WebApplicationExtensions
|
|||
/// <param name="app"></param>
|
||||
/// <param name="routePrefix">The route prefix to apply to Elsa API endpoints.</param>
|
||||
/// <example>E.g. "elsa/api" will expose endpoints like this: "/elsa/api/workflow-definitions"</example>
|
||||
public static IApplicationBuilder UseElsaFastEndpoints(this IApplicationBuilder app, string routePrefix = "elsa/api")
|
||||
{
|
||||
return app.UseFastEndpoints(config =>
|
||||
public static IApplicationBuilder UseElsaFastEndpoints(this IApplicationBuilder app, string routePrefix = "elsa/api") =>
|
||||
app.UseFastEndpoints(config =>
|
||||
{
|
||||
config.Endpoints.RoutePrefix = routePrefix;
|
||||
config.Serializer.RequestDeserializer = DeserializeRequestAsync;
|
||||
config.Serializer.ResponseSerializer = SerializeRequestAsync;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
static ValueTask<object?> DeserializeRequestAsync(HttpRequest httpRequest, Type modelType, JsonSerializerContext? serializerContext, CancellationToken cancellationToken)
|
||||
private static ValueTask<object?> DeserializeRequestAsync(HttpRequest httpRequest, Type modelType, JsonSerializerContext? serializerContext, CancellationToken cancellationToken)
|
||||
{
|
||||
var serializerOptionsProvider = httpRequest.HttpContext.RequestServices.GetRequiredService<SerializerOptionsProvider>();
|
||||
var options = serializerOptionsProvider.CreateApiOptions();
|
||||
|
|
@ -39,7 +37,7 @@ public static class WebApplicationExtensions
|
|||
: JsonSerializer.DeserializeAsync(httpRequest.Body, modelType, serializerContext, cancellationToken);
|
||||
}
|
||||
|
||||
static Task SerializeRequestAsync(HttpResponse httpResponse, object? dto, string contentType, JsonSerializerContext? serializerContext, CancellationToken cancellationToken)
|
||||
private static Task SerializeRequestAsync(HttpResponse httpResponse, object? dto, string contentType, JsonSerializerContext? serializerContext, CancellationToken cancellationToken)
|
||||
{
|
||||
var serializerOptionsProvider = httpResponse.HttpContext.RequestServices.GetRequiredService<SerializerOptionsProvider>();
|
||||
var options = serializerOptionsProvider.CreateApiOptions();
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ using Microsoft.Extensions.Hosting;
|
|||
|
||||
namespace Elsa.Features.Implementations;
|
||||
|
||||
/// <inheritdoc />
|
||||
public class Module : IModule
|
||||
{
|
||||
private record HostedServiceDescriptor(int Order, Type HostedServiceType);
|
||||
|
|
@ -15,15 +16,24 @@ public class Module : IModule
|
|||
private readonly ISet<IFeature> _features = new HashSet<IFeature>();
|
||||
private readonly ICollection<HostedServiceDescriptor> _hostedServiceDescriptors = new List<HostedServiceDescriptor>();
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
public Module(IServiceCollection services)
|
||||
{
|
||||
Services = services;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IServiceCollection Services { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IDictionary<object, object> Properties { get; } = new Dictionary<object, object>();
|
||||
|
||||
/// <inheritdoc />
|
||||
public T Configure<T>(Action<T>? configure = default) where T : class, IFeature => Configure(module => (T)Activator.CreateInstance(typeof(T), module)!, configure);
|
||||
|
||||
/// <inheritdoc />
|
||||
public T Configure<T>(Func<IModule, T> factory, Action<T>? configure = default) where T : class, IFeature
|
||||
{
|
||||
if (_features.FirstOrDefault(x => x is T) is not T feature)
|
||||
|
|
@ -36,12 +46,14 @@ public class Module : IModule
|
|||
return feature;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IModule ConfigureHostedService<T>(int priority = 0)
|
||||
{
|
||||
_hostedServiceDescriptors.Add(new HostedServiceDescriptor(priority, typeof(T)));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Apply()
|
||||
{
|
||||
var featureTypes = _features.Select(x => x.GetType()).TSort(x => x.GetCustomAttributes<DependsOn>().Select(dependsOn => dependsOn.Type)).ToList();
|
||||
|
|
|
|||
|
|
@ -1,12 +1,40 @@
|
|||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace Elsa.Features.Services;
|
||||
|
||||
/// <summary>
|
||||
/// A thin abstraction on top of <see cref="IServiceCollection"/> to help organize features and dependencies.
|
||||
/// </summary>
|
||||
public interface IModule
|
||||
{
|
||||
/// <summary>
|
||||
/// The service collection being populated.
|
||||
/// </summary>
|
||||
IServiceCollection Services { get; }
|
||||
|
||||
/// <summary>
|
||||
/// A dictionary into which features can stash away values for later use.
|
||||
/// </summary>
|
||||
IDictionary<object, object> Properties { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates and configures a feature of the specified type.
|
||||
/// </summary>
|
||||
T Configure<T>(Action<T>? configure = default) where T : class, IFeature;
|
||||
|
||||
/// <summary>
|
||||
/// Creates and configures a feature of the specified type.
|
||||
/// </summary>
|
||||
T Configure<T>(Func<IModule, T> factory, Action<T>? configure = default) where T : class, IFeature;
|
||||
|
||||
/// <summary>
|
||||
/// Configures a <see cref="IHostedService"/> using an optional priority to control in which order it will be registered with the service container.
|
||||
/// </summary>
|
||||
IModule ConfigureHostedService<T>(int priority = 0);
|
||||
|
||||
/// <summary>
|
||||
/// Will apply all configured features, causing the <see cref="Services"/> collection to be populated.
|
||||
/// </summary>
|
||||
void Apply();
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ using Elsa.ActivityDefinitions.Implementations;
|
|||
using Elsa.ActivityDefinitions.Services;
|
||||
using Elsa.Common.Extensions;
|
||||
using Elsa.Common.Features;
|
||||
using Elsa.Extensions;
|
||||
using Elsa.Features.Abstractions;
|
||||
using Elsa.Features.Attributes;
|
||||
using Elsa.Features.Services;
|
||||
|
|
@ -31,6 +32,12 @@ public class ActivityDefinitionsFeature : FeatureBase
|
|||
/// </summary>
|
||||
public Func<IServiceProvider, IActivityDefinitionStore> ActivityDefinitionStore { get; set; } = sp => sp.GetRequiredService<MemoryActivityDefinitionStore>();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Configure()
|
||||
{
|
||||
Module.AddFastEndpointsAssembly(GetType());
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Apply()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
using System.Reflection;
|
||||
using Elsa.Common.Extensions;
|
||||
using Elsa.Common.Features;
|
||||
using Elsa.Extensions;
|
||||
using Elsa.Features.Abstractions;
|
||||
using Elsa.Features.Attributes;
|
||||
using Elsa.Features.Services;
|
||||
|
|
@ -18,22 +20,45 @@ namespace Elsa.Identity.Features;
|
|||
[DependsOn(typeof(SystemClockFeature))]
|
||||
public class IdentityFeature : FeatureBase
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public IdentityFeature(IModule module) : base(module)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A flag indicating whether a default user should be created.
|
||||
/// </summary>
|
||||
public bool CreateDefaultUser { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A delegate to configure <see cref="Options.IdentityOptions"/>.
|
||||
/// </summary>
|
||||
public Action<IdentityOptions>? IdentityOptions { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A delegate that creates an instance of an implementation of <see cref="IUserStore"/>.
|
||||
/// </summary>
|
||||
public Func<IServiceProvider, IUserStore> UserStore { get; set; } = sp => sp.GetRequiredService<MemoryUserStore>();
|
||||
|
||||
/// <summary>
|
||||
/// A delegate that creates an instance of an implementation of <see cref="IRoleStore"/>.
|
||||
/// </summary>
|
||||
public Func<IServiceProvider, IRoleStore> RoleStore { get; set; } = sp => sp.GetRequiredService<MemoryRoleStore>();
|
||||
|
||||
public override void Configure()
|
||||
{
|
||||
Module.AddFastEndpointsAssembly(GetType());
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void ConfigureHostedServices()
|
||||
{
|
||||
if(CreateDefaultUser)
|
||||
Module.ConfigureHostedService<SetupDefaultUserHostedService>();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Apply()
|
||||
{
|
||||
Services.Configure(IdentityOptions);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using Elsa.Common.Extensions;
|
||||
using Elsa.Extensions;
|
||||
using Elsa.Features.Abstractions;
|
||||
using Elsa.Features.Attributes;
|
||||
using Elsa.Features.Services;
|
||||
|
|
@ -11,16 +12,33 @@ using Microsoft.Extensions.DependencyInjection;
|
|||
|
||||
namespace Elsa.Labels.Features;
|
||||
|
||||
/// <summary>
|
||||
/// Enables functionality to tag workflows with labels.
|
||||
/// </summary>
|
||||
[DependsOn(typeof(MediatorFeature))]
|
||||
public class LabelsFeature : FeatureBase
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public LabelsFeature(IModule module) : base(module)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A delegate that provides an instance of an implementation of <see cref="ILabelStore"/>.
|
||||
/// </summary>
|
||||
public Func<IServiceProvider, ILabelStore> LabelStore { get; set; } = sp => sp.GetRequiredService<InMemoryLabelStore>();
|
||||
public Func<IServiceProvider, IWorkflowDefinitionLabelStore> WorkflowDefinitionLabelStore { get; set; } = sp => sp.GetRequiredService<InMemoryWorkflowDefinitionLabelStore>();
|
||||
|
||||
/// <summary>
|
||||
/// A delegate that provides an instance of an implementation of <see cref="IWorkflowDefinitionLabelStore"/>.
|
||||
/// </summary>
|
||||
public Func<IServiceProvider, IWorkflowDefinitionLabelStore> WorkflowDefinitionLabelStore { get; set; } = sp => sp.GetRequiredService<InMemoryWorkflowDefinitionLabelStore>();
|
||||
|
||||
public override void Configure()
|
||||
{
|
||||
Module.AddFastEndpointsAssembly(GetType());
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Apply()
|
||||
{
|
||||
Services
|
||||
|
|
|
|||
|
|
@ -1,18 +1,22 @@
|
|||
using Elsa.Extensions;
|
||||
using Elsa.Features.Abstractions;
|
||||
using Elsa.Features.Services;
|
||||
using Elsa.Workflows.Management.Mappers;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Elsa.Workflows.Api.Features;
|
||||
|
||||
/// <summary>
|
||||
/// Adds workflows API features.
|
||||
/// </summary>
|
||||
public class WorkflowsApiFeature : FeatureBase
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public WorkflowsApiFeature(IModule module) : base(module)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Apply()
|
||||
/// <inheritdoc />
|
||||
public override void Configure()
|
||||
{
|
||||
Services.AddSingleton<VariableDefinitionMapper>();
|
||||
Module.AddFastEndpointsAssembly(GetType());
|
||||
}
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ using Elsa.Workflows.Core.Services;
|
|||
using Elsa.Workflows.Management.Entities;
|
||||
using Elsa.Workflows.Management.Extensions;
|
||||
using Elsa.Workflows.Management.Implementations;
|
||||
using Elsa.Workflows.Management.Mappers;
|
||||
using Elsa.Workflows.Management.Materializers;
|
||||
using Elsa.Workflows.Management.Models;
|
||||
using Elsa.Workflows.Management.Options;
|
||||
|
|
@ -151,6 +152,7 @@ public class WorkflowManagementFeature : FeatureBase
|
|||
.AddSingleton<IWorkflowMaterializer, ClrWorkflowMaterializer>()
|
||||
.AddSingleton<IWorkflowMaterializer, JsonWorkflowMaterializer>()
|
||||
.AddSingleton<SerializerOptionsProvider>()
|
||||
.AddSingleton<VariableDefinitionMapper>()
|
||||
;
|
||||
|
||||
Services.Configure<ManagementOptions>(options =>
|
||||
|
|
|
|||
Loading…
Reference in a new issue