【问题标题】:Keeping the code DRY when dealing with Blazor Navlinks处理 Blazor Navlinks 时保持代码干燥
【发布时间】:2021-06-17 17:06:57
【问题描述】:

这个问题背后的主题可能不是特定于 Blazor 中的导航链接,但它们是组件的一个示例,我可以在其中尝试一些东西而不会破坏我正在处理的所有应用程序。 当您在 blazor 中有导航链接时,您可以做的最快的事情是硬编码路径,例如:

<Navlink href="/products">Products</Navlink>

但是,如果您稍后必须添加一些内容,例如用户将在网站上更改的语言路径参数,该怎么办?此参数将在您的所有组件中通用。

因此,为了避免更改所有导航链接路径,您编写了一个函数,这样您只需要在一个地方更改代码,例如:

<Navlink href"@NavigateToProducts">Products</Navlink>

@code {
    [Parameter]
    public String Language {get; set;} // this comes from main layout page for example

    private void NavigateToProducts()
    {
        NavigationManager.NavigateTo("$/{Language}/products");
    }
}

你也可以像这样写一个更通用的,接受一个或多个参数:

<Navlink href="@NavigateTo("products")">Products</Navlink>

@code {
    [Parameter]
    public String Language {get; set;] // this comes from main layout page for example

    private void NavigateTo(params String[] args)
    {
        var path = $"/{Language}"; // yeah could use a string builder
        foreach(var arg in args)
            path += $"/{arg}";

        NavigationManager.NavigateTo($"{path}");
    }
}

所以这一切都很好,但如果您有更多类别而不仅仅是产品,甚至只是其他组件,您需要复制并粘贴此功能,以便为您的导航链接导航。

我尝试做的是首先使用扩展方法创建一个静态类,以便:

<Navlink href="@(new String[]{"products"}.GetPath(Language))"

// In another file
public static class ExtensionMethods {
 
    public static String GetPath(this String[] parameters, String language)
    {
        var path = $"/{language}";
        foreach(var arg in args)
            path += $"/{arg}";

        return path;
    }
}

但是我很快意识到这并没有解决任何问题,除了使代码更加臃肿,需要创建一个数组并调用方法之外,你并没有真正解决如果需要添加一些东西的问题,你有再次去编辑所有导航链接并添加第二个参数。

那么你还能做什么呢?您可以创建一个注入所有组件的服务:

@inject IUriService UriService

<Navlink href="@(UriService.GetPath("products"))">Products</Navlink>

//In another file
public class UriService : IUriService 
{
    private readonly StringBuilder _strBuilder;
    private String _language;

    public UriService(NavigationManager navigationManager)
    {
        _language = // use navigation manager to get the language from url or whatever
        _strBuilder = new StringBuilder();
    }


    public String GetPath(params String[] pathParams)
    {
        if (pathParams == null)
            throw new ArgumentNullException(nameof(pathParams));

        _strBuilder.Clear();
        _strBuilder.Append($"/{_language}");
        foreach (var param in pathParams)
            _strBuilder.Append($"/{param}");
            
        return _strBuilder.ToString();
     }
}

最后,如果我需要更改所有导航链接的通用内容,我只需要来这里! 这一切都有效,但是当用户更改语言时,我如何使用新语言更新我的服务? 我可以创建变量 _language 属性并将其设置在我需要的位置,在我的情况下是在导航栏组件中:

// The HTML code for the navbar//
// A dropdown menu where you can choose the language that on click will trigger OnLanguageChanged()

@code {
    private void OnLanguageChanged()
    {
        // The logic for changing the language //
        UriService.Language = "en";
    }
}

或者为了更封装和更清楚你在做什么,我可以在 uri 服务中创建一个方法:

public void UpdateLanguage(String newLanguage)
{
     _language = newLanguage;
}

不过,这看起来很混乱,如果您注入了 uri 服务,则可以随时更改变量。另外,该服务目前是瞬态的,应该是单例吗? 我不知道是否有使用事件的方法,但我无法想象可以从 uri 服务订阅导航栏组件的事件。或者是? 我知道这是一个很长的帖子,可能是一个微不足道的问题,但我对这些东西有点缺乏经验,在处理其他类似情况时记住它可能是一件好事。

【问题讨论】:

  • common across all of your components - 听起来您想不理会您的导航链接并在 cascading values 中传递该信息。
  • @GSerg 我想到了这一点,但是如果我需要添加参数,我仍然需要将 NavigateTo 函数复制到所有组件,然后使用新参数对其进行修改。也许没关系,我不是说不是,我只是说那是我需要做的。或者我可以级联一个参数数组并在其中添加新参数?
  • 我的建议是为自己构建一个自定义的 NavLink 组件,而不是 NavLink - 你可以从这里提取代码 - github.com/dotnet/aspnetcore/blob/main/src/Components/Web/src/… - 然后或者从语言中获取你的语言您已注册的服务或级联值,但如果用户可以在 SPA 运行时更改它,该服务看起来是一个更好的选择。如果您喜欢这个想法,可以发布一些入门代码作为答案。
  • @ShaunCurtis 是的,用户可以在应用程序运行时更改语言。它是导航栏中的一个下拉菜单,它与 ofc 共享,并且始终可见(它写在主布局页面中),因此当用户更改语言时,从资源文件中应用所有翻译并且页面中的所有当前内容都重新渲染.是的,您可以继续发布答案,我真的很喜欢您的建议。我对您将如何构建语言服务感兴趣,其余的我可以自己归档并使用示例。
  • 给我几个小时。我给你结构然后你可以填写细节。

