例如,有一个名称为GuestBookController的Controller,这时我们创建View Page时,就需要在Views下面创建一个GuestBook的子文件夹,不能随便命名。然后把View page或者是partial view放进来,或者把文件放在Shared文件夹中也行,如果把view page放在web工程根目录下面,系统会找不到对应的view。
本文解决问题:针对上面的局限性,我们能不能打破呢?即可以实现如下功能:
第一:view page可以放在views之外的文件夹中。
第二:view page的名称和Controller的名称取消名称上的约束,例如:view page名称是ViewContentPage1.aspx,而对应的Controller名称是testController.cs。
实现原理:asp.net mvc之所以能根据用户请求找到对应的view page,主要是路由。这其中有一个重要的类WebFormViewEngine,它负责发现我们创建的view page或者是partial view。为此我们可以重写一个新的ViewEngine来完成我的目的。
1:先看下webformviewengine的构造函数:
{
base.MasterLocationFormats = new string[] { "~/Views/{1}/{0}.master", "~/Views/Shared/{0}.master" };
base.ViewLocationFormats = new string[] { "~/Views/{1}/{0}.aspx", "~/Views/{1}/{0}.ascx", "~/Views/Shared/{0}.aspx", "~/Views/Shared/{0}.ascx" };
base.PartialViewLocationFormats = base.ViewLocationFormats;
}
base.ViewLocationFormats 可以看出view page为什么只能写在views文件夹下的原因了。所以我们只需要在新的view engine的构造函数中修改下base.ViewLocationFormats 路径即可。这里我创建一个新的WebViewEngine,它需要继承WebFormViewEngine。可以这样改写:这样第一点就完成了。
{
base.MasterLocationFormats = new string[] { "~/Views/{1}/{0}.master", "~/Views/Shared/{0}.master" };
base.ViewLocationFormats = new string[] {
"~/com/{0}.aspx",
"~/com/{0}.ascx",
"~/Views/{1}/{0}.aspx",
"~/Views/{1}/{0}.ascx",
"~/Common/Views/{0}.aspx",
"~/Common/Views/{0}.ascx",
"~/Views/Shared/{0}.aspx",
"~/Views/Shared/{0}.ascx" };
base.PartialViewLocationFormats = base.ViewLocationFormats;
}
2:WebFormViewEngine有两个重要方法:FindPartialView以及FindView。它负责从用户请求以及路由配置中查找到具体的view page或者是partial view。第二点就可以在这下手。
首先:我们需要修改下路由配置,增加一个参数viewpath,用来指定路由规则使用的view path路径。
"Default2",
"ViewContentPage1.aspx",
new { id = "", viewpath = "~/com/ViewContentPage1.aspx", controller = "test", action = "test" }
);
其次:修改下WebFormViewEngine的源码:这里主要是修改GetPath方法,它最终会返回一个view page的路径。这样我们就可以这样访问了http://www.testmy.com/guestbook/ViewContentPage1.aspx。
{
string[] strArray;
if (controllerContext == null)
{
throw new ArgumentNullException("controllerContext");
}
if (string.IsNullOrEmpty(partialViewName))
{
throw new ArgumentException("MvcResources.Common_NullOrEmpty", "partialViewName");
}
string requiredString = controllerContext.RouteData.GetRequiredString("controller");
//string vp = this.GetParam(controllerContext, "viewpath");
//if (!string.IsNullOrEmpty(vp))
//{
// requiredString = vp;
//}
string str2 = this.GetPath(controllerContext, this.PartialViewLocationFormats, "PartialViewLocationFormats", partialViewName, requiredString, "Partial",
useCache,false , out strArray);
if (string.IsNullOrEmpty(str2))
{
return new ViewEngineResult(strArray);
}
return new ViewEngineResult(this.CreatePartialView(controllerContext, str2), this);
}
public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
{
string[] strArray;
string[] strArray2;
if (controllerContext == null)
{
throw new ArgumentNullException("controllerContext");
}
if (string.IsNullOrEmpty(viewName))
{
throw new ArgumentException("ArgumentException", "viewName");
}
string requiredString = controllerContext.RouteData.GetRequiredString("controller");
//string vp=this.GetParam(controllerContext, "viewpath");
//if (!string.IsNullOrEmpty(vp))
//{
// requiredString = vp;
//}
string str2 = this.GetPath(controllerContext, this.ViewLocationFormats, "ViewLocationFormats", viewName, requiredString, "View", useCache,true , out strArray);
string str3 = this.GetPath(controllerContext, this.MasterLocationFormats, "MasterLocationFormats", masterName, requiredString, "Master", useCache,true , out
strArray2);
if (string.IsNullOrEmpty(str2) || (string.IsNullOrEmpty(str3) && !string.IsNullOrEmpty(masterName)))
{
return new ViewEngineResult(strArray.Union<string>(strArray2));
}
return new ViewEngineResult(this.CreateView(controllerContext, str2, str3), this);
}
private string GetParam(ControllerContext controllerContext, string key)
{
return controllerContext.RouteData.Values[key] != null ? controllerContext.RouteData.Values[key].ToString() : string.Empty;
}
private string GetPath(ControllerContext controllerContext, string[] locations, string locationsPropertyName, string name, string controllerName, string cacheKeyPrefix,
bool useCache, bool isfindview, out string[] searchedLocations)
{
searchedLocations = _emptyLocations;
if (string.IsNullOrEmpty(name))
{
return string.Empty;
}
if ((locations == null) || (locations.Length == 0))
{
throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture, "MvcResources.Common_PropertyCannotBeNullOrEmpty", new object[] {
locationsPropertyName }));
}
bool flag = IsSpecificPath(name);
string key = this.CreateCacheKey(cacheKeyPrefix, name, flag ? string.Empty : controllerName);
if (useCache)
{
string viewLocation = this.ViewLocationCache.GetViewLocation(controllerContext.HttpContext, key);
if (viewLocation != null)
{
return viewLocation;
}
}
return (flag ? this.GetPathFromSpecificName(controllerContext, name, key,isfindview , ref searchedLocations) : this.GetPathFromGeneralName(controllerContext,
locations, name, controllerName, key,isfindview , ref searchedLocations));
}
private string CreateCacheKey(string prefix, string name, string controllerName)
{
return string.Format(CultureInfo.InvariantCulture, ":ViewCacheEntry:{0}:{1}:{2}:{3}:", new object[] { base.GetType().AssemblyQualifiedName, prefix, name,
controllerName });
}
private static bool IsSpecificPath(string name)
{
char ch = name[0];
return ((ch == '~') || (ch == '/'));
}
private string GetPathFromSpecificName(ControllerContext controllerContext, string name, string cacheKey,bool isfindview, ref string[] searchedLocations)
{
string virtualPath = name;
if (!this.FileExists(controllerContext, name))
{
if (isfindview)
{
string vp = this.GetParam(controllerContext, "viewpath");
if (!string.IsNullOrEmpty(vp))
{
virtualPath = vp;
}
if (string.IsNullOrEmpty(virtualPath))
{
virtualPath = string.Empty;
searchedLocations = new string[] { name };
}
}
else
{
virtualPath = string.Empty;
searchedLocations = new string[] { name };
}
}
this.ViewLocationCache.InsertViewLocation(controllerContext.HttpContext, cacheKey, virtualPath);
return virtualPath;
}
private string GetPathFromGeneralName(ControllerContext controllerContext, string[] locations, string name, string controllerName, string cacheKey, bool isfindview, ref
string[] searchedLocations)
{
string virtualPath = string.Empty;
searchedLocations = new string[locations.Length];
for (int i = 0; i < locations.Length; i++)
{
string str2 = string.Format(CultureInfo.InvariantCulture, locations[i], new object[] { name, controllerName });
if (this.FileExists(controllerContext, str2))
{
searchedLocations = _emptyLocations;
virtualPath = str2;
this.ViewLocationCache.InsertViewLocation(controllerContext.HttpContext, cacheKey, virtualPath);
return virtualPath;
}
else
{
if (isfindview)
{
string vp = this.GetParam(controllerContext, "viewpath");
if (!string.IsNullOrEmpty(vp))
{
searchedLocations = _emptyLocations;
virtualPath = vp;
return virtualPath;
}
}
}
searchedLocations[i] = str2;
}
return virtualPath;
}
最后:在程序中注册新的view engine:
ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new WebViewEngine());
RegisterRoutes(RouteTable.Routes);
总结:很久没有更新mvc的文章了,不过这篇在实际项目中还是非常有用的,例如,我们可以把两个不同的view page指定同一个Controller等等。