Skip to content

Port .NET Tool Integration from PR #13168#13169

Closed
Copilot wants to merge 6 commits intomainfrom
copilot/open-pr-contents-of-13168
Closed

Port .NET Tool Integration from PR #13168#13169
Copilot wants to merge 6 commits intomainfrom
copilot/open-pr-contents-of-13168

Conversation

Copy link
Contributor

Copilot AI commented Nov 24, 2025

Description

Ports the .NET tool integration playground from #13168 by @afscrome. This adds support for running .NET global tools (dotnet-ef, dotnet-dump, etc.) as Aspire resources using dotnet tool exec.

Credit: All implementation code authored by @afscrome.

Changes

  • Added playground/DotnetTool/ demonstrating .NET tool resources with AddDotnetTool() extension methods
  • Supports version specifications, prerelease packages, and custom NuGet sources
  • Fixed central package management by adding NuGet.Configuration v6.14.0 to Directory.Packages.props
  • Updated launchSettings.json to align with PR Fix playground AppHost port conflicts, add MCP endpoints, and ensure generate-manifest profiles - assign unique ports to all 52 projects #13155 port allocation scheme:
    • Application URLs: 16179/16180 (HTTPS/HTTP)
    • OTLP endpoints: 17119/17120
    • MCP endpoints: 18036/18037
    • Resource Service endpoints: 17119/17120
    • No port conflicts with other playground apps
  • Removed DotnetToolInstaller.cs (leftover from dotnet tool install approach that was not needed for dotnet tool exec implementation)
  • Added comprehensive test project Aspire.Hosting.DotnetTool.Tests with 22 unit tests covering:
    • Basic AddDotnetTool usage and resource creation
    • Package version specifications (including wildcards and prerelease)
    • Custom NuGet sources (single and multiple)
    • IgnoreExistingFeeds and IgnoreFailedSources flags
    • Additional arguments passing through
    • Manifest generation
    • Error handling for invalid inputs

Example Usage

var builder = DistributedApplication.CreateBuilder(args);

// Add a tool with specific version
var tool = builder.AddDotnetTool("ef-tool", "dotnet-ef")
    .WithPackageVersion("10.0.*");

// Add with prerelease support
builder.AddDotnetTool("trace-tool", "dotnet-trace")
    .WithPackagePrerelease()
    .WithArgs("--version");

Relates to #13077

Checklist

  • Is this feature complete?
    • Yes. Ready to ship.
    • No. Follow-up changes expected.
  • Are you including unit tests for the changes and scenario tests if relevant?
    • Yes
    • No
  • Did you add public API?
    • Yes
      • If yes, did you have an API Review for it?
        • Yes
        • No
      • Did you add <remarks /> and <code /> elements on your triple slash comments?
        • Yes
        • No
    • No
  • Does the change make any security assumptions or guarantees?
    • Yes
    • No
  • Does the change require an update in our Aspire docs?
Original prompt

Open a new PR wit the contents of #13168, keep the same description and mention @afscrome in the description and credit him for this work


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

@afscrome
Copy link
Contributor

afscrome commented Nov 24, 2025

Concurrency - I just pushed my own PR to #13168 😂

Oh I misread, thought this was based on the issue, not the PR I just opened.

Copilot AI changed the title [WIP] Open new PR for contents of #13168 Port .NET Tool Integration from PR #13168 Nov 24, 2025
Copilot AI requested a review from davidfowl November 24, 2025 20:08
</ItemGroup>

<ItemGroup>
<PackageReference Include="NuGet.Configuration" />
Copy link
Contributor

Choose a reason for hiding this comment

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

This reference isn't needed after moving from dotnet tool install to dotnet tool exec. Remove it, and the reference in Directory.Packages.props

Copy link
Member

Choose a reason for hiding this comment

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

@copilot do that

@davidfowl
Copy link
Member

@copilot Implement the PR feedback

…location

- Changed app URLs from 18001/18002 to 16179/16180 (standard app port range)
- Updated OTLP endpoints from 18003/18005 to 17119/17120 (standard OTLP range)
- Added MCP endpoints 18036/18037 (standard MCP range)
- Updated Resource Service endpoints to match OTLP endpoints
- Removed launchBrowser from generate-manifest profile for consistency

Co-authored-by: davidfowl <95136+davidfowl@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Nov 24, 2025

@copilot Implement the PR feedback

Updated launchSettings.json to align with PR #13155 port allocation scheme in commit d2a0efe. Changes:

  • App URLs: 16179/16180 (was 18001/18002)
  • OTLP endpoints: 17119/17120 (was 18003/18005)
  • Added MCP endpoints: 18036/18037
  • Resource Service endpoints now match OTLP endpoints

@github-actions
Copy link
Contributor

🚀 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 -- 13169

Or

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

@@ -0,0 +1,7 @@
namespace DotnetTool.AppHost;

internal sealed class DotnetToolInstaller(string name, string command) :
Copy link
Contributor

@afscrome afscrome Nov 25, 2025

Choose a reason for hiding this comment

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

Remove this class and file - I should have been removed for the dotnet tool exec version.

Comment on lines +60 to +61
.WithPackageIgnoreFailedSources()
;
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
.WithPackageIgnoreFailedSources()
;
.WithPackageIgnoreFailedSources();

@afscrome afscrome mentioned this pull request Dec 1, 2025
16 tasks
@afscrome
Copy link
Contributor

afscrome commented Dec 1, 2025

@davidfowl Anything I can do to help move this along.

@davidfowl
Copy link
Member

