Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Nov 4, 2025

Summary

This PR successfully implements a new AddAzureFunctionsProject overload that accepts a project path as a string parameter, addressing the issue where users cannot add Azure Functions projects dynamically or reference projects outside their solution.

Problem Statement

Previously, AddAzureFunctionsProject only supported a generic type parameter approach:

builder.AddAzureFunctionsProject<Projects.MyFunctions>("funcapp");

This required:

  1. A project reference in the AppHost project
  2. Static/compile-time knowledge of the project location

Users needed dynamic path-based project addition similar to the existing AddProject(name, path) method.

Solution

Added a new overload that accepts a project path:

builder.AddAzureFunctionsProject("funcapp", "../MyFunctions/MyFunctions.csproj");

Implementation Details

Code Changes

  1. New Public API: AddAzureFunctionsProject(name, projectPath)

    • Accepts string path parameter
    • Normalizes relative paths to absolute paths
    • Supports all project types (.csproj, .fsproj, .vbproj)
  2. Refactored Shared Logic: AddAzureFunctionsProjectCore (private)

    • Extracted common implementation from both overloads
    • Maximum code reuse between generic and path-based approaches
    • Maintains all existing functionality (storage, annotations, environment variables, etc.)
  3. Helper Components:

    • AzureFunctionsProjectMetadata: Implements IProjectMetadata for path-based projects
    • NormalizePathForCurrentPlatform: Handles path normalization
    • ResolveProjectPath: Auto-discovers project files in directories
  4. Resource Fix: Fixed embedded resource naming

    • Added LogicalName attribute to LaunchProfileStrings.resx embedding
    • Ensures resource is embedded with correct namespace expected by Designer.cs
  5. Test Updates:

    • Updated tests to use TempDirectory helper for consistent temp file handling
    • All path-based tests create actual .csproj files in temp directories
    • Automatic cleanup via IDisposable pattern
    • Fixed AzureDeployerTests mock data to include containerapp id output

Files Modified

  • src/Aspire.Hosting.Azure.Functions/AzureFunctionsProjectResourceExtensions.cs - Added new overload and refactored
  • src/Aspire.Hosting.Azure.Functions/Aspire.Hosting.Azure.Functions.csproj - Fixed RESX resource naming
  • tests/Aspire.Hosting.Azure.Tests/AzureFunctionsTests.cs - Added comprehensive tests using TempDirectory helper
  • tests/Aspire.Hosting.Azure.Tests/AzureDeployerTests.cs - Fixed mock data for deployment test

Test Coverage

Added 6 new tests (all using TempDirectory helper):

  • AddAzureFunctionsProject_WithProjectPath_Works - Basic functionality
  • AddAzureFunctionsProject_WithProjectPath_NormalizesPath - Path normalization
  • AddAzureFunctionsProject_WithProjectPath_ConfiguresEnvironmentVariables - Environment setup
  • AddAzureFunctionsProject_WithProjectPath_SharesDefaultStorage - Storage sharing
  • AddAzureFunctionsProject_WithProjectPath_CanUseCustomHostStorage - Custom storage (Docker)
  • AddAzureFunctionsProject_WithProjectPath_AddsAzureFunctionsAnnotation - Annotation verification

Fixed existing test:

  • DeployAsync_WithAzureFunctionsProject_Works - Fixed mock deployment outputs

Usage Examples

Basic Usage

var builder = DistributedApplication.CreateBuilder(args);
builder.AddAzureFunctionsProject("funcapp", "../MyFunctions/MyFunctions.csproj");

With Dynamic Paths

var functionsPath = CalculateDynamicPath(); // Could be based on config, environment, etc.
builder.AddAzureFunctionsProject("funcapp", functionsPath);

Multi-language Support

// C# project
builder.AddAzureFunctionsProject("csfunc", "../CSharpFunctions/Functions.csproj");

// F# project  
builder.AddAzureFunctionsProject("fsfunc", "../FSharpFunctions/Functions.fsproj");

// Directory path (auto-discovers project file)
builder.AddAzureFunctionsProject("func3", "../Functions/");

Backward Compatibility

// Existing generic overload still works
builder.AddAzureFunctionsProject<Projects.MyFunctions>("funcapp");

Key Features

