Statiq.Framework icon indicating copy to clipboard operation
Statiq.Framework copied to clipboard

Display templates based on Controler alternative vor Pipeline

Open Simply007 opened this issue 3 years ago • 6 comments

In the .Net MVC, it is possible to use Controller specific display templates, for specific models by then using:

@Html.DisplayFor(m => m.Property)

I haven't found the exact MS docs, but i suppose it works similarly as Partial view discovery:

  • https://docs.microsoft.com/en-us/aspnet/core/mvc/views/partial?view=aspnetcore-5.0#partial-view-discovery

It might be good to have some similar concept to render the view template based on the Pipeline (or maybe there is some and I don't know about it).

But there are the use cases when combining the pipelines, but this is already solved for COntrollers, sinceou it is possible to render to call other actions from the controller.

It might be easier to use the concept of Structure rich text rendering for integration with Kentico Kontent.

Simply007 avatar Nov 11 '20 15:11 Simply007

Not 100% sure where MVC is getting the display/editor template lookup paths from but I'm guessing it's from RouteData or ActionDescriptor.

In that case, we should initialize RouteData better than with just an empty ctor or be more specific and use ControllerActionDescriptor and ControllerContext instead of ActionDescriptor and ActionContext.

https://github.com/statiqdev/Statiq.Framework/blob/6c833bb2087cf1215cbba4b87dd53d8c79636e9e/src/extensions/Statiq.Razor/RazorCompiler.cs#L148

So we could follow Microsoft's convention-based discovery and e.g. for a pipeline ArticlesPipeline let MVC search for a more specific template in a folder called Articles...

petrsvihlik avatar Nov 11 '20 16:11 petrsvihlik

The overriding of templates is described well here: https://bradwilson.typepad.com/blog/2009/10/aspnet-mvc-2-templates-part-1-introduction.html

petrsvihlik avatar Nov 11 '20 16:11 petrsvihlik

Not 100% sure where MVC is getting the display/editor template lookup paths from...The overriding of templates is described well here

Yep, they're convention based partials using DisplayTemplates and EditorTemplates folders and the name of the model. As far as I know, display and editor template resolution should be working (since it's basically the same mechanism and view location finding logic that does partials and layouts). The challenge to using them in Statiq is that the @Html.DisplayFor() method uses an expression tree to grab the model property for the template and that doesn't mix too well with the default IDocument model (you usually end up with errors like "Templates can be used only with field access, property access, single-dimension array index, or single-parameter custom indexer expressions."). With custom models like what's being used here it would likely work better.

Which brings us to the issue - I'm not entirely sure I'm following the feature request here. Is the idea to add something like a Views/Shared/DisplayTemplates/MyPipeline.cshtml display template and then render it with a call like @Html.DisplayForPipeline()? If so, I'm not sure what the template model would be? Would these kinds of templates always have the current IDocument as their model (and the pipeline comes in only to choose which template to use)? I.e., given the same document in two different pipelines "A" and "B", calling @Html.DisplayForPipeline() would render the template at .../DisplayTemplates/A.cshtml if the RenderRazor module is in the first pipeline and .../DisplayTemplates/B.cshtml if it's in the second?

I wonder if what you're really going after is a way to switch some kind of template based on document (not pipeline). I.e., if a document has some sort of property (for example, a particular "TemplateName" metadata or something) then we could call @Html.DisplayForDocument() (or even an overload that takes the name of the metadata that has the template name: @Html.DisplayForDocument("TemplateName")). That would probably be more flexible than depending on the pipeline. Am I along the right train of thought here?

daveaglick avatar Nov 14 '20 01:11 daveaglick

Let's say I have two display templates that both accept DateTime as a model.

  • Views/Shared/DisplayTemplates/DateTime.cshtml (renders e.g. 2020-11-16 for all controllers with the exception below)
  • Views/Article/DisplayTemplates/DateTime.cshtml (this overrides the logic for the Article controller and redeners e.g. Nov 16, 2020)

In MVC, this works out of the box because the controller context is applied when resolving the display template.

In Statiq, only the first (Shared) works fine. But how can we emulate overriding display templates with controllers in Statiq? Can we somehow inject the pipeline name instead of the controller name to the template resolution logic?

In other words, can we do this?

  • Views/<pipeline>/DisplayTemplates/DateTime.cshtml

petrsvihlik avatar Nov 16 '20 10:11 petrsvihlik

AHHH, got it! Your earlier comment about the controller context makes sense now. In essence the pipeline should really be considered the "controller" for the purpose of the Razor engine (at least as far as resolving controller-convention-based paths like templates).

I wonder if there's a concept that let's us use the pipeline name by default but can still be customized. I'm thinking about how there are lots of different ways to think of pipelines. In some generators like what you're building the pipelines are defined to cleanly represent specific "types" of documents (article, blog post, etc.). In others like Statiq Web, the pipelines are defined more along file distinctions (content, data, etc.) than what they represent. In these setups it would be useful to override or define a custom controller context per-document. I could even see wanting to set that in front matter for specific documents from time to time.

Here's what I'm thinking: we add an optional metadata value (ControllerContext or something). If it's defined for a document we use that, if not we use the name of the current pipeline. Now I just need to figure out how to set that and get Razor to use it when doing path finding (though I think you basically solved that in your comment). I'll give it a shot and see if I can get it working.

daveaglick avatar Nov 16 '20 15:11 daveaglick

Sounds good to me!

petrsvihlik avatar Nov 17 '20 09:11 petrsvihlik