Skip to content

Commit bc2b0da

Browse files
authored
Merge pull request #35666 from dotnet/main
2 parents e938a62 + 0ae5ef7 commit bc2b0da

27 files changed

+794
-484
lines changed

aspnetcore/blazor/call-web-api.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,15 @@ if (builder.Environment.IsProduction())
166166
else
167167
{
168168
// Local development and testing only
169-
credential = new DefaultAzureCredential();
169+
DefaultAzureCredentialOptions options = new()
170+
{
171+
// Specify the tenant ID to use the dev credentials when running the app locally
172+
// in Visual Studio.
173+
VisualStudioTenantId = "{TENANT ID}",
174+
SharedTokenCacheTenantId = "{TENANT ID}"
175+
};
176+
177+
credential = new DefaultAzureCredential(options);
170178
}
171179
172180
builder.Services.AddDataProtection()
@@ -177,6 +185,8 @@ builder.Services.AddDataProtection()
177185
178186
`{MANAGED IDENTITY CLIENT ID}`: The Azure Managed Identity Client ID (GUID).
179187
188+
`{TENANT ID}`: Tenant ID.
189+
180190
`{APPLICATION NAME}`: <xref:Microsoft.AspNetCore.DataProtection.DataProtectionBuilderExtensions.SetApplicationName%2A> sets the unique name of this app within the data protection system. The value should match across deployments of the app.
181191
182192
`{BLOB URI}`: Full URI to the key file. The URI is generated by Azure Storage when you create the key file. Do not use a SAS.

aspnetcore/blazor/components/splat-attributes-and-arbitrary-parameters.md

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,13 @@ uid: blazor/components/attribute-splatting
1212

1313
[!INCLUDE[](~/includes/not-latest-version.md)]
1414

15-
Components can capture and render additional attributes in addition to the component's declared parameters. Additional attributes can be captured in a dictionary and then applied to an element, called *splatting*, when the component is rendered using the [`@attributes`](xref:mvc/views/razor#attributes) Razor directive attribute. This scenario is useful for defining a component that produces a markup element that supports a variety of customizations. For example, it can be tedious to define attributes separately for an `<input>` that supports many parameters.
15+
Components can capture and render additional attributes in addition to the component's declared parameters and fields. Additional attributes can be captured in a dictionary and then applied to an element, called *splatting*, when the component is rendered using the [`@attributes`](xref:mvc/views/razor#attributes) Razor directive attribute. This scenario is useful for defining a component that produces a markup element that supports a variety of customizations. For example, it can be tedious to define attributes separately for an `<input>` that supports many parameters or fields.
1616

1717
## Attribute splatting
1818

1919
In the following `Splat` component:
2020

21-
* The first `<input>` element (`id="useIndividualParams"`) uses individual component parameters.
21+
* The first `<input>` element (`id="useIndividualParams"`) uses individual component fields.
2222
* The second `<input>` element (`id="useAttributesDict"`) uses attribute splatting.
2323

2424
`Splat.razor`:
@@ -59,7 +59,7 @@ In the following `Splat` component:
5959

6060
:::moniker-end
6161

62-
The rendered `<input>` elements in the webpage are identical:
62+
Except for `id`, the rendered `<input>` elements in the webpage have identical attributes:
6363

6464
```html
6565
<input id="useIndividualParams"
@@ -75,6 +75,8 @@ The rendered `<input>` elements in the webpage are identical:
7575
size="50">
7676
```
7777

78+
Although the preceding example uses fields for the first `<input>` element (`id="useIndividualParams"`), the same behavior applies when component parameters are used.
79+
7880
## Arbitrary attributes
7981

