Azure / Azure/azure-functions-dotnet-worker

ExtensionInformationAttribute should support multiple extensions per assembly

Open
#3,423 0 comments 1 reaction 2 assignees Claimed by @kshyju View on GitHub
enhancement
Dominant language
C#
Stars
466
Forks
215
Avg merge
3d 10h
Merged PRs (30d)
7

Description

### Description

## Problem

`ExtensionInformationAttribute` has [`AllowMultiple` defaulting to `false`](https://github.com/Azure/azure-functions-dotnet-worker/blob/cec19d28cf81f943bce994deb276a2885c3a7804/extensions/Worker.Extensions.Abstractions/src/ExtensionInformationAttribute.cs#L9), so a library assembly can only declare **one** host extension dependency. Libraries that need to auto-load multiple host extensions cannot do so today.

This is a problem when a library dynamically registers functions at runtime (via `IFunctionMetadataProvider`) for multiple extension types — the build-time SDK scan won't detect those bindings, so `ExtensionInformationAttribute` with `enableImplicitRegistration: true` is the only way to force-load the host extensions. Being limited to one attribute forces library authors to choose which single extension to auto-load.

### Example

[`Microsoft.Agents.AI.Hosting.AzureFunctions`](https://github.com/microsoft/agent-framework/blob/b298113d15cf5c7e8a7299991cea98ef0400dbbf/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/Microsoft.Agents.AI.Hosting.AzureFunctions.csproj#L43-L56) dynamically registers functions for both DurableTask and MCP extensions, but can only declare one `ExtensionInformationAttribute`.

## Background

During `dotnet build`, the Worker SDK scans all NuGet package assemblies for `ExtensionInformationAttribute` to discover host extensions. The collected references are used to generate `WorkerExtensions.csproj`, which is built to produce `extensions.json`. At runtime, the Functions host reads `extensions.json` to load host extensions — if an extension is missing, it won't load.

Relevant code path:
- [`CollectExtensionPackages`](https://github.com/Azure/azure-functions-dotnet-worker/blob/cec19d28cf81f943bce994deb276a2885c3a7804/src/Azure.Functions.Sdk/Targets/Extensions/Azure.Functions.Sdk.Extensions.targets#L66-L73) — MSBuild target that triggers extension discovery after restore
- [`ResolveExtensionPackages`](https://github.com/Azure/azure-functions-dotnet-worker/blob/cec19d28cf81f943bce994deb276a2885c3a7804/src/Azure.Functions.Sdk/Tasks/Extensions/ResolveExtensionPackages.cs) — MSBuild task that iterates package assemblies
- [`ExtensionReference.TryGetFromModule`](https://github.com/Azure/azure-functions-dotnet-worker/blob/cec19d28cf81f943bce994deb276a2885c3a7804/src/Azure.Functions.Sdk/ExtensionReference.cs#L28-L55) — reads the attribute from an assembly via Mono.Cecil
- [`ExtensionInformationAttribute`](https://github.com/Azure/azure-functions-dotnet-worker/blob/cec19d28cf81f943bce994deb276a2885c3a7804/extensions/Worker.Extensions.Abstractions/src/ExtensionInformationAttribute.cs#L9) — the attribute definition

There are **two** constraints preventing multiple extensions per assembly:

1. The attribute has [`AllowMultiple = false`](https://github.com/Azure/azure-functions-dotnet-worker/blob/cec19d28cf81f943bce994deb276a2885c3a7804/extensions/Worker.Extensions.Abstractions/src/ExtensionInformationAttribute.cs#L9) (compile-time block)
2. `TryGetFromModule` [`return true` after first match](https://github.com/Azure/azure-functions-dotnet-worker/blob/cec19d28cf81f943bce994deb276a2885c3a7804/src/Azure.Functions.Sdk/ExtensionReference.cs#L52) (scanner-level block)

## Proposed Changes

### 1. [`ExtensionInformationAttribute.cs`](https://github.com/Azure/azure-functions-dotnet-worker/blob/cec19d28cf81f943bce994deb276a2885c3a7804/extensions/Worker.Extensions.Abstractions/src/ExtensionInformationAttribute.cs#L9)

```diff
- [AttributeUsage(AttributeTargets.Assembly)]
+ [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
public class ExtensionInformationAttribute : Attribute
```

### 2. [`ExtensionReference.TryGetFromModule`](https://github.com/Azure/azure-functions-dotnet-worker/blob/cec19d28cf81f943bce994deb276a2885c3a7804/src/Azure.Functions.Sdk/ExtensionReference.cs#L28-L55)

Change to return **all** matching attributes instead of stopping at first match. The [caller](https://github.com/Azure/azure-functions-dotnet-worker/blob/cec19d28cf81f943bce994deb276a2885c3a7804/src/Azure.Functions.Sdk/Tasks/Extensions/ResolveExtensionPackages.cs#L134-L160) already uses `yield return`, so handling multiple results is straightforward.

## Result

This enables a **single assembly** to declare multiple host extension dependencies via `ExtensionInformationAttribute`. Today, `extensions.json` already supports multiple extensions (each NuGet package assembly contributes its own), but a single assembly is limited to one. With this change, a library assembly can contribute multiple extensions to `extensions.json`.

This is **backwards-compatible**: assemblies with a single attribute continue to work as before. No changes needed for existing extension packages.

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.