U4-2776 - Default Content For A Razor Layout Section

Created by Sebastiaan Janssen 03 Sep 2013, 07:36:06 Updated by Sebastiaan Janssen 17 Jan 2014, 15:32:08

Relates to: U4-3049

Request from Douglas Robar:

This would be a nice addition to Umbraco: http://haacked.com/archive/2011/03/05/defining-default-content-for-a-razor-layout-section.aspx


Sebastiaan Janssen 03 Sep 2013, 08:23:01

Additional requests: Ability to enter strings and @helper results as defaultContents:

@inherits UmbracoTemplatePage

@RenderSection("Footer", "String: Default Content")
@RenderSection("Footer", DefaultContent())
@RenderSection("Footer", @Inline Html: Default content)

@helper DefaultContent() { Helper: Default Content }


Will display: String: Default Content Helper: Default Content Inline Html: Default content

If you redefine the footer section in your child template then that overrules the default content.

@section Footer { Overrule default }

Will display: Overrule default

This is instead of one of the three default strings from the first code block.

This is in addition to just making the RenderSection required or not, so these will still work:

@RenderSection("Footer") @RenderSection("Footer", false) @RenderSection("Footer", true)

In the first two cases you HAVE to implement the Footer section in your child templates.

@section Footer { Footer text }

In the last case, implementing the footer is not required and will not render at all.

Sebastiaan Janssen 03 Sep 2013, 08:31:58

Implemented in rev 25d2b56ed7c75599d6bd370353460ae9cec674f2

Dan Booth 05 Sep 2013, 12:21:31

It would be nice to use a Partial, too, which could be reused.

Sebastiaan Janssen 05 Sep 2013, 15:00:38

Will have a look at that Dan, thanks! :)

Sebastiaan Janssen 05 Sep 2013, 15:01:34

@Dan what would you expect the notation to be then? @RenderSection("Footer", ...?)

Dan Booth 05 Sep 2013, 20:14:29

Mmm, not sure, Seb! That's for you clever guys to work out :) I guess you can't have the partial name as a string else the signature will be the same. Could you do something like:

@RenderSection("Footer", new { partial = "mypartial", model = new MyModel() })

Douglas Robar 05 Sep 2013, 20:57:16

Yeah, I thought of that, too, re-using the syntax from macro parameters. But that's totally geeky and beyond people without VS or until there is a UI for it.

In the "convention over configuration" department... @RenderSection("Footer", MyPartialView) is not ambiguous because it would require parenthesis after it to know it is an @Helper, and a string without quotes or a leading @ sign can then only be the name/alias of a partial view. The only potential difficulty is if the partial view's name/alias has spaces in it. If that's possible then this is a non-starter.

In the same department, if the above isn't workable, would it be acceptable to say that @RenderSection("Footer", "My Partial View") would look first for a partial view of the string's name/alias but if not found treats it as a literal string to be output?

Dan Booth 05 Sep 2013, 21:50:59

I agree Doug that syntax is geeky and I don't particular like it, either. I reckon what you say could work.

I also wonder whether if you wanted to pass an HTML string in then it would have to be an IHtmlString and thus leaving a plain string as presumed to be a partial?

Funka! 05 Sep 2013, 23:22:14

What about:

{{@RenderSection("Footer", new Partial("myPartialName", myModel))}}

Yes this looks very similar to what Dan suggested three hours ago, which I might not totally agree is "beyond anyone without VS", but is also almost identical to exactly what you do when you say {{@Html.Partial("myPartialName", myModel))}}. Except of course here, {{Partial}} would be a dummy class (not an actual helper method) that you new-up and pass the partial name and model into as constructor parameters. Then you could overload the RenderSection signature to accept a "Partial" class and you'd know what to do with it. Plus, one other tiny advantage is that you'd get strong typing on this instead of the anonymous object.

A related alternative to creating and newing-up a {{"Partial"}} class is to make some other method that returns something similar you could look for, such as:

{{@RenderSection("Footer", DefaultPartial("myPartialName", myModel))}}

Or what about adding 3rd param for the model and you could stick with strings:

{{@RenderSection("Footer", "myPartialName", myModel)}}

This would require you to pass a model which I believe is optional when you use normal Html.Partial, hence it's my least favorite of these suggestions so far.

Dan Booth 06 Sep 2013, 07:45:05

I like

@RenderSection("Footer", new Partial("myPartialName", myModel))

Sebastiaan Janssen 06 Sep 2013, 12:36:12

How about @RenderSection("Footer", "~/Views/Partials/Footer.cshtml") We can just test if the file exists, if not we render the string. This way you can also put your partials wherever you want as you would provide a full path in the @RenderSection call.

Funka! 06 Sep 2013, 19:55:34

Don't forget the optional third param to allow you to pass in a different model?

Sebastiaan Janssen 07 Sep 2013, 05:47:04

Ah yes! Thanks Funka

Douglas Robar 09 Sep 2013, 07:44:59

I like it. My only suggestion would be, for simplicity, to always look in the ~/Views/Partials/ folder for the cshtml file by default and not require the path to be specified unless the view is not in the default location. In fact, aren't there a number of places partials are looked for internally already? If so, all of those places should be looked at before assuming it's a custom path.

The logic for a string in the second parameter then would follow this general sequence:

  1. Assume the string is a filename and look for it in the ~/Views/Partials folder. If found, return the result of the cshtml file. @RenderSection("Footer", "footer.cshtml")

  2. If file is not found in step 1, assume the string contains a fully qualified path (not allowing 'dangerous' paths for security reasons) and if the cshtml file is found return the result. @RenderSection("Footer", "~/views/custom/footer.cshtml", myModel)

  3. If neither step 1 or 2 finds a file, treat it as a string and return the string. @RenderSection("Footer", "copyright 2013")

