Copied to clipboard

Flag this post as spam?

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


  • Thomsen 108 posts 316 karma points
    Mar 13, 2019 @ 23:02
    Thomsen
    0

    Using UmbracoHelper in a custom class in v8

    Hi

    Using the UmbracoHelper in a custom class or service etc., this syntax normally works in v7, but it fails in v8.

    var umbracoHelper = new Umbraco.Web.UmbracoHelper(Umbraco.Web.UmbracoContext.Current);
    

    Is there a new way of doing this now in v8?

    Best regards.

  • Warren Harding 39 posts 90 karma points
    Mar 14, 2019 @ 02:02
    Warren Harding
    106

    Previously I would use say:

    var helper = new Umbraco.Web.UmbracoHelper(Umbraco.Web.UmbracoContext.Current);
    var node = helper.TypedContent(nodeId);
    

    Now I've updated this to:

    var helper = Umbraco.Web.Composing.Current.UmbracoHelper;
    var node = helper.Content(nodeId);
    
  • Sebastiaan Janssen 4890 posts 14567 karma points MVP admin hq
    Mar 14, 2019 @ 06:59
    Sebastiaan Janssen
    6

    But please don't use Umbraco.Web.Composing.Current- in most places you will use this kind of code (like an API controller, or a SurfaceController, etc). you can just access the UmbracoHelper already:

    var node = Umbraco.Content(1234);

    If that's not available the question is: what do you need from the UmbracoHelper exactly?

    I'm guessing the most common scenario would be to query some content, in which case you can inject IUmbracoContextFactory and get an umbracoContext from it, the safe way to do that (in case the context does not exist yet) is to Ensure it exists first:

    private readonly IUmbracoContextFactory _context;
    
    public MyComponent(IUmbracoContextFactory context)
    {
        _context = context;
    }
    
    public void Initialize()
    {
        using (var cref = _context.EnsureUmbracoContext())
        {
            var cache = cref.UmbracoContext.ContentCache;
            var node = cache.GetById(1234);
        }
    
  • Anders Brohus 189 posts 460 karma points
    Oct 21, 2019 @ 09:48
    Anders Brohus
    0

    Hi :)

    When i use this method my media item is just returning null :/

    I'm trying to get it on the Saved event, the IMedia has an ID and all :)

    I need the media item so i can get the absolute url :(

  • Thomsen 108 posts 316 karma points
    Mar 17, 2019 @ 20:52
    Thomsen
    0

    Thanks to both of you for the answers.

    What I need is to query content from a custom helper class where Umbraco.Content is not available as it is in a surface controller or in a viewpage.

    Umbraco.Web.Composing.Current

    Solves the issue right now (though it is mentioned beeing dirty) until I get to understand DI better. Looking forward to the documentation update in general.

    Best regards.

  • Sebastiaan Janssen 4890 posts 14567 karma points MVP admin hq
    Mar 18, 2019 @ 07:04
    Sebastiaan Janssen
    1

    Let's get not dirty then!

    The basic principle of Dependency Injection is: ask and you shall receive.

    So since you're looking for Umbraco.Content, we can get there using the IUmbracoContextFactory. In your SurfaceController you can add a constructor to ask for what you need:

    public class MyTestController : SurfaceController
    {
        private readonly IUmbracoContextFactory _context;
    
        public MyTestController(IUmbracoContextFactory context)
        {
            _context = context;
        }
    
        // ... etc
    }
    

    Then in the methods you've created in your SurfaceController you can ask for that _context and use it:

        public ActionResult Index()
        {
            using (var cref = _context.EnsureUmbracoContext())
            {
                var cache = cref.UmbracoContext.ContentCache;
                var node = cache.GetById(1126);
    
                return Content(node.Name);
            }
        }
    

    Happy to look into other things you might be needing from the UmbracoHelper and provide some samples.

    This way you could eventually even start testing the logic in your controllers so you can safely refactor them at some point.

    And to prove it works, the screenshot is a bit clipped but node 1126 is open there in the backoffice:

    enter image description here

  • Bo Jacobsen 392 posts 1629 karma points
    Mar 18, 2019 @ 20:54
    Bo Jacobsen
    0

    Hi Sebastiaan.

    In Umbraco 7 we had to use the UDI when we used Umbraco Cloud. Because the Content and Media items got different id's on each Environment.

    What is the case in Umbraco 8?

    using (var cref = _context.EnsureUmbracoContext())
    {
        var cache = cref.UmbracoContext.ContentCache;
        // Can't find any UDI.
        var node =  cache.GetById(int or Guid);
                    cache.GetByContentType(PublishedContentType);
                    cache.GetByRoute(string);
                    cache.GetByXPath(string or XPathExpression);
    
        return node.Name;
    }
    
  • Thomsen 108 posts 316 karma points
    Mar 18, 2019 @ 07:38
    Thomsen
    0

    Hi again Sebastiaan

    Thanks, for clarifying once again. I try to implement your solution, but it gives me an error already in the MyTestController method:

    "Method must have a return type."

    enter image description here

  • Bjarne Fyrstenborg 1160 posts 3356 karma points MVP 4x c-trib
    Mar 18, 2019 @ 07:40
    Bjarne Fyrstenborg
    0

    The method MyTestController is the constructor of your class and in your case it needs to be TestSurfaceController.

    /Bjarne

  • Sebastiaan Janssen 4890 posts 14567 karma points MVP admin hq
    Mar 18, 2019 @ 07:45
    Sebastiaan Janssen
    1

    Yep like Bjarne points out correctly, your constructor method needs to have the same name as your class name. In my case, my class is named MyTestController so the constructor I create is public MyTestController().

    Pro-tip, in Visual studio when you type ctor and then hit Enter, it will create the constructor method for you.

    enter image description here

  • Bjarne Fyrstenborg 1160 posts 3356 karma points MVP 4x c-trib
    Mar 18, 2019 @ 07:51
    Bjarne Fyrstenborg
    0

    If the purpose is to get a specific node in the SurfaceController it should also be sufficient to access it via UmbracoHelper

    public ActionResult Index()
    {
        var node = Umbraco.Content(1126);
    
        return Content(node.Name);
    }
    

    but I guess this was to demonstrate how Dependency Injection works in v8 and how to access e.g. UmbracoContext in non Umbraco Controllers or with other dependencies?

  • Thomsen 108 posts 316 karma points
    Mar 18, 2019 @ 08:32
    Thomsen
    0

    Yes - you are right, it was to demonstrate how to access content from non Umbraco Controllers or "out of Umbraco scope", thanks.

  • Sebastiaan Janssen 4890 posts 14567 karma points MVP admin hq
    Mar 18, 2019 @ 09:48
    Sebastiaan Janssen
    0

    Ah, I was not awake yet! Yes, in a SurfaceController UmbracoHelper is "just" available to you! Thanks Bjarne. 👍

  • Thomsen 108 posts 316 karma points
    Mar 18, 2019 @ 08:07
    Thomsen
    0

    Hi - thank you,

    Now it makes sense - the constructor should be named after the class name, and it works..! - Except when I try to use _context in a static method within the class. The example here, is from my non Umbraco Controller helper class:

    enter image description here

  • Thomsen 108 posts 316 karma points
    Mar 18, 2019 @ 08:23
    Thomsen
    0

    It seems to work if I remove readonly when defining _context and make it static also (don't know if I am beeing dirty now ;-). Like this:

      private static IUmbracoContextFactory _context;
    
        public ContentHelpers(IUmbracoContextFactory context)
        {
            _context = context;
        }
    
  • David Zweben 231 posts 688 karma points
    Apr 12, 2019 @ 19:02
    David Zweben
    0

    I would also like to know this. Is it a good idea to create a static instance of IUmbracoContextFactory for use in a static method, or is there a better way to do this?

  • David Zweben 231 posts 688 karma points
    Apr 15, 2019 @ 18:20
    David Zweben
    0

    I could use some help with this. I have the code shown below. When I try to create a new instance of CustomUrlHelper in my template so I can use it, though, it expects me to pass it an argument of type IUmbracoContextFactory, because that's specified as a required parameter in the constructor.

    How am I supposed to instantiate a custom class that uses DI?

    namespace MySite.Core
    {
        public class CustomUrlHelper
        {
            private readonly IUmbracoContextFactory _context;
    
            public CustomUrlHelper(IUmbracoContextFactory context)
            {
                _context = context;
            }
    
            public string GetNodeUrl(int nodeId)
            {
                IPublishedContent node = null;
    
                using (var cref = _context.EnsureUmbracoContext())
                {
                    IPublishedContentCache cache = cref.UmbracoContext.ContentCache;
                     node = cache.GetById(nodeId);
                }
    
                return node.Url;
            }
        }
    }
    
  • Søren Gregersen 330 posts 1358 karma points MVP 2x c-trib
    Apr 15, 2019 @ 19:15
    Søren Gregersen
    0

    How am I supposed to instantiate a custom class that uses DI?

    in DI you don't :)

    DI is when your dependecies are injected. That means you don't instantiate them, but get them passed (injected) to you. ( https://en.wikipedia.org/wiki/Dependency_injection | https://www.codementor.io/mrfojo/c-with-dependency-injection-k2qfxbb8q ... and more... )

    To use your helper, you would need a controller, that would take the helper as a constructor parameter.

    public class MyController : Controller {
        private readonly CustomUrlHelper customUrlHelper;
        public MyController(CustomUrlHelper customUrlHelper){
            this.customUrlHelper=customUrlHelper;
        }
        public object Index(){
            //... use customUrlHelper instance...
        }
    }
    
  • David Zweben 231 posts 688 karma points
    Apr 15, 2019 @ 19:25
    David Zweben
    0

    Thanks. So am I understanding correctly that, to use DI in Umbraco, I can't just inject dependencies into my custom class and use that normally (which is what I was trying to do), but I also have to inject any classes that are using injected dependencies into a controller?

    If so, is the best way to make my custom class accessible globally to change the default Umbraco controller via route hijacking to one that takes my class as a construction parameter?

  • Søren Gregersen 330 posts 1358 karma points MVP 2x c-trib
    Apr 15, 2019 @ 19:36
    Søren Gregersen
    0

    I think you misunderstood my answer then :)

    DI handles creation/instantiating/resolving of all the dependencies - this is not just in Umbraco.

    In order for you to use it, you need a constructor (very simplified). Your template does not have a constructor :)

    You are free to instantiate your class in the template, that could be done like this (your class would then need a bit of refactoring):

    @inherits Umbraco.Web.Mvc.UmbracoViewPage<ContentModels.HomePage>
    @{
        var customUrlHelper = new CustomUrlHelper(this.UmbracoContext);
    }
    <a href="@customUrlHelper.GetNodeUrl(123)">text</a>
    
  • David Zweben 231 posts 688 karma points
    Apr 15, 2019 @ 19:41
    David Zweben
    0

    Søren,

    Thanks again for the reply. I'm still a bit confused, though. I thought that the point of DI was to automatically take care of providing the dependencies (such as UmbracoContext). If I have to pass this.UmbracoContext into the custom class as an argument when instantiating it, isn't that a case of me providing the dependency manually? If so, what is LightInject even doing?

  • Søren Gregersen 330 posts 1358 karma points MVP 2x c-trib
    Apr 15, 2019 @ 19:46
    Søren Gregersen
    0

    Well, as I pointed out, you need somewhere your dependencies can be injected (the constructor), which your template don't have.

    Your template can't have dependencies, since you don't control the constructor. The only way you can pass dependencies to a template, is by having a controller adding them to the model passed to the template.

  • David Zweben 231 posts 688 karma points
    Apr 15, 2019 @ 19:50
    David Zweben
    0

    Ok, I see. So basically I am just doing it manually instead of getting the dependencies injected in that case, because I don't have control over the constructor to request what I want?

    If so, wouldn't it also work to use a custom controller via route hijacking so that I can specify the dependencies I want?

  • Søren Gregersen 330 posts 1358 karma points MVP 2x c-trib
    Apr 15, 2019 @ 19:51
  • David Zweben 231 posts 688 karma points
    Apr 15, 2019 @ 19:54
    David Zweben
    1

    Thanks, this helped clarify things a lot!

  • David Zweben 231 posts 688 karma points
    Apr 15, 2019 @ 20:50
    David Zweben
    0

    Sorry to flood you with questions, but I think this will be helpful to a lot of people.

    I set up a custom controller as shown below. I can see that CustomUrlHelper is getting injected into the controller as expected, which is great!

    Is it possible (and if so, good practice) to pass the instance of CustomUrlHelper that I have inside the controller into the template, so that I can use it there? Or is this approach only good if I am keeping logic in the controller?

    namespace MySite.Core.Controllers
    {
        public class DoctypeNameHereController : RenderMvcController
        {
            private readonly CustomUrlHelper customUrlHelper;
    
            public DoctypeNameHereController(CustomUrlHelper injectedCustomUrlHelper)
            {
                customUrlHelper = injectedCustomUrlHelper;
            }
    
            public override ActionResult Index(ContentModel model)
            {
                return base.Index(model);
            }
    
        }
    
    }
    
  • Marc Goodson 1247 posts 8423 karma points MVP 5x c-trib
    Apr 21, 2019 @ 23:11
    Marc Goodson
    2

    Hi David

    Thinking one possibility here would be to create your own CustomViewPage to use instead of UmbracoViewPage.. something like this:

    using Umbraco.Web.Mvc;
    using Umbraco8.Services;
    using Current = Umbraco.Web.Composing.Current;
    
    namespace Umbraco8.Models
    {
        public abstract class CustomViewPage<T> : UmbracoViewPage<T>
        {
            public readonly ICustomUrlHelper _customUrlHelper;
            public CustomViewPage()
            {
                _customUrlHelper = (ICustomUrlHelper)Current.Factory.GetInstance(typeof(ICustomUrlHelper));
            }
    
            protected override void InitializePage()
            {
                base.InitializePage();
            }
        }
        public abstract class CustomViewPage : UmbracoViewPage
        {
            public readonly ICustomUrlHelper _customUrlHelper;
            public CustomViewPage()
            {
                _customUrlHelper = (ICustomUrlHelper)Current.Factory.GetInstance(typeof(ICustomUrlHelper));
            }
    
            protected override void InitializePage()
            {
                base.InitializePage();
            }
        }
    }
    

    If you define an interface (eg ICustomUrlHelper) for your CustomUrlHelper and register with DI, in a Composer:

       composition.Register<ICustomUrlHelper, CustomUrlHelper>(Lifetime.Request);
    

    Then you should be able to change the inherits statement at the top of your view templates to use your new CustomViewPage eg:

    @using Umbraco8.Models
    @inherits CustomViewPage<ContentModels.Blogpost>
    @using ContentModels = Umbraco.Web.PublishedModels;
    @{
        Layout = "master.cshtml";
    //use your helper!
       var something = _customUrlHelper.GetCustomUrl("whatever");
    }
    @Html.Partial("~/Views/Partials/SectionHeader.cshtml")
    <section class="section">
        <div class="container">
    

    and be able to call your service within your view/templates....

    The answer to your question though would probably depend more upon the context... the name of your helper seems like it might perhaps live as a an extension method on System.Web.Mvc.UrlHelper... calling it within your views as Url.DoCustomThing(), I guess it depends on the custom thing! but anyway above might be an option if you have a helper service that you want to register with DI and call from either controllers or views...

    regards

    Marc

  • David Zweben 231 posts 688 karma points
    Apr 22, 2019 @ 11:51
    David Zweben
    0

    This is really helpful, thanks!

  • George Phillipson 87 posts 236 karma points
    Apr 22, 2019 @ 11:08
    George Phillipson
    1

    I have been playing around with V8, it's getting hard to work out what the question is about now.

    If you are looking to use DI, then the test code I have below may help.

    using Umbraco.Core;
    using Umbraco.Core.Composing;
    using Umbraco.Core.Models.PublishedContent;
    using Umbraco.Web;
    
    namespace Web.Models.Helpers
    {
        public interface IHelperService
        {
            int Sum(int x, int y);
            IPublishedContent PageContentById(int pageId);
        }
    
        public class HelperComposer : IUserComposer
        {
            public void Compose(Composition composition)
            {
                composition.Register<IHelperService, HelperService>(Lifetime.Singleton);
            }
        }
    
        public class HelperService : IHelperService
        {
            private readonly IUmbracoContextFactory _contextFactory;
    
            public HelperService(IUmbracoContextFactory contextFactory)
            {
                _contextFactory = contextFactory;
            }
            public HelperService() { }
            public int Sum(int x, int y)
            {
                int sum = (x + y);
                return sum;
            }
    
            public IPublishedContent PageContentById(int pageId)
            {
    
                using (var cf = _contextFactory.EnsureUmbracoContext())
                {
                    var cache = cf.UmbracoContext.ContentCache;
                    IPublishedContent node = cache.GetById(pageId);
    
                    return node;
                }
            }
        }
    }
    

    Now in my Controller, I have:

    public class HomeController : RenderMvcController
        {
            private readonly IUmbracoContextFactory _contextFactory;
            private readonly IHelperService _helperService;
    
            public HomeController(IUmbracoContextFactory contextFactory, IHelperService helperService)
            {
                _contextFactory = contextFactory;
                _helperService = helperService;
            }
    
            public ActionResult Home()
            {
                var maths = _helperService.Sum(10, 5);
                var currentPage = CurrentPage.Id;
    
                var composeData = _helperService.PageContentById(currentPage);
    
    
                return View("~/Views/Home.cshtml");
            }
        }
    

    The above returns the following

    enter image description here

    I also played about with the following

    using Umbraco.Core.Models.PublishedContent;
    using Umbraco.Web;
    
    namespace Web.Models.Helpers
    {
        public class PageData
        {
            public IPublishedContent PageContentById(int pageId, IUmbracoContextFactory contextFactory)
            {
                using (var cf = contextFactory.EnsureUmbracoContext())
                {
                    var cache = cf.UmbracoContext.ContentCache;
                    IPublishedContent node = cache.GetById(pageId);
    
                    return node;
                }
            }
        }
    }
    

    Controller

     public ActionResult Home()
            {
                var maths = _helperService.Sum(10, 5);
                var currentPage = CurrentPage.Id;
    
                PageData pData = new PageData();
                var bodyText2 = pData.PageContentById(currentPage, _contextFactory);
                var htmlString = bodyText2.Value<IHtmlString>("bodyText");
    
                var composeData = _helperService.PageContentById(currentPage);
    
                var text = composeData.Value("bodyText");
    
                return View("~/Views/Home.cshtml");
            }
    

    Here I'm passing the _contectFactory to my class along with the page Id and it returns the following

    enter image description here

    Hopefully, this will help someone

    Playing around a bit more, say you have a layout page and you only need to get the page title and description.

    For this test, I created a static class which returned a Tuple to see if it worked to return the title and description.

    I then passed in the current pageId as well as UmbracoContext

    var pageId = Umbraco.AssignedContentItem.Id;
    
        var metaInformation = MetaTitleDescriptionHelper.MetaInformationTuple(pageId,UmbracoContext);
    

    The class is below and the codes works as expected and returns the title and description.

    namespace Web.Helper.MetaHelper
    {
        public static class MetaTitleDescriptionHelper
        {
            public static (string Title, string Description) MetaInformationTuple(int currentPage, UmbracoContext context)
            {
                var nodeId = context.ContentCache.GetById(currentPage);
    
                if (nodeId == null) return (string.Empty, string.Empty);
    
                string title        = nodeId.Value<string>("pageTitle", defaultValue: nodeId.Name);
                string description  = nodeId.Value<string>("pageDescription");
    
                return (title, description);
    
            }
        }
    }
    
  • steschu 79 posts 456 karma points
    Jun 11, 2019 @ 13:45
    steschu
    0

    I just read almost the whole thread about the UmbracoContext issue and DI thing. But it is difficult for me to adapt this in my case.

    I have static class, that uses the UmbracoHelper in order to retrieve a Dictionary values. This class is accessed by various Data Annotation classes. These Data Annotation classes are used as Attributes in a Model class. This model is used in a SurfaceController.

    When I understand DI right, I have to pass the context from the controller to the model, somehow to the Attributes and in the Data Annotations to the static class? Maybe I am using bad architecture... but this seems quite complicated for me.

    Stephan

  • Søren Gregersen 330 posts 1358 karma points MVP 2x c-trib
    Jun 11, 2019 @ 14:09
    Søren Gregersen
    0

    Hi Stephan,

    The problem with using static accessors is that you dont know if/when they are accessible.

    Say you use UmbracoContext.Current, this creates a dependency on something that you can’t control. The umbracocontext is only available after something else has initialized it.

    If you use DI you can register when/how the UmbracoContext should be created/accessed.

    The main purpose of all this is to create code that is more SOLID (https://en.m.wikipedia.org/wiki/SOLID). I tend to think about unit testing and “can this run in a console app” when coding, to help with this. Your attributes accessing UmbracoContext would not fit this thinking I guess

  • steschu 79 posts 456 karma points
    Jun 11, 2019 @ 14:35
    steschu
    0

    So static accessors and DI are no friends? Guess I am old fashioned in my architecture. I use static classes a lot, mostly to render our some recurring HTML, e.g. datapager buttons. And then often need information about the current page, which I retrieve vis the UmbracoContext and UmbracoHelper.

  • Søren Gregersen 330 posts 1358 karma points MVP 2x c-trib
    Jun 11, 2019 @ 14:58
    Søren Gregersen
    0

    Hi,

    As long as you provide the dependencies, you are ok. But think about how you would test it, and how much knowledge you would need to have about the whole umbraco environment, for setting up such a test.

    Think about how you would explain your code to a new developer on the project - “that’s handlede for me”, “that’s magic” and “it’s always there” are just very bad answers when talking about dependencies.

  • George Phillipson 87 posts 236 karma points
    Jun 11, 2019 @ 14:26
    George Phillipson
    0

    Hi Stephan

    Are you pulling the dictionary items for form validation, if yes let me know and I'll send you the code I'm using for this? The only difference I'm pulling the values from my contactDoctype

    George

  • steschu 79 posts 456 karma points
    Jun 11, 2019 @ 14:32
    steschu
    0

    @George: What I am using is this: https://gist.github.com/rasmuseeg/3ca1630d45c54f485088#file-umbracodictionarydataannotations-cs

    And there is a static UmbracoDictionary class that retrieves the values by key from the Dictionary

  • George Phillipson 87 posts 236 karma points
    Jun 11, 2019 @ 15:06
    George Phillipson
    0

    Hi Stephan

    Below is how I have done it, point to note I have 2 different forms on my site, contact and blog, so I have hardcoded those values.

    I have removed a lot of code, but hopefully, you can follow it

    Contact View

    @{
        Layout = "Layout.cshtml";
        IHelperService helperService = new HelperService();
    }
    
    @Html.Partial("~/Views/Partials/Contact/pvContact.cshtml", new ContactViewModel(helperService, UmbracoContext))
    

    ContactViewModel - I have only added FirstName and this has PlaceHolder text and Required Helper message

    public class ContactViewModel
        {
            private static IHelperService   _iHelperService;
            private const string NodeName   = "contact";
            private static UmbracoContext   _context;
    
    
            public ContactViewModel(IHelperService iHelperService, UmbracoContext context)
            {
                _iHelperService = iHelperService;
                _context = context;
    
            }
    
            public ContactViewModel() { }
    
    
            [UmbracoGetPropertyValueDisplayNameHelper("nameLabel", "contact")]
            [UmbracoGetPropertyValueRequiredHelper("nameRequired", "contact")]
            public string FromName { get; set; }
    }
    

    Validation Helper

    using System.Linq;
    using Umbraco.Web;
    using Web.ContentHelper.ContentHelper;
    
    namespace Web.ContentHelper.Helpers.FormValidation
    {
        public static class UmbracoPropertyValidationHelper
        {
            public static string GetPropertyValueItem(string key,string nodeAlias)
            {
                var nodeId          = Umbraco.Web.Composing.Current.UmbracoHelper;
                var currentPage     = nodeId.ContentAtRoot().DescendantsOrSelfOfType(nodeAlias).First().Id;
    
                var data            = nodeId.Content(currentPage);
    
                string errorMessage = data.Value<string>(key);
    
                return errorMessage;
    
            }
        }
    }
    

    If you do not want to use UmbarcoHelper, you can also use the following

       using System.Linq;
    using Umbraco.Web;
    using Current = Umbraco.Web.Composing.Current;
    
    namespace Web.ContentHelper.Helpers.FormValidation
    {
        public static class UmbracoPropertyValidationHelper
        {
            public static string GetPropertyValueItem(string key,string nodeAlias)
            {
                return Current.UmbracoContext.ContentCache.GetByXPath($"//{nodeAlias}").Select(x=>x.Value<string>(key)).First();
    
            }
        }
    }
    

    Placeholder Helper

    using System;
    using System.ComponentModel;
    
        namespace Web.ContentHelper.Helpers.FormValidation
        {
            [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)]
            public class UmbracoGetPropertyValueDisplayNameHelper : DisplayNameAttribute
            {
                private readonly string _getPropertyValueKey;
                private readonly string _pageId;
                public UmbracoGetPropertyValueDisplayNameHelper(string propertyValueKey, string pageId)
                {
                    _getPropertyValueKey = propertyValueKey;
                    _pageId = pageId;
                }
    
                public override string DisplayName => UmbracoPropertyValidationHelper.GetPropertyValueItem(_getPropertyValueKey, _pageId);
            }
        }
    

    Required Field helper

    using System;
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    using System.Web.Mvc;
    
    namespace Web.ContentHelper.Helpers.FormValidation
    {
        [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter)]
        public class UmbracoGetPropertyValueRequiredHelper : RequiredAttribute, IClientValidatable
        {
            private readonly string _errorMessageKey;
            private readonly string _pageId;
    
            public UmbracoGetPropertyValueRequiredHelper(string errorMessageKey, string pageId)
            {
                _errorMessageKey = errorMessageKey;
                _pageId = pageId;
                ErrorMessage = UmbracoPropertyValidationHelper.GetPropertyValueItem(_errorMessageKey,_pageId);
            }
    
    
    
            public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
            {
                ErrorMessage = UmbracoPropertyValidationHelper.GetPropertyValueItem(_errorMessageKey,_pageId);
                var error = FormatErrorMessage(metadata.DisplayName);
                var rule = new ModelClientValidationRequiredRule(error);
    
                yield return rule;
            }
        }
    }
    

    In my CMS contact doctype I have

    nameLabel

    nameRequired

  • Markus Johansson 1658 posts 4727 karma points c-trib
    Jul 10, 2019 @ 14:07
    Markus Johansson
    1

    Hi!

    I might be late to the party here but figured I'll share my solution here.

    Depending on what you need to do with the helper you can inject a "sub set" of the features from the UmbracoHelper. Ie if you're looking to query the cache use IPublishedContentQuery.

    Just put it as a constructor-parameter and Umbraco will inject it for you.

    private readonly IPublishedContentQuery _publishedContent;
    
        public MockSettingsRepository(IPublishedContentQuery publishedContent)
        {
            _publishedContent = publishedContent;
        }
    

    Have a look here to use how the UmbracoHelper is using different interfaces internally: https://github.com/umbraco/Umbraco-CMS/blob/v8/dev/src/Umbraco.Web/UmbracoHelper.cs

  • suzyb 460 posts 871 karma points
    Jul 23, 2019 @ 18:44
    suzyb
    0

    I'm using the following code in Umbraco 8.1.0 but it's telling me this is deprecated now.

    var cache = cref.UmbracoContext.ContentCache
    

    Does anyone know what the new way of doing this would be?

  • Bjarne Fyrstenborg 1160 posts 3356 karma points MVP 4x c-trib
    Jul 23, 2019 @ 18:48
    Bjarne Fyrstenborg
    0

    In Umbraco v8.1 you can just access it via the Content property, e.g.

    var node = cref.UmbracoContext.Content.GetById(1234);
    

    /Bjarne

  • suzyb 460 posts 871 karma points
    Jul 23, 2019 @ 18:51
    suzyb
    0

    That worked but I'm sure it threw an error when I tried it earlier :(

    Thanks for the super quick reply.

  • Marc Love (uSkinned.net) 338 posts 894 karma points
    Jul 27, 2019 @ 11:36
    Marc Love (uSkinned.net)
    0

    Hi Folks,

    Been struggling with this new approach. Can anyone explain how I can access UmbracoHelper in a custom HttpHandler.

    With DI the constructor now has a parameter. If I setup my Custom HttpHandler I get an error regarding the constructor now containing a parameter.

    Cheers,

    Marc

  • Marc Goodson 1247 posts 8423 karma points MVP 5x c-trib
    Jul 28, 2019 @ 22:44
    Marc Goodson
    0

    Hi Marc

    With common extension points, Umbraco tends to give you an abstract class to work from that exposes UmbracoHelper, Services etc as 'Properties' on the abstract class - no need to use DI, they are already injected, eg RenderMvcController, SurfaceController, UmbracoViewPage etc... and I believe a HttpHandler is no different!

    If you create your HttpHandler and make it inherit and implement the abstract class UmbracoHttpHandler from Umbraco.Web namespace then you should have access to the usual 'gateway' properties... eg

    using System;
    using System.Web;
    using Umbraco.Core.Models.PublishedContent;
    using Umbraco.Core.Models;
    using Umbraco.Web;
    
    namespace Umbraco8.Handlers
    {
        public class CustomHttpHandler : UmbracoHttpHandler
        {
            public override bool IsReusable => true;
    
            public override void ProcessRequest(HttpContext context)
            {
               // here we have access to Umbraco. and Services. 
                //query the cache
                IPublishedContent myContent = Umbraco.Content(1234);
               //use media service or something
               IMedia mediaItem = Services.MediaService.GetMediaByPath("/media/12345/somepathorsomething.pdf");
    
            }
        }
    }
    

    You can see the Abstract class in the source and what it provides access to:

    https://github.com/umbraco/Umbraco-CMS/blob/853087a75044b814df458457dc9a1f778cc89749/src/Umbraco.Web/UmbracoHttpHandler.cs

    regards

    Marc

  • Marc Love (uSkinned.net) 338 posts 894 karma points
    Jul 29, 2019 @ 10:29
    Marc Love (uSkinned.net)
    0

    Hi Marc,

    Thanks for the reply. Was all excited about trying out the code and then no luck!!!

    It looks like this may be an issue with the new V8 code however I get an error when trying this approach.

    Object reference not set to an instance of an object.
    
    [NullReferenceException: Object reference not set to an instance of an object.]
       Umbraco.Web.Runtime.<>c.<Compose>b__0_6(IFactory factory) in d:\a\1\s\src\Umbraco.Web\Runtime\WebInitialComposer.cs:116
       Umbraco.Core.FactoryExtensions.GetInstance(IFactory factory) in d:\a\1\s\src\Umbraco.Core\FactoryExtensions.cs:22
       Umbraco.Web.UmbracoHttpHandler..ctor() in d:\a\1\s\src\Umbraco.Web\UmbracoHttpHandler.cs:17
       BusinessLogic.ThemeHttpHandler..ctor() +36
       BusinessLogic.ThemeRouteHandler.GetHttpHandler(RequestContext requestContext) +70
       System.Web.Routing.UrlRoutingModule.PostResolveRequestCache(HttpContextBase context) +215
       System.Web.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +222
       System.Web.HttpApplication.ExecuteStepImpl(IExecutionStep step) +212
       System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +90
    
  • Marc Love (uSkinned.net) 338 posts 894 karma points
    Jul 29, 2019 @ 10:42
    Marc Love (uSkinned.net)
    0

    Actually may be something else I am doing wrong.

    So that I can create all of this via code I have a class which inherits from IUserComposer

    In the Compose Method I have:

    composition.Components().Append<RegisterStyleSheetRouteComponent>();
    

    This calls the following class:

    public class RegisterStyleSheetRouteComponent : IComponent
    {
    
        // initialize: runs once when Umbraco starts
        public void Initialize()
        {
            RouteTable.Routes.Add("ThemeStyle", new Route("theme.style", new BusinessLogic.ThemeRouteHandler()));
        }
    
        // terminate: runs once when Umbraco stops
        public void Terminate()
        {
        }
    }
    

    I then have the following simple class which references my HttpHandler:

    public class ThemeRouteHandler : IRouteHandler
    {
        public IHttpHandler GetHttpHandler(RequestContext requestContext)
        { 
            return new ThemeHttpHandler();
        }
    }
    

    This is expecting a return type of IHttpHandler. How do I change this code to work with the UmbracoHttpHandler.

    Cheers,

    Marc

  • Marc Love (uSkinned.net) 338 posts 894 karma points
    Jul 29, 2019 @ 10:57
    Marc Love (uSkinned.net)
    0

    Adding to this, it looks like there IS an issue with UmbracoHttpHandler.

    I added my handler via web.config and got the same error:

    <add verb="*" path="theme.style" name="HttpHandler" type="StarterKit.BusinessLogic.ThemeHttpHandler, StarterKit"/>
    
  • Marc Goodson 1247 posts 8423 karma points MVP 5x c-trib
    Jul 30, 2019 @ 19:20
    Marc Goodson
    1

    Hi Marc

    I created a quick test httphandler in a vanilla V8 site to verify and I get the same issue...

    ... The thing that is null is the injected UmbracoHelper!

    And if you try to use the umbracoContextFactory + ensureumbracocontext approach then this appears also to be null ..

    So I've raised an issue on the tracker for it..

    ... apologies for the bum steer certainly seemed the recommended way....

    Regards

    Marc

  • Marc Love (uSkinned.net) 338 posts 894 karma points
    Jul 31, 2019 @ 08:55
    Marc Love (uSkinned.net)
    1

    Hi Marc,

    Thanks for the heads up and majorly appreciate you looking into this for me. Umbraco 8 seems great for basic stuff but every time I try to do something a bit more complex I keep running into issues. Really itching to get going with a full on Umbraco 8 site but it looks like client sites will need to stay on Umbraco 7 for the foreseeable future.

    Cheers,

    Marc

  • Marc Goodson 1247 posts 8423 karma points MVP 5x c-trib
    Aug 20, 2019 @ 08:35
    Marc Goodson
    1

    Hi Marc

    It was interesting to try to pick at what was going awry here, and with new software it's always a case of is it something that's been broken or not, or am I misunderstanding in some way, or something else! Horrible if it's just you that can't make it work too!

    Anyway Shannon has worked out why it's not working as you'd expected.

    https://github.com/umbraco/Umbraco-CMS/issues/6018

    It's because each incoming request is handled differently if it's a server request or a client side request - how this is determined is via the extension of the file, and .style isn't one that Umbraco knows about, and so the request is deemed to be clientside and an UmbracoContext isn't created.

    so if in your web.config you add the handler for theme.axd instead of theme.style

    then your handler that inherits from UmbracoHttpHandler should 'just work'

    and you'll be able to use Umbraco. to access the cache

    regards

    Marc

  • Marc Love (uSkinned.net) 338 posts 894 karma points
    Aug 21, 2019 @ 09:48
    Marc Love (uSkinned.net)
    0

    Hi Marc,

    Tested your solution and works perfectly. Definitely a pint I owe you if we bump into each other at future meetups.

    Cheers,

    Marc

  • Mackey Kinard 2 posts 73 karma points
    Jan 31, 2020 @ 23:12
    Mackey Kinard
    0

    I could have sworn this post was about using UmbracoHelper in a custom class outside the normal Umbraco Page or controller scope.

    But the ONLY reference to using UmbracoHelper from a custom class is

    var helper = Umbraco.Web.Composing.Current.UmbracoHelper; var node = helper.Content(nodeId);

    What happened to the original question... How to use UmbracoHelper in a custom class

  • Marc Goodson 1247 posts 8423 karma points MVP 5x c-trib
    Feb 01, 2020 @ 08:16
    Marc Goodson
    0

    Hi Mackey

    It's certainly spun off at a tangent from the original question!!

    Since then there has been some information to the documentation about accessing Umbraco Context and Umbraco Helper in custom scenarios.

    Have a look at this page:

    https://our.umbraco.com/Documentation/Implementation/Services/#accessing-core-services-and-helpers-when-there-is-no-umbracocontext-eg-in-a-component-or-c-class

    there are some examples!

    But generally it depends on whether you can be sure an UmbracoContext exists at the point where your custom class is executed to influence your approach.

    regards

    Marc

  • Heather Floyd 528 posts 772 karma points MVP 2x c-trib
    1 week ago
    Heather Floyd
    0

    So, Marc and Seb (and anyone else who understands this DI-in-Umbraco stuff), maybe you can take a look at my spin-off question here:

    Utilizing UmbracoHelper in Content/Media events

    It's got code samples - and error messages ;-)

    Thanks in advance!

    ~Heather

  • Craig100 1067 posts 2333 karma points c-trib
    Jun 05, 2020 @ 21:25
    Craig100
    0

    Umb 8.6.1

    I find this stuff so confusing, but going on Sebs code at the top, I have this:-

    using Umbraco.Core.Models.PublishedContent;
    using Umbraco.Web;
    
    namespace BSL8.Core.Helpers
    {
        public class Helpers
        {
            private readonly IUmbracoContextFactory _context;
    
            public Helpers(IUmbracoContextFactory context)
            {
                _context = context;
            }
    
            public IPublishedContent GetGlobalSettingsNode()
            {               
                using (var cref = _context.EnsureUmbracoContext())
                {
                    var cache = cref.UmbracoContext.ContentCache;
                    var globalContent = cache.ContentSingleAtXPath("//siteWideSettings");
                    return globalContent;
                }            
            }
        }
    }
    

    VS wants me to annotate the GetGlobalSettingsNode with [System.Obsolete] and cache doesn't have a "ContentSingleAtXPath". I just want a way for a C# class to get hold of the SiteWideSettings node.

    Any advice appreciated.

    / Craig

  • Kevin Jump 1768 posts 11344 karma points MVP 4x c-trib
    Jun 06, 2020 @ 05:29
    Kevin Jump
    2

    Hi Craig,

    I think you want to use the 'Content' property.

    so it becomes .

    var contentCache = cref.UmbracoContext.Content;
    var globalContent = contentCache.GetSingleByXPath("//siteWideSettings");
    return globalContent;
    
  • Craig100 1067 posts 2333 karma points c-trib
    Jun 06, 2020 @ 08:35
    Craig100
    0

    Thanks Kevin,

    That's cleared that up. Not sure how we get to know about these things without asking someone, so thanks.

    The "Final" bit is how to call it from a static method.

        private static IPublishedContent SiteWideSettings() {           
            Helpers.Helpers helper = new Helpers.Helpers(xxxxxxx);
            IPublishedContent globalSettings = helper.GetGlobalSettingsNode();
            return globalSettings;
        }
    

    It's asking for an IUmbracoContextFactory in place of xxxxxxx. It seems that you're always going to need an Umbraco Context when there isn't one, so what do you do? This code just appears to move the problem up the tree.

    The idea I had was to have a method I could call to return the siteWideSettings node from anywhere. I guess it's not possible. Looks like I'll have to code it in place each time it's needed, which isn't very DRY. Unless I've got it completely wrong of course.

    Update

    Decided to re-engineer around the problem and use ICONTENT to get the SiteWideSettingsNode instead. One day I might understand DI but today's probably not that day ;)

    Thanks for all the help x

  • Marc Goodson 1247 posts 8423 karma points MVP 5x c-trib
    Jun 06, 2020 @ 11:59
    Marc Goodson
    2

    Hi Craig100

    I put some examples around accessing Umbraco content in various scenarios here:

    https://our.umbraco.com/Documentation/Implementation/Services/#custom-services-and-helpers

    just because, if you are not familiar with DI it can be really hard to explain, it's not 'intuitive' - but real examples can hopefully point the way... be great to know if the above helps explain, or whether we can add some more info here...

    The example shows creating a 'SiteService' which has the job of locating things in the site (eg like your GlobalSettingsNode).

    regards

    Marc

  • Søren Gregersen 330 posts 1358 karma points MVP 2x c-trib
    Jun 06, 2020 @ 12:15
    Søren Gregersen
    0

    You should never use content service in the frontend of your site — unless you want performance issues ;-)

    IContent is only used in the backoffice, so be carefull how you use this

  • Craig100 1067 posts 2333 karma points c-trib
    Jun 06, 2020 @ 13:45
    Craig100
    0

    Hi Søren, totally agree. This is being used to service an API, nothing to do with front end ;)

    Cheers.

  • Jules 204 posts 447 karma points
    Jun 06, 2020 @ 12:12
    Jules
    0

    Artificial example here using a starter kit and apologies if I am missing the point here (please feel free to tell me if I am :) ) but I found that if I register a service using a composer:

    public interface IBlogpostService
    {
        Home GetHomeNode(); // FYI - Home is a modelsbuilder model
    }
    
    public class Composer : IUserComposer
    {
        public void Compose(Composition composition)
        {
            composition.Register<IBlogpostService, BlogpostService>(Lifetime.Scope);
        }
    }
    

    I can inject UmbracoHelper into that service (Is this bad? ) :

    using Umbraco.Web;
    using Umbraco.Web.PublishedModels;
    
    namespace TestWeb.Web.Services.Blogpost
    {
    
        public class BlogpostService : IBlogpostService
        {
            private readonly UmbracoHelper _umbracoHelper;
    
            public BlogpostService(UmbracoHelper umbracoHelper)
            {
                _umbracoHelper = umbracoHelper;
            }
    
            public Home GetHomeNode()
            {
                return _umbracoHelper.ContentSingleAtXPath("//" + Home.ModelTypeAlias) as Home;
            }
        }
    }
    

    NB: This only works in the context of a web request and I do recognise that GetHomeNode would be far better in an extension of UmbracoHelper but just showing that UmbracoHelper is accessible

    Is this bad?

    Jules

  • Marc Goodson 1247 posts 8423 karma points MVP 5x c-trib
    Jun 06, 2020 @ 15:46
    Marc Goodson
    1

    Hi Jules

    Yes, with the caveat, that you've already identified, that this can only be called when used in the context of a web request, then this will work...

    UmbracoHelper though is a wrapper/gateway for 'lots and lots and lots' of stuff, so you might not need to 'inject the whole thing' - if you are only querying the cache then IPublishedContentQuery is probably all you need... this example here shows this approach:

    https://our.umbraco.com/Documentation/Implementation/Services/#1---the-service-will-only-be-used-during-a-request-like-in-a-controller-or-view

    the cool kids will be injecting this interface instead of UmbracoHelper to make it easier to Unit Test the implemented functionality, eg not having to mock the entire UmbracoHelper dependency in tests...

    ... but also for simple things.. finding the NewsSection etc, extension methods on UmbracoHelper can be a really intuitive syntax within a project...

    Finally in your example consider your 'scope' when you register your service - if you want to access the current AssignedContentItem in your implementation, then you'll need to register your service in 'Request' scope , so that this is assigned correctly on each request.

    regards

    Marc

  • Jules 204 posts 447 karma points
    Jun 06, 2020 @ 22:47
    Jules
    0

    Thanks for the clarification Marc

  • Craig100 1067 posts 2333 karma points c-trib
    Jun 06, 2020 @ 13:41
    Craig100
    0

    Thanks Marc,

    I've been converting a V7 site to V8 and converting it's API which it uses to call external API's to create and update content. I had it all working then it came to do the scheduling. As the simple scheduledTask system (which worked absolutely fine) has been done away with I've now had to get it going with code examples kindly offered by Kevin Jump.

    The problem is now with accessing the ContentService. I'm using private static IContentService contentService = Current.Services.ContentService; which only works when the API is called from a browser. When it's called from the now coded up scheduler directly it fails as there's no current. If I remove current and do as examples I've seen which just say to use Services.ContentService it complains that ContentService does not exist in the namespace, despite there being a using Umbraco.Core.Services; available.

    The ContentService reference (https://our.umbraco.com/apidocs/v8/csharp/api/Umbraco.Core.Services.IContentService.html) states it's in namespace System.Dynamic.ExpandoObject but adding a using for it causes VS to complain it's not necessary.

    Pretty much gone round full circle now, brain is melting so I suspect the architecture is all wrong. #stuffshouldntbethisawkward

  • Marc Goodson 1247 posts 8423 karma points MVP 5x c-trib
    Jun 06, 2020 @ 15:30
    Marc Goodson
    0

    Hi Craig100

    Yes, feel your pain, in V8 the ContentService is in the following namespace:

    namespace Umbraco.Core.Services.Implement
    

    https://github.com/umbraco/Umbraco-CMS/blob/v8/contrib/src/Umbraco.Core/Services/Implement/ContentService.cs#L17

    If your custom class is registered with DI - https://our.umbraco.com/Documentation/Reference/Using-Ioc/#registering-dependencies

    then you should be able to inject in IContentService into the constructor of your custom class...

    private readonly IContentService _contentService;
    public MyCustomClass(IContentService contentService){
    _contentService = contentService;
    }
    

    but if you are working with a controller based on a core Umbraco Controller or in a View you can access it via the Services.ContentService option.

    regards

    Marc

  • Craig100 1067 posts 2333 karma points c-trib
    Jun 06, 2020 @ 16:12
    Craig100
    0

    Do we have to use DI now then to use the ContentService in a .Core C# Class (i.e. one that has nothing to do with a web request)? Even if I have absolutely no need to do any unit testing whatsoever?

    I added a using for Umbraco.Core.Services.Implement but VS still said ContentService was missing a ton of arguments when used as

    private static IContentService contentService = new ContentService();
    

    I just need whatever the equivalent of this is:

     // Set up ContentService to SaveAndPublish
            private static IContentService contentService = Current.Services.ContentService;
    

    to run when there is no Umbraco Context so I can do simple things like:-

    var currentPropertyList = contentService.GetPagedChildren(propertyRootNodeID, 0, int.MaxValue, out var propertyCount); 
    

    And then get on with adding pages, updating values, etc.

    Rgds,

    / Craig

    Update

    As I didn't want to re-engineer 80hrs of work I've done a massive cheat and reverted my code to use current and made the new background task scheduler call via a webClient request so the code now has a request context. - Job done.

    Next job is to seriously get to grips with DI before the next job!

    Regards,

    Craig

  • Marc Goodson 1247 posts 8423 karma points MVP 5x c-trib
    Jun 06, 2020 @ 17:11
    Marc Goodson
    0

    Hi Craig

    So if I'm understanding correctly - you have a separate c# Class Library? and you have UmbracoCms.Core library installed via NuGet? (https://www.nuget.org/packages/UmbracoCms.Core/)

    then something like this:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace SuperTestCore
    {
        public class SuperTest
        {
            public SuperTest()
            {
                var cs = Umbraco.Core.Composing.Current.Services.ContentService;
    
            }
        }
    }
    

    If you are trying to 'new up' a ContentService, then that's going to be tricky as you'll need to pass in all the dependencies, same in V7 - but you shouldn't need to...

    in V7 you could always get a reference 'things' 'everywhere' via the ApplicationContext... which is gone in V8, but you can access them from the DI Composition using Current without having to be in a WebRequest like the above...

    or am I totally misunderstanding what you are trying to do!!

    regards

    Marc

  • Craig100 1067 posts 2333 karma points c-trib
    Jun 06, 2020 @ 17:21
    Craig100
    0

    Hi Marc,

    Thanks for your perseverance! Using Current only works if there's a request context which there isn't in this case. I've updated my last post with my "Get out!".

    But as you asked.... I have...

    Solution
    .Core project - containing UmbracoCMS.Core and custom code
    .Web project - containing a normal Umbraco site
    

    I have Kevin's backgroundTaskScheduler in .Core as well as all my API code. Before trying to implement the backgroundTaskScheduler, i.e. just calling my API via a browser, all worked well. The API just called a few classes which pulled a couple of external API endpoints and read and wrote to Umbraco content. Where I came totally unstuck is trying to call those same classes directly from the backgroundTaskScheduler.

    So my "Get Out" as indicated on my updated post, was to revert to calling the API via a URL but from within the backgroundTaskScheduler ;)

    If the Scheduler kicks off again in the next 40 mins, I'm done :)

    Many, many thanks.

    Regards,

    Craig

Please Sign in or register to post replies

Write your reply to:

Draft