Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,12 @@ public static IResourceBuilder<AzureAppServiceEnvironmentResource> AddAzureAppSe
},
Kind = "Linux",
IsReserved = true,
// Enable per-site scaling so each app service can scale independently
IsPerSiteScaling = true
// Enable perSiteScaling or automatic scaling so each app service can scale independently
// Default is automatic scaling where Scaling is managed by platform based on HTTP traffic patterns
IsPerSiteScaling = !resource.EnableAutomaticScaling,
IsElasticScaleEnabled = resource.EnableAutomaticScaling,
// Capping the automatic scaling limit to 10 as per best practices
MaximumElasticWorkerCount = 10
};

infra.Add(plan);
Expand Down Expand Up @@ -170,4 +174,16 @@ public static IResourceBuilder<AzureAppServiceEnvironmentResource> WithDashboard
builder.Resource.EnableDashboard = enable;
return builder;
}

/// <summary>
/// Configures whether automatic scaling should be enabled for the app services in Azure App Service environment.
/// </summary>
/// <param name="builder">The AzureAppServiceEnvironmentResource to configure.</param>
Comment thread
ShilpiRach marked this conversation as resolved.
/// <param name="enable">Whether to enable automatic scaling. Default is true.</param>
/// <returns><see cref="IResourceBuilder{T}"/></returns>
Comment on lines +235 to +240

Copilot AI Oct 23, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The XML documentation is incomplete for a public API. According to Aspire's documentation standards, public APIs require comprehensive documentation including <remarks> and <example> sections. Add a <remarks> section explaining when/why to use this method and an <example> section showing practical usage with code.

Copilot generated this review using guidance from repository custom instructions.

Copilot AI Oct 23, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The return value documentation should follow the standard pattern for fluent APIs: "A reference to the <see cref="IResourceBuilder{T}"/> for chaining additional configuration."

Copilot generated this review using guidance from repository custom instructions.
public static IResourceBuilder<AzureAppServiceEnvironmentResource> WithAutomaticScaling(this IResourceBuilder<AzureAppServiceEnvironmentResource> builder, bool enable = true)
{
builder.Resource.EnableAutomaticScaling = enable;
return builder;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ public class AzureAppServiceEnvironmentResource(string name, Action<AzureResourc
/// </summary>
internal bool EnableDashboard { get; set; } = true;

/// <summary>
/// Enables or disables automatic scaling for the App Service Plan.
/// Default is true.
/// </summary>
Comment thread
ShilpiRach marked this conversation as resolved.
internal bool EnableAutomaticScaling { get; set; } = true;

/// <summary>
/// Gets the name of the App Service Plan.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,10 @@ public static WebSite AddDashboard(AzureResourceInfrastructure infra,
UseManagedIdentityCreds = true,
IsHttp20Enabled = true,
Http20ProxyFlag = 1,
// Setting NumberOfWorkers to 1 to ensure dashboard runs on 1 instance
// Setting instance count to 1 to ensure dashboard runs on 1 instance
NumberOfWorkers = 1,
FunctionAppScaleLimit = 1,
ElasticWebAppScaleLimit = 1,
// IsAlwaysOn set to true ensures the app is always running
IsAlwaysOn = true,
AppSettings = []
Expand Down
21 changes: 21 additions & 0 deletions tests/Aspire.Hosting.Azure.Tests/AzureAppServiceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,27 @@ await Verify(manifest.ToString(), "json")
.AppendContentAsFile(bicep, "bicep");
}

[Fact]
public async Task AddAppServiceToEnvironmentWithoutAutoScale()
{
var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);

builder.AddAzureAppServiceEnvironment("env").WithAutomaticScaling(false);

using var app = builder.Build();

await ExecuteBeforeStartHooksAsync(app, default);

var model = app.Services.GetRequiredService<DistributedApplicationModel>();

var environment = Assert.Single(model.Resources.OfType<AzureAppServiceEnvironmentResource>());

var (manifest, bicep) = await GetManifestWithBicep(environment);

await Verify(manifest.ToString(), "json")
.AppendContentAsFile(bicep, "bicep");
}

