Zum Inhalt springen
DokumentationAspire ausprobieren
DokumentationAusprobieren

Customize Azure resources

Dieser Inhalt ist noch nicht in deiner Sprache verfügbar.

Azure logo

When working with Azure integrations in Aspire, you often need to customize the provisioned infrastructure beyond the default settings. The same customization patterns apply across Azure hosting integrations such as Storage, Service Bus, Key Vault, user-assigned identities, Azure Container Apps, and Azure App Service. This page documents all customization APIs supported across both C# and TypeScript AppHost projects.

For target-specific generated resource customization, see Deploy to Azure Container Apps and Deploy to Azure App Service.

Use AsExisting, RunAsExisting, and PublishAsExisting when you want Aspire to reference an Azure resource that already exists instead of provisioning a new one.

AppHost.cs
var builder = DistributedApplication.CreateBuilder(args);
var existingBusName = builder.AddParameter("existingServiceBusName");
var existingBusResourceGroup = builder.AddParameter("existingServiceBusResourceGroup");
var serviceBus = builder.AddAzureServiceBus("messaging")
.PublishAsExisting(existingBusName, existingBusResourceGroup);
builder.Build().Run();

Use RunAsExisting when only local run mode should use the existing resource, PublishAsExisting when only deployed Azure environments should use it, and AsExisting when both modes should point at the same Azure resource.

Aspire automatically assigns Azure RBAC roles based on how resources reference one another. When you need to opt out of those defaults before applying different permissions, use ClearDefaultRoleAssignments.

AppHost.cs
var builder = DistributedApplication.CreateBuilder(args);
builder.AddAzureServiceBus("messaging")
.ClearDefaultRoleAssignments();
builder.Build().Run();

For built-in and custom RBAC guidance, see Manage Azure role assignments.

Azure resources expose output references for values that Azure assigns during provisioning. Use those references when another resource, deployment step, or app setting needs the resolved Azure value.

AppHost.cs
var builder = DistributedApplication.CreateBuilder(args);
var identity = builder.AddAzureUserAssignedIdentity("identity");
builder.AddProject<Projects.Api>("api")
.WithEnvironment("IDENTITY_CLIENT_ID", identity.Resource.ClientId);
builder.Build().Run();

Use GetBicepIdentifier() inside ConfigureInfrastructure or infrastructure resolvers when you need a stable identifier for a provisioned Azure construct. Use output references such as ClientId, NameOutputReference, or getOutput("...") when you need Azure-assigned values like a client ID, endpoint, or resource name.

The ConfigureInfrastructure API lets you customize the Azure resources that Aspire generates during provisioning. In C#, it provides a strongly-typed surface over the Azure.Provisioning library, letting you modify any property of the generated Azure resource types before Bicep is emitted.

All Azure hosting resources in Aspire inherit from AzureProvisioningResource, which exposes ConfigureInfrastructure. The callback receives an AzureResourceInfrastructure instance that gives you access to all provisioned constructs for that resource.

In C#, use GetProvisionableResources() with the strongly-typed Azure SDK types (for example StorageAccount, ServiceBusNamespace) to navigate and mutate each construct:

