View locations for blocks

Vote:
 

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

#148467
May 13, 2016 12:40
Vote:
 

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);
        }
    }
#148469
May 13, 2016 13:11
Vote:
 

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());
#148489
May 14, 2016 22:48
* You are NOT allowed to include any hyperlinks in the post because your account hasn't associated to your company. User profile should be updated.