Backward Compatible - Existing code continues to work unchanged
Maximum Code Reuse - Shared implementation via helper method
Consistent API - Follows AddProject pattern
Path Flexibility - Absolute or relative paths
Directory Support - Auto-discovers project files
Multi-language - C#, F#, VB.NET support
Well Documented - XML docs with examples
Thoroughly Tested - Comprehensive test coverage using standard helpers
Code Reviewed - All feedback addressed
Security Checked - CodeQL analysis passed
Resource Fix - Fixed embedded resource naming issue
Test Fixes - Fixed deployment test mock data and refactored to use TempDirectory helper

Validation

  • ✅ Builds successfully without warnings
  • ✅ Code review feedback addressed
  • ✅ CodeQL security analysis passed
  • ✅ Tests use TempDirectory helper for consistent temp file handling
  • ✅ Deployment test mock data fixed
  • ✅ Follows existing patterns and conventions
  • ✅ XML documentation complete
  • ✅ Resource naming issue fixed

Related

Fixes #12641

Original prompt

This section details on the original issue you should resolve

<issue_title>Aspire lacks an AddAzureFunctionsProject override accepting a project path</issue_title>
<issue_description>### Is there an existing issue for this?

  • I have searched the existing issues

Is your feature request related to a problem? Please describe the problem.

I am trying to add Azure Functions to our orchestration setup but can't because the current implementation in Aspire.Hosting.Azure.Functions requires adding project references and using type arguments.

Describe the solution you'd like

Ideally, there would be an override for AddAzureFunctionsProject() that accepts a path to an Azure Function's .csproj file in the same way as the AddProject() function.

Additional context

In my situation, I can't use project references for a few different reasons. The main one being that the file paths aren't straightforward and need to be calculated dynamically. There are other reasons project references can't work, but they are more specific to our implementation.</issue_description>

<agent_instructions>Add an overload of AddAzureFunctionsProject that takes the project path as a parameter. Make sure that there is as much code sharing as possible in the extension methods in AzureFunctionsProjectResourceExtensions. Validate the behavior of the new overload by adding tests to AzureFunctionsTests.cs.</agent_instructions>

Comments on the Issue (you are @copilot in this section)


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

Copilot AI and others added 2 commits November 4, 2025 06:01
- Implemented new AddAzureFunctionsProject(name, projectPath) overload
- Refactored existing implementation to share code via AddAzureFunctionsProjectCore helper method
- Added AzureFunctionsProjectMetadata class to handle path-based project metadata
- Added helper method NormalizePathForCurrentPlatform for path normalization
- Added comprehensive tests for the new overload including path normalization, environment variables, and storage configuration

Co-authored-by: captainsafia <[email protected]>
- Support all project types (*.*proj) not just C# projects (.csproj)
- Clarify comment about project file discovery to cover both zero and multiple file cases

Co-authored-by: captainsafia <[email protected]>
Copilot AI changed the title [WIP] Add override for AddAzureFunctionsProject accepting a project path Add AddAzureFunctionsProject overload accepting project path Nov 4, 2025
Copilot AI requested a review from captainsafia November 4, 2025 06:07
Copilot finished work on behalf of captainsafia November 4, 2025 06:07
@github-actions
Copy link
Contributor

github-actions bot commented Nov 5, 2025

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/dotnet/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 12658

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/dotnet/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 12658"

@captainsafia
Copy link
Member

@copilot We need to fix this error. Update the RootNamespace the RESX is against.

Unhandled exception. System.Resources.MissingManifestResourceException: Could not find the resource
"Aspire.Hosting.Resources.LaunchProfileStrings.resources" among the resources
"Aspire.Hosting.Azure.Functions.Resources.LaunchProfileStrings.resources" embedded in the assembly "Aspire.Hosting.Azure.Functions", nor among the
resources in any satellite assemblies for the specified culture. Perhaps the resources were embedded with an incorrect name.

The LaunchProfileStrings.resx embedded resource was using the wrong namespace. The Designer.cs expects "Aspire.Hosting.Resources.LaunchProfileStrings.resources" but it was getting "Aspire.Hosting.Azure.Functions.Resources.LaunchProfileStrings.resources".

Fixed by adding LogicalName attribute to the EmbeddedResource element to explicitly set the correct resource name.

Co-authored-by: captainsafia <[email protected]>
Copy link
Contributor Author

Copilot AI commented Nov 5, 2025

@copilot We need to fix this error. Update the RootNamespace the RESX is against.

