Announcement: Introducing .NET C# Inline Action for Azure Logic Apps (Standard) – Preview
We are introducing a new capability that allows developers to write .NET C# script right within the Logic Apps designer. This complements the custom code feature that we introduced previously for invoking .NET FX and NET8 functions written and deployed to a Logic App.
This new capability provides the following benefits:
Extending our strategy to provide a no-cliff extensibility to our low code offering giving our developers the flexibility and tools needed to solve toughest integration problems. Our development experience within azure portal in Azure portal is what many of our developers first go to when starting to build solution using Logic Apps and this new capability would allow them to use full power of .NET within the portal-based development experience.
Like Custom code, there is no additional plan required – write your code right within the designer.
We built on top of Azure Functions C# script capability and hence inherit many of its features
In the preview release, the script code is running within the Azure Function host process and we do not support referencing custom assemblies at this point for which you can still use our custom code capability. However, you have access to all the framework assemblies as well as Newtonsoft.Json for serialization needs. We intent to add the support for referencing custom assemblies before this capability is generally available.
Adding C# script in your workflow
You will see a new action called “Execute CSharp Script Code” under “Inline Code” in the list of actions available for you.
Upon selecting this action, a code editor will pop-up allowing you to write your code. The code editor will start with “boilerplate” code to help guide you in writing your first CSharp script in Logic App.
The script code is saved as a .csx file in the same folder as your workflow.json file and deployed to your application along with the workflow definition. As part of Logic App initialization, this code file will be compiled and be ready for execution.
How does the scripting work?
The .csx format allows you to write less “boilerplate” and focus on writing just a C# function. Instead of wrapping everything in a namespace and class, just define a Run method. Include any assembly references and namespaces at the beginning of the file as usual. The name of this method is predefined, and your workflow can run only invoke this Run method at runtime.
Data from your workflow flows into your Run method through parameter of WorkflowContext type. In addition to the workflow context, you can also have this method take function logger as a parameter and a cancellation tokens (needed if your script is long running and needs to be gracefully terminate in case of Function Host is shutting down).
// Add the required libraries
#r “Newtonsoft.Json”
#r “Microsoft.Azure.Workflows.Scripting”
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
using Microsoft.Extensions.Logging;
using Microsoft.Azure.Workflows.Scripting;
using Newtonsoft.Json.Linq;
public static async Task<Results> Run(WorkflowContext context, ILogger log)
{
var triggerOutputs = (await context.GetTriggerResults().ConfigureAwait(false)).Outputs;
var name = triggerOutputs?[“body”]?[“name”]?.ToString();
return new Results
{
Message = !string.IsNullOrEmpty(name) ? $”Hello {name} from CSharp action” : “Hello from CSharp action.”
};
}
public class Results
{
public string Message {get; set;}
}
The #r statement is explained here. And class definition for WorkflowContext provided here.
Flowing data into the script from the workflow
The WorkflowContext has two method that you can use to access the data from your workflow.
For accessing data from your trigger, you can use GetTriggerResults method. This will return an object representing the trigger and its outputs is available in the Outputs property. It is an object of type JObject and you can use [] indexer to lookup for various properties in the trigger outputs. For example, the below code retrieves the data from trigger outputs body property
public static async Task<Results> Run(WorkflowContext context, ILogger log)
{
var triggerOutputs = (await context.GetTriggerResults().ConfigureAwait(false)).Outputs;
var body = triggerOutputs[“body”];
}
For accessing data from an action, you can use the GetActionResults method. Like triggers, this will return an object representing the action and its outputs is available in the Outputs property. This method will take action name as parameter as shown below.
public static async Task<Results> Run(WorkflowContext context, ILogger log)
{
var actionOutputs = (await context.GetActionResults(“actionName”).ConfigureAwait(false)).Outputs;
var body = actionOutputs[“body”];
}
Returning data back to your workflow
Your run method can have a return type and it can also be a Task<> if you want the method to be async, the return value then will be set as the outputs body of the script action that any subsequent actions can reference.
Limits
Duration
Your script can run for up to 10mins. Let us know if you have scenarios that require longer durations
Outputs
Output size is subjected to the outputs size limit of the actions (100MB).
Logging
To log output to your streaming logs in C#, include an argument of type ILogger. We recommend that you name it log. Avoid using Console. Write in in your script.
public static void Run(WorkflowContext context, ILogger log)
{
log.LogInformation($”C# script has executed successfully”);
}
Custom metrics logging
You can use the LogMetric extension method on ILogger to create custom metrics in Application Insights. Here’s a sample method call:
logger.LogMetric(“TestMetric”, 1234);
Importing namespaces
If you need to import namespaces, you can do so as usual, with the using clause.
The following namespaces are automatically imported and are therefore optional:
System
System.Collections.Generic
System.IO
System.Linq
System.Net.Http
System.Threading.Tasks
Microsoft.Azure.WebJobs
Microsoft.Azure.WebJobs.Host
Referencing external assemblies
For framework assemblies, add references by using the #r “AssemblyName” directive.
// Add the required libraries
#r “Newtonsoft.Json”
#r “Microsoft.Azure.Workflows.Scripting”
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
using Microsoft.Extensions.Logging;
using Microsoft.Azure.Workflows.Scripting;
using Newtonsoft.Json.Linq;
public static async Task<Results> Run(WorkflowContext context, ILogger log)
The following assemblies are automatically added by the Azure Functions hosting environment:
mscorlib
System
System.Core
System.Xml
System.Net.Http
Microsoft.Azure.WebJobs
Microsoft.Azure.WebJobs.Host
Microsoft.Azure.WebJobs.Extensions
System.Web.Http
System.Net.Http.Formatting
Newtonsoft.Json
Environment variables
To get an environment variable or an app setting value, use System.Environment.GetEnvironmentVariable, as shown in the following code example:
public static void Run(WorkflowContext context, ILogger log)
{
log.LogInformation($”C# Timer trigger function executed at: {DateTime.Now}”);
log.LogInformation(GetEnvironmentVariable(“AzureWebJobsStorage”));
log.LogInformation(GetEnvironmentVariable(“WEBSITE_SITE_NAME”));
}
public static string GetEnvironmentVariable(string name)
{
return name + “: ” + System.Environment.GetEnvironmentVariable(name, EnvironmentVariableTarget.Process);
}
Compilation Errors
The web-based editor has limited IntelliSense support at this time, and we are working on improving as we make this capability generally available. Any compilation error will hence be detected at save time when the logic app runtime compiles the script. These errors will appear in the error-logs of your logic app.
Runtime Errors
Any error that happens at execution time in the script will propagate back to the workflow and the script action will be marked as failed with the error object representing the exception that was thrown from your script.
Example Scripts
Uncompressing a ZIP file containing multiple text files retrieved from an HTTP action into an array of strings
// Add the required libraries
#r “Newtonsoft.Json”
#r “Microsoft.Azure.Workflows.Scripting”
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
using Microsoft.Azure.Workflows.Scripting;
using System;
using System.IO;
using System.IO.Compression;
using System.Text;
using System.Collections.Generic;
/// <summary>
/// Executes the inline csharp code.
/// </summary>
/// <param name=”context”>The workflow context.</param>
public static async Task<List<string>> Run(WorkflowContext context)
{
var outputs = (await context.GetActionResults(“HTTP_1”).ConfigureAwait(false)).Outputs;
var base64zipFileContent = outputs[“body”][“$content”].ToString();
// Decode base64 to bytes
byte[] zipBytes = Convert.FromBase64String(base64zipFileContent);
List<string> fileContents = new List<string>();
// Create an in-memory stream from the zip bytes
using (MemoryStream zipStream = new MemoryStream(zipBytes))
{
// Extract files from the zip archive
using (ZipArchive zipArchive = new ZipArchive(zipStream))
{
foreach (ZipArchiveEntry entry in zipArchive.Entries)
{
// Read each file’s content
using (StreamReader reader = new StreamReader(entry.Open()))
{
string fileContent = reader.ReadToEnd();
fileContents.Add(fileContent);
}
}
}
}
return fileContents;
}
Encrypt Data using a key from App-Settings
// Add the required libraries
#r “Newtonsoft.Json”
#r “Microsoft.Azure.Workflows.Scripting”
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
using Microsoft.Azure.Workflows.Scripting;
using Newtonsoft.Json.Linq;
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
/// <summary>
/// Executes the inline csharp code.
/// </summary>
/// <param name=”context”>The workflow context.</param>
public static async Task<string> Run(WorkflowContext context)
{
var compose = (await context.GetActionResults(“compose”).ConfigureAwait(false)).Outputs;
var text = compose[“sampleData”].ToString();
return EncryptString(text);
}
public static string EncryptString(string plainText)
{
var key = Environment.GetEnvironmentVariable(“app-setting-key”);
var iv = Environment.GetEnvironmentVariable(“app-setting-iv”);
using (Aes aesAlg = Aes.Create())
{
aesAlg.Key = Encoding.UTF8.GetBytes(key);
aesAlg.IV = Encoding.UTF8.GetBytes(iv);
ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);
using (MemoryStream msEncrypt = new MemoryStream())
{
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
{
swEncrypt.Write(plainText);
}
}
return Convert.ToBase64String(msEncrypt.ToArray());
}
}
}
Appendix
WorkflowContext Class
Represents the context of a workflow.
Methods
Task<WorkflowOperationResult> GetActionResult(string actionName)
Gets the result of a specific action within the workflow.
Parameters
actionName
The name of the action.
Returns
A Task representing the asynchronous operation. The task result contains a WorkflowOperationResult object.
Task<WorkflowOperationResult> RunTriggerResult()
Gets the result of the workflow trigger.
Returns
A Task representing the asynchronous operation. The task result contains a WorkflowOperationResult object with the following properties:
WorkflowOperationResult Class
Represents the result of a workflow operation.
Properties
string Name
Gets or sets the operation name.
JToken Inputs
Gets or sets the operation execution inputs.
JToken Outputs
Gets or sets the operation execution outputs.
DateTime? StartTime
Gets or sets the operation start time.
DateTime? EndTime
Gets or sets the operation end time.
string OperationTrackingId
Gets or sets the operation tracking id.
string Code
Gets or sets the status code of the action.
string Status
Gets or sets the status of the action.
JToken Error
Gets or sets the error of the action
JToken TrackedProperties
Gets or sets the tracked properties of the action
Microsoft Tech Community – Latest Blogs –Read More