diff --git a/docs/database/snippets/cosmos-db/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj b/docs/database/snippets/cosmos-db/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj index 73896fdeed..45fc528d77 100644 --- a/docs/database/snippets/cosmos-db/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj +++ b/docs/database/snippets/cosmos-db/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj @@ -10,7 +10,7 @@ - + diff --git a/docs/database/snippets/tutorial/aspiresqldeployazure/AspireSql.ServiceDefaults/AspireSql.ServiceDefaults.csproj b/docs/database/snippets/tutorial/aspiresqldeployazure/AspireSql.ServiceDefaults/AspireSql.ServiceDefaults.csproj index 73896fdeed..45fc528d77 100644 --- a/docs/database/snippets/tutorial/aspiresqldeployazure/AspireSql.ServiceDefaults/AspireSql.ServiceDefaults.csproj +++ b/docs/database/snippets/tutorial/aspiresqldeployazure/AspireSql.ServiceDefaults/AspireSql.ServiceDefaults.csproj @@ -10,7 +10,7 @@ - + diff --git a/docs/database/snippets/tutorial/aspiresqldeploycontainer/AspireSql.ServiceDefaults/AspireSql.ServiceDefaults.csproj b/docs/database/snippets/tutorial/aspiresqldeploycontainer/AspireSql.ServiceDefaults/AspireSql.ServiceDefaults.csproj index 73896fdeed..45fc528d77 100644 --- a/docs/database/snippets/tutorial/aspiresqldeploycontainer/AspireSql.ServiceDefaults/AspireSql.ServiceDefaults.csproj +++ b/docs/database/snippets/tutorial/aspiresqldeploycontainer/AspireSql.ServiceDefaults/AspireSql.ServiceDefaults.csproj @@ -10,7 +10,7 @@ - + diff --git a/docs/database/snippets/tutorial/aspiresqlefcore/AspireSQLEFCore.ServiceDefaults/AspireSQLEFCore.ServiceDefaults.csproj b/docs/database/snippets/tutorial/aspiresqlefcore/AspireSQLEFCore.ServiceDefaults/AspireSQLEFCore.ServiceDefaults.csproj index 73896fdeed..45fc528d77 100644 --- a/docs/database/snippets/tutorial/aspiresqlefcore/AspireSQLEFCore.ServiceDefaults/AspireSQLEFCore.ServiceDefaults.csproj +++ b/docs/database/snippets/tutorial/aspiresqlefcore/AspireSQLEFCore.ServiceDefaults/AspireSQLEFCore.ServiceDefaults.csproj @@ -10,7 +10,7 @@ - + diff --git a/docs/deployment/azure/custom-bicep-templates.md b/docs/deployment/azure/custom-bicep-templates.md new file mode 100644 index 0000000000..18d7b50036 --- /dev/null +++ b/docs/deployment/azure/custom-bicep-templates.md @@ -0,0 +1,173 @@ +--- +title: Use custom Bicep templates +description: Learn how to customize the Bicep templates provided by .NET Aspire to better suit your needs. +ms.date: 06/11/2024 +--- + +# Use custom Bicep templates + +When you're targeting Azure as your desired cloud provider, you can use Bicep to define your infrastructure as code. [Bicep is a domain-specific language (DSL)](/azure/azure-resource-manager/bicep/overview) for deploying Azure resources declaratively. It aims to drastically simplify the authoring experience with a cleaner syntax and better support for modularity and code reuse. + +While .NET Aspire provides a set of pre-built Bicep templates so that you don't need to write them, there might be times when you either want to customize the templates or create your own. This article explains the concepts and corresponding APIs that you can use to customize the Bicep templates. + +> [!IMPORTANT] +> This article is not intended to teach Bicep, but rather to provide guidance on how to create customize Bicep templates for use with .NET Aspire. + +As part of the Azure deployment story for .NET Aspire, the Azure Developer CLI (`azd`) provides an understanding of your .NET Aspire application and the ability to deploy it to Azure. The `azd` CLI uses the Bicep templates to deploy the application to Azure. + +## Install App Host package + +To use any of this functionality, you must install the [Aspire.Hosting.Azure](https://nuget.org/packages/Aspire.Hosting.Azure) NuGet package: + +### [.NET CLI](#tab/dotnet-cli) + +```dotnetcli +dotnet add package Aspire.Hosting.Azure +``` + +### [PackageReference](#tab/package-reference) + +```xml + +``` + +--- + +For more information, see [dotnet add package](/dotnet/core/tools/dotnet-add-package) or [Manage package dependencies in .NET applications](/dotnet/core/tools/dependencies). + +All of the examples in this article assume that you've installed the `Aspire.Hosting.Azure` package and imported the `Aspire.Hosting.Azure` namespace. Additionally, the examples assume you've created an `IDistributedApplicationBuilder` instance: + +```csharp +using Aspire.Hosting.Azure; + +var builder = DistributedApplication.CreateBuilder(args); + +// Examples go here... + +builder.Build().Run(); +``` + +> [!TIP] +> By default, when you call any of the Bicep-related APIs, a call is also made to that adds support for generating Azure resources dynamically during application startup. + +## Reference Bicep files + +Imagine that you've defined a Bicep template in a file named `storage.bicep` that provisions an Azure Storage Account: + +:::code language="bicep" source="snippets/AppHost.Bicep/storage.bicep"::: + +To add a reference to the Bicep file on disk, call the method. Consider the following example: + +:::code language="csharp" source="snippets/AppHost.Bicep/Program.ReferenceBicep.cs" id="addfile"::: + +The preceding code adds a reference to a Bicep file located at `../infra/storage.bicep`. The file paths should be relative to the _app host_ project. This reference results in an being added to the application's resources collection with the `"storage"` name, and the API returns an `IResourceBuilder` instance that can be used to further customize the resource. + +## Reference Bicep inline + +While having a Bicep file on disk is the most common scenario, you can also add Bicep templates inline. Inline templates can be useful when you want to define a template in code or when you want to generate the template dynamically. To add an inline Bicep template, call the method with the Bicep template as a `string`. Consider the following example: + +:::code language="csharp" source="snippets/AppHost.Bicep/Program.InlineBicep.cs" id="addinline"::: + +In this example, the Bicep template is defined as an inline `string` and added to the application's resources collection with the name `"ai"`. This example provisions an Azure AI resource. + +## Pass parameters to Bicep templates + +[Bicep supports accepting parameters](/azure/azure-resource-manager/bicep/parameters), which can be used to customize the behavior of the template. To pass parameters to a Bicep template from .NET Aspire, chain calls to the method as shown in the following example: + +:::code language="csharp" source="snippets/AppHost.Bicep/Program.PassParameter.cs" id="addparameter"::: + +The preceding code: + +- Adds a parameter named `"region"` to the `builder` instance. +- Adds a reference to a Bicep file located at `../infra/storage.bicep`. +- Passes the `"region"` parameter to the Bicep template, which is resolved using the standard parameter resolution. +- Passes the `"storageName"` parameter to the Bicep template with a _hardcoded_ value. +- Passes the `"tags"` parameter to the Bicep template with an array of strings. + +For more information, see [External parameters](../../fundamentals/external-parameters.md). + +### Well-known parameters + +.NET Aspire provides a set of well-known parameters that can be passed to Bicep templates. These parameters are used to provide information about the application and the environment to the Bicep templates. The following well-known parameters are available: + +| Field | Description | Value | +|--|--|--| +| | The name of the key vault resource used to store secret outputs. | `"keyVaultName"` | +| | The location of the resource. This is required for all resources. | `"location"` | +| | The resource ID of the log analytics workspace. | `"logAnalyticsWorkspaceId"` | +| | The principal ID of the current user or managed identity. | `"principalId"` | +| | The principal name of the current user or managed identity. | `"principalName"` | +| | The principal type of the current user or managed identity. Either `User` or `ServicePrincipal`. | `"principalType"` | + +To use a well-known parameter, pass the parameter name to the method, such as `WithParameter(AzureBicepResource.KnownParameters.KeyVaultName)`. You don't pass values for well-known parameters, as they're resolved automatically by .NET Aspire. + +Consider an example where you want to setup an Azure Event Grid webhook. You might define the Bicep template as follows: + + :::code language="bicep" source="snippets/AppHost.Bicep/event-grid-webhook.bicep" highlight="3-4,27-35"::: + +This Bicep template defines several parameters, including the `topicName`, `webHookEndpoint`, `principalId`, `principalType`, and the optional `location`. To pass these parameters to the Bicep template, you can use the following code snippet: + +:::code language="csharp" source="snippets/AppHost.Bicep/Program.PassParameter.cs" id="addwellknownparams"::: + +- The `webHookApi` project is added as a reference to the `builder`. +- The `topicName` parameter is passed a hardcoded name value. +- The `webHookEndpoint` parameter is passed as an expression that resolves to the URL from the `api` project references' "https" endpoint with the `/hook` route. +- The `principalId` and `principalType` parameters are passed as well-known parameters. + +The well-known parameters are convention-based and shouldn't be accompanied with a corresponding value when passed using the `WithParameter` API. Well-known parameters simplify some common functionality, such as _role assignments_, when added to the Bicep templates, as shown in the preceding example. Role assignments are required for the Event Grid webhook to send events to the specified endpoint. For more information, see [EventGrid Data Sender role assignment](/azure/role-based-access-control/built-in-roles/integration#eventgrid-data-sender). + +## Get outputs from Bicep references + +In addition to passing parameters to Bicep templates, you can also get outputs from the Bicep templates. Consider the following Bicep template, as it defines an `output` named `endpoint`: + +:::code language="bicep" source="snippets/AppHost.Bicep/storage-out.bicep"::: + +The Bicep defines an output named `endpoint`. To get the output from the Bicep template, call the method on an `IResourceBuilder` instance as demonstrated in following C# code snippet: + +:::code language="csharp" source="snippets/AppHost.Bicep/Program.GetOutputReference.cs" id="getoutput"::: + +In this example, the output from the Bicep template is retrieved and stored in an `endpoint` variable. Typically, you would pass this output as an environment variable to another resource that relies on it. For instance, if you had an ASP.NET Core Minimal API project that depended on this endpoint, you could pass the output as an environment variable to the project using the following code snippet: + +```csharp +var storage = builder.AddBicepTemplate( + name: "storage", + bicepFile: "../infra/storage.bicep" + ); + +var endpoint = storage.GetOutput("endpoint"); + +var apiService = builder.AddProject( + name: "apiservice" + ) + .WithEnvironment("STORAGE_ENDPOINT", endpoint); +``` + +For more information, see [Bicep outputs](/azure/azure-resource-manager/bicep/outputs). + +## Get secret outputs from Bicep references + +It's important to [avoid outputs for secrets](/azure/azure-resource-manager/bicep/scenarios-secrets#avoid-outputs-for-secrets) when working with Bicep. If an output is considered a _secret_, meaning it shouldn't be exposed in logs or other places, you can treat it as such. This can be achieved by storing the secret in Azure Key Vault and referencing it in the Bicep template. .NET Aspire's Azure integration provides a pattern for securely storing outputs from the Bicep template by allows resources to use the `keyVaultName` parameter to store secrets in Azure Key Vault. + +Consider the following Bicep template as an example the helps to demonstrate this concept of securing secret outputs: + +:::code language="bicep" source="snippets/AppHost.Bicep/cosmosdb.bicep" highlight="2,41"::: + +The preceding Bicep template expects a `keyVaultName` parameter, among several other parameters. It then defines an Azure Cosmos DB resource and stashes a secret into Azure Key Vault, named `connectionString` which represents the fully qualified connection string to the Cosmos DB instance. To access this secret connection string value, you can use the following code snippet: + +:::code language="csharp" source="snippets/AppHost.Bicep/Program.cs" id="secrets"::: + +In the preceding code snippet, the `cosmos` Bicep template is added as a reference to the `builder`. The `connectionString` secret output is retrieved from the Bicep template and stored in a variable. The secret output is then passed as an environment variable (`ConnectionStrings__cosmos`) to the `api` project. This environment variable is used to connect to the Cosmos DB instance. + +When this resource is deployed, the underlying deployment mechanism with automatically [Reference secrets from Azure Key Vault](/azure/container-apps/manage-secrets?tabs=azure-portal#reference-secret-from-key-vault). To guarantee secret isolation, .NET Aspire creates a Key Vault per source. + +> [!NOTE] +> In _local provisioning_ mode, the secret is extracted from Key Vault and set it in an environment variable. For more information, see [Local Azure provisioning](local-provisioning.md). + +## See also + +For continued learning, see the following resources as they relate to .NET Aspire and Azure deployment: + +- [Deploy a .NET Aspire app to Azure Container Apps](aca-deployment.md) +- [Deploy a .NET Aspire app to Azure Container Apps using the Azure Developer CLI (in-depth guide)](aca-deployment-azd-in-depth.md) +- [.NET Aspire manifest format for deployment tool builders](../manifest-format.md) diff --git a/docs/deployment/azure/snippets/AppHost.Bicep/AppHost.Bicep.csproj b/docs/deployment/azure/snippets/AppHost.Bicep/AppHost.Bicep.csproj new file mode 100644 index 0000000000..e4e9f852a7 --- /dev/null +++ b/docs/deployment/azure/snippets/AppHost.Bicep/AppHost.Bicep.csproj @@ -0,0 +1,21 @@ + + + + Exe + net8.0 + enable + enable + true + 5e45e8bd-353b-48d8-ac57-a89e5ee3f8ec + + + + + + + + + + + + diff --git a/docs/deployment/azure/snippets/AppHost.Bicep/AppHost.Bicep.sln b/docs/deployment/azure/snippets/AppHost.Bicep/AppHost.Bicep.sln new file mode 100644 index 0000000000..88454a411f --- /dev/null +++ b/docs/deployment/azure/snippets/AppHost.Bicep/AppHost.Bicep.sln @@ -0,0 +1,30 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.11.34929.205 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AppHost.Bicep", "AppHost.Bicep.csproj", "{9D692CBB-E892-4CA9-B900-BEA71FF0F37D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebHook.Api", "..\WebHook.Api\WebHook.Api.csproj", "{5472D255-76E4-4000-B053-D8231FDB9905}" +EndProject +Global + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {1FECE22C-F99C-4BEF-BE41-5BC5D0B5FCF2} + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {5472D255-76E4-4000-B053-D8231FDB9905}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5472D255-76E4-4000-B053-D8231FDB9905}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5472D255-76E4-4000-B053-D8231FDB9905}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5472D255-76E4-4000-B053-D8231FDB9905}.Release|Any CPU.Build.0 = Release|Any CPU + {9D692CBB-E892-4CA9-B900-BEA71FF0F37D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9D692CBB-E892-4CA9-B900-BEA71FF0F37D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9D692CBB-E892-4CA9-B900-BEA71FF0F37D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9D692CBB-E892-4CA9-B900-BEA71FF0F37D}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/docs/deployment/azure/snippets/AppHost.Bicep/Program.GetOutputReference.cs b/docs/deployment/azure/snippets/AppHost.Bicep/Program.GetOutputReference.cs new file mode 100644 index 0000000000..4e2dc80f85 --- /dev/null +++ b/docs/deployment/azure/snippets/AppHost.Bicep/Program.GetOutputReference.cs @@ -0,0 +1,14 @@ +internal static partial class Program +{ + public static void AddBicepAndGetOutput(IDistributedApplicationBuilder builder) + { + // + var storage = builder.AddBicepTemplate( + name: "storage", + bicepFile: "../infra/storage.bicep" + ); + + var endpoint = storage.GetOutput("endpoint"); + // + } +} diff --git a/docs/deployment/azure/snippets/AppHost.Bicep/Program.InlineBicep.cs b/docs/deployment/azure/snippets/AppHost.Bicep/Program.InlineBicep.cs new file mode 100644 index 0000000000..baac937296 --- /dev/null +++ b/docs/deployment/azure/snippets/AppHost.Bicep/Program.InlineBicep.cs @@ -0,0 +1,37 @@ +internal static partial class Program +{ + public static void AddBicepInline(IDistributedApplicationBuilder builder) + { + // + builder.AddBicepTemplateString( + name: "ai", + bicepContent: """ + @description('That name is the name of our application.') + param cognitiveServiceName string = 'CognitiveService-${uniqueString(resourceGroup().id)}' + + @description('Location for all resources.') + param location string = resourceGroup().location + + @allowed([ + 'S0' + ]) + param sku string = 'S0' + + resource cognitiveService 'Microsoft.CognitiveServices/accounts@2021-10-01' = { + name: cognitiveServiceName + location: location + sku: { + name: sku + } + kind: 'CognitiveServices' + properties: { + apiProperties: { + statisticsEnabled: false + } + } + } + """ + ); + // + } +} diff --git a/docs/deployment/azure/snippets/AppHost.Bicep/Program.PassParameter.cs b/docs/deployment/azure/snippets/AppHost.Bicep/Program.PassParameter.cs new file mode 100644 index 0000000000..572b394aa7 --- /dev/null +++ b/docs/deployment/azure/snippets/AppHost.Bicep/Program.PassParameter.cs @@ -0,0 +1,30 @@ +using Aspire.Hosting; +using Aspire.Hosting.Azure; + +internal static partial class Program +{ + public static void AddParameterToBicepReference(IDistributedApplicationBuilder builder) + { + // + var region = builder.AddParameter("region"); + + builder.AddBicepTemplate("storage", "../infra/storage.bicep") + .WithParameter("region", region) + .WithParameter("storageName", "app-storage") + .WithParameter("tags", ["latest","dev"]); + // + + // + var webHookApi = builder.AddProject("webhook-api"); + + var webHookEndpointExpression = ReferenceExpression.Create( + $"{webHookApi.GetEndpoint("https")}/hook"); + + builder.AddBicepTemplate("event-grid-webhook", "../infra/event-grid-webhook.bicep") + .WithParameter("topicName", "events") + .WithParameter(AzureBicepResource.KnownParameters.PrincipalId) + .WithParameter(AzureBicepResource.KnownParameters.PrincipalType) + .WithParameter("webHookEndpoint", () => webHookEndpointExpression); + // + } +} diff --git a/docs/deployment/azure/snippets/AppHost.Bicep/Program.ReferenceBicep.cs b/docs/deployment/azure/snippets/AppHost.Bicep/Program.ReferenceBicep.cs new file mode 100644 index 0000000000..f5279f37ef --- /dev/null +++ b/docs/deployment/azure/snippets/AppHost.Bicep/Program.ReferenceBicep.cs @@ -0,0 +1,13 @@ +using Aspire.Hosting.Azure; + +internal static partial class Program +{ + public static void AddBicepFileReference(IDistributedApplicationBuilder builder) + { + // + builder.AddBicepTemplate( + name: "storage", + bicepFile: "../infra/storage.bicep"); + // + } +} diff --git a/docs/deployment/azure/snippets/AppHost.Bicep/Program.cs b/docs/deployment/azure/snippets/AppHost.Bicep/Program.cs new file mode 100644 index 0000000000..a1e4b09f93 --- /dev/null +++ b/docs/deployment/azure/snippets/AppHost.Bicep/Program.cs @@ -0,0 +1,20 @@ +using Aspire.Hosting.Azure; + +var builder = DistributedApplication.CreateBuilder(args); + +// +var cosmos = builder.AddBicepTemplate("cosmos", "../infra/cosmosdb.bicep") + .WithParameter("databaseAccountName", "fallout-db") + .WithParameter(AzureBicepResource.KnownParameters.KeyVaultName) + .WithParameter("databases", ["vault-33", "vault-111"]); + +var connectionString = + cosmos.GetSecretOutput("connectionString"); + +builder.AddProject("api") + .WithEnvironment( + "ConnectionStrings__cosmos", + connectionString); +// + +builder.Build().Run(); diff --git a/docs/deployment/azure/snippets/AppHost.Bicep/Properties/launchSettings.json b/docs/deployment/azure/snippets/AppHost.Bicep/Properties/launchSettings.json new file mode 100644 index 0000000000..a645762728 --- /dev/null +++ b/docs/deployment/azure/snippets/AppHost.Bicep/Properties/launchSettings.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:17220;http://localhost:15113", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21208", + "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22139" + } + }, + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:15113", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19077", + "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20027" + } + } + } +} diff --git a/docs/deployment/azure/snippets/AppHost.Bicep/appsettings.Development.json b/docs/deployment/azure/snippets/AppHost.Bicep/appsettings.Development.json new file mode 100644 index 0000000000..0c208ae918 --- /dev/null +++ b/docs/deployment/azure/snippets/AppHost.Bicep/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/docs/deployment/azure/snippets/AppHost.Bicep/appsettings.json b/docs/deployment/azure/snippets/AppHost.Bicep/appsettings.json new file mode 100644 index 0000000000..31c092aa45 --- /dev/null +++ b/docs/deployment/azure/snippets/AppHost.Bicep/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning", + "Aspire.Hosting.Dcp": "Warning" + } + } +} diff --git a/docs/deployment/azure/snippets/AppHost.Bicep/cosmosdb.bicep b/docs/deployment/azure/snippets/AppHost.Bicep/cosmosdb.bicep new file mode 100644 index 0000000000..b7588431e7 --- /dev/null +++ b/docs/deployment/azure/snippets/AppHost.Bicep/cosmosdb.bicep @@ -0,0 +1,52 @@ +param databaseAccountName string +param keyVaultName string + +param databases array = [] + +@description('Tags that will be applied to all resources') +param tags object = {} + +param location string = resourceGroup().location + +var resourceToken = uniqueString(resourceGroup().id) + +resource cosmosDb 'Microsoft.DocumentDB/databaseAccounts@2023-04-15' = { + name: replace('${databaseAccountName}-${resourceToken}', '-', '') + location: location + kind: 'GlobalDocumentDB' + tags: tags + properties: { + consistencyPolicy: { defaultConsistencyLevel: 'Session' } + locations: [ + { + locationName: location + failoverPriority: 0 + } + ] + databaseAccountOfferType: 'Standard' + } + + resource db 'sqlDatabases@2023-04-15' = [for name in databases: { + name: '${name}' + location: location + tags: tags + properties: { + resource: { + id: '${name}' + } + } + }] +} + +var primaryMasterKey = cosmosDb.listKeys(cosmosDb.apiVersion).primaryMasterKey + +resource vault 'Microsoft.KeyVault/vaults@2023-07-01' existing = { + name: keyVaultName + + resource secret 'secrets@2023-07-01' = { + name: 'connectionString' + properties: { + value: 'AccountEndpoint=${cosmosDb.properties.documentEndpoint};AccountKey=${primaryMasterKey}' + } + } +} diff --git a/docs/deployment/azure/snippets/AppHost.Bicep/event-grid-webhook.bicep b/docs/deployment/azure/snippets/AppHost.Bicep/event-grid-webhook.bicep new file mode 100644 index 0000000000..6ccc0b0cfa --- /dev/null +++ b/docs/deployment/azure/snippets/AppHost.Bicep/event-grid-webhook.bicep @@ -0,0 +1,37 @@ +param topicName string +param webHookEndpoint string +param principalId string +param principalType string +param location string = resourceGroup().location + +// The topic name must be unique because it's represented by a DNS entry. +// must be between 3-50 characters and contain only values a-z, A-Z, 0-9, and "-". + +resource topic 'Microsoft.EventGrid/topics@2023-12-15-preview' = { + name: toLower(take('${topicName}${uniqueString(resourceGroup().id)}', 50)) + location: location + + resource eventSubscription 'eventSubscriptions' = { + name: 'customSub' + properties: { + destination: { + endpointType: 'WebHook' + properties: { + endpointUrl: webHookEndpoint + } + } + } + } +} + +resource EventGridRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(topic.id, principalId, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'd5a91429-5739-47e2-a06b-3470a27159e7')) + scope: topic + properties: { + principalId: principalId + principalType: principalType + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'd5a91429-5739-47e2-a06b-3470a27159e7') + } +} + +output endpoint string = topic.properties.endpoint diff --git a/docs/deployment/azure/snippets/AppHost.Bicep/storage-out.bicep b/docs/deployment/azure/snippets/AppHost.Bicep/storage-out.bicep new file mode 100644 index 0000000000..3e5a9074e9 --- /dev/null +++ b/docs/deployment/azure/snippets/AppHost.Bicep/storage-out.bicep @@ -0,0 +1,17 @@ +param storageName string +param location string = resourceGroup().location + +resource myStorageAccount 'Microsoft.Storage/storageAccounts@2019-06-01' = { + name: storageName + location: location + kind: 'StorageV2' + sku:{ + name:'Standard_LRS' + tier: 'Standard' + } + properties: { + accessTier: 'Hot' + } +} + +output endpoint string = myStorageAccount.properties.primaryEndpoints.blob diff --git a/docs/deployment/azure/snippets/AppHost.Bicep/storage.bicep b/docs/deployment/azure/snippets/AppHost.Bicep/storage.bicep new file mode 100644 index 0000000000..ca9508e2b1 --- /dev/null +++ b/docs/deployment/azure/snippets/AppHost.Bicep/storage.bicep @@ -0,0 +1,14 @@ +param location string = resourceGroup().location +param storageAccountName string = 'toylaunch${uniqueString(resourceGroup().id)}' + +resource storageAccount 'Microsoft.Storage/storageAccounts@2021-06-01' = { + name: storageAccountName + location: location + sku: { + name: 'Standard_LRS' + } + kind: 'StorageV2' + properties: { + accessTier: 'Hot' + } +} diff --git a/docs/deployment/azure/snippets/WebHook.Api/Program.cs b/docs/deployment/azure/snippets/WebHook.Api/Program.cs new file mode 100644 index 0000000000..bb04eb2db7 --- /dev/null +++ b/docs/deployment/azure/snippets/WebHook.Api/Program.cs @@ -0,0 +1,44 @@ +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); + +var summaries = new[] +{ + "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" +}; + +app.MapGet("/weatherforecast", () => +{ + var forecast = Enumerable.Range(1, 5).Select(index => + new WeatherForecast + ( + DateOnly.FromDateTime(DateTime.Now.AddDays(index)), + Random.Shared.Next(-20, 55), + summaries[Random.Shared.Next(summaries.Length)] + )) + .ToArray(); + return forecast; +}) +.WithName("GetWeatherForecast") +.WithOpenApi(); + +app.Run(); + +internal record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary) +{ + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); +} diff --git a/docs/deployment/azure/snippets/WebHook.Api/Properties/launchSettings.json b/docs/deployment/azure/snippets/WebHook.Api/Properties/launchSettings.json new file mode 100644 index 0000000000..9cbb8745f4 --- /dev/null +++ b/docs/deployment/azure/snippets/WebHook.Api/Properties/launchSettings.json @@ -0,0 +1,41 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:15531", + "sslPort": 44329 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5008", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7016;http://localhost:5008", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/docs/deployment/azure/snippets/WebHook.Api/WebHook.Api.csproj b/docs/deployment/azure/snippets/WebHook.Api/WebHook.Api.csproj new file mode 100644 index 0000000000..a0c1321461 --- /dev/null +++ b/docs/deployment/azure/snippets/WebHook.Api/WebHook.Api.csproj @@ -0,0 +1,14 @@ + + + + net8.0 + enable + enable + + + + + + + + diff --git a/docs/deployment/azure/snippets/WebHook.Api/WebHook.Api.http b/docs/deployment/azure/snippets/WebHook.Api/WebHook.Api.http new file mode 100644 index 0000000000..95ce1546c7 --- /dev/null +++ b/docs/deployment/azure/snippets/WebHook.Api/WebHook.Api.http @@ -0,0 +1,6 @@ +@WebHook.Api_HostAddress = http://localhost:5008 + +GET {{WebHook.Api_HostAddress}}/weatherforecast/ +Accept: application/json + +### diff --git a/docs/deployment/azure/snippets/WebHook.Api/appsettings.Development.json b/docs/deployment/azure/snippets/WebHook.Api/appsettings.Development.json new file mode 100644 index 0000000000..0c208ae918 --- /dev/null +++ b/docs/deployment/azure/snippets/WebHook.Api/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/docs/deployment/azure/snippets/WebHook.Api/appsettings.json b/docs/deployment/azure/snippets/WebHook.Api/appsettings.json new file mode 100644 index 0000000000..10f68b8c8b --- /dev/null +++ b/docs/deployment/azure/snippets/WebHook.Api/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/docs/extensibility/snippets/MailDevResource/MailDevResource.ServiceDefaults/MailDevResource.ServiceDefaults.csproj b/docs/extensibility/snippets/MailDevResource/MailDevResource.ServiceDefaults/MailDevResource.ServiceDefaults.csproj index 73896fdeed..45fc528d77 100644 --- a/docs/extensibility/snippets/MailDevResource/MailDevResource.ServiceDefaults/MailDevResource.ServiceDefaults.csproj +++ b/docs/extensibility/snippets/MailDevResource/MailDevResource.ServiceDefaults/MailDevResource.ServiceDefaults.csproj @@ -10,7 +10,7 @@ - + diff --git a/docs/frameworks/snippets/Dapr/Dapr.ServiceDefaults/Dapr.ServiceDefaults.csproj b/docs/frameworks/snippets/Dapr/Dapr.ServiceDefaults/Dapr.ServiceDefaults.csproj index 73896fdeed..45fc528d77 100644 --- a/docs/frameworks/snippets/Dapr/Dapr.ServiceDefaults/Dapr.ServiceDefaults.csproj +++ b/docs/frameworks/snippets/Dapr/Dapr.ServiceDefaults/Dapr.ServiceDefaults.csproj @@ -10,7 +10,7 @@ - + diff --git a/docs/frameworks/snippets/Orleans/OrleansServiceDefaults/OrleansServiceDefaults.csproj b/docs/frameworks/snippets/Orleans/OrleansServiceDefaults/OrleansServiceDefaults.csproj index fc30694050..ed177ee5ed 100644 --- a/docs/frameworks/snippets/Orleans/OrleansServiceDefaults/OrleansServiceDefaults.csproj +++ b/docs/frameworks/snippets/Orleans/OrleansServiceDefaults/OrleansServiceDefaults.csproj @@ -14,7 +14,7 @@ - + diff --git a/docs/fundamentals/snippets/components/AspireApp/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj b/docs/fundamentals/snippets/components/AspireApp/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj index 73896fdeed..45fc528d77 100644 --- a/docs/fundamentals/snippets/components/AspireApp/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj +++ b/docs/fundamentals/snippets/components/AspireApp/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj @@ -10,7 +10,7 @@ - + diff --git a/docs/fundamentals/snippets/healthz/Healthz.ServiceDefaults/Healthz.ServiceDefaults.csproj b/docs/fundamentals/snippets/healthz/Healthz.ServiceDefaults/Healthz.ServiceDefaults.csproj index 73896fdeed..45fc528d77 100644 --- a/docs/fundamentals/snippets/healthz/Healthz.ServiceDefaults/Healthz.ServiceDefaults.csproj +++ b/docs/fundamentals/snippets/healthz/Healthz.ServiceDefaults/Healthz.ServiceDefaults.csproj @@ -10,7 +10,7 @@ - + diff --git a/docs/fundamentals/snippets/networking/Networking.ServiceDefaults/Networking.ServiceDefaults.csproj b/docs/fundamentals/snippets/networking/Networking.ServiceDefaults/Networking.ServiceDefaults.csproj index 73896fdeed..45fc528d77 100644 --- a/docs/fundamentals/snippets/networking/Networking.ServiceDefaults/Networking.ServiceDefaults.csproj +++ b/docs/fundamentals/snippets/networking/Networking.ServiceDefaults/Networking.ServiceDefaults.csproj @@ -10,7 +10,7 @@ - + diff --git a/docs/fundamentals/snippets/params/Parameters.ServiceDefaults/Parameters.ServiceDefaults.csproj b/docs/fundamentals/snippets/params/Parameters.ServiceDefaults/Parameters.ServiceDefaults.csproj index c62a974bf4..b6ed656f07 100644 --- a/docs/fundamentals/snippets/params/Parameters.ServiceDefaults/Parameters.ServiceDefaults.csproj +++ b/docs/fundamentals/snippets/params/Parameters.ServiceDefaults/Parameters.ServiceDefaults.csproj @@ -11,7 +11,7 @@ - + diff --git a/docs/fundamentals/snippets/template/YourAppName/YourAppName.ServiceDefaults.csproj b/docs/fundamentals/snippets/template/YourAppName/YourAppName.ServiceDefaults.csproj index 73896fdeed..45fc528d77 100644 --- a/docs/fundamentals/snippets/template/YourAppName/YourAppName.ServiceDefaults.csproj +++ b/docs/fundamentals/snippets/template/YourAppName/YourAppName.ServiceDefaults.csproj @@ -10,7 +10,7 @@ - + diff --git a/docs/fundamentals/snippets/testing/AspireApp1/AspireApp1.ServiceDefaults/AspireApp1.ServiceDefaults.csproj b/docs/fundamentals/snippets/testing/AspireApp1/AspireApp1.ServiceDefaults/AspireApp1.ServiceDefaults.csproj index 73896fdeed..45fc528d77 100644 --- a/docs/fundamentals/snippets/testing/AspireApp1/AspireApp1.ServiceDefaults/AspireApp1.ServiceDefaults.csproj +++ b/docs/fundamentals/snippets/testing/AspireApp1/AspireApp1.ServiceDefaults/AspireApp1.ServiceDefaults.csproj @@ -10,7 +10,7 @@ - + diff --git a/docs/fundamentals/snippets/volumes/VolumeMounts.ServiceDefaults/VolumeMounts.ServiceDefaults.csproj b/docs/fundamentals/snippets/volumes/VolumeMounts.ServiceDefaults/VolumeMounts.ServiceDefaults.csproj index 73896fdeed..45fc528d77 100644 --- a/docs/fundamentals/snippets/volumes/VolumeMounts.ServiceDefaults/VolumeMounts.ServiceDefaults.csproj +++ b/docs/fundamentals/snippets/volumes/VolumeMounts.ServiceDefaults/VolumeMounts.ServiceDefaults.csproj @@ -10,7 +10,7 @@ - + diff --git a/docs/get-started/build-your-first-aspire-app.md b/docs/get-started/build-your-first-aspire-app.md index 3bbb39673b..637d59a221 100644 --- a/docs/get-started/build-your-first-aspire-app.md +++ b/docs/get-started/build-your-first-aspire-app.md @@ -1,7 +1,7 @@ --- title: Build your first .NET Aspire app description: Learn how to build your first .NET Aspire app using the .NET Aspire Started Application template. -ms.date: 05/13/2024 +ms.date: 06/12/2024 ms.topic: quickstart zone_pivot_groups: dev-environment --- @@ -52,6 +52,17 @@ The sample app is now ready for testing. You want to verify the following: In Visual Studio, set the **AspireSample.AppHost** project as the startup project by right-clicking on the project in the **Solution Explorer** and selecting **Set as Startup Project**. Then, press F5 to run the app. +:::zone-end +:::zone pivot="vscode,dotnet-cli" + +If you haven't already trusted the ASP.NET Core localhost certificate, you will need to trust the certificate before running the app: + +```dotnetcli +dotnet dev-certs https --trust +``` + +For more information, see [Troubleshoot untrusted localhost certificate in .NET Aspire](../troubleshooting/untrusted-localhost-certificate.md). For in-depth details about troubleshooting localhost certificates on Linux, see [ASP.NET Core: GitHub repository issue #32842](https://github.com/dotnet/aspnetcore/issues/32842). + :::zone-end :::zone pivot="vscode" diff --git a/docs/get-started/snippets/quickstart/AspireSample/AspireSample.ServiceDefaults/AspireSample.ServiceDefaults.csproj b/docs/get-started/snippets/quickstart/AspireSample/AspireSample.ServiceDefaults/AspireSample.ServiceDefaults.csproj index 73896fdeed..45fc528d77 100644 --- a/docs/get-started/snippets/quickstart/AspireSample/AspireSample.ServiceDefaults/AspireSample.ServiceDefaults.csproj +++ b/docs/get-started/snippets/quickstart/AspireSample/AspireSample.ServiceDefaults/AspireSample.ServiceDefaults.csproj @@ -10,7 +10,7 @@ - + diff --git a/docs/storage/snippets/tutorial/AspireStorage/AspireStorage.ServiceDefaults/AspireStorage.ServiceDefaults.csproj b/docs/storage/snippets/tutorial/AspireStorage/AspireStorage.ServiceDefaults/AspireStorage.ServiceDefaults.csproj index 73896fdeed..45fc528d77 100644 --- a/docs/storage/snippets/tutorial/AspireStorage/AspireStorage.ServiceDefaults/AspireStorage.ServiceDefaults.csproj +++ b/docs/storage/snippets/tutorial/AspireStorage/AspireStorage.ServiceDefaults/AspireStorage.ServiceDefaults.csproj @@ -10,7 +10,7 @@ - + diff --git a/docs/toc.yml b/docs/toc.yml index c7225d1644..4e91688657 100644 --- a/docs/toc.yml +++ b/docs/toc.yml @@ -24,6 +24,9 @@ items: - name: Overview displayName: orchestration,aspire apphost,aspire app href: fundamentals/app-host-overview.md + - name: Networking overview + displayName: inner loop,local networking + href: fundamentals/networking-overview.md - name: Use external parameters displayName: external parameters,configuration href: fundamentals/external-parameters.md @@ -53,9 +56,6 @@ items: - name: Fundamentals items: - - name: Networking overview - displayName: inner loop,local networking - href: fundamentals/networking-overview.md - name: Service discovery displayName: service discovery,service to service communication,Kubernetes,k8s href: service-discovery/overview.md @@ -199,6 +199,8 @@ items: href: database/sql-server-component-deployment.md - name: Deploy .NET Aspire + Redis href: caching/caching-components-deployment.md + - name: Use custom Bicep templates + href: deployment/azure/custom-bicep-templates.md - name: Tool-builder manifest schemas href: deployment/manifest-format.md diff --git a/docs/troubleshooting/untrusted-localhost-certificate.md b/docs/troubleshooting/untrusted-localhost-certificate.md index 7540b6cd10..e4de03081e 100644 --- a/docs/troubleshooting/untrusted-localhost-certificate.md +++ b/docs/troubleshooting/untrusted-localhost-certificate.md @@ -42,3 +42,4 @@ For more troubleshooting, see [Troubleshoot certificate problems such as certifi - [Trust the ASP.NET Core HTTPS development certificate on Windows and macOS](/aspnet/core/security/enforcing-ssl#trust-the-aspnet-core-https-development-certificate-on-windows-and-macos) - [Trust HTTPS certificate on Linux](/aspnet/core/security/enforcing-ssl##trust-https-certificate-on-linux) - [.NET CLI: dotnet dev-certs](/dotnet/core/tools/dotnet-dev-certs) +- [Trust localhost certificate on Linux](https://github.com/dotnet/aspnetcore/issues/32842)