【问题标题】:Blazor - razor page not updating after property from DI Service is changedBlazor - 更改 DI 服务的属性后,剃须刀页面未更新
【发布时间】:2020-03-02 17:48:48
【问题描述】:

使用 dotnet 3.1.100-preview2-014569

好的,考虑下面的例子:

从模板创建一个新的 Blazor WebAssemply 项目,然后添加以下内容:

books.razor

@page "/books"
@inject BookService bookService

@if (bookService.isLoaned)
{
    <p><em>Book loaned</em></p>
}
else
{
    <p><em>Book returned</em></p>
}

BookService.cs

public class BookService
    {
        public int BookId { get; set; }

        public string Title { get; set; }

        public bool isLoaned { get; set; }
    }

Startup.cs

public void ConfigureServices(IServiceCollection services)
        {
            services.AddScoped<BookService>();
        }

NavMenu.razor

@inject BookService bookService;
<div class="top-row pl-4 navbar navbar-dark">
    <a class="navbar-brand" href="">BlazorBlank_PV2</a>
    <button class="navbar-toggler" @onclick="ToggleNavMenu">
        <span class="navbar-toggler-icon"></span>
    </button>
</div>

<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
    <ul class="nav flex-column">
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="" Match="NavLinkMatch.All">
                <span class="oi oi-home" aria-hidden="true"></span> Home
            </NavLink>
        </li>
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="books" @onclick="LoanBookClicked">
                <span class="oi oi-plus" aria-hidden="true"></span>Loan Book
            </NavLink>
        </li>
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="books" @onclick="ReturnBookClicked">
                <span class="oi oi-list-rich" aria-hidden="true"></span>Return Book
            </NavLink>
        </li>
    </ul>
</div>

@code {
    private bool collapseNavMenu = true;

    private string NavMenuCssClass => collapseNavMenu ? "collapse" : null;

    private void ToggleNavMenu()
    {
        collapseNavMenu = !collapseNavMenu;
    }

    private void LoanBookClicked()
    {
        bookService.isLoaned = true;       
    }
    private void ReturnBookClicked()
    {
        bookService.isLoaned = false;
    }


}

会发生什么:

预期行为: 单击菜单项 Loan Book 时,页面应显示 Book Loaned,或者如果我单击 Return Book,则应显示 Book Return。但这只有在我单击另一个页面(例如主页)然后再返回时才会发生。

即使页面已经在同一页面上,如何强制页面重新呈现/重新检查来自 BookService 的更新值?

谢谢!

【问题讨论】:

    标签: asp.net-core blazor blazor-client-side


    【解决方案1】:

    这是一个新的解决方案,我在 BookService 类中实现了 INotifyPropertyChanged 接口。为什么要使用这个接口?

    • 也可以应用于其他属性

    • 通知客户端属性值已更改是良好服务的重要组成部分,这不仅限于仅为此目的调用 StateHasChanged。

    • 通过实现 INotifyPropertyChanged 接口,我可以将事件数据传递给已注册或订阅的对象。

    重要提示:调用像bookService.SetLoanedState(false) 这样的方法来更新属性值是一种糟糕的编程和反模式设计。一个属性可以有两个访问器,get 和 set,它们应该用于获取值和更改值。方法有不同的作用...

    BookService.cs


    using System.ComponentModel;
    using System.Runtime.CompilerServices;
    
    
    
    public class BookService : INotifyPropertyChanged
        {
            private bool isLoaned;
            public event PropertyChangedEventHandler PropertyChanged;
    
            public int BookId { get; set; }
    
            public string Title { get; set; }
    
    
            public bool IsLoaned
            {
                get
                {
                    return this.isLoaned;
                }
    
                set
                {
                    if (value != this.isLoaned)
                    {
                        this.isLoaned = value;
                        NotifyPropertyChanged();
                    }
                }
            }
    
    
            private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
            {
                if (PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
                }
            }
        }
    

    Books.razor

    @page "/books"
    @inject BookService bookService
    @using System.ComponentModel
    
    
    @if (bookService.IsLoaned)
    {
        <p><em>Book loaned</em></p>
    }
    else
    {
        <p><em>Book returned</em></p>
    }
    
    @code{
    
        protected override void OnInitialized()
        {
            bookService.PropertyChanged += PropertyHasChanged;
        }
        private void PropertyHasChanged(object sender, PropertyChangedEventArgs args)
        {
             StateHasChanged();
        }
    }
    

    NavMenu.razor

    @code {
    
        // Code removed for brevity
    
        private void LoanBookClicked()
        {
            bookService.IsLoaned = true;      
        }
        private void ReturnBookClicked()
        {
            bookService.IsLoaned = false;      
        }
    }
    

    希望这行得通...

    【讨论】:

    • 谢谢,Morten_564834...对不起,我忘记了警告不要将 StateHasChanged 方法放在 OnAfterRender 和 OnAterRenderAsync 方法中,其结果是无限循环。我将更新我的答案以改用 IPropertyChange 接口。道德课:在你输入之前三思……
    • np - 希望看到使用上述示例的其他方法。我以前读过 Chris Sainty 的博客,但不确定它是否适用于这个例子,而且方法不断变化,所以你永远不知道 :)
    • 太好了,我删除了我的评论,以免让人们感到困惑。效果很好 :) 我不会讨论哪个最好,我相信它们都是可行的,但我更喜欢这种方法以避免设置属性的方法。
    【解决方案2】:

    books.razor(为图书服务的新 onchange 事件添加处理程序)

    @page "/books"
    @inject BookService bookService
    
    @if (bookService.isLoaned)
    {
        <p><em>Book loaned</em></p>
    }
    else
    {
        <p><em>Book returned</em></p>
    }
    
    @code{
     protected override void OnInitialized()
        {
            bookService.OnChange += StateHasChanged;
        }
    }
    

    BookService.cs(添加事件动作,将属性改为只读,添加setter,触发事件会NotifyStateChanged()

    public class BookService
        {   
            public event Action OnChange;
    
            public int BookId { get; set; }
    
            public string Title { get; set; }
    
            public bool isLoaned { get; }
    
            public void SetLoanedState(bool State)
            {
               isLoaned  = State;
               NotifyStateChanged();
            }
    
            private void NotifyStateChanged() => OnChange?.Invoke();
    
        }
    

    NavMenu.razor(更改为使用服务 SetLoanedState()

    @inject BookService bookService;
    <div class="top-row pl-4 navbar navbar-dark">
        <a class="navbar-brand" href="">BlazorBlank_PV2</a>
        <button class="navbar-toggler" @onclick="ToggleNavMenu">
            <span class="navbar-toggler-icon"></span>
        </button>
    </div>
    
    <div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
        <ul class="nav flex-column">
            <li class="nav-item px-3">
                <NavLink class="nav-link" href="" Match="NavLinkMatch.All">
                    <span class="oi oi-home" aria-hidden="true"></span> Home
                </NavLink>
            </li>
            <li class="nav-item px-3">
                <NavLink class="nav-link" href="books" @onclick="LoanBookClicked">
                    <span class="oi oi-plus" aria-hidden="true"></span>Loan Book
                </NavLink>
            </li>
            <li class="nav-item px-3">
                <NavLink class="nav-link" href="books" @onclick="ReturnBookClicked">
                    <span class="oi oi-list-rich" aria-hidden="true"></span>Return Book
                </NavLink>
            </li>
        </ul>
    </div>
    
    @code {
        private bool collapseNavMenu = true;
    
        private string NavMenuCssClass => collapseNavMenu ? "collapse" : null;
    
        private void ToggleNavMenu()
        {
            collapseNavMenu = !collapseNavMenu;
        }
    
        private void LoanBookClicked()
        {
            bookService.SetLoanedState(true);       
        }
        private void ReturnBookClicked()
        {
            bookService.SetLoanedState(false);
        }
    
    
    }
    

    如需了解更多信息,请查看 Chris Sainty 的博文 here,这非常清楚

    【讨论】:

    • 按预期工作。太棒了:)
    • @David Masterson,您应该在 SetLoanedState 方法中检查 isLoaned 属性的值是否等于 State 参数,如果不等于,您只有将参数分配给属性并调用OnChange 事件委托。每当用户单击“LoanBookClicked”按钮或“ReturnBookClicked”按钮时,您的逻辑都会调用委托。例如,如果您单击“ReturnBookClicked”按钮 10 次,则在显示应用程序后,isLoaned 属性默认为 false,但 OnChange 委托被调用 10 次,这意味着 ->
    • 整个 Component 页面被重新渲染 10 次。顺便说一句,isLoaned 属性缺少它的 set 访问器...
    • Issac,首先问题是为什么组件没有更新其状态。我没有添加比解释答案所需更多的代码,其次,我的理解是,尽管此代码在您的场景中会触发 10 次,但对 statehaschange 的调用会重新计算 DOM 树的差异,但不会有,然后什么也不做到页面。我知道在一个完美的世界中,您可以通过调用 NotifyStateChanged 来测试状态变化,但是由于此代码在客户端浏览器中运行,因此成本由单击相同按钮 10 次的用户支付。为什么会有这条评论?
    猜你喜欢
    • 2020-11-11
    • 1970-01-01
    • 2021-06-24
    • 2020-02-01
    • 2018-03-09
    • 2021-06-28
    • 2018-09-18
    • 2022-01-10
    • 1970-01-01
    相关资源
    最近更新 更多