Copied to clipboard

Flag this post as spam?

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


  • Thomsen 71 posts 261 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 36 posts 79 karma points
    Mar 14, 2019 @ 02:02
    Warren Harding
    100

    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 4806 posts 13987 karma points MVP admin hq
    Mar 14, 2019 @ 06:59
    Sebastiaan Janssen
    0

    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);
        }
    
  • Thomsen 71 posts 261 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 4806 posts 13987 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 203 posts 995 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 71 posts 261 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 1122 posts 3085 karma points MVP 2x 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 4806 posts 13987 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 1122 posts 3085 karma points MVP 2x 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 71 posts 261 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 4806 posts 13987 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 71 posts 261 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 71 posts 261 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 194 posts 518 karma points
    1 week ago
    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 194 posts 518 karma points
    7 days ago
    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 234 posts 977 karma points c-trib
    7 days ago
    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 194 posts 518 karma points
    7 days ago
    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 234 posts 977 karma points c-trib
    7 days ago
    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 194 posts 518 karma points
    7 days ago
    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 234 posts 977 karma points c-trib
    7 days ago
    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 194 posts 518 karma points
    7 days ago
    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 234 posts 977 karma points c-trib
    7 days ago
  • David Zweben 194 posts 518 karma points
    7 days ago
    David Zweben
    1

    Thanks, this helped clarify things a lot!

  • David Zweben 194 posts 518 karma points
    7 days ago
    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 811 posts 5325 karma points MVP 3x c-trib
    12 hours ago
    Marc Goodson
    0

    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

Please Sign in or register to post replies

Write your reply to:

Draft