-
Couldn't load subscription status.
- Fork 709
Description
As discussed here, I'm facing a situation where I need to add two project resources to the IDistributedApplicationBuilder that point to the same project.
var builder = DistributedApplication.CreateBuilder(args);
var apiGateway = builder.AddProject<Projects.Lynx_Backend_APIGateway>("apigateway");
var db = builder
.AddSqlServerContainer("dbServer")
.AddDatabase("db");
// Projects.Lynx_Backend_Host is the host project for every module
var moduleA = builder
.AddProject<Projects.Lynx_Backend_Host>("Core")
.WithLaunchProfile("core")
.WithReference(db);
var moduleB = builder
.AddProject<Projects.Lynx_Backend_Host>("HR")
.WithLaunchProfile("hr")
.WithReference(db);
apiGateway.WithReference(moduleA);
apiGateway.WithReference(moduleB);
builder.Build().Run();The use case here is to use the same project to run different apps, that project acts as a host that loads module configuration at runtime and runs accordingly. The goal is to have a way to pass information to that project (in this case what modules to run, but can be any context needed). The only way I'm able to do that at this moment is to specify a different LaunchProfile for each and then have the configuration on the launchsettings.json at the project.
On running this for 2 instances (each one will load a module and run), I get the following error:
Aspire.Hosting.Dashboard.ProjectViewModelCache[0]
Task to write view model change terminated for resource type: ProjectViewModel
System.InvalidOperationException: Sequence contains more than one matching element
at System.Linq.ThrowHelper.ThrowMoreThanOneMatchException()
at System.Linq.Enumerable.TryGetSingle[TSource](IEnumerable`1 source, Func`2 predicate, Boolean& found)
at System.Linq.Enumerable.SingleOrDefault[TSource](IEnumerable`1 source, Func`2 predicate)
at Aspire.Hosting.ApplicationModel.ProjectResourceExtensions.TryGetProjectWithPath(DistributedApplicationModel model, String path, ProjectResource& projectResource) in /_/src/Aspire.Hosting/ApplicationModel/ProjectResourceExtensions.cs:line 32
at Aspire.Hosting.Dashboard.ViewModelCache`2.<>c__DisplayClass12_0.<FillEndpoints>b__1(Endpoint ep) in /_/src/Aspire.Hosting/Dashboard/ViewModelCache.cs:line 186
at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext()
at System.Linq.Enumerable.WhereEnumerableIterator`1.MoveNext()
at System.Collections.Generic.List`1.AddRange(IEnumerable`1 collection)
at Aspire.Hosting.Dashboard.ViewModelCache`2.FillEndpoints(DistributedApplicationModel applicationModel, IEnumerable`1 services, IEnumerable`1 endpoints, CustomResource resource, ResourceViewModel resourceViewModel) in /_/src/Aspire.Hosting/Dashboard/ViewModelCache.cs:line 174
at Aspire.Hosting.Dashboard.ProjectViewModelCache.ConvertToViewModel(DistributedApplicationModel applicationModel, IEnumerable`1 services, IEnumerable`1 endpoints, Executable executable, List`1 additionalEnvVars) in /_/src/Aspire.Hosting/Dashboard/ProjectViewModelCache.cs:line 40
at Aspire.Hosting.Dashboard.ViewModelCache`2.ViewModelGeneratingEnumerator.ComputeResult(ObjectChangeType objectChangeType, TResource resource, List`1 additionalEnvVars) in /_/src/Aspire.Hosting/Dashboard/ViewModelCache.cs:line 462
at Aspire.Hosting.Dashboard.ViewModelCache`2.ViewModelGeneratingEnumerator.MoveNextAsync() in /_/src/Aspire.Hosting/Dashboard/ViewModelCache.cs:line 352
at Aspire.Hosting.Dashboard.ViewModelCache`2.<>c__DisplayClass7_0.<<-ctor>b__0>d.MoveNext() in /_/src/Aspire.Hosting/Dashboard/ViewModelCache.cs:line 59
Checking the source code I can see that this maybe not going to work, because it checks for projects with the "same path", and in my case they all are the same project running - just with different context.
/// <summary>
/// Tries to get the project resource with the specified path from the distributed application model.
/// </summary>
/// <param name="model">The distributed application model.</param>
/// <param name="path">The path of the project resource.</param>
/// <param name="projectResource">When this method returns, contains the project resource with the specified path, if it is found; otherwise, null.</param>
/// <returns><see langword="true"/> if the project resource with the specified path is found; otherwise, <see langword="false"/>.</returns>
public static bool TryGetProjectWithPath(this DistributedApplicationModel model, string path, [NotNullWhen(true)] out ProjectResource? projectResource)
{
projectResource = model.GetProjectResources().SingleOrDefault(p => p.Annotations.OfType<IServiceMetadata>().FirstOrDefault()?.ProjectPath == path);
return projectResource is not null;
}This launchProfile pattern is not ideal, because the configuration don't stay on the Host that runs (and orchestrate) the entire solution. What I propose is to have something that we can pass context to the project resource, the simplest and more generic way I see here is to be able to set environment variables available for that project execution.
var moduleA = builder
.AddProject<Projects.Lynx_Backend_Host>("Core")
.WithEnvironmentVariable("HOST_CONFIG", "{...}")
.WithReference(db);In my case I want to pass a json, but with this pattern any serialized context can be passed and then used by the project just by using the default configuration mechanisms.