-
Notifications
You must be signed in to change notification settings - Fork 543
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow specifying an address hostname for project/service binding #4319
Comments
What are you doing for certificates in this scenario? Are you manually installing a self-signed cert for |
@mitchdenny I'd have to say all of the above. Which isn't too much overhead compared to needing clone repos, get secrets, etc. That initial ceremony but could mostly be scripted. Manually doing the following.
|
At this point we probably aren't going to try to tackle this with Aspire but I'll leave this on the backlog to collect more feedback. I can see the utility of this but there are some inherent security risks of doing this. |
@mitchdenny I'm not completely understanding if the security risk part is really relevant. I think at this point the main thing I'm missing is just having the flexibility to insert an additional "aliased" endpoint link to render on the dashboard for the resource (and the verbosity to achieve that) to steer others who run the Aspire AppHost as configured. Otherwise I already have the behavior and flow working as I need it to. It's just that I have to communicate this in another way to others -- it's not intuitive to other devs off the shelf that it can be accessed in another way. I did have the hostname and port I'm binding to whitelisted in the external Okta tool (or whatever configurable blackbox that is outside of the purview of Aspire) |
Related to #1116 |
@DamianEdwards Thanks. Sorry I tried to find if there were any existing issues that were similar before posting. I'd say that #1116 is definitely on the same wavelength more than any other previous issues. In my case 1) I would want the same port number (and specified) to serve for all hostnames. And 2) for it to be recognized on the Dashboard so a person would easily see "here are all the possible tenants/brands served for this resource". My application won't even function if I try to access it on localhost or 127.0.0.1 because it essentially will not map to a valid configuration state. From some of what I have gathered service discovery seems to be something a bit more complex to solve for in general. For what it is worth, since I'm retrofitting existing applications into Aspire I'm basically crafting the environment variable for the service url configuration myself so I'm not really depedent on that at the moment. However my hope is to gradually migrate to adopt the Aspire conventions at some later time -- to remove most of the environment variable overrides. I could live with this syntax of accessing some building blocks like this as it is more concise over what I have working now. I'll continue to experiement to see if I can come up with a cleaner workaround in the meantime. AppHost.csvar myUI = builder.AddProject<Projects.MyUI>("myUI")
// 7085 port is required
.WithEndpoint(port: 7085, scheme: "https", name: "vanitya", hostName: "my.vanity-a.com")
.WithEndpoint(port: 7085, scheme: "https", name: "vanityb", hostName: "my.vanity-b.com")
.WithEndpoint(port: 7085, scheme: "https", name: "vanityc", hostName: "my.vanity-c.com")
// Maybe another overload
.WithEndpoint(port: 7085, scheme: "https", hosts: [
new() {Name = "vanitya", hostName: "my.vanity-a.com"},
new() {Name = "vanityb", hostName: "my.vanity-b.com"},
new() {Name = "vanityc", hostName: "my.vanity-c.com"},
]);
;
var myServiceApi = builder.AddProject<Projects.MyServiceApi>("myServiceApi")
// Non-standard service url injection not following Aspire conventions
.WithEnvironment("Brands__A__Url", myUI.GetEndpoint("vanitya").Url)
.WithEnvironment("Brands__A__Url", "https://my.vanitya.com" + myUI.GetEndpoint("https").Port) // an less consise alternative
.WithEnvironment("Brands__B__Url", myUI.GetEndpoint("vanitya").Url)
.WithEnvironment("Brands__C__Url", myUI.GetEndpoint("vanitya").Url); HttpClient Service Discovery
MyUI.csproj launchSettings.json{
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://*:{port doesn't matter to me}",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
}
} |
+1 & subscribed. I am also in the same boat. |
I think that this is a super specific scenario so we'd be wanting to collect a lot more evidence around the need before we race off to implementation. Supporting multi-tenant scenarios is challenging because there are so many different approaches and layers in which tenancy comes into play. |
One idle thought about this. My inclination would be to define a reverse proxy as a resource within the app model which is configured to respond on all the domains you want and then pass the host header through to the ASP.NET project. This is more inline with how this kind of tenanting mechanism works in production anyway. Then you could just have a single wildcard host entry for that endpoint. You'd probably need to handle the cert side of things yourself since we can only issue the standard ASP.NET Core dev cert at this point in time. |
@mitchdenny I believe I understand what you are conveying here. It does feel like a heavier handed approach which is additional complexity just for Aspire purposes but not required with the existing application when launched locally with launchProfile with normal Visual Studio or even IIS. The equivalent was very easy to achieve with Tye, any binding added would just render the scheme and hostname/port on the dashboard. If I'm not mistaken the reverse proxy appeoach still wouldn't help to address the primary "user friendliness" issue of allowing an uninitiated developer to easily discover the all the available hosted vanity urls by strictly relying on what is presented by the dashboard alone. How would the developer know which urls the reverse proxy resource supports? The current missing vanity url details of a resource would have to be part of some buried and lesser obvious means such as exploring internal documentation or app configuration of the reverse proxy or the multi-tenant app as part of an onboarding process which is what I'm wanting to avoid (but currently appears to be my only option). Ideally, I would like the experience for other new devs on the team to be turnkey without having to dig into docs. My actual use cases are driven by OIDC requirements and other multi-tenant/branded app behaviors that are already deployed in a production environment without the need for a reverse proxy. On here I've been relying on communicating similat usage scenarios that are contrived for simplification purposes but hopefully still accurate enough to communicate the desire. Onwards to a better workaroundAssuming no further work is done to make an easier or more convenient interface, at this point I'd say a workaround I'd really be interested in is how to influence what is rendered on the dashboard for all the endpoint links associated with a resource. That would do the trick. I'm just needing to inject a couple vanity urls to render for the resource. I was able to sort of achieve a new entry using But because I'm already using some of the hackiness around overriding I can live with this little hackiness and extra configuration verbosity in the app host if it makes other devs on the project's life much easier. At least I'd have recipe to apply that makes the resource bind correctly and also render all its appropriate links on the dashboard. |
+1 to the issue. This is currently blocking my environment. |
@tdhatcher take a look at the CustomResource playground:
It uses
|
Thanks @mitchdenny I will dig further into this to see what I can do with it. On vacation at the moment so not a laptop in sight for a couple weeks 😀. |
Didn't mean to close this yet. Mobile! |
This would also be useful for unix sockets, which we currently have no way of specifying the file path to the socket for an endpoint as well. |
@glennc as I was talking to him about this recently |
Are you planning to reopen? |
Another thing this could help with is running out of space in localhost cookies with Aspire. I now can't access Redis Commander, PG Admin, etc. because they throw HTTP 431 errors. Clearing cookies doesn't help because Aspire just puts them all back again on next run. Being able to use different hostnames for services and projects would solve this issue. This is similar to feature request #5508 I opened earlier. |
@hades200082 can you create a GitHub repo with a repro of the cookie issue you are seeing. This is the first I've seen of it. If you can reproduce it, then can you open a specific issue for that. |
Will this also let me NOT set any host? When you specify a port binding, by default Docker will not assign a host IP:
"HostConfig": {
"NetworkMode": "bridge",
"PortBindings": {
"8080/tcp": [
{
"HostIp": "",
"HostPort": "8000"
}
]
}, It doesn't look like there are any options to NOT set the HostIp here except to skip the var sample = builder.AddContainer("aspnetcore-sample", "mcr.microsoft.com/dotnet/samples", "aspnetapp")
//.WithEndpoint(8001, 8080, scheme: "http", isProxied: false, isExternal: true)
.WithContainerRuntimeArgs("-p", "8001:8080")
; If I use |
+1 for this. I'm working on an application that can be accessed from multiple subdomains, and requires a cookie to be shared across them. Shared cookies don't work on However I'm transitioning to Aspire, and I can't figure out a way to get the Aspire dashboard to show my actual launch url ( I don't really understand the security argument here. I just need the Aspire dashboard to show the |
In this post I provide a workaround for displaying URLs with custom domains/hosts in the dashboard. You still have to manage your hosts file and create your certificates if needed. https://anthonysimmon.com/dotnet-aspire-non-localhost-endpoints/ |
Before we can use aspire we need to be able to use hostnames. We have an identity server running locally, it uses b2c on azure to do the actual authentication. b2c expects the hostname of the identityserver as valid redirect, adding localhost:4042 to b2c is not an option for us. also we have vite running with hot module replacement that also needs a hostname so it works with both iis or aspire. And it works much easier in the browser when you have a recognisable hostname versus random portnumber. |
I was able to achieve a working PoC of what I needed with some inspiration from @asimmon and @mitchdenny mentions of the The code is just a basic happy path implementation. Haven't given much thought around what to do if there was a use case if one resource has a reference to another resource where these endpoints are added and how you would discover them cleanly. But it does work. AppHostvar permissionApi = builder.AddProject<Projects.Permissions_Api>("permissionsapi")
var productApi = builder.AddProject<Projects.Product_Api>("productapi")
.WithReference(userApi);
var userApi = builder.AddProject<Projects.User_Api>("userapi")
.WithReference(permissionsApi)
.WithReference(productApi);
var identityApi = builder.AddProject<Projects.Identity_Api>("identityapi")
.WithReference(permissionsApi)
.WithReference(userApi);
var authUI = builder.AddProject<Projects.AuthUI_Api>("authui")
.WithHostname(["auth.subsidiary-a.com", "auth.subsidiary-b.com", "auth.subsidiary-c.com"], excludeLocalhost: true)
.WithReference(identityApi);
var dashboardApi = builder.AddProject<Projects.Dashboard_Api>("dashboardapi")
.WithHostname(["dashboard.subsidiary-a.com", "dashboard.subsidiary-b.com", "dashboard.subsidiary-c.com"])
.WithReference(userApi)
.WithReference(productApi);
var dashboardClient = builder.AddNpmApp("dashboardclient", "../../Dashboard/src/AngularClient", "start")
.WithHostname(["dashboard.subsidiary-a.com", "dashboard.subsidiary-b.com", "dashboard.subsidiary-c.com"], excludeLocalhost: true)
.WithReference(dashboardApi)
.WithHttpEndpoint(env: "PORT")
.WithExternalHttpEndpoints();
var adminApi = builder.AddProject<Projects.Admin_Api>("adminapi")
.WithHostname("admin.company-parent.com")
.WithReference(userApi)
.WithReference(productApi)
.WithReference(permissionsApi);
var adminClient = builder.AddNpmApp("adminclient", "../../Admin/src/AngularClient", "start")
.WithHostname("admin.company-parent.com", excludeLocalhost: true)
.WithReference(adminApi)
.WithHttpEndpoint(env: "PORT")
.WithExternalHttpEndpoints();
builder.Build().Run(); Crude Implementationpublic static class ResourceHostnameExtensions
{
public static IResourceBuilder<T> WithHostname<T>(this IResourceBuilder<T> builder, string hostname, bool excludeLocalhost = false)
where T : IResourceWithEndpoints
{
builder.ApplicationBuilder.Services.TryAddLifecycleHook<ResourceHostnameLifecycleHook>();
return builder.WithAnnotation(new HostnameAnnotation([hostname], excludeLocalhost));
}
public static IResourceBuilder<T> WithHostname<T>(this IResourceBuilder<T> builder, List<string> hostnames, bool excludeLocalhost = false)
where T : IResourceWithEndpoints
{
builder.ApplicationBuilder.Services.TryAddLifecycleHook<ResourceHostnameLifecycleHook>();
return builder.WithAnnotation(new HostnameAnnotation(hostnames, excludeLocalhost));
}
private class HostnameAnnotation : IResourceAnnotation
{
public HostnameAnnotation([EndpointName] List<string> hostnames, bool excludeLocalhost)
{
ArgumentNullException.ThrowIfNull(hostnames);
if (!excludeLocalhost)
{
hostnames.Add("localhost");
}
this.Hostnames = hostnames;
}
public List<string> Hostnames { get; }
}
private class ResourceHostnameLifecycleHook(ResourceNotificationService notificationService) : IDistributedApplicationLifecycleHook
{
public Task BeforeStartAsync(DistributedApplicationModel appModel, CancellationToken cancellationToken = default)
{
_ = EnsureUrlsAsync();
return Task.CompletedTask;
}
private async Task EnsureUrlsAsync()
{
await foreach (var evt in notificationService.WatchAsync())
{
if (evt.Snapshot.State != KnownResourceStates.Running || evt.Resource is not IResourceWithEndpoints resource)
{
// By default, .NET Aspire only displays endpoints for running resources.
continue;
}
var hostnames = evt.Resource.Annotations.OfType<HostnameAnnotation>().SelectMany(a => a.Hostnames).Distinct();
var endpoints = evt.Resource.Annotations.OfType<EndpointAnnotation>();
var urlsToAdd = ImmutableArray.CreateBuilder<UrlSnapshot>();
foreach (var hostname in hostnames)
{
foreach (var endpoint in endpoints)
{
if (endpoint.AllocatedEndpoint is null)
{
continue;
}
var url = $"{endpoint.UriScheme}://{hostname}:{endpoint.AllocatedEndpoint.Port}";
if (!Uri.TryCreate(url, UriKind.Absolute, out _))
{
throw new ArgumentException($"'{url}' is not an absolute URL.", nameof(url));
}
var urlAlreadyAdded = evt.Snapshot.Urls.Any(x => string.Equals(x.Name, hostname, StringComparison.OrdinalIgnoreCase));
if (!urlAlreadyAdded)
{
urlsToAdd.Add(new UrlSnapshot(hostname, url, IsInternal: false));
}
}
}
if (urlsToAdd.Count > 0)
{
await notificationService.PublishUpdateAsync(evt.Resource, snapshot => snapshot with
{
Urls = urlsToAdd.ToImmutableArray()
});
}
}
}
}
} |
Could be cool to able to integrate with https://github.com/microsoft/dev-tunnels or at least build the building blocks for this so tools like Cloudflared could provide tunnels of their own useful for MAUI API Testing and webhooks from external services to talk back to local development. |
This comment has been minimized.
This comment has been minimized.
The community toolkit has an ngrok integration https://github.com/CommunityToolkit/Aspire/tree/main/src/CommunityToolkit.Aspire.Hosting.Ngrok. That might be of interest. |
@davidfowl @mitchdenny This issue I'm trying to solve (or feature enhancement) is related to not being able to provide a specific hostname/address alias to resource.
I have a couple use cases:
So it would be awesome to be able supply additional address bindings specifically for local hosting that ALSO display on the dashboard. (This was something I recall doing with Tye)

An existing
tye.yaml
A "kinda" workaround
I've been adapting an existing distributed application into the Aspire model. My goal has been to do this incrementally without being invasive or making modifications to the existing projects to prevent any refactor disruptions or coordination. I have been successful enough so far, so kudos on baking in that flexibility from the start!
The workaround I have found like below so far appears to allow a workable path but not ideal of course from the dashboard or verbosity.
The text was updated successfully, but these errors were encountered: