<!-- ref:aspire-v1 -->
# .NET Aspire Projects

> ⛔ **CRITICAL - READ THIS FIRST**
>
> For .NET Aspire projects, **NEVER manually create azure.yaml or infra/ files.**
> Always use `azd init --from-code` which auto-detects the AppHost and generates everything correctly.
>
> **Failure to follow this causes:** "Could not find a part of the path 'infra\main.bicep'" error.

Guidance for preparing .NET Aspire applications for Azure deployment.

**📖 For detailed AZD workflow:** See [recipes/azd/aspire.md](recipes/azd/aspire.md)

## What is .NET Aspire?

.NET Aspire is an opinionated, cloud-ready stack for building observable, production-ready distributed applications. Aspire projects use an AppHost orchestrator to define and configure the application's components, services, and dependencies.

## Detection

A .NET Aspire project is identified by:

| Indicator | Description |
|-----------|-------------|
| `*.AppHost.csproj` | AppHost orchestrator project file |
| `Aspire.Hosting` package | Core Aspire hosting package reference |
| `Aspire.Hosting.AppHost` | Alternative Aspire hosting package |

**Example project structure:**
```
orleans-voting/
├── OrleansVoting.sln
├── OrleansVoting.AppHost/
│   └── OrleansVoting.AppHost.csproj   ← AppHost indicator
├── OrleansVoting.Web/
├── OrleansVoting.Api/
└── OrleansVoting.Grains/
```

## Azure Preparation Workflow

### Step 1: Detection

When scanning the codebase (per [scan.md](scan.md)), detect Aspire by:

```bash
# Check for AppHost project
find . -name "*.AppHost.csproj"

# Or check for Aspire.Hosting package reference
grep -r "Aspire.Hosting" . --include="*.csproj"
```

### Step 2: Initialize with azd

**CRITICAL: For Aspire projects, use `azd init --from-code -e <environment-name>` instead of creating azure.yaml manually.**

**⚠️ ALWAYS include the `-e <environment-name>` flag:** Without it, `azd init` will fail in non-interactive environments (agents, CI/CD) with the error: `no default response for prompt 'Enter a unique environment name:'`

The `--from-code` flag:
- Auto-detects the AppHost orchestrator
- Reads the Aspire service definitions
- Generates appropriate `azure.yaml` and infrastructure
- Works in non-interactive/CI environments when combined with `-e` flag

```bash
# Non-interactive initialization for Aspire projects (REQUIRED for agents)
ENV_NAME="$(basename "$PWD" | tr '[:upper:]' '[:lower:]' | tr ' _' '-')-dev"
azd init --from-code -e "$ENV_NAME"
```

**Why both flags are required:**
- `--from-code`: Tells azd to detect the AppHost automatically (no "How do you want to initialize?" prompt)
- `-e <name>`: Provides environment name upfront (no "Enter environment name:" prompt)
- Together, they enable fully non-interactive operation essential for automation, agents, and CI/CD pipelines

### Step 3: Configure Subscription and Location

> **⛔ CRITICAL**: After `azd init --from-code` completes, you **MUST** immediately set the user-confirmed subscription and location.
>
> **DO NOT** skip this step or delay it until validation. The `azd init` command creates an environment but does NOT inherit the Azure CLI's subscription. If you skip this step, azd will use its own default subscription, which may differ from the user's confirmed choice.

**Set the subscription and location immediately after initialization:**

```bash
# Set the user-confirmed subscription ID
azd env set AZURE_SUBSCRIPTION_ID <subscription-id>

# Set the location
azd env set AZURE_LOCATION <location>
```

**Verify the configuration:**

```bash
azd env get-values
```

Confirm that `AZURE_SUBSCRIPTION_ID` and `AZURE_LOCATION` match the user's confirmed choices from [Azure Context](azure-context.md).

### Step 4: What azd Generates

`azd init --from-code` creates:

| Artifact | Location | Description |
|----------|----------|-------------|
| `azure.yaml` | Project root | Service definitions from AppHost |
| `infra/` | Project root | Bicep templates for Azure resources |
| `.azure/` | Project root | Environment configuration |

**Example generated azure.yaml:**
```yaml
name: orleans-voting
# metadata section is auto-generated by azd init --from-code

services:
  web:
    project: ./OrleansVoting.Web
    language: dotnet
    host: containerapp

  api:
    project: ./OrleansVoting.Api
    language: dotnet
    host: containerapp
```

## Flags Reference

### azd init for Aspire

| Flag | Required | Description |
|------|----------|-------------|
| `--from-code` | ✅ Yes | Auto-detect AppHost, no interactive prompts |
| `-e <name>` | ✅ Yes | Environment name (required for non-interactive) |
| `--no-prompt` | Optional | Skip additional confirmations |

**Complete initialization sequence:**
```bash
# 1. Initialize the environment
ENV_NAME="$(basename "$PWD" | tr '[:upper:]' '[:lower:]' | tr ' _' '-')-dev"
azd init --from-code -e "$ENV_NAME"

# 2. IMMEDIATELY set the user-confirmed subscription
azd env set AZURE_SUBSCRIPTION_ID <subscription-id>

# 3. Set the location
azd env set AZURE_LOCATION <location>

# 4. Verify configuration
azd env get-values
```

## Common Aspire Samples

