diff --git a/Aspirant.sln b/Aspirant.sln
index 775f2aa..e8280a5 100644
--- a/Aspirant.sln
+++ b/Aspirant.sln
@@ -48,6 +48,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aspirant.Hosting.PostgreSQL
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aspirant.Hosting.RabbitMQ", "src\Aspirant.Hosting.RabbitMQ\Aspirant.Hosting.RabbitMQ.csproj", "{45EC9DE1-B3E7-4EF4-A0AA-8CD7230EB7FB}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aspirant.Hosting.WebApplication", "src\Aspirant.Hosting.WebApplication\Aspirant.Hosting.WebApplication.csproj", "{BA7B7E8C-EBC1-495E-86FB-56DA477AA4F4}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -110,6 +112,10 @@ Global
{45EC9DE1-B3E7-4EF4-A0AA-8CD7230EB7FB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{45EC9DE1-B3E7-4EF4-A0AA-8CD7230EB7FB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{45EC9DE1-B3E7-4EF4-A0AA-8CD7230EB7FB}.Release|Any CPU.Build.0 = Release|Any CPU
+ {BA7B7E8C-EBC1-495E-86FB-56DA477AA4F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {BA7B7E8C-EBC1-495E-86FB-56DA477AA4F4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {BA7B7E8C-EBC1-495E-86FB-56DA477AA4F4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {BA7B7E8C-EBC1-495E-86FB-56DA477AA4F4}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -130,6 +136,7 @@ Global
{135004A9-F7E0-46D9-B3D0-3D64BA3C40B9} = {6E773CFD-9758-4C09-96F6-5EDCED0E3A34}
{77D17495-C537-4446-8C2E-8B46560E0992} = {6E773CFD-9758-4C09-96F6-5EDCED0E3A34}
{45EC9DE1-B3E7-4EF4-A0AA-8CD7230EB7FB} = {6E773CFD-9758-4C09-96F6-5EDCED0E3A34}
+ {BA7B7E8C-EBC1-495E-86FB-56DA477AA4F4} = {6E773CFD-9758-4C09-96F6-5EDCED0E3A34}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {52627286-38AF-4E95-BDA0-B21166B2B18C}
diff --git a/src/Aspirant.Hosting.WebApplication/Aspirant.Hosting.WebApplication.csproj b/src/Aspirant.Hosting.WebApplication/Aspirant.Hosting.WebApplication.csproj
new file mode 100644
index 0000000..6f19045
--- /dev/null
+++ b/src/Aspirant.Hosting.WebApplication/Aspirant.Hosting.WebApplication.csproj
@@ -0,0 +1,20 @@
+
+
+
+ net8.0
+ enable
+ enable
+ A custom ASP.NET Core web app resource for .NET Aspire App Host projects.
+ aspire hosting aspnet aspnetcore
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Aspirant.Hosting.WebApplication/WebApplicationResource.cs b/src/Aspirant.Hosting.WebApplication/WebApplicationResource.cs
new file mode 100644
index 0000000..89375c3
--- /dev/null
+++ b/src/Aspirant.Hosting.WebApplication/WebApplicationResource.cs
@@ -0,0 +1,225 @@
+using Aspire.Hosting;
+using Aspire.Hosting.ApplicationModel;
+using Aspire.Hosting.Lifecycle;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Hosting.Server;
+using Microsoft.AspNetCore.Hosting.Server.Features;
+using Microsoft.AspNetCore.Http.Features;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+
+namespace Aspirant.Hosting;
+
+///
+/// Represents a ASP.NET Web Application resource.
+///
+public class WebApplicationResource(string name) : Resource(name), IResourceWithServiceDiscovery, IResourceWithEnvironment
+{
+
+}
+
+public abstract class WebApplicationResourceLifecycleHook(
+ IHostEnvironment hostEnvironment,
+ DistributedApplicationExecutionContext executionContext,
+ ResourceNotificationService resourceNotificationService,
+ ResourceLoggerService resourceLoggerService) : IDistributedApplicationLifecycleHook, IAsyncDisposable
+ where TResource : WebApplicationResource
+{
+ protected abstract string ResourceTypeName { get; }
+
+ private WebApplication? _app;
+
+ ///
+ public async Task BeforeStartAsync(DistributedApplicationModel appModel, CancellationToken cancellationToken = default)
+ {
+ if (executionContext.IsPublishMode)
+ {
+ return;
+ }
+
+ var resource = appModel.Resources.OfType().SingleOrDefault();
+
+ if (resource is null)
+ {
+ return;
+ }
+
+ await resourceNotificationService.PublishUpdateAsync(resource, s => s with
+ {
+ ResourceType = ResourceTypeName,
+ State = "Starting"
+ });
+
+ var bindings = resource.Annotations.OfType().ToList();
+
+ foreach (var b in bindings)
+ {
+ b.IsProxied = false;
+ }
+ }
+
+ ///
+ public async Task AfterEndpointsAllocatedAsync(DistributedApplicationModel appModel, CancellationToken cancellationToken = default)
+ {
+ if (executionContext.IsPublishMode)
+ {
+ return;
+ }
+
+ var resource = appModel.Resources.OfType().SingleOrDefault();
+
+ if (resource is null)
+ {
+ return;
+ }
+
+ var builder = WebApplication.CreateSlimBuilder(new WebApplicationOptions
+ {
+ ContentRootPath = hostEnvironment.ContentRootPath,
+ EnvironmentName = hostEnvironment.EnvironmentName,
+ WebRootPath = Path.Combine(hostEnvironment.ContentRootPath, "wwwroot")
+ });
+
+ builder.Logging.ClearProviders();
+
+ builder.Logging.AddProvider(new ResourceLoggerProvider(resourceLoggerService.GetLogger(resource.Name)));
+
+ if (resource.TryGetEnvironmentVariables(out var environmentVariables))
+ {
+ var context = new EnvironmentCallbackContext(executionContext, cancellationToken: cancellationToken);
+
+ foreach (var cb in environmentVariables)
+ {
+ await cb.Callback(context);
+ }
+
+ var dict = new Dictionary();
+ foreach (var (k, v) in context.EnvironmentVariables)
+ {
+ var val = v switch
+ {
+ string s => s,
+ IValueProvider vp => await vp.GetValueAsync(context.CancellationToken),
+ _ => throw new NotSupportedException()
+ };
+
+ if (val is not null)
+ {
+ dict[k.Replace("__", ":")] = val;
+ }
+ }
+
+ builder.Configuration.AddInMemoryCollection(dict);
+
+ await ConfigureBuilderAsync(builder, resource, cancellationToken);
+
+ resource.TryGetEndpoints(out var endpoints);
+ var defaultScheme = Environment.GetEnvironmentVariable("ASPNETCORE_URLS")?.Contains("https://") == true ? "https" : "http";
+ var needHttps = defaultScheme == "https" || endpoints?.Any(ep => ep.UriScheme == "https") == true;
+
+ if (needHttps)
+ {
+ builder.WebHost.UseKestrelHttpsConfiguration();
+ }
+
+ _app = builder.Build();
+
+ var urlToEndpointNameMap = new Dictionary();
+
+ if (endpoints is null)
+ {
+ var url = $"{defaultScheme}://127.0.0.1:0/";
+ _app.Urls.Add(url);
+ urlToEndpointNameMap[url] = "default";
+ }
+ else
+ {
+ foreach (var ep in endpoints)
+ {
+ var scheme = ep.UriScheme ?? defaultScheme;
+ needHttps = needHttps || scheme == "https";
+
+ var url = ep.Port switch
+ {
+ null => $"{scheme}://127.0.0.1:0/",
+ _ => $"{scheme}://localhost:{ep.Port}"
+ };
+
+ var uri = new Uri(url);
+ _app.Urls.Add(url);
+ urlToEndpointNameMap[uri.ToString()] = ep.Name;
+ }
+ }
+
+ await ConfigureApplicationAsync(_app, resource, cancellationToken);
+
+ await _app.StartAsync(cancellationToken);
+
+ var addresses = _app.Services.GetRequiredService().Features.GetRequiredFeature().Addresses;
+
+ foreach (var url in addresses)
+ {
+ if (urlToEndpointNameMap.TryGetValue(new Uri(url).ToString(), out var name)
+ || urlToEndpointNameMap.TryGetValue((new UriBuilder(url) { Port = 0 }).Uri.ToString(), out name))
+ {
+ var ep = endpoints?.FirstOrDefault(ep => ep.Name == name);
+ if (ep is not null)
+ {
+ var uri = new Uri(url);
+ var host = uri.Host is "127.0.0.1" or "[::1]" ? "localhost" : uri.Host;
+ ep.AllocatedEndpoint = new(ep, host, uri.Port);
+ }
+ }
+ }
+
+ await resourceNotificationService.PublishUpdateAsync(resource, s => s with
+ {
+ State = "Running",
+ Urls = [.. endpoints?.Select(ep => new UrlSnapshot(ep.Name, ep.AllocatedEndpoint?.UriString ?? "", IsInternal: false))],
+ });
+ }
+ }
+
+ protected abstract ValueTask ConfigureBuilderAsync(WebApplicationBuilder builder, TResource resource, CancellationToken cancellationToken);
+
+ protected abstract ValueTask ConfigureApplicationAsync(WebApplication application, TResource resource, CancellationToken cancellationToken);
+
+ ///
+ public ValueTask DisposeAsync()
+ {
+ return _app?.DisposeAsync() ?? default;
+ }
+
+ private class ResourceLoggerProvider(ILogger logger) : ILoggerProvider
+ {
+ public ILogger CreateLogger(string categoryName)
+ {
+ return new ResourceLogger(logger);
+ }
+
+ public void Dispose()
+ {
+ }
+
+ private class ResourceLogger(ILogger logger) : ILogger
+ {
+ public IDisposable? BeginScope(TState state) where TState : notnull
+ {
+ return logger.BeginScope(state);
+ }
+
+ public bool IsEnabled(LogLevel logLevel)
+ {
+ return logger.IsEnabled(logLevel);
+ }
+
+ public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter)
+ {
+ logger.Log(logLevel, eventId, state, exception, formatter);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Aspirant.Hosting.WebApplication/WebApplicationResourceExtensions.cs b/src/Aspirant.Hosting.WebApplication/WebApplicationResourceExtensions.cs
new file mode 100644
index 0000000..e972dec
--- /dev/null
+++ b/src/Aspirant.Hosting.WebApplication/WebApplicationResourceExtensions.cs
@@ -0,0 +1,35 @@
+using Aspire.Hosting;
+using Aspire.Hosting.ApplicationModel;
+using Aspire.Hosting.Lifecycle;
+
+namespace Aspirant.Hosting;
+
+
+///
+/// Extensions method to add WebApplication resources.
+///
+public static class WebApplicationResourceExtensions
+{
+ public static IResourceBuilder AddWebApplication(this IDistributedApplicationBuilder builder, TResource resource, bool excludeFromManifest = false)
+ where TResource : WebApplicationResource
+ where TLifecycleHook : WebApplicationResourceLifecycleHook
+ {
+ var webApplicationResource = builder.Resources.OfType().SingleOrDefault();
+
+ if (webApplicationResource is not null)
+ {
+ throw new InvalidOperationException($"A resource of type {nameof(TResource)} has already been added to this application");
+ }
+
+ builder.Services.TryAddLifecycleHook();
+
+ var resourceBuilder = builder.AddResource(resource);
+
+ if (excludeFromManifest)
+ {
+ resourceBuilder = resourceBuilder.ExcludeFromManifest();
+ }
+
+ return resourceBuilder;
+ }
+}
\ No newline at end of file
diff --git a/src/Aspirant.Hosting.Yarp/Aspirant.Hosting.Yarp.csproj b/src/Aspirant.Hosting.Yarp/Aspirant.Hosting.Yarp.csproj
index e6cef82..0b4f4f9 100644
--- a/src/Aspirant.Hosting.Yarp/Aspirant.Hosting.Yarp.csproj
+++ b/src/Aspirant.Hosting.Yarp/Aspirant.Hosting.Yarp.csproj
@@ -17,6 +17,7 @@
+
diff --git a/src/Aspirant.Hosting.Yarp/YarpResource.cs b/src/Aspirant.Hosting.Yarp/YarpResource.cs
index 6084e95..baf09e3 100644
--- a/src/Aspirant.Hosting.Yarp/YarpResource.cs
+++ b/src/Aspirant.Hosting.Yarp/YarpResource.cs
@@ -1,238 +1,276 @@
-using System.Linq;
-using Aspire.Hosting.ApplicationModel;
-using Aspire.Hosting.Lifecycle;
-using k8s.Models;
+using Aspire.Hosting.ApplicationModel;
using Microsoft.AspNetCore.Builder;
-using Microsoft.AspNetCore.Hosting;
-using Microsoft.AspNetCore.Hosting.Server;
-using Microsoft.AspNetCore.Hosting.Server.Features;
-using Microsoft.AspNetCore.Http.Features;
-using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
-using Microsoft.Extensions.Logging;
using Yarp.ReverseProxy.Configuration;
+using Aspirant.Hosting;
+
namespace Aspire.Hosting;
///
/// Represents a YARP resource.
///
/// The name of the resource in the application model.
-public class YarpResource(string name) : Resource(name), IResourceWithServiceDiscovery, IResourceWithEnvironment
+public class YarpResource(string name) : WebApplicationResource(name)
{
- // YARP configuration
internal Dictionary RouteConfigs { get; } = [];
internal Dictionary ClusterConfigs { get; } = [];
internal string? ConfigurationSectionName { get; set; }
}
-// This starts up the YARP reverse proxy with the configuration from the resource
-internal class YarpResourceLifecyclehook(
- IHostEnvironment hostEnvironment,
- DistributedApplicationExecutionContext executionContext,
- ResourceNotificationService resourceNotificationService,
- ResourceLoggerService resourceLoggerService) : IDistributedApplicationLifecycleHook, IAsyncDisposable
+internal class YarpResourceLifecycleHook(IHostEnvironment hostEnvironment, DistributedApplicationExecutionContext executionContext, ResourceNotificationService resourceNotificationService, ResourceLoggerService resourceLoggerService) : WebApplicationResourceLifecycleHook(hostEnvironment, executionContext, resourceNotificationService, resourceLoggerService)
{
- private WebApplication? _app;
+ protected override string ResourceTypeName { get; } = "Yarp";
- public async Task BeforeStartAsync(DistributedApplicationModel appModel, CancellationToken cancellationToken = default)
+ protected override ValueTask ConfigureApplicationAsync(WebApplication app, YarpResource resource, CancellationToken cancellationToken)
{
- if (executionContext.IsPublishMode)
- {
- return;
- }
-
- var yarpResource = appModel.Resources.OfType().SingleOrDefault();
-
- if (yarpResource is null)
- {
- return;
- }
+ app.MapReverseProxy();
- await resourceNotificationService.PublishUpdateAsync(yarpResource, s => s with
- {
- ResourceType = "Yarp",
- State = "Starting"
- });
-
- // We don't want to proxy for yarp resources so force endpoints to not proxy
- var bindings = yarpResource.Annotations.OfType().ToList();
-
- foreach (var b in bindings)
- {
- b.IsProxied = false;
- }
+ return ValueTask.CompletedTask;
}
- public async Task AfterEndpointsAllocatedAsync(DistributedApplicationModel appModel, CancellationToken cancellationToken = default)
+ protected override ValueTask ConfigureBuilderAsync(WebApplicationBuilder builder, YarpResource resource, CancellationToken cancellationToken)
{
- if (executionContext.IsPublishMode)
- {
- return;
- }
-
- var yarpResource = appModel.Resources.OfType().SingleOrDefault();
-
- if (yarpResource is null)
- {
- return;
- }
- var builder = WebApplication.CreateSlimBuilder(new WebApplicationOptions
- {
- ContentRootPath = hostEnvironment.ContentRootPath,
- EnvironmentName = hostEnvironment.EnvironmentName,
- WebRootPath = Path.Combine(hostEnvironment.ContentRootPath, "wwwroot")
- });
-
- builder.Logging.ClearProviders();
-
- builder.Logging.AddProvider(new ResourceLoggerProvider(resourceLoggerService.GetLogger(yarpResource.Name)));
-
- // Convert environment variables into configuration
- if (yarpResource.TryGetEnvironmentVariables(out var envAnnotations))
- {
- var context = new EnvironmentCallbackContext(executionContext, cancellationToken: cancellationToken);
-
- foreach (var cb in envAnnotations)
- {
- await cb.Callback(context);
- }
-
- var dict = new Dictionary();
- foreach (var (k, v) in context.EnvironmentVariables)
- {
- var val = v switch
- {
- string s => s,
- IValueProvider vp => await vp.GetValueAsync(context.CancellationToken),
- _ => throw new NotSupportedException()
- };
-
- if (val is not null)
- {
- dict[k.Replace("__", ":")] = val;
- }
- }
-
- builder.Configuration.AddInMemoryCollection(dict);
- }
-
builder.Services.AddServiceDiscovery();
var proxyBuilder = builder.Services.AddReverseProxy();
- if (yarpResource.RouteConfigs.Count > 0)
+ if (resource.RouteConfigs.Count > 0)
{
- proxyBuilder.LoadFromMemory([.. yarpResource.RouteConfigs.Values], [.. yarpResource.ClusterConfigs.Values]);
+ proxyBuilder.LoadFromMemory([.. resource.RouteConfigs.Values], [.. resource.ClusterConfigs.Values]);
}
- if (yarpResource.ConfigurationSectionName is not null)
+ if (resource.ConfigurationSectionName is not null)
{
- proxyBuilder.LoadFromConfig(builder.Configuration.GetSection(yarpResource.ConfigurationSectionName));
+ proxyBuilder.LoadFromConfig(builder.Configuration.GetSection(resource.ConfigurationSectionName));
}
proxyBuilder.AddServiceDiscoveryDestinationResolver();
- yarpResource.TryGetEndpoints(out var endpoints);
- var defaultScheme = Environment.GetEnvironmentVariable("ASPNETCORE_URLS")?.Contains("https://") == true ? "https" : "http";
- var needHttps = defaultScheme == "https" || endpoints?.Any(ep => ep.UriScheme == "https") == true;
-
- if (needHttps)
- {
- builder.WebHost.UseKestrelHttpsConfiguration();
- }
-
- _app = builder.Build();
-
- var urlToEndpointNameMap = new Dictionary();
-
- if (endpoints is null)
- {
- var url = $"{defaultScheme}://127.0.0.1:0/";
- _app.Urls.Add(url);
- urlToEndpointNameMap[url] = "default";
- }
- else
- {
- foreach (var ep in endpoints)
- {
- var scheme = ep.UriScheme ?? defaultScheme;
- needHttps = needHttps || scheme == "https";
-
- var url = ep.Port switch
- {
- null => $"{scheme}://127.0.0.1:0/",
- _ => $"{scheme}://localhost:{ep.Port}"
- };
-
- var uri = new Uri(url);
- _app.Urls.Add(url);
- urlToEndpointNameMap[uri.ToString()] = ep.Name;
- }
- }
-
- _app.MapReverseProxy();
-
- await _app.StartAsync(cancellationToken);
-
- var addresses = _app.Services.GetRequiredService().Features.GetRequiredFeature().Addresses;
-
- // Update the EndpointAnnotations with the allocated URLs from ASP.NET Core
- foreach (var url in addresses)
- {
- if (urlToEndpointNameMap.TryGetValue(new Uri(url).ToString(), out var name)
- || urlToEndpointNameMap.TryGetValue((new UriBuilder(url) { Port = 0 }).Uri.ToString(), out name))
- {
- var ep = endpoints?.FirstOrDefault(ep => ep.Name == name);
- if (ep is not null)
- {
- var uri = new Uri(url);
- var host = uri.Host is "127.0.0.1" or "[::1]" ? "localhost" : uri.Host;
- ep.AllocatedEndpoint = new(ep, host, uri.Port);
- }
- }
- }
-
- await resourceNotificationService.PublishUpdateAsync(yarpResource, s => s with
- {
- State = "Running",
- Urls = [.. endpoints?.Select(ep => new UrlSnapshot(ep.Name, ep.AllocatedEndpoint?.UriString ?? "", IsInternal: false))],
- });
- }
-
- public ValueTask DisposeAsync()
- {
- return _app?.DisposeAsync() ?? default;
+ return ValueTask.CompletedTask;
}
+}
- private class ResourceLoggerProvider(ILogger logger) : ILoggerProvider
- {
- public ILogger CreateLogger(string categoryName)
- {
- return new ResourceLogger(logger);
- }
-
- public void Dispose()
- {
- }
- private class ResourceLogger(ILogger logger) : ILogger
- {
- public IDisposable? BeginScope(TState state) where TState : notnull
- {
- return logger.BeginScope(state);
- }
-
- public bool IsEnabled(LogLevel logLevel)
- {
- return logger.IsEnabled(logLevel);
- }
-
- public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter)
- {
- logger.Log(logLevel, eventId, state, exception, formatter);
- }
- }
- }
-}
\ No newline at end of file
+// ///
+// /// Represents a YARP resource.
+// ///
+// /// The name of the resource in the application model.
+// public class YarpResource(string name) : Resource(name), IResourceWithServiceDiscovery, IResourceWithEnvironment
+// {
+// // YARP configuration
+// internal Dictionary RouteConfigs { get; } = [];
+// internal Dictionary ClusterConfigs { get; } = [];
+// internal string? ConfigurationSectionName { get; set; }
+// }
+
+// // This starts up the YARP reverse proxy with the configuration from the resource
+// internal class YarpResourceLifecyclehook(
+// IHostEnvironment hostEnvironment,
+// DistributedApplicationExecutionContext executionContext,
+// ResourceNotificationService resourceNotificationService,
+// ResourceLoggerService resourceLoggerService) : IDistributedApplicationLifecycleHook, IAsyncDisposable
+// {
+// private WebApplication? _app;
+
+// public async Task BeforeStartAsync(DistributedApplicationModel appModel, CancellationToken cancellationToken = default)
+// {
+// if (executionContext.IsPublishMode)
+// {
+// return;
+// }
+
+// var yarpResource = appModel.Resources.OfType().SingleOrDefault();
+
+// if (yarpResource is null)
+// {
+// return;
+// }
+
+// await resourceNotificationService.PublishUpdateAsync(yarpResource, s => s with
+// {
+// ResourceType = "Yarp",
+// State = "Starting"
+// });
+
+// // We don't want to proxy for yarp resources so force endpoints to not proxy
+// var bindings = yarpResource.Annotations.OfType().ToList();
+
+// foreach (var b in bindings)
+// {
+// b.IsProxied = false;
+// }
+// }
+
+// public async Task AfterEndpointsAllocatedAsync(DistributedApplicationModel appModel, CancellationToken cancellationToken = default)
+// {
+// if (executionContext.IsPublishMode)
+// {
+// return;
+// }
+
+// var yarpResource = appModel.Resources.OfType().SingleOrDefault();
+
+// if (yarpResource is null)
+// {
+// return;
+// }
+// var builder = WebApplication.CreateSlimBuilder(new WebApplicationOptions
+// {
+// ContentRootPath = hostEnvironment.ContentRootPath,
+// EnvironmentName = hostEnvironment.EnvironmentName,
+// WebRootPath = Path.Combine(hostEnvironment.ContentRootPath, "wwwroot")
+// });
+
+// builder.Logging.ClearProviders();
+
+// builder.Logging.AddProvider(new ResourceLoggerProvider(resourceLoggerService.GetLogger(yarpResource.Name)));
+
+// // Convert environment variables into configuration
+// if (yarpResource.TryGetEnvironmentVariables(out var envAnnotations))
+// {
+// var context = new EnvironmentCallbackContext(executionContext, cancellationToken: cancellationToken);
+
+// foreach (var cb in envAnnotations)
+// {
+// await cb.Callback(context);
+// }
+
+// var dict = new Dictionary();
+// foreach (var (k, v) in context.EnvironmentVariables)
+// {
+// var val = v switch
+// {
+// string s => s,
+// IValueProvider vp => await vp.GetValueAsync(context.CancellationToken),
+// _ => throw new NotSupportedException()
+// };
+
+// if (val is not null)
+// {
+// dict[k.Replace("__", ":")] = val;
+// }
+// }
+
+// builder.Configuration.AddInMemoryCollection(dict);
+// }
+
+// builder.Services.AddServiceDiscovery();
+
+// var proxyBuilder = builder.Services.AddReverseProxy();
+
+// if (yarpResource.RouteConfigs.Count > 0)
+// {
+// proxyBuilder.LoadFromMemory([.. yarpResource.RouteConfigs.Values], [.. yarpResource.ClusterConfigs.Values]);
+// }
+
+// if (yarpResource.ConfigurationSectionName is not null)
+// {
+// proxyBuilder.LoadFromConfig(builder.Configuration.GetSection(yarpResource.ConfigurationSectionName));
+// }
+
+// proxyBuilder.AddServiceDiscoveryDestinationResolver();
+
+// yarpResource.TryGetEndpoints(out var endpoints);
+// var defaultScheme = Environment.GetEnvironmentVariable("ASPNETCORE_URLS")?.Contains("https://") == true ? "https" : "http";
+// var needHttps = defaultScheme == "https" || endpoints?.Any(ep => ep.UriScheme == "https") == true;
+
+// if (needHttps)
+// {
+// builder.WebHost.UseKestrelHttpsConfiguration();
+// }
+
+// _app = builder.Build();
+
+// var urlToEndpointNameMap = new Dictionary();
+
+// if (endpoints is null)
+// {
+// var url = $"{defaultScheme}://127.0.0.1:0/";
+// _app.Urls.Add(url);
+// urlToEndpointNameMap[url] = "default";
+// }
+// else
+// {
+// foreach (var ep in endpoints)
+// {
+// var scheme = ep.UriScheme ?? defaultScheme;
+// needHttps = needHttps || scheme == "https";
+
+// var url = ep.Port switch
+// {
+// null => $"{scheme}://127.0.0.1:0/",
+// _ => $"{scheme}://localhost:{ep.Port}"
+// };
+
+// var uri = new Uri(url);
+// _app.Urls.Add(url);
+// urlToEndpointNameMap[uri.ToString()] = ep.Name;
+// }
+// }
+
+// _app.MapReverseProxy();
+
+// await _app.StartAsync(cancellationToken);
+
+// var addresses = _app.Services.GetRequiredService().Features.GetRequiredFeature().Addresses;
+
+// // Update the EndpointAnnotations with the allocated URLs from ASP.NET Core
+// foreach (var url in addresses)
+// {
+// if (urlToEndpointNameMap.TryGetValue(new Uri(url).ToString(), out var name)
+// || urlToEndpointNameMap.TryGetValue((new UriBuilder(url) { Port = 0 }).Uri.ToString(), out name))
+// {
+// var ep = endpoints?.FirstOrDefault(ep => ep.Name == name);
+// if (ep is not null)
+// {
+// var uri = new Uri(url);
+// var host = uri.Host is "127.0.0.1" or "[::1]" ? "localhost" : uri.Host;
+// ep.AllocatedEndpoint = new(ep, host, uri.Port);
+// }
+// }
+// }
+
+// await resourceNotificationService.PublishUpdateAsync(yarpResource, s => s with
+// {
+// State = "Running",
+// Urls = [.. endpoints?.Select(ep => new UrlSnapshot(ep.Name, ep.AllocatedEndpoint?.UriString ?? "", IsInternal: false))],
+// });
+// }
+
+// public ValueTask DisposeAsync()
+// {
+// return _app?.DisposeAsync() ?? default;
+// }
+
+// private class ResourceLoggerProvider(ILogger logger) : ILoggerProvider
+// {
+// public ILogger CreateLogger(string categoryName)
+// {
+// return new ResourceLogger(logger);
+// }
+
+// public void Dispose()
+// {
+// }
+
+// private class ResourceLogger(ILogger logger) : ILogger
+// {
+// public IDisposable? BeginScope(TState state) where TState : notnull
+// {
+// return logger.BeginScope(state);
+// }
+
+// public bool IsEnabled(LogLevel logLevel)
+// {
+// return logger.IsEnabled(logLevel);
+// }
+
+// public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter)
+// {
+// logger.Log(logLevel, eventId, state, exception, formatter);
+// }
+// }
+// }
+// }
\ No newline at end of file
diff --git a/src/Aspirant.Hosting.Yarp/YarpResourceExtensions.cs b/src/Aspirant.Hosting.Yarp/YarpResourceExtensions.cs
index efb3d49..ff7d381 100644
--- a/src/Aspirant.Hosting.Yarp/YarpResourceExtensions.cs
+++ b/src/Aspirant.Hosting.Yarp/YarpResourceExtensions.cs
@@ -1,5 +1,5 @@
+using Aspirant.Hosting;
using Aspire.Hosting.ApplicationModel;
-using Aspire.Hosting.Lifecycle;
namespace Aspire.Hosting;
@@ -17,48 +17,9 @@ public static class YarpResourceExtensions
///
public static IResourceBuilder AddYarp(this IDistributedApplicationBuilder builder, string name)
{
- var yarp = builder.Resources.OfType().SingleOrDefault();
-
- if (yarp is not null)
- {
- // You only need one yarp resource per application
- throw new InvalidOperationException("A yarp resource has already been added to this application");
- }
-
- builder.Services.TryAddLifecycleHook();
-
var resource = new YarpResource(name);
- return builder.AddResource(resource).ExcludeFromManifest();
-
- // REVIEW: YARP resource type?
- //.WithManifestPublishingCallback(context =>
- // {
- // context.Writer.WriteString("type", "yarp.v0");
- // context.Writer.WriteStartObject("routes");
- // // REVIEW: Make this less YARP specific
- // foreach (var r in resource.RouteConfigs.Values)
- // {
- // context.Writer.WriteStartObject(r.RouteId);
-
- // context.Writer.WriteStartObject("match");
- // context.Writer.WriteString("path", r.Match.Path);
-
- // if (r.Match.Hosts is not null)
- // {
- // context.Writer.WriteStartArray("hosts");
- // foreach (var h in r.Match.Hosts)
- // {
- // context.Writer.WriteStringValue(h);
- // }
- // context.Writer.WriteEndArray();
- // }
- // context.Writer.WriteEndObject();
- // context.Writer.WriteString("destination", r.ClusterId);
- // context.Writer.WriteEndObject();
- // }
- // context.Writer.WriteEndObject();
- // });
+ return builder.AddWebApplication(resource, excludeFromManifest: true);
}
///
@@ -73,3 +34,74 @@ public static IResourceBuilder LoadFromConfiguration(this IResourc
return builder;
}
}
+
+// ///
+// /// Extensions for the .
+// ///
+// public static class YarpResourceExtensions
+// {
+// ///
+// /// Adds a YARP resource to the application.
+// ///
+// /// The builder.
+// /// The name of the resource.
+// /// The builder.
+// ///
+// public static IResourceBuilder AddYarp(this IDistributedApplicationBuilder builder, string name)
+// {
+// var yarp = builder.Resources.OfType().SingleOrDefault();
+
+// if (yarp is not null)
+// {
+// // You only need one yarp resource per application
+// throw new InvalidOperationException("A yarp resource has already been added to this application");
+// }
+
+// builder.Services.TryAddLifecycleHook();
+
+// var resource = new YarpResource(name);
+// return builder.AddResource(resource).ExcludeFromManifest();
+
+// // REVIEW: YARP resource type?
+// //.WithManifestPublishingCallback(context =>
+// // {
+// // context.Writer.WriteString("type", "yarp.v0");
+
+// // context.Writer.WriteStartObject("routes");
+// // // REVIEW: Make this less YARP specific
+// // foreach (var r in resource.RouteConfigs.Values)
+// // {
+// // context.Writer.WriteStartObject(r.RouteId);
+
+// // context.Writer.WriteStartObject("match");
+// // context.Writer.WriteString("path", r.Match.Path);
+
+// // if (r.Match.Hosts is not null)
+// // {
+// // context.Writer.WriteStartArray("hosts");
+// // foreach (var h in r.Match.Hosts)
+// // {
+// // context.Writer.WriteStringValue(h);
+// // }
+// // context.Writer.WriteEndArray();
+// // }
+// // context.Writer.WriteEndObject();
+// // context.Writer.WriteString("destination", r.ClusterId);
+// // context.Writer.WriteEndObject();
+// // }
+// // context.Writer.WriteEndObject();
+// // });
+// }
+
+// ///
+// /// Loads the YARP configuration from the specified configuration section.
+// ///
+// /// The builder.
+// /// The configuration section name to load from.
+// /// The builder.
+// public static IResourceBuilder LoadFromConfiguration(this IResourceBuilder builder, string sectionName)
+// {
+// builder.Resource.ConfigurationSectionName = sectionName;
+// return builder;
+// }
+// }