Oxite移植到ASP.NET MVC2 BETA 笔记(关于Html.RenderPartialFromSkin)
在将Oxite移植到asp.net mvc2 beta平台后,经过一系列有关“方法调用”变更的修正后,终于能够通过编译运行起来了!(移植后的源码参见:http://ecubecms.codeplex.com/)但经过测试,发现存在一个问题:
在site.master中的<%Html.RenderPartialFromSkin("HeadCustomContents"); %>无法加载到"~/skins/admin/Views/…”下的*.ascx了("~/Views/”下的可以),换句话说Oxite中的Htmlhelper扩展方法RenderPartialFromSkin方法无法加载指定皮肤的视图了。下面对该问题的根源和如何解决做一分析。
一、Oxite 皮肤机制的简单分析。
1、Oxite首先实现了自己的ViewEngine,即Oxite.Skinning.OxiteWebFormViewEngine。
OxiteWebFormViewEngine继承自System.Web.MVC.WebFormViewEngine并实现了IOxiteViewEngine接口。它的主要职责是通过设置RootPath来封装请求的文件路径。
2、Oxite自动注册了一个实现IResultFilter接口的类Oxite.Filters.ViewEnginesResultFilter。ViewEnginesResultFilter的OnResultExecuting方法中根据指定的皮肤来实例化几个OxiteWebFormViewEngine(一个皮肤实例化一个,由SkinResolverRegistry.GenerateViewEngines方法来完成的。)并存储到ResultExecutingContext.Result.ViewEngineCollection和ViewData["OxiteViewEngines"]。
检索皮肤的优先顺序:
request.QueryString["skin"]
request.Cookies["skin"]
request.Url.PathAndQuery.StartsWith("/Admin")
ViewData["Skin"]
Site.Skin
3、Controller.View和PartialView方法通过ResultExecutingContext.Result.ViewEngineCollection检索到View(.aspx,.ascx)。而Html.RednerPartial方法则是直接通过ViewEngines.Engines集合来检索View,结果是检索不到有皮肤的View的。所以Oxite定制了RenderPartialFromSkin的Htmlhelper扩展方法,它不通过ViewEngines.Engines而通过ViewData["OxiteViewEngines"](别忘了上面提到过它是由ViewEnginesResultFilter.OnResultExecuting方法存进去的)以检索到View。
二、为什么加载不到有皮肤的ascx(aspx)文件了?
通过上面的分析,RenderPartialFromSkin方法加载View的原理很清楚了,在ASP.NET MVC1.0中运行的也非常好。但为什么移植到ASP.NET MVC2 BETA就不行了呢?
先看一下Oxite中的RenderPartialFromSkin方法加载到ViewEngines后都干了些什么?
foreach (IViewEngine viewEngine in (IEnumerable<IOxiteViewEngine>)newViewData[viewEngineCollectionName])
{
ViewEngineResult result = viewEngine.FindPartialView(htmlHelper.ViewContext, partialViewName, true);
…
}
上面这段代码的意思是,从newViewData[viewEngineCollectionName](注:ViewData["OxiteViewEngines”])中检索每个IViewEngine,并调用它们的FindPartialView方法。也就是:此处调用的FindPartialView方法在asp.net mvc2 beta下并没有找到由该方法的参数partialViewName指定的View。
三、如何初步解决问题。
先来看一下IViewEngine.FindPartialView方法的定义:
ViewEngineResult IViewEngine.FindPartialView(ControllerContext controllerContext,string partialViewName,bool useCache)
注意一下第三个参数:useCache。RenderPartialFromSkin方法调用时传的是true值(意思是使用缓存),那不使用缓存是不是能找到View呢,就把ture改为false。即这样调用:ViewEngineResult result = viewEngine.FindPartialView(htmlHelper.ViewContext, partialViewName,false);// true);
竟然成功了!至此,问题已初步解决。但“不使用缓存”,心里总有些遗憾。
四、深入分析问题的根源并完全解决问题。
如何解决上面提到的“遗憾”呢?首先的思路是在OxiteWebFormViewEngine中重载FindPartialView方法,这样我不但要写大量代码,而且要考虑缓存等各种情况,会深陷细节的泥沼。那只有换另一个思路了,能不能不直接调用FindPartialView方法。通过反射htmlhelper的RenderPartial方法,发现它最终是通过调用ViewEngineCollection.FindPartialView(ControllerContext controllerContext,string partialViewName),这个方法没有useCache参数,终于见到了解决问题的一丝曙光。
马上修改Oxite的有关RenderPartialFromSkin方法源码。
1、Oxite的ViewEnginesResultFilter中放入ViewData["OxiteViewEngines"]中的是IEnumerable<IOxiteViewEngine>,我把它改为ViewEngineCollection放进去。
ViewEngineCollection vec = new ViewEngineCollection(viewEngines.Cast<IViewEngine>().ToList());
viewData["OxiteViewEngines"] = vec;
注:此处修改的是Oxite.Filters.ViewEnginesResultFilter.setupSkinViewEngines()方法,位于Oxite(项目)/Filters/ViewEnginesResultFilter.cs中。
2、在renderPartialFromSkin方法中改成这样调用:
ViewEngineCollection vec = newViewData[viewEngineCollectionName] as ViewEngineCollection;
ViewEngineResult result = vec.FindPartialView(vc, partialViewName);
注:此处修改的是Oxite.Extensions.HtmlHelperExtensions.renderPartialFromSkin()方法,位于Oxite(项目)/Extensions/HtmlHelperExtensions.cs中。
至此,问题终于较完美的解决。
源码可参考我的ECubeCMS项目:http://ecubecms.codeplex.com/。