Skip to content

Instantly share code, notes, and snippets.

@MattHoneycutt
Created May 2, 2013 02:09
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save MattHoneycutt/5499729 to your computer and use it in GitHub Desktop.
Save MattHoneycutt/5499729 to your computer and use it in GitHub Desktop.
A Razor view engine that supports several methods of organizing views and controllers within the same folder.
public class ModularConventionViewEngine : RazorViewEngine
{
//This needs to be initialized to the root namespace of your MVC project.
//Usually, the namespace of your Global.asax's codebehind will do the trick.
private static readonly string RootNamespace = typeof(MvcApplication).Namespace;
private static IEnumerable<string> GetPath(ControllerContext controllerContext, string viewName)
{
var paths = new List<string>();
//TODO: Cache?
var controllerType = controllerContext.Controller.GetType();
var controllerName = controllerType.Name.Replace("Controller", string.Empty);
var featureFolder = "~" + controllerType.Namespace.Replace(RootNamespace, string.Empty).Replace(".", "/");
//View in the same folder as controller (controller-folder/view.cshtml)
paths.Add(string.Format("{0}/{1}.cshtml", featureFolder, viewName));
//View in a view-folder within controller-folder (controller-folder/views/view.cshtml)
paths.Add(string.Format("{0}/Views/{1}.cshtml", featureFolder, viewName));
//View in folder with controller name within a view-folder within a controller-folder (controller-folder/views/controller-name/view.cshtml)
paths.Add(string.Format("{0}/Views/{1}/{2}.cshtml", featureFolder, controllerName, viewName));
return paths;
}
public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
{
var paths = GetPath(controllerContext, viewName);
var path = paths.FirstOrDefault(p => VirtualPathProvider.FileExists(p));
if (path != null)
{
return new ViewEngineResult(CreateView(controllerContext, path, null), this);
}
//Check the usual suspects
return base.FindView(controllerContext, viewName, masterName, useCache);
}
public override ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache)
{
var paths = GetPath(controllerContext, partialViewName);
var path = paths.FirstOrDefault(p => VirtualPathProvider.FileExists(p));
if (path != null)
{
return new ViewEngineResult(CreateView(controllerContext, path, null), this);
}
//check the usual suspects
return base.FindPartialView(controllerContext, partialViewName, useCache);
}
}
@namtih58
Copy link

namtih58 commented May 2, 2013

Attempt at caching the valid view locations

public class ModularConventionViewEngine : RazorViewEngine
    {
        //This needs to be initialized to the root namespace of your MVC project.
        //Usually, the namespace of your Global.asax's codebehind will do the trick.
        private static readonly string RootNamespace = typeof(MvcApplication).Namespace;
        private static readonly ConcurrentDictionary<string, string> ViewPathCache = new ConcurrentDictionary<string, string>();

        public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
        {
            var path = GetPath(controllerContext, viewName);

            if (path != null)
            {
                return new ViewEngineResult(CreateView(controllerContext, path, null), this);
            }
            //Check the usual suspects
            return base.FindView(controllerContext, viewName, masterName, useCache);
        }

        public override ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache)
        {
            var path = GetPath(controllerContext, partialViewName);

            if (path != null)
            {
                return new ViewEngineResult(CreateView(controllerContext, path, null), this);
            }
            //check the usual suspects
            return base.FindPartialView(controllerContext, partialViewName, useCache);
        }

        private string GetPath(ControllerContext controllerContext, string viewName)
        {
            string path = null;

            var props = GetControllerProperties(controllerContext, viewName);

            //check if in cache
            var found = ViewPathCache.TryGetValue(props.cachekey, out path);
            if (!found && props.controllerNameSpace != null)
            {
                IEnumerable<string> paths = GetPaths(props.controllerNameSpace, props.controllerName, viewName);
                path = paths.FirstOrDefault(p => VirtualPathProvider.FileExists(p));

                //add to cache
                if (path != null)
                {
                    ViewPathCache.TryAdd(props.cachekey, path);
                }
            }
            return path;
        }

        private static IEnumerable<string> GetPaths(string controllerNamespace, string controllerName, string viewName)
        {
            var paths = new List<string>();

            //var controllerNamespace = controllerType.Namespace.Replace(RootNamespace, string.Empty).Replace(".", "/");
            var controllerFolder = string.Format("~{0}", controllerNamespace);

            //View in the same folder as controller (controller-folder/view.cshtml)
            paths.Add(string.Format("{0}/{1}.cshtml", controllerFolder, viewName));

            //View in a view-folder within controller-folder (controller-folder/views/view.cshtml)
            paths.Add(string.Format("{0}/Views/{1}.cshtml", controllerFolder, viewName));

            //View in folder with controller name within a view-folder within a controller-folder (controller-folder/views/controller-name/view.cshtml)
            paths.Add(string.Format("{0}/Views/{1}/{2}.cshtml", controllerFolder, controllerName, viewName));

            return paths;
        }

        private static dynamic GetControllerProperties(ControllerContext controllerContext, string viewName)
        {
            string controllerNameSpace = null;
            var controllerType = controllerContext.Controller.GetType();
            var controllerName = controllerType.Name.Replace("Controller", string.Empty);
            if (controllerType.Namespace != null)
            {
                controllerNameSpace = controllerType.Namespace.Replace(RootNamespace, string.Empty).Replace(".", "/");
            }
            //NOTE: Is this a good key????
            var cachekey = controllerName + "-" + viewName;

            return new {controllerNameSpace, controllerName, cachekey };
        }
    }

@namtih58
Copy link

namtih58 commented May 3, 2013

private string GetPath(ControllerContext controllerContext, string viewName)
        {
            string path;

            var ctrlProps = GetControllerProperties(controllerContext, viewName);

            //check if in cache
            var found = ViewPathCache.TryGetValue(ctrlProps.cachekey, out path);
            if (!found && ctrlProps.controllerNameSpace != null)
            {
                IEnumerable<string> paths = GetPossiblePaths(ctrlProps.controllerNameSpace, ctrlProps.controllerName, viewName);
                path = paths.FirstOrDefault(p => VirtualPathProvider.FileExists(p));

                //add to cache or indicate to delegate processing by adding a null for path
                ViewPathCache.TryAdd(ctrlProps.cachekey, path);
            }
            return path;
        }

@namtih58
Copy link

Fix to render a partial as a partial not as a view

public override ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache)
{
    var path = GetPath(controllerContext, partialViewName);

    if (path != null)
    {
        return new ViewEngineResult(CreatePartialView(controllerContext, path), this);
    }
    //check the usual suspects
    return base.FindPartialView(controllerContext, partialViewName, useCache);
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment