- name
- dotnet-local-functions-logic-apps
- description
- 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.
---
## Table of Contents
<!-- TOC - using HTML anchors to avoid markdown link validation issues -->
1. <a href="#1-overview">Overview</a>
2. <a href="#2-architecture--workspace-layout-both-approaches">Architecture & Workspace Layout (Both Approaches)</a>
3. <a href="#3-critical-nuget-package-information">Critical NuGet Package Information</a>
4. <a href="#4-approach-a--net-8-recommended">Approach A — .NET 8 (RECOMMENDED)</a>
5. <a href="#5-approach-b--net-framework-472-legacy">Approach B — .NET Framework 4.7.2 (Legacy)</a>
6. <a href="#6-shared-logic-app-project-files-both-approaches">Shared Logic App Project Files (Both Approaches)</a>
7. <a href="#7-calling-custom-code-from-workflowjson">Calling Custom Code from workflow.json</a>
8. <a href="#8-functionjson-reference">function.json Reference</a>
9. <a href="#9-building-and-running-locally">Building and Running Locally</a>
10. <a href="#10-supported-parameter--return-types">Supported Parameter & Return Types</a>
11. <a href="#11-verification-checklists">Verification Checklists</a>
12. <a href="#12-troubleshooting-guide">Troubleshooting Guide</a>
13. <a href="#13-complete-working-examples">Complete Working Examples</a>
14. <a href="#14-quick-reference-card">Quick Reference Card</a>
15. <a href="#15-step-by-step-creation-guide-for-agents">Step-by-Step Creation Guide for Agents</a>
---
## 1. Overview
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
### Which Approach to Choose
| Factor | .NET Framework 4.7.2 (`net472`) | .NET 8 (`net8`) |
| ----------------------------- | --------------------------------------------- | --------------------------------------------- |
| **Status** | Legacy — still works | **Recommended** — modern path |
| **Worker model** | In-process (classic) | Isolated worker |
| **Function attribute** | `[FunctionName("...")]` | `[Function("...")]` |
| **Using for function attr** | `Microsoft.Azure.WebJobs` | `Microsoft.Azure.Functions.Worker` |
| **Build output folder** | `lib/custom/net472/` | `lib/custom/net8/` |
| **function.json Language** | `"net472"` | `"net8"` |
| **VS Code customCodeRuntime** | `"clr"` | `"coreclr"` |
| **Project layout** | **Sibling** `Functions/` and LogicApp folders | **Sibling** `Functions/` and LogicApp folders |
| **Best for** | Quick ports of existing .NET Framework code | Production projects, CI/CD, new development |
---
## 2. Architecture & Workspace Layout (Both Approaches)
> **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.
### 2.1 Complete Directory Structure
```
<workspace-root>/
├── <workspace-name>.code-workspace ← VS Code workspace file
│
├── Functions/ ← .NET function project (sibling)
│ ├── Functions.csproj ← Project file with MSBuild auto-deploy targets
│ ├── <FunctionName>.cs ← One or more C# function files
│ └── .vscode/
│ ├── extensions.json
│ ├── settings.json
│ └── tasks.json
│
└── <logicapp-name>/ ← Logic Apps Standard project (sibling)
├── host.json ← Functions host configuration
├── local.settings.json ← Runtime settings (CRITICAL)
├── .funcignore ← Files to exclude from deployment
├── .gitignore ← Git ignore rules
│
├── .vscode/
│ ├── extensions.json
│ ├── launch.json ← Debug configuration (CRITICAL: customCodeRuntime differs!)
│ ├── settings.json
│ └── tasks.json
│
├── lib/
│ └── custom/ ← Build outputs auto-copied here
│ ├── <FunctionName>/ ← Contains function.json (auto-generated)
│ │ └── function.json
│ └── net8/ OR net472/ ← Contains DLLs (auto-copied by build)
│ ├── Functions.dll
│ ├── Functions.pdb
│ └── (dependency DLLs)
│
├── <workflow-name>/ ← One or more workflow folders
│ └── workflow.json
│
└── workflow-designtime/ ← Design-time configuration
├── host.json
└── local.settings.json
```
### 2.2 Key Structural Rules
1. **Functions/ and LogicApp are siblings** — they sit next to each other under the workspace root
2. **The workspace file references both folders** — so VS Code opens them as a multi-root workspace
3. **Build auto-deploys** — MSBuild targets in the `.csproj` copy DLLs and `function.json` to `lib/custom/`
4. **`lib/custom/` is the bridge** — DLLs go in `lib/custom/net8/` or `lib/custom/net472/`, function metadata goes in `lib/custom/<FunctionName>/function.json`
5. **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.
### Package Verification Command
```bash
dotnet package search "Microsoft.Azure.Workflows" --take 5
# Expected result: Microsoft.Azure.Workflows.WebJobs.Sdk
```
---
## 4. Approach A — .NET 8 (RECOMMENDED)
### 4.1 Project File (`Functions/Functions.csproj`)
```xml
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<IsPackable>false</IsPackable>
<TargetFramework>net8</TargetFramework>
<AzureFunctionsVersion>v4</AzureFunctionsVersion>
<OutputType>Library</OutputType>
<PlatformTarget>AnyCPU</PlatformTarget>
<LogicAppFolderToPublish>$(MSBuildProjectDirectory)\..\my-logicapp</LogicAppFolderToPublish>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<SelfContained>false</SelfContained>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Abstractions" Version="1.3.0" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="1.15.1" />
<PackageReference Include="Microsoft.Azure.Workflows.Webjobs.Sdk" Version="1.2.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
</ItemGroup>
<Target Name="TriggerPublishOnBuild" AfterTargets="Build">
<CallTarget Targets="Publish" />
</Target>
</Project>
```
### 4.2 Critical csproj Details — .NET 8
| Property/Element | Value | Why It Matters |
| ------------------------------ | ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `<TargetFramework>` | `net8` | Must be `net8` — NOT `net8.0` (Logic Apps convention) |
| `<AzureFunctionsVersion>` | `v4` | Required for Azure Functions v4 runtime |
| `<OutputType>` | `Library` | Functions are libraries, not executables |
| `<PlatformTarget>` | `AnyCPU` | Standard target for .NET 8 |
| `<LogicAppFolderToPublish>` | `$(MSBuildProjectDirectory)\..\my-logicapp` | **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.
### 4.3 Required Packages — .NET 8
| Package | Version | Why It's Needed |
View on GitHub