In time of MVC1, I created a view engine to support customized view for shopping cart system. Recently I need this feature again in another project, that’s when I tried to make it a more generic way, and come across this post -
ASP.NET MVC 2 - Building extensible view engine.
It turn out to be working very well with my case with minor changes.
In my project, I want to be able to display a customized view for each carrier/program/language. The view engine will try to search the view in the following order:
// 0-View Name; 1-Controller; 2-Area, 3-Carrier; 4-Language
// 0-View Name; 1-Controller; 2-Area, 3-Carrier; 4-Language
"~/Content/Views/{3}/{1}/{0}.{4}.aspx",
"~/Content/Views/{3}/{1}/{0}.aspx",
"~/Content/Views/{3}/Shared/{0}.{4}.aspx",
"~/Content/Views/{3}/Shared/{0}.aspx",
"~/Views/{1}/{0}.{4}.aspx",
"~/Views/{1}/{0}.aspx",
"~/Views/Shared/{0}.{4}.aspx",
"~/Views/Shared/{0}.aspx",
Below is the extend view engine:
public class WebFormThemeViewEngine : WebFormExtensibleViewEngine
{
public WebFormThemeViewEngine()
{
// initialize placeholders dictionary
this.Config = new PlaceholdersDictionary();
this.Config.Add(3, GetCarrierName );
this.Config.Add(4, GetLanguageName);
// calls ValidateAndPrepareConfig method of the base class
ValidateAndPrepareConfig();
// 0-View Name; 1-Controller; 2-Area, 3-Carrier; 4-Language
// initialize *LocationFormats with appropriate values.
this.ViewLocationFormats = new string[] {
"~/Content/Views/{3}/{1}/{0}.{4}.aspx",
"~/Content/Views/{3}/{1}/{0}.aspx",
"~/Content/Views/{3}/Shared/{0}.{4}.aspx",
"~/Content/Views/{3}/Shared/{0}.aspx",
"~/Views/{1}/{0}.{4}.aspx",
"~/Views/{1}/{0}.aspx",
"~/Views/Shared/{0}.{4}.aspx",
"~/Views/Shared/{0}.aspx",
};
this.AreaViewLocationFormats = new string[] {
"~/Content/Areas/{2}/Views/{3}/{1}/{0}.{4}.aspx",
"~/Content/Areas/{2}/Views/{3}/{1}/{0}.aspx",
"~/Content/Areas/{2}/Views/{3}/Shared/{0}.{4}.aspx",
"~/Content/Areas/{2}/Views/{3}/Shared/{0}.aspx",
"~/Areas/{2}/Views/{1}/{0}.{4}.aspx",
"~/Areas/{2}/Views/{1}/{0}.aspx",
"~/Areas/{2}/Views/Shared/{0}.{4}.aspx",
"~/Areas/{2}/Views/Shared/{0}.aspx",
};
this.MasterLocationFormats = new string[] {
"~/Content/Views/{3}/Shared/{0}.{4}.master",
"~/Content/Views/{3}/Shared/{0}.master",
"~/Views/Shared/{0}.{4}.master",
"~/Views/Shared/{0}.master",
};
this.AreaMasterLocationFormats = new string[] {
"~/Content/Areas/{2}/Views/{3}/Shared/{0}.{4}.master",
"~/Content/Areas/{2}/Views/{3}/Shared/{0}.master",
"~/Areas/{2}/Views/Shared/{0}.{4}.master",
"~/Areas/{2}/Views/Shared/{0}.master",
};
this.PartialViewLocationFormats = new string[] {
"~/Content/Views/{3}/{1}/{0}.{4}.ascx",
"~/Content/Views/{3}/{1}/{0}.ascx",
"~/Content/Views/{3}/Shared/{0}.{4}.ascx",
"~/Content/Views/{3}/Shared/{0}.ascx",
"~/Views/{1}/{0}.{4}.ascx",
"~/Views/{1}/{0}.ascx",
"~/Views/Shared/{0}.{4}.ascx",
"~/Views/Shared/{0}.ascx",
};
this.AreaPartialViewLocationFormats = new string[] {
"~/Content/Areas/{2}/Views/{3}/{1}/{0}.{4}.ascx",
"~/Content/Areas/{2}/Views/{3}/{1}/{0}.ascx",
"~/Content/Areas/{2}/Views/{3}/Shared/{0}.{4}.ascx",
"~/Content/Areas/{2}/Views/{3}/Shared/{0}.ascx",
"~/Areas/{2}/Views/{1}/{0}.{4}.ascx",
"~/Areas/{2}/Views/{1}/{0}.ascx",
"~/Areas/{2}/Views/Shared/{0}.{4}.ascx",
"~/Areas/{2}/Views/Shared/{0}.ascx",
};
}
protected virtual object GetCarrierName(ControllerContext controllerContext, string locationFormat, ref bool skipLocation)
{
string carrierName = controllerContext.RouteData.Values["Carrier"] as string;
return carrierName;
}
protected virtual object GetLanguageName(ControllerContext controllerContext, string locationFormat, ref bool skipLocation)
{
string language = System.Threading.Thread.CurrentThread.CurrentUICulture.TwoLetterISOLanguageName;
return language;
}
}
Also include the WebFormExtensibleViewEngine class from Hennadiy Kurabko.
///
/// a PlaceholderValueFunc delegate that represents a signature of method used to calculate value of the placeholder.
///
///
/// a controller context
/// location format string
/// used to specify if we must skip processed location and does not perform any searching in it.
///an object and sends a boolean skipLocation argument by reference.
public delegate object PlaceholderValueFunc(ControllerContext controllerContext, string locationFormat, ref bool skipLocation);
///
/// link a placeholder number with calculation.
/// Integer key represents a placeholder number,
/// PlaceholderValueFunc delegate represents calculation logic.
///
public class PlaceholdersDictionary : Dictionary
{
}
///
///
///
public class WebFormExtensibleViewEngine : WebFormViewEngine
{
public WebFormExtensibleViewEngine()
: this(new PlaceholdersDictionary())
{
}
///
/// Constructor takes an instance of the PlaceholdersDictionary class as an argument
///
///
public WebFormExtensibleViewEngine(PlaceholdersDictionary config)
: base()
{
Config = config;
// calls to ValidateAndPrepareConfig method.
// It checks if {0}, {1}, {2} placeholders are used in dictionary, that is denied.
// And adds this three placeholders with anonymous delegates that simply return "{0}" for 0 placeholder, {1} for 1 placeholder
ValidateAndPrepareConfig();
}
public new string[] AreaMasterLocationFormats { get; set; }
public new string[] AreaPartialViewLocationFormats { get; set; }
public new string[] AreaViewLocationFormats { get; set; }
public new string[] ViewLocationFormats { get; set; }
public new string[] MasterLocationFormats { get; set; }
public new string[] PartialViewLocationFormats { get; set; }
protected PlaceholdersDictionary Config { get; set; }
public override ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache)
{
base.AreaPartialViewLocationFormats = PrepareLocationFormats(controllerContext, this.AreaPartialViewLocationFormats);
base.PartialViewLocationFormats = PrepareLocationFormats(controllerContext, this.PartialViewLocationFormats);
return base.FindPartialView(controllerContext, partialViewName, useCache);
}
public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
{
base.AreaViewLocationFormats = PrepareLocationFormats(controllerContext, this.AreaViewLocationFormats);
base.AreaMasterLocationFormats = PrepareLocationFormats(controllerContext, this.AreaMasterLocationFormats);
base.ViewLocationFormats = PrepareLocationFormats(controllerContext, this.ViewLocationFormats);
base.MasterLocationFormats = PrepareLocationFormats(controllerContext, this.MasterLocationFormats);
//if (string.IsNullOrEmpty(masterName))
// masterName = "Site";
return base.FindView(controllerContext, viewName, masterName, useCache);
}
///
/// checks if {0}, {1}, {2} placeholders are used in dictionary, that is denied.
///
protected virtual void ValidateAndPrepareConfig()
{
// Validate
if (Config.ContainsKey(0) || Config.ContainsKey(1) || Config.ContainsKey(2))
throw new InvalidOperationException("Placeholder index must be greater than 2. Because {0} - view name, {1} - controller name, {2} - area name.");
// Prepare
Config[0] = (ControllerContext controllerContext, string location, ref bool skipLocation) => "{0}";
Config[1] = (ControllerContext controllerContext, string location, ref bool skipLocation) => "{1}";
Config[2] = (ControllerContext controllerContext, string location, ref bool skipLocation) => "{2}";
}
protected virtual string[] PrepareLocationFormats(ControllerContext controllerContext, string[] locationFormats)
{
// First it checks if locationFormats array is null or contains no items
// simply returns this array as result if so.
if (locationFormats == null || locationFormats.Length == 0)
return locationFormats;
// it initializes locationFormatsPrepared local variable -
// a list that will be used to store locations that was prepared
// ready to be used by standard MVC mechanism.
ListlocationFormatsPrepared = new List ();
// for every location format it creates an array of values that will be used to replace placeholders.
// Each value calculated by invoking appropriate delegate. If delegate sets skipLocation flag to true,
// processing of location will be stopped and such location will be skipped.
foreach (string locationFormat in locationFormats)
{
object[] formatValues = new object[Config.Count];
bool skipLocation = false;
for (int i = 0; i < Config.Count; i++)
{
object formatValue = Config[i](controllerContext, locationFormat, ref skipLocation);
if (skipLocation) break;
formatValues[i] = formatValue;
}
if (skipLocation) continue;
locationFormatsPrepared.Add(string.Format(locationFormat, formatValues));
}
return locationFormatsPrepared.ToArray();
}
}
1 comment:
Post a Comment