Copied to clipboard

Flag this post as spam?

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


  • Nathan Woulfe 347 posts 1292 karma points MVP c-trib
    Dec 25, 2018 @ 23:13
    Nathan Woulfe
    0

    ApiControllers, LightInject and service injection in packages/content apps

    Hi folks, looking for some insight/direction around how to set up my controllers in a V8 package/content app.

    In V7, I'd typically do something like this:

    public class MyApiController : UmbracoAuthorizedApiController
    {
        private readonly IFirstService _firstService;
        private readonly IAnotherService _anotherService;
    
        public MyApiController() : this(new FirstService(), new AnotherService())
        {
        }
    
        public MyApiController(IFirstService firstService, IAnotherService anotherService)
        {
            _firstService = firstService;
            _anotherService = anotherService;
        }
    
        // API methods
    }
    

    That will work fine without any additional setup - it's pretty standard DI stuff, makes it easy to replace the services with mocks/other implementations. Cool.

    In V8 however, that same pattern throws a generic error - API controller does not have a parameterless controller. The stack trace references LightInject, so I'm assuming it's related to the new container?

    I can work around it by newing up the services in the respective API methods, but that's shitty.

    How should this be managed in V8?

  • Stephen 756 posts 2225 karma points hq
    Dec 26, 2018 @ 11:20
    Stephen
    0

    just to be sure can you share the full stack trace?

    will reply with full details asap

  • Stephen 756 posts 2225 karma points hq
    Dec 26, 2018 @ 21:24
    Stephen
    0

    So... LightInject uses the constructor with the most parameters that it can resolve. If no constructor can fit, then you get the generic "Make sure that the controller has a parameterless public constructor" exception - but with an inner exception that should tell you why LightInject is not happy.

    (reason why I'm asking for the stack trace)

    In your example above, you have a parameterless constructor, so it should work. But maybe you got the error with a slightly different class. And then, typical DI as you said: you need to register your services in the container. Have you done this?

    Would happen in a component (inheriting UmbracoComponentBase) Compose method.

    Give me a bit more details and I'll be happy to help

  • Nathan Woulfe 347 posts 1292 karma points MVP c-trib
    Dec 27, 2018 @ 03:20
    Nathan Woulfe
    0

    Thanks Stephen - I'd missed the Compose method. Added that in to my startup class, registered the services and everything is working fine.

    Knew it wouldn't be anything major.

    This change means (correct me if I'm wrong) that any backoffice extension must use LightInject for DI?

  • Stephen 756 posts 2225 karma points hq
    Dec 27, 2018 @ 10:10
    Stephen
    0

    At the moment: yes. Soon: no. What the Compose method will get is a LightInject-agnostic register. No dependency on LightInject. Stay tuned.

  • Kenn Jacobsen 132 posts 763 karma points c-trib
    Jan 09, 2019 @ 07:43
    Kenn Jacobsen
    0

    It'd be really awesome to have a write-up on how to register services and implementations for plugins/content apps/whole sites, so we can start injecting stuff into both RenderMvcControllers and ApiControllers. Any chance you have something that could give us a head start?

  • Nathan Woulfe 347 posts 1292 karma points MVP c-trib
    Jan 09, 2019 @ 08:55
    Nathan Woulfe
    1

    FWIW, here's my current implementation:

    public class UmbracoStartup : UmbracoComponentBase, IUmbracoUserComponent
    {
        public override void Compose(Composition composition)
        {
            composition.Container.RegisterSingleton<ISomethingService, SomethingService>();
    
            composition.Container.Register<ISomethingPlugin, SomethingPlugin>();
        }
    
        public void Initialize()
        {
            // here we can register listeners etc, or do any other startup tasks
            ServerVariablesParser.Parsing += ServerVariablesParser_Parsing;
        }
    }
    

    Once those services are registered in Compose, they can be injected into controllers

    public class MyController() : RenderMvcController {
        private readonly ISomethingService _somethingService;
    
        public MyController() : this(new SomethingService()) {}
    
        public MyController(ISomethingService somethingService) {
            _somethingService = somethingService;
        }
    }
    

    At least, that's my understanding.

  • Kenn Jacobsen 132 posts 763 karma points c-trib
    Jan 09, 2019 @ 10:37
    Kenn Jacobsen
    0

    Thanks Nathan. I'll give that a spin soon as I can.

    Do you need the default constructor? It doesn't look terribly decoupled having to pass default implementations of services to constructors.

  • Nathan Woulfe 347 posts 1292 karma points MVP c-trib
    Jan 09, 2019 @ 11:11
    Nathan Woulfe
    0

    It's the only way I could get it working, but I agree, it kinda defeats the purpose...

    Tried it again just now without the default constructor, but LightInject complains about not having a parameterless public constructor. Highly likely it's user error on my part.

  • Kenn Jacobsen 132 posts 763 karma points c-trib
    Jan 09, 2019 @ 11:32
    Kenn Jacobsen
    0

    Thanks for getting back.

    I don't think it's a user error on your part... you're seeing the same as I was when I played around with it.

    Based on the previous comments I did manage to find the UmbracoComponentBase and the container registration stuff in its Compose method. But registering a service implementation didn't really work for my RenderMvcController - I got the exact same exception.

    I also tried registering my service in a custom UmbracoApplication (I'm setting up a site so I have that luxury) but the same happens. I have yet to test if the registration works in an API controller.

  • Nathan Woulfe 347 posts 1292 karma points MVP c-trib
    Jan 09, 2019 @ 21:35
    Nathan Woulfe
    0

    It should (I believe) work in an ApiController - Umbraco's services are exposed on ServiceContext, ideally any other registered services would be there too.

    Will try to find time today to fiddle more.

  • Nathan Woulfe 347 posts 1292 karma points MVP c-trib
    Jan 10, 2019 @ 05:19
    Nathan Woulfe
    1

    Latest nightly handles this a bit differently - below is how I have it working, with proper DI in classes

    public class MyComposer : IUserComposer
    {
        public void Compose(Composition composition)
        {
            composition.Register<IMyThing, MyThing>();            
            composition.Components().Append<MyComponent>();
        }
    }
    
    public class MyComponent : IComponent {
        public void Initialize() {
            // do stuff on startup
        }
    
        public void Terminate() {
            // HASTA LA VISTA!
        }
    }
    
    public class MyClass {
        private readonly IMyThing _myThing;
    
        public MyClass(IMyThing myThing) {
            _myThing = myThing;
        }         
    }
    
  • Kenn Jacobsen 132 posts 763 karma points c-trib
    Jan 10, 2019 @ 09:09
    Kenn Jacobsen
    0

    Thanks Nathan, works like a charm now!

  • Nathan Woulfe 347 posts 1292 karma points MVP c-trib
    Jan 10, 2019 @ 05:40
    Nathan Woulfe
    0

    And to build on that, with a bit of a detour from what this thread was discussing, here's the next issue I've hit - more a general DI thing than Umbraco specific.

    Trying to get property injection working - scenario is I have an interface, from which I derive a class. I want all instances of that class to have a service injected.

    If I do it via ctor-injection, all derived classes must provide the parameter. I don't want that. Derived classes shouldn't need to know the parent implementation.

  • James Jackson-South 488 posts 1735 karma points c-trib
    Jan 10, 2019 @ 05:50
    James Jackson-South
    0

    You don't want property injection. That introduces temporal coupling which is a Bad Thing ™

  • Nathan Woulfe 347 posts 1292 karma points MVP c-trib
    Jan 10, 2019 @ 06:02
    Nathan Woulfe
    0

    So how do I get out of this dilly of a pickle in which I am mired?

  • James Jackson-South 488 posts 1735 karma points c-trib
    Jan 10, 2019 @ 06:10
    James Jackson-South
    0

    Just to clarify.

    You want to inherit from a base class and not have to specify the injected parameter?

    Personally, that's a no-no for me. Without constructor parameters you have no idea when each injected service in the base class is available in the lifetime of your derived objects. That can lead to hard to track bugs.

    Do you need inheritance? Why are parameters a big deal. The derived classes need to know about the parent since that's how inheritance works.

  • Nathan Woulfe 347 posts 1292 karma points MVP c-trib
    Jan 10, 2019 @ 06:24
    Nathan Woulfe
    0

    I want the base class to provide data from the service, and have it available to the derived class, without the ctor injection

    public class Foo : IFoo {
        public IService Service { get; set; }
    
        public int Digits => Service.GetDigits();
    }
    
    public class Bar : Foo {
        public Bar() {
            var x = Digits + 1;
        }
    }
    

    If I have 20 different implementations of Foo, they shouldn't each need a reference to Service just to retrieve Digits.

    If there's a better way to achieve this, I'm all for it.

  • James Jackson-South 488 posts 1735 karma points c-trib
    Jan 10, 2019 @ 06:50
    James Jackson-South
    0

    In this case I recommend redesigning your classes to accept the service as a method parameter.

    public class Foo {   
        public int Digits => Service.GetDigits(IService service);
    }
    
    public class Bar : Foo {   
    }
    
    
    public class Caller 
    {    
         private readonly IService service;
    
         public Caller(IService service) {
             this.service = service;
         }
    
         public int GetDigits() {
             Bar bar  = new Bar();
             return bar.Digits(this.Service) + 1;
         }     
    }
    
  • James Jackson-South 488 posts 1735 karma points c-trib
    Jan 10, 2019 @ 05:49
    James Jackson-South
    0
    public class MyComponent : IComponent {
        public void Initialize() {
            // do stuff on startup
        }
    
        public void Terminate() {
            // HASTA LA VISTA!
        }
    }
    

    Call me crazy but this seems more.... Well more.

    IComponent : IDisposable
    {
    
    }
    
    public class MyComponent : IComponent {
        public MyComponent() {
            // do stuff on startup
        }
    
        public void Dispose() {
            // HASTA LA VISTA!
        }
    }
    
  • Stephen 756 posts 2225 karma points hq
    Jan 10, 2019 @ 07:15
    Stephen
    0

    Ah well, guess what? That was my original design and it got changed by... popular demand. Not that you are not popular ;-) So yea... I'm not sure, don't know if there is a "right" answer to that one.

    As for the rest of the thread... I'm following it and plan to post a reply.

  • James Jackson-South 488 posts 1735 karma points c-trib
    Jan 10, 2019 @ 08:41
    James Jackson-South
    0

    Should have stuck with your guns there chum, you definitely had it right first time. :)

    Relying on an external caller to invoke an initialise call is more temporal coupling.

  • Nick Bennett 14 posts 84 karma points
    Mar 15, 2019 @ 12:31
    Nick Bennett
    0

    I'm trying to follow this pattern to do some initialization but I am falling at the first hurdle - the class UmbracoComponentBase doesn't exist. Even the namespace

    I've got the following NuGet packages:

    Umbraco.ModelsBuilder 8.0.1 Umbraco.ModelsBuilder.Ui 8.0.1 Umbraco.SqlServerCE 4.0.0.1 UmbracoCms 8.0.0 UmbracoCms.Core 8.0.0 UmbracoCms.Web 8.0.0

    Where did you guys get this class from?

  • Stephen 756 posts 2225 karma points hq
    Mar 15, 2019 @ 14:03
    Stephen
    0

    UmbracoComponentBase never made it to 8.0.0 indeed. The more recent comments on this post do things differently. You may also want to read this post for more details.

  • Nathan Woulfe 347 posts 1292 karma points MVP c-trib
    Mar 15, 2019 @ 19:49
    Nathan Woulfe
    0

    Feel free to have a poke around in github.com/nathanwoulfe/plumber8

    The .Web and .Core projects both use the composer pattern, but only .Core uses both composer and component.

Please Sign in or register to post replies

Write your reply to:

Draft