转载请注明作者(think8848)和出处(http://think8848.cnblogs.com)
依照本人惯例,开篇先说些与主题无关的话:本来打算把写博客的这个习惯坚持下去,就算不能出精品,也能出一些水货,对于某些小问题提供点解决方案,但是今年的8月真可谓是多事之“秋”,很多事情都凑到一起去了,几乎没有时间学习新的东西,更别说去写博客了,9月眼看要过去一半了,昨天才憋出一个小东西,觉得还稍能滥竽充数下。
打算用ASP.NET MVC实现公司的某产品了,昨天遇到一个问题:在异常发生时转回提交前的页面后,原来输入的内容不见了,这可是个大问题,记得以前我在写《ASP.NET MVC异常处理方案》一文时已经解决了这个问题,怎么又看不见提交前的输入了呢,把以前的代码打开后发现了问题所在:
在当前的代码中表单的代码为:
<input id="txtName" name="Name" type="text" />
而之前能出现效果的表单代码为:
@Html.TextBoxFor(x => x.Name)
稍经测试,就发现,使用Html的扩展方法生成的<input>标签可以获得提交之前的值,但是自已手写的则不行,所以这个TextBox扩展方法中肯定有某种机制,能自动将值填进<input>标签中。一开始和同事讨论后觉得,使用@Html.TextBoxFor方法有一个好处,那就是如果更换了Name属性的名称,VS可以自动重构代码,使*.cshtml代码的x.Name自动更新至新的属性名,经测试后发现原来不是想的这么回事,修改了Name属性的名称,如:PName,使用VS重构代码,发现在视图中属性名居然没有改过来;而且如果使用Html的扩展方法,似乎也有一些问题,最重要一点就是不直观,在目前的Razor引擎中还不太明显,反正也没有设计器,但是如果以后有了Razor引擎有了设计器功能,基本可以断定的是,使用@Html.TextBoxFor()的方式很难能做到所见即所得的效果,而且在一个cshtml页面中,即时不能使用设计器,看代码时如果视图上使用@Html.XXX也不是很直观,既然使用Html扩展方法的方式即不能有利于重构代码,又不直观,那么使用Html标签的理由似乎就变的充分多了,如果使用这种方法,即使不会C#的人也可以写出来页面。
在这种想法的驱动下,想出一个办法:自已实现一个填充标签值的扩展方法。于是打开ASP.NET MVC 3源代码,看看在这个TextBox内部到底在做些什么,为什么它可以把模型(ViewData.Model)中的值,以及ViewData.ModelState中的值填充到标签中,一步一步查下来,发现原来实现方法比较简单,直接上代码:
public static class HtmlValueExtension { public static MvcHtmlString Value<TModel, TProperty>(this HtmlHelper<TModel> html, Expression<Func<TModel, TProperty>> expression) { ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData); return Value(html, metadata.PropertyName); } public static MvcHtmlString Value(this HtmlHelper html, string name) { string attemptedValue = null; ModelState modelState; if (html.ViewData.ModelState.TryGetValue(name, out modelState)) { if (modelState.Value != null) { attemptedValue = modelState.Value.ConvertTo(typeof(string), null /* culture */).ToString(); } } return new MvcHtmlString(attemptedValue ?? Convert.ToString(html.ViewData.Eval(name), CultureInfo.CurrentCulture)); } }
定义一个HtmlHelper<TModel>的扩展方法Value<TModel,TProperty>,然后根据Lambda表达式获取到指定属性的元数据,优先考虑从ModelState中拿出对应的数据,也就是提交前页面表单数据,如果这个数据为null,则尝试ViewData.Model中指定的数据,很简单吧:)
有了这个类,在页面上使用如下代码调用:
<input id="txtDeptName" name="Name" type="text" value="@Html.Value(x => x.Name)"/>
这样,就可以达到与@Html.TextBox()一样的效果了,但是从视图的代码角度来说,直观了不少,而且如果以后Razor引擎有了设计器,估计也可以不用调试也能看到页面效果了。
最后再友情提示下,如果您在一个Razor的视图中定义了一个表单标签,这个表单标签的值并不对应Model的某个属性,这时如果您想获取提交前的值话,使用Request.Params["TagName"]即可。