Blazor导航管理器如何返回上一页?

18

在使用 Blazor 时,我希望能够“返回”到之前的页面。

我找到了这个问题,看起来是一个死胡同?

这个功能非常基本,我简直不敢相信它不存在。

有没有什么解决方法来实现这个“返回”功能?

请注意,我不能使用 window.goBackhistory.goBack 因为我的应用程序不会创建任何历史记录,也不应该创建任何历史记录。

唯一创建“历史记录”的方法是使用 Navigation.NavigateToforceLoad 选项,但是如果我这样做,它将尝试再次加载整个应用程序,这很慢,而且我不想这样做。

7个回答

25
你需要的是一个页面历史状态管理器:
以下示例我使用了Blazor Wasm,但你也可以在Blazor Server中使用此示例。
在客户端应用程序中,我添加了这个类: PageHistoryState:
 public class PageHistoryState
    {
        private List<string> previousPages;

        public PageHistoryState()
        {
            previousPages = new List<string>();
        }
        public void AddPageToHistory(string pageName)
        {
            previousPages.Add(pageName);
        }

        public string GetGoBackPage()
        {
            if (previousPages.Count > 1)
            {
                // You add a page on initialization, so you need to return the 2nd from the last
                return previousPages.ElementAt(previousPages.Count - 2);
            }

            // Can't go back because you didn't navigate enough
            return previousPages.FirstOrDefault();
        }

        public bool CanGoBack()
        {
            return previousPages.Count > 1;
        }
    }

然后将此类作为单例添加到服务中:

builder.Services.AddSingleton<PageHistoryState>();

将其注入到您的页面中:

@inject WasmBlazor.Client.PageHistoryState PageHistoryState

在我的标记中,我检查是否可以返回上一页:
@if (PageHistoryState.CanGoBack())
{
    <a href="@PageHistoryState.GetGoBackPage()">Go Back</a>
}

我已经重写了 OnInitialized() 方法

protected override void OnInitialized()
{
    PageHistoryState.AddPageToHistory("/counter");
    base.OnInitialized();
}

我在“获取数据”页面也做了同样的事情,并且无需使用JSInterop即可返回。


2
在 GetGoBackPage 方法中,你可能想要删除列表中的最后一页 - 或者当你返回到上一页时,你应该提供一些其他的方法来递减列表。否则,你将无法回到多个页面,这种情况下,你可以使用一些变量(currentPage、lastPage),而不是使用一个列表。 - John Christopher Linstrum
4
我认为"Stack"是您想要的。 - Soundar Rajan
5
因为这是单例模式,我猜它会跟踪所有用户的页面,对吗?所以如果多个用户使用它,你可能会回到另一个用户之前访问过的页面? - Patric
@Patric 是的,当时感觉是个好主意,但现在看来,你可能想尝试不同的方法,以避免发生这种情况。 - Diogo Neves
1
这不会发生,因为此服务已在客户端机器上注册。这里的假设是使用Blazor WASM。 - Taylor C. White
显示剩余3条评论

22

使用JavaScript怎么样?

@inject IJSRuntime JSRuntime

// Go back in browser using Javascript on a Razor Page 
private async Task GoBack()
{
    await JSRuntime.InvokeVoidAsync("history.back");
}

目前你的回答不够清晰。请编辑并添加更多细节,以帮助其他人理解它如何回答所提出的问题。你可以在帮助中心找到有关如何撰写好答案的更多信息。 - Community
NavigationManager.Uri 是否反映了这个变化? - angularsen
1
是的,这正是我猜测的。 (提问者说他没有历史记录。因此,我猜这对他不是一个解决方案) - Andreas
2
坏社区机器人!这是一个完全可以接受的答案,而且在我看来,它是最简单、最优雅、最直接回答问题的答案。这应该是被接受的答案。 - DSN
1
很棒的答案!我认为这个答案必须被标记为被接受的答案。 - Ramil Aliyev 007

16

我最终得出了一个稍微改进的解决方案,它封装了NavigationManager,将所有内容都放在一个地方,并且不依赖于Pages或其他东西。它还将历史记录缓存大小保持在一定的合理范围内。

Navigation.cs

using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Routing;

namespace MyApp
{
    public class Navigation : IDisposable
    {
        private const int MinHistorySize = 256;
        private const int AdditionalHistorySize = 64;
        private readonly NavigationManager _navigationManager;
        private readonly List<string> _history;

        public Navigation(NavigationManager navigationManager)
        {
            _navigationManager = navigationManager;
            _history = new List<string>(MinHistorySize + AdditionalHistorySize);
            _history.Add(_navigationManager.Uri);
            _navigationManager.LocationChanged += OnLocationChanged;
        }

        /// <summary>
        /// Navigates to the specified url.
        /// </summary>
        /// <param name="url">The destination url (relative or absolute).</param>
        public void NavigateTo(string url)
        {
            _navigationManager.NavigateTo(url);
        }

