2022-01-04 08:42:12 +00:00
using System.Collections ;
using System.ComponentModel ;
using System.Reflection ;
2022-05-26 10:47:31 +00:00
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 ;
2022-05-26 10:47:31 +00:00
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 ( ) ;
2022-04-28 10:37:39 +00:00
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 ,
2022-04-28 10:37:39 +00:00
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 )
{
2022-06-07 11:37:34 +00:00
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
(
2022-06-07 11:37:34 +00:00
( outputAttribute ? . Name ? ? propertyInfo . Name ) . Pascalize ( ) ,
outputAttribute ? . DisplayName ? ? propertyInfo . Name . Humanize ( LetterCasing . Title ) ,
2022-01-04 08:42:12 +00:00
wrappedPropertyType ,
2022-06-07 11:37:34 +00:00
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 ;
}
}