| | | 1 | | using System.Collections; |
| | | 2 | | using System.ComponentModel; |
| | | 3 | | using System.Diagnostics.CodeAnalysis; |
| | | 4 | | using System.Reflection; |
| | | 5 | | using Elsa.Extensions; |
| | | 6 | | using Elsa.Workflows.Activities.Flowchart.Attributes; |
| | | 7 | | using Elsa.Workflows.Attributes; |
| | | 8 | | using Elsa.Workflows.Helpers; |
| | | 9 | | using Elsa.Workflows.Memory; |
| | | 10 | | using Elsa.Workflows.Models; |
| | | 11 | | using Elsa.Workflows.UIHints; |
| | | 12 | | using Humanizer; |
| | | 13 | | using Container = Elsa.Workflows.Activities.Container; |
| | | 14 | | |
| | | 15 | | namespace Elsa.Workflows; |
| | | 16 | | |
| | | 17 | | /// <inheritdoc /> |
| | 528 | 18 | | public class ActivityDescriber(IPropertyDefaultValueResolver defaultValueResolver, IPropertyUIHandlerResolver propertyUI |
| | | 19 | | { |
| | | 20 | | /// <inheritdoc /> |
| | | 21 | | public async Task<ActivityDescriptor> DescribeActivityAsync([DynamicallyAccessedMembers(DynamicallyAccessedMemberTyp |
| | | 22 | | { |
| | 11976 | 23 | | var activityAttr = activityType.GetCustomAttribute<ActivityAttribute>(); |
| | 11976 | 24 | | var ns = activityAttr?.Namespace ?? ActivityTypeNameHelper.GenerateNamespace(activityType) ?? "Elsa"; |
| | 11976 | 25 | | var friendlyName = GetFriendlyActivityName(activityType); |
| | 11976 | 26 | | var typeName = activityAttr?.Type ?? friendlyName; |
| | 11976 | 27 | | var typeVersion = activityAttr?.Version ?? 1; |
| | 11976 | 28 | | var fullTypeName = ActivityTypeNameHelper.GenerateTypeName(activityType); |
| | 11976 | 29 | | var displayNameAttr = activityType.GetCustomAttribute<DisplayNameAttribute>(); |
| | 11976 | 30 | | var displayName = displayNameAttr?.DisplayName ?? activityAttr?.DisplayName ?? friendlyName.Humanize(LetterCasin |
| | 11976 | 31 | | var categoryAttr = activityType.GetCustomAttribute<CategoryAttribute>(); |
| | 11976 | 32 | | var category = categoryAttr?.Category ?? activityAttr?.Category ?? ActivityTypeNameHelper.GetCategoryFromNamespa |
| | 11976 | 33 | | var descriptionAttr = activityType.GetCustomAttribute<DescriptionAttribute>(); |
| | 11976 | 34 | | var description = descriptionAttr?.Description ?? activityAttr?.Description; |
| | | 35 | | |
| | 11976 | 36 | | var embeddedPorts = |
| | 11976 | 37 | | from prop in activityType.GetProperties(BindingFlags.Public | BindingFlags.Instance) |
| | | 38 | | where typeof(IActivity).IsAssignableFrom(prop.PropertyType) |
| | | 39 | | let portAttr = prop.GetCustomAttribute<PortAttribute>() |
| | | 40 | | let portBrowsableAttr = prop.GetCustomAttribute<BrowsableAttribute>() |
| | | 41 | | select new Port |
| | | 42 | | { |
| | | 43 | | Name = portAttr?.Name ?? prop.Name, |
| | | 44 | | DisplayName = portAttr?.DisplayName ?? portAttr?.Name ?? prop.Name, |
| | | 45 | | Type = PortType.Embedded, |
| | | 46 | | IsBrowsable = portAttr != null && (portBrowsableAttr == null || portBrowsableAttr.Browsable), |
| | | 47 | | }; |
| | | 48 | | |
| | 11976 | 49 | | var flowNodeAttr = activityType.GetCustomAttribute<FlowNodeAttribute>(); |
| | 11976 | 50 | | var flowPorts = |
| | 11976 | 51 | | flowNodeAttr |
| | | 52 | | ?.Outcomes.Select(x => new Port |
| | | 53 | | { |
| | | 54 | | Type = PortType.Flow, |
| | | 55 | | Name = x, |
| | | 56 | | DisplayName = x, |
| | | 57 | | }) |
| | | 58 | | .ToDictionary(x => x.Name) ?? new Dictionary<string, Port>(); |
| | | 59 | | |
| | 11976 | 60 | | var allPorts = embeddedPorts.Concat(flowPorts.Values); |
| | 11976 | 61 | | var inputProperties = GetInputProperties(activityType).ToList(); |
| | 11976 | 62 | | var outputProperties = GetOutputProperties(activityType).ToList(); |
| | 11976 | 63 | | var isTrigger = activityType.IsAssignableTo(typeof(ITrigger)); |
| | 11976 | 64 | | var browsableAttr = activityType.GetCustomAttribute<BrowsableAttribute>(); |
| | | 65 | | var isTerminal = activityType.FindInterfaces((type, criteria) => type == typeof(ITerminalNode), null).Any(); |
| | | 66 | | var isStart = activityType.FindInterfaces((type, criteria) => type == typeof(IStartNode), null).Any(); |
| | 11976 | 67 | | var attributes = activityType.GetCustomAttributes(true).Cast<Attribute>().ToList(); |
| | 11976 | 68 | | var outputAttribute = attributes.OfType<OutputAttribute>().FirstOrDefault(); |
| | | 69 | | |
| | 11976 | 70 | | var descriptor = new ActivityDescriptor |
| | 11976 | 71 | | { |
| | 11976 | 72 | | TypeName = fullTypeName, |
| | 11976 | 73 | | ClrType = activityType, |
| | 11976 | 74 | | Namespace = ns, |
| | 11976 | 75 | | Name = typeName, |
| | 11976 | 76 | | Category = category, |
| | 11976 | 77 | | Description = description, |
| | 11976 | 78 | | Version = typeVersion, |
| | 11976 | 79 | | DisplayName = displayName, |
| | 11976 | 80 | | Kind = isTrigger ? ActivityKind.Trigger : activityAttr?.Kind ?? ActivityKind.Action, |
| | 11976 | 81 | | Ports = allPorts.ToList(), |
| | 11976 | 82 | | Inputs = (await DescribeInputPropertiesAsync(inputProperties, cancellationToken)).ToList(), |
| | 11976 | 83 | | Outputs = (await DescribeOutputPropertiesAsync(outputProperties, cancellationToken)).ToList(), |
| | 11976 | 84 | | IsContainer = typeof(Container).IsAssignableFrom(activityType), |
| | 11976 | 85 | | IsBrowsable = browsableAttr == null || browsableAttr.Browsable, |
| | 11976 | 86 | | IsStart = isStart, |
| | 11976 | 87 | | IsTerminal = isTerminal, |
| | 11976 | 88 | | Attributes = attributes, |
| | 11976 | 89 | | CustomProperties = |
| | 11976 | 90 | | { |
| | 11976 | 91 | | ["Type"] = activityType |
| | 11976 | 92 | | }, |
| | 11976 | 93 | | Constructor = context => |
| | 11976 | 94 | | { |
| | 2075 | 95 | | var activity = context.CreateActivity(activityType); |
| | 2075 | 96 | | activity.Type = fullTypeName; |
| | 2075 | 97 | | return activity; |
| | 11976 | 98 | | }, |
| | 11976 | 99 | | }; |
| | | 100 | | |
| | | 101 | | // If the activity has a default output, set its IsSerializable property to the value of the OutputAttribute.IsS |
| | | 102 | | var defaultOutputDescriptor = descriptor.Outputs.FirstOrDefault(x => x.Name == ActivityOutputRegister.DefaultOut |
| | | 103 | | |
| | 11976 | 104 | | if (defaultOutputDescriptor != null) |
| | | 105 | | { |
| | 2816 | 106 | | var isResultSerializable = outputAttribute?.IsSerializable; |
| | 2816 | 107 | | defaultOutputDescriptor.IsSerializable = isResultSerializable; |
| | | 108 | | } |
| | | 109 | | |
| | 11976 | 110 | | return descriptor; |
| | 11976 | 111 | | } |
| | | 112 | | |
| | | 113 | | /// <inheritdoc /> |
| | 211280 | 114 | | public IEnumerable<PropertyInfo> GetInputProperties([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Publi |
| | | 115 | | |
| | | 116 | | /// <inheritdoc /> |
| | 198411 | 117 | | public IEnumerable<PropertyInfo> GetOutputProperties([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Publ |
| | | 118 | | |
| | | 119 | | /// <inheritdoc /> |
| | | 120 | | public Task<OutputDescriptor> DescribeOutputPropertyAsync(PropertyInfo propertyInfo, CancellationToken cancellationT |
| | | 121 | | { |
| | 4468 | 122 | | var outputAttribute = propertyInfo.GetCustomAttribute<OutputAttribute>(); |
| | 4468 | 123 | | var descriptionAttribute = propertyInfo.GetCustomAttribute<DescriptionAttribute>(); |
| | 4468 | 124 | | var typeArgs = propertyInfo.PropertyType.GenericTypeArguments; |
| | 4468 | 125 | | var wrappedPropertyType = typeArgs.Any() ? typeArgs[0] : typeof(object); |
| | | 126 | | |
| | 4468 | 127 | | return Task.FromResult(new OutputDescriptor((outputAttribute?.Name ?? propertyInfo.Name).Pascalize(), outputAttr |
| | | 128 | | } |
| | | 129 | | |
| | | 130 | | /// <inheritdoc /> |
| | | 131 | | public async Task<InputDescriptor> DescribeInputPropertyAsync(PropertyInfo propertyInfo, CancellationToken cancellat |
| | | 132 | | { |
| | 17337 | 133 | | var inputAttribute = propertyInfo.GetCustomAttribute<InputAttribute>(); |
| | 17337 | 134 | | var descriptionAttribute = propertyInfo.GetCustomAttribute<DescriptionAttribute>(); |
| | 17337 | 135 | | var propertyType = propertyInfo.PropertyType; |
| | 17337 | 136 | | var isWrappedProperty = typeof(Input).IsAssignableFrom(propertyType); |
| | 17337 | 137 | | var autoEvaluate = inputAttribute?.AutoEvaluate ?? true; |
| | 17337 | 138 | | var wrappedPropertyType = !isWrappedProperty ? propertyType : propertyInfo.PropertyType.GenericTypeArguments[0]; |
| | | 139 | | |
| | 17337 | 140 | | if (wrappedPropertyType.IsNullableType()) |
| | 66 | 141 | | wrappedPropertyType = wrappedPropertyType.GetTypeOfNullable(); |
| | | 142 | | |
| | 17337 | 143 | | var uiSpecification = await propertyUIHandlerResolver.GetUIPropertiesAsync(propertyInfo, null, cancellationToken |
| | | 144 | | |
| | 17337 | 145 | | return new( |
| | 17337 | 146 | | inputAttribute?.Name ?? propertyInfo.Name, |
| | 17337 | 147 | | wrappedPropertyType, |
| | 17337 | 148 | | propertyInfo.GetValue, |
| | 17337 | 149 | | propertyInfo.SetValue, |
| | 17337 | 150 | | isWrappedProperty, |
| | 17337 | 151 | | GetUIHint(wrappedPropertyType, inputAttribute), |
| | 17337 | 152 | | inputAttribute?.DisplayName ?? propertyInfo.Name.Humanize(LetterCasing.Title), |
| | 17337 | 153 | | descriptionAttribute?.Description ?? inputAttribute?.Description, |
| | 17337 | 154 | | inputAttribute?.Category, |
| | 17337 | 155 | | inputAttribute?.Order ?? 0, |
| | 17337 | 156 | | defaultValueResolver.GetDefaultValue(propertyInfo), |
| | 17337 | 157 | | inputAttribute?.DefaultSyntax, |
| | 17337 | 158 | | inputAttribute?.IsReadOnly ?? false, |
| | 17337 | 159 | | inputAttribute?.IsBrowsable ?? true, |
| | 17337 | 160 | | inputAttribute?.IsSerializable ?? true, |
| | 17337 | 161 | | false, |
| | 17337 | 162 | | autoEvaluate, |
| | 17337 | 163 | | inputAttribute?.EvaluatorType, |
| | 17337 | 164 | | null, |
| | 17337 | 165 | | propertyInfo, |
| | 17337 | 166 | | uiSpecification |
| | 17337 | 167 | | ); |
| | 17337 | 168 | | } |
| | | 169 | | |
| | | 170 | | /// <inheritdoc /> |
| | | 171 | | public async Task<IEnumerable<InputDescriptor>> DescribeInputPropertiesAsync([DynamicallyAccessedMembers(Dynamically |
| | | 172 | | { |
| | 0 | 173 | | var properties = GetInputProperties(activityType); |
| | 0 | 174 | | return await DescribeInputPropertiesAsync(properties, cancellationToken); |
| | 0 | 175 | | } |
| | | 176 | | |
| | | 177 | | /// <inheritdoc /> |
| | | 178 | | public async Task<IEnumerable<OutputDescriptor>> DescribeOutputPropertiesAsync([DynamicallyAccessedMembers(Dynamical |
| | | 179 | | { |
| | 0 | 180 | | return await DescribeOutputPropertiesAsync(GetOutputProperties(activityType), cancellationToken); |
| | 0 | 181 | | } |
| | | 182 | | |
| | | 183 | | public static string GetUIHint(Type wrappedPropertyType, InputAttribute? inputAttribute = null) |
| | | 184 | | { |
| | 34674 | 185 | | if (inputAttribute?.UIHint != null) |
| | 11962 | 186 | | return inputAttribute.UIHint; |
| | | 187 | | |
| | 22712 | 188 | | if (wrappedPropertyType == typeof(bool) || wrappedPropertyType == typeof(bool?)) |
| | 4262 | 189 | | return InputUIHints.Checkbox; |
| | | 190 | | |
| | 18450 | 191 | | if (wrappedPropertyType == typeof(string)) |
| | 8856 | 192 | | return InputUIHints.SingleLine; |
| | | 193 | | |
| | 9594 | 194 | | if (typeof(IEnumerable).IsAssignableFrom(wrappedPropertyType)) |
| | 2508 | 195 | | return InputUIHints.DropDown; |
| | | 196 | | |
| | 7086 | 197 | | if (wrappedPropertyType.IsEnum || wrappedPropertyType.IsNullableType() && wrappedPropertyType.GetTypeOfNullable( |
| | 0 | 198 | | return InputUIHints.DropDown; |
| | | 199 | | |
| | 7086 | 200 | | if (wrappedPropertyType == typeof(Variable)) |
| | 498 | 201 | | return InputUIHints.VariablePicker; |
| | | 202 | | |
| | 6588 | 203 | | if (wrappedPropertyType == typeof(WorkflowInput)) |
| | 0 | 204 | | return InputUIHints.InputPicker; |
| | | 205 | | |
| | 6588 | 206 | | if (wrappedPropertyType == typeof(Type)) |
| | 0 | 207 | | return InputUIHints.TypePicker; |
| | | 208 | | |
| | 6588 | 209 | | return InputUIHints.SingleLine; |
| | | 210 | | } |
| | | 211 | | |
| | | 212 | | private static string GetFriendlyActivityName(Type t) |
| | | 213 | | { |
| | 11976 | 214 | | if (!t.IsGenericType) |
| | 11943 | 215 | | return t.Name; |
| | 33 | 216 | | var baseName = t.Name.Substring(0, t.Name.IndexOf('`')); |
| | 66 | 217 | | var argNames = string.Join(", ", t.GetGenericArguments().Select(a => a.Name)); |
| | 33 | 218 | | return $"{baseName}<{argNames}>"; |
| | | 219 | | } |
| | | 220 | | |
| | | 221 | | private async Task<IEnumerable<InputDescriptor>> DescribeInputPropertiesAsync(IEnumerable<PropertyInfo> properties, |
| | | 222 | | { |
| | 29313 | 223 | | return await Task.WhenAll(properties.Select(async x => await DescribeInputPropertyAsync(x, cancellationToken))); |
| | 11976 | 224 | | } |
| | | 225 | | |
| | | 226 | | private async Task<IEnumerable<OutputDescriptor>> DescribeOutputPropertiesAsync(IEnumerable<PropertyInfo> properties |
| | | 227 | | { |
| | 16444 | 228 | | return await Task.WhenAll(properties.Select(async x => await DescribeOutputPropertyAsync(x, cancellationToken))) |
| | 11976 | 229 | | } |
| | | 230 | | } |