Copied to clipboard

Flag this post as spam?

This post will be reported to the moderators as potential spam to be looked at


  • SandyK-W 21 posts 61 karma points
    Aug 29, 2024 @ 10:04
    SandyK-W
    0

    Umbraco v13 content node as a class for use in all views and partials?

    Hi,

    enter image description here

    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

  • Joppe Ruessink 18 posts 168 karma points
    Aug 29, 2024 @ 14:34
    Joppe Ruessink
    0

    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.

    @inject IUmbracoContextAccessor_umbracoContextAccessor;
    

    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
    }
    

    Let me know if this helps!

    Greetings, Joppe

  • SandyK-W 21 posts 61 karma points
    Aug 29, 2024 @ 15:10
    SandyK-W
    0

    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!

  • bh 444 posts 1544 karma points
    Aug 29, 2024 @ 18:02
    bh
    0

    Here's how I access mine a lil different than Joppe...

    SiteSettings siteSettings = (SiteSettings)Umbraco.Content("6214009f-38e1-408f-89b0-bc5dcc9254dc");
    

    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.

  • SandyK-W 21 posts 61 karma points
    Aug 30, 2024 @ 08:22
    SandyK-W
    0

    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

  • Joppe Ruessink 18 posts 168 karma points
    Aug 30, 2024 @ 08:04
    Joppe Ruessink
    0

    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!

    Greetings, Joppe

  • SandyK-W 21 posts 61 karma points
    Aug 30, 2024 @ 08:24
    SandyK-W
    0

    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

  • Joppe Ruessink 18 posts 168 karma points
    Aug 30, 2024 @ 14:27
    Joppe Ruessink
    100

    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 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:

    builder.CreateUmbracoBuilder()
        .AddBackOffice(builder => builder?.AddMvcOptions(builder => builder.Filters.Add(new ViewBagActionFilterAttribute())))
        ...
        ...
        ...
        .Build();
    


    Step 4: enjoy easy Settings retrieval in .cshtml

    It is now super easy to read out the settings in each view or partial. See:

    @{
        var settings = ViewBag.Settings as Settings;
    }
    


    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

  • SandyK-W 21 posts 61 karma points
    Aug 30, 2024 @ 15:33
    SandyK-W
    1

    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

  • Joppe Ruessink 18 posts 168 karma points
    Aug 30, 2024 @ 17:18
    Joppe Ruessink
    0

    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:

    public class MyNewService : IMyNewService
    {
        private readonly IUmbracoContextAccessor _umbracoContextAccessor;
    
        public MyNewService(IUmbracoContextAccessor umbracoContextAccessor)
        {
            _umbracoContextAccessor = umbracoContextAccessor;
        }
    
        public Settings? GetSettings()
        {
            return _umbracoContextAccessor.TryGetCurrentSettings();
        }
    }
    

    And one more thing: would you mind marking my answer as the solution so that the other community members also know. Thanks!

    Cheers, Joppe

  • SandyK-W 21 posts 61 karma points
    Sep 02, 2024 @ 08:12
    SandyK-W
    0

    Thanks so much!

Please Sign in or register to post replies

Write your reply to:

Draft