用不同情况渲染的布尔字段

Dis*_*nky 12 c# asp.net-mvc razor

对于有想法的人来说,这是一个奇怪的...我正在特定页面上渲染一个隐藏的布尔字段.但是,对于同一个字段,我会得到两个略有不同的标记,具体取决于特定事件是否在流程之前发生.生成的两个字段是;

<input id="HasPreviouslyIssuedCard" name="HasPreviouslyIssuedCard" type="hidden" value="false" />
Run Code Online (Sandbox Code Playgroud)

<input id="HasPreviouslyIssuedCard" name="HasPreviouslyIssuedCard" type="hidden" value="False" />
Run Code Online (Sandbox Code Playgroud)

问题是"值"属性中的文本的情况,您会注意到该文本是不同的,后来影响JS条件.生成这个的剃刀标记是;

@Html.Hidden("HasPreviouslyIssuedCard", Model.HasPreviouslyIssuedCard?.ToString(), new { id = nameof(Model.HasPreviouslyIssuedCard) })
Run Code Online (Sandbox Code Playgroud)

但是,我也尝试使用以下变体,在渲染隐藏字段时具有相同的差异;

@Html.HiddenFor(m => m.HasPreviouslyIssuedCard)
Run Code Online (Sandbox Code Playgroud)

我该怎么做才能获得这种差异?要获得大写变体,我在进入相关页面之前点击浏览器后退按钮.两种方法都以相同的方式加载数据,并以相同的方式将相同的值传递给渲染器.两种不同的输出.

请记住,这是一个要呈现的布尔值类型字段.应该没有太大的修补空间.有很多种方法可以解决这个问题,但由于我们在积压工作中有两个与布尔字段和后退按钮有关的项目,我想解释一下而不是解决它.

我最好的猜测是,按下后退按钮会以某种方式改变渲染器的状态,或者模型中的其他一些标志是不同的(有70多个字段,因为它是一个向导)正在改变渲染器如何解释如何设置布尔值值.值相同,页面相同,数据以相同的方式读取.

基于这个页面(为什么Boolean.ToString输出"True"而不是"true"),我们应该假设一直是获得大写变体,但这不是结果.

任何接受者/想法/想法?

编辑1

通过HiddenFor()方法挖掘MVC的渲染逻辑,最终Convert.ToString(value, CultureInfo.CurrentCulture)被称为.直接调用时,我不能让它产生一个小写的布尔值,但它显然是这样做的.我当前的文化代码设置为en-IE但我在直接调用时看到了大写的布尔值.

编辑2

我已经对我的应用程序进行了更多的修补和跟踪,并且可以提供更多有关正在发生的事情的详细信息,但我还没有能够在更简单的应用程序中重现这一点.

MVC 5应用:它有;

  1. 初始登录页面,/通过HTTP检索根域的URL GET.布尔input标签呈现为True/False
  2. /Apply通过HTTP检索的URL向导中的第一页GET.布尔input标签呈现为True/False
  3. 在步骤2中用户提交页面后,向导中的第二页在同一URL处.通过HTTP检索POST.案例input标签现在呈现为true/ false.
  4. 点击浏览器的后退按钮并点击陷阱页面(我们将浏览器历史记录设置为始终点击Back上的陷阱页面,因为它与向导一起玩快乐地狱).
  5. 用户点击陷阱页面上的按钮,将它们带回到他们中断的应用程序中.input标签现在以大写形式返回渲染(原始报告的问题).

我一直在钻研MVC库,使用ILSpy来尝试扫描和MVC(如果我正确地读取代码)实际上使用了一个实现IConverter来编写布尔值,而Convert.ToString(value, CultureInfo.CurrentCulture)不像我原先想的那样.

从调用中追踪的代码堆栈HiddenFor()(我认为);

  1. System.Web.Mvc.InputExtentions.HiddenFor() (公共职能)
  2. System.Web.Mvc.InputExtentions.HiddenHelper() (私有函数,这里有一些逻辑用于数组但不适用于我们的情况)
  3. System.Web.Mvc.InputExtentions.InputHelper() (私人功能,魔术发生在这里)

反编译代码System.Web.Mvc.InputExtentions.InputHelper();