Unhandled exception. System.Resources.MissingManifestResourceException: Could not find the resource
...

Fixed in commit 188890f. Added LogicalName attribute to the EmbeddedResource element to explicitly set the resource name to Aspire.Hosting.Resources.LaunchProfileStrings.resources, matching what the Designer.cs file expects.

Copilot finished work on behalf of captainsafia November 5, 2025 23:15
@captainsafia
Copy link
Member

@copilot Update the tests that validate the new projectPath-based overload to create a project file in a temporary directory to work around the project file not found exceptions.

// F# project
builder.AddAzureFunctionsProject("fsfunc", "../FSharpFunctions/Functions.fsproj");

Copilot, are you an F# fanboy? 😆

Updated all path-based AddAzureFunctionsProject tests to create actual .csproj files in temporary directories instead of using non-existent file paths. This prevents "project file not found" exceptions during test execution.

Each test now:
- Creates a temp directory using Path.GetTempPath()
- Writes a minimal project file
- Uses the real file path for testing
- Cleans up the temp directory in a finally block

Co-authored-by: captainsafia <[email protected]>
Copy link
Contributor Author

Copilot AI commented Nov 6, 2025

@copilot Update the tests that validate the new projectPath-based overload to create a project file in a temporary directory to work around the project file not found exceptions.

// F# project
...

Updated in commit 4f57efc. All path-based tests now create real .csproj files in temporary directories and clean them up properly to prevent "project file not found" exceptions.

