Background and Motivation
My project has an API that needs access to an external resource. This resource is not available outside of our production environments, so to test locally, we spin up a mock server. I have created a custom resource that takes in two arguments. One of them is a IResourceBuilder<IResource> that is used for the mock server. The second one is an ExternalServiceResource that is to be used when publishing the application. I have also written a new WithReference method that looks at my custom resource and provides the mock server's URLs to the caller when running locally. When publishing, it provides the ExternalServiceResource's URL.
In implementing my custom WithReference method, I referenced existing Aspire code. The existing code makes use of several extremely helpful utility methods and annotations. The utility methods could obviously be re-implemented, in user-code, however, the goal is to make the custom WithReference method behave as closely to native Aspire methods as possible. Re-implementing would cause duplicated code now, and outdated code later, as Aspire's public and private APIs evolve.
Here are some example methods and annotations:
EnvironmentVariableNameEncoder.Encode(string) public method contained in an internal class. Useful when implementing custom methods that create dynamic environment variable names based on string values.
ExternalServiceResource.UrlIsValidForExternalService(string, out Uri?, out string?) internal method. Useful when attempting to validate ParameterResources that are intended to resolve to a URI matching the format expected for an ExternalServiceResource
EndpointReferenceAnnotation internal annotation. This annotation provides an easy method of ensuring endpoints have not already been processed for a given resource.
I am sure there are other useful methods and annotations that could be useful for developers extending Aspire with their own resources. I'd like to see a more thorough review of the public and private APIs for candidates to make public.
Proposed API
- internal sealed class EndpointReferenceAnnotation(IResourceWithEndpoints resource) : IResourceAnnotation
+ public sealed class EndpointReferenceAnnotation(IResourceWithEndpoints resource) : IResourceAnnotation
/// <summary>
/// Provides helpers for producing environment variable friendly names.
/// </summary>
- internal static partial class EnvironmentVariableNameEncoder
+ public static partial class EnvironmentVariableNameEncoder
{
[GeneratedRegex("^[a-zA-Z_][a-zA-Z0-9_]*$", RegexOptions.CultureInvariant)]
private static partial Regex ValidNameRegex();
- internal static bool UrlIsValidForExternalService(string? url, [NotNullWhen(true)] out Uri? uri, [NotNullWhen(false)] out string? message)
+ public static bool UrlIsValidForExternalService(string? url, [NotNullWhen(true)] out Uri? uri, [NotNullWhen(false)] out string? message)
{
if (url is null || !Uri.TryCreate(url, UriKind.Absolute, out uri))
{
uri = null;
message = "The URL for the external service must be an absolute URI.";
return false;
}
Usage Examples
var environmentVariableName = EnvironmentVariableNameEncode.Encode(endpoint.Name);
if (ExternalServiceResource.UrlIsValidForExternalService(parameterResource.GetValueAsync(CancellationToken.None), out var uri, out var message) { /* ... */ }
// When adding an endpoint we get to see whether there is an EndpointReferenceAnnotation
// on the resource, if there is then it means we have already been here before and we can just
// skip this and note the endpoint that we want to apply to the environment in the future
// in a single pass. There is one EndpointReferenceAnnotation per endpoint source.
var endpointReferenceAnnotation = builder.Resource.Annotations
.OfType<EndpointReferenceAnnotation>()
.Where(sra => sra.Resource == resourceWithEndpoints)
.SingleOrDefault();
Alternative Designs
--
Risks
--
Background and Motivation
My project has an API that needs access to an external resource. This resource is not available outside of our production environments, so to test locally, we spin up a mock server. I have created a custom resource that takes in two arguments. One of them is a
IResourceBuilder<IResource>that is used for the mock server. The second one is anExternalServiceResourcethat is to be used when publishing the application. I have also written a newWithReferencemethod that looks at my custom resource and provides the mock server's URLs to the caller when running locally. When publishing, it provides theExternalServiceResource's URL.In implementing my custom
WithReferencemethod, I referenced existing Aspire code. The existing code makes use of several extremely helpful utility methods and annotations. The utility methods could obviously be re-implemented, in user-code, however, the goal is to make the customWithReferencemethod behave as closely to native Aspire methods as possible. Re-implementing would cause duplicated code now, and outdated code later, as Aspire's public and private APIs evolve.Here are some example methods and annotations:
EnvironmentVariableNameEncoder.Encode(string)public method contained in an internal class. Useful when implementing custom methods that create dynamic environment variable names based on string values.ExternalServiceResource.UrlIsValidForExternalService(string, out Uri?, out string?)internal method. Useful when attempting to validateParameterResources that are intended to resolve to a URI matching the format expected for anExternalServiceResourceEndpointReferenceAnnotationinternal annotation. This annotation provides an easy method of ensuring endpoints have not already been processed for a given resource.I am sure there are other useful methods and annotations that could be useful for developers extending Aspire with their own resources. I'd like to see a more thorough review of the public and private APIs for candidates to make public.
Proposed API
Usage Examples
Alternative Designs
--
Risks
--