Skip to content

Lab 5.2 ‐ Move Pages Clientside

Roy Cornelissen edited this page Nov 17, 2024 · 3 revisions

Objectives

Now that we have a WebAPI serving our data and have authentication and authorization in place, we can further improve our application's architecture.

Let's move the rendering of our pages to the browser, and reduce the load on the server.

Steps

Making pages render client side

The process of moving our pages is pretty much the same for all pages. We'll take the AddRoom.razor page as an example.

If you want your page to render in the browser, you have to set its @rendermode to InteractiveWebAssembly. Once you do that, you have to move the page to the HmsBlazor.Client project, because it must be included in the client bundle that is downloaded by the browser.

  • Move the AddRoom.razor page to the Pages folder in the HmsBlazor.Client project
  • Change its @rendermode to InteractiveWebAssembly

Your page will now be rendered in the browser.

Move Entities to Client project

You will now find that the AddRoom.razor page causes compilation errors. That is because the Room entity is not known client side. The easiest way to solve that is to move the Entities folder from the HmsBlazor (server side) project to the HmsBlazor.Client project. Now, these classes will be available both client and server side.

Now, when you test your app and navigate to the /addroom page, you will notice an error. In the browser's error console (press F12 in your browser to reveal the Developer Tools), you will find an error that says:

Cannot provide a value for property 'Http' on type 'HmsBlazor.Client.Pages.AddRoom'. There is no registered service of type 'System.Net.Http.HttpClient'

We'll fix this in the next step.

Wiring up HttpClient in the Client app

Now that your page lives in the browser, it has a different runtime: the Blazor WebAssembly runtime. The services you registered for Dependency Injection in the HmsBlazor server project don't exist here, so you will also have to register them in the client.

  • Add the Microsoft.Extensions.Http Nuget package the HmsBlazor.Client.csproj inside the <PackageReferences> node:
    <PackageReference Include="Microsoft.Extensions.Http" Version="8.0.1" />
  • Open the Program.cs of the HmsBlazor.Client project and add the following code:
builder.Services.AddHttpClient("HmsApi", (sp, client) =>{
    var navigation = sp.GetRequiredService<NavigationManager>();
    client.BaseAddress = new Uri(navigation.BaseUri);
});

builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>()
    .CreateClient("HmsApi"));

This will setup an HttpClient configured for calling the HMS WebAPI. On the client side, we can use the NavigationManager to resolve the base address the page is running on, so we don't have to put anything in the appsettings.json.

Note that even though the code looks very similar, not all services or types are always available both on the server side and in WebAssembly.

  • Test your application again; the /addroom page should work again.

Secure your API

Since we added authentication and authorization to our Blazor app using EntraID, we can now also secure our WebAPI.

  • In the RoomController in the HmsBlazor server project, add an [Authorize] attribute above the class definition:
[Route("api/[controller]")]
[ApiController]
[Authorize]
public class RoomController ...

For more specific authorization, add an attribute above the AddRoom, UpdateRoom and DeleteRoom methods:

[Authorize(Roles="Role.Owner")]

This will instruct the ASP.NET authorization middleware to only allow authenticated users that have the Role.Owner role in their claimset. This means that the hms_manager user will not be able to call the APIs to manage rooms. The API call will be redirected to the AccessDenied page we created earlier.

Since we have authentication setup in our Blazor application, and the Web API lives on the same URL / base address as the web app, the authentication middleware will send authentication cookies to the server on each request. This way, we have transparent and out-of-the box authentication, and we can identify the user on the server.

Secure the API further

For completeness, add an [Authorize] attribute on all API controller classes. Now, we also have to tweak the HttpClient that lives on the server side a bit. In Program.cs, find the registration of the HttpCLient and replace it with the following:

// enables HttpClientFactory.CreateClient()
builder.Services.AddHttpClient("HmsApi", 
    client => client.BaseAddress = new Uri(baseAddress))
     .AddMicrosoftIdentityAppAuthenticationHandler("HmsApi", entraConfig);

The addition of .AddMicrosoftIdentityAppAuthenticationHandler("HmsApi", entraConfig) will inject a DelegatingHandler into the HttpClient which will make it fetch an access token for the API before executing the request. It uses the values from the EntraID config section for authentication. The ClientSecret you configured earlier in this lab is important for this!

Move all pages (except one) client side

Finally, nove all pages from the server to the client project, EXCEPT Reservation.razor!

Make sure to change the @rendermode to ``InteractiveWebAssembly`:

@rendermode=InteractiveServer

becomes:

@rendermode=InteractiveWebAssembly

Now all pages will work again and will be able to call the API from the client side using authentication. In addition Reservation.razor should be able to call the APIs from the server side.

⚠️ Do not move Reservation.razor to the client project. We have some interesting plans for that in the next lab, which requires code to run on the server.