AppHost.cs
var builder = DistributedApplication.CreateBuilder(args);
var storage = builder.AddAzureStorage("storage");
storage.ConfigureInfrastructure(infra =>
{
var storageAccount = infra.GetProvisionableResources()
.OfType<StorageAccount>()
.Single();
storageAccount.Sku = new StorageSku { Name = StorageSkuName.PremiumLRS };
storageAccount.Tags["Environment"] = "Development";
storageAccount.Tags["CostCenter"] = "Engineering";
});
builder.Build().Run();
AppHost.cs (Storage)
storage.ConfigureInfrastructure(infra =>
{
var account = infra.GetProvisionableResources()
.OfType<StorageAccount>()
.Single();
account.Sku = new StorageSku { Name = StorageSkuName.StandardGRS };
account.Kind = StorageKind.StorageV2;
});
AppHost.cs (Service Bus)
serviceBus.ConfigureInfrastructure(infra =>
{
var ns = infra.GetProvisionableResources()
.OfType<ServiceBusNamespace>()
.Single();
ns.Sku = new ServiceBusSku
{
Name = ServiceBusSkuName.Premium,
Capacity = 2
};
});
AppHost.cs (Redis)
redis.ConfigureInfrastructure(infra =>
{
var cache = infra.GetProvisionableResources()
.OfType<RedisCache>()
.Single();
cache.Sku = new RedisCacheSku
{
Name = RedisCacheSkuName.Premium,
Family = RedisCacheSkuFamily.P,
Capacity = 1
};
});
AppHost.cs
storage.ConfigureInfrastructure(infra =>
{
var account = infra.GetProvisionableResources()
.OfType<StorageAccount>()
.Single();
account.NetworkRuleSet = new StorageAccountNetworkRuleSet
{
DefaultAction = StorageNetworkDefaultAction.Deny,
Bypass = "AzureServices"
};
account.NetworkRuleSet.IpRules.Add(new StorageAccountIPRule
{
IPAddressOrRange = "203.0.113.0/24",
Action = "Allow"
});
});
AppHost.cs
storage.ConfigureInfrastructure(infra =>
{
var account = infra.GetProvisionableResources()
.OfType<StorageAccount>()
.Single();
account.EnableHttpsTrafficOnly = true;
account.MinimumTlsVersion = StorageMinimumTlsVersion.Tls1_2;
account.Encryption = new StorageAccountEncryption
{
KeySource = StorageAccountKeySource.MicrosoftStorage,
Services = new StorageAccountEncryptionServices
{
Blob = new StorageEncryptionService { Enabled = true },
File = new StorageEncryptionService { Enabled = true }
}
};
});

When an integration creates multiple resources (for example, a Service Bus namespace and its queues), you can customize each one inside a single ConfigureInfrastructure call:

AppHost.cs
var servicebus = builder.AddAzureServiceBus("messaging");
var queue = servicebus.AddQueue("orders");
servicebus.ConfigureInfrastructure(infra =>
{
var ns = infra.GetProvisionableResources()
.OfType<ServiceBusNamespace>()
.Single();
ns.Sku = new ServiceBusSku { Name = ServiceBusSkuName.Standard };
var queueResource = infra.GetProvisionableResources()
.OfType<ServiceBusQueue>()
.FirstOrDefault(q => q.Name.Contains("orders"));
if (queueResource != null)
{
queueResource.MaxDeliveryCount = 5;
queueResource.DefaultMessageTimeToLive = TimeSpan.FromHours(24);
}
});

For common scenarios, prefer Aspire’s higher-level resource builders. For example, to add a private endpoint to a storage account, use AddPrivateEndpoint from the Aspire.Hosting.Azure.Network package, which automatically wires up the Private DNS Zone, VNet link, and DNS Zone Group:

AppHost.cs
var builder = DistributedApplication.CreateBuilder(args);
var vnet = builder.AddAzureVirtualNetwork("vnet");
var peSubnet = vnet.AddSubnet("pe-subnet", "10.0.1.0/24");
var storage = builder.AddAzureStorage("storage");
var blobs = storage.AddBlobs("blobs");
peSubnet.AddPrivateEndpoint(blobs);
builder.Build().Run();

When no Aspire-native resource builder exists for what you need, fall back to ConfigureInfrastructure to add an Azure.Provisioning construct directly. See the Azure.Provisioning API reference for the available construct types and their properties.

Customize naming and provisioning with an infrastructure resolver

Section titled “Customize naming and provisioning with an infrastructure resolver”

Another way to customize Azure provisioning is to create an InfrastructureResolver. This is a C#-only API that lets you apply organization-wide naming conventions or other centralized provisioning behavior across many Azure resources.

Define a resolver by inheriting from InfrastructureResolver and overriding the relevant virtual members:

AppHost.cs
using Azure.Provisioning;
using Azure.Provisioning.CosmosDB;
using Azure.Provisioning.Primitives;
internal sealed class FixedNameInfrastructureResolver : InfrastructureResolver
{
public override void ResolveProperties(ProvisionableConstruct construct, ProvisioningBuildOptions options)
{
if (construct is CosmosDBAccount account)
{
account.Name = "ContosoCosmosDb";
}
base.ResolveProperties(construct, options);
}
}

Register the resolver in the AppHost:

AppHost.cs
using Aspire.Hosting.Azure;
using Microsoft.Extensions.DependencyInjection;
var builder = DistributedApplication.CreateBuilder(args);
builder.Services.Configure<AzureProvisioningOptions>(options =>
{
options.ProvisioningBuildOptions.InfrastructureResolvers.Add(new FixedNameInfrastructureResolver());
});
builder.Build().Run();