[Fact]
public async Task AddAppServiceWithArgs()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,10 @@ resource env_asplan 'Microsoft.Web/serverfarms@2024-11-01' = {
name: take('envasplan-${uniqueString(resourceGroup().id)}', 60)
location: location
properties: {
perSiteScaling: true
elasticScaleEnabled: true
perSiteScaling: false
reserved: true
maximumElasticWorkerCount: 10
}
kind: 'Linux'
sku: {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
@description('The location for the resource(s) to be deployed.')
param location string = resourceGroup().location

param userPrincipalId string = ''

param tags object = { }

resource env_mi 'Microsoft.ManagedIdentity/userAssignedIdentities@2024-11-30' = {
name: take('env_mi-${uniqueString(resourceGroup().id)}', 128)
location: location
tags: tags
}

resource env_acr 'Microsoft.ContainerRegistry/registries@2025-04-01' = {
name: take('envacr${uniqueString(resourceGroup().id)}', 50)
location: location
sku: {
name: 'Basic'
}
tags: tags
}

resource env_acr_env_mi_AcrPull 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(env_acr.id, env_mi.id, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '7f951dda-4ed3-4680-a7ca-43fe172d538d'))
properties: {
principalId: env_mi.properties.principalId
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '7f951dda-4ed3-4680-a7ca-43fe172d538d')
principalType: 'ServicePrincipal'
}
scope: env_acr
}

resource env_asplan 'Microsoft.Web/serverfarms@2024-11-01' = {
name: take('envasplan-${uniqueString(resourceGroup().id)}', 60)
location: location
properties: {
elasticScaleEnabled: false
perSiteScaling: true
reserved: true
maximumElasticWorkerCount: 10
}
kind: 'Linux'
sku: {
name: 'P0V3'
tier: 'Premium'
}
}

resource env_contributor_mi 'Microsoft.ManagedIdentity/userAssignedIdentities@2024-11-30' = {
name: take('env_contributor_mi-${uniqueString(resourceGroup().id)}', 128)
location: location
}

resource env_ra 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(resourceGroup().id, env_contributor_mi.id, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7'))
properties: {
principalId: env_contributor_mi.properties.principalId
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')
principalType: 'ServicePrincipal'
}
}

resource dashboard 'Microsoft.Web/sites@2024-11-01' = {
name: take('${toLower('env')}-${toLower('aspiredashboard')}-${uniqueString(resourceGroup().id)}', 60)
location: location
properties: {
serverFarmId: env_asplan.id
siteConfig: {
numberOfWorkers: 1
linuxFxVersion: 'ASPIREDASHBOARD|1.0'
acrUseManagedIdentityCreds: true
acrUserManagedIdentityID: env_mi.properties.clientId
appSettings: [
{
name: 'Dashboard__Frontend__AuthMode'
value: 'Unsecured'
}
{
name: 'Dashboard__Otlp__AuthMode'
value: 'Unsecured'
}
{
name: 'Dashboard__Otlp__SuppressUnsecuredTelemetryMessage'
value: 'true'
}
{
name: 'Dashboard__ResourceServiceClient__AuthMode'
value: 'Unsecured'
}
{
name: 'WEBSITES_PORT'
value: '5000'
}
{
name: 'HTTP20_ONLY_PORT'
value: '4317'
}
{
name: 'WEBSITE_START_SCM_WITH_PRELOAD'
value: 'true'
}
{
name: 'AZURE_CLIENT_ID'
value: env_contributor_mi.properties.clientId
}
{
name: 'ALLOWED_MANAGED_IDENTITIES'
value: env_mi.properties.clientId
}
{
name: 'ASPIRE_ENVIRONMENT_NAME'
value: 'env'
}
]
alwaysOn: true
http20Enabled: true
http20ProxyFlag: 1
functionAppScaleLimit: 1
elasticWebAppScaleLimit: 1
}
}
identity: {
type: 'UserAssigned'
userAssignedIdentities: {
'${env_contributor_mi.id}': { }
}
}
kind: 'app,linux,aspiredashboard'
}

output name string = env_asplan.name

