Guest harimehta Posted June 8 Posted June 8 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. [HEADING=2]Adding C# script in your workflow [/HEADING] 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. [HEADING=2]How does the scripting work? [/HEADING] 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. [HEADING=2]Flowing data into the script from the workflow[/HEADING] 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"]; } [HEADING=2]Returning data back to your workflow[/HEADING] 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. [HEADING=2]Limits [/HEADING] 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). [HEADING=2]Logging [/HEADING] 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"); } [HEADING=2]Custom metrics logging [/HEADING] You can use the LogMetric extension method on ILogger to create custom metrics in Application Insights. Here's a sample method call: [iCODE]logger.LogMetric("TestMetric", 1234); [/iCODE] [HEADING=2] [/HEADING] [HEADING=2]Importing namespaces [/HEADING] 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 [HEADING=2] [/HEADING] [HEADING=2]Referencing external assemblies [/HEADING] 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 [HEADING=2] [/HEADING] [HEADING=2]Environment variables [/HEADING] 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); } [HEADING=2]Compilation Errors [/HEADING] 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. [HEADING=2]Runtime Errors [/HEADING] 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. [HEADING=2]Example Scripts [/HEADING] [HEADING=3] [/HEADING] [HEADING=3]Uncompressing a ZIP file containing multiple text files retrieved from an HTTP action into an array of strings [/HEADING] // 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; } [HEADING=3]Encrypt Data using a key from App-Settings [/HEADING] // 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()); } } } We value your feedback, please provide any comments and suggestions about this action using this form: Microsoft Forms. [HEADING=2]Appendix[/HEADING] 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 Continue reading... Quote
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.