【问题标题】:Efficient way to bring parameters into controller action URL's将参数带入控制器操作 URL 的有效方法
【发布时间】:2018-12-05 14:22:44
【问题描述】:

在 ASP.Net Core 中,您有多种方法可以为控制器操作生成 URL,最新的是标签助手。

为 GET 请求使用标签助手 asp-route 用于指定路由参数。据我了解,不支持在路由请求中使用复杂对象。有时,一个页面可能有许多指向自身的不同链接,可能只需要对每个链接的 URL 进行少量添加。

对我来说,对控制器动作签名的任何修改都需要更改所有使用该动作的标签助手似乎是错误的。 IE。如果将string query 添加到控制器,则必须将查询添加到模型并添加asp-route-query="@Model.Query" 分布在cshtml 文件中的20 个不同位置。使用这种方法是为将来的错误设置代码。

有没有更优雅的方法来处理这个问题?例如某种拥有 Request 对象的方式? (即来自控制器的请求对象可以放入模型并反馈到操作 URL。)

【问题讨论】:

  • asp-all-route-data 可能会帮助您解决这里的具体问题。我不会对 Chris 的建议如此不屑一顾(这很可靠),但我知道有时“正确地做事”所需的更改并不那么实际。
  • 谢谢,我已将路由数据作为一种选择。但它需要读取为Dictionary<string, string>(值是字符串类型)的参数在起作用,或者对象反映到Dictionary<string, string>。那将是我的后备方案,但希望也许有更好的选择。
  • 你的意思是像that这样的东西吗?并不是说我会推荐这种查询传递方法
  • 谢谢。那里提出的解决方案似乎可行。描述的问题现在已经在 ASP.Net Core 中解决了,asp-route-*asp-all-route-data 之后应用。因此,如果没有扩展字典,该解决方案可能会起作用。

标签: asp.net-core


【解决方案1】:

在我的另一个答案中,我找到了一种通过模型提供请求对象的方法。

从 SO 文章 @tseng 提供的我找到了一个更小的解决方案。这个不使用模型中的请求对象,但保留所有路由参数,除非显式覆盖。它不允许您通过请求对象指定路由,这通常不是您想要的。但它解决了 OP 中的问题。

<a asp-controller="Test" asp-action="HelloWorld" asp-all-route-data="@Context.GetQueryParameters()" asp-route-somestring="optional override">Link</a>

这需要扩展方法将查询参数转换为字典。

public static Dictionary GetQueryParameters(this HttpContext context) { return context.Request.Query.ToDictionary(d => d.Key, d => d.Value.ToString()); }

【讨论】:

  • 马上asp-all-route-data="@Context.Request.Query.ToDictionary(d => d.Key, d => d.Value.ToString())"
【解决方案2】:

这里有一个理由,我认为你没有得到。 GET 请求是故意简单化的。它们应该描述特定的资源。它们没有主体,因为您不应该首先传递复杂的数据对象。 HTTP 协议不是这样设计的。

此外,查询字符串参数通常应该是可选的。如果需要一些数据来识别资源,它应该是主 URI(即路径)的一部分。因此,忽略添加 query 参数之类的东西,只会导致返回完整的数据集,而不是 query 定义的某个子集。或者在类似搜索页面的情况下,它通常会导致向用户呈现一个表单以收集query。换句话说,您的操作应该考虑到该参数丢失并相应地处理这种情况。

长短,不,我想没有“优雅”的方式来处理这个问题,但原因是不需要。如果您正确地设计路线和操作,这通常不是问题。

