将参数带入控制器操作URL的有效方法

时间:2018-12-05 14:22:44

标签: asp.net-core

在ASP.Net Core中,您可以通过多种方式来生成用于控制器操作的URL,最新的是标签助手。

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

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

有没有更优雅的方式来处理此问题?例如某种具有Request对象的方式? (即,可以将来自控制器的请求对象放到模型中,然后反馈到操作网址中。)

3 个答案:

答案 0 :(得分:0)

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

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

长短不一,不,我想没有办法“优雅”地解决这个问题,但是这样做的原因是没有必要。如果您正确地设计了路线和动作,通常就没有问题了。

答案 1 :(得分:0)

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

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

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

我找到的解决方案是使用@ kirk-larkin建议的asp-all-route-data以及用于序列化为Dictionary的扩展方法。任何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) { }

在请求对象中(使用时)通常只有几个简单的属性。但是我认为,如果它也支持子对象,那就太好了。将对象序列化为Dictionary通常是使用反射完成的,这可能会很慢。我想出了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 } };
    }
}

答案 2 :(得分:0)

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

在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()); }