Announcement: Introducing .NET C# Inline Action for Azure Logic Apps (Standard) - Preview

  • Thread starter Thread starter harimehta
  • Start date Start date
H

harimehta

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.

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.





large?v=v2&px=999.png



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.





large?v=v2&px=999.png



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.



large?v=v2&px=999.png

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).













Code:
// 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.



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













Code:
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.













Code:
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.



large?v=v2&px=999.png



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.











Code:
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.











Code:
// 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:













Code:
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.



large?v=v2&px=999.png



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.



large?v=v2&px=999.png



Example Scripts​

Uncompressing a ZIP file containing multiple text files retrieved from an HTTP action into an array of strings​












Code:
// 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​












Code:
// 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.



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

Continue reading...
 
Back
Top