microsoft / microsoft/aspire

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

Open
#15,255 0 comments 0 reactions 0 assignees View on GitHub
area-app-model
Dominant language
C#
Stars
6.3k
Forks
991
Avg merge
2d 15h
Merged PRs (30d)
196

Description

## 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/` for packages that contain a runnable entry point such as `sample.dll` plus `sample.runtimeconfig.json`
- `tools//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

```diff
namespace Aspire.Hosting;

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

## Usage Examples

```csharp
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();
```

```csharp
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();
```

```csharp
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/` and `tools//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.

Contributor guide

Open the contributing guide

Research direction

Start with the existing AddExecutable API and docs/specs/polyglot-apphost.md, then compare the proposed PackageExecutableResource extensions with the related #13077 dotnet-tool work. Done means the supported lib/ and tools//any layouts, restore and publish behavior, diagnostics, and public API boundaries are resolved well enough to implement and validate.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp
Domain
backend-api-design
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.