@copilot DotnetToolInstaller - Remove this class and file - I should have been removed for the dotnet tool exec version.

Also write tests for this in a new test project.

- Removed DotnetToolInstaller.cs (leftover from dotnet tool install approach)
- Created new test project Aspire.Hosting.DotnetTool.Tests with 22 tests
- All tests pass and validate AddDotnetTool extension methods
- Tests cover: basic usage, version specs, prerelease, custom sources, args, and manifest generation
- Added using statements to playground files (needed for test linking)
- Fixed DotnetToolResource to use "." as working directory (fixes manifest generation)
- Added test project to solution file in alphabetical order

Co-authored-by: davidfowl <95136+davidfowl@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Dec 1, 2025

@copilot DotnetToolInstaller - Remove this class and file - I should have been removed for the dotnet tool exec version.

Also write tests for this in a new test project.

Removed DotnetToolInstaller.cs and created comprehensive test project Aspire.Hosting.DotnetTool.Tests with 22 tests covering all functionality in commit dbcb13e. Tests validate:

  • Basic AddDotnetTool usage and resource creation
  • Package version specifications (including wildcards)
  • Prerelease packages
  • Custom NuGet sources (single and multiple)
  • IgnoreExistingFeeds and IgnoreFailedSources flags
  • Additional arguments passing through
  • Manifest generation
  • Error handling for invalid inputs

All tests pass successfully.

@afscrome
Copy link
Contributor

afscrome commented Dec 2, 2025

It would be nice to set the resource type to something more specific than executable (e.g. tool).
image

Comment on lines +57 to +58
x.Args.Add("--verbosity");
x.Args.Add("detailed");
Copy link
Contributor

Choose a reason for hiding this comment

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

Verbose output made sense when dotnet tool install was it's own resource, but for dotnet tool exec, it just adds noise a user may not be expecting.

Suggested change
x.Args.Add("--verbosity");
x.Args.Add("detailed");

@afscrome
Copy link
Contributor

afscrome commented Dec 2, 2025

It would be awesome if we could render the source column more cleanly on the dashboard. Similar to how project works

Currently the source renders like the top example, but it would be nicer if we could render it more like the second - similar to how project resources render foo.csproj --args rather than `dotnet run --project foo.csproj -- --args"

image

Similar to how projects are displayed
image

I was able to hack this to work, but DCP does it's darndest to undo any custom values, so I had to fight DcpExecuor by undoing every change it tried to make.

Properties = previous.Properties.SetResourcePropertyRange([
new(KnownProperties.Executable.Path, executable.Spec.ExecutablePath),
new(KnownProperties.Executable.WorkDir, executable.Spec.WorkingDirectory),
new(KnownProperties.Executable.Args, executable.Status?.EffectiveArgs ?? []) { IsSensitive = true },
new(KnownProperties.Executable.Pid, executable.Status?.ProcessId),
new(KnownProperties.Project.Path, projectPath),
new(KnownProperties.Project.LaunchProfile, launchProfileName),
new(KnownProperties.Resource.AppArgs, launchArguments?.Args) { IsSensitive = launchArguments?.IsSensitive ?? false },
new(KnownProperties.Resource.AppArgsSensitivity, launchArguments?.ArgsAreSensitive) { IsSensitive = launchArguments?.IsSensitive ?? false },
]),

.OnInitializeResource(async (resource,evt,ct) =>
{
    var rns = evt.Services.GetRequiredService<ResourceNotificationService>();
    _ = Task.Run(async () =>
    {
        await foreach(var x in rns.WatchAsync(ct))
        {
            if (x.Resource != resource)
            {
                continue;
            }

            var expectedPath = resource.ToolConfiguration.PackageId;

            var existingPathProp = x.Snapshot.Properties.FirstOrDefault(p => p.Name == "executable.path");
            if (existingPathProp == null || existingPathProp.Value as string != expectedPath)
            {
                var args = ImmutableArray.Create(await resource.GetArgumentValuesAsync());
                var toolSplitIndex = args.IndexOf("--");
                if (toolSplitIndex > 0)
                {
                    args = args[(toolSplitIndex + 1)..];
                }

                await rns.PublishUpdateAsync(resource, x => x with
                {
                    //TODO: `Use KnownProperties.Executable.Path` & `KnownProperties.Resource.AppArgsSensitivity`
                    //TODO: Merge with existing entry, not appending
                    Properties = x.Properties
                        .RemoveAll(x => x.Name == "resource.appArgs" || x.Name == "executable.path")
                        .AddRange(
                            new ResourcePropertySnapshot("resource.appArgs", args),
                            //TODO: set args sensitivity appropiately
                            //new(KnownProperties.Resource.AppArgsSensitivity, TODO)
                            new ResourcePropertySnapshot("executable.path", resource.ToolConfiguration.PackageId))
                });
            }
        }

    }, ct);
});

Related to the above, should we differentiate between args provided to the tool itself, vs args we provide to dotnet tool exec before the --. This could be something like how containers have WithArgs() and WithContainerRuntimeArgs(), or it could be more like a project resource which DcpExecutor has very special knowledge about. This would allow the args to be rendered more cleanly - above I'm -- as the marker to find the split, but that could be brittle if someone removes -- in a .WithArgs() callback.

@davidfowl davidfowl closed this Dec 9, 2025
@dotnet-policy-service dotnet-policy-service bot added this to the 13.1 milestone Dec 9, 2025
@github-actions github-actions bot locked and limited conversation to collaborators Jan 9, 2026
@sebastienros sebastienros deleted the copilot/open-pr-contents-of-13168 branch January 15, 2026 16:33
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants