< Summary

Information
Class: Elsa.Workflows.Management.Services.HostMethodActivityDescriber
Assembly: Elsa.Workflows.Management
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Workflows.Management/Services/HostMethodActivityDescriber.cs
Line coverage
71%
Covered lines: 109
Uncovered lines: 44
Coverable lines: 153
Total lines: 245
Line coverage: 71.2%
Branch coverage
59%
Covered branches: 103
Total branches: 174
Branch coverage: 59.1%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
DescribeAsync()100%22100%
DescribeMethodAsync()86%515092.68%
BuildActivityTypeName(...)83.33%66100%
StripAsyncSuffix(...)50%22100%
CreatePropertyInputDescriptor(...)0%1190340%
CreateParameterInputDescriptor(...)62.5%333291.66%
CreateOutputDescriptor(...)77.77%383689.28%
IsSpecialParameter(...)100%44100%
IsInputProperty(...)0%4260%

File(s)

/home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Workflows.Management/Services/HostMethodActivityDescriber.cs

#LineLine coverage
 1using System.ComponentModel;
 2using System.ComponentModel.DataAnnotations;
 3using System.Reflection;
 4using Elsa.Extensions;
 5using Elsa.Workflows.Attributes;
 6using Elsa.Workflows.Management.Activities.HostMethod;
 7using Elsa.Workflows.Management.Attributes;
 8using Elsa.Workflows.Models;
 9using Humanizer;
 10
 11namespace Elsa.Workflows.Management.Services;
 12
 44113public class HostMethodActivityDescriber(IActivityDescriber activityDescriber) : IHostMethodActivityDescriber
 14{
 15    public async Task<IEnumerable<ActivityDescriptor>> DescribeAsync(string key, Type hostType, CancellationToken cancel
 16    {
 2817        var methods = hostType
 2818            .GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly)
 25219            .Where(m => !m.IsSpecialName)
 2820            .ToList();
 21
 2822        var descriptors = new List<ActivityDescriptor>(methods.Count);
 56023        foreach (var method in methods)
 24        {
 25225            var descriptor = await DescribeMethodAsync(key, hostType, method, cancellationToken);
 25226            descriptors.Add(descriptor);
 27        }
 28
 2829        return descriptors;
 2830    }
 31
 32    public async Task<ActivityDescriptor> DescribeMethodAsync(string key, Type hostType, MethodInfo method, Cancellation
 33    {
 25234        var descriptor = await activityDescriber.DescribeActivityAsync(typeof(HostMethodActivity), cancellationToken);
 25235        var activityAttribute = hostType.GetCustomAttribute<ActivityAttribute>() ?? method.GetCustomAttribute<ActivityAt
 36
 25237        var methodName = method.Name;
 25238        var activityTypeName = BuildActivityTypeName(key, method, activityAttribute);
 39
 25240        var displayAttribute = method.GetCustomAttribute<DisplayAttribute>();
 25241        var typeDisplayName = activityAttribute?.DisplayName ?? hostType.GetCustomAttribute<DisplayNameAttribute>()?.Dis
 25242        var methodNameWithoutAsync = StripAsyncSuffix(methodName);
 25243        var methodDisplayName = displayAttribute?.Name ?? methodNameWithoutAsync.Humanize().Transform(To.TitleCase);
 25244        var displayName = !string.IsNullOrWhiteSpace(typeDisplayName) ? typeDisplayName : methodDisplayName;
 25245        if (!string.IsNullOrWhiteSpace(activityAttribute?.DisplayName))
 1446            displayName = activityAttribute.DisplayName!;
 47
 25248        descriptor.Name = methodName;
 25249        descriptor.TypeName = activityTypeName;
 25250        descriptor.DisplayName = displayName;
 25251        descriptor.Description = activityAttribute?.Description ?? method.GetCustomAttribute<DescriptionAttribute>()?.De
 25252        descriptor.Category = activityAttribute?.Category ?? hostType.Name.Humanize().Transform(To.TitleCase);
 25253        descriptor.Kind = activityAttribute?.Kind ?? ActivityKind.Task;
 25254        descriptor.RunAsynchronously = activityAttribute?.RunAsynchronously ?? false;
 25255        descriptor.IsBrowsable = true;
 25256        descriptor.ClrType = typeof(HostMethodActivity);
 57
 25258        descriptor.Constructor = context =>
 25259        {
 060            var activity = context.CreateActivity<HostMethodActivity>();
 061            activity.Type = activityTypeName;
 062            activity.HostType = hostType;
 063            activity.MethodName = methodName;
 064            activity.RunAsynchronously ??= descriptor.RunAsynchronously;
 065            return activity;
 25266        };
 67
 25268        descriptor.Inputs.Clear();
 50469        foreach (var prop in hostType.GetProperties(BindingFlags.Instance | BindingFlags.Public))
 70        {
 071            if (!IsInputProperty(prop))
 72                continue;
 73
 074            var inputDescriptor = CreatePropertyInputDescriptor(prop);
 075            descriptor.Inputs.Add(inputDescriptor);
 76        }
 77
 103678        foreach (var parameter in method.GetParameters())
 79        {
 26680            if (IsSpecialParameter(parameter))
 81                continue;
 82
 83            // If FromServices is used, the parameter is not a workflow input unless explicitly forced via [Input].
 21084            var isFromServices = parameter.GetCustomAttribute<FromServicesAttribute>() != null;
 21085            var isExplicitInput = parameter.GetCustomAttribute<InputAttribute>() != null;
 21086            if (isFromServices && !isExplicitInput)
 87                continue;
 88
 21089            var inputDescriptor = CreateParameterInputDescriptor(parameter);
 21090            descriptor.Inputs.Add(inputDescriptor);
 91        }
 92
 25293        descriptor.Outputs.Clear();
 25294        var outputDescriptor = CreateOutputDescriptor(method);
 25295        if (outputDescriptor != null)
 8496            descriptor.Outputs.Add(outputDescriptor);
 97
 25298        return descriptor;
 25299    }
 100
 101    private string BuildActivityTypeName(string key, MethodInfo method, ActivityAttribute? activityAttribute)
 102    {
 252103        var methodName = StripAsyncSuffix(method.Name);
 104
 252105        if (activityAttribute != null && !string.IsNullOrWhiteSpace(activityAttribute.Namespace))
 106        {
 14107            var typeSegment = activityAttribute.Type ?? methodName;
 14108            return $"{activityAttribute.Namespace}.{typeSegment}";
 109        }
 110
 238111        return $"Elsa.Dynamic.HostMethod.{key.Pascalize()}.{methodName}";
 112    }
 113
 114    private static string StripAsyncSuffix(string name)
 115    {
 504116        return name.EndsWith("Async", StringComparison.Ordinal)
 504117            ? name[..^5]
 504118            : name;
 119    }
 120
 121    private InputDescriptor CreatePropertyInputDescriptor(PropertyInfo prop)
 122    {
 0123        var inputAttribute = prop.GetCustomAttribute<InputAttribute>();
 0124        var displayNameAttribute = prop.GetCustomAttribute<DisplayNameAttribute>();
 0125        var descriptionAttribute = prop.GetCustomAttribute<DescriptionAttribute>();
 126
 0127        var inputName = inputAttribute?.Name ?? prop.Name;
 0128        var displayName = inputAttribute?.DisplayName ?? displayNameAttribute?.DisplayName ?? prop.Name.Humanize();
 0129        var description = inputAttribute?.Description ?? descriptionAttribute?.Description;
 0130        var nakedInputType = prop.PropertyType;
 131
 0132        return new()
 0133        {
 0134            Name = inputName,
 0135            DisplayName = displayName,
 0136            Description = description,
 0137            Type = nakedInputType,
 0138            ValueGetter = activity => activity.SyntheticProperties.GetValueOrDefault(inputName),
 0139            ValueSetter = (activity, value) => activity.SyntheticProperties[inputName] = value!,
 0140            IsSynthetic = true,
 0141            IsWrapped = true,
 0142            UIHint = inputAttribute?.UIHint ?? ActivityDescriber.GetUIHint(nakedInputType),
 0143            Category = inputAttribute?.Category,
 0144            DefaultValue = inputAttribute?.DefaultValue,
 0145            Order = inputAttribute?.Order ?? 0,
 0146            IsBrowsable = inputAttribute?.IsBrowsable ?? true,
 0147            AutoEvaluate = inputAttribute?.AutoEvaluate ?? true,
 0148            IsSerializable = inputAttribute?.IsSerializable ?? true
 0149        };
 150    }
 151
 152    private InputDescriptor CreateParameterInputDescriptor(ParameterInfo parameter)
 153    {
 210154        var inputAttribute = parameter.GetCustomAttribute<InputAttribute>();
 210155        var displayNameAttribute = parameter.GetCustomAttribute<DisplayNameAttribute>();
 156
 210157        var inputName = inputAttribute?.Name ?? parameter.Name ?? "input";
 210158        var displayName = inputAttribute?.DisplayName ?? displayNameAttribute?.DisplayName ?? inputName.Humanize();
 210159        var description = inputAttribute?.Description;
 210160        var nakedInputType = parameter.ParameterType;
 161
 210162        return new()
 210163        {
 210164            Name = inputName,
 210165            DisplayName = displayName,
 210166            Description = description,
 210167            Type = nakedInputType,
 0168            ValueGetter = activity => activity.SyntheticProperties.GetValueOrDefault(inputName),
 0169            ValueSetter = (activity, value) => activity.SyntheticProperties[inputName] = value!,
 210170            IsSynthetic = true,
 210171            IsWrapped = true,
 210172            UIHint = inputAttribute?.UIHint ?? ActivityDescriber.GetUIHint(nakedInputType),
 210173            Category = inputAttribute?.Category,
 210174            DefaultValue = inputAttribute?.DefaultValue,
 210175            Order = inputAttribute?.Order ?? 0,
 210176            IsBrowsable = inputAttribute?.IsBrowsable ?? true,
 210177            AutoEvaluate = inputAttribute?.AutoEvaluate ?? true,
 210178            IsSerializable = inputAttribute?.IsSerializable ?? true
 210179        };
 180    }
 181
 182    private OutputDescriptor? CreateOutputDescriptor(MethodInfo method)
 183    {
 252184        var returnType = method.ReturnType;
 185
 186        // No output for void or Task.
 252187        if (returnType == typeof(void) || returnType == typeof(Task))
 168188            return null;
 189
 190        // Determine the "real" return type.
 191        Type actualReturnType;
 84192        if (returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(Task<>))
 14193            actualReturnType = returnType.GetGenericArguments()[0];
 70194        else if (typeof(Task).IsAssignableFrom(returnType))
 0195            return null;
 196        else
 70197            actualReturnType = returnType;
 198
 84199        var outputAttribute = method.ReturnParameter.GetCustomAttribute<OutputAttribute>() ??
 84200                              method.GetCustomAttribute<OutputAttribute>() ??
 84201                              method.DeclaringType?.GetCustomAttribute<OutputAttribute>();
 202
 84203        var displayNameAttribute = method.ReturnParameter.GetCustomAttribute<DisplayNameAttribute>();
 84204        var outputName = outputAttribute?.Name ?? "Output";
 84205        var displayName = outputAttribute?.DisplayName ?? displayNameAttribute?.DisplayName ?? outputName.Humanize();
 84206        var description = outputAttribute?.Description ?? "The method output.";
 84207        var nakedOutputType = actualReturnType;
 208
 84209        return new()
 84210        {
 84211            Name = outputName,
 84212            DisplayName = displayName,
 84213            Description = description,
 84214            Type = nakedOutputType,
 84215            IsSynthetic = true,
 0216            ValueGetter = activity => activity.SyntheticProperties.GetValueOrDefault(outputName),
 0217            ValueSetter = (activity, value) => activity.SyntheticProperties[outputName] = value!,
 84218            IsBrowsable = outputAttribute?.IsBrowsable ?? true,
 84219            IsSerializable = outputAttribute?.IsSerializable ?? true
 84220        };
 221    }
 222
 223    private static bool IsSpecialParameter(ParameterInfo parameter)
 224    {
 225        // These parameters are supplied by the runtime and should not become input descriptors.
 266226        if (parameter.ParameterType == typeof(CancellationToken))
 14227            return true;
 228
 252229        if (parameter.ParameterType == typeof(ActivityExecutionContext))
 42230            return true;
 231
 210232        return false;
 233    }
 234
 235    private static bool IsInputProperty(PropertyInfo prop)
 236    {
 0237        if (!prop.CanRead || !prop.CanWrite)
 0238            return false;
 239
 0240        if (prop.GetIndexParameters().Length > 0)
 0241            return false;
 242
 0243        return true;
 244    }
 245}