Skip to content

First-class package-backed executable resources in Aspire.Hosting #15255

@YandyZaldivar

Description

@YandyZaldivar

Background and Motivation

Aspire.Hosting has AddExecutable(...), but that API assumes the executable already exists on disk.

There is currently no first-class way to model an executable resource whose runnable artifact comes from a NuGet package restored by the AppHost. That gap makes some integrations awkward to implement, because the fallback is usually one of these:

  1. require a separate manual install step,
  2. materialize helper files or projects to disk, or
  3. keep the executable outside the Aspire resource model.

A package-backed executable resource would let an AppHost restore a package at startup and run a selected entry point from the restored package contents.

This is useful for at least these package layouts:

  • lib/<tfm> for packages that contain a runnable entry point such as sample.dll plus sample.runtimeconfig.json
  • tools/<tfm>/any for packages that need published output and additional managed dependencies beside the entry point

One concrete use case is the work in CommunityToolkit/Aspire#1136, where an integration needs to run packaged helper or provisioning logic as part of the Aspire resource model instead of relying on custom extraction or separate setup.

This proposal also appears consistent with the polyglot AppHost direction in docs/specs/polyglot-apphost.md: the API is exposed through [AspireExport] capabilities, so guest-language apphosts should be able to model these resources through ATS-generated bindings while the .NET backend continues to own NuGet restore and orchestration behavior.

The closest related item appears to be #13077 (Dotnet Tool Integration), but this proposal is broader than dotnet-tool packages and dotnet-tool depends on the .NET SDK, not just the runtime.

Proposed API

namespace Aspire.Hosting;

+public static class PackageExecutableResourceBuilderExtensions
+{
+    public static IResourceBuilder<PackageExecutableResource> AddPackageExecutable(
+        this IDistributedApplicationBuilder builder,
+        string name,
+        string packageId);
+
+    public static IResourceBuilder<T> AddPackageExecutable<T>(
+        this IDistributedApplicationBuilder builder,
+        T resource)
+        where T : PackageExecutableResource;
+
+    public static IResourceBuilder<T> WithPackageVersion<T>(
+        this IResourceBuilder<T> builder,
+        string version)
+        where T : PackageExecutableResource;
+
+    public static IResourceBuilder<T> WithPackageSource<T>(
+        this IResourceBuilder<T> builder,
+        string source)
+        where T : PackageExecutableResource;
+
+    public static IResourceBuilder<T> WithPackageSources<T>(
+        this IResourceBuilder<T> builder,
+        params string[] sources)
+        where T : PackageExecutableResource;
+
+    public static IResourceBuilder<T> WithPackageExecutable<T>(
+        this IResourceBuilder<T> builder,
+        string executableName)
+        where T : PackageExecutableResource;
+
+    public static IResourceBuilder<T> WithPackageIgnoreExistingFeeds<T>(
+        this IResourceBuilder<T> builder)
+        where T : PackageExecutableResource;
+
+    public static IResourceBuilder<T> WithPackageIgnoreFailedSources<T>(
+        this IResourceBuilder<T> builder)
+        where T : PackageExecutableResource;
+
+    public static IResourceBuilder<T> WithPackageWorkingDirectory<T>(
+        this IResourceBuilder<T> builder,
+        string workingDirectory)
+        where T : PackageExecutableResource;
+}
+
+namespace Aspire.Hosting.ApplicationModel;
+
+public class PackageExecutableResource : ExecutableResource
+{
+    public PackageExecutableResource(string name, string packageId);
+}

Usage Examples

var builder = DistributedApplication.CreateBuilder(args);

var openApi = builder.AddPackageExecutable("openapi", "Contoso.OpenApi.Server")
    .WithPackageVersion("1.2.3")
    .WithPackageExecutable("Contoso.OpenApi.Server")
    .WithArgs("--urls", "http://localhost:8080");

builder.Build().Run();
var builder = DistributedApplication.CreateBuilder(args);

var sample = builder.AddPackageExecutable("sample", "Contoso.Sample.WebApi")
    .WithPackageVersion("1.2.3")
    .WithPackageExecutable("sample.dll")
    .WithPackageWorkingDirectory("lib/net10.0");

builder.Build().Run();
var builder = DistributedApplication.CreateBuilder(args);

var packagedTool = builder.AddPackageExecutable("tool", "Contoso.Tooling")
    .WithPackageVersion("1.2.3")
    .WithPackageSource("https://api.nuget.org/v3/index.json")
    .WithPackageExecutable("tool.dll");

builder.Build().Run();

Alternative Designs

  1. Extend AddExecutable with package-related overloads instead of introducing a separate resource type. This would reduce API surface, but it would also mix two different acquisition models: existing-on-disk executables and package-restored executables.

  2. Support only DotnetTool packages. That would help some scenarios, but it is narrower than the actual gap. Regular NuGet packages can also carry runnable assets, and this proposal is meant to cover both lib/<tfm> and tools/<tfm>/any layouts.

  3. Keep this as an integration-specific pattern instead of a hosting primitive. That would continue to push each integration toward custom restore, extraction, and process-launch logic instead of giving Aspire a reusable app-model concept.

Risks

  • This introduces new public API in Aspire.Hosting, so the main risk is committing to an API shape before all supported package layouts and restore behaviors are fully understood.
  • Package-backed executables are close to, but not identical to, dotnet-tool scenarios. If both are supported, the relationship between this API and dotnet-tool support will need to stay clear.
  • Restore-time behavior could introduce startup-time failures related to package sources, authentication, or feed configuration, so error handling and diagnostics will matter.
  • Publish behavior needs clear expectations, especially for packages that rely on additional managed dependencies being present beside the entry point.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-app-modelIssues pertaining to the APIs in Aspire.Hosting, e.g. DistributedApplication

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions