MVC 用擴展方法執行自定義視圖,替代 UIHint
項目中用了 Bootstrap , 這樣就不用寫太多的CSS了,省去很多事情。
但是這個業務系統需要輸入的地方很多,每個表都有100多個字段,每個頁面需要大量的表單。
把這些表單按 bootstrap 的格式寫出來,也是件頭痛的事情。
我想到模板,EditorTemplates UIHint, 但是 UIHint 需要用 Metadata 標注,一個一個的加,也是不現實的。
還有別外一種辦法,就是擴展 HtmlHelper。
要用HtmlHelper ,大家可能就想到了 TagBuilder 了,TagBuilder 基本全是 Hard code 了,不方便調整顯示格式。
最終我用了另外一種辦法:
在 HtmlHelper 擴展里,取自定義的視圖,視圖可以隨時改,又不用每個字段去加 UIHint.
1 public static MvcHtmlString EditorBlockAFor<TModel, TProperty>(this HtmlHelper<TModel> helper, string template, Expression<Func<TModel, TProperty>> property, bool withLabel, string containerClass = "col-xs-4", object htmlAttributes = null) { 2 3 var body = (MemberExpression)property.Body; 4 if (body == null) 5 throw new ArgumentException(); 6 7 var ctx = helper.ViewContext.Controller.ControllerContext; 8 var result = ViewEngines.Engines.FindPartialView(helper.ViewContext.Controller.ControllerContext, template); 9 if (result.View != null) { 10 11 var metadata = ModelMetadata.FromLambdaExpression(property, helper.ViewData); 12 //var model = metadata.Model; 13 14 var attrs = HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes); 15 16 using (var writer = new StringWriter(CultureInfo.CurrentCulture)) { 17 var vctx = new ViewContext(ctx, result.View, helper.ViewData, helper.ViewContext.TempData, writer); 18 vctx.ViewBag.PropertyName = string.Join(".", body.ToString().Split(new char[] { '.' }).Skip(1));//body.Member.Name; 19 20 vctx.ViewBag.ContainerClass = containerClass; 21 vctx.ViewBag.IsRequired = metadata.IsRequired; 22 vctx.ViewBag.HtmlAttributes = attrs; 23 vctx.ViewBag.WithLabel = withLabel; 24 25 vctx.ViewBag.DisplayName = metadata.DisplayName ?? metadata.PropertyName; 26 27 result.View.Render(vctx, writer); 28 29 return MvcHtmlString.Create(writer.ToString()); 30 } 31 } else { 32 throw new InvalidOperationException(string.Format("particle view {0} not found", template)); 33 } 34 }
?
這個是核心,其它的擴展都是調用這個方法,比如:
1 public static MvcHtmlString TextBlockFor<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> property, string containerClass = "col-lg-4 col-md-4 col-xs-4", object htmlAttributes = null, bool withLabel = true) { 2 return EditorBlockAFor(helper, "TextBlock", property, withLabel, containerClass, htmlAttributes); 3 }
?
在 EditorBlockAFor 這個方法里,有一句:
var result = ViewEngines.Engines.FindPartialView(helper.ViewContext.Controller.ControllerContext, template);
這個是去查找指定的自定義視圖,它就是整個思路的關鍵。
自定義視圖 TextBlock.cshtml?
@model object @{ this.Layout = null; string propertyExpression = ViewBag.PropertyName; string containerClass = ViewBag.ContainerClass; RouteValueDictionary htmlAttributes = SharedTemplatesHelper.MargeClass(ViewBag); } <div class="@containerClass"> @SharedTemplatesHelper.Label(ViewBag) @Html.TextBox(propertyExpression, null, htmlAttributes) </div>
注意,模型是object , 因為不確定模型的類型。
?
這個文件需要放到 Shared 下,這個是簡單的結構。
調用:
@Html.TextBlockFor(m => m.VesselInfo.VESSEL_NAME_CN, "col-lg-2 col-md-2")
最終會生成這樣一段HTML:
<div class="col-lg-2 col-md-2"> <span class="help-block">中文名稱 <span class="red">*</span> </span> <input class="form-control input-sm" data-val="true" data-val-length="The field 中文名稱 must be a string with a maximum length of 500." data-val-length-max="500" data-val-required="The 中文名稱 field is required." id="VesselInfo_VESSEL_NAME_CN" name="VesselInfo.VESSEL_NAME_CN" type="text" value="" /> </div>
?
長這個樣子:
?
?
--------------分隔線內是廢話,有興趣可以了解一下我的崩潰經歷-------------------
一切都按照設想的樣子,直到。。。。
有一天,同事說明明是 Required 的,為什么沒有執行驗證?
我簡單的看了一下,是因為沒有生成 data-xxx 這樣的驗證屬性。看了一下 Action ,就是 Return View(); 沒有傳遞 model 到視圖。
聲明了一個 model 傳給視圖 (return View(XXX);) 后,一切正常。
我做MVC3的時候,不給 Model 都會輸出驗證屬性,當時趕時間,沒有去深入研究為什么,還以為 MVC 5 的新特性呢。
國慶過7天豬一樣的生活,項目也進行的七七八八了,終于有時間回頭看看了。
下了MVC的最新源碼:
http://aspnetwebstack.codeplex.com/SourceControl/latest
版本號是 5.2.3.0, 我們項目中用的是 5.2.0.0 ,差別不大。
新建了一個測試項目,
編譯了一份MVC的DLL,連同PDB一起放到項目的引用目錄下,改了一下MVC的配置、引用,在 InputExtensions 下加了斷點,但是卻無法斷點。
按照網上的搜到的調試 MVC 源碼的方法去做,很不幸,沒有一個適用的。
搞到晚上8點多,還是沒有辦法調試進源碼。真是崩潰至極了。
又找到了篇 pdb 符號服務器的博文:
http://weblogs.asp.net/gunnarpeipman/stepping-into-asp-net-mvc-source-code-with-visual-studio-debugger
但是需要下載這些符號,電腦沒關,回去了。
今天按照博文的說明去做,仍然不能調試進源碼。
很有幸,找到另外一篇文:
http://blogs.msdn.com/b/micl/archive/2014/06/07/how-to-debug-your-code-with-mvc-fresh-source-code.aspx
Before each version of MVC launch, the contribute team always strong name each MVC related assembly by a specified keyfile 35MSSharedLib1024.snk which is located in tools folder to prevent assembly tamper. But the snk file that you get doesn't contain private key, that you can only delay signed all assemblies if you compile directly. Unfortunately, delay signed assembly doesn't support debug feature.
?
簡單的翻譯一下:
因為我們下載到的 MVC 源碼是經過簽名的,源碼里提供的密鑰不包含私鑰,只能是延遲簽名。很不幸,延遲答名是不能DEBUG 的。
(這方面我沒有經驗,不懂)
按照博文的做法,將相關的項目都去掉了簽名:
System.Web.Helpers
System.Web.Mvc
System.Web.Razor
System.web.WebPages
等
然后將 System.Web.WebPages 下的
AssemblyInfo.cs (在 Properties 下) 中的 InternalsVisibleTo 參數換成不帶版本號的。
[assembly: InternalsVisibleTo("System.Web.Mvc")]
[assembly: InternalsVisibleTo("System.Web.Helpers")]
編譯,修改測試項目的的 web.config ,將 System.Web.Mvc 的版本改成編譯的版本號,刪除原來的相關引用,添加為剛剛編譯過的相關DLL
加斷點,運行,調試進去了。
--------------分隔線-------------------
?
上面都是廢話。
調了一圈,發現在 ModelMetadata.cs 第 382 行:
ModelMetadata propertyMetadata = viewData.ModelMetadata.Properties.Where(p => p.PropertyName == expression).FirstOrDefault();
匹配不到 property, propertyMetadata 的結果是 null.
屬性明明是有的,就死活就是沒有屬性的 Metadata.
當快速監視 viewData.ModelMetadata 后,這個值又有了,說明問題出在這個 viewData.ModelMetadata 上。
在 ViewDataDictionary.cs ,定義的是 ViewDataDictionary , 第82行,
if (_modelMetadata == null && _model != null)
_model 無疑是傳到視圖里的 model
當 _modelMetadata = null 且 _model 不為 null 的時候,會去取模型的Metadata ,這就解釋了為什么當傳 Model 到視圖的時候,會輸出驗證屬性了。
當是不傳 Model 到視圖,就返回 null 了。
進入 ViewDataDictionaryOfModel.cs?
定義的是 ViewDataDictionary<TModel> ,繼承自 ViewDataDictionary
在 第35行,取父類的 ModelMetadata, 因為返回的是 null, 又去按 模型(Model) 的類型去取 Metadata.
接上面的自定義視圖,TextBlock.cshtml
@model object
...
...
指定的模型類型是 object , 這是因為它是 Shared 的,不確定模型的確切類型,所以我用了 object.
好了,問題來了,挖掘機哪家強?按 object 去取 Metadata 當然是取不到的!
轉了一轉,又把我逼到當初處理這個東西的原點上。
想到快速監視后,是可以取到想要的結果的,我把上面的擴展方法改加一句:
。。。
var vctx = new ViewContext(ctx, result.View, helper.ViewData, helper.ViewContext.TempData, writer);
vctx.ViewData.ModelMetadata = helper.ViewData.ModelMetadata;//////必須的,調了很長時間,定位問題在這個 ModelMetadata 上
。。。
運行,通過!
?
?
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
