< 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
70%
Covered lines: 109
Uncovered lines: 45
Coverable lines: 154
Total lines: 246
Line coverage: 70.7%
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
 46313public class HostMethodActivityDescriber(IActivityDescriber activityDescriber) : IHostMethodActivityDescriber
 14{
 15    public async Task<IEnumerable<ActivityDescriptor>> DescribeAsync(string key, Type hostType, CancellationToken cancel
 16    {
 11217        var methods = hostType
 11218            .GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly)
 100819            .Where(m => !m.IsSpecialName)
 11220            .ToList();
 21
 11222        var descriptors = new List<ActivityDescriptor>(methods.Count);
 224023        foreach (var method in methods)
 24        {
 100825            var descriptor = await DescribeMethodAsync(key, hostType, method, cancellationToken);
 100826            descriptors.Add(descriptor);
 27        }
 28
 11229        return descriptors;
 11230    }
 31
 32    public async Task<ActivityDescriptor> DescribeMethodAsync(string key, Type hostType, MethodInfo method, Cancellation
 33    {
 100834        var descriptor = await activityDescriber.DescribeActivityAsync(typeof(HostMethodActivity), cancellationToken);
 100835        var activityAttribute = hostType.GetCustomAttribute<ActivityAttribute>() ?? method.GetCustomAttribute<ActivityAt
 36
 100837        var methodName = method.Name;
 100838        var activityTypeName = BuildActivityTypeName(key, method, activityAttribute);
 39
 100840        var displayAttribute = method.GetCustomAttribute<DisplayAttribute>();
 100841        var typeDisplayName = activityAttribute?.DisplayName ?? hostType.GetCustomAttribute<DisplayNameAttribute>()?.Dis
 100842        var methodNameWithoutAsync = StripAsyncSuffix(methodName);
 100843        var methodDisplayName = displayAttribute?.Name ?? methodNameWithoutAsync.Humanize().Transform(To.TitleCase);
 100844        var displayName = !string.IsNullOrWhiteSpace(typeDisplayName) ? typeDisplayName : methodDisplayName;
 100845        if (!string.IsNullOrWhiteSpace(activityAttribute?.DisplayName))
 5646            displayName = activityAttribute.DisplayName!;
 47
 100848        descriptor.Name = methodName;
 100849        descriptor.TypeName = activityTypeName;
 100850        descriptor.DisplayName = displayName;
 100851        descriptor.Description = activityAttribute?.Description ?? method.GetCustomAttribute<DescriptionAttribute>()?.De
 100852        descriptor.Category = activityAttribute?.Category ?? hostType.Name.Humanize().Transform(To.TitleCase);
 100853        descriptor.Kind = activityAttribute?.Kind ?? ActivityKind.Task;
 100854        descriptor.RunAsynchronously = activityAttribute?.RunAsynchronously ?? false;
 100855        descriptor.IsBrowsable = true;
 100856        descriptor.ClrType = typeof(HostMethodActivity);
 57
 100858        descriptor.Constructor = context =>
 100859        {
 060            var activityResult = context.CreateActivity<HostMethodActivity>();
 061            var activity = activityResult.Activity;
 062            activity.Type = activityTypeName;
 063            activity.HostType = hostType;
 064            activity.MethodName = methodName;
 065            activity.RunAsynchronously ??= descriptor.RunAsynchronously;
 066            return activityResult;
 100867        };
 68
 100869        descriptor.Inputs.Clear();
 201670        foreach (var prop in hostType.GetProperties(BindingFlags.Instance | BindingFlags.Public))
 71        {
 072            if (!IsInputProperty(prop))
 73                continue;
 74
 075            var inputDescriptor = CreatePropertyInputDescriptor(prop);
 076            descriptor.Inputs.Add(inputDescriptor);
 77        }
 78
 414479        foreach (var parameter in method.GetParameters())
 80        {
 106481            if (IsSpecialParameter(parameter))
 82                continue;
 83
 84            // If FromServices is used, the parameter is not a workflow input unless explicitly forced via [Input].
 84085            var isFromServices = parameter.GetCustomAttribute<FromServicesAttribute>() != null;
 84086            var isExplicitInput = parameter.GetCustomAttribute<InputAttribute>() != null;
 84087            if (isFromServices && !isExplicitInput)
 88                continue;
 89
 84090            var inputDescriptor = CreateParameterInputDescriptor(parameter);
 84091            descriptor.Inputs.Add(inputDescriptor);
 92        }
 93
 100894        descriptor.Outputs.Clear();
 100895        var outputDescriptor = CreateOutputDescriptor(method);
 100896        if (outputDescriptor != null)
 33697            descriptor.Outputs.Add(outputDescriptor);
 98
 100899        return descriptor;
 1008100    }
 101
 102    private string BuildActivityTypeName(string key, MethodInfo method, ActivityAttribute? activityAttribute)
 103    {
 1008104        var methodName = StripAsyncSuffix(method.Name);
 105
 1008106        if (activityAttribute != null && !string.IsNullOrWhiteSpace(activityAttribute.Namespace))
 107        {
 56108            var typeSegment = activityAttribute.Type ?? methodName;
 56109            return $"{activityAttribute.Namespace}.{typeSegment}";
 110        }
 111
 952112        return $"Elsa.Dynamic.HostMethod.{key.Pascalize()}.{methodName}";
 113    }
 114
 115    private static string StripAsyncSuffix(string name)
 116    {
 2016117        return name.EndsWith("Async", StringComparison.Ordinal)
 2016118            ? name[..^5]
 2016119            : name;
 120    }
 121
 122    private InputDescriptor CreatePropertyInputDescriptor(PropertyInfo prop)
 123    {
 0124        var inputAttribute = prop.GetCustomAttribute<InputAttribute>();
 0125        var displayNameAttribute = prop.GetCustomAttribute<DisplayNameAttribute>();
 0126        var descriptionAttribute = prop.GetCustomAttribute<DescriptionAttribute>();
 127
 0128        var inputName = inputAttribute?.Name ?? prop.Name;
 0129        var displayName = inputAttribute?.DisplayName ?? displayNameAttribute?.DisplayName ?? prop.Name.Humanize();
 0130        var description = inputAttribute?.Description ?? descriptionAttribute?.Description;
 0131        var nakedInputType = prop.PropertyType;
 132
 0133        return new()
 0134        {
 0135            Name = inputName,
 0136            DisplayName = displayName,
 0137            Description = description,
 0138            Type = nakedInputType,
 0139            ValueGetter = activity => activity.SyntheticProperties.GetValueOrDefault(inputName),
 0140            ValueSetter = (activity, value) => activity.SyntheticProperties[inputName] = value!,
 0141            IsSynthetic = true,
 0142            IsWrapped = true,
 0143            UIHint = inputAttribute?.UIHint ?? ActivityDescriber.GetUIHint(nakedInputType),
 0144            Category = inputAttribute?.Category,
 0145            DefaultValue = inputAttribute?.DefaultValue,
 0146            Order = inputAttribute?.Order ?? 0,
 0147            IsBrowsable = inputAttribute?.IsBrowsable ?? true,
 0148            AutoEvaluate = inputAttribute?.AutoEvaluate ?? true,
 0149            IsSerializable = inputAttribute?.IsSerializable ?? true
 0150        };
 151    }
 152
 153    private InputDescriptor CreateParameterInputDescriptor(ParameterInfo parameter)
 154    {
 840155        var inputAttribute = parameter.GetCustomAttribute<InputAttribute>();
 840156        var displayNameAttribute = parameter.GetCustomAttribute<DisplayNameAttribute>();
 157
 840158        var inputName = inputAttribute?.Name ?? parameter.Name ?? "input";
 840159        var displayName = inputAttribute?.DisplayName ?? displayNameAttribute?.DisplayName ?? inputName.Humanize();
 840160        var description = inputAttribute?.Description;
 840161        var nakedInputType = parameter.ParameterType;
 162
 840163        return new()
 840164        {
 840165            Name = inputName,
 840166            DisplayName = displayName,
 840167            Description = description,
 840168            Type = nakedInputType,
 0169            ValueGetter = activity => activity.SyntheticProperties.GetValueOrDefault(inputName),
 0170            ValueSetter = (activity, value) => activity.SyntheticProperties[inputName] = value!,
 840171            IsSynthetic = true,
 840172            IsWrapped = true,
 840173            UIHint = inputAttribute?.UIHint ?? ActivityDescriber.GetUIHint(nakedInputType),
 840174            Category = inputAttribute?.Category,
 840175            DefaultValue = inputAttribute?.DefaultValue,
 840176            Order = inputAttribute?.Order ?? 0,
 840177            IsBrowsable = inputAttribute?.IsBrowsable ?? true,
 840178            AutoEvaluate = inputAttribute?.AutoEvaluate ?? true,
 840179            IsSerializable = inputAttribute?.IsSerializable ?? true
 840180        };
 181    }
 182
 183    private OutputDescriptor? CreateOutputDescriptor(MethodInfo method)
 184    {
 1008185        var returnType = method.ReturnType;
 186
 187        // No output for void or Task.
 1008188        if (returnType == typeof(void) || returnType == typeof(Task))
 672189            return null;
 190
 191        // Determine the "real" return type.
 192        Type actualReturnType;
 336193        if (returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(Task<>))
 56194            actualReturnType = returnType.GetGenericArguments()[0];
 280195        else if (typeof(Task).IsAssignableFrom(returnType))
 0196            return null;
 197        else
 280198            actualReturnType = returnType;
 199
 336200        var outputAttribute = method.ReturnParameter.GetCustomAttribute<OutputAttribute>() ??
 336201                              method.GetCustomAttribute<OutputAttribute>() ??
 336202                              method.DeclaringType?.GetCustomAttribute<OutputAttribute>();
 203
 336204        var displayNameAttribute = method.ReturnParameter.GetCustomAttribute<DisplayNameAttribute>();
 336205        var outputName = outputAttribute?.Name ?? "Output";
 336206        var displayName = outputAttribute?.DisplayName ?? displayNameAttribute?.DisplayName ?? outputName.Humanize();
 336207        var description = outputAttribute?.Description ?? "The method output.";
 336208        var nakedOutputType = actualReturnType;
 209
 336210        return new()
 336211        {
 336212            Name = outputName,
 336213            DisplayName = displayName,
 336214            Description = description,
 336215            Type = nakedOutputType,
 336216            IsSynthetic = true,
 0217            ValueGetter = activity => activity.SyntheticProperties.GetValueOrDefault(outputName),
 0218            ValueSetter = (activity, value) => activity.SyntheticProperties[outputName] = value!,
 336219            IsBrowsable = outputAttribute?.IsBrowsable ?? true,
 336220            IsSerializable = outputAttribute?.IsSerializable ?? true
 336221        };
 222    }
 223
 224    private static bool IsSpecialParameter(ParameterInfo parameter)
 225    {
 226        // These parameters are supplied by the runtime and should not become input descriptors.
 1064227        if (parameter.ParameterType == typeof(CancellationToken))
 56228            return true;
 229
 1008230        if (parameter.ParameterType == typeof(ActivityExecutionContext))
 168231            return true;
 232
 840233        return false;
 234    }
 235
 236    private static bool IsInputProperty(PropertyInfo prop)
 237    {
 0238        if (!prop.CanRead || !prop.CanWrite)
 0239            return false;
 240
 0241        if (prop.GetIndexParameters().Length > 0)
 0242            return false;
 243
 0244        return true;
 245    }
 246}