WebFormViewEngine及用户控件寻址bug
在做我的网站的时候遇到了主题切换的问题,特总结与大家共享。
熟悉asp.net mvc的朋友都知道,mvc中,默认情况下视图都在views文件夹下放着。要想改变文件必须重写WebFormViewEngine,也就是从WebFormViewEngine继承。对于razor模板是从RazorViewEngine继承。
首选我们来回顾一下,传统的做法。假若一个网站有前台、个人中心、企业中心、后台组成(对于类似博客系统不同用户不同主题的现象问题一样),那么,每个功能都需要定义一个视图引擎。个人中心代码如下:
public class UserViewEngine : WebFormViewEngine {
public UserViewEngine()
{
MasterLocationFormats = new[]{
"~/Views/Users/{1}/{0}.master",
"~/Views/Users/shared/{0}.master"
}; ViewLocationFormats = new[]{
"~/Views/Users/{1}/{0}.aspx",
"~/Views/Users/{1}/{0}.ascx",
"~/Views/Users/shared/{0}.aspx",
"~/Views/Users/shared/{0}.ascx"
}; PartialViewLocationFormats = new[]{
"~/Views/Users/{1}/{0}.ascx",
"~/Views/Users/shared/{0}.ascx"
};
} public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
{
return base.FindView(controllerContext, viewName, masterName, useCache);
} public override ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache)
{
return base.FindPartialView(controllerContext, partialViewName, useCache);
}
}
企业中心代码如下:
public class MemberViewEngine : WebFormViewEngine {
public MemberViewEngine()
{
MasterLocationFormats = new[]{
"~/Views/Member/{1}/{0}.master",
"~/Views/Member/shared/{0}.master"
}; ViewLocationFormats = new[]{
"~/Views/Member/{1}/{0}.aspx",
"~/Views/Member/shared/{0}.aspx"
}; PartialViewLocationFormats = new[]{
"~/Views/Member/{1}/{0}.ascx",
"~/Views/Member/shared/{0}.ascx",
"~/Views/shared/{0}.ascx"
};
} public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
{
return base.FindView(controllerContext, viewName, masterName, useCache);
} public override ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache)
{
return base.FindPartialView(controllerContext, partialViewName, useCache);
}
}
对于前台和后台的模板引擎,可以自己通过简单的修改去实现 。
视图引擎能被调用的地方有三处,第一处是在globals中,也就是在application_start中注册视图引擎;第二个地方时在父类的构造函数中初始化;第三个是在view函数被调用前,也就是利用过滤器。
application_start中代码如下:
protected void Application_Start() {
ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new UserViewEngine());
AreaRegistration.RegisterAllAreas();
RegisterRoutes(RouteTable.Routes); }
构造函数中如下:
public ctor() {
ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new UserViewEngine()); }
过滤器中如下:
public override void OnActionExecuting(ActionExecutingContext filterContext) {
//其他操作
ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new UserViewEngine());
}
对于第一种调用方法,显然满足不了我们的需求。这样只有求助于第二种和第三种调用方法。 也就是在view函数被调用之前进行对当前视图引擎的替换。view函数一般如下:
public ActionResult About() {
return View();
}
这样便实现了我们的需求。
但是,目前实现的只能在单线程下使用,或者在同时都访问前台或者都放我用户中心的情况下是对的。假若同时出现并发现象。也就是一个用户访问企业中心,一个用户访问用户中心。为了说明问题(简单代码实现),贴代码如下,用户中心类似代码:
public ActionResult List() {
ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new UserViewEngine()); Thread.Sleep()
return View();
}
企业中心类似代码
public ActionResult List() {
ViewEngineCollection.Clear();
ViewEngineCollection.Add(new MemberViewEngine()); Thread.Sleep()
return View();
}
为了说明问题,我故意在上面加了线程睡眠时间。
现在,个人中心先被访问,然后企业中心再被访问。这样运行之后是会报错的,说找不到视图。
究其原因,是由于个人中心运行慢,企业中心运行快。那么用户中心的视图引擎就变成了企业中心的视图引擎。但我们明明都在函数里加了如下代码,为啥还报错呢?
ViewEngines.Engines.Clear(); ViewEngines.Engines.Add(new UserViewEngine());
ViewEngines.Clear(); ViewEngines.Add(new MemberViewEngine());
我抱着究根问底的态度去研究程序的源码,发觉微软的实现如下:
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. namespace System.Web.Mvc
{
public static class ViewEngines
{
private static readonly ViewEngineCollection _engines = new ViewEngineCollection
{
new WebFormViewEngine(),
new RazorViewEngine(),
}; public static ViewEngineCollection Engines
{
get { return _engines; }
}
}
}
看到上面的代码,大家应该都明白了问题出现的根源,是静态类造成的。
那么,我们有没解决问题的办法呢?幸运的是,微软给出了另外一个调用办法,这个方法是在controller中有一个非静态变量,定义如下:
private ViewEngineCollection _viewEngineCollection; [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "This entire type is meant to be mutable.")]
public ViewEngineCollection ViewEngineCollection
{
get { return _viewEngineCollection ?? ViewEngines.Engines; }
set { _viewEngineCollection = value; }
}
看到这里,大家是不是感觉到微软考虑问题是很周全了呢?
别急,我们看看 get { return _viewEngineCollection ?? ViewEngines.Engines; }
也就是说 _viewEngineCollection 为空的时候取的是ViewEngines.Engines。那么,我们在每个view函数被调用前都把ViewEngineCollection赋值不就行了吗?类似于下面的代码:
ViewEngineCollection.Clear(); ViewEngineCollection.Add(new MemberViewEngine());
有的朋友可能发觉,这样实现是不对的,他们会提出下面的实现办法:
ViewEngineCollection = new ViewEngineCollection { new MemberViewEngine() };
这样的代码一运行,确实正确,我们ok!
但这是在我们没用到用户控件的情况下,假若一个母版页中加载了用户控件,类似下面的代码
<%Html.RenderPartial("Menu");%>
上面的代码在运行中还是会报错的。
这个问题困扰了我一段时间,我想微软不会错吧。是不是我的程序出错了,郁闷了好久。
然后,我就想到了看看RenderPartial 是怎么实现的。一查源码,发觉代码如下:
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. namespace System.Web.Mvc.Html
{
public static class RenderPartialExtensions
{
// Renders the partial view with the parent's view data and model
public static void RenderPartial(this HtmlHelper htmlHelper, string partialViewName)
{
htmlHelper.RenderPartialInternal(partialViewName, htmlHelper.ViewData, null /* model */, htmlHelper.ViewContext.Writer, ViewEngines.Engines);
} // Renders the partial view with the given view data and, implicitly, the given view data's model
public static void RenderPartial(this HtmlHelper htmlHelper, string partialViewName, ViewDataDictionary viewData)
{
htmlHelper.RenderPartialInternal(partialViewName, viewData, null /* model */, htmlHelper.ViewContext.Writer, ViewEngines.Engines);
} // Renders the partial view with an empty view data and the given model
public static void RenderPartial(this HtmlHelper htmlHelper, string partialViewName, object model)
{
htmlHelper.RenderPartialInternal(partialViewName, htmlHelper.ViewData, model, htmlHelper.ViewContext.Writer, ViewEngines.Engines);
} // Renders the partial view with a copy of the given view data plus the given model
public static void RenderPartial(this HtmlHelper htmlHelper, string partialViewName, object model, ViewDataDictionary viewData)
{
htmlHelper.RenderPartialInternal(partialViewName, viewData, model, htmlHelper.ViewContext.Writer, ViewEngines.Engines);
}
}
}
以上是微软asp.net mvc 4中实现的源码,我们看到了,微软用的还是ViewEngines.Engines,也就是说用的还是静态变量。这样,我们便找到了问题的根源。
找到问题的根源后,我就在想,能不能把该函数重写了呢?我就跟踪到源码中去,结果发觉它的实现如下:
internal virtual void RenderPartialInternal(string partialViewName, ViewDataDictionary viewData, object model, TextWriter writer, ViewEngineCollection viewEngineCollection) {
if (String.IsNullOrEmpty(partialViewName))
{
throw new ArgumentException(MvcResources.Common_NullOrEmpty, "partialViewName");
} ViewDataDictionary newViewData = null; if (model == null)
{
if (viewData == null)
{
newViewData = new ViewDataDictionary(ViewData);
}
else
{
newViewData = new ViewDataDictionary(viewData);
}
}
else
{
if (viewData == null)
{
newViewData = new ViewDataDictionary(model);
}
else
{
newViewData = new ViewDataDictionary(viewData) { Model = model };
}
} ViewContext newViewContext = new ViewContext(ViewContext, ViewContext.View, newViewData, ViewContext.TempData, writer);
IView view = FindPartialView(newViewContext, partialViewName, viewEngineCollection);
view.Render(newViewContext, writer);
}
也即使说RenderPartialInternal 只能在程序集内部被调用,外部没法调用。这样的情况让我郁闷了好久。
然后我就把 RenderPartialInternal及其相关的源码复制了出来,组成了一个自己的类,幸运的是,复制出来的代码在略微修改后可以运行,实现如下:
public static class HtmlHelperExtension
{
public static void RenderPartial2(this HtmlHelper htmlHelper, string partialViewName)
{
RenderPartialInternal(htmlHelper.ViewContext,partialViewName, htmlHelper.ViewData, null /* model */, htmlHelper.ViewContext.Writer, ((Controller)htmlHelper.ViewContext.Controller).ViewEngineCollection);
} internal static IView FindPartialView(ViewContext viewContext, string partialViewName, ViewEngineCollection viewEngineCollection)
{
ViewEngineResult result = viewEngineCollection.FindPartialView(viewContext, partialViewName);
if (result.View != null)
{
return result.View;
} StringBuilder locationsText = new StringBuilder();
foreach (string location in result.SearchedLocations)
{
locationsText.AppendLine();
locationsText.Append(location);
} throw new InvalidOperationException(partialViewName+locationsText);
} internal static void RenderPartialInternal(ViewContext ViewContext, string partialViewName, ViewDataDictionary viewData, object model, TextWriter writer, ViewEngineCollection viewEngineCollection)
{
if (String.IsNullOrEmpty(partialViewName))
{
throw new ArgumentException("partialViewName");
} ViewDataDictionary newViewData = null; if (model == null)
{
newViewData = new ViewDataDictionary(viewData);
}
else
{
if (viewData == null)
{
newViewData = new ViewDataDictionary(model);
}
else
{
newViewData = new ViewDataDictionary(viewData) { Model = model };
}
} ViewContext newViewContext = new ViewContext(ViewContext, ViewContext.View, newViewData, ViewContext.TempData, writer);
IView view = FindPartialView(newViewContext, partialViewName, viewEngineCollection);
view.Render(newViewContext, writer);
}
}
这样用户控件的调用便变成了下面的形式:
<%Html.RenderPartial2("Menu");%>
经过测试,一切正确。
对于上面mvc中bug,不知道有朋友发觉没,应该有吧。这是我首次遇到,就发帖出来与大家共享。
这篇文章,其实也总结了mvc中自定义主题的实现方法。
这是我在做我的网站车聘网的时候遇到的。
上面的源码本来都折叠的,但发出来后竟然打不开,然后又重新贴了边,郁闷坏了,难道是博客园的编辑器有问题?
我用的cuteeditor,有知道的朋友说下。
WebFormViewEngine及用户控件寻址bug的更多相关文章
- C# 自定义控件VS用户控件
1 自定义控件与用户控件区别 WinForm中, 用户控件(User Control):继承自 UserControl,主要用于开发 Container 控件,Container控件可以添加其他Con ...
- .net 用户控件ascx.cs注册js脚本代码无效果
在.net web项目中碰到一个比较奇怪的问题,网上没找到解决方案,先自己mark一下 问题描述: 添加一个用户控件ascx,在后端.cs添加js注册脚本,执行后没有弹出框 注册脚本为: this.P ...
- winform 用户控件、 动态创建添加控件、timer控件、控件联动
用户控件: 相当于自定义的一个panel 里面可以放各种其他控件,并可以在后台一下调用整个此自定义控件. 使用方法:在项目上右键.添加.用户控件,之后用户控件的编辑与普通容器控件类似.如果要在后台往窗 ...
- C#窗体程序【用户控件-窗体】委托事件
这里的自定义控件是由普通控件组合而成的.希望事件响应代码推迟到使用自定义控件的窗体里写.步骤一:新建一个用户控件,放两个按钮,Tag分别是btn1,btn2.这两个按钮的共用单击事件处理代码如下: u ...
- WinForm用户控件、动态创建添加控件、timer控件--2016年12月12日
好文要顶 关注我 收藏该文 徐淳 关注 - 1 粉丝 - 3 0 0 用户控件: 通过布局将多个控件整合为一个控件,根据自己的需要进行修改,可对用户控件内的所有控件及控件属性进行修 ...
- wpf的UserControl用户控件怎么添加到Window窗体中
转载自 http://www.cnblogs.com/shuang121/archive/2013/01/09/2853591.html 我们来新建一个用户控件UserControl1.xaml &l ...
- winfrom获取用户控件里的控件对象
如何获取用户控件里的控件对象呢,其实思路也是很简单的, 比如有一个panel 用户控件 里面有许多的其他控件. 那么要找出一个Label控件怎么找呢,好的.现在我们就开始 首先,一个foreach循环 ...
- winform用户控件、动态创建添加控件、timer控件、控件联动
用户控件: 相当于自定义的一个panel 里面可以放各种其他控件,并可以在后台一下调用整个此自定义控件. 使用方法:在项目上右键.添加.用户控件,之后用户控件的编辑与普通容器控件类似.如果要在后台往窗 ...
- 039. asp.netWeb用户控件之七实现具有虚拟键盘的功能的用户控件
用户控件ascx代码: <%@ Control Language="C#" AutoEventWireup="true" CodeFile="K ...
随机推荐
- 高能天气——团队Scrum冲刺阶段-Day 3
高能天气--团队Scrum冲刺阶段-Day 3 今日完成任务 于欣月:完成天气预报部分收尾工作 余坤澎:进行特别关心的实现 康皓越:实现闹钟部分添加音乐 范雯琪:初步开始界面优化,寻找天气预报部分的背 ...
- Python中“if __name__=='__main__':”理解与总结
1 引言 在Python当中,如果代码写得规范一些,通常会写上一句“if __name__==’__main__:”作为程序的入口,但似乎没有这么一句代码,程序也能正常运行.这句代码多余吗?原理又在哪 ...
- 子类 父类强转 HttpServlet service实现
相当于 走父类 临时走了一趟 HttpServletRequest ->ServletRequets -> HttpServeltRequest /* */ public void ser ...
- NOIP练习赛题目6
长途旅行 难度级别:A: 运行时间限制:3000ms: 运行空间限制:262144KB: 代码长度限制:2000000B 试题描述 JY 是一个爱旅游的探险家,也是一名强迫症患者.现在JY 想要在C ...
- C++ 队列(queue)堆栈(stack)实现基础
Queue 在C++中只要#include<queue>即可使用队列类,其中在面试或笔试中常用的成员函数如下(按照最常用到不常用的顺序) 1. push 2. pop 3. size 4. ...
- BZOJ3712[PA2014]Fiolki 建图+倍增lca
居然是一道图论题 毫无思路 我们对于每一次的融合操作 $(a,b)$ 建一个新点$c$ 并向$a,b$连边 再将$b$瓶当前的位置赋成$c$ 这样子我们就可以建成一个森林 现在枚举每一种反应$M_i$ ...
- Eclipse添加Spket插件实现ExtJs智能提示
1 . 开发环境 MyEclipse 12.0.0 ExtJs 4.2.1.883 Spket 1.6.23 2 . 下载资源 extjs 4.2.1.883 - http://www.sencha. ...
- HDU 4741 Save Labman No.004 (2013杭州网络赛1004题,求三维空间异面直线的距离及最近点)
Save Labman No.004 Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Other ...
- Ubuntu 中启用 root 帐号
参考:http://linuxtoy.org/archives/howto_enable_ubuntu_root_account.html 如果你实在需要在 Ubuntu 中启用 root 帐号的话, ...
- 前端使用AngularJS的$resource,后端ASP.NET Web API,实现分页、过滤
在上一篇中实现了增删改查,本篇实现分页和过滤. 本系列包括: 1.前端使用AngularJS的$resource,后端ASP.NET Web API,实现增删改查2.前端使用AngularJS的$re ...