I want to change view locations at runtime based on current UI culture. How can I achieve this with default Web Form view engine?
我想基于当前的UI文化在运行时更改视图位置。如何使用默认的Web表单视图引擎实现这一点?
Basically I want to know how implement with WebFormViewEngine
something what is custom IDescriptorFilter in Spark.
基本上,我想知道如何用WebFormViewEngine实现在Spark中定制的IDescriptorFilter。
Is there other view engine which gives me runtime control over view locations?
是否有其他的视图引擎让我对视图位置进行运行时控制?
Edit: My URLs should looks following {lang}/{controller}/{action}/{id}
. I don't need language dependent controllers and views are localized with resources. However few of the views will be different in some languages. So I need to tell view engine to looks to the language specific folder first.
编辑:我的url应该是{lang}/{controller}/{action}/{id}。我不需要依赖语言的控制器,视图是用资源本地化的。然而,在某些语言中,很少有不同的视图。所以我需要告诉view engine先查找语言特定的文件夹。
5 个解决方案
#1
31
A simple solution would be to, in your Appication_Start
get hold of the appropriate ViewEngine
from the ViewEngines.Engines
collection and update its ViewLocationFormats
array and PartialViewLocationFormats
. No hackery: it's read/write by default.
一个简单的解决方案是,在Appication_Start中从ViewEngine获取适当的ViewEngine。引擎收集和更新它的ViewLocationFormats数组和PartialViewLocationFormats。没有恶意:它是默认的读/写。
protected void Application_Start()
{
...
// Allow looking up views in ~/Features/ directory
var razorEngine = ViewEngines.Engines.OfType<RazorViewEngine>().First();
razorEngine.ViewLocationFormats = razorEngine.ViewLocationFormats.Concat(new string[]
{
"~/Features/{1}/{0}.cshtml"
}).ToArray();
...
// also: razorEngine.PartialViewLocationFormats if required
}
The default one for Razor looks like this:
剃须刀的默认配置如下:
ViewLocationFormats = new string[]
{
"~/Views/{1}/{0}.cshtml",
"~/Views/{1}/{0}.vbhtml",
"~/Views/Shared/{0}.cshtml",
"~/Views/Shared/{0}.vbhtml"
};
Note that you may want to update PartialViewLocationFormats
also.
注意,您可能还想更新PartialViewLocationFormats。
#2
8
VirtualPathProviderViewEngine.GetPathFromGeneralName
must be changed to allow an additional parameter from the route. Its not public, that's why you have to copy GetPath
, GetPathFromGeneralName
, IsSpecificPath
...over to your own ViewEngine
implementation.
VirtualPathProviderViewEngine。必须更改GetPathFromGeneralName,以允许从路由中获得附加参数。它不是公共的,这就是为什么你必须复制GetPath, GetPathFromGeneralName, IsSpecificPath…转到您自己的ViewEngine实现。
You are right: this looks like a complete rewrite. I wished GetPathFromGeneralName
was public.
你是对的:这看起来像是一个完整的重写。我希望GetPathFromGeneralName是公共的。
using System.Web.Mvc;
using System;
using System.Web.Hosting;
using System.Globalization;
using System.Linq;
namespace MvcLocalization
{
public class LocalizationWebFormViewEngine : WebFormViewEngine
{
private const string _cacheKeyFormat = ":ViewCacheEntry:{0}:{1}:{2}:{3}:";
private const string _cacheKeyPrefix_Master = "Master";
private const string _cacheKeyPrefix_Partial = "Partial";
private const string _cacheKeyPrefix_View = "View";
private static readonly string[] _emptyLocations = new string[0];
public LocalizationWebFormViewEngine()
{
base.ViewLocationFormats = new string[] {
"~/Views/{1}/{2}/{0}.aspx",
"~/Views/{1}/{2}/{0}.ascx",
"~/Views/Shared/{2}/{0}.aspx",
"~/Views/Shared/{2}/{0}.ascx" ,
"~/Views/{1}/{0}.aspx",
"~/Views/{1}/{0}.ascx",
"~/Views/Shared/{0}.aspx",
"~/Views/Shared/{0}.ascx"
};
}
private VirtualPathProvider _vpp;
public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
{
if (controllerContext == null)
throw new ArgumentNullException("controllerContext");
if (String.IsNullOrEmpty(viewName))
throw new ArgumentException( "viewName");
string[] viewLocationsSearched;
string[] masterLocationsSearched;
string controllerName = controllerContext.RouteData.GetRequiredString("controller");
string viewPath = GetPath(controllerContext, ViewLocationFormats, "ViewLocationFormats", viewName, controllerName, _cacheKeyPrefix_View, useCache, out viewLocationsSearched);
string masterPath = GetPath(controllerContext, MasterLocationFormats, "MasterLocationFormats", masterName, controllerName, _cacheKeyPrefix_Master, useCache, out masterLocationsSearched);
if (String.IsNullOrEmpty(viewPath) || (String.IsNullOrEmpty(masterPath) && !String.IsNullOrEmpty(masterName)))
{
return new ViewEngineResult(viewLocationsSearched.Union(masterLocationsSearched));
}
return new ViewEngineResult(CreateView(controllerContext, viewPath, masterPath), this);
}
private string GetPath(ControllerContext controllerContext, string[] locations, string locationsPropertyName, string name, string controllerName, string cacheKeyPrefix, bool useCache, out string[] searchedLocations)
{
searchedLocations = _emptyLocations;
if (String.IsNullOrEmpty(name))
return String.Empty;
if (locations == null || locations.Length == 0)
throw new InvalidOperationException();
bool nameRepresentsPath = IsSpecificPath(name);
string cacheKey = CreateCacheKey(cacheKeyPrefix, name, (nameRepresentsPath) ? String.Empty : controllerName);
if (useCache)
{
string result = ViewLocationCache.GetViewLocation(controllerContext.HttpContext, cacheKey);
if (result != null)
{
return result;
}
}
return (nameRepresentsPath) ?
GetPathFromSpecificName(controllerContext, name, cacheKey, ref searchedLocations) :
GetPathFromGeneralName(controllerContext, locations, name, controllerName, cacheKey, ref searchedLocations);
}
private string GetPathFromGeneralName(ControllerContext controllerContext, string[] locations, string name, string controllerName, string cacheKey, ref string[] searchedLocations)
{
string result = String.Empty;
searchedLocations = new string[locations.Length];
string language = controllerContext.RouteData.Values["lang"].ToString();
for (int i = 0; i < locations.Length; i++)
{
string virtualPath = String.Format(CultureInfo.InvariantCulture, locations[i], name, controllerName,language);
if (FileExists(controllerContext, virtualPath))
{
searchedLocations = _emptyLocations;
result = virtualPath;
ViewLocationCache.InsertViewLocation(controllerContext.HttpContext, cacheKey, result);
break;
}
searchedLocations[i] = virtualPath;
}
return result;
}
private string CreateCacheKey(string prefix, string name, string controllerName)
{
return String.Format(CultureInfo.InvariantCulture, _cacheKeyFormat,
GetType().AssemblyQualifiedName, prefix, name, controllerName);
}
private string GetPathFromSpecificName(ControllerContext controllerContext, string name, string cacheKey, ref string[] searchedLocations)
{
string result = name;
if (!FileExists(controllerContext, name))
{
result = String.Empty;
searchedLocations = new[] { name };
}
ViewLocationCache.InsertViewLocation(controllerContext.HttpContext, cacheKey, result);
return result;
}
private static bool IsSpecificPath(string name)
{
char c = name[0];
return (c == '~' || c == '/');
}
}
}
#3
3
1) Extend the class from razor view engine
1)从razor视图引擎中扩展类
public class LocalizationWebFormViewEngine : RazorViewEngine
公共类LocalizationWebFormViewEngine: RazorViewEngine。
2) Add the partial location formats
2)添加部分位置格式
public LocalizationWebFormViewEngine()
{
base.PartialViewLocationFormats = new string[] {
"~/Views/{2}/{1}/{0}.cshtml",
"~/Views/{2}/{1}/{0}.aspx",
"~/Views/{2}/Shared/{0}.cshtml",
"~/Views/{2}/Shared/{0}.aspx"
};
base.ViewLocationFormats = new string[] {
"~/Views/{2}/{1}/{0}.cshtml",
"~/Views/{2}/{1}/{0}.aspx",
"~/Views/{2}/Shared/{0}.cshtml",
"~/Views/{2}/Shared/{0}.aspx"
};
}
3) Create the override method for partial view render
3)为局部视图渲染创建覆盖方法
public override ViewEngineResult FindPartialView(ControllerContext controllerContext, String partialViewName, Boolean useCache)
{
if (controllerContext == null)
{
throw new ArgumentNullException("controllerContext");
}
if (String.IsNullOrEmpty(partialViewName))
{
throw new ArgumentException("partialViewName");
}
string[] partialViewLocationsSearched;
string controllerName = controllerContext.RouteData.GetRequiredString("controller");
string partialPath = GetPath(controllerContext, PartialViewLocationFormats, "PartialViewLocationFormats", partialViewName, controllerName, _cacheKeyPrefix_Partial, useCache, out partialViewLocationsSearched);
return new ViewEngineResult(CreatePartialView(controllerContext, partialPath), this);}
}
#4
1
I believe that solution would be to create your own ViewEngine which inherits from WebFormViewEngine. In constructor, it should check current UI culture from current thread and add appropriate locations. Just don't forget to add it to your view engines.
我认为解决方案是创建您自己的ViewEngine,它继承自WebFormViewEngine。在构造函数中,它应该从当前线程检查当前UI区域性并添加适当的位置。不要忘记将它添加到视图引擎中。
This should look something like this:
应该是这样的:
public class ViewEngine : WebFormViewEngine
{
public ViewEngine()
{
if (CultureIsX())
ViewLocationFormats = new string[]{"route1/controller.aspx"};
if (CultureIsY())
ViewLocationFormats = new string[]{"route2/controller.aspx"};
}
}
in global.asax:
在global.asax:
ViewEngines.Engines.Add(new ViewEngine());
#5
1
Below is a localized view engine without the rewrite.
下面是一个没有重写的本地化视图引擎。
In a nutshell, the engine will insert new locations into the view locations everytime a view is looked up. The engine will use the two character language to find the view. So if the current language is es
(Spanish), it'll look for ~/Views/Home/Index.es.cshtml
.
简单地说,引擎将在每次查找视图时向视图位置插入新的位置。引擎将使用这两种字符语言来查找视图。因此,如果当前语言是es(西班牙语),它将查找~/Views/Home/Index.es.cshtml。
See code comments for more details.
有关更多细节,请参阅代码注释。
A better approach would be to override the way view locations are parsed, but the methods are not overridable; maybe in ASP.NET MVC 5?
更好的方法是重写视图位置解析的方式,但方法不可重写;也许在ASP。净MVC 5 ?
public class LocalizedViewEngine : RazorViewEngine
{
private string[] _defaultViewLocationFormats;
public LocalizedViewEngine()
: base()
{
// Store the default locations which will be used to append
// the localized view locations based on the thread Culture
_defaultViewLocationFormats = base.ViewLocationFormats;
}
public override ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache)
{
AppendLocalizedLocations();
return base.FindPartialView(controllerContext, partialViewName, useCache:fase);
}
public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
{
AppendLocalizedLocations();
returnbase.FindView(controllerContext, viewName, masterName, useCache:false);
}
private void AppendLocalizedLocations()
{
// Use language two letter name to identify the localized view
string lang = Thread.CurrentThread.CurrentUICulture.TwoLetterISOLanguageName;
// Localized views will be in the format "{action}.{lang}.cshtml"
string localizedExtension = string.Format(".{0}.cshtml", lang);
// Create an entry for views and layouts using localized extension
string view = "~/Views/{1}/{0}.cshtml".Replace(".cshtml", localizedExtension);
string shared = "~/Views/{1}/Shared/{0}".Replace(".cshtml", localizedExtension);
// Create a copy of the default view locations to modify
var list = _defaultViewLocationFormats.ToList();
// Insert the new locations at the top of the list of locations
// so they're used before non-localized views.
list.Insert(0, shared);
list.Insert(0, view);
base.ViewLocationFormats = list.ToArray();
}
}
#1
31
A simple solution would be to, in your Appication_Start
get hold of the appropriate ViewEngine
from the ViewEngines.Engines
collection and update its ViewLocationFormats
array and PartialViewLocationFormats
. No hackery: it's read/write by default.
一个简单的解决方案是,在Appication_Start中从ViewEngine获取适当的ViewEngine。引擎收集和更新它的ViewLocationFormats数组和PartialViewLocationFormats。没有恶意:它是默认的读/写。
protected void Application_Start()
{
...
// Allow looking up views in ~/Features/ directory
var razorEngine = ViewEngines.Engines.OfType<RazorViewEngine>().First();
razorEngine.ViewLocationFormats = razorEngine.ViewLocationFormats.Concat(new string[]
{
"~/Features/{1}/{0}.cshtml"
}).ToArray();
...
// also: razorEngine.PartialViewLocationFormats if required
}
The default one for Razor looks like this:
剃须刀的默认配置如下:
ViewLocationFormats = new string[]
{
"~/Views/{1}/{0}.cshtml",
"~/Views/{1}/{0}.vbhtml",
"~/Views/Shared/{0}.cshtml",
"~/Views/Shared/{0}.vbhtml"
};
Note that you may want to update PartialViewLocationFormats
also.
注意,您可能还想更新PartialViewLocationFormats。
#2
8
VirtualPathProviderViewEngine.GetPathFromGeneralName
must be changed to allow an additional parameter from the route. Its not public, that's why you have to copy GetPath
, GetPathFromGeneralName
, IsSpecificPath
...over to your own ViewEngine
implementation.
VirtualPathProviderViewEngine。必须更改GetPathFromGeneralName,以允许从路由中获得附加参数。它不是公共的,这就是为什么你必须复制GetPath, GetPathFromGeneralName, IsSpecificPath…转到您自己的ViewEngine实现。
You are right: this looks like a complete rewrite. I wished GetPathFromGeneralName
was public.
你是对的:这看起来像是一个完整的重写。我希望GetPathFromGeneralName是公共的。
using System.Web.Mvc;
using System;
using System.Web.Hosting;
using System.Globalization;
using System.Linq;
namespace MvcLocalization
{
public class LocalizationWebFormViewEngine : WebFormViewEngine
{
private const string _cacheKeyFormat = ":ViewCacheEntry:{0}:{1}:{2}:{3}:";
private const string _cacheKeyPrefix_Master = "Master";
private const string _cacheKeyPrefix_Partial = "Partial";
private const string _cacheKeyPrefix_View = "View";
private static readonly string[] _emptyLocations = new string[0];
public LocalizationWebFormViewEngine()
{
base.ViewLocationFormats = new string[] {
"~/Views/{1}/{2}/{0}.aspx",
"~/Views/{1}/{2}/{0}.ascx",
"~/Views/Shared/{2}/{0}.aspx",
"~/Views/Shared/{2}/{0}.ascx" ,
"~/Views/{1}/{0}.aspx",
"~/Views/{1}/{0}.ascx",
"~/Views/Shared/{0}.aspx",
"~/Views/Shared/{0}.ascx"
};
}
private VirtualPathProvider _vpp;
public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
{
if (controllerContext == null)
throw new ArgumentNullException("controllerContext");
if (String.IsNullOrEmpty(viewName))
throw new ArgumentException( "viewName");
string[] viewLocationsSearched;
string[] masterLocationsSearched;
string controllerName = controllerContext.RouteData.GetRequiredString("controller");
string viewPath = GetPath(controllerContext, ViewLocationFormats, "ViewLocationFormats", viewName, controllerName, _cacheKeyPrefix_View, useCache, out viewLocationsSearched);
string masterPath = GetPath(controllerContext, MasterLocationFormats, "MasterLocationFormats", masterName, controllerName, _cacheKeyPrefix_Master, useCache, out masterLocationsSearched);
if (String.IsNullOrEmpty(viewPath) || (String.IsNullOrEmpty(masterPath) && !String.IsNullOrEmpty(masterName)))
{
return new ViewEngineResult(viewLocationsSearched.Union(masterLocationsSearched));
}
return new ViewEngineResult(CreateView(controllerContext, viewPath, masterPath), this);
}
private string GetPath(ControllerContext controllerContext, string[] locations, string locationsPropertyName, string name, string controllerName, string cacheKeyPrefix, bool useCache, out string[] searchedLocations)
{
searchedLocations = _emptyLocations;
if (String.IsNullOrEmpty(name))
return String.Empty;
if (locations == null || locations.Length == 0)
throw new InvalidOperationException();
bool nameRepresentsPath = IsSpecificPath(name);
string cacheKey = CreateCacheKey(cacheKeyPrefix, name, (nameRepresentsPath) ? String.Empty : controllerName);
if (useCache)
{
string result = ViewLocationCache.GetViewLocation(controllerContext.HttpContext, cacheKey);
if (result != null)
{
return result;
}
}
return (nameRepresentsPath) ?
GetPathFromSpecificName(controllerContext, name, cacheKey, ref searchedLocations) :
GetPathFromGeneralName(controllerContext, locations, name, controllerName, cacheKey, ref searchedLocations);
}
private string GetPathFromGeneralName(ControllerContext controllerContext, string[] locations, string name, string controllerName, string cacheKey, ref string[] searchedLocations)
{
string result = String.Empty;
searchedLocations = new string[locations.Length];
string language = controllerContext.RouteData.Values["lang"].ToString();
for (int i = 0; i < locations.Length; i++)
{
string virtualPath = String.Format(CultureInfo.InvariantCulture, locations[i], name, controllerName,language);
if (FileExists(controllerContext, virtualPath))
{
searchedLocations = _emptyLocations;
result = virtualPath;
ViewLocationCache.InsertViewLocation(controllerContext.HttpContext, cacheKey, result);
break;
}
searchedLocations[i] = virtualPath;
}
return result;
}
private string CreateCacheKey(string prefix, string name, string controllerName)
{
return String.Format(CultureInfo.InvariantCulture, _cacheKeyFormat,
GetType().AssemblyQualifiedName, prefix, name, controllerName);
}
private string GetPathFromSpecificName(ControllerContext controllerContext, string name, string cacheKey, ref string[] searchedLocations)
{
string result = name;
if (!FileExists(controllerContext, name))
{
result = String.Empty;
searchedLocations = new[] { name };
}
ViewLocationCache.InsertViewLocation(controllerContext.HttpContext, cacheKey, result);
return result;
}
private static bool IsSpecificPath(string name)
{
char c = name[0];
return (c == '~' || c == '/');
}
}
}
#3
3
1) Extend the class from razor view engine
1)从razor视图引擎中扩展类
public class LocalizationWebFormViewEngine : RazorViewEngine
公共类LocalizationWebFormViewEngine: RazorViewEngine。
2) Add the partial location formats
2)添加部分位置格式
public LocalizationWebFormViewEngine()
{
base.PartialViewLocationFormats = new string[] {
"~/Views/{2}/{1}/{0}.cshtml",
"~/Views/{2}/{1}/{0}.aspx",
"~/Views/{2}/Shared/{0}.cshtml",
"~/Views/{2}/Shared/{0}.aspx"
};
base.ViewLocationFormats = new string[] {
"~/Views/{2}/{1}/{0}.cshtml",
"~/Views/{2}/{1}/{0}.aspx",
"~/Views/{2}/Shared/{0}.cshtml",
"~/Views/{2}/Shared/{0}.aspx"
};
}
3) Create the override method for partial view render
3)为局部视图渲染创建覆盖方法
public override ViewEngineResult FindPartialView(ControllerContext controllerContext, String partialViewName, Boolean useCache)
{
if (controllerContext == null)
{
throw new ArgumentNullException("controllerContext");
}
if (String.IsNullOrEmpty(partialViewName))
{
throw new ArgumentException("partialViewName");
}
string[] partialViewLocationsSearched;
string controllerName = controllerContext.RouteData.GetRequiredString("controller");
string partialPath = GetPath(controllerContext, PartialViewLocationFormats, "PartialViewLocationFormats", partialViewName, controllerName, _cacheKeyPrefix_Partial, useCache, out partialViewLocationsSearched);
return new ViewEngineResult(CreatePartialView(controllerContext, partialPath), this);}
}
#4
1
I believe that solution would be to create your own ViewEngine which inherits from WebFormViewEngine. In constructor, it should check current UI culture from current thread and add appropriate locations. Just don't forget to add it to your view engines.
我认为解决方案是创建您自己的ViewEngine,它继承自WebFormViewEngine。在构造函数中,它应该从当前线程检查当前UI区域性并添加适当的位置。不要忘记将它添加到视图引擎中。
This should look something like this:
应该是这样的:
public class ViewEngine : WebFormViewEngine
{
public ViewEngine()
{
if (CultureIsX())
ViewLocationFormats = new string[]{"route1/controller.aspx"};
if (CultureIsY())
ViewLocationFormats = new string[]{"route2/controller.aspx"};
}
}
in global.asax:
在global.asax:
ViewEngines.Engines.Add(new ViewEngine());
#5
1
Below is a localized view engine without the rewrite.
下面是一个没有重写的本地化视图引擎。
In a nutshell, the engine will insert new locations into the view locations everytime a view is looked up. The engine will use the two character language to find the view. So if the current language is es
(Spanish), it'll look for ~/Views/Home/Index.es.cshtml
.
简单地说,引擎将在每次查找视图时向视图位置插入新的位置。引擎将使用这两种字符语言来查找视图。因此,如果当前语言是es(西班牙语),它将查找~/Views/Home/Index.es.cshtml。
See code comments for more details.
有关更多细节,请参阅代码注释。
A better approach would be to override the way view locations are parsed, but the methods are not overridable; maybe in ASP.NET MVC 5?
更好的方法是重写视图位置解析的方式,但方法不可重写;也许在ASP。净MVC 5 ?
public class LocalizedViewEngine : RazorViewEngine
{
private string[] _defaultViewLocationFormats;
public LocalizedViewEngine()
: base()
{
// Store the default locations which will be used to append
// the localized view locations based on the thread Culture
_defaultViewLocationFormats = base.ViewLocationFormats;
}
public override ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache)
{
AppendLocalizedLocations();
return base.FindPartialView(controllerContext, partialViewName, useCache:fase);
}
public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
{
AppendLocalizedLocations();
returnbase.FindView(controllerContext, viewName, masterName, useCache:false);
}
private void AppendLocalizedLocations()
{
// Use language two letter name to identify the localized view
string lang = Thread.CurrentThread.CurrentUICulture.TwoLetterISOLanguageName;
// Localized views will be in the format "{action}.{lang}.cshtml"
string localizedExtension = string.Format(".{0}.cshtml", lang);
// Create an entry for views and layouts using localized extension
string view = "~/Views/{1}/{0}.cshtml".Replace(".cshtml", localizedExtension);
string shared = "~/Views/{1}/Shared/{0}".Replace(".cshtml", localizedExtension);
// Create a copy of the default view locations to modify
var list = _defaultViewLocationFormats.ToList();
// Insert the new locations at the top of the list of locations
// so they're used before non-localized views.
list.Insert(0, shared);
list.Insert(0, view);
base.ViewLocationFormats = list.ToArray();
}
}