Html.BeginForm()添加HTML属性的变体

4

我需要在我的ASP.NET MVC Razor页面上添加一个表单。我更喜欢使用以下语法:

@using (Html.BeginForm())
{
}

然而,我需要在表单中添加几个属性。因此,最终的代码类似于以下内容:

@using (Html.BeginForm(null, null, FormMethod.Post, new { name = "value" }))
{
}

然而,这样做会产生一个不良副作用。如果该页面的请求中存在查询参数,则第一个表单在提交时将它们一起传递。然而,第二个版本则不会。
我真的不知道为什么`BeginForm()`不支持属性,但是否有一种简单的方法可以添加属性到`BeginForm()`并在提交表单时仍然传递任何查询参数呢?
编辑:经过调查,最好的解决方案似乎是像这样:
<form action="@Request.RawUrl" method="post" name="value">
</form>

然而,当使用此语法时,客户端验证被禁用。似乎在不使用更复杂和可能不可靠的结构的情况下,没有很好的解决方案。
3个回答

5

确实如此,但是我会选择使用自定义帮助程序来保留表单上下文,以便用于客户端验证:

public static class FormExtensions
{
    private static object _lastFormNumKey = new object();

    public static IDisposable BeginForm(this HtmlHelper htmlHelper, object htmlAttributes)
    {
        string rawUrl = htmlHelper.ViewContext.HttpContext.Request.RawUrl;
        return htmlHelper.FormHelper(rawUrl, FormMethod.Post, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
    }

    private static int IncrementFormCount(IDictionary items)
    {
        object obj2 = items[_lastFormNumKey];
        int num = (obj2 != null) ? (((int)obj2) + 1) : 0;
        items[_lastFormNumKey] = num;
        return num;
    }

    private static string DefaultFormIdGenerator(this HtmlHelper htmlhelper)
    {
        int num = IncrementFormCount(htmlhelper.ViewContext.HttpContext.Items);
        return string.Format(CultureInfo.InvariantCulture, "form{0}", new object[] { num });
    }

    private static IDisposable FormHelper(this HtmlHelper htmlHelper, string formAction, FormMethod method, IDictionary<string, object> htmlAttributes)
    {
        var builder = new TagBuilder("form");
        builder.MergeAttributes<string, object>(htmlAttributes);
        builder.MergeAttribute("action", formAction);
        builder.MergeAttribute("method", HtmlHelper.GetFormMethodString(method), true);
        bool flag = htmlHelper.ViewContext.ClientValidationEnabled && !htmlHelper.ViewContext.UnobtrusiveJavaScriptEnabled;
        if (flag)
        {
            builder.GenerateId(htmlHelper.DefaultFormIdGenerator());
        }
        htmlHelper.ViewContext.Writer.Write(builder.ToString(TagRenderMode.StartTag));
        var form = new MvcForm(htmlHelper.ViewContext);
        if (flag)
        {
            htmlHelper.ViewContext.FormContext.FormId = builder.Attributes["id"];
        }
        return form;
    }
}

这可以这样使用:

@using (Html.BeginForm(htmlAttributes: new { name = "value" }))
{
    ...
}

谢谢@Darin。您能描述一下IncrementFormCountDefaultFormIdGeneratorFormHelper是做什么的吗? - Jonathan Wood
@JonathanWood,FormHelper使用TagBuilders生成<form>元素。 DefaultFormIdGenerator基本上通过将其计数存储在HttpContext中并在每次增加时递增来跟踪给定视图中呈现的表单数量。它基本上允许您拥有类似于form0、form1、form2等的表单ID,这被无侵入式客户端验证所使用。 - Darin Dimitrov
谢谢。看起来可以了。但是,我不得不修改使用方式为 @using (Html.BeginForm(HtmlHelper.AnonymousObjectToHtmlAttributes(new { @class = "form-horizontal" }))),以避免与只接受路由值的 BeginForm() 版本产生歧义。 - Jonathan Wood
@JonathanWood,不,这并不能解决你的问题,因为还有一个BeginForm重载,它需要一个RouteValueDictionary来表示routeValues而不是htmlAttributes。因此,如果你这样做,你的表单上就不会有name html属性了。我已经更新了我的答案,以说明如何使用命名参数来解决这个问题:@using (Html.BeginForm(htmlAttributes: new { name = "value" })) - Darin Dimitrov
我明白了。HtmlHelper.AnonymousObjectToHtmlAttributes()也返回类型为RouteValueDictionary。这很奇怪。我发布的代码没有任何问题就编译通过了。但是你的版本更加简洁。 - Jonathan Wood
@JonathanWood,你的代码编译没有报错,但在运行时我猜它没有调用正确的重载。 - Darin Dimitrov

1
我曾经遇到过类似的问题,这里有一个快速解决方案(适用于MVC4)。
声明扩展方法:
public static MvcForm BeginForm(this HtmlHelper helper, object htmlAttributes)
{
        return helper.BeginForm(helper.ViewContext.RouteData.Values["Action"].ToString(),
                                helper.ViewContext.RouteData.Values["Controller"].ToString(), 
                                FormMethod.Post, htmlAttributes);
}

并在您的页面中使用它:

@using (Html.BeginForm(htmlAttributes: new {@class="form-horizontal"})) 
{
...
}

2
我使用这种方法会丢失所有的查询字符串。 - Mike Cole

0

源代码的小修改:

http://aspnetwebstack.codeplex.com/SourceControl/latest#src/System.Web.Mvc/Html/FormExtensions.cs

public static MvcForm BeginForm(this HtmlHelper htmlHelper, object htmlAttributes)
{
   // generates <form action="{current url}" method="post">...</form>
   string formAction = htmlHelper.ViewContext.HttpContext.Request.RawUrl;
   return FormHelper(htmlHelper, formAction, FormMethod.Post, new RouteValueDictionary(htmlAttributes));
}

private static MvcForm FormHelper(this HtmlHelper htmlHelper, string formAction, FormMethod method, IDictionary<string, object> htmlAttributes)
{
   TagBuilder tagBuilder = new TagBuilder("form");
   tagBuilder.MergeAttributes(htmlAttributes);
   // action is implicitly generated, so htmlAttributes take precedence.
   tagBuilder.MergeAttribute("action", formAction);
   // method is an explicit parameter, so it takes precedence over the htmlAttributes.
   tagBuilder.MergeAttribute("method", HtmlHelper.GetFormMethodString(method), true);

   bool traditionalJavascriptEnabled = htmlHelper.ViewContext.ClientValidationEnabled
                                       && !htmlHelper.ViewContext.UnobtrusiveJavaScriptEnabled;

   if (traditionalJavascriptEnabled)
   {
       // forms must have an ID for client validation
       tagBuilder.GenerateId(htmlHelper.ViewContext.FormIdGenerator());
   }

   htmlHelper.ViewContext.Writer.Write(tagBuilder.ToString(TagRenderMode.StartTag));
   MvcForm theForm = new MvcForm(htmlHelper.ViewContext);

   if (traditionalJavascriptEnabled)
   {
       htmlHelper.ViewContext.FormContext.FormId = tagBuilder.Attributes["id"];
   }

   return theForm;
}

ViewContext.FormIdGenerator() 是内部的。 :-( - Keith Hill

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