        /// <summary>
        /// Returns true if it is possible to navigate to the previous url.
        /// </summary>
        public bool CanNavigateBack => _history.Count >= 2;

        /// <summary>
        /// Navigates to the previous url if possible or does nothing if it is not.
        /// </summary>
        public void NavigateBack()
        {
            if (!CanNavigateBack) return;
            var backPageUrl = _history[^2];
            _history.RemoveRange(_history.Count - 2, 2);
            _navigationManager.NavigateTo(backPageUrl);
        }

        // .. All other navigation methods.

        private void OnLocationChanged(object sender, LocationChangedEventArgs e)
        {
            EnsureSize();
            _history.Add(e.Location);
        }

        private void EnsureSize()
        {
            if (_history.Count < MinHistorySize + AdditionalHistorySize) return;
            _history.RemoveRange(0, _history.Count - MinHistorySize);
        }

        public void Dispose()
        {
            _navigationManager.LocationChanged -= OnLocationChanged;
        }
    }
}

然后,您可以将此类作为单例服务添加到依赖注入中并进行初始化。

Program.cs

using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.Extensions.DependencyInjection;

namespace MyApp
{
    public class Program
    {
        public static async Task Main(string[] args)
        {
            var builder = WebAssemblyHostBuilder.CreateDefault(args);
            builder.RootComponents.Add<App>("app");

            builder.Services.AddSingleton<Navigation>();
            // .. other services.
            
            var host = builder.Build();
            await Initialize(host);
            await host.RunAsync();
        }

        private static async Task Initialize(WebAssemblyHost host)
        {
            host.Services.GetService<Navigation>();
            // .. other initialization calls.
        }
    }
}

在此之后,您可以使用Inject指令/属性将其应用于任何地方。

SomePage.cshtml

@page "/SomePage"
@inject Navigation Navigation

<h3>SomePage</h3>

<button @onclick="NavigateBackClick">Navigate Back</button>

@code {
    private void NavigateBackClick()
    {
        Navigation.NavigateBack();
    }
}

SomeService.cs

namespace MyApp
{
    public class SomeService
    {
        private readonly Navigation _navigation;

        public SomeService(Navigation navigation)
        {
            _navigation = navigation;
        }

        public void SomeMethod()
        {
            // ...
            _navigation.NavigateBack();
        }
    }
}

2
你为什么不直接使用栈? - Jóhann Østerø
3
"builder.Services.AddSingleton<Navigation>()" 你确定,在服务器端 Blazor 中,这样的 Singleton 只对一个用户有效吗?(而不是同时适用于所有用户) - Lealan
1
@lealan,不是的。我只为客户端做了这个。对于服务器端,它应该进行一些修改... ;) - Pavel Melnikov
2
@JóhannØsterø 在 EnsureSize() 方法中从“堆栈底部”进行移除。 - user3625699
1
绝对不要使用单例模式,将其作为作用域添加。 - Mana
不错。我不得不在NavigateBack方法中添加一个forceload布尔参数,以便能够将其传递给此方法内部的NavigateTo调用。 - Dabblernl

11

我将 Diogo 在上面的回答进行了修改,成为一个我认为更加优雅的解决方案。

首先,创建一个 BasePageComponent.cs 类,该类继承自 ComponentBase 类:

// Using statements/namespace go here

[Authorize]
public class BasePageComponent: ComponentBase
{
    [Inject]
    protected NavigationManager _navManager { get; set; }
    [Inject]
    protected PageHistoryState _pageState { get; set; }

    public BasePageComponent(NavigationManager navManager, PageHistoryState pageState)
    {
        _navManager = navManager;
        _pageState = pageState;
    }

    public BasePageComponent()
    {
    }

    protected override void OnInitialized()
    {
        base.OnInitialized();
        _pageState.AddPage(_navManager.Uri);
    }
}

每个页面都将从这里继承。它负责注入PageHistoryState服务,并附加一个新导航的页面。它在您实际页面的“幕后”完成所有这些操作。

现在,在给定页面中,您需要从BasePageComponent继承:

@page "/workouts/new"
@inherits BasePageComponent

/* ...RenderFragments/Razor view here...*/

@code {
    /* ...properties here...*/

    // This is an example of how to consume the _navManager and _pageState objects if desired, without any boilerplate code.
    private void OnCancel()
    {
        _navManager.NavigateTo(_pageState.PreviousPage());
    }
}

在我的示例组件中(为简洁起见),它添加了一个没有标记的新元素到页面历史堆栈中,该元素继承自BasePageComponent。Taylor

我必须将BasePageComponent放在命名空间中才能正常工作。我遇到了这个错误:error RZ3008: 标签助手不能以'<全局命名空间> .BasePageComponent'为目标,因为它包含一个“<”字符。 - Rhuan Barros

1

我创建了自己的类并将其注册为服务。与其使用客户端来注册页面更改,我使用了MainLayout - 因此不需要JS。