8082
To accept arbitrary attributes, define a [component parameter](xref:blazor/components/index#component-parameters) with the <xref:Microsoft.AspNetCore.Components.ParameterAttribute.CaptureUnmatchedValues> property set to `true`:
@@ -88,7 +90,7 @@ To accept arbitrary attributes, define a [component parameter](xref:blazor/compo
8890

8991
The <xref:Microsoft.AspNetCore.Components.ParameterAttribute.CaptureUnmatchedValues> property on [`[Parameter]`](xref:Microsoft.AspNetCore.Components.ParameterAttribute) allows the parameter to match all attributes that don't match any other parameter. A component can only define a single parameter with <xref:Microsoft.AspNetCore.Components.ParameterAttribute.CaptureUnmatchedValues>. The property type used with <xref:Microsoft.AspNetCore.Components.ParameterAttribute.CaptureUnmatchedValues> must be assignable from [`Dictionary<string, object>`](xref:System.Collections.Generic.Dictionary%602) with string keys. Use of [`IEnumerable<KeyValuePair<string, object>>`](xref:System.Collections.Generic.IEnumerable%601) or [`IReadOnlyDictionary<string, object>`](xref:System.Collections.Generic.IReadOnlyDictionary%602) are also options in this scenario.
9092

91-
The position of [`@attributes`](xref:mvc/views/razor#attributes) relative to the position of element attributes is important. When [`@attributes`](xref:mvc/views/razor#attributes) are splatted on the element, the attributes are processed from right to left (last to first). Consider the following example of a parent component that consumes a child component:
93+
The position of [`@attributes`](xref:mvc/views/razor#attributes) relative to the position of element attributes is important. When [`@attributes`](xref:mvc/views/razor#attributes) are splatted on the rendered element, the attributes are processed from right to left (last to first) with the first attribute winning for any common attributes. Consider the following example of a parent component that consumes a child component, where the child sets an "`extra`" attribute and the parent component splats an "`extra`" attribute on the child component.
9294

9395
`AttributeOrderChild1.razor`:
9496

@@ -176,13 +178,13 @@ The position of [`@attributes`](xref:mvc/views/razor#attributes) relative to the
176178

177179
:::moniker-end
178180

179-
The `AttributeOrderChild1` component's `extra` attribute is set to the right of [`@attributes`](xref:mvc/views/razor#attributes). The `AttributeOrderParent1` component's rendered `<div>` contains `extra="5"` when passed through the additional attribute because the attributes are processed right to left (last to first):
181+
The `AttributeOrderChild1` component's `extra` attribute is set to the right of [`@attributes`](xref:mvc/views/razor#attributes). The `AttributeOrderParent1` component's rendered `<div>` contains `extra="5"` when passed through the additional attribute because the attributes are processed right to left (last to first) with the first "`extra`" attribute winning, which is the hard-coded `extra` HTML attribute of the `AttributeOrderParent1` component:
180182

181183
```html
182184
<div extra="5" />
183185
```
184186

185-
In the following example, the order of `extra` and [`@attributes`](xref:mvc/views/razor#attributes) is reversed in the child component's `<div>`:
187+
In the following example, the order of `extra` and [`@attributes`](xref:mvc/views/razor#attributes) is reversed in the child component's `<div>`. In this scenario, the `AttributeOrderParent2` component's rendered `<div>` contains `extra="10"` when passed through the additional attribute because the first "`extra`" attribute processed is the splatted `extra` HTML attribute from the parent component.
186188

187189
`AttributeOrderChild2.razor`:
188190

@@ -270,7 +272,7 @@ In the following example, the order of `extra` and [`@attributes`](xref:mvc/view
270272

271273
:::moniker-end
272274

273-
The `<div>` in the parent component's rendered webpage contains `extra="10"` when passed through the additional attribute:
275+
The `<div>` in the parent component's rendered webpage contains `extra="10"`:
274276

275277
```html
276278
<div extra="10" />

aspnetcore/blazor/security/account-confirmation-and-password-recovery.md

Lines changed: 45 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -78,12 +78,18 @@ For more information, see <xref:security/app-secrets>.
7878

7979
[Azure Key Vault](https://azure.microsoft.com/products/key-vault/) provides a safe approach for providing the app's client secret to the app.
8080

81-
To create a key vault and set a secret, see [About Azure Key Vault secrets (Azure documentation)](/azure/key-vault/secrets/about-secrets), which cross-links resources to get started with Azure Key Vault. To implement the code in this section, record the key vault URI and the secret name from Azure when you create the key vault and secret. When you set the access policy for the secret in the **Access policies** panel:
81+
To create a key vault and set a secret, see [About Azure Key Vault secrets (Azure documentation)](/azure/key-vault/secrets/about-secrets), which cross-links resources to get started with Azure Key Vault. For the example in this section, the secret name is "`EmailAuthKey`."
8282

83-
* Only the **Get** secret permission is required.
84-
* Select the application as the **Principal** for the secret.
83+
When establishing the key vault in the Entra or Azure portal:
8584

86-
Confirm in the Azure or Entra portal that the app has been granted access to the secret that you created for the email provider key.
85+
* Configure the key vault to use Azure role-based access control (RABC). If you aren't operating on an [Azure Virtual Network](/azure/virtual-network/virtual-networks-overview), including for local development and testing, confirm that public access on the **Networking** step is **enabled** (checked). Enabling public access only exposes the key vault endpoint. Authenticated accounts are still required for access.
86+
87+
* Create an Azure Managed Identity (or add a role to the existing Managed Identity that you plan to use) with the **Key Vault Secrets User** role. Assign the Managed Identity to the Azure App Service that's hosting the deployment: **Settings** > **Identity** > **User assigned** > **Add**.
88+
89+
> [!NOTE]
90+
> If you also plan to run an app locally with an authorized user for blob access using the [Azure CLI](/cli/azure/) or Visual Studio's Azure Service Authentication, add your developer Azure user account in **Access Control (IAM)** with the **Key Vault Secrets User** role. If you want to use the Azure CLI through Visual Studio, execute the `az login` command from the Developer PowerShell panel and follow the prompts to authenticate with the tenant.
91+
92+
To implement the code in this section, record the key vault URI (example: "`https://contoso.vault.azure.net/`", trailing slash required) and the secret name (example: "`EmailAuthKey`") from Azure when you create the key vault and secret.
8793

8894
> [!IMPORTANT]
8995
> A key vault secret is created with an expiration date. Be sure to track when a key vault secret is going to expire and create a new secret for the app prior to that date passing.
@@ -93,74 +99,66 @@ Add the following `AzureHelper` class to the server project. The `GetKeyVaultSec
9399
`Helpers/AzureHelper.cs`:
94100

95101
```csharp
96-
using Azure;
97-
using Azure.Identity;
102+
using Azure.Core;
98103
using Azure.Security.KeyVault.Secrets;
99104

100105
namespace BlazorSample.Helpers;
101106

102107
public static class AzureHelper
103108
{
104-
public static string GetKeyVaultSecret(string tenantId, string vaultUri, string secretName)
109+
public static string GetKeyVaultSecret(string vaultUri,
110+
TokenCredential credential, string secretName)
105111
{
106-
DefaultAzureCredentialOptions options = new()
107-
{
108-
// Specify the tenant ID to use the dev credentials when running the app locally
109-
// in Visual Studio.
110-
VisualStudioTenantId = tenantId,
111-
SharedTokenCacheTenantId = tenantId
112-
};
113-
114-
var client = new SecretClient(new Uri(vaultUri), new DefaultAzureCredential(options));
112+
var client = new SecretClient(new Uri(vaultUri), credential);
115113
var secret = client.GetSecretAsync(secretName).Result;
116114

117115
return secret.Value.Value;
118116
}
119117
}
120118
```
121119

122-
> [!NOTE]
123-
> The preceding example uses <xref:Azure.Identity.DefaultAzureCredential> to simplify authentication while developing apps that deploy to Azure by combining credentials used in Azure hosting environments with credentials used in local development. When moving to production, an alternative is a better choice, such as <xref:Azure.Identity.ManagedIdentityCredential>. For more information, see [Authenticate Azure-hosted .NET apps to Azure resources using a system-assigned managed identity](/dotnet/azure/sdk/authentication/system-assigned-managed-identity).
124-
125120
Where services are registered in the server project's `Program` file, obtain and bind the secret with [Options configuration](xref:fundamentals/configuration/options):
126121

127122
```csharp
128-
var tenantId = builder.Configuration.GetValue<string>("AzureAd:TenantId")!;
129-
var vaultUri = builder.Configuration.GetValue<string>("AzureAd:VaultUri")!;
130-
131-
var emailAuthKey = AzureHelper.GetKeyVaultSecret(
132-
tenantId, vaultUri, "EmailAuthKey");
133-
134-
var authMessageSenderOptions =
135-
new AuthMessageSenderOptions() { EmailAuthKey = emailAuthKey };
136-
builder.Configuration.GetSection(authMessageSenderOptions.EmailAuthKey)
137-
.Bind(authMessageSenderOptions);
138-
```
123+
TokenCredential? credential;
139124

140-
If you wish to control the environment where the preceding code operates, for example to avoid running the code locally because you've opted to use the [Secret Manager tool](#secret-manager-tool) for local development, you can wrap the preceding code in a conditional statement that checks the environment:
141-
142-
```csharp
143-
if (!context.HostingEnvironment.IsDevelopment())
125+
if (builder.Environment.IsProduction())
144126
{
145-
...
127+
credential = new ManagedIdentityCredential("{MANAGED IDENTITY CLIENT ID}");
146128
}
147-
```
148-
149-
In the `AzureAd` section of `appsettings.json` in the server project, confirm the presence of the app's Entra ID `TenantId` and add the following `VaultUri` configuration key and value, if it isn't already present:
150-
151-
```json
152-
"VaultUri": "{VAULT URI}"
153-
```
129+
else
130+
{
131+
// Local development and testing only
132+
DefaultAzureCredentialOptions options = new()
133+
{
134+
// Specify the tenant ID to use the dev credentials when running the app locally
135+
// in Visual Studio.
136+
VisualStudioTenantId = "{TENANT ID}",
137+
SharedTokenCacheTenantId = "{TENANT ID}"
138+
};
154139

155-
In the preceding example, the `{VAULT URI}` placeholder is the key vault URI. Include the trailing slash on the URI.
140+
credential = new DefaultAzureCredential(options);
141+
}
156142

157-
Example:
143+
var emailAuthKey = AzureHelper.GetKeyVaultSecret("{VAULT URI}", credential,
144+
"EmailAuthKey");
158145

159-
```json
160-
"VaultUri": "https://contoso.vault.azure.net/"
146+
var authMessageSenderOptions =
147+
new AuthMessageSenderOptions() { EmailAuthKey = emailAuthKey };
148+
builder.Configuration.GetSection(authMessageSenderOptions.EmailAuthKey)
149+
.Bind(authMessageSenderOptions);
161150
```
162151

163-
Configuration is used to facilitate supplying dedicated key vaults and secret names based on the app's environmental configuration files. For example, you can supply different configuration values for `appsettings.Development.json` in development, `appsettings.Staging.json` when staging, and `appsettings.Production.json` for the production deployment. For more information, see <xref:blazor/fundamentals/configuration>.
152+
> [!NOTE]
153+
> In non-Production environments, the preceding example uses <xref:Azure.Identity.DefaultAzureCredential> to simplify authentication while developing apps that deploy to Azure by combining credentials used in Azure hosting environments with credentials used in local development. For more information, see [Authenticate Azure-hosted .NET apps to Azure resources using a system-assigned managed identity](/dotnet/azure/sdk/authentication/system-assigned-managed-identity).
154+
>
155+
> The preceding example implies that the Managed Identity Client ID (`{MANAGED IDENTITY CLIENT ID}`), directory (tenant) ID (`{TENANT ID}`), and key vault URI (`{VAULT URI}`, example: `https://contoso.vault.azure.net/`, trailing slash required) are supplied by hard-coded values. Any or all of these values can be supplied from app settings configuration. For example, the following obtains the vault URI from the `AzureAd` node of an app settings file, and `vaultUri` can be used in the call to `GetKeyVaultSecret` in the preceding example:
156+
>
157+
> ```csharp
158+
> var vaultUri = builder.Configuration.GetValue<string>("AzureAd:VaultUri")!;
159+
> ```
160+
>
161+
> For more information, see <xref:blazor/fundamentals/configuration>.
164162
165163
## Implement `IEmailSender`
166164

0 commit comments

Comments
 (0)