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, чтобы сделать их доступными для всех страниц, может быть легко выполнено с помощью VisibleAPI, который является свойством двусторонней привязки. Мы также подготовили образец для ознакомления,

Фрагмент кода: 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)]

Надеюсь это поможет...

Другие вопросы по тегам