blazor wasm訪問非本地的restful service


准備工作

blazor wasm正式版發布了!在嘗試使用的過程中,發現幾個小坑,跟大家分享一下,希望有所幫助。

我是通過keycloak來保護blazor和service的,如何保護service請參考我之前的隨筆,此處不再重復說明。

啟動一個新的wasm項目,包含身份認證,但不是hosted(web前端和service在同一個網址),也就是最常見的web和service分別開發和分別部署的場景,使用如下命令新建項目:

dotnet new blazorwasm -o {存放路徑} --au Individual

自動生成客戶端代碼

由於我的service采用了openapi,因此在wasm項目中,可以借助openapi代碼生成工具,自動生成客戶端代碼,節約開發時間。這個步驟不是必須的,但是個人建議這樣做。

首先,如果沒有安裝工具,先安裝工具:

dotnet tool install -g microsoft.dotnet-openapi

然后,運行命令生成客戶端代碼:

dotnet openapi add url http://localhost:5000/swagger/v1/swagger.json --output-file Weather.json

以上工具只是將json文件注冊到項目中,項目中還必須安裝NSWag.CodeGeneration.CSharp,才能正常生成客戶端代碼,為了方便大家參考,將所有需要引用的包都寫在這兒了。

<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>netstandard2.1</TargetFramework>
    <RazorLangVersion>3.0</RazorLangVersion>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="3.2.0" />
    <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Build" Version="3.2.0" PrivateAssets="all" />
    <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="3.2.0" PrivateAssets="all" />
    <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="3.2.0" />
    <PackageReference Include="Microsoft.Extensions.Http" Version="3.1.4" />
    <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
    <PackageReference Include="NSwag.ApiDescription.Client" Version="13.0.5" />
    <PackageReference Include="NSwag.CodeGeneration.CSharp" Version="13.5.0" />
    <PackageReference Include="System.Net.Http.Json" Version="3.2.0" />
  </ItemGroup>
  <ItemGroup>
    <OpenApiReference Include="Weather.json" SourceUrl="http://localhost:5000/swagger/v1/swagger.json" />
  </ItemGroup>
</Project>

為了保證代碼能夠順利生成,可以運行dotnet build命令,如果成功,Obj目錄會出現一個WeatherClient.cs文件,其中包含了model和service。因為他使用了Newtonsoft.Json,所以要添加這個包的引用。

配置oidc

模板包含了oidc的基本配置,修改配置文件中的地址和客戶端名稱即可連接到openid服務。

實際操作中遇到幾個坑:

  1. 無法獲得access token或者json解析錯誤
  2. 無法獲得用戶名
  3. 訪問service時,無法自動添加acess token到請求頭部

無法獲得access token

主要是app默認采用implicit認證流程,keycloak默認沒有開啟該流程,而且開啟后,會無法解析json。

解決方法,修改program.cs,在builder.Services.AddOidcAuthentication中添加如下代碼 

options.ProviderOptions.ResponseType = "code";
修改認證流程為標准流程。

無法獲得用戶名

keycloak使用preferred_username發送用戶名信息,而.net默認需要name字段,兩邊不匹配。

解決方法,要么修改這邊,要么修改那邊。

方法1:修改keycloak的client scopes - profile - mappers - username,修改"Token Claim Name"的內容為"name"

方法2:在builder.Services.AddOidcAuthentication中添加如下代碼 

options.UserOptions.NameClaim = "preferred_username";

訪問service時,無法自動添加acess token到請求頭部

這是最大的一個坑。

官方模板和文檔中,總是在說如何訪問自己hosted的service,但是實際使用中,app和service往往在不同的服務器上,官方文檔最這種情況作了說明,但是如果不很細致的看文檔,就容易理解錯。

最主要的一點就是:BaseAddressAuthorizationMessageHandler只能給本地地址的請求加token,並不能處理不同服務器的情況。

下面是我的解決方法:

 1 var clientName = "BlazorWithIdentity.ServerAPI";
 2 var baseUrl = builder.Configuration.GetValue<string>("ApiBaseUrl");
 3 
 4 builder.Services.AddHttpClient(clientName, client => client.BaseAddress = new Uri(baseUrl))
 5                 .AddHttpMessageHandler(sp => sp.GetRequiredService<AuthorizationMessageHandler>()
 6                     .ConfigureHandler(new [] { baseUrl }));
 7             
 8 builder.Services.AddTransient(sp => sp.GetRequiredService<IHttpClientFactory>()
 9                 .CreateClient(clientName));
10 
11 builder.Services.AddTransient<WeatherClient>(s => 
12                 new WeatherClient(null, s.GetRequiredService<IHttpClientFactory>().CreateClient(clientName)));

4行:注入名稱為clientName的客戶端,並且設置handler,對於來自baseUrl的所有請求自動添加Auth頭;

8行:注入HttpClient,在頁面上通過 @inject HttpClient Http,就可以使用自帶Auth頭部的HttpClient了;

11行:注入自動生成的WeatherClient,使用4行的HttpClient

頁面調用service訪問數據

經過前面的各種配置,頁面訪問數據就很簡單了,如下:

@page "/fetchdata"
@using Microsoft.AspNetCore.Authorization
@inject WeatherClient Client
@attribute [Authorize]

<h1>Weather forecast</h1>

<p>This component demonstrates fetching data from the server.</p>

@if (forecasts == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <table class="table">
        <thead>
            <tr>
                <th>Date</th>
                <th>Temp. (C)</th>
                <th>Temp. (F)</th>
                <th>Summary</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var forecast in forecasts)
            {
                <tr>
                    <td>@forecast.Date.Date.ToShortDateString()</td>
                    <td>@forecast.TemperatureC</td>
                    <td>@forecast.TemperatureF</td>
                    <td>@forecast.Summary</td>
                </tr>
            }
        </tbody>
    </table>
}

@code {
    private IEnumerable<WeatherForecast> forecasts;

    protected override async Task OnInitializedAsync()
    {     
        forecasts = await Client.WeatherForecastAsync();
    }
}

怎么樣,是不是很簡潔的代碼?


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM