using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
namespace GPM.Web.Code { public class MasterMenuDataFilterAttribute : ActionFilterAttribute {
// private vairables supporting MasterPageFile discovery
private static Dictionary<string, string> _viewMasterType = new Dictionary<string, string>();
private static Regex _masterFilePath = new Regex("\\bMasterPageFile=\"(?<path>[^\"]*)\"", RegexOptions.Compiled);
// private members for improved readability
private HttpContextBase _httpContext;
private ControllerContext _controllerContext;
/// <summary>
/// Loads data for dynamic menus in our MasterPage (if applicable)
/// </summary>
private void LoadMenuData(string viewName, string masterPath) { if (string.IsNullOrEmpty(masterPath) || !System.IO.File.Exists(_httpContext.Server.MapPath(masterPath)))
return;
switch (Path.GetFileName(masterPath)) { case "Site.Master":
break;
case "Default.Master":
break;
case "Custom.Master":
break;
default:
break;
}
}
/// <summary>
/// Discovers the master page declared by the view so we can determine
/// which menu data we need loaded for the view
/// </summary>
/// <remarks>
/// If we find that we have too many controllers which don't need this
/// functionality we can impelment this as a filter attribute instead
/// and apply it only where needed.
/// </remarks>
public override void OnActionExecuted(ActionExecutedContext filterContext) { // this logic only applies to ViewResult
ViewResult result = filterContext.Result as ViewResult;
if (result == null)
return;
// store contexts as private members to make things easier
_httpContext = filterContext.HttpContext;
_controllerContext = filterContext.Controller.ControllerContext;
// get the default value for ViewName
if (string.IsNullOrEmpty(result.ViewName))
result.ViewName = _controllerContext.RouteData.GetRequiredString("action");
string cacheKey = _controllerContext.Controller.ToString() + "_" + result.ViewName;
// check to see if we have cached the MasterPageFile for this view
if (_viewMasterType.ContainsKey(cacheKey)) { // Load the data for the menus in our MasterPage
LoadMenuData(result.ViewName, _viewMasterType[cacheKey]);
return;
}
// get the MasterPageFile (if any)
string masterPath = DiscoverMasterPath(result);
// make sure this is thread-safe
lock (_viewMasterType) { // cache the value of MasterPageFile
if (!_viewMasterType.ContainsKey(cacheKey)) { _viewMasterType.Add(cacheKey, masterPath);
}
}
// now we can load the data for the menus in our MasterPage
LoadMenuData(result.ViewName, masterPath);
}
/// <summary>
/// Parses the View's source for the MasterPageFile attribute of the Page directive
/// </summary>
/// <param name="result">The ViewResult returned from the Controller's action</param>
/// <returns>The value of the Page directive's MasterPageFile attribute</returns>
private string DiscoverMasterPath(ViewResult result) { string masterPath = string.Empty;
// get the view
ViewEngineResult engineResult = result.ViewEngineCollection.FindView(
_controllerContext, result.ViewName, result.MasterName);
// oops! caller is going to throw a "view not found" exception for us, so just exit now
if (engineResult.View == null)
return string.Empty;
// we currently only support the WebForms view engine, so we'll exit if it isn't WebFormView
WebFormView view = engineResult.View as WebFormView;
if (view == null)
return string.Empty;
// open file contents and read header for MasterPage directive
using (StreamReader reader = System.IO.File.OpenText(_httpContext.Server.MapPath(view.ViewPath))) { // flag to help short circuit our loop early
bool readingDirective = false;
while (!reader.EndOfStream) { string line = reader.ReadLine();
// don't bother with empty lines
if (string.IsNullOrEmpty(line))
continue;
// check to see if the current line contains the Page directive
if (line.IndexOf("<%@ Page") != -1) readingDirective = true;
// if we're reading the Page directive, check this line for the MasterPageFile attribute
if (readingDirective) { Match filePath = _masterFilePath.Match(line);
if (filePath.Success) { // found it - exit loop
masterPath = filePath.Groups["path"].Value;
break;
}
}
// check to see if we're done reading the page directive (multiline directive)
if (readingDirective && line.IndexOf("%>") != -1) break; // no MasterPageFile attribute found
}
}
return masterPath;
}
}
}