【问题标题】:Type-safe `a` tag helper类型安全的 `a` 标记助手
【发布时间】:2020-11-10 21:50:29
【问题描述】:

关于 ASP.NET Core 最受欢迎的书籍之一是 Adam Freeman 的 Pro ASP.NET Core 3

在第 7-11 章中,他构建了一个示例应用程序 SportsStore:

在左侧,您会注意到各种产品类别(“国际象棋”、“足球”等)的按钮。这些链接由以下代码生成:

@foreach (string category in Model) {
    <a class="btn btn-block
       @(category == ViewBag.SelectedCategory 
           ? "btn-primary": "btn-outline-secondary")"
       asp-action="Index" asp-controller="Home"
       asp-route-category="@category"
       asp-route-productPage="1">
        @category
    </a>
}

Views/Shared/Components/NavigationMenu/Default.cshtml

对动作的类型安全引用

这部分:

asp-action="Index"

指的是HomeController中的Index方法。但是,这段代码不是类型安全的;如果我们有错字:

asp-action="IndexAbc"

项目仍在编译。

好的,不用担心,我们可以解决这个问题:

asp-action="@nameof(HomeController.Index)"

我们去吧。现在像下面这样的错字会在编辑时立即给我们一个错误,项目当然不会编译:

asp-action="@nameof(HomeController.IndexAbc)"

对控制器的类型安全引用

对控制器的引用也不是类型安全的:

asp-controller="Home"

解决这个比较冗长,但可行:

asp-controller="@Regex.Replace(nameof(HomeController), "Controller$", String.Empty)"

同样,现在控制器名称中的拼写错误会在编译时被捕获。如果控制器被重命名,这也会捕获它。

类型安全的路由参数

以类型安全的方式处理路由值怎么样?以下是我们正在处理的两个参数和值:

asp-route-category="@category"
asp-route-productPage="1"

我们不能像前面的例子那样真正使用nameof;没有什么可以真正使用nameof

这是Index 方法签名:

public ViewResult Index(string category, int productPage = 1) 

这几乎就像我们希望能够说 nameof(productPage) 但我们无法访问视图组件中的 productPage 参数。

让我们退后一步......

以下代码:

<a class="btn btn-block @(category == ViewBag.SelectedCategory ? "btn-primary" : "btn-outline-secondary")"
   asp-action="Index"
   asp-controller="Home"
   asp-route-category="@category"
   asp-route-productPage="1">
    @category
</a>

最终被扩展成这样的:

<a class="btn btn-block btn-outline-secondary" href="/Chess/Page1">
    Chess
</a>

特别是以下几行:

asp-action="Index"
asp-controller="Home"
asp-route-category="@category"
asp-route-productPage="1"

被压扁成这样:

href="/Chess/Page1"

ASP.NET Core 允许我们使用 LinkGenerator.GetPathByAction 方法执行此映射 programmatically

例如,下面的调用:

_linkGenerator.GetPathByAction("Index", "Home", new { category = "Chess", productPage = 1 })

为我们提供以下信息:

"/Chess/Page1"

我们现在非常接近。现在的挑战是:

new { category = "Chess", productPage = 1 }

不是类型安全的。以下拼写错误不会阻止项目的构建:

new { categoryAbc = "Chess", productPageXyz = 1 }

让我们改变Index方法来接收一个对象作为参数:

public ViewResult Index(IndexParameters parameters)

IndexParameters 在哪里:

public class IndexParameters
{
    public string Category { get; set; }
    public int ProductPage { get; set; } = 1;
}

现在我们可以以类型安全的方式传递参数值了:

_linkGenerator.GetPathByAction("Index", "Home", new IndexParameters() { Category = category, ProductPage = 1 }))

完整的类型安全示例

好的,现在我们已经到达了完整的类型安全 a 标签:

<a class="btn btn-block @(category == ViewBag.SelectedCategory ? "btn-primary" : "btn-outline-secondary")"
   href="@(
    _linkGenerator.GetPathByAction(
        nameof(HomeController.Index), 
        Regex.Replace(nameof(HomeController), "Controller$", String.Empty),
        new IndexParameters() 
        { 
            Category = category, 
            ProductPage = 1 
        }))">
    @category
</a>

我已将以下项目添加到此新版本代码的视图组件文件中:

@using SportsStore.Controllers
@using System.Text.RegularExpressions 
@using Microsoft.AspNetCore.Routing

@inject LinkGenerator _linkGenerator

我的问题是......有没有更简洁的方法可以在相同的代码中获得相同级别的类型安全?还是我真的必须跳过所有这些障碍才能使代码类型安全?

【问题讨论】:

  • 如果类型安全对你很重要,我只能想办法,那就是完全在服务器上,在Controller中创建链接,并通过视图模型传递给视图。
  • 我在最后更新了完整的示例,使其成为多行而不是全部在一行。这使它看起来更好一点。
  • @DavidLiang 据我所知,通过上述方法,代码完全在服务器上运行,虽然不完全在控制器中;其中一些是在视图组件中完成的。
  • 是的,我明白了。我想我的意思是,不是在视图上使用标签助手来生成带有参数的链接,而是在控制器中生成链接并将它们作为字符串传递回视图......
  • @DavidLiang 有人建议我研究一下 R4MVC 项目。我添加了一个引用该项目的答案。听起来很有希望!

标签: asp.net-core asp.net-core-mvc asp.net-core-3.1 strong-typing


【解决方案1】:

实现此目的的两种主要方法:

  • 代码生成 - 根据您的控制器、模型等的反映生成辅助类。

  • 更现代的方法是编写一个 Roslyn 代码分析器,如果您引用不存在的操作,它会发出警告(或错误)。

需要考虑的事情:您甚至可以走得更远,例如确保将正确的参数传递给链接等等...

【讨论】:

  • 谢谢 Zdravko!是的,有人刚刚建议我看一下R4MVC 项目,该项目似乎采用了您在此处推荐的方法。我还添加了一个引用该项目的答案。
  • 这是一个演示 R4MVC 提供的类型安全标签助手的视频:link
【解决方案2】:

感谢用户 quentech 在 reddit who suggested 以下项目:

R4MVC 是一个基于 Roslyn 的代码生成器,用于 ASP.NET MVC Core 应用程序,它创建强类型帮助器,从而消除了在许多地方使用文字字符串。

看来这个项目旨在解决我在问题中展示的一些相同问题。

演示 R4MVC 提供的静态类型标签助手的视频:

https://youtu.be/cFD9QOjEIxc

【讨论】:

    猜你喜欢
    • 2011-07-21
    • 1970-01-01
    • 1970-01-01
    • 2015-06-06
    • 1970-01-01
    • 2016-06-04
    • 2019-08-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多