< Summary

Information
Class: Elsa.Expressions.CSharp.Services.CSharpEvaluator
Assembly: Elsa.Expressions.CSharp
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Expressions.CSharp/Services/CSharpEvaluator.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 35
Coverable lines: 35
Total lines: 91
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 12
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%210%
EvaluateAsync()0%4260%
GetCompiledScript(...)0%620%
Hash(...)0%2040%

File(s)

/home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Expressions.CSharp/Services/CSharpEvaluator.cs

#LineLine coverage
 1using System.Security.Cryptography;
 2using System.Text;
 3using Elsa.Expressions.CSharp.Contracts;
 4using Elsa.Expressions.CSharp.Models;
 5using Elsa.Expressions.CSharp.Notifications;
 6using Elsa.Expressions.CSharp.Options;
 7using Elsa.Expressions.Models;
 8using Elsa.Mediator.Contracts;
 9using Microsoft.CodeAnalysis;
 10using Microsoft.CodeAnalysis.CSharp.Scripting;
 11using Microsoft.CodeAnalysis.Scripting;
 12using Microsoft.Extensions.Caching.Memory;
 13using Microsoft.Extensions.Options;
 14
 15namespace Elsa.Expressions.CSharp.Services;
 16
 17/// <summary>
 18/// A C# expression evaluator using Roslyn.
 19/// </summary>
 20/// <remarks>
 21/// Initializes a new instance of the <see cref="CSharpEvaluator"/> class.
 22/// </remarks>
 023public class CSharpEvaluator(INotificationSender notificationSender, IOptions<CSharpOptions> scriptOptions, IMemoryCache
 24{
 025    private readonly CSharpOptions _csharpOptions = scriptOptions.Value;
 26
 27    /// <inheritdoc />
 28    public async Task<object?> EvaluateAsync(
 29        string expression,
 30        Type returnType,
 31        ExpressionExecutionContext context,
 32        ExpressionEvaluatorOptions options,
 33        Func<ScriptOptions, ScriptOptions>? configureScriptOptions = default,
 34        Func<Script<object>, Script<object>>? configureScript = default,
 35        CancellationToken cancellationToken = default)
 36    {
 037        if (!_csharpOptions.AllowHostCodeExecution)
 038            throw new InvalidOperationException("C# workflow expression execution is disabled. Set CSharpOptions.AllowHo
 39
 040        var scriptOptions = ScriptOptions.Default.WithOptimizationLevel(OptimizationLevel.Release);
 41
 042        if (configureScriptOptions != null)
 043            scriptOptions = configureScriptOptions(scriptOptions);
 44
 045        var globals = new Globals(context, options.Arguments);
 046        var script = CSharpScript.Create("", scriptOptions, typeof(Globals));
 47
 048        if (configureScript != null)
 049            script = configureScript(script);
 50
 051        var notification = new EvaluatingCSharp(options, script, scriptOptions, context);
 052        await notificationSender.SendAsync(notification, cancellationToken);
 053        scriptOptions = notification.ScriptOptions;
 054        script = notification.Script.ContinueWith(expression, scriptOptions);
 055        var runner = GetCompiledScript(script);
 056        return await runner(globals, cancellationToken: cancellationToken);
 057    }
 58
 59    private ScriptRunner<object> GetCompiledScript(Script<object> script)
 60    {
 061        var cacheKey = "csharp:script:" + Hash(script);
 62
 063        return memoryCache.GetOrCreate(cacheKey, entry =>
 064        {
 065            if (_csharpOptions.ScriptCacheTimeout.HasValue)
 066                entry.SetSlidingExpiration(_csharpOptions.ScriptCacheTimeout.Value);
 067
 068            return script.CreateDelegate();
 069        })!;
 70    }
 71
 72    private static string Hash(Script<object> script)
 73    {
 074        var ms = new MemoryStream();
 075        using (var sw = new StreamWriter(ms, Encoding.UTF8))
 76        {
 077            for (Script current = script; current != null; current = current.Previous)
 78            {
 079                sw.WriteLine(current.Code);
 80            }
 081        }
 82
 083        if (!ms.TryGetBuffer(out var segment))
 84        {
 085            segment = ms.ToArray();
 86        }
 87
 088        var hash = SHA256.HashData(segment.AsSpan());
 089        return Convert.ToBase64String(hash);
 90    }
 91}