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 312 posts 1120 karma points MVP c-trib
    22 days ago
    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 727 posts 2082 karma points hq
    21 days ago
    Stephen
    0

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

    will reply with full details asap

  • Stephen 727 posts 2082 karma points hq
    21 days ago
    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 312 posts 1120 karma points MVP c-trib
    21 days ago
    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 727 posts 2082 karma points hq
    20 days ago
    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 131 posts 760 karma points c-trib
    1 week ago
    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 312 posts 1120 karma points MVP c-trib
    1 week ago
    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 131 posts 760 karma points c-trib
    1 week ago
    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 312 posts 1120 karma points MVP c-trib
    1 week ago
    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 131 posts 760 karma points c-trib
    1 week ago
    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 312 posts 1120 karma points MVP c-trib
    1 week ago
    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 312 posts 1120 karma points MVP c-trib
    7 days ago
    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 131 posts 760 karma points c-trib
    7 days ago
    Kenn Jacobsen
    0

    Thanks Nathan, works like a charm now!

  • Nathan Woulfe 312 posts 1120 karma points MVP c-trib
    7 days ago
    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 484 posts 1719 karma points c-trib
    7 days ago
    James Jackson-South
    0

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

  • Nathan Woulfe 312 posts 1120 karma points MVP c-trib
    7 days ago
    Nathan Woulfe
    0

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

  • James Jackson-South 484 posts 1719 karma points c-trib
    7 days ago
    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 312 posts 1120 karma points MVP c-trib
    7 days ago
    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 484 posts 1719 karma points c-trib
    7 days ago
    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 484 posts 1719 karma points c-trib
    7 days ago
    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 727 posts 2082 karma points hq
    7 days ago
    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 484 posts 1719 karma points c-trib
    7 days ago
    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.

Please Sign in or register to post replies

Write your reply to:

Draft