File: AzureWebPubSubExtensions.cs
Web Access
Project: src\src\Aspire.Hosting.Azure.WebPubSub\Aspire.Hosting.Azure.WebPubSub.csproj (Aspire.Hosting.Azure.WebPubSub)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.Azure;
using Azure.Provisioning;
using Azure.Provisioning.Expressions;
using Azure.Provisioning.WebPubSub;
 
namespace Aspire.Hosting;
 
/// <summary>
/// Provides extension methods for adding the Azure Web PubSub resources to the application model.
/// </summary>
public static class AzureWebPubSubExtensions
{
    /// <summary>
    /// Adds an Azure Web PubSub resource to the application model.
    /// Change sku: WithParameter("sku", "Standard_S1")
    /// Change capacity: WithParameter("capacity", 2)
    /// </summary>
    /// <param name="builder">The <see cref="IDistributedApplicationBuilder"/>.</param>
    /// <param name="name">The name of the resource. This name will be used as the connection string name when referenced in a dependency.</param>
    /// <returns>A reference to the <see cref="IResourceBuilder{T}"/>.</returns>
    public static IResourceBuilder<AzureWebPubSubResource> AddAzureWebPubSub(this IDistributedApplicationBuilder builder, [ResourceName] string name)
    {
        builder.AddAzureProvisioning();
 
        var configureInfrastructure = (AzureResourceInfrastructure infrastructure) =>
        {
            // Supported values are Free_F1 Standard_S1 Premium_P1
            var skuParameter = new ProvisioningParameter("sku", typeof(string))
            {
                Value = "Free_F1"
            };
            infrastructure.Add(skuParameter);
 
            // Supported values are 1 2 5 10 20 50 100
            var capacityParameter = new ProvisioningParameter("capacity", typeof(int))
            {
                Value = new BicepValue<int>(1)
            };
            infrastructure.Add(capacityParameter);
 
            var service = new WebPubSubService(infrastructure.AspireResource.GetBicepIdentifier())
            {
                Sku = new BillingInfoSku()
                {
                    Name = skuParameter,
                    Capacity = capacityParameter
                },
                Tags = { { "aspire-resource-name", infrastructure.AspireResource.Name } }
            };
            infrastructure.Add(service);
 
            infrastructure.Add(new ProvisioningOutput("endpoint", typeof(string)) { Value = BicepFunction.Interpolate($"https://{service.HostName}") });
 
            var principalTypeParameter = new ProvisioningParameter(AzureBicepResource.KnownParameters.PrincipalType, typeof(string));
            infrastructure.Add(principalTypeParameter);
            var principalIdParameter = new ProvisioningParameter(AzureBicepResource.KnownParameters.PrincipalId, typeof(string));
            infrastructure.Add(principalIdParameter);
 
            infrastructure.Add(service.CreateRoleAssignment(WebPubSubBuiltInRole.WebPubSubServiceOwner, principalTypeParameter, principalIdParameter));
 
            var resource = (AzureWebPubSubResource)infrastructure.AspireResource;
            foreach (var setting in resource.Hubs)
            {
                var hubName = setting.Key;
                
                var hubBuilder = setting.Value;
                var hubResource = hubBuilder;
                var hub = new WebPubSubHub(Infrastructure.NormalizeBicepIdentifier(hubResource.Name))
                {
                    Name = setting.Key,
                    Parent = service,
                    Properties = new WebPubSubHubProperties()
                };
 
                var hubProperties = hub.Properties;
 
                // invoke the configure from AddEventHandler
                for (var i = 0; i < hubResource.EventHandlers.Count; i++)
                {
                    var (url, userEvents, systemEvents, auth) = hubResource.EventHandlers[i];
                    var urlExpression = url;
 
                    BicepValue<string> urlParameter;
                    if (urlExpression.ManifestExpressions.Count == 0)
                    {
                        // when urlExpression is literal string, simply add
                        urlParameter = urlExpression.Format;
                    }
                    else
                    {
                        // otherwise add parameter to the construct
                        var parameter = new ProvisioningParameter($"{hubName}_url_{i}", typeof(string));
                        infrastructure.Add(parameter);
                        resource.Parameters[parameter.BicepIdentifier] = urlExpression;
                        urlParameter = parameter;
                    }
 
                    var eventHandler = GetWebPubSubEventHandler(urlParameter, userEvents, systemEvents, auth);
 
                    hubProperties.EventHandlers.Add(eventHandler);
                }
 
                // add to construct
                infrastructure.Add(hub);
            }
        };
 
        var resource = new AzureWebPubSubResource(name, configureInfrastructure);
        return builder.AddResource(resource)
                      .WithManifestPublishingCallback(resource.WriteToManifest);
    }
 
