We are excited to introduce a new capability that allows developers to write .NET C# script right within the Logic Apps designer in Azure Portal. This complements the custom code feature in VS Code that we introduced previously for invoking .NET FX and NET8 (planned) functions written and deployed to a Logic App.
As you try this feature please keep the following in mind:
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.
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. Learn more on C# scripting for portal at aka.ms/csharp-scripting.
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"];
}
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.
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). |
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");
}
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);
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:
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:
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);
}
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.
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.
// 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;
}
// 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());
}
}
}
This feature is only in preview for Logic App (Standard) SKU and not Logic App (Consumption). We appreciate your feedback, please provide any comments and suggestions about this action using this form: https://aka.ms/csharplogicapps.
WorkflowContext Class
Represents the context of a workflow.
Methods
Parameters
Returns
A Task representing the asynchronous operation. The task result contains a WorkflowOperationResult object.
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
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.