| Sample | Repository | Notes |
|--------|------------|-------|
| orleans-voting | [dotnet/aspire-samples](https://github.com/dotnet/aspire-samples/tree/main/samples/orleans-voting) | Orleans cluster with voting app |
| AspireYarp | [dotnet/aspire-samples](https://github.com/dotnet/aspire-samples/tree/main/samples/AspireYarp) | YARP reverse proxy |
| AspireWithDapr | [dotnet/aspire-samples](https://github.com/dotnet/aspire-samples/tree/main/samples/AspireWithDapr) | Dapr integration |
| eShop | [dotnet/eShop](https://github.com/dotnet/eShop) | Reference microservices app |

## Troubleshooting

### Error: "no default response for prompt 'Enter a unique environment name:'"

**Cause:** Missing `-e` flag when running `azd init --from-code` in non-interactive environment  
**Solution:** Always include the `-e <environment-name>` flag

```bash
# ❌ Wrong - fails in non-interactive environments (agents, CI/CD)
azd init --from-code

# ✅ Correct - provides environment name upfront
ENV_NAME="$(basename "$PWD" | tr '[:upper:]' '[:lower:]' | tr ' _' '-')-dev"
azd init --from-code -e "$ENV_NAME"
```

**Important:** This error typically occurs when:
- Running in an agent or automation context
- No TTY is available for interactive prompts
- The `-e` flag was omitted

### Error: "no default response for prompt 'How do you want to initialize your app?'"

**Cause:** Missing `--from-code` flag  
**Solution:** Add `--from-code` to the `azd init` command

```bash
# ❌ Wrong - requires interactive prompt
azd init -e "my-env"

# ✅ Correct - auto-detects AppHost
azd init --from-code -e "my-env"
```

### No AppHost detected

**Symptoms:** `azd init --from-code` doesn't find the AppHost

**Solutions:**
1. Verify AppHost project exists: `find . -name "*.AppHost.csproj"`
2. Check project builds: `dotnet build`
3. Ensure Aspire.Hosting package is referenced in AppHost project

### Azure Functions: Secret initialization from Blob storage failed

**Symptoms:** Azure Functions app fails at startup with error:
```
System.InvalidOperationException: Secret initialization from Blob storage failed due to missing both
an Azure Storage connection string and a SAS connection uri.
```

**Cause:** When using `AddAzureFunctionsProject` with `WithHostStorage(storage)`, Aspire configures identity-based storage access (managed identity). However, Azure Functions' internal secret management does not support identity-based URIs and requires file-based secret storage for Container Apps deployments.

**Solution:** Add `AzureWebJobsSecretStorageType=Files` environment variable to the Functions resource in the AppHost **before running `azd up`**:

```csharp
var functions = builder.AddAzureFunctionsProject<Projects.ImageGallery_Functions>("functions")
                       .WithReference(queues)
                       .WithReference(blobs)
                       .WaitFor(storage)
                       .WithRoleAssignments(storage, ...)
                       .WithHostStorage(storage)
                       .WithEnvironment("AzureWebJobsSecretStorageType", "Files")  // Required for Container Apps
                       .WithUrlForEndpoint("http", u => u.DisplayText = "Functions App");
```

> 💡 **Why this is required:**
> - `WithHostStorage(storage)` sets identity-based URIs like `AzureWebJobsStorage__blobServiceUri`
> - This is correct and secure for runtime storage operations
> - However, Functions' secret/key management doesn't support these URIs
> - File-based secrets are mandatory for Container Apps deployments

> ⚠️ **Important:** This is required when:
> - Using `AddAzureFunctionsProject` in Aspire
> - Using `WithHostStorage()` with identity-based storage
> - Deploying to Azure Container Apps (the default for Aspire Functions)

**Generated Infrastructure Note:**

If you need to modify the generated Container Apps infrastructure directly, ensure the Functions container app has this environment variable:

```bicep
resource functionsContainerApp 'Microsoft.App/containerApps@2024-03-01' = {
  properties: {
    template: {
      containers: [
        {
          env: [
            {
              name: 'AzureWebJobsSecretStorageType'
              value: 'Files'
            }
            // ... other environment variables
          ]
        }
      ]
    }
  }
}
```

### Error: azd uses wrong subscription despite user confirmation

**Symptoms:** `azd provision --preview` shows a different subscription than the one the user confirmed

**Cause:** The `AZURE_SUBSCRIPTION_ID` was not set immediately after `azd init --from-code`. The Azure CLI and azd can have different default subscriptions.

**Solution:** Always set the subscription immediately after initialization:

```bash
# After azd init --from-code completes:
azd env set AZURE_SUBSCRIPTION_ID <user-confirmed-subscription-id>
azd env set AZURE_LOCATION <location>

# Verify before proceeding:
azd env get-values
```

**Prevention:** Follow the complete initialization sequence in the [Flags Reference](#azd-init-for-aspire) section above.

## References

- [.NET Aspire Documentation](https://learn.microsoft.com/en-us/dotnet/aspire/)
- [Azure Developer CLI (azd)](https://learn.microsoft.com/en-us/azure/developer/azure-developer-cli/)
- [Aspire Samples Repository](https://github.com/dotnet/aspire-samples)
- [azd + Aspire Integration](https://learn.microsoft.com/en-us/dotnet/aspire/deployment/azure/aca-deployment-azd-in-depth)

## Next Steps

After `azd init --from-code`:
1. Review generated `azure.yaml` and `infra/` files (if present)
2. Set AZURE_SUBSCRIPTION_ID and AZURE_LOCATION with `azd env set`
3. Customize infrastructure as needed
4. Proceed to **azure-validate** skill
5. Deploy with **azure-deploy** skill

> ⚠️ **Important for Container Apps:** If using Aspire with Container Apps, azure-validate will check and help set up required environment variables after provisioning.
