In-Depth Guide · 8 Sections · Platform
Azure Platform In Depth
App Service, Azure SQL, Key Vault with Managed Identity, Application Insights, Bicep infrastructure-as-code, and blue-green deployment slots. Complements Azure for .NET (Table Storage, Service Bus, Functions).
App Service
Azure SQL
Key Vault
Bicep & Slots
Scenario map: Job Reference — Cloud.
Messaging & serverless: Azure for .NET (Table Storage, Service Bus, Functions).
Container pipeline: Docker & CI/CD.
Most line-of-business .NET APIs land on a platform stack: App Service hosts the web app, Azure SQL holds relational data, Key Vault stores secrets, Application Insights watches health. This guide covers that stack; use Azure for .NET when you need queues, NoSQL tables, or Functions.
App Service → HTTP APIs, Blazor, background workers (Always On)
Azure SQL → relational data, EF Core, existing T-SQL skills
Key Vault → connection strings, API keys, certs — no secrets in git
App Insights → traces, requests, exceptions, dependency calls
Service Bus → async messaging (see azure-dotnet-tutorial.html)
Table Storage → high-volume entity storage, audit logs
Functions → event-driven, timer jobs, lightweight processors
AKS → full Kubernetes control — when PaaS isn't enough
1.2
Reference architecture
Internet → App Service (Minimal API)
├─ Managed Identity → Key Vault (secrets)
├─ Managed Identity → Azure SQL
├─ telemetry → Application Insights
└─ optional → Service Bus (async side effects)
Deploy: GitHub Actions → ACR → App Service slot → swap
App Service is managed IIS/Kestrel in the cloud. Choose Linux + container or Windows + zip deploy. For new projects, Linux containers from ACR are the default path in 2026.
2.1
Configure for production ASP.NET Core
// Program.cs — forward headers behind Azure load balancer
builder.Services.Configure<ForwardedHeadersOptions>(o =>
{
o.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
});
var app = builder.Build();
app.UseForwardedHeaders();
// App Service settings (Configuration blade):
// ASPNETCORE_ENVIRONMENT = Production
// WEBSITE_HTTPLOGGING_RETENTION_DAYS = 7
// Always On = true (required for BackgroundService)
# Bind domain and managed certificate (free)
az webapp config hostname add --webapp-name myapi --resource-group rg-prod \
--hostname api.mycompany.com
az webapp config ssl bind --certificate-thumbprint <thumb> \
--ssl-type SNI --name myapi --resource-group rg-prod
# Enforce HTTPS only
az webapp update --name myapi --resource-group rg-prod --https-only true
Azure SQL is SQL Server as a service. Your EF Core and T-SQL skills transfer directly. Use Managed Identity instead of SQL logins in connection strings when possible.
3.1
EF Core with Azure SQL
builder.Services.AddDbContext<AppDbContext>((sp, options) =>
{
var conn = builder.Configuration.GetConnectionString("Default");
options.UseSqlServer(conn, sql =>
{
sql.EnableRetryOnFailure(maxRetryCount: 5);
sql.CommandTimeout(30);
});
});
// Connection string format (password in Key Vault):
// Server=tcp:myserver.database.windows.net,1433;
// Database=MyDb;Encrypt=True;TrustServerCertificate=False;
3.2
Azure AD auth to SQL (no password)
// App Service system-assigned Managed Identity
// SQL: CREATE USER [myapi] FROM EXTERNAL PROVIDER;
// ALTER ROLE db_datareader ADD MEMBER [myapi];
var conn = new SqlConnection(
"Server=tcp:myserver.database.windows.net;Database=MyDb;Encrypt=True;");
conn.AccessToken = await new DefaultAzureCredential()
.GetTokenAsync(new TokenRequestContext(
new[] { "https://database.windows.net/.default" }));
await conn.OpenAsync();
Managed Identity gives your App Service an Azure AD principal with no credentials to rotate. Grant it get on Key Vault secrets and read configuration at startup.
4.1
Enable and grant access
# Enable system-assigned identity on web app
az webapp identity assign --name myapi --resource-group rg-prod
# note principalId output
# Grant Key Vault Secrets User role
az role assignment create \
--assignee <principalId> \
--role "Key Vault Secrets User" \
--scope /subscriptions/.../resourceGroups/rg-prod/providers/Microsoft.KeyVault/vaults/my-kv
// NuGet: Azure.Extensions.AspNetCore.Configuration.Secrets
builder.Configuration.AddAzureKeyVault(
new Uri($"https://{builder.Configuration["KeyVaultName"]}.vault.azure.net/"),
new DefaultAzureCredential());
// Vault secret name: ConnectionStrings--Default
// Becomes: builder.Configuration["ConnectionStrings:Default"]
// Local dev: Azure CLI login or user secrets — same code path with DefaultAzureCredential
Application Insights captures requests, dependencies (SQL, HTTP), exceptions, and custom events. Wire it once in Program.cs and use structured logging everywhere.
5.1
OpenTelemetry + Azure Monitor
builder.Services.AddOpenTelemetry()
.UseAzureMonitor(o =>
o.ConnectionString = builder.Configuration["ApplicationInsights:ConnectionString"])
.WithTracing(t => t
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddEntityFrameworkCoreInstrumentation())
.WithMetrics(m => m
.AddAspNetCoreInstrumentation()
.AddRuntimeInstrumentation());
5.2
Structured logging & custom metrics
public class OrderService(ILogger<OrderService> log)
{
public async Task CreateAsync(Order order)
{
using var activity = Activity.StartActivity("CreateOrder");
activity?.SetTag("order.id", order.Id);
log.LogInformation("Creating order {OrderId} for {CustomerId}",
order.Id, order.CustomerId);
// failures auto-captured as exceptions in App Insights
}
}
Bicep declares your Azure resources in source control. Deploy the same template to dev and prod with different parameters — no click-ops drift.
// main.bicep
param location string = resourceGroup().location
param appName string
param sqlAdminLogin string
@secure()
param sqlAdminPassword string
resource plan 'Microsoft.Web/serverfarms@2023-01-01' = {
name: '${appName}-plan'
location: location
sku: { name: 'P1v3', tier: 'PremiumV3' }
properties: { reserved: true } // Linux
}
resource web 'Microsoft.Web/sites@2023-01-01' = {
name: appName
location: location
identity: { type: 'SystemAssigned' }
properties: {
serverFarmId: plan.id
siteConfig: { linuxFxVersion: 'DOCKER|myregistry.azurecr.io/myapi:latest' }
}
}
# One-time or on infra changes
az deployment group create \
--resource-group rg-prod \
--template-file infra/main.bicep \
--parameters appName=myapi-prod sqlAdminLogin=sqladmin sqlAdminPassword=@secure.txt
# Store Bicep in repo; PR review for SKU changes and firewall rules
Staging slots share the production App Service Plan but run a different build. Deploy to staging, warm up, run smoke tests, then swap — instant cutover with rollback = swap back.
7.1
Slot settings — sticky vs shared
// Slot-specific (don't swap — each slot keeps its value):
// ASPNETCORE_ENVIRONMENT might stay "Production" on both
// KeyVaultName, ApplicationInsights:ConnectionString → sticky
// Swap with setting:
// New API version deploys to staging
// Staging slot setting: DeploymentSlot = staging (for logging)
az webapp deployment slot create --name myapi --resource-group rg-prod --slot staging
7.2
Swap with preview + auto rollback
# Preview swap — staging gets production hostname temporarily for validation
az webapp deployment slot swap \
--name myapi --resource-group rg-prod \
--slot staging --action preview
# Run synthetic tests against preview URL
# Complete swap
az webapp deployment slot swap --name myapi --resource-group rg-prod --slot staging
# Bad release? Swap again — previous prod is now on staging
8.1
Security & reliability checklist
- Private endpoints for SQL and Key Vault on sensitive workloads
- Azure SQL firewall: allow Azure services + App Service outbound IPs only
- Enable Defender for Cloud recommendations
- Autoscale rules on CPU / queue depth (Premium plan)
- Backup policy on Azure SQL (PITR) + test restore quarterly
- Alert on failed requests > 1% and SQL DTU > 80%
// Dev/test: Basic B1 App Service + serverless SQL (auto-pause)
// Prod: Premium v3 (slots, autoscale, VNet integration)
// Right-size SQL DTU/vCore — watch App Insights dependency duration
// Reserved instances after 6 months stable load
// Pair platform stack with messaging when needed:
// App Service (sync API) + Service Bus (async) — see azure-dotnet-tutorial
Mastery Move — complete the Azure picture
1. Azure for .NET — add Table Storage, Service Bus processors and Functions to the same resource group.
2. Docker & CI/CD — automate image build and slot deploy on every merge to main.
3. Redis & Resilience — distributed cache and Polly policies in front of Azure SQL.