我将服务注入到需要后退按钮的页面中

我还将其添加到了MainLayout中

<a href="@pvs.GetPreviousPage()">Go Back</a>






 @code {
    protected override void OnInitialized()
    {
        pvs.AddPage(NavigationManager.Uri);

        NavigationManager.LocationChanged += LocationChanged;
        base.OnInitialized();
    }

    void LocationChanged(object? sender, LocationChangedEventArgs e)
    {
        if (sender is NavigationManager vm)
        {
            pvs.AddPage(NavigationManager.Uri);
        }
    }
}

公共类PageVisitedService { 列表 _pageVisitHistory = new 列表();

    /// <summary>
    /// Gets a cloned list of history
    /// </summary>
    public List<string> PageVisitHistory
    {
        get
        {
            return new List<string>(_pageVisitHistory.Reverse<string>());
        }
    }


    public void AddPage(string page)
    {
        if (_pageVisitHistory.Count > 0 && page == _pageVisitHistory[_pageVisitHistory.Count - 1])
        {
            return;
        }

        _pageVisitHistory.Add(page);
    }

    public string GetPreviousPage()
    {
        if (_pageVisitHistory.Count > 0)
        {
            return _pageVisitHistory[_pageVisitHistory.Count - 1];
        }

        return string.Empty;
    }
}

1

这是 Diogo's answer 的扩展。

PageHistoryState 的正确实现应该使用Stack。请记住您必须向历史记录添加一些起点。在第一次页面加载时(可以是MainLayout),您应该调用AddPageToHistory。如果历史记录为空,您将被重定向到错误页面:

public class PageHistoryState
{
    private Stack<string> previousPages;
    private Stack<string> nextPages;
    private readonly string errorPageUrl;
    public PageHistoryState()
    {
        previousPages = new Stack<string>();
        nextPages = new Stack<string>();
        errorPageUrl = "/errorPage";
    }

    public void AddPageToHistory(string pageName)
    {
        previousPages.Push(pageName);
    }

    public string GetGoBackPage()
    {
        // This condition is to check if it is the first loaded page "/"
        if (previousPages.TryPeek(out string url) && !string.IsNullOrWhiteSpace(url))
        {
            // If moved to the next page check
            if (previousPages.Count > 1)
            {
                // Pop the current page
                nextPages.Push(previousPages.Pop());
                // Pop the previous page -> "/"
                url = previousPages.Pop();
                return url;
            }
        }

        // If stack is empty redirect to the error page
        return errorPageUrl;
    }

    public string GetGoForwardPage()
    {
        if (nextPages.TryPop(out string url) && !string.IsNullOrWhiteSpace(url))
            return url;

        // If stack is empty redirect to the error page
        return errorPageUrl;
    }

    public bool CanGoForward() => nextPages.Any();
    public bool CanGoBack() => previousPages.Count > 1;
}

1

导航器类

 public class Navigator
    {
        string _currentPage = "/";
        string _homePage { get; set; } = "/";
        NavigationManager Navigation { get; set; }
        IJSRuntime JS { get; set; }
        public void SetNavigation(NavigationManager navigation, IJSRuntime js, string dfaultPage = "/")
        {
            this.Navigation = navigation;
            this.JS = js;
            _currentPage = dfaultPage;
            _homePage = _currentPage;
        }
        public Navigator()
        {
            
        }
        public Stack<string> Navigators = new Stack<string>();
        public void NavigateTo(string parameter)
        {
            if (parameter != _currentPage)
            {
                Navigators.Push(parameter);
                Navigation?.NavigateTo(parameter);
                _currentPage = parameter;
            }
        }
        public async void ExecuteCommand(NavigateType code, string param)
        {
            if (code ==  NavigateType.Back)
            {
                if (Navigators.Count >= 1)
                {
                    string nav = null;
                    if(Peek() == param)
                        Navigators.Pop();
                    if(Navigators.Count >=1)
                      nav = Navigators.Pop();
                    if (nav != null)
                        _currentPage = nav;
                    else
                    {
                        _currentPage = _homePage;
                    }
                    await JS.InvokeVoidAsync("history.back");
                    BackPressed?.Invoke(this, nav);
                }
                else
                {
                   
                    _currentPage = _homePage;
                    await JS.InvokeVoidAsync("history.back");
                    BackPressed?.Invoke(this, null);
                   
                }
                
            }
            else if (code == NavigateType.Navigate)
            {
                if (param != _currentPage)
                {
                    _currentPage = param;
                    Navigators.Push(param);
                    Navigation?.NavigateTo(param);
                    Navigated?.Invoke(this, param);
                }

            }
        }
        public string Peek()
        {
            return Navigators.Peek();
        }
        public string Pop()
        {
            return Navigators.Pop();
        }
        public event EventHandler<string> BackPressed;
        public event EventHandler<string> Navigated;
    }
    public enum NavigateType
    {
        Navigate,
        Back,

    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接