خوب برم سر اصل مطلب اگر پست های قبلی blazor دیده باشید من در مورد این ها صحبت کردم:
البته این دو تا آخری صرفا برای Blazor نیست ولی اگر با blazor webassembly کاردارید (بخصوص با تنظیمات پیش فرض) حتما با این دو پست همکار خواهید داشت!
برای پایان مقدمه هم باید بگم چیزی که می خواهم الان بگم حدود 1 الی 2 هفته طول کشید که فقط بفهمم که مشکل "redirect شدن" است!
تنظیم پیش فرض blazor در فایل Program.cs پروژه Client به صورت زیر هست :
builder.Services.AddHttpClient("Web.ServerAPI", client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)) .AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();
دقیق تر بخواهم بگم مشکل redirect شدن از این خط شروع میشود:
.AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();
کاری که این خط میکنه این که اینکه زمانی که می خواهد درخواستی (request) را به api خارجی ارسال کند، در مرورگر دنبال Access Token می گردد. اگر این Access Token پیدا شد آن را به request اضافه میکند و به سرور می فرستد. اگر این توکن پیدا نشد یک exception ایجاد میکند، برای همین از الگوی try...catch در component استفاده میکند:
@code { private WeatherForecast[] forecasts; protected override async Task OnInitializedAsync() { try { forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast"); } catch (AccessTokenNotAvailableException exception) { exception.Redirect(); } } }
در قسمت catch این redirect اتفاق می افتد. کلا attribute هایی مانند [AllowAnonymous] یا [Authorize] چه در کامپوننت و چه در سطح api هیچ تاثیری روی این قضیه ندارند.
برای درست کردن این موضوع باید کاری کنیم که api های خارجی که نیاز به access token ندارند، blazor در مرورگر دنبال access token نگردد و آن را به request اضافه نکند.
این کار چند روش دارد که بهترین روش میگم دو روش دیگر در منبع ها هست.
برای درست کردن باید یک کلاس با نام دلخواه (که من در اینجا از PublicClient استفاده کردم) میسازیم و در آن یک :
public class PublicClient { public HttpClient Client { get; } public PublicClient(HttpClient httpClient) { Client = httpClient; } }
ما یک کلاس ساختیم که در سازنده آن یک httpClient فراخوانی کردیم. این روش چند مزیت دارد:
در نهایت خط کد زیر را در فایل Program.cs پروژه Client به صورت زیر تعریف کنیم:
builder.Services.AddHttpClient<PublicClient>(client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress));
کل فایل Program.cs پروژه Client به صورت زیر میشود:
public class Program { public static async Task Main(string[] args) { var builder = WebAssemblyHostBuilder.CreateDefault(args); builder.RootComponents.Add<App>("app"); builder.Services.AddHttpClient("Web.ServerAPI", client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)) .AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>(); builder.Services.AddHttpClient<PublicClient>(client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)); // Supply HttpClient instances that include access tokens when making requests to the server project builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("Web.ServerAPI")); builder.Services.AddApiAuthorization(); await builder.Build().RunAsync(); } }
برای استفاده در کامپوننت هم باید به صورت زیر استفاده شود:
@inject PublicClient PublicClient ... @code { private async Task GetSomethingFromAPI() { var result = PublicClient.Client.GetFromJsonAsync<Something>("/api/something"); } }
دقت کنید نیازی به الگوی try...catch هم نیست!
اگر هم خواستید متدی را به کلاس PublicClient اضافه کنید به صورت زیر هست :
public class PublicClient { private HttpClient _client public PublicClient(HttpClient httpClient) { _client = httpClient; } public async Task<SomeThing> GetSomething() { var result = await _client.GetFromJsonAsync<SomeThing>("/api/something"); return result; } }
و نحوه استفاده هم:
@inject PublicClient PublicClient ... @code { private async Task GetSomethingFromAPI() { var result = PublicClient.GetSomething(); } }
منظورم از کپسوله کردن یا wrapper هم همین مثال بالا بود.
موفق و پیروز باشید.