Blazor - привязка к свойству службы, измененной другим компонентом
Обновление (решение)
Благодаря ответу мистера Магу, у меня все получилось. Решение сделано с событиями, а также показано в официальном примере проекта FlightFinder.
Убедитесь, что вы используете singleton
:
Пример: Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<LoginService, ILoginService>();
}
LoginService.cs:
public event Action OnChange;
public async Task<bool> LoginFromLocalStorageAsync()
{
var response = await _http.PostJsonAsync<TokenResult>("/api/auth", model);
Token = response.Token;
ExpireDate = response.ExpireDate;
_http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", Token);
OnChange?.Invoke(); // Here we invoke the event
}
NavMenu.cshtml:
protected override void OnInit()
{
LoginService.OnChange += StateHasChanged;
}
Начальный вопрос
В настоящее время я пытаюсь и изучаю блейзор. у меня есть NavMenu
Компонент, имеющий несколько ссылок, одна из которых: <a href="login">Login</a>
, который должен измениться на <a onclick="@Logout">Logout</a>
как только пользователь входит в систему. Вход происходит в другой компонент (Login
Компонент) используя мой собственный сервис LoginService
,
LoginService
имеет Token
собственность для Знака на предъявителя и собственность public bool IsLoggedIn => !string.IsNullOrEmpty(Token);
, Я пытался использовать простую привязку с if-else
заявление в виде бритвы. Это не сработало, моя следующая попытка использовала StateHasChanged();
в моем Login
компонент, как только кто-то входит в систему. Также не работает (вероятно, потому что я хочу обновить NavMenu
и не Login
...)
NavMenu.cshtml:
@inject ILoginService LoginService
@if(LoginService.IsLoggedIn) {
<a href="logout">Logout</a>
}
else {
<a href="login">Login</a>
}
Login.cshtml:
<form onsubmit="@Submit">
<input type="email" placeholder="Email Address" bind="@LoginViewModel.Email" />
<input type="password" placeholder="Password" bind="@LoginViewModel.Password" />
<button type="submit">Login</button>
</form>
@functions
{
public LoginViewModel LoginViewModel { get; } = new LoginViewModel();
public async Task Submit()
{
await LoginService.LoginAsync(LoginViewModel);
}
}
LoginService.cs
public class LoginService : ILoginService
{
private readonly HttpClient _http;
public LoginService(HttpClient http) => _http = http;
public string Token { get; private set; }
public bool IsLoggedIn => !string.IsNullOrEmpty(Token);
public async Task<bool> LoginAsync(LoginViewModel model)
{
try
{
var response = await _http.PostJsonAsync<TokenResult>("/api/auth", model);
Token = response.Token;
return true;
}
catch (Exception)
{
return false;
}
}
}
К несчастью, NavMenu
остается на <a href="login">Login</a>
, Я думал об отправке сообщения NavMenu
от Login
Составная часть. Как я могу получить NavMenu
обновить свой взгляд?
3 ответа
Вы можете добавить событие в LoginService, которое вы будете вызывать при каждом изменении вашего токена.
Затем ваш компонент меню может подписаться на это событие (у вас уже есть служба LoginService) и вызвать StateHasChanged().
Это обновит представление и обновит клиент.
Ваше требование разместить компонент Spinner в MainLayout, чтобы сделать их доступными для всех страниц, может быть легко выполнено с помощью
Visible
API, который является свойством двусторонней привязки. Мы также подготовили образец для ознакомления,
Фрагмент кода: MainLayout.razor:
@using Syncfusion.Blazor.Spinner
<CascadingValue Value="@this">
<div class="page">
<div class="sidebar">
<NavMenu />
</div>
<div class="main">
<div class="top-row px-4">
<a href=http://blazor.net target="_blank" class="ml-md-auto">About</a>
</div>
<div class="content px-4">
@Body
</div>
</div>
<SfSpinner @bind-Visible="@SpinnerVisible" CssClass="e-spin-overlay">
</SfSpinner>
</div>
</CascadingValue>
@code{
public bool SpinnerVisible { get; set; } = false;
public async Task ClickHandler()
{
this.SpinnerVisible = true;
StateHasChanged();
await Task.Delay(2000);
this.SpinnerVisible = false;
StateHasChanged();
}
}
Index.razor
<div>
<SfButton @onclick="@ClickHandler">Show Spinner</SfButton>
</div>
@code{
[CascadingParameter]
public MainLayout mainLayoutObj { get; set; }
private async Task ClickHandler()
{
await mainLayoutObj.ClickHandler();
}
}
Этот метод ClickHandler можно вызывать в любом месте страницы в зависимости от использования,
Примечание: используйте
CascadingValue
в MainLayout для доступа к основному макету в любом месте тела.
Пример: https://www.syncfusion.com/downloads/support/directtrac/342190/ze/Web_spinner848284331
Пожалуйста, проверьте приведенный выше фрагмент кода и образец и сообщите нам, соответствует ли он вашим требованиям.
С уважением, Винита.
Вы не должны использовать элемент формы и не должны отправлять форму. К счастью, насколько мне известно, внутренний код Blazor должен остановить отправку с помощью protectDefault(); В любом случае, LoginAsync, вероятно, вызывается после того, как происходит обратная запись. Подумайте только об этом: с одной стороны, ваш код инициирует "обратную пересылку", с другой стороны, он отправляет http-запрос на сервер. Короче говоря, вы должны опубликовать данные формы, используя HttpClient.
данные формы:
<div>
<input type="email" placeholder="Email Address" bind="@LoginViewModel.Email" />
<input type="password" placeholder="Password" bind="@LoginViewModel.Password" />
<button type="button">Login</button>
</div>
Обратите внимание, что атрибут type для кнопки должен быть установлен в "button". Теперь, когда вы нажимаете кнопку, вызывается метод LoginAsync, из которого вы публикуете свои данные для входа на сервер.
Попробуйте следующее:
Добавьте эти фрагменты кода в свой LoginService:
[Inject]
protected LocalStorage localStorage;
// Note: LocalStorage is a library for storing data in Web Storage API. You may store your token in the LocalStorage, and retrieve it when you need to verify whether a user is authenticated.
// This method may be called from your NavMenu Component
public bool IsAuthenticated()
{
var token = localStorage.GetItem<string>("token");
return (token != null);
}
public async Task<bool> LoginAsync(LoginViewModel model)
{
try
{
var response = await _http.PostJsonAsync<TokenResult>("/api/auth", model);
Token = response.Token;
if (Token)
{
// Save the JWT token in the LocalStorage
// https://github.com/BlazorExtensions/Storage
await localStorage.SetItem<Object>("token", Token);
// Returns true to indicate the user has been logged // in and the JWT token is saved on the user browser
return true;
}
}
catch (Exception)
{
return false;
}
}
И, наконец, NavMenu.cshtml:
@inject ILoginService LoginService
@if(LoginService.IsAuthenticated()) {
<a href="logout">Logout</a>
}
else {
<a href="login">Login</a>
}
// Вы также должны установить класс Startup на клиенте следующим образом:
public void ConfigureServices(IServiceCollection services)
{
// Add Blazor.Extensions.Storage
// Both SessionStorage and LocalStorage are registered
// https://github.com/BlazorExtensions/Storage
**services.AddStorage();**
...
}
// Вообще говоря, это то, что вы должны сделать на клиенте. // На сервере у вас должен быть метод, скажем, в контроллере учетных записей, функция которого состоит в том, чтобы генерировать токен JWT, вы должны сконфигурировать промежуточное программное обеспечение JWT, чтобы аннотировать ваши контроллеры необходимым атрибутом, как для пример:
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
Надеюсь это поможет...