dotnet / aspire

An opinionated, cloud ready stack for building observable, production ready, distributed applications in .NET
https://learn.microsoft.com/dotnet/aspire
MIT License
3.63k stars 409 forks source link

Add support for optionally generating a settings file #5015

Open BretJohnson opened 1 month ago

BretJohnson commented 1 month ago

Background and Motivation

There are various scenarios where it's useful to be able to launch a project in the "normal way", outside of Aspire launching it, but still have that project be configured with the right Aspire settings in order to connect to Aspire services and the Aspire OpenTelemetry dashboard.

Scenarios include:

This API meets that need, by allowing the settings, normally passed via environment variables, to optionally be written to a settings file (e.g. a JSON file). Writing the settings file can happen in addition to building/launching the project normally, allowing both Aspire integrated and standalone startup to work, or it be the only option, with Aspire configured to not build/launch the project, just generate settings. The API here supports both use cases.

Proposed API

namespace Aspire.Hosting;

public static class ProjectResourceBuilderExtensions
{
+     /// <summary>
+    /// Adds a settings file to the project resource, to optionally persist settings that are normally set via environment variables.
+    /// The settings file can be generated with or without also launching the project.
+    /// </summary>
+    /// <param name="builder">The <see cref="IDistributedApplicationBuilder"/>.</param>
+    /// <param name="settingsFilePath">The path to settings file, absolute or relative to app host directory.</param>
+    /// <param name="settingsFileType">The settings file type, C# code (for use with IConfigurationBuilder.AddInMemoryCollection) or JSON (for use with IConfigurationBuilder.AddJsonFile).</param>
+    /// <param name="onlyGenerateSettings">Indicate whether to skip build/running this resource, only generating settings.</param>
+    public static IResourceBuilder<ProjectResource> WithSettingsFile(this IResourceBuilder<ProjectResource> builder, string settingsFilePath, SettingsFileType settingsFileType, bool onlyGenerateSettings)

+    /// <summary>
+    /// Adds a settings file to the project resource, to optionally persist settings that are normally set via environment variables.
+    /// The settings file can be generated with or without also launching the project.
+    /// </summary>
+    /// <param name="builder">The <see cref="IDistributedApplicationBuilder"/>.</param>
+    /// <param name="settingsFileOptions">The options to use for settings file generation.</param>
+    /// <returns></returns>
+    public static IResourceBuilder<ProjectResource> WithSettingsFile(this IResourceBuilder<ProjectResource> builder, SettingsFileOptions settingsFileOptions)

+    /// <summary>
+    /// Adds a .NET project to the application model, just generating a settings file for the project.
+    /// </summary>
+    /// <param name="builder">The <see cref="IDistributedApplicationBuilder"/>.</param>
+    /// <param name="name">The name of the resource. This name will be used for service discovery when referenced in a dependency.</param>
+    /// <returns>A reference to the <see cref="IResourceBuilder{T}"/>.</returns>
+    /// <remarks>
+    /// <para>
+    /// This overload of the <see cref="AddProject(IDistributedApplicationBuilder, string, string)"/> method adds a project to the application
+    /// model where Aspire only generates a settings file for the project, not otherwise trying to build or run it. This is useful for projects
+    /// that you wish to always launch manually, but have them connect to Aspire services and OTEL.
+    /// </para>
+    public static IResourceBuilder<ProjectResource> AddSettingsOnlyProject(this IDistributedApplicationBuilder builder, string name,
+        string settingsFilePath, SettingsFileType settingsFileType)
}

A draft PR implementing this API is here: https://github.com/dotnet/aspire/pull/5016.

Usage Examples

builder.AddProject<Projects.MyFrontend>("mywebapp")
       .WithSettingsFile("..\\MyWebApp\\obj\\appsettingsAspire.g.json", SettingsFileType.Json, onlyGenerateSettings: false)
       .WithExternalHttpEndpoints()
       .WithReference(basketService)
       .WithReference(catalogService);

builder.AddSettingsOnlyProject("mymauiapp", "..\\MyMauiApp\\obj\\appsettingsAspire.g.cs", SettingsFileType.CSharp)
       .WithExternalHttpEndpoints()
       .WithReference(basketService)
       .WithReference(catalogService);

Alternative Designs

Initially, I considered having a separate project type for projects that generate settings. But allowing settings to optionally be generated for any .NET project, via a settings file IResourceAnnotation, allows more flexibility and emphasizes this is optional functionality in addition to integrated launch. Plus it required less changes to add. It seems like a nicer design all around.

Risks

maddymontaquila commented 1 month ago

I think this is unnecessary - Aspire can handle env variables, and this is just adding extra orchestration steps that arent necessary. A whole project for a settings file seems too much too.

maddymontaquila commented 1 month ago

At no point does wiring things up from Aspire mean you cant switch to the individual startup project - so you can just always switch around. I have been running frontends separately to test them and it doesn't change anything.

BretJohnson commented 1 month ago

I think this is unnecessary - Aspire can handle env variables, and this is just adding extra orchestration steps that arent necessary. A whole project for a settings file seems too much too.

Just to be clear, the proposal here doesn't (necessarily) add a whole project for the settings file. The settings file is an optional addition to an existing project (via WithSettingsFile).

BretJohnson commented 1 month ago

At no point does wiring things up from Aspire mean you cant switch to the individual startup project - so you can just always switch around. I have been running frontends separately to test them and it doesn't change anything.

Yes, you can do this, but how do those projects get the right settings to use, to connect back to Aspire hosted services or the Aspire dashboard?

mitchdenny commented 1 month ago

I'm not super enthused about this API myself. Seems to me the root of the issue is passing environment variables into mobile apps when you launch them. Android for example has the concept of a Wrap script:

https://developer.android.com/ndk/guides/wrap-script

Maybe there is some prior art here we can lean on which is idiomatic within each target ecosystem?

BretJohnson commented 1 month ago

I'm not super enthused about this API myself. Seems to me the root of the issue is passing environment variables into mobile apps when you launch them. Android for example has the concept of a Wrap script:

https://developer.android.com/ndk/guides/wrap-script

Maybe there is some prior art here we can lean on which is idiomatic within each target ecosystem?

We can't use the Android wrap script because that's embedded into the app at build time. But we have another custom way to pass environment variables at launch time to Android apps, which looks like it should get into 17.12, so I think we'll have Aspire integrated launch support in VS 17.12 for MAUI. All that said, I still see value with this proposal. There are multiple scenarios, especially with mobile, where you want to be able to launch the app the traditional way (not thru AppHost in VS), but still use Aspire and dashboard. I added another one to the bulleted list above - device test tools like Appium, BrowserStack, xharness, etc. want to do the app deploy and launch - that's the way they are built. Tools like Appium will not just do the initial app launch but also relaunch the app for each test (optional) or on crash. All that tooling isn't built to launch through AppHost (especially not thru VS, as it's generally CLI based). But it's still useful to test with Aspire, running in CI or locally. And then there are the other scenarios I mentioned above, like especially faster inner dev loop, but also Rider, CLI, etc.

I just see this proposal as giving users more options & greater compatibility with existing workflows. Integrated launch is nice. But sometimes you want - or need - to launch the traditional way & still use Aspire services/dashboard. It's nice to support both.