MVC 用擴(kuò)展方法執(zhí)行自定義視圖,替代 UIHint
項目中用了 Bootstrap , 這樣就不用寫太多的CSS了,省去很多事情。
但是這個業(yè)務(wù)系統(tǒng)需要輸入的地方很多,每個表都有100多個字段,每個頁面需要大量的表單。
把這些表單按 bootstrap 的格式寫出來,也是件頭痛的事情。
我想到模板,EditorTemplates UIHint, 但是 UIHint 需要用 Metadata 標(biāo)注,一個一個的加,也是不現(xiàn)實(shí)的。
還有別外一種辦法,就是擴(kuò)展 HtmlHelper。
要用HtmlHelper ,大家可能就想到了 TagBuilder 了,TagBuilder 基本全是 Hard code 了,不方便調(diào)整顯示格式。
最終我用了另外一種辦法:
在 HtmlHelper 擴(kuò)展里,取自定義的視圖,視圖可以隨時改,又不用每個字段去加 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 }
?
這個是核心,其它的擴(kuò)展都是調(diào)用這個方法,比如:
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);
這個是去查找指定的自定義視圖,它就是整個思路的關(guān)鍵。
自定義視圖 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 , 因?yàn)椴淮_定模型的類型。
?
這個文件需要放到 Shared 下,這個是簡單的結(jié)構(gòu)。
調(diào)用:
@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>
?
長這個樣子:
?
?
--------------分隔線內(nèi)是廢話,有興趣可以了解一下我的崩潰經(jīng)歷-------------------
一切都按照設(shè)想的樣子,直到。。。。
有一天,同事說明明是 Required 的,為什么沒有執(zhí)行驗(yàn)證?
我簡單的看了一下,是因?yàn)闆]有生成 data-xxx 這樣的驗(yàn)證屬性??戳艘幌?Action ,就是 Return View(); 沒有傳遞 model 到視圖。
聲明了一個 model 傳給視圖 (return View(XXX);) 后,一切正常。
我做MVC3的時候,不給 Model 都會輸出驗(yàn)證屬性,當(dāng)時趕時間,沒有去深入研究為什么,還以為 MVC 5 的新特性呢。
國慶過7天豬一樣的生活,項目也進(jìn)行的七七八八了,終于有時間回頭看看了。
下了MVC的最新源碼:
http://aspnetwebstack.codeplex.com/SourceControl/latest
版本號是 5.2.3.0, 我們項目中用的是 5.2.0.0 ,差別不大。
新建了一個測試項目,
編譯了一份MVC的DLL,連同PDB一起放到項目的引用目錄下,改了一下MVC的配置、引用,在 InputExtensions 下加了斷點(diǎn),但是卻無法斷點(diǎn)。
按照網(wǎng)上的搜到的調(diào)試 MVC 源碼的方法去做,很不幸,沒有一個適用的。
搞到晚上8點(diǎn)多,還是沒有辦法調(diào)試進(jìn)源碼。真是崩潰至極了。
又找到了篇 pdb 符號服務(wù)器的博文:
http://weblogs.asp.net/gunnarpeipman/stepping-into-asp-net-mvc-source-code-with-visual-studio-debugger
但是需要下載這些符號,電腦沒關(guān),回去了。
今天按照博文的說明去做,仍然不能調(diào)試進(jìn)源碼。
很有幸,找到另外一篇文:
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.
?
簡單的翻譯一下:
因?yàn)槲覀兿螺d到的 MVC 源碼是經(jīng)過簽名的,源碼里提供的密鑰不包含私鑰,只能是延遲簽名。很不幸,延遲答名是不能DEBUG 的。
(這方面我沒有經(jīng)驗(yàn),不懂)
按照博文的做法,將相關(guān)的項目都去掉了簽名:
System.Web.Helpers
System.Web.Mvc
System.Web.Razor
System.web.WebPages
等
然后將 System.Web.WebPages 下的
AssemblyInfo.cs (在 Properties 下) 中的 InternalsVisibleTo 參數(shù)換成不帶版本號的。
[assembly: InternalsVisibleTo("System.Web.Mvc")]
[assembly: InternalsVisibleTo("System.Web.Helpers")]
編譯,修改測試項目的的 web.config ,將 System.Web.Mvc 的版本改成編譯的版本號,刪除原來的相關(guān)引用,添加為剛剛編譯過的相關(guān)DLL
加斷點(diǎn),運(yùn)行,調(diào)試進(jìn)去了。
--------------分隔線-------------------
?
上面都是廢話。
調(diào)了一圈,發(fā)現(xiàn)在 ModelMetadata.cs 第 382 行:
ModelMetadata propertyMetadata = viewData.ModelMetadata.Properties.Where(p => p.PropertyName == expression).FirstOrDefault();
匹配不到 property, propertyMetadata 的結(jié)果是 null.
屬性明明是有的,就死活就是沒有屬性的 Metadata.
當(dāng)快速監(jiān)視 viewData.ModelMetadata 后,這個值又有了,說明問題出在這個 viewData.ModelMetadata 上。
在 ViewDataDictionary.cs ,定義的是 ViewDataDictionary , 第82行,
if (_modelMetadata == null && _model != null)
_model 無疑是傳到視圖里的 model
當(dāng) _modelMetadata = null 且 _model 不為 null 的時候,會去取模型的Metadata ,這就解釋了為什么當(dāng)傳 Model 到視圖的時候,會輸出驗(yàn)證屬性了。
當(dāng)是不傳 Model 到視圖,就返回 null 了。
進(jìn)入 ViewDataDictionaryOfModel.cs?
定義的是 ViewDataDictionary<TModel> ,繼承自 ViewDataDictionary
在 第35行,取父類的 ModelMetadata, 因?yàn)榉祷氐氖?null, 又去按 模型(Model) 的類型去取 Metadata.
接上面的自定義視圖,TextBlock.cshtml
@model object
...
...
指定的模型類型是 object , 這是因?yàn)樗?Shared 的,不確定模型的確切類型,所以我用了 object.
好了,問題來了,挖掘機(jī)哪家強(qiáng)?按 object 去取 Metadata 當(dāng)然是取不到的!
轉(zhuǎn)了一轉(zhuǎn),又把我逼到當(dāng)初處理這個東西的原點(diǎn)上。
想到快速監(jiān)視后,是可以取到想要的結(jié)果的,我把上面的擴(kuò)展方法改加一句:
。。。
var vctx = new ViewContext(ctx, result.View, helper.ViewData, helper.ViewContext.TempData, writer);
vctx.ViewData.ModelMetadata = helper.ViewData.ModelMetadata;//////必須的,調(diào)了很長時間,定位問題在這個 ModelMetadata 上
。。。
運(yùn)行,通過!
?
?
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

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