Add DropIn support (#4471)
* Implemented dynamic loading of activities at runtime * Add support for loading NuGet packages
This commit is contained in:
parent
a5d13e4e9b
commit
da34e23c2b
24
Elsa.sln
24
Elsa.sln
|
|
@ -237,6 +237,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Samples.AspNet.ChildWo
|
|||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Samples.AspNet.DocumentApproval", "src\samples\aspnet\Elsa.Samples.AspNet.DocumentApproval\Elsa.Samples.AspNet.DocumentApproval.csproj", "{CA9391E3-FA3F-4F96-B6DF-25309BFE7D05}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.DropIns.Core", "src\common\Elsa.DropIns.Core\Elsa.DropIns.Core.csproj", "{1B794B9C-9B8A-4D5A-9848-B51274BD1FC5}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.DropIns", "src\common\Elsa.DropIns\Elsa.DropIns.csproj", "{4E7F15D1-729E-4DA7-992A-C3A2C6054B37}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "drop-ins", "drop-ins", "{6AB6F2F7-CAC1-45C8-9D87-EEE56BC68601}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleDropIn", "src\samples\drop-ins\SampleDropIn\SampleDropIn.csproj", "{E0567846-AC0E-4E09-96A1-EFED4B570A01}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
|
@ -591,6 +599,18 @@ Global
|
|||
{CA9391E3-FA3F-4F96-B6DF-25309BFE7D05}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{CA9391E3-FA3F-4F96-B6DF-25309BFE7D05}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{CA9391E3-FA3F-4F96-B6DF-25309BFE7D05}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{1B794B9C-9B8A-4D5A-9848-B51274BD1FC5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{1B794B9C-9B8A-4D5A-9848-B51274BD1FC5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{1B794B9C-9B8A-4D5A-9848-B51274BD1FC5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{1B794B9C-9B8A-4D5A-9848-B51274BD1FC5}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{4E7F15D1-729E-4DA7-992A-C3A2C6054B37}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4E7F15D1-729E-4DA7-992A-C3A2C6054B37}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4E7F15D1-729E-4DA7-992A-C3A2C6054B37}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4E7F15D1-729E-4DA7-992A-C3A2C6054B37}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{E0567846-AC0E-4E09-96A1-EFED4B570A01}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{E0567846-AC0E-4E09-96A1-EFED4B570A01}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E0567846-AC0E-4E09-96A1-EFED4B570A01}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{E0567846-AC0E-4E09-96A1-EFED4B570A01}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{155227F0-A33B-40AA-A4B4-06F813EB921B} = {61017E64-6D00-49CB-9E81-5002DC8F7D5F}
|
||||
|
|
@ -697,5 +717,9 @@ Global
|
|||
{B99B23D4-6D36-4A31-BE92-DE2415514C79} = {08B41FFA-CEE3-46A7-B5C0-3EB65D37A16C}
|
||||
{2EA47068-6079-4276-8776-F67DE99B568D} = {56C2FFB8-EA54-45B5-A095-4A78142EB4B5}
|
||||
{CA9391E3-FA3F-4F96-B6DF-25309BFE7D05} = {56C2FFB8-EA54-45B5-A095-4A78142EB4B5}
|
||||
{1B794B9C-9B8A-4D5A-9848-B51274BD1FC5} = {C6658DE0-2B2F-47F0-BB61-2CA66D435C09}
|
||||
{4E7F15D1-729E-4DA7-992A-C3A2C6054B37} = {C6658DE0-2B2F-47F0-BB61-2CA66D435C09}
|
||||
{6AB6F2F7-CAC1-45C8-9D87-EEE56BC68601} = {155227F0-A33B-40AA-A4B4-06F813EB921B}
|
||||
{E0567846-AC0E-4E09-96A1-EFED4B570A01} = {6AB6F2F7-CAC1-45C8-9D87-EEE56BC68601}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
|
|
|||
|
|
@ -9,35 +9,41 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\modules\Elsa.Dapper.Migrations\Elsa.Dapper.Migrations.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Dapper\Elsa.Dapper.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.EntityFrameworkCore.SqlServer\Elsa.EntityFrameworkCore.SqlServer.csproj" />
|
||||
<ProjectReference Include="..\Elsa\Elsa.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Elasticsearch\Elsa.Elasticsearch.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Email\Elsa.Email.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.EntityFrameworkCore.PostgreSql\Elsa.EntityFrameworkCore.PostgreSql.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.EntityFrameworkCore.Sqlite\Elsa.EntityFrameworkCore.Sqlite.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.EntityFrameworkCore\Elsa.EntityFrameworkCore.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Environments\Elsa.Environments.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.MassTransit\Elsa.MassTransit.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.MongoDb\Elsa.MongoDb.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.ProtoActor\Elsa.ProtoActor.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Identity\Elsa.Identity.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.ProtoActor.Cluster.AzureContainerApps\Elsa.ProtoActor.Cluster.AzureContainerApps.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.WorkflowProviders.BlobStorage\Elsa.WorkflowProviders.BlobStorage.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Hangfire\Elsa.Hangfire.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Http\Elsa.Http.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Labels\Elsa.Labels.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Liquid\Elsa.Liquid.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Quartz\Elsa.Quartz.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.WorkflowContexts\Elsa.WorkflowContexts.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Workflows.Api\Elsa.Workflows.Api.csproj" />
|
||||
<ProjectReference Include="..\..\common\Elsa.DropIns\Elsa.DropIns.csproj" />
|
||||
<ProjectReference Include="..\Elsa\Elsa.csproj"/>
|
||||
<ProjectReference Include="..\..\modules\Elsa.Dapper.Migrations\Elsa.Dapper.Migrations.csproj"/>
|
||||
<ProjectReference Include="..\..\modules\Elsa.Dapper\Elsa.Dapper.csproj"/>
|
||||
<ProjectReference Include="..\..\modules\Elsa.EntityFrameworkCore.SqlServer\Elsa.EntityFrameworkCore.SqlServer.csproj"/>
|
||||
<ProjectReference Include="..\..\modules\Elsa.Elasticsearch\Elsa.Elasticsearch.csproj"/>
|
||||
<ProjectReference Include="..\..\modules\Elsa.Email\Elsa.Email.csproj"/>
|
||||
<ProjectReference Include="..\..\modules\Elsa.EntityFrameworkCore.PostgreSql\Elsa.EntityFrameworkCore.PostgreSql.csproj"/>
|
||||
<ProjectReference Include="..\..\modules\Elsa.EntityFrameworkCore.Sqlite\Elsa.EntityFrameworkCore.Sqlite.csproj"/>
|
||||
<ProjectReference Include="..\..\modules\Elsa.EntityFrameworkCore\Elsa.EntityFrameworkCore.csproj"/>
|
||||
<ProjectReference Include="..\..\modules\Elsa.Environments\Elsa.Environments.csproj"/>
|
||||
<ProjectReference Include="..\..\modules\Elsa.MassTransit\Elsa.MassTransit.csproj"/>
|
||||
<ProjectReference Include="..\..\modules\Elsa.MongoDb\Elsa.MongoDb.csproj"/>
|
||||
<ProjectReference Include="..\..\modules\Elsa.ProtoActor\Elsa.ProtoActor.csproj"/>
|
||||
<ProjectReference Include="..\..\modules\Elsa.Identity\Elsa.Identity.csproj"/>
|
||||
<ProjectReference Include="..\..\modules\Elsa.ProtoActor.Cluster.AzureContainerApps\Elsa.ProtoActor.Cluster.AzureContainerApps.csproj"/>
|
||||
<ProjectReference Include="..\..\modules\Elsa.WorkflowProviders.BlobStorage\Elsa.WorkflowProviders.BlobStorage.csproj"/>
|
||||
<ProjectReference Include="..\..\modules\Elsa.Hangfire\Elsa.Hangfire.csproj"/>
|
||||
<ProjectReference Include="..\..\modules\Elsa.Http\Elsa.Http.csproj"/>
|
||||
<ProjectReference Include="..\..\modules\Elsa.Labels\Elsa.Labels.csproj"/>
|
||||
<ProjectReference Include="..\..\modules\Elsa.Liquid\Elsa.Liquid.csproj"/>
|
||||
<ProjectReference Include="..\..\modules\Elsa.Quartz\Elsa.Quartz.csproj"/>
|
||||
<ProjectReference Include="..\..\modules\Elsa.WorkflowContexts\Elsa.WorkflowContexts.csproj"/>
|
||||
<ProjectReference Include="..\..\modules\Elsa.Workflows.Api\Elsa.Workflows.Api.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.Identity" Version="1.8.2" />
|
||||
<PackageReference Include="Proto.Persistence.Sqlite" Version="1.1.0" />
|
||||
<PackageReference Include="Proto.Persistence.SqlServer" Version="1.3.1-alpha.0.2" />
|
||||
<PackageReference Include="Azure.Identity" Version="1.8.2"/>
|
||||
<PackageReference Include="Proto.Persistence.Sqlite" Version="1.1.0"/>
|
||||
<PackageReference Include="Proto.Persistence.SqlServer" Version="1.3.1-alpha.0.2"/>
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="App_Data\DropIns\"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
using System.Text.Encodings.Web;
|
||||
using Elsa.Dapper.Extensions;
|
||||
using Elsa.Dapper.Services;
|
||||
using Elsa.DropIns;
|
||||
using Elsa.DropIns.Extensions;
|
||||
using Elsa.EntityFrameworkCore.Extensions;
|
||||
using Elsa.EntityFrameworkCore.Modules.Identity;
|
||||
using Elsa.EntityFrameworkCore.Modules.Management;
|
||||
|
|
@ -35,25 +37,25 @@ var mongoDbConnectionString = configuration.GetConnectionString("MongoDb")!;
|
|||
services
|
||||
.AddElsa(elsa =>
|
||||
{
|
||||
if(useMongoDb)
|
||||
if (useMongoDb)
|
||||
elsa.UseMongoDb(mongoDbConnectionString);
|
||||
|
||||
if(useDapper)
|
||||
|
||||
if (useDapper)
|
||||
elsa.UseDapper(dapper =>
|
||||
{
|
||||
dapper.UseMigrations();
|
||||
dapper.DbConnectionProvider = sp =>
|
||||
{
|
||||
if(useSqlServer)
|
||||
if (useSqlServer)
|
||||
return new SqlServerDbConnectionProvider(sqlServerConnectionString!);
|
||||
else
|
||||
return new SqliteDbConnectionProvider(sqliteConnectionString);
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
if (useHangfire)
|
||||
elsa.UseHangfire();
|
||||
|
||||
|
||||
elsa
|
||||
.AddActivitiesFrom<Program>()
|
||||
.AddWorkflowsFrom<Program>()
|
||||
|
|
@ -61,19 +63,19 @@ services
|
|||
.AddTypeAlias<ApiResponse<User>>("ApiResponse[User]")
|
||||
.UseIdentity(identity =>
|
||||
{
|
||||
if(useMongoDb)
|
||||
if (useMongoDb)
|
||||
identity.UseMongoDb();
|
||||
else if (useDapper)
|
||||
identity.UseDapper();
|
||||
else
|
||||
identity.UseEntityFrameworkCore(ef =>
|
||||
{
|
||||
if(useSqlServer)
|
||||
if (useSqlServer)
|
||||
ef.UseSqlServer(sqlServerConnectionString!);
|
||||
else
|
||||
ef.UseSqlite(sqliteConnectionString);
|
||||
});
|
||||
|
||||
|
||||
identity.IdentityOptions = options => identitySection.Bind(options);
|
||||
identity.TokenOptions = options => identityTokenSection.Bind(options);
|
||||
identity.UseConfigurationBasedUserProvider(options => identitySection.Bind(options));
|
||||
|
|
@ -83,49 +85,49 @@ services
|
|||
.UseDefaultAuthentication()
|
||||
.UseWorkflowManagement(management =>
|
||||
{
|
||||
if(useMongoDb)
|
||||
if (useMongoDb)
|
||||
management.UseMongoDb();
|
||||
else if (useDapper)
|
||||
management.UseDapper();
|
||||
else
|
||||
management.UseEntityFrameworkCore(ef =>
|
||||
{
|
||||
if(useSqlServer)
|
||||
if (useSqlServer)
|
||||
ef.UseSqlServer(sqlServerConnectionString!);
|
||||
else
|
||||
ef.UseSqlite(sqliteConnectionString);
|
||||
});
|
||||
|
||||
|
||||
management.AddVariableType<ApiResponse<User>>("Api");
|
||||
management.AddVariableType<User>("Api");
|
||||
management.AddVariableType<Support>("Api");
|
||||
})
|
||||
.UseWorkflowRuntime(runtime =>
|
||||
{
|
||||
if(useMongoDb)
|
||||
if (useMongoDb)
|
||||
runtime.UseMongoDb();
|
||||
else if (useDapper)
|
||||
runtime.UseDapper();
|
||||
else
|
||||
runtime.UseEntityFrameworkCore(ef =>
|
||||
{
|
||||
if(useSqlServer)
|
||||
if (useSqlServer)
|
||||
ef.UseSqlServer(sqlServerConnectionString!);
|
||||
else
|
||||
ef.UseSqlite(sqliteConnectionString);
|
||||
});
|
||||
|
||||
if(useProtoActor)
|
||||
|
||||
if (useProtoActor)
|
||||
{
|
||||
runtime.UseProtoActor(proto => proto.PersistenceProvider = _ =>
|
||||
{
|
||||
if(useSqlServer)
|
||||
if (useSqlServer)
|
||||
return new SqlServerProvider(sqlServerConnectionString!, true, "", "proto_actor");
|
||||
else
|
||||
return new SqliteProvider(new SqliteConnectionStringBuilder(sqliteConnectionString));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
runtime.UseMassTransitDispatcher();
|
||||
runtime.WorkflowInboxCleanupOptions = options => configuration.GetSection("Runtime:WorkflowInboxCleanup").Bind(options);
|
||||
})
|
||||
|
|
@ -141,15 +143,19 @@ services
|
|||
.UseLiquid(liquid => liquid.FluidOptions = options => options.Encoder = HtmlEncoder.Default)
|
||||
.UseHttp(http => http.HttpEndpointAuthorizationHandler = sp => sp.GetRequiredService<AllowAnonymousHttpEndpointAuthorizationHandler>())
|
||||
.UseEmail(email => email.ConfigureOptions = options => configuration.GetSection("Smtp").Bind(options));
|
||||
|
||||
// Initialize drop-ins.
|
||||
elsa.InstallDropIns(options => options.DropInRootDirectory = Path.Combine(Directory.GetCurrentDirectory(), "App_Data", "DropIns"));
|
||||
});
|
||||
|
||||
services.AddHealthChecks();
|
||||
services.AddControllers();
|
||||
services.AddCors(cors => cors.AddDefaultPolicy(policy => policy.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin().WithExposedHeaders("*")));
|
||||
|
||||
// Configure middleware pipeline.
|
||||
// Build the web application.
|
||||
var app = builder.Build();
|
||||
|
||||
// Configure the pipeline.
|
||||
if (app.Environment.IsDevelopment())
|
||||
app.UseDeveloperExceptionPage();
|
||||
|
||||
|
|
|
|||
22
src/common/Elsa.DropIns.Core/Elsa.DropIns.Core.csproj
Normal file
22
src/common/Elsa.DropIns.Core/Elsa.DropIns.Core.csproj
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<Import Project="..\..\..\common.props" />
|
||||
<Import Project="..\..\..\configureawait.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
|
||||
<Description>
|
||||
Reference this package when developing drop-ins for Elsa Workflows.
|
||||
</Description>
|
||||
<PackageTags>elsa drop-ins common</PackageTags>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0.0"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Elsa.Features\Elsa.Features.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
3
src/common/Elsa.DropIns.Core/FodyWeavers.xml
Normal file
3
src/common/Elsa.DropIns.Core/FodyWeavers.xml
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
|
||||
<ConfigureAwait />
|
||||
</Weavers>
|
||||
20
src/common/Elsa.DropIns.Core/IDropIn.cs
Normal file
20
src/common/Elsa.DropIns.Core/IDropIn.cs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
using Elsa.Features.Services;
|
||||
|
||||
namespace Elsa.DropIns.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Implement this in your drop-in module to configure elsa and register services.
|
||||
/// </summary>
|
||||
public interface IDropIn
|
||||
{
|
||||
/// <summary>
|
||||
/// Called when the drop-in is installed.
|
||||
/// </summary>
|
||||
/// <param name="module">The Elsa module.</param>
|
||||
void Install(IModule module);
|
||||
|
||||
/// <summary>
|
||||
/// Called when the drop-in is being configured.
|
||||
/// </summary>
|
||||
ValueTask ConfigureAsync(IServiceProvider serviceProvider, CancellationToken cancellationToken);
|
||||
}
|
||||
29
src/common/Elsa.DropIns/Catalogs/AssembliesDropInCatalog.cs
Normal file
29
src/common/Elsa.DropIns/Catalogs/AssembliesDropInCatalog.cs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
using System.Reflection;
|
||||
using Elsa.DropIns.Contracts;
|
||||
using Elsa.DropIns.Models;
|
||||
|
||||
namespace Elsa.DropIns.Catalogs;
|
||||
|
||||
public class AssembliesDropInCatalog : IDropInCatalog
|
||||
{
|
||||
private readonly IEnumerable<Assembly> _assemblies;
|
||||
|
||||
public AssembliesDropInCatalog(IEnumerable<Assembly> assemblies)
|
||||
{
|
||||
_assemblies = assemblies;
|
||||
}
|
||||
|
||||
public IEnumerable<DropInDescriptor> List()
|
||||
{
|
||||
var allDropIns = new List<DropInDescriptor>();
|
||||
|
||||
foreach (var assembly in _assemblies)
|
||||
{
|
||||
var assemblyCatalog = new AssemblyDropInCatalog(assembly);
|
||||
var dropIns = assemblyCatalog.List();
|
||||
allDropIns.AddRange(dropIns);
|
||||
}
|
||||
|
||||
return allDropIns;
|
||||
}
|
||||
}
|
||||
26
src/common/Elsa.DropIns/Catalogs/AssemblyDropInCatalog.cs
Normal file
26
src/common/Elsa.DropIns/Catalogs/AssemblyDropInCatalog.cs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
using System.Reflection;
|
||||
using Elsa.DropIns.Contracts;
|
||||
using Elsa.DropIns.Core;
|
||||
using Elsa.DropIns.Helpers;
|
||||
using Elsa.DropIns.Models;
|
||||
|
||||
namespace Elsa.DropIns.Catalogs;
|
||||
|
||||
public class AssemblyDropInCatalog : IDropInCatalog
|
||||
{
|
||||
private readonly Assembly _assembly;
|
||||
|
||||
public AssemblyDropInCatalog(Assembly assembly)
|
||||
{
|
||||
_assembly = assembly;
|
||||
}
|
||||
|
||||
public IEnumerable<DropInDescriptor> List()
|
||||
{
|
||||
var dropIns = TypeFinder.FindImplementationsOf<IDropIn>(_assembly)
|
||||
.Select(type => new DropInDescriptor(type))
|
||||
.ToList();
|
||||
|
||||
return dropIns;
|
||||
}
|
||||
}
|
||||
53
src/common/Elsa.DropIns/Catalogs/DirectoryDropInCatalog.cs
Normal file
53
src/common/Elsa.DropIns/Catalogs/DirectoryDropInCatalog.cs
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
using System.Reflection;
|
||||
using System.Runtime.Loader;
|
||||
using Elsa.DropIns.Contexts;
|
||||
using Elsa.DropIns.Contracts;
|
||||
using Elsa.DropIns.Helpers;
|
||||
using Elsa.DropIns.Models;
|
||||
|
||||
namespace Elsa.DropIns.Catalogs;
|
||||
|
||||
public class DirectoryDropInCatalog : IDropInCatalog
|
||||
{
|
||||
private readonly string _directoryPath;
|
||||
|
||||
public DirectoryDropInCatalog(string directoryPath)
|
||||
{
|
||||
_directoryPath = directoryPath;
|
||||
}
|
||||
|
||||
public IEnumerable<DropInDescriptor> List()
|
||||
{
|
||||
var assemblies = ListAssemblies();
|
||||
var packages = ListPackages();
|
||||
var assembliesCatalog = new AssembliesDropInCatalog(assemblies);
|
||||
var packagesCatalog = new NuGetPackagesCatalog(packages);
|
||||
|
||||
return assembliesCatalog.List().Concat(packagesCatalog.List());
|
||||
}
|
||||
|
||||
private IEnumerable<Assembly> ListAssemblies()
|
||||
{
|
||||
var assemblyPaths = Directory.GetFiles(_directoryPath, "*.dll", SearchOption.AllDirectories);
|
||||
|
||||
foreach (var assemblyPath in assemblyPaths)
|
||||
{
|
||||
var assembly = LoadDropInAssembly(assemblyPath);
|
||||
|
||||
if (assembly == null)
|
||||
continue;
|
||||
|
||||
yield return assembly;
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<string> ListPackages()
|
||||
{
|
||||
return Directory.GetFiles(_directoryPath, "*.nupkg", SearchOption.AllDirectories);
|
||||
}
|
||||
|
||||
private Assembly? LoadDropInAssembly(string path)
|
||||
{
|
||||
return !File.Exists(path) ? null : AssemblyLoader.LoadPath(path);
|
||||
}
|
||||
}
|
||||
24
src/common/Elsa.DropIns/Catalogs/NuGetPackageCatalog.cs
Normal file
24
src/common/Elsa.DropIns/Catalogs/NuGetPackageCatalog.cs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
using Elsa.DropIns.Contexts;
|
||||
using Elsa.DropIns.Contracts;
|
||||
using Elsa.DropIns.Models;
|
||||
|
||||
namespace Elsa.DropIns.Catalogs;
|
||||
|
||||
public class NuGetPackageCatalog : IDropInCatalog
|
||||
{
|
||||
private readonly string _packagePath;
|
||||
|
||||
public NuGetPackageCatalog(string packagePath)
|
||||
{
|
||||
_packagePath = packagePath;
|
||||
}
|
||||
|
||||
public IEnumerable<DropInDescriptor> List()
|
||||
{
|
||||
var assemblyLoadContext = new NuGetPackageAssemblyLoadContext(_packagePath);
|
||||
var assemblies = assemblyLoadContext.Assemblies;
|
||||
var assembliesCatalog = new AssembliesDropInCatalog(assemblies);
|
||||
|
||||
return assembliesCatalog.List();
|
||||
}
|
||||
}
|
||||
19
src/common/Elsa.DropIns/Catalogs/NuGetPackagesCatalog.cs
Normal file
19
src/common/Elsa.DropIns/Catalogs/NuGetPackagesCatalog.cs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
using Elsa.DropIns.Contracts;
|
||||
using Elsa.DropIns.Models;
|
||||
|
||||
namespace Elsa.DropIns.Catalogs;
|
||||
|
||||
public class NuGetPackagesCatalog : IDropInCatalog
|
||||
{
|
||||
private readonly IEnumerable<string> _packagePaths;
|
||||
|
||||
public NuGetPackagesCatalog(IEnumerable<string> packagePaths)
|
||||
{
|
||||
_packagePaths = packagePaths;
|
||||
}
|
||||
|
||||
public IEnumerable<DropInDescriptor> List()
|
||||
{
|
||||
return _packagePaths.Select(packagePath => new NuGetPackageCatalog(packagePath)).SelectMany(catalog => catalog.List());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
using System.Reflection;
|
||||
using System.Runtime.Loader;
|
||||
|
||||
namespace Elsa.DropIns.Contexts;
|
||||
|
||||
internal class DirectoryAssemblyLoadContext : AssemblyLoadContext
|
||||
{
|
||||
private readonly AssemblyDependencyResolver _resolver;
|
||||
|
||||
public DirectoryAssemblyLoadContext(string dropInPath)
|
||||
{
|
||||
_resolver = new AssemblyDependencyResolver(dropInPath);
|
||||
}
|
||||
|
||||
protected override Assembly? Load(AssemblyName assemblyName)
|
||||
{
|
||||
var assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
|
||||
return assemblyPath != null ? LoadFromAssemblyPath(assemblyPath) : null;
|
||||
}
|
||||
|
||||
protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
|
||||
{
|
||||
var libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName);
|
||||
return libraryPath != null ? LoadUnmanagedDllFromPath(libraryPath) : IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
using System.Reflection;
|
||||
using System.Runtime.Loader;
|
||||
using NuGet.Packaging;
|
||||
|
||||
namespace Elsa.DropIns.Contexts;
|
||||
|
||||
internal class NuGetPackageAssemblyLoadContext : AssemblyLoadContext
|
||||
{
|
||||
private readonly Dictionary<string, Assembly> _loadedAssemblies = new Dictionary<string, Assembly>();
|
||||
|
||||
public NuGetPackageAssemblyLoadContext(string nugetPackagePath)
|
||||
{
|
||||
var packageReader = new PackageArchiveReader(nugetPackagePath);
|
||||
|
||||
foreach (var dllFile in packageReader.GetFiles().Where(fileName => fileName.EndsWith(".dll")))
|
||||
{
|
||||
using var dllStream = packageReader.GetStream(dllFile);
|
||||
using var memoryStream = new MemoryStream();
|
||||
dllStream.CopyTo(memoryStream);
|
||||
memoryStream.Seek(0, SeekOrigin.Begin);
|
||||
var assembly = LoadFromStream(memoryStream);
|
||||
|
||||
_loadedAssemblies[assembly.FullName!] = assembly;
|
||||
}
|
||||
}
|
||||
|
||||
protected override Assembly? Load(AssemblyName assemblyName)
|
||||
{
|
||||
return _loadedAssemblies.TryGetValue(assemblyName.FullName, out var assembly) ? assembly : null;
|
||||
}
|
||||
}
|
||||
8
src/common/Elsa.DropIns/Contracts/IDropInCatalog.cs
Normal file
8
src/common/Elsa.DropIns/Contracts/IDropInCatalog.cs
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
using Elsa.DropIns.Models;
|
||||
|
||||
namespace Elsa.DropIns.Contracts;
|
||||
|
||||
public interface IDropInCatalog
|
||||
{
|
||||
IEnumerable<DropInDescriptor> List();
|
||||
}
|
||||
8
src/common/Elsa.DropIns/Contracts/IDropInInstaller.cs
Normal file
8
src/common/Elsa.DropIns/Contracts/IDropInInstaller.cs
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
using Elsa.Features.Services;
|
||||
|
||||
namespace Elsa.DropIns.Contracts;
|
||||
|
||||
public interface IDropInInstaller
|
||||
{
|
||||
void Install(IModule module);
|
||||
}
|
||||
26
src/common/Elsa.DropIns/Elsa.DropIns.csproj
Normal file
26
src/common/Elsa.DropIns/Elsa.DropIns.csproj
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<Import Project="..\..\..\common.props" />
|
||||
<Import Project="..\..\..\configureawait.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
|
||||
<Description>
|
||||
Provides drop-in features for Elsa workflows.
|
||||
</Description>
|
||||
<PackageTags>elsa drop-ins common</PackageTags>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0"/>
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="7.0.1"/>
|
||||
<PackageReference Include="NuGet.Packaging" Version="6.7.0" />
|
||||
<PackageReference Include="NuGet.Protocol" Version="6.7.0" />
|
||||
<PackageReference Include="ThrottleDebounce" Version="2.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Elsa.DropIns.Core\Elsa.DropIns.Core.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
21
src/common/Elsa.DropIns/Extensions/ModuleExtensions.cs
Normal file
21
src/common/Elsa.DropIns/Extensions/ModuleExtensions.cs
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
using Elsa.DropIns.Contracts;
|
||||
using Elsa.DropIns.Options;
|
||||
using Elsa.Features.Services;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Elsa.DropIns.Extensions;
|
||||
|
||||
public static class ModuleExtensions
|
||||
{
|
||||
public static IModule InstallDropIns(this IModule elsa, Action<DropInOptions>? configureOptions = default)
|
||||
{
|
||||
// Install drop-ins.
|
||||
var serviceProvider = new ServiceCollection().AddDropInInstaller(configureOptions).BuildServiceProvider();
|
||||
serviceProvider.GetRequiredService<IDropInInstaller>().Install(elsa);
|
||||
|
||||
// Register drop-in monitor.
|
||||
elsa.Services.AddDropInMonitor(configureOptions);
|
||||
|
||||
return elsa;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
using Elsa.DropIns.Contracts;
|
||||
using Elsa.DropIns.HostedServices;
|
||||
using Elsa.DropIns.Options;
|
||||
using Elsa.DropIns.Services;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Elsa.DropIns.Extensions;
|
||||
|
||||
internal static class ServiceCollectionExtensions
|
||||
{
|
||||
public static IServiceCollection AddDropInMonitor(this IServiceCollection services, Action<DropInOptions>? configureOptions = default)
|
||||
{
|
||||
services.AddDropInCore(configureOptions);
|
||||
services.AddHostedService<DropInDirectoryMonitorHostedService>();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddDropInInstaller(this IServiceCollection services, Action<DropInOptions>? configureOptions = default)
|
||||
{
|
||||
services.AddDropInCore(configureOptions);
|
||||
services.AddSingleton<IDropInInstaller, DropInInstaller>();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddDropInCore(this IServiceCollection services, Action<DropInOptions>? configureOptions = default)
|
||||
{
|
||||
services.Configure(configureOptions ?? (_ => { }));
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
3
src/common/Elsa.DropIns/FodyWeavers.xml
Normal file
3
src/common/Elsa.DropIns/FodyWeavers.xml
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
|
||||
<ConfigureAwait />
|
||||
</Weavers>
|
||||
17
src/common/Elsa.DropIns/Helpers/AssemblyLoader.cs
Normal file
17
src/common/Elsa.DropIns/Helpers/AssemblyLoader.cs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
using System.Reflection;
|
||||
using System.Runtime.Loader;
|
||||
using Elsa.DropIns.Contexts;
|
||||
|
||||
namespace Elsa.DropIns.Helpers;
|
||||
|
||||
public static class AssemblyLoader
|
||||
{
|
||||
public static Assembly LoadPath(string path)
|
||||
{
|
||||
var loadContext = new DirectoryAssemblyLoadContext(path);
|
||||
var assemblyName = AssemblyLoadContext.GetAssemblyName(path);
|
||||
var assembly = loadContext.LoadFromAssemblyName(assemblyName);
|
||||
|
||||
return assembly;
|
||||
}
|
||||
}
|
||||
18
src/common/Elsa.DropIns/Helpers/TypeFinder.cs
Normal file
18
src/common/Elsa.DropIns/Helpers/TypeFinder.cs
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
using System.Reflection;
|
||||
|
||||
namespace Elsa.DropIns.Helpers;
|
||||
|
||||
public static class TypeFinder
|
||||
{
|
||||
public static IEnumerable<Type> FindImplementationsOf<T>(IEnumerable<Assembly> assemblies)
|
||||
{
|
||||
// Find all types that implement IDropInStartup:
|
||||
return assemblies.SelectMany(FindImplementationsOf<T>);
|
||||
}
|
||||
|
||||
public static IEnumerable<Type> FindImplementationsOf<T>(Assembly assembly)
|
||||
{
|
||||
// Find all types that implement T:
|
||||
return assembly.GetExportedTypes().Where(t => typeof(T).IsAssignableFrom(t) && t is { IsAbstract: false, IsInterface: false });
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
using Elsa.DropIns.Catalogs;
|
||||
using Elsa.DropIns.Core;
|
||||
using Elsa.DropIns.Options;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Options;
|
||||
using ThrottleDebounce;
|
||||
|
||||
namespace Elsa.DropIns.HostedServices;
|
||||
|
||||
/// <summary>
|
||||
/// Monitors the drop-in directory for changes and loads any new drop-ins.
|
||||
/// </summary>
|
||||
public class DropInDirectoryMonitorHostedService : BackgroundService
|
||||
{
|
||||
private readonly IOptions<DropInOptions> _options;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly RateLimitedFunc<string, Task> _debouncedLoader;
|
||||
|
||||
/// <inheritdoc />
|
||||
public DropInDirectoryMonitorHostedService(IOptions<DropInOptions> options, IServiceProvider serviceProvider)
|
||||
{
|
||||
_options = options;
|
||||
_serviceProvider = serviceProvider;
|
||||
_debouncedLoader = Debouncer.Debounce<string, Task>(LoadDropInAssemblyAsync, TimeSpan.FromSeconds(2));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
var rootDirectoryPath = _options.Value.DropInRootDirectory;
|
||||
|
||||
var watcher = new FileSystemWatcher(rootDirectoryPath)
|
||||
{
|
||||
EnableRaisingEvents = true,
|
||||
IncludeSubdirectories = true,
|
||||
NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName
|
||||
};
|
||||
|
||||
watcher.Changed += OnChanged;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async void OnChanged(object sender, FileSystemEventArgs e)
|
||||
{
|
||||
var task = _debouncedLoader.Invoke(e.FullPath);
|
||||
|
||||
if (task == null)
|
||||
return;
|
||||
|
||||
await task;
|
||||
}
|
||||
|
||||
private async Task LoadDropInAssemblyAsync(string fullPath)
|
||||
{
|
||||
var directory = Path.GetDirectoryName(fullPath)!;
|
||||
var directoryCatalog = new DirectoryDropInCatalog(directory);
|
||||
var dropInDescriptors = directoryCatalog.List();
|
||||
|
||||
foreach (var dropInDescriptor in dropInDescriptors)
|
||||
{
|
||||
var dropIn = (IDropIn)Activator.CreateInstance(dropInDescriptor.Type)!;
|
||||
await dropIn.ConfigureAsync(_serviceProvider, CancellationToken.None);
|
||||
}
|
||||
}
|
||||
}
|
||||
3
src/common/Elsa.DropIns/Models/DropInDescriptor.cs
Normal file
3
src/common/Elsa.DropIns/Models/DropInDescriptor.cs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
namespace Elsa.DropIns.Models;
|
||||
|
||||
public record DropInDescriptor(Type Type);
|
||||
6
src/common/Elsa.DropIns/Options/DropInOptions.cs
Normal file
6
src/common/Elsa.DropIns/Options/DropInOptions.cs
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
namespace Elsa.DropIns.Options;
|
||||
|
||||
public class DropInOptions
|
||||
{
|
||||
public string DropInRootDirectory { get; set; } = default!;
|
||||
}
|
||||
36
src/common/Elsa.DropIns/Services/DropInInstaller.cs
Normal file
36
src/common/Elsa.DropIns/Services/DropInInstaller.cs
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
using Elsa.DropIns.Catalogs;
|
||||
using Elsa.DropIns.Contracts;
|
||||
using Elsa.DropIns.Core;
|
||||
using Elsa.DropIns.Options;
|
||||
using Elsa.Features.Services;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Elsa.DropIns.Services;
|
||||
|
||||
/// <inheritdoc />
|
||||
public class DropInInstaller : IDropInInstaller
|
||||
{
|
||||
private readonly IOptions<DropInOptions> _options;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DropInInstaller"/> class.
|
||||
/// </summary>
|
||||
public DropInInstaller(IOptions<DropInOptions> options)
|
||||
{
|
||||
_options = options;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Install(IModule module)
|
||||
{
|
||||
var dropInRootDirectory = _options.Value.DropInRootDirectory;
|
||||
var directoryCatalog = new DirectoryDropInCatalog(dropInRootDirectory);
|
||||
var dropInDescriptors = directoryCatalog.List();
|
||||
|
||||
foreach (var dropInDescriptor in dropInDescriptors)
|
||||
{
|
||||
var dropIn = (IDropIn)Activator.CreateInstance(dropInDescriptor.Type)!;
|
||||
dropIn.Install(module);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
using Elsa.Workflows.Core;
|
||||
|
||||
namespace SampleDropIn.Activities;
|
||||
|
||||
public class SampleActivity : CodeActivity
|
||||
{
|
||||
protected override void Execute(ActivityExecutionContext context)
|
||||
{
|
||||
Console.WriteLine("Hello, world! From SampleDropIn!");
|
||||
}
|
||||
}
|
||||
24
src/samples/drop-ins/SampleDropIn/DropIn.cs
Normal file
24
src/samples/drop-ins/SampleDropIn/DropIn.cs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
using Elsa.DropIns.Core;
|
||||
using Elsa.Extensions;
|
||||
using Elsa.Features.Services;
|
||||
using Elsa.Workflows.Core.Contracts;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using SampleDropIn.Activities;
|
||||
|
||||
namespace SampleDropIn;
|
||||
|
||||
[PublicAPI]
|
||||
public class DropIn : IDropIn
|
||||
{
|
||||
public void Install(IModule module)
|
||||
{
|
||||
module.AddActivitiesFrom<DropIn>();
|
||||
}
|
||||
|
||||
public async ValueTask ConfigureAsync(IServiceProvider serviceProvider, CancellationToken cancellationToken)
|
||||
{
|
||||
var activityRegistry = serviceProvider.GetRequiredService<IActivityRegistry>();
|
||||
await activityRegistry.RegisterAsync<SampleActivity>(cancellationToken: cancellationToken);
|
||||
}
|
||||
}
|
||||
16
src/samples/drop-ins/SampleDropIn/SampleDropIn.csproj
Normal file
16
src/samples/drop-ins/SampleDropIn/SampleDropIn.csproj
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<EnableDynamicLoading>true</EnableDynamicLoading>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\common\Elsa.DropIns.Core\Elsa.DropIns.Core.csproj" />
|
||||
<ProjectReference Include="..\..\..\modules\Elsa.Workflows.Management\Elsa.Workflows.Management.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
</Project>
|
||||
Loading…
Reference in a new issue