轉載請註明作者(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()的方式很難能做到所見即所得 (WYSIWYG)的效果,而且在一個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"]即可。