For scenarios requiring full control, you can supply a custom Bicep file and reference it from your AppHost. This approach works in both C# and TypeScript.

Create a Bicep file in your AppHost project:

custom-storage.bicep
@description('Storage account name')
param storageAccountName string
@description('Location')
param location string = resourceGroup().location
resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = {
name: storageAccountName
location: location
sku: {
name: 'Premium_LRS'
}
kind: 'BlockBlobStorage'
properties: {
minimumTlsVersion: 'TLS1_2'
allowBlobPublicAccess: false
networkAcls: {
defaultAction: 'Deny'
bypass: 'AzureServices'
}
}
}
output storageAccountName string = storageAccount.name

Reference the file from the AppHost using AddBicepTemplate (C#) or addBicepTemplate (TypeScript). Use WithParameter / withParameter to supply input parameters, and GetOutput / getOutput to consume a named output by passing the resulting reference to another resource:

AppHost.cs
var builder = DistributedApplication.CreateBuilder(args);
var storage = builder.AddBicepTemplate("storage", "./custom-storage.bicep")
.WithParameter("storageAccountName", "mystorageaccount");
builder.AddProject<Projects.WebApp>("webapp")
.WithEnvironment("STORAGE_ACCOUNT_NAME", storage.GetOutput("storageAccountName"));
builder.Build().Run();

WithParameter accepts several kinds of values beyond plain strings, so a custom Bicep template can take values that are computed at deployment time. Common sources include:

  • A ParameterResource declared with AddParameter (including secret parameters).
  • A BicepOutputReference from another Bicep resource — useful when one template’s output feeds another template’s input.
  • A ReferenceExpression that composes values from multiple resources.
  • A connection-string-bearing resource via IResourceBuilder<IResourceWithConnectionString>.
  • An EndpointReference from a resource’s endpoint.

The example below adds a secret administrator password as a parameter and then reads the deployed SQL server name back out of the template so that another resource can consume it:

Create the following Bicep file in your AppHost directory:

custom-sql.bicep
@secure()
param administratorLoginPassword string
param location string = resourceGroup().location
resource sqlServer 'Microsoft.Sql/servers@2021-11-01' = {
name: 'sql-${uniqueString(resourceGroup().id)}'
location: location
properties: {
administratorLogin: 'sqladmin'
administratorLoginPassword: administratorLoginPassword
}
}
output sqlServerName string = sqlServer.name
AppHost.cs
var builder = DistributedApplication.CreateBuilder(args);
var adminPassword = builder.AddParameter("adminPassword", secret: true);
var sql = builder.AddBicepTemplate("sql", "./custom-sql.bicep")
.WithParameter("administratorLoginPassword", adminPassword);
builder.AddProject<Projects.Api>("api")
.WithEnvironment("SQL_SERVER_NAME", sql.GetOutput("sqlServerName"));
builder.Build().Run();

For more end-to-end examples — including chaining outputs between Bicep resources and using the existing Bicep keyword to reference resources that Aspire didn’t provision — see the Aspire playground/bicep sample.

By default, Aspire deploys Bicep resources at the resource group scope. Some Bicep templates require a different deployment scope, such as the subscription or the tenant.

Set the Scope property on the Bicep resource using AzureBicepResourceScope.CreateForSubscription or AzureBicepResourceScope.CreateForTenant:

AppHost.cs
using Aspire.Hosting.Azure;
var builder = DistributedApplication.CreateBuilder(args);
var subscriptionId = builder.AddParameter("subscriptionId");
var subscriptionScoped = builder.AddBicepTemplateString(
"subscriptionScoped",
"""
targetScope = 'subscription'
param location string
output value string = 'subscription'
""");
subscriptionScoped.Resource.Scope = AzureBicepResourceScope.CreateForSubscription(subscriptionId.Resource);
var tenantScoped = builder.AddBicepTemplateString(
"tenantScoped",
"""
targetScope = 'tenant'
param location string
output value string = 'tenant'
""");
tenantScoped.Resource.Scope = AzureBicepResourceScope.CreateForTenant();
builder.Build().Run();

To see the Bicep that Aspire emits after applying your ConfigureInfrastructure callbacks, publish the AppHost and read the files from the output folder:

  1. From the AppHost directory, run aspire publish.
  2. Open the aspire-output folder in the AppHost directory. To write somewhere else, pass --output-path to the publish command.
  3. Review the generated .bicep files to verify your customizations.