AI OnAI Off
You can control how EPiServer tries to resolve paths to blocks etc by the IViewTemplateRegistrator like:
[ServiceConfiguration(typeof(IViewTemplateModelRegistrator))]
public class TemplateCoordinator : IViewTemplateModelRegistrator
{
public const string BlockFolder = "~/Views/Shared/Blocks/";
public const string PagePartialsFolder = "~/Views/Shared/PagePartials/";
public static void OnTemplateResolved(object sender, TemplateResolverEventArgs args)
{
//Disable DefaultPageController for page types that shouldn't have any renderer as pages
if (args.ItemToRender is IContainerPage && args.SelectedTemplate != null && args.SelectedTemplate.TemplateType == typeof(DefaultPageController))
{
args.SelectedTemplate = null;
}
}
/// <summary>
/// Registers renderers/templates which are not automatically discovered,
/// i.e. partial views whose names does not match a content type's name.
/// </summary>
/// <remarks>
/// Using only partial views instead of controllers for blocks and page partials
/// has performance benefits as they will only require calls to RenderPartial instead of
/// RenderAction for controllers.
/// Registering partial views as templates this way also enables specifying tags and
/// that a template supports all types inheriting from the content type/model type.
/// </remarks>
public void Register(TemplateModelCollection viewTemplateModelRegistrator)
{
viewTemplateModelRegistrator.Add(typeof(JumbotronBlock), new TemplateModel
{
Tags = new[] { Global.ContentAreaTags.FullWidth },
AvailableWithoutTag = false,
Path = BlockPath("JumbotronBlockWide.cshtml")
});
viewTemplateModelRegistrator.Add(typeof(TeaserBlock), new TemplateModel
{
Name = "TeaserBlockWide",
Tags = new[] { Global.ContentAreaTags.TwoThirdsWidth, Global.ContentAreaTags.FullWidth },
AvailableWithoutTag = false,
Path = BlockPath("TeaserBlockWide.cshtml")
});
var partialControllers = typeof(SitePageData).GetCustomAttributes(true);
var types = GetMvcPartialControllerTypes();
foreach (var t in types)
{
var pageType = t.BaseType.GenericTypeArguments.FirstOrDefault();
viewTemplateModelRegistrator.Add(pageType, new TemplateModel
{
Name = pageType.Name,
TemplateType =t,
TemplateTypeCategory = EPiServer.Framework.Web.TemplateTypeCategories.MvcPartialController,
Path = $"/Views/{pageType.Name}/Index.cshtml"
});
}
viewTemplateModelRegistrator.Add(typeof(IContentData), new TemplateModel
{
Name = "NoRendererMessage",
Inherit = true,
Tags = new[] { Global.ContentAreaTags.NoRenderer },
AvailableWithoutTag = false,
Path = BlockPath("NoRenderer.cshtml")
});
}
private static IEnumerable<Type> GetMvcPartialControllerTypes()
{
return System.Reflection.Assembly
.GetExecutingAssembly()
.GetTypes()
.Where(t => t.GetCustomAttributes(typeof(MvcPartialControllerAttribute), true).Any() && IsSubclassOfRawGeneric(typeof(PageControllerBase<>), t));
}
// TODO this can be moved to extension class
static bool IsSubclassOfRawGeneric(Type generic, Type toCheck)
{
if (generic.Equals(toCheck))
return false;
while (toCheck != null && toCheck != typeof(object))
{
var cur = toCheck.IsGenericType ? toCheck.GetGenericTypeDefinition() : toCheck;
if (cur == toCheck)
{
return true;
}
//generic = generic.BaseType;
toCheck = toCheck.BaseType;
}
return false;
}
private static string BlockPath(string fileName)
{
return string.Format("{0}{1}", BlockFolder, fileName);
}
private static string PagePartialPath(string fileName)
{
return string.Format("{0}{1}", PagePartialsFolder, fileName);
}
}
It really depends on how you want to structure your solution. If you are using tags - you can follow Daniel's answer - to register where is template for particular content within particular tag. When you are just thinking about strucutring templates into folder structure, then just make sure that there is a view engine in ViewEnginesCollection that is capable of finding partial views in your newly created folders. EPIServer will use all possible view engines registered to find template (by conventions - template name is the same as content type name -> TestBlock.cs => TestBlock.cshtml).
public class MyViewEngine : RazorViewEngine
{
public MyViewEngine()
{
PartialViewLocationFormats = PartialViewLocationFormats.Union(new[]
{
"~/Views/Shared/Blocks/{0}.cshtml",
"~/Views/Shared/Partials/{0}.cshtml",
"~/Views/Shared/PagePartials/{0}.cshtml",
}).ToArray();
}
}
and then (in Global.asax for instance):
protected void Application_Start()
{
...
ViewEngines.Engines.Insert(0, new MyViewEngine());
I have created a block type testblock.cs in the Models/Blocks folder. I could able to render the view only when I create testblock.cshtml in the Views/Shared folder.
1) Could it be possible to have the view in the path Views/Blocks or Views/Blocks/TestBlock?I would need to create many blocks and I do not want everything in the shared folder.
2) The error I am getting is 'TestBlock cannot be displayed', what config settings do I need to set to see the actual error rather than a generic message