Skip to content

Instantly share code, notes, and snippets.

@afscrome
Last active January 14, 2026 23:13
Show Gist options
  • Select an option

  • Save afscrome/9edc1fec9b400e05efe6ae45a958b9f9 to your computer and use it in GitHub Desktop.

Select an option

Save afscrome/9edc1fec9b400e05efe6ae45a958b9f9 to your computer and use it in GitHub Desktop.
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();
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