简体   繁体   English

Blazor-绑定到服务的属性,由另一个组件更改

[英]Blazor - Bind to a property of a service, changed by another component

Update (Solution) 更新(解决方案)

Thanks to Mister Magoo's answer I've got it working. 多亏了Magoo先生的回答,我才开始运作。 The solution is done with events and is also shown in the official sample project FlightFinder . 该解决方案通过事件完成,并且也显示在官方示例项目FlightFinder中

Make sure you use a singleton : 确保使用singleton

Example: Startup.cs 示例:Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<LoginService, ILoginService>();
}

LoginService.cs: 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: NavMenu.cshtml:

protected override void OnInit()
{
    LoginService.OnChange += StateHasChanged;
}

Initial Question 最初的问题

I'm currently trying and learning blazor. 我目前正在尝试学习火焰。 I have a NavMenu Component which has multiple links, one of them being: <a href="login">Login</a> , which should change to <a onclick="@Logout">Logout</a> as soon as the user logs in. The login happens in another component ( Login Component) using my own service LoginService . 我有一个具有多个链接的NavMenu组件,其中之一是: <a href="login">Login</a> ,该链接应尽快更改为<a onclick="@Logout">Logout</a>用户登录。使用我自己的服务LoginService在另一个组件( Login Component)中进行LoginService

LoginService has a Token property for the Bearer Token and a property public bool IsLoggedIn => !string.IsNullOrEmpty(Token); LoginService具有不记名令牌的Token属性和属性public bool IsLoggedIn => !string.IsNullOrEmpty(Token); . I tried to use a simple binding with an if-else -statement in the razor view. 我试图在剃刀视图中使用带有if-else -statement的简单绑定。 That didn't work, my next try was using StateHasChanged(); 那没有用,我的下一个尝试是使用StateHasChanged(); in my Login component, as soon as someone logs in. Didn't work either (probably because I want to update NavMenu and not Login ...) 在我的Login组件中,一旦有人登录就无法使用。(也可能是因为我想更新NavMenu而不是Login ...)

NavMenu.cshtml: NavMenu.cshtml:

@inject ILoginService LoginService 
@if(LoginService.IsLoggedIn) {
    <a href="logout">Logout</a>
}
else {
    <a href="login">Login</a>
}

Login.cshtml: 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 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;
        }
    }
}

Unfortunately, NavMenu stays on <a href="login">Login</a> . 不幸的是, NavMenu停留在<a href="login">Login</a> I was thinking about sending a message to NavMenu from the Login Component. 我正在考虑从Login组件向NavMenu发送消息。 How do I get NavMenu to update its view? 如何获取NavMenu更新其视图?

You can add an event to the LoginService, which you raise whenever your Token changes. 您可以将一个事件添加到LoginService中,每当令牌更改时便会引发该事件。

Then your menu component can subscribe to that event (you already have the LoginService injected) and call StateHasChanged(). 然后,您的菜单组件可以订阅该事件(您已经注入了LoginService)并调用StateHasChanged()。

This will refresh the view and update the client. 这将刷新视图并更新客户端。

You should not use a form element, nor should you submit the form. 您不应使用form元素,也不应提交表单。 Luckily, as far as I know, internal Blazor code should stop the submission using preventDefault(); 幸运的是,据我所知,内部Blazor代码应使用preventDefault()停止提交。 In any case, LoginAsync is probably called after the post back occurs. 无论如何,回发发生后可能会调用LoginAsync。 Just think about this: On the one hand your code initiates a "post back", on the other hand, it makes an http request to the server. 只需考虑一下:一方面,您的代码启动了一个“回发”,另一方面,它向服务器发出了一个http请求。 In short, you should post your form data employing the HttpClient. 简而言之,您应该使用HttpClient发布表单数据。

form data: 表格数据:

<div>
    <input type="email" placeholder="Email Address" bind="@LoginViewModel.Email" />
    <input type="password" placeholder="Password" bind="@LoginViewModel.Password" />
    <button type="button">Login</button>
</div>

Note that the type attribute of the button should be set to "button". 请注意,按钮的type属性应设置为“ button”。 Now, whenever you hit the button, the LoginAsync method would be called, from which you post your login data to the server. 现在,每当您按下按钮时,都会调用LoginAsync方法,您将从中将登录数据发布到服务器。

Try the following: 请尝试以下操作:

Add these code snippets to your LoginService: 将这些代码段添加到您的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;
    }
}

And finally NavMenu.cshtml: 最后是NavMenu.cshtml:

@inject ILoginService LoginService 
@if(LoginService.IsAuthenticated()) {
    <a href="logout">Logout</a>
}
else {
    <a href="login">Login</a>
}

// You also have to set the Startup class on the client as follows: //您还必须在客户端上设置Startup类,如下所示:

public void ConfigureServices(IServiceCollection services)
    {
        // Add Blazor.Extensions.Storage
       // Both SessionStorage and LocalStorage are registered
       // https://github.com/BlazorExtensions/Storage
       **services.AddStorage();**

      ...
    }

// Generally speaking this is what you've got to do on the client. //一般来说,这是您必须在客户端上执行的操作。 // On the server, you've got to have a method, say in the Account controller, whose function is to generate the JWT token, you've to configure the JWT middleware, to annotate your controllers with the necessary attribute, as for instance: //在服务器上,您必须有一个方法,例如在Account控制器中,该方法的功能是生成JWT令牌,您必须配置JWT中间件,以为控制器添加必要的属性,例如例如:

[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]

Hope this helps... 希望这可以帮助...

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM