Umbraco v13 content node as a class for use in all views and partials?
Hi,
We have a Global > Settings content tree/ node that holds a variety of header, footer, content settings that we need to be able to call in a variety of places, consistently and easily. I've tried to work this out but I can only find info on how to do this in older .NET versions of Umbraco.
Any help is much appreciated! Also apologies if this has a readily available solution!
If I understand correctly you would like to use the content on the "Settings" page doctype in different views or partials.
This is a scenario that occurs with many Umbraco websites and is easily solved by using the Umbraco Context and the generated model for the "Settings" doctype.
By using so-called .NET extensions this is made even easier and you could apply this to every view or partial.
It is possible to inject the so-called IUmbracoContextAccessor into each view. This is done by means of the following piece of code at the top of the view.
It is now possible to build an extension on the IUmbracoContextAccessor. The name of this extension is TryGetCurrentSettings() and returns the generated model of the "Settings" doctype which is probably called: Settings.cs. The implementation of this extension looks like this:
public static class UmbracoContextAccessorExtensions
{
public static Settings? TryGetCurrentSettings(this IUmbracoContextAccessor umbracoContextAccessor)
{
using (var contextReference = umbracoContextAccessor.GetRequiredUmbracoContext())
{
//Trying to retrieve the homepage (root node)
var homepage = contextReference?.Content?.GetAtRoot()?.OfType<Homepage>()?.FirstOrDefault();
if (homepage == null)
return null; //Homepage has not been found.
//Trying to retrieve the settings page
Settings? settings = homepage.FirstChild<Settings>();
return settings;
}
}
}
The above solution will only work if the "Settings" doctype is located somewhere under the "Homepage" in the content tree. If the doctype "Global" is a root node itself it wont work. Please contact me if this is the case.
It is now possible to read the Settings in a view or partial by applying the following logic.
@inject IUmbracoContextAccessor _umbracoContextAccessor;
@{
Settings? currentSettings = _umbracoContextAccessor.TryGetCurrentSettings();
if (currentSettings == null)
return; //Will not render
}
SiteSettings is the DocType, and that part between the "" is the id from the info tab on that content node. I reference that at the top of my TemplateMaster.cshtml and can use the siteSettings.AttributeName to access the various attribute values.
This works but it seems like I'd need to re-declare it on every view which might be a bit of an inefficiency with how many views and partials we have. Very interesting to learn though!
The solution that BH gave would indeed work. However, there are a number of reasons why I do not use this method.
A hardcoded GUID string is used. If the pages are not staged between environments (by using uSync complete) but are created manually, the GUID of the pages would differ on each environment. This can be solved by using so-called "environment variables" but I do not see the advantage of this. Personally, I find it easier to directly look up the first node of the type "Settings" instead of directly referring to an ID.
An explicit cast is done by using (SiteSettings). This is of course possible, but by using generics (
The solution is not easy to maintain because the logic is not managed in 1 place. If you add this piece of code to every view and change the ID of the SiteSettings page (the GUID) then you will also have to adjust this in every view. It is easier to write the retrieval of the site settings in a function.
The solution cannot be reused in any future solution within for example Services because the object Umbraco is not available outside views but the IUmbracoContextAccessor is (even available in the Core package of Umbraco).
I can imagine that the above may come across as "dramatic" and that this is not necessary for a relatively simple setup of an Umbraco site. If this is the case, I would definitely use BH's solution, but with one caveat: store the hardcoded GUID string in a static variable somewhere in the code so that it is easier to manage if it changes.
If you are interested in the other version of my solution, please let me know!
Could you possibly explain your way to me? It would be good to have another angle to try and see what will work best for this site as it's quite beefy!
Actually, I was wondering, why in Umbraco (and it might just be an MVC thing) if I declare something in the master template does it not feed through to partials and child views?
If I was to do this using PHP and includes then I could declare everything just once and it feeds down through the file in a way I haven't seen in C# ASP.Net
Passing data from a layout to a page got me thinking. It is possible to use the so-called ViewBag within MVC/Razor. This object is always available within every partial or view, as far as I know. The type of this object is dynamic which means that you can literally add anything to the ViewBag.
It is also possible to fill the ViewBag with the Settings and call it within each partial or view and request the data from it. By using this solution, no additional code is required in a view or a partial and you can ALWAYS view the data directly.
Step 1: create the IUmbracoContextAccessor extension for retrieving the Settings doctype.
Based on the previous answer stating that the doctype Global was its own root node and the doctype Settings is located below, the implementation to retrieve the Settings via an extension is as follows:
public static class UmbracoContextAccessorExtensions
{
public static Settings? TryGetCurrentSettings(this IUmbracoContextAccessor umbracoContextAccessor)
{
using (var contextReference = umbracoContextAccessor.GetRequiredUmbracoContext())
{
//Trying to retrieve the global node (root node)
var globalRootNode = contextReference?.Content?.GetAtRoot()?.OfType<Global>()?.FirstOrDefault();
if (globalRootNode == null)
return null; //Global root node has not been found.
//Trying to retrieve the settings page
Settings? settings = globalRootNode.FirstChild<Settings>();
return settings;
}
}
}
Please note that in the above implementation I assume that the doctype of the Global page has resulted in the generated model called: Global.cs and that the doctype of the Settings page has resulted in the generated model called: Settings.cs. If the generated models are called differently or are not available then adjust this in the code.
Step 2: implement an ActionFilterAttribute to 'feed' the ViewBag.
Now that it is possible to retrieve the settings using the IUmbracoContextAccessor we need to put these settings in the ViewBag so that it is available for every view or partial. See implementation below:
public sealed class ViewBagActionFilterAttribute : ActionFilterAttribute
{
public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
await base.OnActionExecutionAsync(context, next);
//Checking if the call on this filter its origin is an controller
if (context.Controller is Controller controller)
{
//Retrieving the IUmbracoContextAccessor from the dependency container
IUmbracoContextAccessor? umbracoContextAccessor = context.HttpContext.RequestServices.GetService<IUmbracoContextAccessor>();
if (umbracoContextAccessor == null)
return;
//Trying to retrieve the Settings
Settings? settings = umbracoContextAccessor.TryGetCurrentSettings();
if (settings == null)
return;
//Feeding the settings into the ViewBag
controller.ViewBag.Settings = settings;
//Note: you can add more data to the ViewBag in the future here \/
}
}
}
Step 3: add ActionFilterAttribute to Umbraco builder.
Because the application needs to know that it needs to execute this ActionFilterAttribute when requesting a view or partial, it is necessary to indicate this to the Umbraco builder in the Program.cs of your web project. See below:
Because retrieving the Settings doctype is written in an extension method, retrieving it within a service or controller is a separate class is super easy. The thing you have to keep in mind is that the new class (perhaps via an interface) is added to the solution via dependency injection.
Umbraco v13 content node as a class for use in all views and partials?
Hi,
We have a Global > Settings content tree/ node that holds a variety of header, footer, content settings that we need to be able to call in a variety of places, consistently and easily. I've tried to work this out but I can only find info on how to do this in older .NET versions of Umbraco.
Any help is much appreciated! Also apologies if this has a readily available solution!
I'm still quite new to umbraco and .NET
Hi Sandy,
If I understand correctly you would like to use the content on the "Settings" page doctype in different views or partials.
This is a scenario that occurs with many Umbraco websites and is easily solved by using the Umbraco Context and the generated model for the "Settings" doctype.
By using so-called .NET extensions this is made even easier and you could apply this to every view or partial.
It is possible to inject the so-called
IUmbracoContextAccessor
into each view. This is done by means of the following piece of code at the top of the view.It is now possible to build an extension on the
IUmbracoContextAccessor
. The name of this extension isTryGetCurrentSettings()
and returns the generated model of the "Settings" doctype which is probably called:Settings.cs
. The implementation of this extension looks like this:The above solution will only work if the "Settings" doctype is located somewhere under the "Homepage" in the content tree. If the doctype "Global" is a root node itself it wont work. Please contact me if this is the case.
It is now possible to read the Settings in a view or partial by applying the following logic.
Let me know if this helps!
Greetings, Joppe
Hi Joppe,
Thanks for laying this all out, unfortunately the Global is it's own root node with Settings as a child.
I'm not 100% on the terminology so apologies for the extra problem solving!
Here's how I access mine a lil different than Joppe...
SiteSettings is the DocType, and that part between the "" is the id from the info tab on that content node. I reference that at the top of my TemplateMaster.cshtml and can use the
siteSettings.AttributeName
to access the various attribute values.Hope that helps.
Hi BH,
This works but it seems like I'd need to re-declare it on every view which might be a bit of an inefficiency with how many views and partials we have. Very interesting to learn though!
Thanks, Sandy
The solution that BH gave would indeed work. However, there are a number of reasons why I do not use this method.
A hardcoded GUID string is used. If the pages are not staged between environments (by using uSync complete) but are created manually, the GUID of the pages would differ on each environment. This can be solved by using so-called "environment variables" but I do not see the advantage of this. Personally, I find it easier to directly look up the first node of the type "Settings" instead of directly referring to an ID.
An explicit cast is done by using (SiteSettings). This is of course possible, but by using generics (
The solution is not easy to maintain because the logic is not managed in 1 place. If you add this piece of code to every view and change the ID of the SiteSettings page (the GUID) then you will also have to adjust this in every view. It is easier to write the retrieval of the site settings in a function.
The solution cannot be reused in any future solution within for example Services because the object
Umbraco
is not available outside views but theIUmbracoContextAccessor
is (even available in the Core package of Umbraco).I can imagine that the above may come across as "dramatic" and that this is not necessary for a relatively simple setup of an Umbraco site. If this is the case, I would definitely use BH's solution, but with one caveat: store the hardcoded GUID string in a static variable somewhere in the code so that it is easier to manage if it changes.
If you are interested in the other version of my solution, please let me know!
Greetings, Joppe
Hi Joppe,
Could you possibly explain your way to me? It would be good to have another angle to try and see what will work best for this site as it's quite beefy!
Actually, I was wondering, why in Umbraco (and it might just be an MVC thing) if I declare something in the master template does it not feed through to partials and child views?
If I was to do this using PHP and includes then I could declare everything just once and it feeds down through the file in a way I haven't seen in C# ASP.Net
Thanks, Sandy
Hi Sandy,
Passing data from a layout to a page got me thinking. It is possible to use the so-called
ViewBag
within MVC/Razor. This object is always available within every partial or view, as far as I know. The type of this object isdynamic
which means that you can literally add anything to theViewBag
.It is also possible to fill the
ViewBag
with theSettings
and call it within each partial or view and request the data from it. By using this solution, no additional code is required in a view or a partial and you can ALWAYS view the data directly.Step 1: create the
IUmbracoContextAccessor
extension for retrieving the Settings doctype.Based on the previous answer stating that the doctype
Global
was its own root node and the doctypeSettings
is located below, the implementation to retrieve theSettings
via an extension is as follows:Please note that in the above implementation I assume that the doctype of the Global page has resulted in the generated model called:
Global.cs
and that the doctype of the Settings page has resulted in the generated model called:Settings.cs
. If the generated models are called differently or are not available then adjust this in the code.Step 2: implement an ActionFilterAttribute to 'feed' the ViewBag.
Now that it is possible to retrieve the settings using the IUmbracoContextAccessor we need to put these settings in the
ViewBag
so that it is available for every view or partial. See implementation below:Step 3: add ActionFilterAttribute to Umbraco builder.
Because the application needs to know that it needs to execute this ActionFilterAttribute when requesting a view or partial, it is necessary to indicate this to the Umbraco builder in the
Program.cs
of your web project. See below:Step 4: enjoy easy Settings retrieval in .cshtml
It is now super easy to read out the settings in each view or partial. See:
Thats it!
It is now not nessesary to add additional code to each view or partial.
Please let me know what you think!
Greetings, Joppe
Hi Joppe!
That does seem to work thanks! Very interesting to know about viewbag, can't say I've come across it before.
Would it be a similar theory if settings was needed in a class or would that then need a service/controller?
Thanks, Sandy
Hi Sandy!
Because retrieving the
Settings
doctype is written in an extension method, retrieving it within a service or controller is a separate class is super easy. The thing you have to keep in mind is that the new class (perhaps via an interface) is added to the solution via dependency injection.If you have no idea how dependency injection works within .NET applications, Microsoft has written a guide for this: https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection.
The following code is an example of how you can retrieve the settings in an service or an controller:
And one more thing: would you mind marking my answer as the solution so that the other community members also know. Thanks!
Cheers, Joppe
Thanks so much!
is working on a reply...