Aspire
Add Groundhog to your AppHost
Install the Aspire package, give one of your resources a public address, and pass that address to the apps that need it.
Install the package
#You need .NET 10, Aspire 13.6.1 or later, and a tunnel with its connection key. The examples on this page use a tunnel named my-tunnel and a project resource named web. Add the package to your AppHost project:
dotnet add package Groundhog.Aspire.Hosting --version 0.4.0-preview.7The client runs inside the AppHost, so there is no tool to install and no extra process.
Add a tunnel with WithGroundhogTunnel
#var builder = DistributedApplication.CreateBuilder(args);
var web = builder.AddProject<Projects.Web>("web")
.WithGroundhogTunnel("my-tunnel");
builder.Build().Run();Replace my-tunnel with your tunnel’s name, without a URL or domain. This adds 2 resources to the Aspire dashboard:
- web-tunnel: the tunnel, named after the resource it forwards to. Visitors reach
webathttps://my-tunnel.groundhog.unifinity.com. - groundhog-my-tunnel-key: a secret parameter for
my-tunnel’s connection key. Enter the key when the dashboard asks for it and turn on Save to user secrets.
Choose the endpoint to forward to
#The tunnel forwards to the resource’s https endpoint, or to its http endpoint when it has no https one. Pass endpointName to choose another:
var web = builder.AddProject<Projects.Web>("web")
.WithGroundhogTunnel("my-tunnel", endpointName: "http");Building the AppHost fails with a message naming the resource when the endpoint does not exist. An https endpoint needs a certificate this computer trusts; run dotnet dev-certs https --trust for the ASP.NET Core development certificate.
Give your app its public address
#The web project runs on your computer and does not know its public address on its own. It needs that address whenever it hands a URL to someone outside your computer: registering a webhook with a third party, sending an OAuth sign-in redirect URI, putting a link in an email, or listing the host a dev server such as Vite accepts. Requests that arrive through the tunnel carry the public Host header, but at startup, before any request, web has nothing to read it from.
Use configure to pass the tunnel’s address to web as environment variables:
var web = builder.AddProject<Projects.Web>("web");
web.WithGroundhogTunnel("my-tunnel", configure: webTunnel =>
{
web.WithEnvironment("PublicOrigin", webTunnel.Resource.PublicAddress)
.WithEnvironment("PublicHost", webTunnel.Resource.PublicHost);
});| Value | For my-tunnel | Use it for |
|---|---|---|
| webTunnel.Resource.PublicAddress | https://my-tunnel.groundhog.unifinity.com | Public origins, webhook and sign-in callback addresses |
| webTunnel.Resource.PublicHost | my-tunnel.groundhog.unifinity.com | A dev server’s allowed hosts |
The value is filled in when web starts: the address the service announces, or after 5 seconds the address the tunnel normally has. The web project starts either way.
In web, read the setting and build the addresses you hand out. This builds the webhook callback address web registers with a third party:
var publicOrigin = builder.Configuration["PublicOrigin"];
var webhookCallback = new Uri(new Uri(publicOrigin!), "/webhooks");publicOrigin is https://my-tunnel.groundhog.unifinity.com and webhookCallback is https://my-tunnel.groundhog.unifinity.com/webhooks.
If web only needs its address while handling a request, you can skip this: read the request’s Host header instead.
Get the tunnel with GetGroundhogTunnel
#WithGroundhogTunnel returns web, so you can keep chaining. To use web-tunnel later in the AppHost, ask web for it:
var web = builder.AddProject<Projects.Web>("web")
.WithGroundhogTunnel("my-tunnel");
var webTunnel = web.GetGroundhogTunnel();
web.WithEnvironment("PublicOrigin", webTunnel.Resource.PublicAddress);webTunnel is the builder for the web-tunnel resource, and webTunnel.Resource.PublicAddress gives web the value https://my-tunnel.groundhog.unifinity.com as PublicOrigin.
GetGroundhogTunnel needs client 0.4.0-preview.7 or later. It fails when no tunnel, or more than one, forwards to the resource.
Add a tunnel with AddGroundhogTunnel
#AddGroundhogTunnel adds the same tunnel when you want to choose the resource and key parameter names yourself, or read the tunnel name from a parameter:
// tunnelKey holds my-tunnel's connection key. secret: true makes it a secret parameter:
// Aspire masks it in the dashboard and reads it from Parameters:groundhog-tunnel-key in user secrets.
var tunnelKey = builder.AddParameter("groundhog-tunnel-key", secret: true);
var web = builder.AddProject<Projects.Web>("web");
var webTunnel = builder.AddGroundhogTunnel("web-tunnel", "my-tunnel", tunnelKey)
.WithReference(web.GetEndpoint("https"));AddGroundhogTunnel takes 3 values. Only the second and third come from Groundhog:
| Parameter | In this example | What it is | Where it comes from |
|---|---|---|---|
| name | "web-tunnel" | The tunnel’s resource name in the AppHost. It is the row in the Aspire dashboard; Groundhog never sees it. | You choose it. |
| tunnelName | "my-tunnel" | The tunnel’s name in Groundhog. It becomes the public host, my-tunnel.groundhog.unifinity.com. | The name you gave the tunnel when you created it in the Groundhog dashboard. It can also be a parameter. |
| key | tunnelKey | The tunnel’s connection key, as a secret parameter. | Copy key, when you create the tunnel in the Groundhog dashboard. |
webTunnel is the same web-tunnel resource as before, forwarding https://my-tunnel.groundhog.unifinity.com to web’s https endpoint.
Aspire now asks for groundhog-tunnel-key instead of groundhog-my-tunnel-key. The key must be a secret parameter; anything else is refused when the AppHost is built. It is handed to the client in memory and never appears on a command line, in the dashboard or in a log.
Make a resource wait for the tunnel
#Nothing waits for the tunnel by default, so an unreachable service, a wrong key or no internet never stops your app from starting locally. If a resource really cannot start without the tunnel, for example a worker that registers webhooks at startup, say so:
var web = builder.AddProject<Projects.Web>("web")
.WithGroundhogTunnel("my-tunnel");
var worker = builder.AddProject<Projects.Worker>("worker")
.WaitFor(web.GetGroundhogTunnel());Deploy an AppHost that has a tunnel
#A tunnel is for local development, and a deployed app has its own public address. In publish mode the tunnel never starts, and the deployment manifest leaves out the tunnel, its parameters and every environment variable that uses its address. The same AppHost code works for both:
var web = builder.AddProject<Projects.Web>("web");
web.WithGroundhogTunnel("my-tunnel", configure: webTunnel =>
web.WithEnvironment("PublicOrigin", webTunnel.Resource.PublicAddress));When the AppHost runs locally, web gets PublicOrigin set to https://my-tunnel.groundhog.unifinity.com. In the published manifest, web has no PublicOrigin, so set it in the deployment’s own configuration.
Leaving these variables out of the manifest needs client 0.4.0-preview.7 or later. Command-line arguments are not left out: add an argument that uses the address (with WithArgs) only when builder.ExecutionContext.IsRunMode is true.
For request logging, visitor access rules and every option, see the Aspire integration reference.