您现在的位置: 365建站网 > 365文章 > Asp.Net MVC中Html.TextBox扩展,其行为也许不是你期望的

Asp.Net MVC中Html.TextBox扩展,其行为也许不是你期望的

文章来源:365jz.com     点击数:1497    更新时间:2009-09-18 10:23   参与评论

用ASP.Net MVC做网站,用的最多的扩展,大概就是Html.TextBox扩展,但在某些特定的情况下,其行为也许不是你所期望的那样。下面我们来看看这种特定的情况吧。

为了说明问题,我们假设有这样一个Modal类型:

</>code

  1. /// <summary>
  2. /// 我的对象
  3. /// </summary>
  4. public class MyObject
  5. {
  6. /// <summary>
  7. /// ID
  8. /// </summary>
  9. public int Id { get; set; }
  10. /// <summary>
  11. /// 名称
  12. /// </summary>
  13. public string Name { get; set; }
  14. }

然后你在Controller的Action中获得了该对象的一个实例和一个列表,并通过

</>code

  1. ViewData["myObjects"] = myObjects;
  2. return View(myObject);

把实例和这个列表输出到了View中,在View中,你想编辑这个列表中的每个对象,于是,你可能写了类似与下面所示的代码:

</>code

  1. <%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<MyObject>" %>

</>code

  1. <div class="clearfix">
  2. <div class="nameDiv">ID:</div>
  3. <div class="contentDiv"><%=Html.TextBox("id",Model.Id) %></div>
  4. </div>
  5. <div class="clearfix">
  6. <div class="nameDiv">名称:</div>
  7. <div class="contentDiv"><%=Html.TextArea("id",Model.Name) %></div>
  8. </div>
  9. </div>
    <div class="clearfix">
  10. <%foreach (var item in myObjects)
  11. {%>
  12. <div class="clearfix">
  13. <div class="nameDiv">ID:</div>
  14. <div class="contentDiv"><%=Html.TextBox("id",item.Id) %></div>
  15. </div>
  16. <div class="clearfix">
  17. <div class="nameDiv">名称:</div>
  18. <div class="contentDiv"><%=Html.TextBox("name",item.Name) %></div>
  19. </div>
  20. <%} %>
  21. </div>

如果你像我这样,在这种循环中,使用了Html.TextBox,Html.Hidden或任何input类型的扩展,以及Html.TextArea扩展,那么恭喜你,你会看到不希望看到的结果。

会看到什么结果呢?除了第一个MyObject输出了它的值以外,剩下的值也都变成了第一个对象的值。也即,假如你第一个对象的Name=”MyObject1”,那么你除了在第一个“名称”输入框内看到“MyObject1”以外,剩下的所有“名称”输入框内看到还是“MyObject1”。

为什么会这样呢?我们去看看Html.TextBox扩展的源代码吧!(我下的是RC版的代码,但问题在正式版V1中也一样)。

源代码在System.Web.Mvc项目的Mvc/Html文件夹的InputExtensions文件中,先看我们上面用到的那个扩展的签名和实现:

</>code

  1. public static string TextBox(this HtmlHelper htmlHelper, string name, object value) {
  2. return TextBox(htmlHelper, name, value, (object)null /* htmlAttributes */);
  3. }

我们就跟着它走吧,最后一直到

</>code

  1. return InputHelper(htmlHelper, InputType.Text, name, value, (value == null) /* useViewData */, false /* isChecked */,
    true /* setId */, true /* isExplicitValue */, htmlAttributes);
这儿了。那么我们去看看那个InputHelper函数的实现吧:

</>code

  1. private static string InputHelper(this HtmlHelper htmlHelper, InputType inputType, string name, object value, bool useViewData,
    bool isChecked, bool setId, bool isExplicitValue, IDictionary<string, object> htmlAttributes) {
  2. if (String.IsNullOrEmpty(name)) {
  3. throw new ArgumentException(MvcResources.Common_NullOrEmpty, "name");
  4. }
  5. TagBuilder tagBuilder = new TagBuilder("input");
  6. //略去属性name,type等
  7. string valueParameter = Convert.ToString(value, CultureInfo.CurrentCulture);
  8. switch (inputType) {
  9. //case InputType.CheckBox:
  10. //case InputType.Radio:
  11. //case InputType.Password:
  12. default:
  13. string attemptedValue = (string)htmlHelper.GetModelStateValue(name, typeof(string));
  14. tagBuilder.MergeAttribute("value",
    attemptedValue ?? ((useViewData) ? htmlHelper.EvalString(name) : valueParameter), isExplicitValue);
  15. break;
  16. }
  17. if (setId) {
  18. tagBuilder.GenerateId(name);
  19. }
  20. return tagBuilder.ToString(TagRenderMode.SelfClosing);
  21. }
