How to make blocks without controllers work in feature folders

Vote:
 

I am trying to use feature folders for my first EPiServer site build and running into an issue with blocks. I can get blocks to work fine that have controllers, but for some reason using blocks without controllers does not work. I get the message that the block cannot be displayed when rendered on a page. Add the controller and it works just fine, even without specifying a path to the view so I know the view resolution logic is working. There is no logic in the controller, just a call to PartialView(model) which I want to avoid for the sake of performance. I followed some articles online on how to create a view engine to add the additional paths for feature folders, shown below:

public SiteViewEngine()
{
   var additionalLocations = new[]
      {
         "~/Features/{0}.cshtml",
         "~/Features/{1}/{0}.cshtml",
         "~/Features/{1}/{1}.cshtml",
         "~/Features/{1}/Views/{0}.cshtml",
         "~/Features/{1}/Views/{1}.cshtml",
         "~/Features/Shared/{0}.cshtml",
         "~/Features/Shared/Views/{0}.cshtml"
      }
      .Union(FeatureFolders())
      .ToArray();

   ViewLocationFormats = ViewLocationFormats.Union(additionalLocations).ToArray();
   MasterLocationFormats = MasterLocationFormats.Union(additionalLocations).ToArray();
   PartialViewLocationFormats = PartialViewLocationFormats.Union(additionalLocations).ToArray();
}
#203482
Apr 24, 2019 21:13
Vote:
 

as it might sound quite easy task for episerver to carry out - it's actually not. episerver in this case (when you are using feature folders) have no idea in which folder view should be located. inside view path conventions `{1}` placeholder is controller name. as we do not have controller in your case - this convetion breaks basically.

if you want to dig deeper what's actually happening under the hood when episerver is asked to resolve template for the content this part of the series might be interesting for you - https://blog.tech-fellow.net/2015/05/30/content-area-under-the-hood-part-2/

also to understand who is going to "render" your content template - you could install DeveloperTools (https://nuget.episerver.com/package/?id=EPiServer.DeveloperTools) and check under content templates (Developer > Templates) section. there will be a list of all found content types and their respective renderers.

have you also considered to just install nuget package (http://marisks.net/2017/12/17/better-feature-folders/) for this task?

#203486
Apr 25, 2019 8:26
Vote:
 

Hi Jason

If you are making controller-less blocks, have a look at implementing the IViewTemplateModelRegistrator interface (documentation here). Here is a sample:

public void Register(TemplateModelCollection viewTemplateModelRegistrator)
{
    viewTemplateModelRegistrator.Add(
        typeof(StandardBlock),
        new TemplateModel
        {
            Name = "StandardBlock",
            AvailableWithoutTag = true,
            Path = "~/Features/GenericContent/Views/StandardBlock.cshtml"
        });
}

I usually create a class for this in each feature folder (where needed). You will still need to specify relative paths to the view files, though.

#203496
Apr 25, 2019 10:45
Vote:
 

I was hoping I was just doing something incorrectly in my implementation. Seems I am out of luck. So my choices are use controllers or register each view manually. Sucks it requires more code, but I have a feeling the controller route may be a better option for me since it feels easier to maintain, and many blocks will have logic necessitating a controller anyway. If performance becomes an issue, I may switch to the template registration method. Thanks for your help guys.

#203506
Apr 25, 2019 15:32
Vote:
 

measure before switching :)

#203507
Apr 25, 2019 15:33
Vote:
 

UPDATE:

You should be able to create just one controller for all your controller-less blocks. Create a generic block controller and implement the Index method (if you need to, in order to resolve your view) and make sure to use the [TemplateDescriptor] attribute. Make sure to set Inherit to true:

[TemplateDescriptor(Inherited = true)]
public class GenericBlockController : BlockController<BlockData>
{
    public override ActionResult Index(BlockData currentContent)
    {
        return PartialView(currentContent.GetOriginalType().Name, currentContent);
    }
}

My idea is that this controller will handle any block that doesn't have it's own dedicated controller. One controller for all your logic-less blocks is better than one controller per block :)

ORIGINAL POST:

Is this really true though? What happens if you still implement a controller, but don't override the Index method?

Looking at PartialContentController<TContentData> (implemented by BlockController<TBlockData>), you can see that the Index method contains the following code:

public virtual ActionResult Index(TContentData currentContent)
{
  if (typeof (TContentData) == typeof (IContentData))
    throw new InvalidOperationException(string.Format((IFormatProvider) CultureInfo.InvariantCulture, "The controller '{0}' tried to call the partial view '{1}', which would cause an infinite loop. Make sure it overrides the 'Index' action from 'PartialContentController'.", (object) this.GetType().Name, (object) typeof (TContentData).Name));
  return (ActionResult) this.PartialView(typeof (TContentData).Name, (object) currentContent);
}

I would assume that your controller-less blocks and views (wherever they may be placed), should be resolved using that PartialView(typeof(TContentData).Name, currentContent) statement, which in turn very likely uses your custom view engine.

#203611
Edited, Apr 29, 2019 16:50
* 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.