private static MvcHtmlString InputHelper(HtmlHelper htmlHelper, InputType inputType, ModelMetadata metadata, string name, object value, bool useViewData, bool isChecked, bool setId, bool isExplicitValue, string format, IDictionary<string, object> htmlAttributes)
{
    string fullHtmlFieldName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name);
    if (string.IsNullOrEmpty(fullHtmlFieldName))
    {
        throw new ArgumentException(MvcResources.Common_NullOrEmpty, "name");
    }
    TagBuilder tagBuilder = new TagBuilder("input");
    tagBuilder.MergeAttributes<string, object>(htmlAttributes);
    tagBuilder.MergeAttribute("type", HtmlHelper.GetInputTypeString(inputType));
    tagBuilder.MergeAttribute("name", fullHtmlFieldName, true);
    string text = htmlHelper.FormatValue(value, format);
    bool flag = false;
    switch (inputType)
    {
    case InputType.CheckBox:
    {
        bool? flag2 = htmlHelper.GetModelStateValue(fullHtmlFieldName, typeof(bool)) as bool?;
        if (flag2.HasValue)
        {
            isChecked = flag2.Value;
            flag = true;
        }
        break;
    }
    case InputType.Hidden:
        goto IL_131;
    case InputType.Password:
        if (value != null)
        {
            tagBuilder.MergeAttribute("value", text, isExplicitValue);
            goto IL_16C;
        }
        goto IL_16C;
    case InputType.Radio:
        break;
    default:
        goto IL_131;
    }
    if (!flag)
    {
        string text2 = htmlHelper.GetModelStateValue(fullHtmlFieldName, typeof(string)) as string;
        if (text2 != null)
        {
            isChecked = string.Equals(text2, text, StringComparison.Ordinal);
            flag = true;
        }
    }
    if (!flag && useViewData)
    {
        isChecked = htmlHelper.EvalBoolean(fullHtmlFieldName);
    }
    if (isChecked)
    {
        tagBuilder.MergeAttribute("checked", "checked");
    }
    tagBuilder.MergeAttribute("value", text, isExplicitValue);
    goto IL_16C;
    IL_131:
    string text3 = (string)htmlHelper.GetModelStateValue(fullHtmlFieldName, typeof(string));
    tagBuilder.MergeAttribute("value", text3 ?? (useViewData ? htmlHelper.EvalString(fullHtmlFieldName, format) : text), isExplicitValue);
    IL_16C:
    if (setId)
    {
        tagBuilder.GenerateId(fullHtmlFieldName);
    }
    ModelState modelState;
    if (htmlHelper.ViewData.ModelState.TryGetValue(fullHtmlFieldName, out modelState) && modelState.Errors.Count > 0)
    {
        tagBuilder.AddCssClass(HtmlHelper.ValidationInputCssClassName);
    }
    tagBuilder.MergeAttributes<string, object>(htmlHelper.GetUnobtrusiveValidationAttributes(name, metadata));
    if (inputType == InputType.CheckBox)
    {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.Append(tagBuilder.ToString(TagRenderMode.SelfClosing));
        TagBuilder tagBuilder2 = new TagBuilder("input");
        tagBuilder2.MergeAttribute("type", HtmlHelper.GetInputTypeString(InputType.Hidden));
        tagBuilder2.MergeAttribute("name", fullHtmlFieldName);
        tagBuilder2.MergeAttribute("value", "false");
        stringBuilder.Append(tagBuilder2.ToString(TagRenderMode.SelfClosing));
        return MvcHtmlString.Create(stringBuilder.ToString());
    }
    return tagBuilder.ToMvcHtmlString(TagRenderMode.SelfClosing);
}
Run Code Online (Sandbox Code Playgroud)

编辑3

只是重述,因为有一些关于javascript方面的评论.我在尝试诊断问题时就考虑过这个问题.为了排除任何潜在的JS干扰/操纵,我使用Fiddler来捕获传输中的HTML.MVC正在生成的HTML正在改变案例 - 我可以在Fiddler中看到这一点,在JS甚至加载之前的某个时刻,从不介意运行.这不是JS问题.

Dis*_*nky 3

看来我错了,毕竟JS,尽管还没有到我试图排除它的地步。重现此事件的顺序;

  1. form使用布尔隐藏字段+非提交按钮创建 HTML
  2. onclick按钮的事件中,有一段 JS 将布尔字段设置为true(小写)
  3. 在控制器中的 POST 操作中,渲染相同的视图

POST 后,布尔字段将具有小写值而不是大写值。

这是怎么回事?好吧,如果集合中存在键HiddenFor(),(及其变体)将渲染布尔值ViewData.ModelState而不是模型属性的值。看起来很直观,但可以(并且对我来说)让您感到困惑的是,所有其他数据类型、模型绑定器都是非常具体的 - 值和模型的属性值将是相同的。除了布尔值的情况之外 - 模型绑定器足够智能,可以在转换 POST 时将和视为相同,但是如果通过 JS 布尔值设置,则这会使该值和字符串化属性值变得不正常。当模型属性的值显示为时,JS 将设置该值。而在 JS 中,.ToString()ModelStateModelStateTruetrueModelStateModelStatetrueToString()True"true" !== "True"

至于为什么在点击浏览器的“后退”按钮后会重置 - 我们的陷阱页面没有表单值,也没有通过 HTTP GET 链接回应用程序,这会导致HiddenFor调用模型属性.ToString()而不是从ModelStateas中提取它那时它还不在那儿。同样,在我的测试中,用户在通过 JS 设置很久之后就会处于向导中的某个点,因此当他们继续通过向导时,它将保持大写状态。

我的假设是 JS 在页面加载后启动。这实际上是由于 JS 在启动 POST 之前设置了一个值,并且该小写值通过ModelState. 去搞清楚。

编辑

复制代码;

模型;

public class Test
{
    public bool Sample { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

剃刀标记;

@model TestModelValueCase.Models.Test
@{
    ViewBag.Title = "Test Page";
}

@using (Html.BeginForm())
{
    @Html.HiddenFor(m => m.Sample)
    <div>
        <label>Hidden Value:</label>
        <span id="_uiValue"></span>
    </div>
    <button type="submit">Try Post</button>
}
@section scripts
{
    <script type="text/javascript" language="javascript">
        $(document).ready(function() {
            var source = $('#@nameof(Model.Sample)');
            $('#_uiValue').html(source.val());
            source.val(true);
        });
    </script>
}
Run Code Online (Sandbox Code Playgroud)

控制器;

    public ActionResult Index()
    {
        Test model = new Test();
        return View(model);
    }

    [HttpPost]
    public ActionResult Index(Test model)
    {
        return View(model);
    }
Run Code Online (Sandbox Code Playgroud)