Consuming web services using HTTP clients
Now that we have built and tested our Northwind service, we will learn how to call it from any .NET app using the HttpClient
class and its factory.
Understanding HttpClient
The easiest way to consume a web service is to use the HttpClient
class. However, many people use it wrongly because it implements IDisposable
, and Microsoft’s own documentation shows poor usage of it. See the book links in the GitHub repository for articles with more discussion of this.
Usually, when a type implements IDisposable
, you should create it inside a using
statement to ensure that it is disposed of as soon as possible. HttpClient
is different because it is shared, reentrant, and partially thread-safe.
The problem has to do with how the underlying network sockets must be managed. The bottom line is that you should use a single instance of it for each HTTP endpoint that you consume during the life of your application. This will allow each HttpClient
instance to have defaults set that are appropriate for the endpoint it works with while managing the underlying network sockets efficiently.
Configuring HTTP clients
Microsoft is aware of the issue of .NET developers misusing HttpClient
, and in ASP.NET Core 2.1, it introduced HttpClientFactory
to encourage best practices; that is the technique we will use.
In the following example, we will create a Northwind Blazor WebAssembly standalone project as a client for the Northwind Web API service. Let’s configure an HTTP client:
- Use your preferred code editor to open the
ModernWeb
solution and then add a new project, as defined in the following list:- Project template: Blazor WebAssembly Standalone App /
blazorwasm
- Solution file and folder:
ModernWeb
- Project file and folder:
Northwind.WebApi.WasmClient
- Authentication type: None
- Configure for HTTPS: Selected
- Progressive Web Application: Cleared
- Include sample pages: Selected
- Do not use top-level statements: Cleared
- Project template: Blazor WebAssembly Standalone App /
- In the
Northwind.WebApi.WasmClient.csproj
project file, in the package references, remove version attributes. - In the
Properties
folder, inlaunchSettings.json
, for thehttps
profile, for itsapplicationUrl
, change the random port number for HTTPS to5152
and for HTTP to5153
, as shown highlighted in the following markup:"applicationUrl": "https://localhost:5152;http://localhost:5153",
- Save changes to all modified files.
- In
Program.cs
, in the call to theAddScoped
method, add a statement to enableHttpClientFactory
with a named client to make calls to the Northwind Web API service using HTTPS on port5151
and request JSON as the default response format, as shown in the following code:builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri("https://localhost:5151/") });
- In the
Northwind.WebApi
project, inProgram.cs
, at the top of the file after the namespace imports, declare a string constant for the name of a CORS policy, as shown in the following code:const string corsPolicyName = "allowWasmClient";
- In
Program.cs
, before the call toBuild
, add CORS and configure a policy to allow HTTP calls from clients with different port numbers from the web service itself, as shown in the following code:builder.Services.AddCors(options => { options.AddPolicy(name: corsPolicyName, policy => { policy.WithOrigins("https://localhost:5152", "http://localhost:5153"); }); });
- In
Program.cs
, after the call toUseHttpsRedirection
, enable CORS with the named policy, as shown in the following code:app.UseCors(corsPolicyName);
Getting customers as JSON in a Blazor component
We can now create a client page that:
- Makes a
GET
request for customers. - Deserializes the JSON response using convenient extension methods introduced with .NET 5 in the
System.Net.Http.Json
assembly and namespace.
Let’s go:
- In the
Northwind.WebApi.WasmClient.csproj
project file, add a reference to the entity models project, as shown in the following markup:<ItemGroup> <ProjectReference Include= "..\Northwind.EntityModels.Sqlite\Northwind.EntityModels.Sqlite.csproj" /> </ItemGroup>
- In the
Northwind.WebApi.WasmClient
project, in_Imports.razor
, import the namespace for working with entity models, as shown in the following code:@using Northwind.EntityModels @* To use Customer. *@
- In the
Northwind.WebApi.WasmClient
project, in thePages
folder, add a new file namedCustomers.razor
. - In
Customers.razor
, inject the HTTP client service, and use it to call the Northwind Web API service, fetching all customers, and passing them to a table, as shown in the following markup:@attribute [StreamRendering] @page "/customers/{country?}" @inject HttpClient Http <h3> Customers @(string.IsNullOrWhiteSpace(Country) ? "Worldwide" : "in " + Country) </h3> @if (customers is null) { <p><em>Loading...</em></p> } else { <table class="table"> <thead> <tr> <th>Id</th> <th>Company Name</th> <th>Address</th> <th>Phone</th> <th></th> </tr> </thead> <tbody> @foreach (Customer c in customers) { <tr> <td>@c.CustomerId</td> <td>@c.CompanyName</td> <td> @c.Address<br /> @c.City<br /> @c.PostalCode<br /> @c.Country </td> <td>@c.Phone</td> </tr> } </tbody> </table> } @code { [Parameter] public string? Country { get; set; } private IEnumerable<Customer>? customers; protected override async Task OnParametersSetAsync() { if (string.IsNullOrWhiteSpace(Country)) { customers = await Http.GetFromJsonAsync <Customer[]>("/customers"); } else { customers = await Http.GetFromJsonAsync <Customer[]>($"/customers/in/{Country}"); } } }
- In the
Layout
folder, inNavMenu.razor
, change the Weather menu item to show customers instead, as shown in the following markup:<NavLink class="nav-link" href="customers"> <span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> Customers </NavLink>
Starting multiple projects
Up to this point, we have only started one project at a time. Now we have two projects that need to be started, a web service and a Blazor client website. In the step-by-step instructions, I will only tell you to start individual projects one at a time, but you should use whatever technique you prefer to start them.
If you are using Visual Studio
Visual Studio can start multiple projects manually one by one if the debugger is not attached, as described in the following steps:
- In Solution Explorer, right-click on the solution or any project and then select Configure Startup Projects…, or select the solution and navigate to Project | Configure Startup Projects….
- In the Solution ‘<name>’ Property Pages dialog box, select Current selection.
- Click OK.
- Select a project in Solution Explorer so that its name becomes bold.
- Navigate to Debug | Start Without Debugging or press Ctrl + F5.
- Repeat steps 2 and 3 for as many projects as you need.
If you need to debug the projects, then you must start multiple instances of Visual Studio. Each instance can start a single project with debugging.
You can also configure multiple projects to start up at the same time using the following steps:
- In Solution Explorer, right-click the solution or any project and then select Configure Startup Projects…, or select the solution and navigate to Project | Configure Startup Projects….
- In the Solution ‘<name>’ Property Pages dialog box, select Multiple startup projects, and for any projects that you want to start, select either Start or Start without debugging, as shown in Figure 15.6:
Figure 15.6: Selecting multiple projects to start up in Visual Studio
- Click OK.
- Navigate to Debug | Start Debugging or Debug | Start Without Debugging or click the equivalent buttons in the toolbar to start all the projects that you selected.
You can learn more about multi-project startup using Visual Studio at the following link: https://learn.microsoft.com/en-us/visualstudio/ide/how-to-set-multiple-startup-projects.
If you are using VS Code
If you need to start multiple projects at the command line with dotnet
, then write a script or batch file to execute multiple dotnet run
commands, or open multiple command prompt or terminal windows.
If you need to debug multiple projects using VS Code, then after you’ve started the first debug session, you can just launch another session. Once the second session is running, the user interface switches to multi-target mode. For example, in the CALL STACK, you will see both named projects with their own threads, and then the debug toolbar shows a drop-down list of sessions with the active one selected. Alternatively, you can define compound launch configurations in the launch.json
.
You can learn more about multi-target debugging using VS Code at the following link: https://code.visualstudio.com/Docs/editor/debugging#_multitarget-debugging.
Starting the web service and Blazor client projects
Now we can try out the web service with the Blazor client calling it:
- Start the
Northwind.WebApi
project and confirm that the web service is listening on ports5151
and5150
, as shown in the following output:info: Microsoft.Hosting.Lifetime[14] Now listening on: https://localhost:5151 info: Microsoft.Hosting.Lifetime[14] Now listening on: http://localhost:5150
- Start the
Northwind.WebApi.WasmClient
project and confirm that the website is listening on ports5152
and5153
, as shown in the following output:info: Microsoft.Hosting.Lifetime[14] Now listening on: https://localhost:5152 info: Microsoft.Hosting.Lifetime[14] Now listening on: http://localhost:5153
- Start Chrome and navigate to
https://localhost:5152/
. - On the home page, in the left navigation menu, click Customers, and note the list of customers, as shown in Figure 15.7:
Figure 15.7: Customers worldwide fetched from a web service
- In the command prompt or terminal for the web service, note that HTTP logging shows that a successful request was made for customers, as shown in the following output:
info: Microsoft.EntityFrameworkCore.Database.Command[20101] Executed DbCommand (20ms) [Parameters=[], CommandType='Text', CommandTimeout='30'] SELECT "c"."CustomerId", "c"."Address", "c"."City", "c"."CompanyName", "c"."ContactName", "c"."ContactTitle", "c"."Country", "c"."Fax", "c"."Phone", "c"."PostalCode", "c"."Region" FROM "Customers" AS "c" info: Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware[2] Response: StatusCode: 200 Content-Type: application/json; charset=utf-8 info: Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware[4] ResponseBody: [{"customerId":"ALFKI","companyName":"Alfreds Futterkiste","contactName":"Maria Anders","contactTitle":"Sales Representative","address":"Obere Str. 57","city":"Berlin","region":null,"postalCode":"12209","country":"Germany","phone":"030-0074321","fax":"030-0076545","orders":[]},... info: Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware[8] Duration: 1039.4409ms
- In the address bar, change the path to specify a country like
Germany
,UK
, orUSA
, for example:customers/UK
. Press Enter and note the table updates to only show UK customers. - Close Chrome and shut down the two web servers.