【讨论】:

  • 感谢您的意见。您认为我不理解 HTTP、我的用例是错误的以及我应该以不同方式设计所有内容的答案并不是我想要的。
  • 嗯,到处传递“查询”的行为本身就表明设计不佳。 HTTP 是无状态的是有原因的。
  • 我相信你错过了重点,模式 - 不是特定的用例。 “查询”是一个示例参数。在我今天的用例中,它是客户 ID 和部分。到页面的每个链接都必须携带这些,因为我想避免使用 cookie 来存储用户当前导航的位置。我还想避免将每个链接封装在一个表单中以进行回发。缺少“客户 ID”不能返回所有客户,这需要一个列表页面。这需要我将两个单独的视图组合到一个控制器中。似乎有很多额外的代码。
  • @TeddHansen:那为什么要查询呢?一个 MVC 风格的 url 将是 /admin/customer/{customerId}/Edit(获取显示编辑表单的请求,发布请求以保留更改)或在 WebApi 中 /api/admin/customer/{customerId} 获取读取,PUT 以创建/替换资源数据,发布/补丁以修改它。与大多数框架一样,ASP.NET Core 固执己见(偏向于)MVC/REST Api 模式,而查询参数的这种通用传递并不是该模式的真正组成部分
  • 该页面可以包含的不仅仅是客户的编辑表单,例如页面左侧的项目列表,用于快速导航。我们需要客户 ID、左侧菜单(搜索结果)的过滤器以及可能有关视图的信息。 /customer/{customerid}/{view}?query=Oslo。如果我们决定将过滤器扩展到?query=Oslo&category=A,我们将不得不用asp-route-category="@Model.Route.Category" 更新cshtml-pages 中的20+ a href 链接。当用户点击链接时,缺少一个链接会导致“类别”丢失。我的目标是消除代码本身中的这种依赖关系。与 MVC 本身无关。
【解决方案3】:

为了解决这个问题,我想要一个请求对象用作锚 TagHelper 的路由参数。这意味着所有路由链接仅在一个位置定义,而不是在整个解决方案中定义。对请求对象模型所做的更改会自动传播到<a asp-action>-tags 的 URL。

这样做的好处是减少了我们在更改控制器操作的方法签名时需要更改的代码位置。我们仅将更改本地化为模型和操作。

我认为为自定义 asp-object-route 编写标签助手可能会有所帮助。我研究了链接 Taghelpers 以便我的可以在 AnchorTagHelper 之前运行,但这不起作用。创建实例并嵌套它们需要我对ASP.Net Cores AnchorTagHelper 的所有属性进行硬编码,这可能需要在将来进行维护。也考虑过使用带有 UrlHelper 的自定义方法来构建 URL,但是 TagHelper 不起作用。

我找到的解决方案是使用@kirk-larkin 建议的asp-all-route-data 以及用于序列化到字典的扩展方法。任何asp-all-route-* 都会覆盖asp-all-route-data 中的值。

<a asp-controller="Test" asp-action="HelloWorld" asp-all-route-data="@Model.RouteParameters.ToDictionary()" asp-route-somestring="optional override">Link</a>

ASP.Net Core 可以反序列化复杂对象(包括列表和子对象)。
public IActionResult HelloWorld(HelloWorldRequest request) { }

在请求对象中(使用时)通常只有几个简单的属性。但我认为如果它也支持子对象会很好。将对象序列化为字典通常是使用反射完成的,这可能很慢。我认为 Newtonsoft.Json 会比自己编写简单的反射代码更优化,并发现此实现已准备就绪:

public static class ExtensionMethods
{

    public static IDictionary ToDictionary(this object metaToken)
    {
        // From https://geeklearning.io/serialize-an-object-to-an-url-encoded-string-in-csharp/
        if (metaToken == null)
        {
            return null;
        }

        JToken token = metaToken as JToken;
        if (token == null)
        {
            return ToDictionary(JObject.FromObject(metaToken));
        }

        if (token.HasValues)
        {
            var contentData = new Dictionary();
            foreach (var child in token.Children().ToList())
            {
                var childContent = child.ToDictionary();
                if (childContent != null)
                {
                    contentData = contentData.Concat(childContent)
                        .ToDictionary(k => k.Key, v => v.Value);
                }
            }

            return contentData;
        }

        var jValue = token as JValue;
        if (jValue?.Value == null)
        {
            return null;
        }

        var value = jValue?.Type == JTokenType.Date ?
            jValue?.ToString("o", CultureInfo.InvariantCulture) :
            jValue?.ToString(CultureInfo.InvariantCulture);

        return new Dictionary { { token.Path, value } };
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-08-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-17
    • 2017-04-21
    • 1970-01-01
    相关资源
    最近更新 更多