我似乎遇到了ASP.NET MVC的问题,如果我在一个页面上有多个表单,每个表单在每个表单中使用相同的名称,但是作为不同的类型(radio / hidden / etc),那么,当第一个表单发布时(我选择'Date'单选按钮),如果表单被重新呈现(比如作为结果页面的一部分),我似乎有问题,SearchType的隐藏值其他表单更改为最后一个单选按钮值(在本例中为SearchType.Name)。
以下是用于减少目的的示例表单。
<% Html.BeginForm("Search", "Search", FormMethod.Post); %>
<%= Html.RadioButton("SearchType", SearchType.Date, true) %>
<%= Html.RadioButton("SearchType", SearchType.Name) %>
<input type="submit" name="submitForm" value="Submit" />
<% Html.EndForm(); %>
<% Html.BeginForm("Search", "Search", FormMethod.Post); %>
<%= Html.Hidden("SearchType", SearchType.Colour) %>
<input type="submit" name="submitForm" value="Submit" />
<% Html.EndForm(); %>
<% Html.BeginForm("Search", "Search", FormMethod.Post); %>
<%= Html.Hidden("SearchType", SearchType.Reference) %>
<input type="submit" name="submitForm" value="Submit" />
<% Html.EndForm(); %>
产生的页面源(这将是结果页面的一部分)
<form action="/Search/Search" method="post">
<input type="radio" name="SearchType" value="Date" />
<input type="radio" name="SearchType" value="Name" />
<input type="submit" name="submitForm" value="Submit" />
</form>
<form action="/Search/Search" method="post">
<input type="hidden" name="SearchType" value="Name" /> <!-- Should be Colour -->
<input type="submit" name="submitForm" value="Submit" />
</form>
<form action="/Search/Search" method="post">
<input type="hidden" name="SearchType" value="Name" /> <!-- Should be Reference -->
<input type="submit" name="submitForm" value="Submit" />
</form>
请RC1的其他人确认一下吗?
也许是因为我正在使用枚举。我不知道。我应该补充一点,我可以通过对隐藏字段使用'manual'input()标记来解决这个问题,但如果我使用MVC标记(&lt;%= Html.Hidden(...)%&gt;),.NET MVC每次都替换它们。
非常感谢。
更新
我今天又看到了这个bug。当你返回一个发布的页面并使用MVC设置隐藏的表单标签和Html帮助程序时,这似乎就会产生影响。我已经联系过Phil Haack了解这个问题,因为我不知道还有什么地方可以转变,而且我不认为这应该是大卫规定的预期行为。
答案 0 :(得分:36)
是的,此行为目前正在设计中。即使您明确设置了值,如果您回发到同一个URL,我们也会查看模型状态并在那里使用值。通常,这允许我们显示您在回发时提交的值,而不是原始值。
有两种可能的解决方案:
为每个字段使用唯一名称。请注意,默认情况下,我们使用您指定的名称作为HTML元素的ID。多个元素具有相同的ID是无效的HTML。因此,使用唯一名称是一种很好的做法。
请勿使用隐藏的助手。看起来你真的不需要它。相反,你可以这样做:
<input type="hidden" name="the-name"
value="<%= Html.AttributeEncode(Model.Value) %>" />
当然,正如我对此的考虑更多,基于回发更改值对于Textboxes是有意义的,但对隐藏的输入没有多大意义。我们无法为v1.0更改此内容,但我会将其视为v2。但我们需要仔细思考这种变化的含义。
答案 1 :(得分:10)
与其他人相同我希望ModelState用于填充模型,并且当我们在视图中明确使用模型时,它应该使用Model而不是ModelState。
这是一个设计选择,我确实得到了原因:如果验证失败,输入值可能无法解析模型中的数据类型,并且您仍希望呈现用户键入的任何错误值,因此很容易纠正它。
我唯一不理解的是:为什么设计不使用模型,开发人员明确设置,如果发生验证错误,则使用ModelState。强>
我见过很多人使用像
这样的解决方法<强>问题强>
所以问题是模型是从ModelState填充的,在我们明确设置的视图中使用Model。除非存在验证错误,否则每个人都希望使用Model值(如果它已更改);然后可以使用ModelState。
目前在MVC Helper扩展中,ModelState值优先于Model值。
<强>解决方案强>
因此,此问题的实际修复应该是:对于每个表达式来提取Model值,如果该值没有验证错误,则应删除ModelState值。如果该输入控件存在验证错误,则不应删除ModelState值,并且将像平常一样使用它。 我认为这完全解决了这个问题,这比大多数解决方法更好。
代码在这里:
/// <summary>
/// Removes the ModelState entry corresponding to the specified property on the model if no validation errors exist.
/// Call this when changing Model values on the server after a postback,
/// to prevent ModelState entries from taking precedence.
/// </summary>
public static void RemoveStateFor<TModel, TProperty>(this HtmlHelper helper,
Expression<Func<TModel, TProperty>> expression)
{
//First get the expected name value. This is equivalent to helper.NameFor(expression)
string name = ExpressionHelper.GetExpressionText(expression);
string fullHtmlFieldName = helper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name);
//Now check whether modelstate errors exist for this input control
ModelState modelState;
if (!helper.ViewData.ModelState.TryGetValue(fullHtmlFieldName, out modelState) ||
modelState.Errors.Count == 0)
{
//Only remove ModelState value if no modelstate error exists,
//so the ModelState will not be used over the Model
helper.ViewData.ModelState.Remove(name);
}
}
然后我们在调用MVC扩展之前创建自己的HTML Helper扩展:
public static MvcHtmlString TextBoxForModel<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression,
string format = "",
Dictionary<string, object> htmlAttributes = null)
{
RemoveStateFor(htmlHelper, expression);
return htmlHelper.TextBoxFor(expression, format, htmlAttributes);
}
public static IHtmlString HiddenForModel<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression)
{
RemoveStateFor(htmlHelper, expression);
return htmlHelper.HiddenFor(expression);
}
此解决方案消除了该问题,但不要求您反编译,分析和重建MVC正常提供的任何内容(不要忘记管理更改的持续时间,浏览器差异等)。
我认为“模型值的逻辑,除非验证错误然后是ModelState”应该是按设计的。如果是的话,它就不会被这么多人咬伤,但仍然涵盖了MVC的意图。
答案 2 :(得分:6)
我刚遇到同样的问题。传递值的TextBox()优先级之类的Html助手似乎与我从Documentation推断的行为完全相反:
文本输入元素的值。如果此值为null引用 (在Visual Basic中没有任何内容),从中检索元素的值 ViewDataDictionary对象。如果那里没有值,则值为 从ModelStateDictionary对象中检索。
对我来说,我读过如果传递了值,则使用该值。但是阅读TextBox()源代码:
string attemptedValue = (string)htmlHelper.GetModelStateValue(name, typeof(string));
tagBuilder.MergeAttribute("value", attemptedValue ?? ((useViewData) ? htmlHelper.EvalString(name) : valueParameter), isExplicitValue);
似乎表明实际的顺序与记录的完全相反。实际订单似乎是:
答案 3 :(得分:6)
答案 4 :(得分:5)
这将是预期的行为 - MVC不使用视图状态或其他背后技巧来传递表单中的额外信息,因此它不知道您提交的是哪种表单(表单名称不是数据的一部分)提交,只有一个名称/值对列表。)
当MVC重新呈现表单时,它只是检查是否存在具有相同名称的提交值 - 再次,它无法知道命名值来自哪个表单,甚至不知道它是什么类型的控件(无论你使用收音机,文本还是隐藏的,当通过HTTP提交时,它都只是name = value。)
答案 5 :(得分:4)
foreach (var s in ModelState.Keys.ToList())
if (s.StartsWith("detalleProductos"))
ModelState.Remove(s);
ModelState.Remove("TimeStamp");
ModelState.Remove("OtherOfendingHiddenFieldNamePostedToSamePage1");
ModelState.Remove("OtherOfendingHiddenFieldNamePostedToSamePage2");
return View(model);
答案 6 :(得分:3)
重现“设计问题”的示例,以及可能的工作方法。 尝试找到“虫子”的3个小时没有解决方法,但...... 请注意,此“设计”仍在ASP.NET MVC 2.0 RTM中。
[HttpPost]
public ActionResult ProductEditSave(ProductModel product)
{
//Change product name from what was submitted by the form
product.Name += " (user set)";
//MVC Helpers are using, to find the value to render, these dictionnaries in this order:
//1) ModelState 2) ViewData 3) Value
//This means MVC won't render values modified by this code, but the original values posted to this controller.
//Here we simply don't want to render ModelState values.
ModelState.Clear(); //Possible workaround which works. You loose binding errors information though... => Instead you could replace HtmlHelpers by HTML input for the specific inputs you are modifying in this method.
return View("ProductEditForm", product);
}
如果您的表单最初包含此内容:<%= Html.HiddenFor( m => m.ProductId ) %>
如果“名称”的原始值(在呈现表单时)是“虚拟”,则在提交表单后,您希望看到“虚拟(用户设置)”呈现。
如果没有ModelState.Clear()
,你仍会看到“虚拟”!!!!!!
正确的解决方法:
<input type="hidden" name="Name" value="<%= Html.AttributeEncode(Model.Name) %>" />
我觉得这根本不是一个好设计,因为每个mvc表单开发人员都需要牢记这一点。
答案 7 :(得分:3)
这个问题在MVC 5中仍然存在,显然它不被认为是一个很好的错误。
我们发现,虽然按设计,这不是我们预期的行为。相反,我们总是希望隐藏字段的值与其他类型的字段类似地操作,而不是特殊处理,或者从一些模糊的集合中提取它的值(这会让我们想起ViewState!)。
一些发现(我们的正确值是模型值,不正确的是ModelState值):
Html.DisplayFor()
显示正确的值(从模型中提取)Html.ValueFor
不会(它从ModelState中提取)ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData).Model
拉出正确的值我们的解决方案是简单地实现我们自己的扩展:
/// <summary>
/// Custom HiddenFor that addresses the issues noted here:
/// http://stackoverflow.com/questions/594600/possible-bug-in-asp-net-mvc-with-form-values-being-replaced
/// We will only ever want values pulled from the model passed to the page instead of
/// pulling from modelstate.
/// Note, do not use 'ValueFor' in this method for these reasons.
/// </summary>
public static IHtmlString HiddenTheWayWeWantItFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression,
object value = null,
bool withValidation = false)
{
if (value == null)
{
value = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData).Model;
}
return new HtmlString(String.Format("<input type='hidden' id='{0}' name='{1}' value='{2}' />",
htmlHelper.IdFor(expression),
htmlHelper.NameFor(expression),
value));
}
答案 8 :(得分:1)
这可能是“按设计”,但不是记录的内容:
Public Shared Function Hidden( ByVal htmlHelper As System.Web.Mvc.HtmlHelper, ByVal name As String, ByVal value As Object) As String
System.Web.Mvc.Html.InputExtensions
的成员摘要:返回隐藏的输入标记。
参数:
htmlHelper:HTML助手。
name:用于查找值的表单字段名称和System.Web.Mvc.ViewDataDictionary键 value:隐藏输入的值。如果为null,则查看System.Web.Mvc.ViewDataDictionary,然后查看System.Web.Mvc.ModelStateDictionary的值。
这似乎表明,只有当value参数为null(或未指定)时,HtmlHelper才能在其他地方查找值。
在我的应用程序中,我有一个表单,其中:html.Hidden(“remote”,True)呈现为
<input id="remote" name="remote" type="hidden" value="False" />
请注意,该值会被ViewData.ModelState字典中的内容覆盖。
或者我错过了什么?
答案 9 :(得分:1)
所以在MVC 4中,“设计问题”仍然存在。这是我必须使用的代码,以便在集合中设置正确的隐藏值,因为无论我在控制器中做什么,视图总是显示不正确的值。
旧代码
for (int i = 0; i < Model.MyCollection.Count; i++)
{
@Html.HiddenFor(m => Model.MyCollection[i].Name) //It doesn't work. Ignores what I changed in the controller
}
更新的代码
for (int i = 0; i < Model.MyCollection.Count; i++)
{
<input type="hidden" name="MyCollection[@(i)].Name" value="@Html.AttributeEncode(Model.MyCollection[i].Name)" /> // Takes the recent value changed in the controller!
}
他们是否已在MVC 5中修复此问题?
答案 10 :(得分:1)
有解决方法:
public static class HtmlExtensions
{
private static readonly String hiddenFomat = @"<input id=""{0}"" type=""hidden"" value=""{1}"" name=""{2}"">";
public static MvcHtmlString HiddenEx<T>(this HtmlHelper htmlHelper, string name, T[] values)
{
var builder = new StringBuilder(values.Length * 100);
for (Int32 i = 0; i < values.Length;
builder.AppendFormat(hiddenFomat,
htmlHelper.Id(name),
values[i++].ToString(),
htmlHelper.Name(name)));
return MvcHtmlString.Create(builder.ToString());
}
}
答案 11 :(得分:0)
正如其他人所建议的,我使用直接html代码而不是使用HtmlHelpers(TextBoxFor,CheckBoxFor,HiddenFor等)。
使用这种方法的问题是你需要将name和id属性作为字符串。我想保持我的模型属性强类型,所以我使用了NameFor和IdFor HtmlHelpers。
<input type="hidden" name="@Html.NameFor(m => m.Name)" id="@Html.IdFor(m=>m.Name)" value="@Html.AttributeEncode(Model.Name)">
<强>更新强> 这是一个方便的HtmlHelper扩展
public static MvcHtmlString MyHiddenFor<TModel, TValue>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TValue>> expression, object htmlAttributes = null)
{
return new MvcHtmlString(
string.Format(
@"<input id=""{0}"" type=""hidden"" value=""{1}"" name=""{2}"">",
helper.IdFor(expression),
helper.NameFor(expression),
GetValueFor(helper, expression)
));
}
/// <summary>
/// Retrieves value from expression
/// </summary>
private static string GetValueFor<TModel, TValue>(HtmlHelper<TModel> helper, Expression<Func<TModel, TValue>> expression)
{
object obj = expression.Compile().Invoke(helper.ViewData.Model);
string val = string.Empty;
if (obj != null)
val = obj.ToString();
return val;
}
然后您可以像
一样使用它@Html.MyHiddenFor(m => m.Name)