Blazor - 绑定到服务的 属性,由另一个组件更改
Blazor - Bind to a property of a service, changed by another component
更新(解决方案)
感谢 Magoo 先生的回答,我已经开始工作了。该解决方案是通过事件完成的,并且在官方示例项目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;
}
初始问题
我目前正在尝试和学习 blazor。我有一个 NavMenu
组件,它有多个链接,其中之一是:<a href="login">Login</a>
,用户登录后应该立即更改为 <a onclick="@Logout">Logout</a>
。登录发生在另一个组件中(Login
组件) 使用我自己的服务 LoginService
.
LoginService
有一个 Token
属性 作为 Bearer Token 和一个 属性 public bool IsLoggedIn => !string.IsNullOrEmpty(Token);
。我尝试在 razor 视图中使用带有 if-else
语句的简单绑定。那没有用,我的下一次尝试是在我的 Login
组件中使用 StateHasChanged();
,一旦有人登录。也没有用(可能是因为我想更新 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>
。我正在考虑从 Login
组件向 NavMenu
发送消息。如何让 NavMenu
更新其视图?
您可以向 LoginService 添加一个事件,每当您的 Token 发生变化时都会引发该事件。
然后您的菜单组件可以订阅该事件(您已经注入了 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 方法可以在页面的任何位置调用,具体取决于使用情况,
注意:在 MainLayout 中使用 CascadingValue
可以在主体的任何位置访问主布局。
样本:https://www.syncfusion.com/downloads/support/directtrac/342190/ze/Web_spinner848284331
请检查上面的代码片段和示例,如果它满足您的要求,请告诉我们。
此致,
维尼萨
更新(解决方案)
感谢 Magoo 先生的回答,我已经开始工作了。该解决方案是通过事件完成的,并且在官方示例项目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;
}
初始问题
我目前正在尝试和学习 blazor。我有一个 NavMenu
组件,它有多个链接,其中之一是:<a href="login">Login</a>
,用户登录后应该立即更改为 <a onclick="@Logout">Logout</a>
。登录发生在另一个组件中(Login
组件) 使用我自己的服务 LoginService
.
LoginService
有一个 Token
属性 作为 Bearer Token 和一个 属性 public bool IsLoggedIn => !string.IsNullOrEmpty(Token);
。我尝试在 razor 视图中使用带有 if-else
语句的简单绑定。那没有用,我的下一次尝试是在我的 Login
组件中使用 StateHasChanged();
,一旦有人登录。也没有用(可能是因为我想更新 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>
。我正在考虑从 Login
组件向 NavMenu
发送消息。如何让 NavMenu
更新其视图?
您可以向 LoginService 添加一个事件,每当您的 Token 发生变化时都会引发该事件。
然后您的菜单组件可以订阅该事件(您已经注入了 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 方法可以在页面的任何位置调用,具体取决于使用情况,
注意:在 MainLayout 中使用 CascadingValue
可以在主体的任何位置访问主布局。
样本:https://www.syncfusion.com/downloads/support/directtrac/342190/ze/Web_spinner848284331
请检查上面的代码片段和示例,如果它满足您的要求,请告诉我们。
此致, 维尼萨