Skip to content

Commit eb42369

Browse files
committed
Updated instructions
1 parent 428a059 commit eb42369

5 files changed

+95
-97
lines changed
Loading

powerapps-docs/developer/common-data-service/walkthrough-blazor-webassembly-single-tenant.md

Lines changed: 95 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -58,24 +58,11 @@ Let’s make sure that your environment is configured properly, and you understa
5858

5959
### Get the CDS Web API URI
6060

61-
> [!NOTE]
62-
> These instructions were valid when this content was written. The steps may change in the future.
63-
64-
1. When you have an environment with the database you want to connect to, select the **Settings** (Gear) icon and choose **Advanced settings**.
65-
66-
:::image type="content" source="media/blazor-webassembly-walkthrough-advanced-settings.png" alt-text="Navigating to the Advanced settings are from the maker portal":::
67-
68-
1. The Dynamics 365 settings app will open. Select **Settings** > **Customizations**.
61+
You will need the Instance Web API Service Root URL. This is found on the Developer Resources page of your CDS environment.
6962

70-
:::image type="content" source="media/blazor-webassembly-walkthrough-settings-customizations.png" alt-text="Navigating to the customizations area in the Dynamics 365 settings area.":::
63+
Follow the instructions found in [View or download developer resources](view-download-developer-resources.md) to copy the Url.
7164

72-
1. Then select **Developer Resources**.
73-
74-
:::image type="content" source="media/blazor-webassembly-walkthrough-developer-resources.png" alt-text="Navigating to the developer resources page.":::
75-
76-
1. Then copy the **Instance Web API Service Root URL**. You will need this in the [Step 3: Apply code changes](#step-3-apply-code-changes).
77-
78-
:::image type="content" source="media/blazor-webassembly-walkthrough-instance-web-api.png" alt-text="The developer resources page showing the Instance Web API URL":::
65+
It will look something like this: `https://yourorgname.api.crm.dynamics.com/api/data/v9.1/`
7966

8067
### Navigate to the Azure Active Directory portal
8168

@@ -101,9 +88,14 @@ The [Secure an ASP.NET Core Blazor WebAssembly standalone app with Azure Active
10188
These steps will describe how to create an app registration in AAD and run a .NET Core CLI command to generate the scaffold for the basic app with support for AAD authentication.
10289

10390
> [!NOTE]
104-
> At this time, you must use the .NET Core CLI command to generate the app. There is no template for this app when creating a project using Visual Studio.
91+
> At this time, you must use the .NET Core CLI command to generate the app. There is no template for this specific type of app when creating a project using Visual Studio.
92+
93+
Go to [Secure an ASP.NET Core Blazor WebAssembly standalone app with Azure Active Directory](/aspnet/core/security/blazor/webassembly/standalone-with-azure-active-directory) and follow the instructions there to generate the basic app template.
10594

106-
The rest of the content in this section provides supplemental information to assist in completing the steps described in the [Secure an ASP.NET Core Blazor WebAssembly standalone app with Azure Active Directory](/aspnet/core/security/blazor/webassembly/standalone-with-azure-active-directory) topic.
95+
> [!NOTE]
96+
> The rest of the content in this section provides supplemental information to assist in completing the steps described in the [Secure an ASP.NET Core Blazor WebAssembly standalone app with Azure Active Directory](/aspnet/core/security/blazor/webassembly/standalone-with-azure-active-directory) topic.
97+
>
98+
> You may want to review this as you complete those steps, but it is not required. When you are done, come back here and start with [Step 3 Grant API permissions](#step-3-grant-api-permissions).
10799
108100
Registering the application involves completing a form. The default value for the Redirect URI includes a placeholder for the port value. You must replace the placeholder with a number value to complete the registration, for example, just add `1111` for now. You can provide the randomly generated port value later after you open the project in Visual Studio. See [Update callback URL](#update-callback-url).
109101

@@ -158,7 +150,7 @@ Now that the Redirect URI has been updated; you should be able to press F5 in Vi
158150

159151
At this point, all the capabilities of the app work whether you log-in or not. Only members of the AAD tenant can log in.
160152

161-
### Grant API permissions
153+
## Step 3: Grant API permissions
162154

163155
To connect to CDS, you must configure permissions for the app to connect.
164156

@@ -183,12 +175,24 @@ To connect to CDS, you must configure permissions for the app to connect.
183175

184176
:::image type="content" source="media/blazor-webassembly-walkthrough-grant-admin-consent.png" alt-text="The button showing the optional button to grant admin consent for the registered application.":::
185177

186-
## Step 3: Apply code changes
178+
## Step 4: Apply code changes
187179

188180
Apply changes to the following files to enable displaying CDS data in the application.
189181

190182
### \wwwroot\appsettings.json
191183

184+
You will find that this file already has configuration information generated by the template with information about the application registered in Azure AD. It will look like this:
185+
186+
```json
187+
{
188+
"AzureAd": {
189+
"Authority": "https://login.microsoftonline.com/22222222-2222-2222-2222-222222222222",
190+
"ClientId": "11111111-1111-1111-1111-111111111111",
191+
"ValidateAuthority": true
192+
}
193+
}
194+
```
195+
192196
Update the file to include a new `CDSWebAPI` section that includes the root of the **Instance Web API Service Root URL** you copied in the [Get the CDS Web API URI](#get-the-cds-web-api-uri) step.
193197

194198
> [!NOTE]
@@ -211,41 +215,39 @@ Update the file to include a new `CDSWebAPI` section that includes the root of t
211215

212216
### Program.cs
213217

214-
1. Comment out or remove the following line:
218+
1. Install the `Microsoft.Extensions.Http` NuGet package.
219+
1. In the solution explorer, right-click the project and select **Manage NuGet Packages...**.
220+
1. Select **Browse** and search for `Microsoft.Extensions.Http`.
221+
1. Install the latest version of the package.
215222

216-
```csharp
217-
builder.Services.AddTransient(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
218-
```
219-
> [!NOTE]
220-
> This line enables an HttpClient to access JSON data within the web site for the Weather Forecast functionality. Changes in this walkthrough will break this functionality
223+
:::image type="content" source="media/blazor-webassembly-walkthrough-install-microsoft.extensions.http-nuget-package.png" alt-text="Install the required NuGet package":::
221224

222-
1. Add the following code to replace the line commented out in step 1. This code will read configuration information from appsettings.json and enable an HttpClient to use for CDS Data.
225+
1. Add the following code below the line that starts with `builder.Services.AddTransient(sp => new HttpClient...`
223226

224227
```csharp
228+
// Get configuration data about the Web API set in wwwroot/appsettings.json
225229
var CDSWebApiConfig = builder.Configuration.GetSection("CDSWebAPI");
226230
var resourceUrl = CDSWebApiConfig.GetSection("ResourceUrl").Value;
227231
var version = CDSWebApiConfig.GetSection("Version").Value;
228232
var timeoutSeconds = int.Parse(CDSWebApiConfig.GetSection("TimeoutSeconds").Value);
229233

230-
builder.Services.AddTransient(sp => new HttpClient
234+
// Create an named definition of an HttpClient that can be created in a component page
235+
// using IHttpClientFactory.CreateClient("CDSClient")
236+
builder.Services.AddHttpClient("CDSClient", client =>
231237
{
232-
BaseAddress = new Uri($"{resourceUrl}/api/data/{version}/"),
233-
Timeout = TimeSpan.FromSeconds(timeoutSeconds)
234-
235-
}
236-
);
238+
// See https://docs.microsoft.com/powerapps/developer/common-data-service/webapi/compose-http-requests-handle-errors
239+
client.BaseAddress = new Uri($"{resourceUrl}/api/data/{version}/");
240+
client.Timeout = TimeSpan.FromSeconds(timeoutSeconds);
241+
client.DefaultRequestHeaders.Add("OData-Version", "4.0");
242+
client.DefaultRequestHeaders.Add("OData-MaxVersion", "4.0");
243+
});
237244
```
238245

239-
1. Add the the second line within the section where MSAL authentication options are configured. This will include access to CDS data to the scope of accesstoken.
246+
1. Add the following code below the line that starts with `builder.Configuration.Bind("AzureAd" ...`
240247

241248
```csharp
242-
builder.Services.AddMsalAuthentication(options =>
243-
{
244-
builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
245-
246-
options.ProviderOptions.DefaultAccessTokenScopes.Add($"{resourceUrl}/user_impersonation");
247-
248-
});
249+
// Add access to CDS to the scope of the access token when the user signs in
250+
options.ProviderOptions.DefaultAccessTokenScopes.Add($"{resourceUrl}/user_impersonation");
249251
```
250252

251253
### Add \Pages\FetchAccounts.razor
@@ -260,42 +262,18 @@ This is a new page that will display the account information.
260262
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
261263
@using System.Net.Http.Headers
262264
@using System.Net.Http.Json
265+
@using Microsoft.Extensions.Logging;
266+
@using System.Text.Json.Serialization;
263267
@inject IAccessTokenProvider TokenProvider
264-
@inject HttpClient Http
268+
@inject IHttpClientFactory ClientFactory
269+
@inject ILogger<FetchAccounts> logger;
265270

266271
<AuthorizeView>
272+
@*Only show the list if the user is signed in and authorized*@
267273
<Authorized>
268274
<h3>Fetch Accounts</h3>
269275

270-
@if (accounts == null)
271-
{
272-
<p><em>Loading...</em></p>
273-
<p>@message</p>
274-
@if (error != null)
275-
{
276-
<h3>Error:</h3>
277-
<table>
278-
<tr>
279-
<td>Status Code: </td>
280-
<td>@error.statuscode</td>
281-
</tr>
282-
<tr>
283-
<td>Reason: </td>
284-
<td>@error.reason</td>
285-
</tr>
286-
<tr>
287-
<td>Code: </td>
288-
<td>@error.error.code</td>
289-
</tr>
290-
<tr>
291-
<td>Message: </td>
292-
<td>@error.error.message</td>
293-
</tr>
294-
</table>
295-
296-
}
297-
}
298-
else
276+
@if (accounts != null)
299277
{
300278
<table class="table">
301279
<thead>
@@ -344,7 +322,10 @@ This is a new page that will display the account information.
344322
}
345323
</tbody>
346324
</table>
347-
325+
}
326+
else
327+
{
328+
<p><em>@message</em></p>
348329
}
349330
</Authorized>
350331
<NotAuthorized>
@@ -356,60 +337,82 @@ This is a new page that will display the account information.
356337

357338
@code {
358339

340+
//The collection of Account records to display
359341
private AccountCollection accounts;
360342

361-
private string message;
343+
//An informational message
344+
private string message = "Loading...";
362345

346+
//Contains data about an error returned from the Web API
363347
private Error error;
364348

349+
// Method invoked when the component is ready to start, having received its initial parameters from its parent in the render tree.
350+
// Override this method if you will perform an asynchronous operation and want the component to refresh when that operation is completed.
365351
protected override async Task OnInitializedAsync()
366352
{
367-
353+
// Tries to get an access token for the current user with the default set of permissions.
368354
var tokenResult = await TokenProvider.RequestAccessToken();
369355

356+
// If the token request was successful
370357
if (tokenResult.TryGetToken(out var token))
371358
{
359+
//Creates an HttpClient based on the named definition found in Program.Main
360+
var client = ClientFactory.CreateClient("CDSClient");
361+
362+
//Prepare the request to get the data
372363
var request = new HttpRequestMessage()
373364
{
374365
Method = HttpMethod.Get,
375-
RequestUri = new Uri($"{Http.BaseAddress}accounts?" +
376-
$"$select=name,telephone1,address1_city&" +
377-
$"$expand=primarycontactid($select=fullname,emailaddress1)")
366+
RequestUri = new Uri($"{client.BaseAddress}accounts?" +
367+
"$select=name,telephone1,address1_city&" +
368+
"$expand=primarycontactid($select=fullname,emailaddress1)")
378369
};
370+
//Add the access token
379371
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token.Value);
380-
request.Headers.Add("OData-MaxVersion", "4.0");
381-
request.Headers.Add("OData-Version", "4.0");
372+
//Specify a JSON result is expected
382373
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
374+
//Limit the number of results to 10
383375
request.Headers.Add("Prefer", "odata.maxpagesize=10");
384376

385-
var response = await Http.SendAsync(request);
377+
//Send the request
378+
var response = await client.SendAsync(request);
386379

387380
if (response.IsSuccessStatusCode)
388381
{
389-
382+
//Parse the JSON returned into a strongly typed AccountCollection
390383
accounts = await response.Content.ReadFromJsonAsync<AccountCollection>();
391384
}
392385
else
393386
{
394-
387+
//Parse the JSON returned into a strongly typed Error
395388
error = await response.Content.ReadFromJsonAsync<Error>();
396389
error.statuscode = (int)response.StatusCode;
397390
error.reason = response.ReasonPhrase;
391+
//Display a message to the user
392+
message = "An error occurred.";
393+
//Log the details so they can be seen in the browser console
394+
logger.LogError($"{error.detail.message}");
395+
398396
}
399397

400398
}
401399
else
402400
{
401+
// Notify user that the token request was not successful
403402
message = "There was a problem authenticating.";
404403
}
405404

406405
}
407406

407+
408+
// The result will be a JSON object with an array of entities set to the value property
408409
public class AccountCollection
409410
{
410411
public Account[] value { get; set; }
411412
}
412413

414+
//Just the properties of the Account EntityType used for this sample
415+
// See https://docs.microsoft.com/dynamics365/customer-engagement/web-api/account
413416
public class Account
414417
{
415418

@@ -425,6 +428,8 @@ This is a new page that will display the account information.
425428

426429
}
427430

431+
//Just the properties of the Contact EntityType that are expanded from the Account entity
432+
// See https://docs.microsoft.com/dynamics365/customer-engagement/web-api/contact
428433
public class Contact
429434
{
430435

@@ -433,14 +438,18 @@ This is a new page that will display the account information.
433438
public string emailaddress1 { get; set; }
434439
}
435440

441+
// Contains the error data returned by the Web API and the HttpMessageResponse
436442
public class Error
437443
{
438-
public ErrorDetail error { get; set; }
444+
[JsonPropertyName("error")]
445+
public ErrorDetail detail { get; set; }
439446
public int statuscode { get; set; }
440447
public string reason { get; set; }
441448

442449
}
443450

451+
//Contains data from the Web API
452+
//See https://docs.microsoft.com/powerapps/developer/common-data-service/webapi/compose-http-requests-handle-errors#parse-errors-from-the-response
444453
public class ErrorDetail
445454
{
446455
public string code { get; set; }
@@ -454,25 +463,14 @@ This code does the following:
454463

455464
1. Ensures that only authenticated users can view the page with data.
456465
1. Defines a table to display Account data after it is retrieved.
457-
1. Displays error information if there is an error retrieving the Account data.
458466
1. Requests an accesstoken and then uses that token with an HttpRequestMessage to retrieve data from CDS.
459467
1. Defines classes to enable strongly typed data when the JSON returned from the service is deserialized.
460468

461469
### \Shared\NavMenu.razor
462470

463-
Edit this file to remove the navigation option for **Fetch Data** and replace it with one to **Fetch Accounts** data.
464-
465-
Replace this:
466-
467-
```html
468-
<li class="nav-item px-3">
469-
<NavLink class="nav-link" href="fetchdata">
470-
<span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
471-
</NavLink>
472-
</li>
473-
```
471+
Edit this file to add the fetchaccounts razor component page.
474472

475-
With this:
473+
Add this node where ever you like within the `<ul class="nav flex-column">` element.
476474

477475
```html
478476
<li class="nav-item px-3">
@@ -482,7 +480,7 @@ With this:
482480
</li>
483481
```
484482

485-
## Step 4: Verify Behavior
483+
## Step 5: Verify it works
486484

487485
In Visual Studio, press F5 to launch the app with the code changes.
488486

0 commit comments

Comments
 (0)