-
Notifications
You must be signed in to change notification settings - Fork 824
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:
- require a separate manual install step,
- materialize helper files or projects to disk, or
- 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 assample.dllplussample.runtimeconfig.jsontools/<tfm>/anyfor 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
-
Extend
AddExecutablewith 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. -
Support only
DotnetToolpackages. 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 bothlib/<tfm>andtools/<tfm>/anylayouts. -
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.