Creates .NET local functions (custom code) for Azure Logic Apps Standard. Covers exact NuGet packages, namespaces, csproj, MSBuild targets, VS Code config, function.json, and workflow InvokeFunction patterns for both .NET 8 and .NET Framework 4.7.2.
Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.
Quelldateien prüfen
Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.
Mit Codex oder Claude installieren Kopieren Sie diesen Prompt, fügen Sie ihn in Codex, Claude oder einen anderen Assistant ein und lassen Sie die Skill-Seite prüfen und installieren.
Ein direkter Befehl überspringt den Prüf-Prompt. Prüfen Sie die Quelle, bevor Sie ihn ausführen.
Creates .NET local functions (custom code) for Azure Logic Apps Standard. Covers exact NuGet packages, namespaces, csproj, MSBuild targets, VS Code config, function.json, and workflow InvokeFunction patterns for both .NET 8 and .NET Framework 4.7.2.
Skill: Creating .NET Local Functions for Azure Logic Apps Standard
Purpose: This document is a definitive reference for AI agents generating .NET local functions (custom code) that run inside Azure Logic Apps Standard workflows. It contains the exact files, folder structure, NuGet packages, MSBuild targets, VS Code configuration, and workflow invocation patterns taken from verified working projects. Following this document precisely will produce a working project on the first attempt.
Two approaches are documented: .NET Framework 4.7.2 (legacy, in-process) and .NET 8 (modern, recommended). Both use the same workspace layout pattern. Pick one based on your needs.
Logic Apps Standard supports calling custom .NET code directly from workflows using the "Call a local function in this logic app" built-in action (InvokeFunction type). The .NET code runs alongside the Logic Apps runtime — no separate Azure Function hosting is needed.
When to Use
Porting MuleSoft custom Java components, complex DataWeave logic, or MEL expressions to .NET
Custom business logic (calculations, transformations, validations)
XML/JSON document construction using System.Xml or System.Text.Json
Any scenario where built-in Logic Apps actions/expressions are insufficient
When NOT to Use
Processes that take more than 10 minutes
Large message/data transformations (use Data Mapper instead)
Complex batching/debatching
MuleSoft streaming transformations or large payload processing
CRITICAL: Both .NET 8 and .NET 4.7.2 use the same workspace layout pattern — the Functions project and Logic App project are sibling folders with a .code-workspace file at the root. The Functions project is NOT nested inside the Logic App.
Functions/ and LogicApp are siblings — they sit next to each other under the workspace root
The workspace file references both folders — so VS Code opens them as a multi-root workspace
Build auto-deploys — MSBuild targets in the .csproj copy DLLs and function.json to lib/custom/
lib/custom/ is the bridge — DLLs go in lib/custom/net8/ or lib/custom/net472/, function metadata goes in lib/custom/<FunctionName>/function.json
workflow-designtime/ is required — it enables the Logic Apps designer to discover custom functions
3. Critical NuGet Package Information
⚠️ THE #1 MISTAKE: Wrong Package Name
What you might guess
Exists on NuGet?
Correct?
Microsoft.Azure.Functions.Extensions.Workflows
NO ❌
❌
Microsoft.Azure.Workflows.WebJobs.Sdk
YES ✅
✅
Why this is confusing: The NuGet package ID is Microsoft.Azure.Workflows.WebJobs.Sdk, but the DLL inside the package is named Microsoft.Azure.Functions.Extensions.Workflows.Sdk.dll, and the namespace for the WorkflowActionTrigger attribute is Microsoft.Azure.Functions.Extensions.Workflows. Three different naming conventions for the same thing.
⚠️ THE #2 MISTAKE: Wrong Namespace in Using Statements
Using statement
Compiles?
Correct?
using Microsoft.Azure.Workflows.WebJobs.Sdk;
NO ❌
❌
using Microsoft.Azure.Functions.Extensions.Workflows;
YES ✅
✅
Both approaches use the same [WorkflowActionTrigger] attribute from the same namespace. The difference is the function declaration attribute and its associated packages.
Absolute path to the sibling Logic App folder — the Publish target uses this to know where to deploy outputs. Replace my-logicapp with your Logic App folder name.
<SelfContained>
false
Must NOT be self-contained — Logic Apps runtime provides the host
TriggerPublishOnBuild target
Calls Publish AfterTargets Build
This is the auto-deploy mechanism — on every build, the Publish target automatically copies DLLs to lib/custom/net8/ and function.json to lib/custom/<FunctionName>/ inside the Logic App folder
IMPORTANT: The .NET 8 approach uses a simple TriggerPublishOnBuild target that calls Publish. The Publish target (from the Worker SDK) handles all file copying automatically. You do NOT need manual copy targets.
Worker SDK — generates function.json and handles Publish
Microsoft.Azure.Workflows.Webjobs.Sdk
1.2.0
Provides [WorkflowActionTrigger] attribute
Microsoft.Extensions.Logging.Abstractions
6.0.0
Provides ILogger<T>, ILoggerFactory
Microsoft.Extensions.Logging
6.0.0
Logging infrastructure
4.4 Function Code — .NET 8
//------------------------------------------------------------// Copyright (c) Microsoft Corporation. All rights reserved.//------------------------------------------------------------namespace <YourNamespace>
{
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Azure.Functions.Extensions.Workflows; // [WorkflowActionTrigger]using Microsoft.Azure.Functions.Worker; // [Function] — NOT [FunctionName]!using Microsoft.Extensions.Logging;
///<summary>/// Represents the <FunctionName> flow invoked function.///</summary>publicclass <ClassName>
{
privatereadonlyILogger<<ClassName>> logger;
public <ClassName>(ILoggerFactory loggerFactory)
{
logger = loggerFactory.CreateLogger<<ClassName>>();
}
///<summary>/// Executes the logic app workflow action.///</summary>
[Function("<FunctionName>")] // ← [Function], NOT [FunctionName]public Task<<ResultType>> Run(
[WorkflowActionTrigger] <type> param1, <type> param2)
{
this.logger.LogInformation("Starting <FunctionName>: " + param1);
var result = new <ResultType>()
{
// Set properties...
};
return Task.FromResult(result);
}
///<summary>/// Result model — properties become accessible in workflow via @body('ActionName')?['PropertyName']///</summary>publicclass <ResultType>
{
public <type> Property1 { get; set; }
public <type> Property2 { get; set; }
}
}
}
4.5 Key Using Statements — .NET 8
using Microsoft.Azure.Functions.Extensions.Workflows; // [WorkflowActionTrigger] — same for both approachesusing Microsoft.Azure.Functions.Worker; // [Function("...")] — .NET 8 ONLYusing Microsoft.Extensions.Logging; // ILogger<T>, ILoggerFactory
Note: ParameterizedFunctionJsonGenerator (NOT ParameterizedFunctionJsonGeneratorNetCore — that's for .NET 8)
<Reference Include="Microsoft.CSharp" />
Assembly reference
Required for dynamic features
IMPORTANT: The .NET 4.7.2 approach uses explicit MSBuild targets to clean, copy, and reorganize build outputs. The CopyExtensionFiles target runs after ParameterizedFunctionJsonGenerator which is provided by the Microsoft.NET.Sdk.Functions package to auto-generate function.json files.
5.3 How the MSBuild Targets Work (net472)
The targets perform these steps in order:
Task target (after Compile): Deletes ../my-logicapp/lib/custom/ to start fresh
⚠️ IMPORTANT: Use Microsoft.Azure.WebJobs.Core (not Microsoft.Azure.WebJobs). The .Core package provides just the attributes without pulling in the full WebJobs host.
5.5 Function Code — .NET 4.7.2
//------------------------------------------------------------// Copyright (c) Microsoft Corporation. All rights reserved.//------------------------------------------------------------namespace <YourNamespace>
{
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Azure.Functions.Extensions.Workflows; // [WorkflowActionTrigger]using Microsoft.Azure.WebJobs; // [FunctionName] — NOT [Function]!using Microsoft.Extensions.Logging;
///<summary>/// Represents the <FunctionName> flow invoked function.///</summary>publicclass <ClassName>
{
privatereadonlyILogger<<ClassName>> logger;
public <ClassName>(ILoggerFactory loggerFactory)
{
logger = loggerFactory.CreateLogger<<ClassName>>();
}
///<summary>/// Executes the logic app workflow action.///</summary>
[FunctionName("<FunctionName>")] // ← [FunctionName], NOT [Function]public Task<<ResultType>> Run(
[WorkflowActionTrigger] <type> param1, <type> param2)
{
this.logger.LogInformation("Starting <FunctionName>: " + param1);
var result = new <ResultType>()
{
// Set properties...
};
return Task.FromResult(result);
}
///<summary>/// Result model — properties become accessible in workflow via @body('ActionName')?['PropertyName']///</summary>publicclass <ResultType>
{
public <type> Property1 { get; set; }
public <type> Property2 { get; set; }
}
}
}
5.6 Key Using Statements — .NET 4.7.2
using Microsoft.Azure.Functions.Extensions.Workflows; // [WorkflowActionTrigger] — same for both approachesusing Microsoft.Azure.WebJobs; // [FunctionName("...")] — net472 ONLYusing Microsoft.Extensions.Logging; // ILogger<T>, ILoggerFactory
Note: The Logic App folder MUST be listed first in the folders array. The settings block can optionally include terminal.integrated.env.windows with a PATH to the Logic Apps DotNetSDK if needed for local development.
NEVER use "dotnet-isolated" even for .NET 8 local functions
FUNCTIONS_INPROC_NET8_ENABLED
"1"
Enables the .NET 8 host that loads both net472 and net8 assemblies. Required for BOTH approaches.
APP_KIND
"workflowapp"
Identifies this as a Logic App Standard project (note: lowercase 'a' in 'app')
AzureWebJobsFeatureFlags
"EnableMultiLanguageWorker"
Required to enable custom code execution alongside the workflow engine
AzureWebJobsStorage
"UseDevelopmentStorage=true"
Uses Azurite for local development
ProjectDirectoryPath
Absolute path to logicapp folder
Tells the runtime where to find workflow definitions
WORKFLOWS_SUBSCRIPTION_ID
""
Azure subscription ID (empty for local development)
6.4 launch.json (⚠️ DIFFERS BETWEEN APPROACHES)
For .NET 8:
{"version":"0.2.0","configurations":[{"name":"Run/Debug logic app with local function my-logicapp","type":"logicapp","request":"launch","funcRuntime":"coreclr","customCodeRuntime":"coreclr","isCodeless":true}]}
For .NET 4.7.2:
{"version":"0.2.0","configurations":[{"name":"Run/Debug logic app with local function my-logicapp","type":"logicapp","request":"launch","funcRuntime":"coreclr","customCodeRuntime":"clr","isCodeless":true}]}
⚠️ CRITICAL DIFFERENCE: customCodeRuntime must be "coreclr" for .NET 8 and "clr" for .NET 4.7.2. Getting this wrong will cause the custom code to fail at runtime. Both use "funcRuntime": "coreclr".
Note: azureLogicAppsStandard.projectLanguage is "JavaScript" even though the custom code is C#. This refers to the Logic Apps workflow engine language, not the custom code language. azureFunctions.suppressProject must be true to prevent the Azure Functions extension from interfering.
Purpose: This enables the Logic Apps designer to discover available custom functions and connectors at design time. Without this, the designer won't show the "Call a local function" action.
Do NOT create Artifacts/ by default as part of the local-functions setup.
Only create Artifacts/Maps/, Artifacts/Rules/, or Artifacts/Schemas/ later if the migrated solution actually needs maps, schemas, HIDX files, or rules.
6.13 lib/builtinOperationSdks/ Folder
Do NOT create lib/builtinOperationSdks/ by default as part of the local-functions setup.
Only create that folder later if runtime/tooling explicitly requires it. If it ever exists, keep it completely empty.
⚠️ CRITICAL — Never put .gitkeep or any file inside lib/builtinOperationSdks/JAR/ or lib/builtinOperationSdks/net472/ if those folders are ever created. The Azure Functions runtime detects non-empty folders there and attempts to load Java and .NET Framework workers. If those workers fail (for example, because JAVA_HOME is not set), the .NET 8 worker can also fail to initialize — causing InvokeFunction calls to error with "function does not exist".
7. Calling Custom Code from workflow.json
The workflow.json is identical regardless of which .NET approach you use.
The function.json file is auto-generated by the build process and placed in lib/custom/<FunctionName>/function.json. You should NOT create this file manually — the MSBuild targets handle it.
"ScriptFile": "../bin/<ProjectName>.dll" — this is the auto-generated path from the build. The MSBuild targets move the DLLs to net472/ but do NOT update this path. The Logic Apps runtime resolves the DLL location using the "Language": "net472" field to look in the net472/ subfolder.
"Language": "net472"
8.3 net472 extensions.json
For .NET 4.7.2, an extensions.json file is also generated in the lib/custom/net472/ folder:
{"extensions":[]}
9. Building and Running Locally
9.1 Build — .NET 8
cd Functions
dotnet build
# The TriggerPublishOnBuild target automatically:
# 1. Builds the project
# 2. Calls Publish
# 3. Copies DLLs to ../my-logicapp/lib/custom/net8/
# 4. Copies function.json to ../my-logicapp/lib/custom/<FunctionName>/
9.2 Build — .NET 4.7.2
cd Functions
dotnet build
# The MSBuild targets automatically:
# 1. Compile → Cleans ../my-logicapp/lib/custom/
# 2. ParameterizedFunctionJsonGenerator → Generates function.json
# 3. CopyExtensionFiles → Copies DLLs to ../my-logicapp/lib/custom/net472/
# → Copies function.json to ../my-logicapp/lib/custom/<FunctionName>/
//------------------------------------------------------------// Copyright (c) Microsoft Corporation. All rights reserved.//------------------------------------------------------------namespacetest
{
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Azure.Functions.Extensions.Workflows;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Logging;
///<summary>/// Represents the Functions flow invoked function.///</summary>publicclassFunctions
{
privatereadonly ILogger<Functions> logger;
publicFunctions(ILoggerFactory loggerFactory)
{
logger = loggerFactory.CreateLogger<Functions>();
}
///<summary>/// Executes the logic app workflow.///</summary>///<param name="zipCode">The zip code.</param>///<param name="temperatureScale">The temperature scale (e.g., Celsius or Fahrenheit).</param>
[Function("Functions")]
public Task<Weather> Run([WorkflowActionTrigger] int zipCode, string temperatureScale)
{
this.logger.LogInformation("Starting Functions with Zip Code: " + zipCode + " and Scale: " + temperatureScale);
// Generate random temperature within a range based on the temperature scale
Random rnd = new Random();
var currentTemp = temperatureScale == "Celsius" ? rnd.Next(1, 30) : rnd.Next(40, 90);
var lowTemp = currentTemp - 10;
var highTemp = currentTemp + 10;
// Create a Weather object with the temperature informationvar weather = new Weather()
{
ZipCode = zipCode,
CurrentWeather = $"The current weather is {currentTemp}{temperatureScale}",
DayLow = $"The low for the day is {lowTemp}{temperatureScale}",
DayHigh = $"The high for the day is {highTemp}{temperatureScale}"
};
return Task.FromResult(weather);
}
///<summary>/// Represents the weather information for Functions.///</summary>publicclassWeather
{
publicint ZipCode { get; set; }
publicstring CurrentWeather { get; set; }
publicstring DayLow { get; set; }
publicstring DayHigh { get; set; }
}
}
}
//------------------------------------------------------------// Copyright (c) Microsoft Corporation. All rights reserved.//------------------------------------------------------------namespacetest
{
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Azure.Functions.Extensions.Workflows;
using Microsoft.Azure.WebJobs;
using Microsoft.Extensions.Logging;
///<summary>/// Represents the Functions flow invoked function.///</summary>publicclassFunctions
{
privatereadonly ILogger<Functions> logger;
publicFunctions(ILoggerFactory loggerFactory)
{
logger = loggerFactory.CreateLogger<Functions>();
}
///<summary>/// Executes the logic app workflow.///</summary>///<param name="zipCode">The zip code.</param>///<param name="temperatureScale">The temperature scale (e.g., Celsius or Fahrenheit).</param>
[FunctionName("Functions")]
public Task<Weather> Run([WorkflowActionTrigger] int zipCode, string temperatureScale)
{
this.logger.LogInformation("Starting Functions with Zip Code: " + zipCode + " and Scale: " + temperatureScale);
// Generate random temperature within a range based on the temperature scale
Random rnd = new Random();
var currentTemp = temperatureScale == "Celsius" ? rnd.Next(1, 30) : rnd.Next(40, 90);
var lowTemp = currentTemp - 10;
var highTemp = currentTemp + 10;
// Create a Weather object with the temperature informationvar weather = new Weather()
{
ZipCode = zipCode,
CurrentWeather = $"The current weather is {currentTemp}{temperatureScale}",
DayLow = $"The low for the day is {lowTemp}{temperatureScale}",
DayHigh = $"The high for the day is {highTemp}{temperatureScale}"
};
return Task.FromResult(weather);
}
///<summary>/// Represents the weather information for Functions.///</summary>publicclassWeather
{
publicint ZipCode { get; set; }
publicstring CurrentWeather { get; set; }
publicstring DayLow { get; set; }
publicstring DayHigh { get; set; }
}
}
}
{"version":"0.2.0","configurations":[{"name":"Run/Debug logic app with local function my-logicapp","type":"logicapp","request":"launch","funcRuntime":"coreclr","customCodeRuntime":"clr","isCodeless":true}]}
my-logicapp/my-workflow/workflow.json:
(Identical to the .NET 8 example — same workflow.json works for both approaches)