using System.Dynamic; using Elsa.Expressions.Models; using Elsa.Extensions; using Elsa.Scripting.Liquid.Helpers; using Elsa.Scripting.Liquid.Notifications; using Elsa.Scripting.Liquid.Options; using Elsa.Mediator.Contracts; using Elsa.Workflows.Management.Options; using Elsa.Workflows.Memory; using Fluid; using Fluid.Values; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Options; namespace Elsa.Scripting.Liquid.Handlers; /// /// Configures the liquid templating engine before evaluating a liquid expression. /// internal class ConfigureLiquidEngine : INotificationHandler { private readonly IConfiguration _configuration; private readonly ManagementOptions _managementOptions; private readonly FluidOptions _fluidOptions; /// /// Constructor. /// public ConfigureLiquidEngine(IConfiguration configuration, IOptions fluidOptions, IOptions managementOptions) { _configuration = configuration; _managementOptions = managementOptions.Value; _fluidOptions = fluidOptions.Value; } /// public Task HandleAsync(RenderingLiquidTemplate notification, CancellationToken cancellationToken) { var context = notification.TemplateContext; var options = context.Options; var memberAccessStrategy = options.MemberAccessStrategy; memberAccessStrategy.Register(); memberAccessStrategy.Register((x, name) => x.GetValueAsync(name)); memberAccessStrategy.Register((x, name) => ((IDictionary)x!)[name]); memberAccessStrategy.Register("Variables", x => new LiquidPropertyAccessor(name => GetVariable(x, name, options))); memberAccessStrategy.Register("Input", x => new LiquidPropertyAccessor(name => GetInput(x, name, options))); memberAccessStrategy.Register("CorrelationId", x => x.GetWorkflowExecutionContext().CorrelationId); memberAccessStrategy.Register("WorkflowDefinitionId", x => x.GetWorkflowExecutionContext().Workflow.Identity.DefinitionId); memberAccessStrategy.Register("WorkflowDefinitionVersionId", x => x.GetWorkflowExecutionContext().Workflow.Identity.Id); memberAccessStrategy.Register("WorkflowDefinitionVersion", x => x.GetWorkflowExecutionContext().Workflow.Identity.Version); memberAccessStrategy.Register("WorkflowInstanceId", x => x.GetActivityExecutionContext().WorkflowExecutionContext.Id); if (_fluidOptions.AllowConfigurationAccess) { memberAccessStrategy.Register("Configuration", x => new LiquidPropertyAccessor(name => ToFluidValue(GetConfigurationValue(name), options))); memberAccessStrategy.Register((source, name) => source.GetSection(name)); } // Register all variable types. foreach (var variableDescriptor in _managementOptions.VariableDescriptors.Where(x => x.Type is { IsClass: true, ContainsGenericParameters: false })) memberAccessStrategy.Register(variableDescriptor.Type); return Task.CompletedTask; } private ConfigurationSectionWrapper GetConfigurationValue(string name) => new(_configuration.GetSection(name)); private Task ToFluidValue(object? input, TemplateOptions options) => Task.FromResult(FluidValue.Create(input, options)); private Task GetVariable(ExpressionExecutionContext context, string key, TemplateOptions options) { var value = GetVariableInScope(context, key); return Task.FromResult(value == null ? NilValue.Instance : FluidValue.Create(value, options)); } private Task GetInput(ExpressionExecutionContext context, string key, TemplateOptions options) { var workflowExecutionContext = context.GetWorkflowExecutionContext(); var input = workflowExecutionContext.Input.TryGetValue(key, out var value) ? value : default; return Task.FromResult(input == null ? NilValue.Instance : FluidValue.Create(value, options)); } private static object? GetVariableInScope(ExpressionExecutionContext context, string variableName) { var q = from variable in EnumerateVariablesInScope(context) where variable.Name == variableName where variable.TryGet(context, out _) select variable.Get(context); return q.FirstOrDefault(); } private static IEnumerable EnumerateVariablesInScope(ExpressionExecutionContext context) { var currentScope = context; while (currentScope != null) { if (!currentScope.TryGetActivityExecutionContext(out var activityExecutionContext)) break; var variables = activityExecutionContext.Variables; foreach (var variable in variables) yield return variable; currentScope = currentScope.ParentContext; } } }