2023-04-19 19:28:58 +00:00
|
|
|
using System.ComponentModel;
|
2022-05-26 10:47:31 +00:00
|
|
|
using System.Reflection;
|
2022-12-31 15:16:43 +00:00
|
|
|
using Elsa.Extensions;
|
2022-05-27 10:36:39 +00:00
|
|
|
using Elsa.Features.Attributes;
|
2023-04-19 19:28:58 +00:00
|
|
|
using Elsa.Features.Contracts;
|
|
|
|
|
using Elsa.Features.Models;
|
2022-05-27 10:36:39 +00:00
|
|
|
using Elsa.Features.Services;
|
2022-05-26 10:47:31 +00:00
|
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
|
|
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
|
|
|
|
using Microsoft.Extensions.Hosting;
|
|
|
|
|
|
2022-05-27 10:36:39 +00:00
|
|
|
namespace Elsa.Features.Implementations;
|
2022-05-26 10:47:31 +00:00
|
|
|
|
2022-12-09 11:25:39 +00:00
|
|
|
/// <inheritdoc />
|
2022-05-27 10:36:39 +00:00
|
|
|
public class Module : IModule
|
2022-05-26 10:47:31 +00:00
|
|
|
{
|
2023-05-28 09:54:12 +00:00
|
|
|
private record HostedServiceDescriptor(int Order, Type Type);
|
2022-05-26 10:47:31 +00:00
|
|
|
|
2023-04-17 22:08:24 +00:00
|
|
|
private IDictionary<Type, IFeature> _features = new Dictionary<Type, IFeature>();
|
|
|
|
|
private readonly ISet<IFeature> _configuredFeatures = new HashSet<IFeature>();
|
2022-05-26 10:47:31 +00:00
|
|
|
private readonly ICollection<HostedServiceDescriptor> _hostedServiceDescriptors = new List<HostedServiceDescriptor>();
|
|
|
|
|
|
2022-12-09 11:25:39 +00:00
|
|
|
/// <summary>
|
|
|
|
|
/// Constructor.
|
|
|
|
|
/// </summary>
|
2022-05-27 10:36:39 +00:00
|
|
|
public Module(IServiceCollection services)
|
2022-05-26 10:47:31 +00:00
|
|
|
{
|
|
|
|
|
Services = services;
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-09 11:25:39 +00:00
|
|
|
/// <inheritdoc />
|
2022-05-26 10:47:31 +00:00
|
|
|
public IServiceCollection Services { get; }
|
2023-04-17 21:58:18 +00:00
|
|
|
|
2022-12-09 11:25:39 +00:00
|
|
|
/// <inheritdoc />
|
|
|
|
|
public IDictionary<object, object> Properties { get; } = new Dictionary<object, object>();
|
2022-05-26 10:47:31 +00:00
|
|
|
|
2022-12-09 11:25:39 +00:00
|
|
|
/// <inheritdoc />
|
2023-01-01 17:02:38 +00:00
|
|
|
public T Configure<T>(Action<T>? configure = default) where T : class, IFeature
|
|
|
|
|
=> Configure(module => (T)Activator.CreateInstance(typeof(T), module)!, configure);
|
2022-05-26 10:47:31 +00:00
|
|
|
|
2022-12-09 11:25:39 +00:00
|
|
|
/// <inheritdoc />
|
2022-05-27 10:36:39 +00:00
|
|
|
public T Configure<T>(Func<IModule, T> factory, Action<T>? configure = default) where T : class, IFeature
|
2022-05-26 10:47:31 +00:00
|
|
|
{
|
2023-04-17 22:08:24 +00:00
|
|
|
if (!_features.TryGetValue(typeof(T), out var feature))
|
2022-05-26 10:47:31 +00:00
|
|
|
{
|
2022-08-14 17:38:58 +00:00
|
|
|
feature = factory(this);
|
2023-04-17 22:08:24 +00:00
|
|
|
_features[typeof(T)] = feature;
|
2022-05-26 10:47:31 +00:00
|
|
|
}
|
|
|
|
|
|
2023-04-17 22:08:24 +00:00
|
|
|
configure?.Invoke((T)feature);
|
|
|
|
|
|
|
|
|
|
if (!_isApplying)
|
|
|
|
|
return (T)feature;
|
2023-04-17 21:58:18 +00:00
|
|
|
|
|
|
|
|
var dependencies = GetDependencyTypes(feature.GetType()).ToHashSet();
|
2023-04-17 22:08:24 +00:00
|
|
|
foreach (var dependency in dependencies.Select(GetOrCreateFeature))
|
2023-04-17 21:58:18 +00:00
|
|
|
ConfigureFeature(dependency);
|
|
|
|
|
|
|
|
|
|
ConfigureFeature(feature);
|
2023-04-17 22:08:24 +00:00
|
|
|
return (T)feature;
|
2022-05-26 10:47:31 +00:00
|
|
|
}
|
|
|
|
|
|
2022-12-09 11:25:39 +00:00
|
|
|
/// <inheritdoc />
|
2023-04-17 13:23:05 +00:00
|
|
|
public IModule ConfigureHostedService<T>(int priority = 0) where T : class, IHostedService
|
2022-05-26 10:47:31 +00:00
|
|
|
{
|
2023-10-29 15:17:38 +00:00
|
|
|
return ConfigureHostedService(typeof(T), priority);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
|
public IModule ConfigureHostedService(Type hostedServiceType, int priority = 0)
|
|
|
|
|
{
|
|
|
|
|
_hostedServiceDescriptors.Add(new HostedServiceDescriptor(priority, hostedServiceType));
|
2022-05-26 10:47:31 +00:00
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-17 21:58:18 +00:00
|
|
|
private bool _isApplying;
|
|
|
|
|
|
2022-12-09 11:25:39 +00:00
|
|
|
/// <inheritdoc />
|
2022-05-26 10:47:31 +00:00
|
|
|
public void Apply()
|
|
|
|
|
{
|
2023-04-17 21:58:18 +00:00
|
|
|
_isApplying = true;
|
|
|
|
|
var featureTypes = GetFeatureTypes();
|
2023-04-17 22:08:24 +00:00
|
|
|
_features = featureTypes.ToDictionary(featureType => featureType, featureType => _features.TryGetValue(featureType, out var existingFeature) ? existingFeature : (IFeature)Activator.CreateInstance(featureType, this)!);
|
2022-12-30 12:11:29 +00:00
|
|
|
|
2023-04-17 21:58:18 +00:00
|
|
|
// Iterate over a copy of the features to avoid concurrent modification exceptions.
|
2023-04-17 22:08:24 +00:00
|
|
|
foreach (var feature in _features.Values.ToList())
|
2022-05-26 10:47:31 +00:00
|
|
|
{
|
2023-04-17 21:58:18 +00:00
|
|
|
// This will cause additional features to be added to _features.
|
|
|
|
|
ConfigureFeature(feature);
|
2022-05-26 10:47:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach (var hostedServiceDescriptor in _hostedServiceDescriptors.OrderBy(x => x.Order))
|
2023-05-28 09:54:12 +00:00
|
|
|
Services.TryAddEnumerable(ServiceDescriptor.Singleton(typeof(IHostedService), hostedServiceDescriptor.Type));
|
2022-05-26 10:47:31 +00:00
|
|
|
|
2023-04-17 21:58:18 +00:00
|
|
|
// Make sure to use the complete list of features when applying them.
|
2023-04-17 22:08:24 +00:00
|
|
|
foreach (var feature in _features.Values)
|
2022-08-14 17:38:58 +00:00
|
|
|
feature.Apply();
|
2023-05-28 09:54:12 +00:00
|
|
|
|
2023-04-19 19:28:58 +00:00
|
|
|
// Add a registry of enabled features to the service collection for client applications to reflect on what features are installed.
|
|
|
|
|
var registry = new InstalledFeatureRegistry();
|
|
|
|
|
foreach (var feature in _features.Values)
|
|
|
|
|
{
|
|
|
|
|
var type = feature.GetType();
|
2023-06-10 13:31:45 +00:00
|
|
|
var name = type.Name.Replace("Feature", string.Empty);
|
|
|
|
|
var ns = "Elsa";
|
|
|
|
|
var displayName = type.GetCustomAttribute<DisplayNameAttribute>()?.DisplayName ?? name;
|
2023-04-19 19:28:58 +00:00
|
|
|
var description = type.GetCustomAttribute<DescriptionAttribute>()?.Description;
|
2023-06-10 13:31:45 +00:00
|
|
|
registry.Add(new FeatureDescriptor(name, ns, displayName, description));
|
2023-04-19 19:28:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Services.AddSingleton<IInstalledFeatureRegistry>(registry);
|
2022-05-26 10:47:31 +00:00
|
|
|
}
|
2023-04-17 21:58:18 +00:00
|
|
|
|
|
|
|
|
private void ConfigureFeature(IFeature feature)
|
|
|
|
|
{
|
2023-04-17 22:08:24 +00:00
|
|
|
if (_configuredFeatures.Contains(feature))
|
2023-04-17 21:58:18 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
feature.Configure();
|
2023-04-17 22:13:40 +00:00
|
|
|
feature.ConfigureHostedServices();
|
2023-04-17 22:08:24 +00:00
|
|
|
_features[feature.GetType()] = feature;
|
2023-04-17 21:58:18 +00:00
|
|
|
_configuredFeatures.Add(feature);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private IFeature GetOrCreateFeature(Type featureType)
|
|
|
|
|
{
|
2023-04-17 22:08:24 +00:00
|
|
|
return _features.TryGetValue(featureType, out var existingFeature) ? existingFeature : (IFeature)Activator.CreateInstance(featureType, this)!;
|
2023-04-17 21:58:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private ISet<Type> GetFeatureTypes()
|
|
|
|
|
{
|
2023-04-17 22:08:24 +00:00
|
|
|
var featureTypes = _features.Keys.ToHashSet();
|
|
|
|
|
var featureTypesWithDependencies = featureTypes.Concat(featureTypes.SelectMany(GetDependencyTypes)).ToHashSet();
|
2023-04-17 21:58:18 +00:00
|
|
|
return featureTypesWithDependencies.TSort(x => x.GetCustomAttributes<DependsOn>().Select(dependsOn => dependsOn.Type)).ToHashSet();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Recursively get dependency types.
|
|
|
|
|
private IEnumerable<Type> GetDependencyTypes(Type type)
|
|
|
|
|
{
|
|
|
|
|
var dependencies = type.GetCustomAttributes<DependsOn>().Select(dependsOn => dependsOn.Type).ToList();
|
|
|
|
|
return dependencies.Concat(dependencies.SelectMany(GetDependencyTypes));
|
|
|
|
|
}
|
2022-05-26 10:47:31 +00:00
|
|
|
}
|