从上面代码中我删除了许多与TextBox设置value值无关的代码,我们不管那些逻辑控制的代码,就看它是如何为input类型为text、hidden的标签设置value的:

</>code

  1. string valueParameter = Convert.ToString(value, CultureInfo.CurrentCulture);

首先,上面这行代码把我们设置的任意类型的value转换为string类型,接下来,看default中的两行:

</>code

  1. string attemptedValue = (string)htmlHelper.GetModelStateValue(name, typeof(string));
  2. tagBuilder.MergeAttribute("value", attemptedValue ?? ((useViewData) ? htmlHelper.EvalString(name) : valueParameter), isExplicitValue);
现在,你看到最关键的代码了,它在做什么?它不管我们是否已经显示的设置了value参数,试图去某个地方取值,而且,取出来的这个值有最高的优先级,如果不是null,它就是<input>标签的value值了,那么这个地方是哪儿呢? 好,我们去看看htmlHelper.GetModelStateValue(name, typeof(string))是怎么取值的呢?

</>code

  1. internal object GetModelStateValue(string key, Type destinationType) {
  2. ModelState modelState;
  3. if (ViewData.ModelState.TryGetValue(key, out modelState)) {
  4. return modelState.Value.ConvertTo(destinationType, null /* culture */);
  5. }
  6. return null;
  7. }

我们看到,它是从htmlHelper.ViewData.ModelState中取值的,好,我们先看看ViewData.ModelState.TryGetValue(key, out modelState):

</>code

  1. public bool TryGetValue(string key, out ModelState value) {
  2. return _innerDictionary.TryGetValue(key, out value);
  3. }

现在,我们看到它是从一个内部词典中根据我们输入的key取值的,那这个内部词典的值是从哪儿来的呢?也就是htmlHelper.ViewData.ModelState是何时被创建,并构造它的内部词典呢?

我们知道,我们的每个View都有一个Html属性,而且我们用的还很顺手,这个东东就是htmlHelper,那么,我们的每个View怎么会有这个属性呢?因为每个View都是直接或间接从ViewPage<TModel>或从ViewPage 类型中继承来的。那么,我们就去看ViewPage<TModel>是怎么创建和初始化htmlHelper的吧。那么,View又是谁负责创建的呢?我们只是在Controller的一个方法中返回了一个ViewResult的实例。要讲清楚这个问题,我们需要把整个MVC模式讲一次,我们只是简单的看看Controller这个类的一个方法吧:

</>code

  1. protected internal ViewResult View(object model) {
  2. return View(null /* viewName */, null /* masterName */, model);
  3. }
这个我们常用的方法,调用了

</>code

  1. protected internal virtual ViewResult View(string viewName, string masterName, object model) {
  2. if (model != null) {
  3. ViewData.Model = model;
  4. }
  5. return new ViewResult {
  6. ViewName = viewName,
  7. MasterName = masterName,
  8. ViewData = ViewData,
  9. TempData = TempData
  10. };
  11. }

我们看到,我们设置的model被赋给了ViewData.Model,那么是不是用这个构造我们上面看到的htmlHelper.ViewData.ModelState的内部词典的呢?我想是的。到这里,你应该明白了,为什么html.TextBox会表现的和我们预期的不一样,因为它始终想从内部ViewData中取值,而忽略我们显式设置的值。

在这种情况下,要么自己写一个TextBox扩展(也不难,是不是?),或者就用<input>标签吧,也不是很麻烦。

备注:本文不讨论文中描述的应用出现的场景。

Tag标签: MVC,C#

如对本文有疑问,请提交到交流论坛,广大热心网友会为你解答!! 点击进入论坛

发表评论 (1497人查看0条评论)
请自觉遵守互联网相关的政策法规,严禁发布色情、暴力、反动的言论。
昵称:
最新评论
------分隔线----------------------------

快速入口

· 365软件
· 杰创官网
· 建站工具
· 网站大全

其它栏目

· 建站教程
· 365学习

业务咨询

· 技术支持
· 服务时间:9:00-18:00
365建站网二维码

Powered by 365建站网 RSS地图 HTML地图

copyright © 2013-2024 版权所有 鄂ICP备17013400号