Last active
January 14, 2026 23:13
-
-
Save afscrome/9edc1fec9b400e05efe6ae45a958b9f9 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| var builder = DistributedApplication.CreateBuilder(); | |
| var webServer = builder.AddInMemoryWebserver("test"); | |
| var exe = builder.AddExecutable("pwsh", "pwsh", ".") | |
| .WithEnvironment("url", webServer.GetEndpoint("https")) | |
| .WithArgs("-Command", "Write-Host $env:url; Invoke-RestMethod $env:url") | |
| ; | |
| var container = builder.AddContainer("curl", "mcr.microsoft.com/dotnet/sdk") | |
| .WithEnvironment("url", webServer.GetEndpoint("https")) | |
| .WithEntrypoint("sh") | |
| .WithArgs("-c", "echo $url; curl $url"); | |
| builder.Build().Run(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| public class InMemoryWebserverResource([ResourceName] string name) : Resource(name), | |
| IResourceWithArgs, IResourceWithEndpoints, IResourceWithEnvironment, IResourceWithWaitSupport | |
| { | |
| } | |
| // Semi based on https://github.com/dotnet/aspire/blob/main/src/Aspire.Hosting/Dashboard/DashboardServiceHost.cs | |
| public static class InMemoryWebserverResourceExtensions | |
| { | |
| public static IResourceBuilder<InMemoryWebserverResource> AddInMemoryWebserver(this IDistributedApplicationBuilder builder, [ResourceName] string name, object? callback = null) | |
| { | |
| var resource = new InMemoryWebserverResource(name); | |
| return builder.AddResource(resource) | |
| .WithIconName("WebEnvironment") | |
| .WithInitialState(new CustomResourceSnapshot // Aspire type for custom resource state. | |
| { | |
| ResourceType = "InMemoryWebServer", | |
| CreationTimeStamp = DateTime.UtcNow, | |
| State = KnownResourceStates.NotStarted, | |
| Properties = [] | |
| }) | |
| .OnInitializeResource(Initialise) | |
| .WithHttpsEndpoint() | |
| ; | |
| async Task Initialise(InMemoryWebserverResource resource, InitializeResourceEvent evt, CancellationToken ct) | |
| { | |
| var logger = evt.Services.GetRequiredService<ResourceLoggerService>() | |
| .GetLogger(resource); | |
| #pragma warning disable ASPIRECERTIFICATES001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. | |
| var developerCertificateService = evt.Services.GetRequiredService<IDeveloperCertificateService>(); | |
| #pragma warning restore ASPIRECERTIFICATES001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. | |
| var endpoint = resource.GetEndpoint("https"); | |
| var endpointAnnotation = endpoint.EndpointAnnotation; | |
| // Publish BeforeStart event. This will block if Waits have been configured | |
| await evt.Eventing.PublishAsync(new BeforeResourceStartedEvent(resource, evt.Services), ct); | |
| await evt.Notifications.PublishUpdateAsync(resource, s => s with | |
| { | |
| StartTimeStamp = DateTime.UtcNow, | |
| State = KnownResourceStates.Starting | |
| }); | |
| //TODO: dispose properly, add start /stop commands | |
| WebApplication app; | |
| try | |
| { | |
| var builder = WebApplication.CreateSlimBuilder(); | |
| await ConfigureUrls(builder, endpointAnnotation, ct); | |
| builder.Services.AddSingleton(evt.Services.GetRequiredService<ILoggerFactory>()); | |
| app = builder.Build(); | |
| app.MapGet("/", () => "Service Discovery is running"); | |
| await app.StartAsync(ct); | |
| AllocateEndpoint(app, endpointAnnotation, evt.Services); | |
| await evt.Notifications.PublishUpdateAsync(resource, x => x with | |
| { | |
| State = KnownResourceStates.Running, | |
| StartTimeStamp = DateTime.UtcNow, | |
| }); | |
| } | |
| catch | |
| { | |
| throw; | |
| } | |
| int exitCode = 0; | |
| try | |
| { | |
| await app.WaitForShutdownAsync(ct); | |
| } | |
| catch | |
| { | |
| exitCode = 1; | |
| throw; | |
| } | |
| finally | |
| { | |
| await evt.Notifications.PublishUpdateAsync(resource, x => x with | |
| { | |
| State = KnownResourceStates.Finished, | |
| ExitCode = exitCode, | |
| StopTimeStamp = DateTime.UtcNow | |
| }); | |
| } | |
| } | |
| static void AllocateEndpoint(WebApplication app, EndpointAnnotation endpointAnnotation, IServiceProvider services) | |
| { | |
| var server = app.Services.GetRequiredService<IServer>(); | |
| var addressFeature = server.Features.GetRequiredFeature<IServerAddressesFeature>(); | |
| var actualAddress = addressFeature.Addresses.First(); | |
| var actualUri = new Uri(actualAddress); | |
| var hostEndpoint = new AllocatedEndpoint(endpointAnnotation, actualUri.Host, actualUri.Port, EndpointBindingMode.SingleAddress, networkID: KnownNetworkIdentifiers.LocalhostNetwork); | |
| endpointAnnotation.AllocatedEndpointSnapshot.SetValue(hostEndpoint); | |
| var containerHostName = GetContainerHostName(services); | |
| var containerAllocatedEndpoint = new AllocatedEndpoint(endpointAnnotation, containerHostName, actualUri.Port, EndpointBindingMode.SingleAddress, networkID: KnownNetworkIdentifiers.DefaultAspireContainerNetwork); | |
| var snapshot = new ValueSnapshot<AllocatedEndpoint>(); | |
| snapshot.SetValue(containerAllocatedEndpoint); | |
| endpointAnnotation.AllAllocatedEndpoints.TryAdd(KnownNetworkIdentifiers.DefaultAspireContainerNetwork, snapshot); | |
| // This is ugly, but the container name is derrived from several possible options, some of which are non public (_dcpInfo) | |
| // so I can't even replicate the checks against IConfiguration myself | |
| static string GetContainerHostName(IServiceProvider services) | |
| { | |
| var dcpExecutorType = Type.GetType("Aspire.Hosting.Dcp.IDcpExecutor, Aspire.Hosting") | |
| ?? throw new InvalidOperationException("Failed to get DcpExecutor"); | |
| var dcpExecutor = services.GetRequiredService(dcpExecutorType); | |
| var hostNameMethod = dcpExecutor.GetType().GetMethod("get_ContainerHostName", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic) | |
| ?? throw new InvalidOperationException("Failed to get ContainerHostName from DcpExecutor"); | |
| return (string)hostNameMethod.Invoke(dcpExecutor, [])!; | |
| } | |
| } | |
| async Task ConfigureUrls(WebApplicationBuilder builder, EndpointAnnotation endpointAnnotation, CancellationToken ct) | |
| { | |
| builder.Configuration["ASPNETCORE_PREFERHOSTINGURLS"] = "true"; | |
| var targetAddress = endpointAnnotation.TargetHost; | |
| var dnsEntry = await Dns.GetHostEntryAsync(endpointAnnotation.TargetHost, ct); | |
| //if entry is localhost | |
| if (dnsEntry.AddressList.Any(x => x.Equals(IPAddress.Loopback))) | |
| { | |
| targetAddress = IPAddress.Loopback.ToString(); | |
| } | |
| else if (dnsEntry.AddressList.Any(x => x.Equals(IPAddress.IPv6Loopback))) | |
| { | |
| targetAddress = IPAddress.IPv6Loopback.ToString(); | |
| } | |
| var url = new UriBuilder | |
| { | |
| Scheme = endpointAnnotation.UriScheme, | |
| Host = targetAddress, | |
| Port = endpointAnnotation.TargetPort ?? 0 | |
| }.ToString(); | |
| builder.WebHost.UseUrls(url); | |
| if (endpointAnnotation.UriScheme == "https") | |
| { | |
| builder.WebHost.UseKestrelHttpsConfiguration(); | |
| } | |
| } | |
| } | |
| } | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment