آرمان
آرمان
خواندن ۴ دقیقه·۴ سال پیش

رفع مشکل redirect شدن blazor webassembly بعد از فراخوانی api

مقدمه

خوب برم سر اصل مطلب اگر پست های قبلی blazor دیده باشید من در مورد این ها صحبت کردم:

https://virgool.io/@espiar/%D9%86%D9%88%D8%B4%D8%AA%D9%86-%D8%AA%D8%B3%D8%AA-%D9%88%D8%A7%D8%AD%D8%AF-%D8%A8%D8%B1%D8%A7%DB%8C-blazor-webassembly-aghnyjk6vxeg
https://virgool.io/@espiar/%D8%B3%D9%81%D8%A7%D8%B1%D8%B4%DB%8C-%DA%A9%D8%B1%D8%AF%D9%86-identityserver4-ui-vyyncneeck6b
https://virgool.io/@espiar/%D8%B3%D9%81%D8%A7%D8%B1%D8%B4%DB%8C-%DA%A9%D8%B1%D8%AF%D9%86-identity-server-cgphbndnln2x


البته این دو تا آخری صرفا برای Blazor نیست ولی اگر با blazor webassembly کاردارید (بخصوص با تنظیمات پیش فرض) حتما با این دو پست همکار خواهید داشت!

برای پایان مقدمه هم باید بگم چیزی که می خواهم الان بگم حدود 1 الی 2 هفته طول کشید که فقط بفهمم که مشکل "redirect شدن" است!

بریم سر اصل مطلب

تنظیم پیش فرض blazor در فایل Program.cs پروژه Client به صورت زیر هست :

builder.Services.AddHttpClient(&quotWeb.ServerAPI&quot, 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[]>(&quotWeatherForecast&quot); } 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 فراخوانی کردیم. این روش چند مزیت دارد:

  • کلاس ما بطور خودکار در DI ثبت میشود و می توانیم به راحتی از تزریق وابستگی استفاده کنیم.
  • ما میتونم یک سری متد به کلاس بالا اضافه کنیم و از آن به راحتی استفاده کنیم.(کسپوله کردن یا wrapper کردن)


در نهایت خط کد زیر را در فایل 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>(&quotapp&quot); builder.Services.AddHttpClient(&quotWeb.ServerAPI&quot, 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(&quotWeb.ServerAPI&quot)); builder.Services.AddApiAuthorization(); await builder.Build().RunAsync(); } }

برای استفاده در کامپوننت هم باید به صورت زیر استفاده شود:

@inject PublicClient PublicClient ... @code { private async Task GetSomethingFromAPI() { var result = PublicClient.Client.GetFromJsonAsync<Something>(&quot/api/something&quot); } }

دقت کنید نیازی به الگوی 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>(&quot/api/something&quot); return result; } }

و نحوه استفاده هم:

@inject PublicClient PublicClient ... @code { private async Task GetSomethingFromAPI() { var result = PublicClient.GetSomething(); } }

منظورم از کپسوله کردن یا wrapper هم همین مثال بالا بود.

منبع این و این

موفق و پیروز باشید.

blazor webassembly
یک برنامه نویس که هرآنچه را که یاد میگیرد در دفترچه یادداشت ویرگولیش یادداشت میکرد(!) حتی یک خط ! تا درصورت نیاز به آن رجوع کند...
شاید از این پست‌ها خوشتان بیاید