Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ASP.NET Core best practices and guidance #42881

Merged
merged 41 commits into from
Nov 15, 2024
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
10dded8
ASP.NET Core guidance
alexwolfmsft Oct 8, 2024
ee2cb3f
draft
alexwolfmsft Oct 8, 2024
ef9c9eb
updates
alexwolfmsft Oct 8, 2024
d4ebcdb
title update
alexwolfmsft Oct 8, 2024
695a30c
title
alexwolfmsft Oct 8, 2024
6987420
progress
alexwolfmsft Oct 22, 2024
45b9c21
sample code
alexwolfmsft Oct 23, 2024
8c6bbf2
progress
alexwolfmsft Oct 23, 2024
5a2d54e
fixes
alexwolfmsft Oct 23, 2024
c61094f
fixes
alexwolfmsft Oct 23, 2024
48b9d82
added language
alexwolfmsft Oct 23, 2024
53bd17c
progress
alexwolfmsft Oct 23, 2024
1ac7f25
line numbers
alexwolfmsft Oct 23, 2024
a2e886f
fix lines
alexwolfmsft Oct 23, 2024
5c9cbee
updates
alexwolfmsft Oct 23, 2024
9ee494a
Fixes
alexwolfmsft Oct 23, 2024
530a569
tweaks
alexwolfmsft Oct 23, 2024
cc7d963
tweak
alexwolfmsft Oct 23, 2024
4130ee5
links
alexwolfmsft Oct 23, 2024
5b17c7f
Apply suggestions from code review
alexwolfmsft Oct 24, 2024
bd826ae
updates
alexwolfmsft Oct 24, 2024
b8b9e85
fix toc
alexwolfmsft Oct 24, 2024
d90b999
fix
alexwolfmsft Oct 24, 2024
568fb75
Apply suggestions from code review
alexwolfmsft Oct 24, 2024
447284f
udpate auth instructions
alexwolfmsft Oct 29, 2024
ffc02b7
Apply suggestions from code review
alexwolfmsft Nov 5, 2024
affa4e7
fixes
alexwolfmsft Nov 5, 2024
e8762bb
Merge branch 'aspnetcore-guidance' of https://github.com/alexwolfmsft…
alexwolfmsft Nov 5, 2024
5f9dbb0
fixes
alexwolfmsft Nov 5, 2024
0351996
Apply suggestions from code review
alexwolfmsft Nov 6, 2024
86221c2
Apply suggestions from code review
alexwolfmsft Nov 6, 2024
033832c
Update aspnetcore-guidance.md
alexwolfmsft Nov 7, 2024
ef78dea
Apply suggestions from code review
alexwolfmsft Nov 14, 2024
c830fe4
update to .NET 9
alexwolfmsft Nov 15, 2024
f4f995d
package updates
alexwolfmsft Nov 15, 2024
f51d348
fix build
alexwolfmsft Nov 15, 2024
270bd4d
fix link
alexwolfmsft Nov 15, 2024
5f7f61e
fix lines
alexwolfmsft Nov 15, 2024
db1fd8a
fix lines
alexwolfmsft Nov 15, 2024
80e628e
line number
alexwolfmsft Nov 15, 2024
df32fae
Apply suggestions from code review
alexwolfmsft Nov 15, 2024
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
2 changes: 2 additions & 0 deletions docs/azure/TOC.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@
href: ./sdk/authentication/additional-methods.md
- name: Credential chains
href: ./sdk/authentication/credential-chains.md
- name: ASP.NET Core guidance
href: ./sdk/aspnetcore-guidance.md
- name: Resource management
href: ./sdk/resource-management.md
- name: Dependency injection
Expand Down
144 changes: 144 additions & 0 deletions docs/azure/sdk/aspnetcore-guidance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
---
title: Best practices for using the Azure SDK with ASP.NET Core
description: Learn best practices and the steps to properly implement the Azure SDK for .NET in your ASP.NET Core apps.
ms.topic: conceptual
ms.custom: devx-track-dotnet
ms.date: 10/22/2024
---

# Use the Azure SDK for .NET in ASP.NET Core apps

The Azure SDK for .NET enables ASP.NET Core apps to integrate with many different Azure services. In this article, you'll learn best practices and the steps to adopt the Azure SDK for .NET in your ASP.NET Core apps. You'll learn how to:

- Register services for dependency injection.
alexwolfmsft marked this conversation as resolved.
Show resolved Hide resolved
- Authenticate to Azure without using passwords or secrets.
- Implement centralized, standardized configuration.
- Configure common web app concerns such as logging and retries.

## Explore common Azure SDK client libraries

ASP.NET Core apps that connect to Azure services generally depend on the following Azure SDK client libraries:

- [Microsoft.Extensions.Azure](https://www.nuget.org/packages/Microsoft.Extensions.Azure) provides helper methods to register clients with the dependency injection service collection and handles various concerns for you, such as setting up logging, handling DI service lifetimes, and authentication credential management.
- [Azure.Identity](https://www.nuget.org/packages/Azure.Identity) enables Microsoft Entra ID authentication support across the Azure SDK. It provides a set of [TokenCredential](/dotnet/api/azure.core.tokencredential?view=azure-dotnet) implementations to construct Azure SDK clients that support Microsoft Entra authentication.
- `Azure.<service-namespace>` libraries, such as [Azure.Storage.Blob](https://www.nuget.org/packages/Azure.Storage.Blobs) and [Azure.Messaging.ServiceBus](https://www.nuget.org/packages/Azure.Messaging.ServiceBus), provide service clients and other types to help you connect to and consume specific Azure services. For a complete inventory of these libraries, see [Libraries using Azure.Core](/dotnet/azure/sdk/packages#libraries-using-azurecore).
alexwolfmsft marked this conversation as resolved.
Show resolved Hide resolved

In the sections ahead, you'll explore how to implement an ASP.NET Core application that uses these libraries.

## Register Azure SDK clients with the DI service collection

The Azure SDK for .NET client libraries provide service clients to connect your app to Azure services such as Azure Blob Storage and Azure Key Vault. Register these services with the dependency container in the `Program.cs` file of your app to make them available via [dependency injection](/aspnet/core/fundamentals/dependency-injection).

Complete the following steps to register the services you need:

1. Add the [Microsoft.Extensions.Azure](https://www.nuget.org/packages/Microsoft.Extensions.Azure) package:

```dotnetcli
dotnet add package Microsoft.Extensions.Azure
```

2. Add the relevant `Azure.*` service client packages:

```dotnetcli
dotnet add package Azure.Security.KeyVault.Secrets
dotnet add package Azure.Storage.Blobs
dotnet add package Azure.Messaging.ServiceBus
```

3. In the `Program.cs` file of your app, invoke the <xref:Microsoft.Extensions.Azure.AzureClientServiceCollectionExtensions.AddAzureClients%2A> extension method from the `Microsoft.Extensions.Azure` library to register a client to communicate with each Azure service. Some client libraries provide additional subclients for specific subgroups of Azure service functionality. You can register such subclients for dependency injection via the <xref:Microsoft.Extensions.Azure.AzureClientFactoryBuilder.AddClient%2A> extension method.
alexwolfmsft marked this conversation as resolved.
Show resolved Hide resolved

:::code language="csharp" source="snippets/aspnetcore-guidance/BlazorSample/Program.cs" range="11-30" highlight="4-7,13-15":::

4. Inject the registered clients into your ASP.NET Core app components, services, or API endpoint:

<!-- markdownlint-disable MD023 -->
## [Minimal API](#tab/api)

:::code language="csharp" source="snippets/aspnetcore-guidance/MinApiSample/Program.cs" range="44-59" highlight="1,4,5":::

<!-- markdownlint-disable MD023 -->
## [Blazor](#tab/blazor)

:::code language="razor" source="snippets/aspnetcore-guidance/BlazorSample/Components/Pages/Home.razor" range="1-28" highlight="5,21-22":::

---

For more information, see [Dependency injection with the Azure SDK for .NET](/dotnet/azure/sdk/dependency-injection).

## Authenticate using Microsoft Entra ID

Token-based authentication with [Microsoft Entra ID](/entra/fundamentals/whatis) is the recommended approach to authenticate requests to Azure services. To authorize those requests, [Azure role-based access control (RBAC)](/azure/role-based-access-control/overview) manages access to Azure resources based on a user's Microsoft Entra identity and assigned roles.

Use the [Azure Identity](/dotnet/api/overview/azure/identity-readme) library for the aforementioned token-based authentication support. The library provides classes such as [`DefaultAzureCredential`](/dotnet/api/azure.identity.defaultazurecredential) to simplify configuring secure connections. `DefaultAzureCredential` supports multiple authentication methods and determines which method should be used at runtime. This approach enables your app to use different authentication methods in different environments (local vs. production) without implementing environment-specific code. Visit the [Authentication](/dotnet/azure/sdk/authentication) section of the Azure SDK for .NET docs for more details on these topics.

> [!NOTE]
> Many Azure services also allow you to authorize requests using keys. However, this approach should be used with caution. Developers must be diligent to never expose the access key in an unsecure location. Anyone who has the access key can authorize requests against the associated Azure resource.

1. Add the [Azure.Identity](https://www.nuget.org/packages/Azure.Identity) package:

```dotnetcli
dotnet add package Azure.Identity
```

1. In the `Program.cs` file of your app, invoke the <xref:Microsoft.Extensions.Azure.AzureClientFactoryBuilder.UseCredential%2A> extension method from the `Microsoft.Extensions.Azure` library to set a shared `DefaultAzureCredential` instance for all registered Azure service clients:
alexwolfmsft marked this conversation as resolved.
Show resolved Hide resolved

:::code language="csharp" source="snippets/aspnetcore-guidance/BlazorSample/Program.cs" range="11-30" highlight="19":::

`DefaultAzureCredential` discovers available credentials in the current environment and uses them to authenticate to Azure services. For the order and locations in which `DefaultAzureCredential` scans for credentials, see [DefaultAzureCredential overview](/dotnet/azure/sdk/authentication/credential-chains?tabs=dac#defaultazurecredential-overview).
alexwolfmsft marked this conversation as resolved.
Show resolved Hide resolved

## Apply configurations

Azure SDK service clients support configurations to change their default behaviors. There are two ways to configure service clients:

- [JSON configuration files](/dotnet/core/extensions/configuration-providers#json-configuration-provider) are generally the recommended approach because they simplify managing differences in app deployments between environments.
alexwolfmsft marked this conversation as resolved.
Show resolved Hide resolved
- Inline code configurations can be applied when you register the service client. For example, in the [Register clients and subclients](#register-service-clients) section, you explicitly passed the URI variables to the client constructors.

`IConfiguration` precedence rules are respected by the `Microsoft.Extensions.Azure` extension methods, which are detailed in the [Configuration Providers](/dotnet/core/extensions/configuration#configuration-providers) documentation.

Complete the steps in the following sections to update your app to use JSON file configuration for the appropriate environments. Use the `appsettings.Development.json` file for development settings and the `appsettings.Production.json` file for production environment settings. You can add configuration settings whose names are public properties on the <xref:Azure.Core.ClientOptions> class to the JSON file.

### Configure registered services

1. Update the `appsettings.<environment>.json` file in your app with the highlighted service configurations:

:::code language="json" source="snippets/aspnetcore-guidance/MinApiSample/appsettings.Development.json" highlight="19-27":::

In the preceding JSON sample:

- The top-level key names, `KeyVault`, `ServiceBus`, and `Storage`, are arbitrary names used to reference the config sections from your code. You will pass these names to `AddClient` extension methods to configure a given client. All other key names map to specific client options, and JSON serialization is performed in a case-insensitive manner.
- The `KeyVault:VaultUri`, `ServiceBus:Namespace`, and `Storage:ServiceUri` key values map to the arguments of the <xref:Azure.Security.KeyVault.Secrets.SecretClient.%23ctor(System.Uri,Azure.Core.TokenCredential,Azure.Security.KeyVault.Secrets.SecretClientOptions)?displayProperty=name>, <xref:Azure.Messaging.ServiceBus.ServiceBusClient.%23ctor(System.String)?displayProperty=name>, and <xref:Azure.Storage.Blobs.BlobServiceClient.%23ctor(System.Uri,Azure.Core.TokenCredential,Azure.Storage.Blobs.BlobClientOptions)?displayProperty=name> constructor overloads, respectively. The `TokenCredential` variants of the constructors are used because a default `TokenCredential` is set via the <xref:Microsoft.Extensions.Azure.AzureClientFactoryBuilder.UseCredential(Azure.Core.TokenCredential)?displayProperty=name> method call.
alexwolfmsft marked this conversation as resolved.
Show resolved Hide resolved

1. Update the the `Program.cs` file to retrieve the JSON file configurations using `IConfiguration` and pass them into your service registrations:

:::code language="csharp" source="snippets/aspnetcore-guidance/MinApiSample/Program.cs" range="13-31" highlight="4-5,7-8,11-12":::

### Configure Azure defaults and retries

You may want to change default Azure client configurations globally or for a specific service client. For example, you may want different retry settings or to use a different service API version. You can set the retry settings globally or on a per-service basis.

1. Update your configuration file to set default Azure settings, such as a new default retry policy that all registered Azure clients will use:

:::code language="json" source="snippets/aspnetcore-guidance/MinApiSample/appsettings.Development.json" highlight="9-18":::

2. In the `Program.cs` file, the `ConfigureDefaults` extension method retrieves the default settings and applies them to your services:
alexwolfmsft marked this conversation as resolved.
Show resolved Hide resolved
alexwolfmsft marked this conversation as resolved.
Show resolved Hide resolved

:::code language="csharp" source="snippets/aspnetcore-guidance/MinApiSample/Program.cs" range="13-31" highlight="17-18":::

## Configure logging

The Azure SDK for .NET client libraries can log client library operations to monitor requests and responses to Azure services. Client libraries can also log a variety of other events, including retries, token retrieval, and service-specific events from various clients. When you register an Azure SDK client using the <xref:Microsoft.Extensions.Azure.AzureClientServiceCollectionExtensions.AddAzureClients%2A> extension method, the <xref:Microsoft.Extensions.Azure.AzureEventSourceLogForwarder> is registered with the dependency injection container. The `AzureEventSourceLogForwarder` forwards log messages from Azure SDK event sources to <xref:Microsoft.Extensions.Logging.ILoggerFactory> to enables you to use the standard ASP.NET Core logging configuration for logging.

The following table depicts how the Azure SDK for .NET `EventLevel` maps to the ASP.NET Core `LogLevel`. For more information on these topics and other scenarios, visit the [Logging with the Azure SDK for .NET](/dotnet/azure/sdk/logging) and [Dependency injection with the Azure SDK for .NET](/dotnet/azure/sdk/dependency-injection) pages.
alexwolfmsft marked this conversation as resolved.
Show resolved Hide resolved

| Azure SDK `EventLevel` | ASP.NET Core `LogLevel` |
|------------------------|-------------------------|
| `Critical` | `Critical` |
| `Error` | `Error` |
| `Informational` | `Information` |
| `Warning` | `Warning` |
| `Verbose` | `Debug` |
| `LogAlways` | `Information` |

You can change default log levels and other settings using the same JSON configurations outlined in the [configure authentication](#authenticate-using-microsoft-entra-id) section. For example, toggle the `ServiceBusClient` log level to `Debug` by setting the `Logging:LogLevel:Azure.Messaging.ServiceBus` key as follows:
alexwolfmsft marked this conversation as resolved.
Show resolved Hide resolved

:::code language="json" source="snippets/aspnetcore-guidance/MinApiSample/appsettings.Development.json" highlight="6":::
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Azure.Messaging.ServiceBus" Version="7.18.2" />
<PackageReference Include="Azure.Security.KeyVault.Secrets" Version="4.7.0" />
<PackageReference Include="Azure.Storage.Blobs" Version="12.22.2" />
<PackageReference Include="Microsoft.Extensions.Azure" Version="1.8.0" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<base href="/" />
<link rel="stylesheet" href="bootstrap/bootstrap.min.css" />
<link rel="stylesheet" href="app.css" />
<link rel="stylesheet" href="AspNetCoreAzureSDK.styles.css" />
<link rel="icon" type="image/png" href="favicon.png" />
<HeadOutlet />
</head>

<body>
<Routes />
<script src="_framework/blazor.web.js"></script>
</body>

</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
@inherits LayoutComponentBase

<div class="page">
<div class="sidebar">
<NavMenu />
</div>

<main>
<div class="top-row px-4">
<a href="https://learn.microsoft.com/aspnet/core/" target="_blank">About</a>
</div>

<article class="content px-4">
@Body
</article>
</main>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
.page {
position: relative;
display: flex;
flex-direction: column;
}

main {
flex: 1;
}

.sidebar {
background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
}

.top-row {
background-color: #f7f7f7;
border-bottom: 1px solid #d6d5d5;
justify-content: flex-end;
height: 3.5rem;
display: flex;
align-items: center;
}

.top-row ::deep a, .top-row ::deep .btn-link {
white-space: nowrap;
margin-left: 1.5rem;
text-decoration: none;
}

.top-row ::deep a:hover, .top-row ::deep .btn-link:hover {
text-decoration: underline;
}

.top-row ::deep a:first-child {
overflow: hidden;
text-overflow: ellipsis;
}

@media (max-width: 640.98px) {
.top-row {
justify-content: space-between;
}

.top-row ::deep a, .top-row ::deep .btn-link {
margin-left: 0;
}
}

@media (min-width: 641px) {
.page {
flex-direction: row;
}

.sidebar {
width: 250px;
height: 100vh;
position: sticky;
top: 0;
}

.top-row {
position: sticky;
top: 0;
z-index: 1;
}

.top-row.auth ::deep a:first-child {
flex: 1;
text-align: right;
width: 0;
}

.top-row, article {
padding-left: 2rem !important;
padding-right: 1.5rem !important;
}
}

#blazor-error-ui {
background: lightyellow;
bottom: 0;
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
display: none;
left: 0;
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
position: fixed;
width: 100%;
z-index: 1000;
}

#blazor-error-ui .dismiss {
cursor: pointer;
position: absolute;
right: 0.75rem;
top: 0.5rem;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<div class="top-row ps-3 navbar navbar-dark">
<div class="container-fluid">
<a class="navbar-brand" href="">AspNetCoreAzureSDK</a>
</div>
</div>

<input type="checkbox" title="Navigation menu" class="navbar-toggler" />

<div class="nav-scrollable" onclick="document.querySelector('.navbar-toggler').click()">
<nav class="flex-column">
<div class="nav-item px-3">
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
<span class="bi bi-house-door-fill-nav-menu" aria-hidden="true"></span> Home
</NavLink>
</div>

<div class="nav-item px-3">
<NavLink class="nav-link" href="weather">
<span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> Weather
</NavLink>
</div>
</nav>
</div>

Loading
Loading