File: AspireSiteContainer.cs
Web Access
Project: src\src\Aspire.Hosting.Azure.AppService\Aspire.Hosting.Azure.AppService.csproj (Aspire.Hosting.Azure.AppService)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using Azure.Provisioning.AppService;
using Azure.Provisioning.Expressions;
 
namespace Aspire.Hosting.Azure.AppService;
 
/// <summary>
/// A derived <see cref="global::Azure.Provisioning.AppService.SiteContainer"/> that supports adding the @onlyIfNotExists() decorator
/// to the generated Bicep resource statement.
/// </summary>
internal sealed class SiteContainer : global::Azure.Provisioning.AppService.SiteContainer
{
    private readonly bool _addOnlyIfNotExistsDecorator;
 
    /// <summary>
    /// Initializes a new instance of <see cref="SiteContainer"/>.
    /// </summary>
    /// <param name="bicepIdentifier">The Bicep identifier name of the resource.</param>
    /// <param name="addOnlyIfNotExistsDecorator">
    /// When <c>true</c>, adds the @onlyIfNotExists() decorator to the resource statement.
    /// </param>
    public SiteContainer(string bicepIdentifier, bool addOnlyIfNotExistsDecorator = false)
        : base(bicepIdentifier)
    {
        _addOnlyIfNotExistsDecorator = addOnlyIfNotExistsDecorator;
    }
 
    /// <inheritdoc />
    protected override IEnumerable<BicepStatement> Compile()
    {
        foreach (var statement in base.Compile())
        {
            if (_addOnlyIfNotExistsDecorator && statement is ResourceStatement resourceStatement)
            {
                // Add @onlyIfNotExists() decorator to the resource statement
                // Using FunctionCallExpression to generate "onlyIfNotExists()" with parentheses
                resourceStatement.Decorators.Add(
                    new DecoratorExpression(new FunctionCallExpression(new IdentifierExpression("onlyIfNotExists"))));
            }
 
            yield return statement;
        }
    }
}
 
/// <summary>
/// A derived <see cref="WebSite"/> that supports adding the @onlyIfNotExists() decorator
/// to the generated Bicep resource statement.
/// </summary>
internal sealed class AspireWebSite : WebSite
{
    private readonly bool _addOnlyIfNotExistsDecorator;
 
    /// <summary>
    /// Initializes a new instance of <see cref="AspireWebSite"/>.
    /// </summary>
    /// <param name="bicepIdentifier">The Bicep identifier name of the resource.</param>
    /// <param name="addOnlyIfNotExistsDecorator">
    /// When <c>true</c>, adds the @onlyIfNotExists() decorator to the resource statement.
    /// </param>
    public AspireWebSite(string bicepIdentifier, bool addOnlyIfNotExistsDecorator = false)
        : base(bicepIdentifier)
    {
        _addOnlyIfNotExistsDecorator = addOnlyIfNotExistsDecorator;
    }
 
    /// <inheritdoc />
    protected override IEnumerable<BicepStatement> Compile()
    {
        foreach (var statement in base.Compile())
        {
            if (_addOnlyIfNotExistsDecorator && statement is ResourceStatement resourceStatement)
            {
                // Add @onlyIfNotExists() decorator to the resource statement
                // Using FunctionCallExpression to generate "onlyIfNotExists()" with parentheses
                resourceStatement.Decorators.Add(
                    new DecoratorExpression(new FunctionCallExpression(new IdentifierExpression("onlyIfNotExists"))));
            }
 
            yield return statement;
        }
    }
}
 
/// <summary>
/// A derived <see cref="global::Azure.Provisioning.AppService.SiteNetworkConfig"/> that emits the required
/// singleton resource name.
/// </summary>
internal sealed class AspireSiteNetworkConfig : global::Azure.Provisioning.AppService.SiteNetworkConfig
{
    /// <summary>
    /// Initializes a new instance of <see cref="AspireSiteNetworkConfig"/>.
    /// </summary>
    /// <param name="bicepIdentifier">The Bicep identifier name of the resource.</param>
    public AspireSiteNetworkConfig(string bicepIdentifier)
        : base(bicepIdentifier)
    {
    }
 
    /// <inheritdoc />
    protected override IEnumerable<BicepStatement> Compile()
    {
        foreach (var statement in base.Compile())
        {
            if (statement is ResourceStatement resourceStatement &&
                resourceStatement.Body is ObjectExpression body &&
                !body.Properties.Any(static property => property.Name is "name"))
            {
                // Workaround for https://github.com/Azure/azure-sdk-for-net/issues/54629.
                // SiteNetworkConfig models its fixed "virtualNetwork" name as an output, so the
                // generated resource omits the required Bicep property. Bicep requires a name even
                // for this singleton child and otherwise reports BCP035. Emit the fixed App Service
                // child name explicitly until the Azure.Provisioning.AppService version consumed by
                // Aspire emits the default name as a resource property.
                var resourceWithName = new ResourceStatement(
                    resourceStatement.Name,
                    resourceStatement.Type,
                    new ObjectExpression(
                    [
                        new PropertyExpression("name", new StringLiteralExpression("virtualNetwork")),
                        .. body.Properties
                    ]))
                {
                    Existing = resourceStatement.Existing
                };
 
                foreach (var decorator in resourceStatement.Decorators)
                {
                    resourceWithName.Decorators.Add(decorator);
                }
 
                yield return resourceWithName;
                continue;
            }
 
            yield return statement;
        }
    }
}