-
Notifications
You must be signed in to change notification settings - Fork 656
Closed
Copy link
Labels
area-integrationsIssues pertaining to Aspire Integrations packagesIssues pertaining to Aspire Integrations packages
Milestone
Description
Description:
Configuring YARP in Aspire via code is currently verbose and unintuitive. To address this, I propose a new approach based on #9766, introducing dedicated resources for routing.
Proposed Solution:
- Introduce new Route and Destination resources (aligned with YARP’s terminology, where "Destination" maps to "Cluster").
- Configure these resources via fluent methods, similar to how ports are exposed on a project.
- All configuration will be done via extension methods that modify the underlying RouteConfig or ClusterConfig objects, ensuring a consistent and expressive API surface.
- This enables a more natural and composable way to define reverse proxy behavior directly in code, without relying on JSON blobs.
YarpRoute
public class YarpRoute(IResourceBuilder<YarpResource> parent)
{
internal IResourceBuilder<YarpResource> ParentBuilder { get; } = parent;
internal RouteConfig RouteConfig { get; private set; } = new RouteConfig
{
[...]
};
internal void Configure(Func<RouteConfig, RouteConfig> configure)
{
RouteConfig = configure(RouteConfig);
}
}
YarpDestination
/// <summary>
/// Represents a destination for YARP routes
/// </summary>
public class YarpDestination(IResourceBuilder<YarpResource> parent, EndpointReference endpoint)
{
internal IResourceBuilder<YarpResource> ParentBuilder { get; } = parent;
internal ClusterConfig ClusterConfig { get; private set; } = new()
{
[...]
};
internal void Configure(Func<ClusterConfig, ClusterConfig> configure)
{
ClusterConfig = configure(ClusterConfig);
}
}
Extensions
All configuration will be done with extensions. For example, a subset of the extensions for YarpRoute
could be:
/// <summary>
/// Provides extension methods for configuring a YARP destination
/// </summary>
public static class YarpRouteExtensions
{
[...]
/// <summary>
/// Only match requests that use these optional HTTP methods. E.g. GET, POST.
/// </summary>
public static YarpRoute WithMatchMethods(this YarpRoute route, params string[] methods)
{
route.ConfigureMatch(match => match with { Methods = methods });
return route;
}
/// <summary>
/// Only match requests that contain all of these headers.
/// </summary>
public static YarpRoute WithMatchHeaders(this YarpRoute route, params RouteHeader[] headers)
{
route.ConfigureMatch(match => match with { Headers = headers.ToList() });
return route;
}
[...]
/// <summary>
/// Set the Metadata of the destination
/// </summary>
public static YarpRoute WithMetadata(this YarpRoute route, IReadOnlyDictionary<string, string>? metadata)
{
route.Configure(r => r with { Metadata = metadata });
return route;
}
[...]
}
Usage Examples
Basic Example
In this scenario, the developer only interacts with the Route resource and passes a reference to the endpoint being proxied:
var yarp = builder.AddYarp("apigateway");
// catalog
yarp.AddRoute("/catalog/{**catch-all}")
.WithTransformPathRemovePrefix("/catalog")
.WithReference(catalogService.GetEndpoint("http"));
// basket
yarp.AddRoute("/basket/{**catch-all}")
.WithTransformPathRemovePrefix("/basket")
.WithReference(basketService.GetEndpoint("http"));
Advanced Example
This approach allows you to explicitly create a reusable destination using AddDestination(...)
, reference it from multiple routes, and customize its behavior using WithForwarderRequestConfig(...)
:
var yarp = builder.AddYarp("apigateway");
var backend = yarp.AddDestination(backendService.GetEndpoint("http"))
.WithForwarderRequestConfig(new ForwarderRequestConfig
{
ActivityTimeout = TimeSpan.FromSeconds(30),
Version = HttpVersion.Version20
});
yarp.AddRoute("/somepath/{**catch-all}")
.WithMatchMethods("GET", "HEAD")
.WithTransformPathRemovePrefix("/somepath")
.WithReference(backend);
yarp.AddRoute("/anotherpath/{**catch-all}")
.WithMatchMethods("POST")
.WithMaxRequestBodySize(1000)
.WithTransformPathRemovePrefix("/anotherpath")
.WithTransformRequestHeader("x-my-header", "custom-value")
.WithReference(backend);
Benefits:
- Aligns with Aspire’s developer-friendly philosophy.
- Reduces boilerplate and improves discoverability.
- Makes routing logic easier to test, reuse, and extend.
- Enables fine-grained control over forwarding behavior and destination reuse.
- Future-proof design: Extending this model to support middleware configuration (e.g., authentication, rate limiting, logging) will be straightforward and will not introduce breaking changes, thanks to the composable and fluent API structure.
DeagleGross
Metadata
Metadata
Assignees
Labels
area-integrationsIssues pertaining to Aspire Integrations packagesIssues pertaining to Aspire Integrations packages