标签: c# asp.net oop blazor


【解决方案1】:

我的测试项目名为 NavTest,它是 Blazor 服务器,因为它的测试速度更快。

这是非常基本的服务。一个 getter/setter 和一个 Event。

using System;

namespace NavTest.Services
{
    public class LanguageService
    {
        public string Language 
        {
            get => _language;
            set
            {
                if (!value.Equals(_language))
                {
                    _language = value;
                    LanguageChanged?.Invoke(value, EventArgs.Empty);
                }
            }
        }

        private string _language { get; set; } = "en-GB"; // set the default language

        public event EventHandler LanguageChanged;

        public string GetLanguageUrl(string url)
            => $"{this.Language}/{url}".Replace("//", "/");
    }
}

Startup 中注册服务 - 范围基本上是服务器中的每个用户。

public void ConfigureServices(IServiceCollection services)
{
    services.AddRazorPages();
    services.AddServerSideBlazor();
    services.AddSingleton<WeatherForecastService>();
    services.AddScoped<LanguageService>();
}

新的 NavLink 继承自 NavLink。我们:

  1. 注入 LanguageService。
  2. 注册 LanguageService 语言更改事件
  3. 覆盖OnParameterSet 以捕获基本href,然后更新使用的href。我们必须重建和更改AdditionalAttributes,因为它是只读的。
  4. 更新 href 并在语言更改事件上刷新组件。
using NavTest.Services;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Routing;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Threading.Tasks;

namespace NavTest.Components
{
    public class MyNavLink : NavLink
    {
        [Inject] private LanguageService LanguageService {get; set;}

        private string _basehref = string.Empty;
        private bool _firstrender = true;

        protected override Task OnInitializedAsync()
        {
            LanguageService.LanguageChanged += this.OnLanguageChanged;
            return base.OnInitializedAsync();   
        }

        protected override void OnParametersSet()
        {
            this.UpdateHref();
            base.OnParametersSet();
        }

        private void UpdateHref()
        {
            // Update the href in the Additional Attributes
            var href = string.Empty;
            var ats = new Dictionary<string, object>();
            if (this.LanguageService != null && this.AdditionalAttributes != null && this.AdditionalAttributes.TryGetValue("href", out var obj))
            {
                href = Convert.ToString(obj, CultureInfo.InvariantCulture);
                if (_firstrender)
                {
                    _basehref = href;
                    _firstrender = false;
                }
                // create a new attribute dictionary
                foreach (var attrib in this.AdditionalAttributes)
                {
                    ats.Add(attrib.Key, attrib.Value);
                }
                ats["href"] = $"{LanguageService.Language}/{_basehref}".Replace("//", "/");
            }
            // replace the existing dictionary
            this.AdditionalAttributes = ats;
        }

        private void OnLanguageChanged(object sender, EventArgs e)
        {
            this.UpdateHref();
            _ = InvokeAsync(StateHasChanged);
        }
    }
}

最后我的测试 Index.razor 看起来像这样:

@using NavTest.Services
@using NavTest.Components
@page "/"

<h1>Hello, world!</h1>

Welcome to your new app.

<div class="container">
    <div class="row">
        <div class="col-12">
            <h2>Language Tester</h2>
        </div>
    </div>
    <div class="row mb-2">
        <div class="col-4 form-label" for="txtcountry">
            Language
        </div>
        <div class="col-4">
            <input class="form-control" type="text" @bind-value="this.lang" />
        </div>
    </div>
    <div class="row mb-2">
        <div class="col-6">
        </div>
        <div class="col-6 text-right">
            <button class="btn btn-primary" @onclick="() => Click()">Update</button>
        </div>
    </div>
    <div class="row">
        <div class="col-6">
            <MyNavLink href="/products/12"> Products</MyNavLink>
        </div>
        <div class="col-6">
            <div>@LangService.Language</div>
        </div>
    </div>
</div>

@code {
    [Inject] LanguageService LangService { get; set; }

    string lang { get; set; }

    void Click()
    {
        LangService.Language = lang;
    }

    // example Navigate to
    void SomeMethod(string url)
    {
        NavManager.NavigateTo(LangService.GetLanguageUrl(url));
    }
}

我还将它添加到 NavMenu 中以在页面的另一部分进行测试:

<li class="nav-item px-3">
    <MyNavLink class="nav-link" href="fetchdata">
        <span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
    </MyNavLink>
</li>

应该让你朝着正确的方向前进。

【讨论】:

  • 哇,谢谢你的回答,很快就会试试。肯定会在其他场景中牢记这种模式
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-01-26
  • 1970-01-01
  • 2023-03-08
  • 1970-01-01
  • 2011-08-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多