Multisite Umbraco 9 solution with different front end themes
I'm building a multisite Umbraco solution where each site shares the same document, data, and media types, but has a very different front-end theme including views, scripts and styles.
I've so far organised the views and front-end assets into folders, set the theme on a per-site basis via a property in the Umbraco back office, then used a custom render controller to look up the selected theme and load the themed root-level template view.
i.e.: If the current theme is ThemeXYZ, render the page using ~/Themes/ThemeXYZ/Views/Home.cshtml.
All subsequent assets and partials loaded are then specific to their theme:
The downside of this approach is that every theme must contain files for every view, even though some views are not unique.
I'd like to remove the need for duplicate code by falling back to a "Default" theme in the event that there is no view for the current theme.
i.e.: If the current theme is "ThemeXYZ" and ~/Themes/ThemeXYZ/Views/Home.cshtml does not exist, render the page using ~/Views/Home.cshtml instead.
I've looked into adding to the Razor view location expander, but am finding that Umbraco's view engine is created and cached before I can add new expanders to dynamically add in my theme views before the default location.
Is there any way to solve this without resorting to string manipulation of view paths?
Can this be accomplished with the simple switching of a stylesheet?
You could have the home node of the site (which ever it is on) OR have a "site configuration" node where you have your theme properties or a "theme picker" which is a dropdown list in the admin node on the node.
Then in your "master" or primary layout template, you would just look at that doctype and get the property that would be in the site property theme selector. Then in your primary layout template you would just look through the list and then if the selection is of the one type, then show the CSS sheet, etc.
Maybe have a base CSS file and then the picker can be an theme file that will either extend or override your base CSS file styles.
That is a simple way rather than going through the file system.
So
Create a new doctype (Site configuration) and add a dropdown property (theme names) and add your theme names
Allow the doctype below the homepage doctype.
Go into your primary layout template that all your other templates would inherit from and in the 'head' tag, call your doctype
/// Example Code Below,not tested but pretty sure it will work assuming you have your templates inheriting from the "primaryLayout" template
@{
@*THIS IS THE PRIMARY LAYOUT TEMPLATE ALL TEMPLATES WILL INHERIT FROM*@
Layout = null;
var siteRoot = Model.Root(); //gets the current site root not the main Umbraco root
var siteConfigNode = siteRoot.Descendants.Where(x => x.DocumentTypeAlias == "siteConfiguration"));
}
<head>
......
@{
var themePicker = siteConfigNode.Value("themePicker");
if(themePicker == "Red"){
<link rel="stylesheet" href="/css/themes/red.css" >
}else if(themePicker == "Blue"){
<link rel="stylesheet" href="/css/themes/blue.css" >
}else{
<link rel="stylesheet" href="/css/themes/default.css" >
}
}@*end code block*@
</head>
Example template structure in your Umbraco Templates folder
Does that make sense?
More or less this could be a very simple solution. If that is basically how you want the layouts to work.
Unfortunately, switching the stylesheet is not sufficient. While the document, media and data types are shared, the front end theme designs can be different enough that they require different views.
If that were the case, would the theme picker still be an option and use either partials or @Html.Partial("myHomeLayoutOne"), @Html.Partial("myHomeLayoutTwo"), be in your if/else or a case statement in some standard or base templates (Home (home partials), General Page(general page partials), Search Page (search page partials)) and then you can have your default partials as well.
Have some standard templates and have your partials determine the layout.
Have done that too. I know others have done controllers if you want to go that route. For me doing them in the views has always been faster for my development and it seems to perform pretty well in general
Content editors can already pick the theme they wish to apply to a given site via a dropdown in the Umbraco back office.
When the Home page is requested, a custom render controller looks up the selected theme and returns the appropriate template view. So if a user views the Home page on Site A, the ~/Themes/ThemeA/Views/Home.cshtml view is rendered.
No problems there.
Because the designs can be so different, it is likely that the header for each theme will need to be unique, so ~/Themes/Theme A/Views/Home.cshtml would load the theme-specific partial view ~/Themes/ThemeA/Views/Partials/_Header.cshtml.
That's how I currently have it working. Each theme has all of the views.
Which isn't ideal. A breadcrumb for example might be generic enough that the necessary view is identical in both ThemeA and ThemeB. In cases like this, I'd like to simply not have a _Breadcrumb.cshtml file in ~/Themes/ThemeA/Views/Partials/ OR ~/Themes/ThemeB/Views/Partials/ and have the system automatically fall back to using the file in ~/Views/Partials instead. The same might also apply for some document type page templates i.e. XmlSitemap.cshtml.
Doing things this way would make adding additional themes in future much easier, and result in a lot less code to maintain.
This could be achieved by using a helper method to return the correct view path by doing file system look ups, but this would be slow and inelegant.
What's frustrating is that .NET has a built in way of doing what I want with view location expanders (defining a hierarchy of views to search and fall back through), but Umbraco's own view engine is getting in the way.
Doing a quick search of "Custom Umbraco View Engine", as you probably have already, I ran across an article for a V8 solution that night get you on your way. The difference between 8 and 9 probably won't be hugely significant significant.
I will also ask around to a few of my other fellow Umbraco developers to see they have any ideas.
Multisite Umbraco 9 solution with different front end themes
I'm building a multisite Umbraco solution where each site shares the same document, data, and media types, but has a very different front-end theme including views, scripts and styles.
I've so far organised the views and front-end assets into folders, set the theme on a per-site basis via a property in the Umbraco back office, then used a custom render controller to look up the selected theme and load the themed root-level template view.
i.e.: If the current theme is ThemeXYZ, render the page using
~/Themes/ThemeXYZ/Views/Home.cshtml
.All subsequent assets and partials loaded are then specific to their theme:
The downside of this approach is that every theme must contain files for every view, even though some views are not unique.
I'd like to remove the need for duplicate code by falling back to a "Default" theme in the event that there is no view for the current theme.
i.e.: If the current theme is "ThemeXYZ" and
~/Themes/ThemeXYZ/Views/Home.cshtml
does not exist, render the page using~/Views/Home.cshtml
instead.I've looked into adding to the Razor view location expander, but am finding that Umbraco's view engine is created and cached before I can add new expanders to dynamically add in my theme views before the default location.
Is there any way to solve this without resorting to string manipulation of view paths?
Are you layouts the same?
Meaning,
Homepage
Page Layout, etc.?
Can this be accomplished with the simple switching of a stylesheet?
You could have the home node of the site (which ever it is on) OR have a "site configuration" node where you have your theme properties or a "theme picker" which is a dropdown list in the admin node on the node.
Then in your "master" or primary layout template, you would just look at that doctype and get the property that would be in the site property theme selector. Then in your primary layout template you would just look through the list and then if the selection is of the one type, then show the CSS sheet, etc.
Maybe have a base CSS file and then the picker can be an theme file that will either extend or override your base CSS file styles. That is a simple way rather than going through the file system.
So
Create a new doctype (Site configuration) and add a dropdown property (theme names) and add your theme names
Allow the doctype below the homepage doctype.
Go into your primary layout template that all your other templates would inherit from and in the 'head' tag, call your doctype
/// Example Code Below,not tested but pretty sure it will work assuming you have your templates inheriting from the "primaryLayout" template
Example template structure in your Umbraco Templates folder
Does that make sense?
More or less this could be a very simple solution. If that is basically how you want the layouts to work.
I have a similar set up on a multi site set up.
SiteHomepage doctype allowing SiteProperties
Thanks for responding Carlos.
Unfortunately, switching the stylesheet is not sufficient. While the document, media and data types are shared, the front end theme designs can be different enough that they require different views.
If that were the case, would the theme picker still be an option and use either partials or @Html.Partial("myHomeLayoutOne"), @Html.Partial("myHomeLayoutTwo"), be in your if/else or a case statement in some standard or base templates (Home (home partials), General Page(general page partials), Search Page (search page partials)) and then you can have your default partials as well.
Have some standard templates and have your partials determine the layout.
Have done that too. I know others have done controllers if you want to go that route. For me doing them in the views has always been faster for my development and it seems to perform pretty well in general
Thanks Carlos.
My solution looks something like this:
Content editors can already pick the theme they wish to apply to a given site via a dropdown in the Umbraco back office.
When the Home page is requested, a custom render controller looks up the selected theme and returns the appropriate template view. So if a user views the Home page on Site A, the
~/Themes/ThemeA/Views/Home.cshtml
view is rendered.No problems there.
Because the designs can be so different, it is likely that the header for each theme will need to be unique, so
~/Themes/Theme A/Views/Home.cshtml
would load the theme-specific partial view~/Themes/ThemeA/Views/Partials/_Header.cshtml
.That's how I currently have it working. Each theme has all of the views.
Which isn't ideal. A breadcrumb for example might be generic enough that the necessary view is identical in both ThemeA and ThemeB. In cases like this, I'd like to simply not have a
_Breadcrumb.cshtml
file in~/Themes/ThemeA/Views/Partials/
OR~/Themes/ThemeB/Views/Partials/
and have the system automatically fall back to using the file in~/Views/Partials
instead. The same might also apply for some document type page templates i.e.XmlSitemap.cshtml
.Doing things this way would make adding additional themes in future much easier, and result in a lot less code to maintain.
This could be achieved by using a helper method to return the correct view path by doing file system look ups, but this would be slow and inelegant.
i.e.:
What's frustrating is that .NET has a built in way of doing what I want with view location expanders (defining a hierarchy of views to search and fall back through), but Umbraco's own view engine is getting in the way.
Hi Stephen, I'm starting to look at this atm. have you found a way of doing this?
Ahhh, I now see what you are saying.
Doing a quick search of "Custom Umbraco View Engine", as you probably have already, I ran across an article for a V8 solution that night get you on your way. The difference between 8 and 9 probably won't be hugely significant significant.
I will also ask around to a few of my other fellow Umbraco developers to see they have any ideas.
https://ericceric.com/2019/05/31/custom-razor-view-engine-paths-for-umbraco-8/
Most likely you will have to be tapping into the Routes engine as well if the solution above doesn't get you on your way.
https://our.umbraco.com/documentation/reference/routing/custom-routes
is working on a reply...