Sebastiaan Janssen 09 Sep 2013, 08:17:57

That's too much "magic" for my liking and this is how we end up with weird edge-case bugs. "Oh, you want it to be a string but you also have a view of the same name? No worries, we'll just add yet another parameter and call it 'renderStringAsStringInsteadOfView'". Also, then we'll have a default path, and people will ask: "why is that the default path, I want it to be x", which will then turn into a config option... etc (I wouldn't allow that but still you see how we end up with these things throughout Umbraco).

So.. I say if people want a file, they type the path to the file. Not so hard, is it?

Douglas Robar 09 Sep 2013, 08:29:09

I appreciate the point. I was looking for simplicity and that most people will use the default location anyway so let's cater to the 80%+ of people who will use that and not force needlessly long strings with hard-coded paths except for when people have their partials in a different place. I'll let you make the call if that is too much 'simplicity'.

As long as the (eventual) UI for inserting the @RenderSection() allows selecting the section name and the optional second param in its various forms (string, inline-html, helper or partial) and generates the correct markup for users I'm content. (I think the 3rd param needn't be in the UI as it isn't really relevant when using the UI, though it should be discoverable via intellisense in VS).

Funka! 09 Sep 2013, 17:57:03

I feel averse to the overloading of the string to mean either "content to be rendered" or "reference to a partial". Surely some other method signature can be found which eliminates all ambiguity? Dan's original suggestion to use the anonymous object is looking better and better to me now---which, if a button can be added to the toolbar to help you generate, seems no more challenging than the complexities of using {{@Umbraco.Field(...)}} with all of its optional params...

Dan Booth 09 Sep 2013, 19:07:25

Maybe just drop rendering a string altogether? I can't really think of any real-world example where you would render a string as an alternative. Much better to use a partial or a @helper method, as this is more realistic of actual usage. Then the overload would never be needed.

Funka! 09 Sep 2013, 19:34:45

Simple string rendering might be nice for something prominent like a page heading, where by default you display the current node's name or maybe the name of the section you're in, but still allow child or special pages to override this and supply their own heading... I don't think it sounds all that unusual, if not an uncommon thing to do?

Douglas Robar 14 Sep 2013, 21:24:17

@Sebastiaan - you've probably handled this already, but just to be sure about one point we hadn't specifically mentioned...

If I were to set default content with @RenderSection("sidebar", "I'm a sidebar!") and wanted to override the default I'd simply need to specify a @section sidebar . Easy.

It should also be that if I didn't want any output at all, even though using the default content specified in the @RenderSection() as before, I should be able to have @section sidebar . That would replace the default output with that contained in the section (no output in this case) because the section is empty. Whereas, omitting the @section entirely would cause the default output from the @RenderSection() to display.

Sebastiaan Janssen 06 Oct 2013, 16:00:52

The best I can do at the moment for the partials notation is:

@RenderSection("Footer", Html.Partial("MyTest")) And @RenderSection("Footer", Html.Partial("MyTest", new MyModel()))

I think that actually makes a lot of sense and we teach in the courses that you get the content of a partial by doing Html.Partial. I'm steering away from just providing it as a string which represents the path as it would all lead to a bit of dark magic. This makes it explicit and follows what you would "normally" do to get the contents of a partial somewhere. I also think this will be seldomly used by non-geeky, non-VS people. Eventually we can make a UI for this, but that's a seperate issue (U4-3049).

I've committed the latest changes in rev 130f6d3a427d72e1c8a82a5e11f3bab80a11ddb8

@Doug yes, that is exactly how it works.

Douglas Robar 16 Jan 2014, 13:28:31

@Seb - can this be added to v7 as well, please?

Sebastiaan Janssen 16 Jan 2014, 18:53:54

Yeah it's already been merged into 7.0.2.

Douglas Robar 16 Jan 2014, 23:40:56

Just tested in 7.0.2 nightly #240 and everything works except the one thing I tried first and therefore assumed none of it was implemented.

This situation errors in 7.0.2 nightly #240 (didn't try 6.2.0 nightly) if there is no @section Footer defined.

@RenderSection("Footer", "String: Default Content")

The error is: The following sections have been defined but have not been rendered for the layout page "~/Views/Master.cshtml": "Footer".

Everything is fine if you do define the @section but that kind of defeats the purpose of having a default string ;)

Otherwise, everything works perfectly.

Sebastiaan Janssen 17 Jan 2014, 15:13:41

Doug I can't reproduce this, in my Layout I have

@RenderSection("Footer", "Blabla")

And this shows Blabla in the footer.

If I then go to a template that has this layout defined and add @section Footer { Heeey! } it shows: Heeey!

Maybe you've defined @section Footer somewhere but forgot to put it in the layout file that's used by that template? This is usually what that error means.

Douglas Robar 17 Jan 2014, 15:26:07

Okay, tried it on the latest nightly and it works. Go figure. Sorry for the false alarm.

Sebastiaan Janssen 17 Jan 2014, 15:32:08

Weird! Code hasn't been touched in week :/

Priority: Normal

Type: Feature (request)

State: Fixed


Difficulty: Normal


Backwards Compatible: True

Fix Submitted:

Affected versions:

Due in version: 6.2.0, 7.0.2


Story Points: