elsa-core/src/modules/Elsa.Workflows.Management/Implementations/ActivityDescriber.cs

144 lines
6.8 KiB
C#
Raw Normal View History

2022-01-04 08:42:12 +00:00
using System.Collections;
using System.ComponentModel;
using System.Reflection;
using Elsa.Workflows.Core.Attributes;
using Elsa.Workflows.Core.Helpers;
using Elsa.Workflows.Core.Models;
using Elsa.Workflows.Core.Services;
using Elsa.Workflows.Management.Extensions;
using Elsa.Workflows.Management.Models;
using Elsa.Workflows.Management.Services;
2022-01-04 08:42:12 +00:00
using Humanizer;
namespace Elsa.Workflows.Management.Implementations;
2022-01-04 08:42:12 +00:00
public class ActivityDescriber : IActivityDescriber
{
private readonly IPropertyOptionsResolver _optionsResolver;
private readonly IPropertyDefaultValueResolver _defaultValueResolver;
private readonly IActivityFactory _activityFactory;
public ActivityDescriber(IPropertyOptionsResolver optionsResolver, IPropertyDefaultValueResolver defaultValueResolver, IActivityFactory activityFactory)
{
_optionsResolver = optionsResolver;
_defaultValueResolver = defaultValueResolver;
_activityFactory = activityFactory;
}
public ValueTask<ActivityDescriptor> DescribeActivityAsync(Type activityType, CancellationToken cancellationToken = default)
{
2022-03-24 11:56:31 +00:00
var activityAttr = activityType.GetCustomAttribute<ActivityAttribute>();
2022-04-27 20:32:40 +00:00
var ns = activityAttr?.Namespace ?? ActivityTypeNameHelper.GenerateNamespace(activityType);
2022-03-24 11:56:31 +00:00
var typeName = activityAttr?.TypeName ?? activityType.Name;
2022-06-11 18:04:16 +00:00
var fullTypeName = ActivityTypeNameHelper.GenerateTypeName(activityType);
2022-01-04 08:42:12 +00:00
var displayNameAttr = activityType.GetCustomAttribute<DisplayNameAttribute>();
2022-03-24 11:56:31 +00:00
var displayName = displayNameAttr?.DisplayName ?? activityAttr?.DisplayName ?? typeName.Humanize(LetterCasing.Title);
2022-01-04 08:42:12 +00:00
var categoryAttr = activityType.GetCustomAttribute<CategoryAttribute>();
2022-04-27 20:32:40 +00:00
var category = categoryAttr?.Category ?? activityAttr?.Category ?? ActivityTypeNameHelper.GetCategoryFromNamespace(ns) ?? "Miscellaneous";
2022-01-04 08:42:12 +00:00
var descriptionAttr = activityType.GetCustomAttribute<DescriptionAttribute>();
2022-03-24 11:56:31 +00:00
var description = descriptionAttr?.Description ?? activityAttr?.Description;
2022-01-04 08:42:12 +00:00
var outboundPorts =
from prop in activityType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
where typeof(IActivity).IsAssignableFrom(prop.PropertyType) || typeof(IEnumerable<IActivity>).IsAssignableFrom(prop.PropertyType)
let portAttr = prop.GetCustomAttribute<OutboundAttribute>()
where portAttr != null
select new Port
{
Name = portAttr.Name ?? prop.Name,
DisplayName = portAttr.DisplayName ?? portAttr.Name ?? prop.Name
};
var properties = activityType.GetProperties();
2022-01-26 14:48:59 +00:00
var inputProperties = properties.Where(x => typeof(Input).IsAssignableFrom(x.PropertyType) || x.GetCustomAttribute<InputAttribute>() != null).ToList();
2022-01-04 08:42:12 +00:00
var outputProperties = properties.Where(x => typeof(Output).IsAssignableFrom(x.PropertyType)).ToList();
var isTrigger = activityType.IsAssignableTo(typeof(ITrigger));
2022-01-04 08:42:12 +00:00
var descriptor = new ActivityDescriptor
{
Category = category,
Description = description,
2022-03-15 12:01:10 +00:00
ActivityType = fullTypeName,
2022-01-04 08:42:12 +00:00
DisplayName = displayName,
Kind = isTrigger ? ActivityKind.Trigger : ActivityKind.Action,
2022-01-04 08:42:12 +00:00
OutPorts = outboundPorts.ToList(),
InputProperties = DescribeInputProperties(inputProperties).ToList(),
OutputProperties = DescribeOutputProperties(outputProperties).ToList(),
Constructor = context =>
{
var activity = _activityFactory.Create(activityType, context);
2022-03-07 11:01:27 +00:00
activity.TypeName = fullTypeName;
2022-01-04 08:42:12 +00:00
return activity;
}
};
return ValueTask.FromResult(descriptor);
}
2022-01-26 14:48:59 +00:00
2022-01-04 08:42:12 +00:00
private IEnumerable<InputDescriptor> DescribeInputProperties(IEnumerable<PropertyInfo> properties)
{
foreach (var propertyInfo in properties)
{
var inputAttribute = propertyInfo.GetCustomAttribute<InputAttribute>();
var descriptionAttribute = propertyInfo.GetCustomAttribute<DescriptionAttribute>();
var wrappedPropertyType = propertyInfo.PropertyType.GenericTypeArguments[0];
yield return new InputDescriptor
(
inputAttribute?.Name ?? propertyInfo.Name,
wrappedPropertyType,
GetUIHint(wrappedPropertyType, inputAttribute),
inputAttribute?.DisplayName ?? propertyInfo.Name.Humanize(LetterCasing.Title),
2022-03-08 21:41:12 +00:00
descriptionAttribute?.Description ?? inputAttribute?.Description,
2022-01-04 08:42:12 +00:00
_optionsResolver.GetOptions(propertyInfo),
inputAttribute?.Category,
inputAttribute?.Order ?? 0,
_defaultValueResolver.GetDefaultValue(propertyInfo),
inputAttribute?.DefaultSyntax,
2022-03-08 21:41:12 +00:00
//inputAttribute?.SupportedSyntaxes, TODO: Come up with a different way to specify support languages for activity inputs. By default, maybe all props should support all registered scripting languages?
2022-01-04 08:42:12 +00:00
inputAttribute?.IsReadOnly ?? false,
inputAttribute?.IsBrowsable ?? true
);
}
}
private IEnumerable<OutputDescriptor> DescribeOutputProperties(IEnumerable<PropertyInfo> properties)
{
foreach (var propertyInfo in properties)
{
var outputAttribute = propertyInfo.GetCustomAttribute<OutputAttribute>();
2022-06-06 18:14:51 +00:00
var descriptionAttribute = propertyInfo.GetCustomAttribute<DescriptionAttribute>();
2022-03-15 12:01:10 +00:00
var typeArgs = propertyInfo.PropertyType.GenericTypeArguments;
var wrappedPropertyType = typeArgs.Any() ? typeArgs[0] : typeof(object);
2022-01-04 08:42:12 +00:00
yield return new OutputDescriptor
(
(outputAttribute?.Name ?? propertyInfo.Name).Pascalize(),
outputAttribute?.DisplayName ?? propertyInfo.Name.Humanize(LetterCasing.Title),
2022-01-04 08:42:12 +00:00
wrappedPropertyType,
descriptionAttribute?.Description ?? outputAttribute?.Description,
outputAttribute?.IsBrowsable ?? true
2022-01-04 08:42:12 +00:00
);
}
}
private string GetUIHint(Type wrappedPropertyType, InputAttribute? inputAttribute)
{
if (inputAttribute?.UIHint != null)
return inputAttribute.UIHint;
if (wrappedPropertyType == typeof(bool) || wrappedPropertyType == typeof(bool?))
return InputUIHints.Checkbox;
if (wrappedPropertyType == typeof(string))
return InputUIHints.SingleLine;
if (typeof(IEnumerable).IsAssignableFrom(wrappedPropertyType))
return InputUIHints.Dropdown;
if (wrappedPropertyType.IsEnum || wrappedPropertyType.IsNullableType() && wrappedPropertyType.GetTypeOfNullable().IsEnum)
return InputUIHints.Dropdown;
return InputUIHints.SingleLine;
}
}