output planId string = env_asplan.id

output webSiteSuffix string = uniqueString(resourceGroup().id)

output AZURE_CONTAINER_REGISTRY_NAME string = env_acr.name

output AZURE_CONTAINER_REGISTRY_ENDPOINT string = env_acr.properties.loginServer

output AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID string = env_mi.id

output AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_CLIENT_ID string = env_mi.properties.clientId

output AZURE_WEBSITE_CONTRIBUTOR_MANAGED_IDENTITY_ID string = env_contributor_mi.id

output AZURE_WEBSITE_CONTRIBUTOR_MANAGED_IDENTITY_PRINCIPAL_ID string = env_contributor_mi.properties.principalId

output AZURE_APP_SERVICE_DASHBOARD_URI string = 'https://${take('${toLower('env')}-${toLower('aspiredashboard')}-${uniqueString(resourceGroup().id)}', 60)}.azurewebsites.net'
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "azure.bicep.v0",
"path": "env.module.bicep",
"params": {
"userPrincipalId": ""
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,10 @@ resource env_asplan 'Microsoft.Web/serverfarms@2024-11-01' = {
name: take('envasplan-${uniqueString(resourceGroup().id)}', 60)
location: location
properties: {
perSiteScaling: true
elasticScaleEnabled: true
perSiteScaling: false
reserved: true
maximumElasticWorkerCount: 10
}
kind: 'Linux'
sku: {
Expand Down Expand Up @@ -113,6 +115,8 @@ resource dashboard 'Microsoft.Web/sites@2024-11-01' = {
alwaysOn: true
http20Enabled: true
http20ProxyFlag: 1
functionAppScaleLimit: 1
elasticWebAppScaleLimit: 1
}
}
identity: {
Expand Down Expand Up @@ -142,4 +146,4 @@ output AZURE_WEBSITE_CONTRIBUTOR_MANAGED_IDENTITY_ID string = env_contributor_mi

output AZURE_WEBSITE_CONTRIBUTOR_MANAGED_IDENTITY_PRINCIPAL_ID string = env_contributor_mi.properties.principalId

output AZURE_APP_SERVICE_DASHBOARD_URI string = 'https://${take('${toLower('env')}-${toLower('aspiredashboard')}-${uniqueString(resourceGroup().id)}', 60)}.azurewebsites.net'
output AZURE_APP_SERVICE_DASHBOARD_URI string = 'https://${take('${toLower('env')}-${toLower('aspiredashboard')}-${uniqueString(resourceGroup().id)}', 60)}.azurewebsites.net'
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,10 @@ resource env_asplan 'Microsoft.Web/serverfarms@2024-11-01' = {
name: take('envasplan-${uniqueString(resourceGroup().id)}', 60)
location: location
properties: {
perSiteScaling: true
elasticScaleEnabled: true
perSiteScaling: false
reserved: true
maximumElasticWorkerCount: 10
}
kind: 'Linux'
sku: {
Expand Down Expand Up @@ -113,6 +115,8 @@ resource dashboard 'Microsoft.Web/sites@2024-11-01' = {
alwaysOn: true
http20Enabled: true
http20ProxyFlag: 1
functionAppScaleLimit: 1
elasticWebAppScaleLimit: 1
}
}
identity: {
Expand Down Expand Up @@ -142,4 +146,4 @@ output AZURE_WEBSITE_CONTRIBUTOR_MANAGED_IDENTITY_ID string = env_contributor_mi

output AZURE_WEBSITE_CONTRIBUTOR_MANAGED_IDENTITY_PRINCIPAL_ID string = env_contributor_mi.properties.principalId

output AZURE_APP_SERVICE_DASHBOARD_URI string = 'https://${take('${toLower('env')}-${toLower('aspiredashboard')}-${uniqueString(resourceGroup().id)}', 60)}.azurewebsites.net'
output AZURE_APP_SERVICE_DASHBOARD_URI string = 'https://${take('${toLower('env')}-${toLower('aspiredashboard')}-${uniqueString(resourceGroup().id)}', 60)}.azurewebsites.net'