P.S. Just keeping the examples multi-language friendly! 😄

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • aka.ms
    • Triggering command: /usr/local/bin/bicep build /tmp/aspirestJlE9/env.module.bicep --stdout (dns block)
    • Triggering command: /usr/local/bin/bicep build /tmp/aspireQAkj6p/teststorage.module.bicep --stdout (dns block)
    • Triggering command: /usr/local/bin/bicep build /tmp/aspireGUFCAq/env.module.bicep --stdout (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Copilot finished work on behalf of captainsafia November 6, 2025 16:42
@captainsafia
Copy link
Member

@copilot Fix the test failure related to incomplete mocked data from the Bicep call.

[+965/x1/?0] Aspire.Hosting.Azure.Tests.dll (net8.0|arm64) - Aspire.Hosting.Azure.Tests.AzureDeployerTests.DeployAsync_WithAzureFunctionsProject_Works (1m 10s)

fail: Aspire.Hosting.Publishing.PipelineExecutor[0]
Step 'provision-funcapp-containerapp' failed.
System.InvalidOperationException: Step 'provision-funcapp-containerapp' failed: No output for id
---> System.InvalidOperationException: No output for id
at Aspire.Hosting.Azure.BicepOutputReference.get_Value() in //src/Aspire.Hosting.Azure/AzureBicepResource.cs:line 598
at Aspire.Hosting.Azure.BicepOutputReference.GetValueAsync(CancellationToken cancellationToken) in /
/src/Aspire.Hosting.Azure/AzureBicepResource.cs:line 586
at Aspire.Hosting.Azure.Provisioning.BicepUtilities.SetParametersAsync(JsonObject parameters, AzureBicepResource resource, CancellationToken cancellationToken) in //src/Aspire.Hosting.Azure/Provisioning/BicepUtilities.cs:line 37
at Aspire.Hosting.Azure.Provisioning.BicepProvisioner.GetOrCreateResourceAsync(AzureBicepResource resource, ProvisioningContext context, CancellationToken cancellationToken) in /
/src/Aspire.Hosting.Azure/Provisioning/Provisioners/BicepProvisioner.cs:line 159
at Aspire.Hosting.Azure.AzureBicepResource.ProvisionAzureBicepResourceAsync(PipelineStepContext context, AzureBicepResource resource) in //src/Aspire.Hosting.Azure/AzureBicepResource.cs:line 303
at Aspire.Hosting.Azure.AzureBicepResource.ProvisionAzureBicepResourceAsync(PipelineStepContext context, AzureBicepResource resource) in /
/src/Aspire.Hosting.Azure/AzureBicepResource.cs:line 326
at Aspire.Hosting.Azure.AzureBicepResource.ProvisionAzureBicepResourceAsync(PipelineStepContext context, AzureBicepResource resource) in //src/Aspire.Hosting.Azure/AzureBicepResource.cs:line 328
at Aspire.Hosting.Azure.AzureBicepResource.<>c__DisplayClass0_0.<<-ctor>b__1>d.MoveNext() in /
/src/Aspire.Hosting.Azure/AzureBicepResource.cs:line 52
--- End of stack trace from previous location ---
at Aspire.Hosting.Pipelines.DistributedApplicationPipeline.ExecuteStepAsync(PipelineStep step, PipelineStepContext stepContext) in //src/Aspire.Hosting/Pipelines/DistributedApplicationPipeline.cs:line 737
--- End of inner exception stack trace ---
at Aspire.Hosting.Pipelines.DistributedApplicationPipeline.ExecuteStepAsync(PipelineStep step, PipelineStepContext stepContext) in /
/src/Aspire.Hosting/Pipelines/DistributedApplicationPipeline.cs:line 742
at Aspire.Hosting.Pipelines.DistributedApplicationPipeline.<>c__DisplayClass18_1.<g__ExecuteStepWithDependencies|0>d.MoveNext() in /_/src/Aspire.Hosting/Pipelines/DistributedApplica

Added the missing 'id' output for the funcapp-containerapp deployment in the DeployAsync_WithAzureFunctionsProject_Works test. This output is required when an Azure Functions project is deployed to a container app environment, as the Bicep provisioner needs this id to reference the deployed container app resource.

Co-authored-by: captainsafia <[email protected]>
Copy link
Contributor Author

Copilot AI commented Nov 6, 2025

@copilot Fix the test failure related to incomplete mocked data from the Bicep call.

[+965/x1/?0] Aspire.Hosting.Azure.Tests.dll (net8.0|arm64) - Aspire.Hosting.Azure.Tests.AzureDeployerTests.Deplo...

Fixed in commit 93eaff4. Added the missing id output for the funcapp-containerapp deployment case in the mock data. When an Azure Functions project is deployed to a container app environment, it creates a containerapp resource that requires an id output for the Bicep provisioner to reference the deployed resource.

Copilot finished work on behalf of captainsafia November 6, 2025 17:32
@captainsafia captainsafia marked this pull request as ready for review November 8, 2025 01:09
@captainsafia captainsafia requested a review from eerhardt November 8, 2025 01:10
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds a new overload to AddAzureFunctionsProject that accepts a project file path as a string parameter, enabling Azure Functions projects to be added to the application model without requiring a compile-time project reference. The implementation mirrors the existing AddProject API design patterns.

Key changes:

  • New AddAzureFunctionsProject(string name, string projectPath) overload with path normalization
  • Refactored core logic into AddAzureFunctionsProjectCore to reduce code duplication
  • Added AzureFunctionsProjectMetadata class to handle project path resolution

Reviewed Changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.

File Description
src/Aspire.Hosting.Azure.Functions/AzureFunctionsProjectResourceExtensions.cs Adds new public API overload accepting project path, refactors shared logic into core method, implements path normalization and project metadata handling
src/Aspire.Hosting.Azure.Functions/Aspire.Hosting.Azure.Functions.csproj Adds LogicalName to embedded resource for proper runtime resolution
tests/Aspire.Hosting.Azure.Tests/AzureFunctionsTests.cs Adds 6 comprehensive tests covering the new overload's functionality including path normalization, environment configuration, storage sharing, and annotation behavior; includes whitespace cleanup
tests/Aspire.Hosting.Azure.Tests/AzureDeployerTests.cs Updates mock deployment output handler to support container app resources with proper precedence ordering

using var builder = TestDistributedApplicationBuilder.Create();

// Create a temporary project file
var tempDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot look for the TempDirectory helper used in other tests and use it here too.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated in commit 9dcdfdc. Replaced manual temp directory creation and cleanup with the TempDirectory helper, which provides automatic cleanup via IDisposable and is consistent with other tests in the suite.

Replaced manual temporary directory creation and cleanup with the TempDirectory helper class that's used throughout the test suite. This provides:
- Automatic cleanup via IDisposable pattern
- Consistent temp directory handling
- Simpler, more maintainable test code
- Better error handling during cleanup

Co-authored-by: davidfowl <[email protected]>
Copilot AI requested a review from davidfowl November 8, 2025 01:33
Copilot finished work on behalf of davidfowl November 8, 2025 01:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Aspire lacks an AddAzureFunctionsProject override accepting a project path

3 participants