    /// <summary>
    /// Add hub settings
    /// </summary>
    /// <param name="builder">The builder for the distributed application.</param>
    /// <param name="hubName">The hub name. Hub name is case-insensitive.</param>
    /// <returns></returns>
    public static IResourceBuilder<AzureWebPubSubHubResource> AddHub(this IResourceBuilder<AzureWebPubSubResource> builder, [ResourceName] string hubName)
    {
        AzureWebPubSubHubResource? hubResource;
        if (!builder.Resource.Hubs.TryGetValue(hubName, out hubResource))
        {
            hubResource = new AzureWebPubSubHubResource(hubName, builder.Resource);
            builder.Resource.Hubs[hubName] = hubResource;
        }
        var hubBuilder = builder.ApplicationBuilder.CreateResourceBuilder(hubResource);
        return hubBuilder;
    }
 
    /// <summary>
    /// Add event handler setting with expression
    /// </summary>
    /// <param name="builder">The builder for a Web PubSub hub.</param>
    /// <param name="urlTemplateExpression">The expression to evaluate the URL template configured for the event handler.</param>
    /// <param name="userEventPattern">The user event pattern for the event handler.</param>
    /// <param name="systemEvents">The system events for the event handler.</param>
    /// <param name="authSettings">The auth settings configured for the event handler.</param>
    /// <returns></returns>
#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters
    public static IResourceBuilder<AzureWebPubSubHubResource> AddEventHandler(this IResourceBuilder<AzureWebPubSubHubResource> builder,
#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters
        ReferenceExpression.ExpressionInterpolatedStringHandler urlTemplateExpression, string userEventPattern = "*", string[]? systemEvents = null, UpstreamAuthSettings? authSettings = null)
    {
        var urlExpression = ReferenceExpression.Create(urlTemplateExpression);
 
        return builder.AddEventHandler(urlExpression, userEventPattern, systemEvents, authSettings);
    }
 
    /// <summary>
    /// Add event handler setting with expression
    /// </summary>
    /// <param name="builder">The builder for a Web PubSub hub.</param>
    /// <param name="urlExpression">The expression to evaluate the URL template configured for the event handler.</param>
    /// <param name="userEventPattern">The user event pattern for the event handler.</param>
    /// <param name="systemEvents">The system events for the event handler.</param>
    /// <param name="authSettings">The auth settings configured for the event handler.</param>
    /// <returns></returns>
#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters
    public static IResourceBuilder<AzureWebPubSubHubResource> AddEventHandler(this IResourceBuilder<AzureWebPubSubHubResource> builder,
#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters
        ReferenceExpression urlExpression, string userEventPattern = "*", string[]? systemEvents = null, UpstreamAuthSettings? authSettings = null)
    {
        builder.Resource.EventHandlers.Add((urlExpression, userEventPattern, systemEvents, authSettings));
        return builder;
    }
 
    private static WebPubSubEventHandler GetWebPubSubEventHandler(BicepValue<string> urlValue, string userEventPattern, string[]? systemEvents, UpstreamAuthSettings? authSettings)
    {
        var handler = new WebPubSubEventHandler
        {
            UrlTemplate = urlValue,
            UserEventPattern = userEventPattern,
        };
 
        if (systemEvents != null)
        {
            handler.SystemEvents = [..systemEvents];
        }
 
        if (authSettings != null)
        {
            handler.Auth = authSettings;
        }
        return handler;
    }
}