0

I have the following problem and I currently have a solution using conditional dependency injection. I have read that this is a bad idea, such as here in the SimpleInjector docs. I have read a large number of posts now and have seen various things suggesting using Strategy, Factory patterns, etc. What I am really looking for is some specifics - i.e. an example of some code - about how to solve without conditional injection. I need more that "use a factory". Here's a simplified version of my code. This is in an MVC web app, thus the controllers.

public abstract class QAControllerBase : Controller
{
    protected readonly QABusinessLayer _blQA;
    public QAControllerBase(QABusinessLayer bl) 
    {
        _blQA = bl;
    }

    [HttpGet]
    public ActionResult PPR(string accession, string site)
    {
        var m = _blQA.popPPRViewModel(accession);

        return View(m);
    }
}

public class QASouthController : QAControllerBase
{

    public QASouthController([QASouthBinding] QABusinessLayer bl) : base(bl)
    {

    }

    // some actions that are specific to South
}


public class QANorthController : QAControllerBase
{

    public QANorthController([QANorthBinding] QABusinessLayer bl) : base(bl)
    {

    }

    // some actions that are specific to North
}

public abstract class QABusinessLayer
{
    protected readonly IFullBaseRepo _repo;
    public QABusinessLayer(IFullBaseRepo repo)
    {
        _repo = repo;
    }

    public abstract PPRViewModel popPPRViewModel(string accession);

    protected PPRViewModel DoSomeCommonStuff(PPRViewModel model)
    {
        ...
        return model;
    }

}

public class SouthBusinessLayer: QABusinessLayer
{

    public SouthBusinessLayer([QASouthBinding] IFullBaseRepo repo) : base(repo)
    {

    }

    public override PPRViewModel popPPRViewModel(string accession)
    {
        var m = new PPRViewModel();
        // do some stuff that is specific to South
        DoSomeCommonStuff(m);

        return m;
    }
}

public class NorthBusinessLayer : QABusinessLayer
{


    public NorthBusinessLayer([QANorthBinding] IFullBaseRepo repo) : base(repo)
    {

    }
    public override PPRViewModel popPPRViewModel(string accession)
    {
        var m = new PPRViewModel();
        // do some stuff that is specific to North
        DoSomeCommonStuff(m);

        return m;
    }
}

and here is the Ninject binding code that is pertinent:

            kernel.Bind<QABusinessLayer>()
            .To<SouthBusinessLayer>()
            .WhenTargetHas<QASouthBinding>()
            .InRequestScope();

        kernel.Bind<QABusinessLayer>()
            .To<NorthBusinessLayer>()
            .WhenTargetHas<QANorthBinding>()
            .InRequestScope();

The QASouthBinding and QANorthBinding are just simple attributes. I am not asking for Ninject specific example. Any code sample as to how this might be handled without using conditional or context based injection as I am now.

PBMe_HikeIt
  • 659
  • 8
  • 24
  • Based on what do you decide which one to create? – xszaboj Apr 14 '17 at 20:19
  • 1
    If QANorthController needs NorthBusinessLayer then state this in the constructor. There is no need for the attribute, just change QABusinessLayer to NorthBusinessLayer – Sir Rufo Apr 14 '17 at 20:29
  • 1
    For specific examples of the strategy pattern, see [this answer](http://stackoverflow.com/a/32415954), [this answer](http://stackoverflow.com/a/31971691), and [this answer](http://stackoverflow.com/a/1501517). You have business logic specific methods in your controller - those methods should be responsible for picking the specific implementation, not the DI container or some attribute. – NightOwl888 Apr 14 '17 at 22:43
  • 1
    Context injection isn't bad per se. There is a simple check you can do to find out whether you are on track or not. If Contextual Injection helps in covering up from a Liskov Substitution Principle violation, it's not a good thing to do. In that case, you should probably create multipl independent interfaces, or one generic interface (which is at runtime the same as multiple independent interfaces). – Steven Apr 15 '17 at 12:14
  • @NightOwl888 yes, this is proof of concept code so I will be refactoring and placing bus logic into bus logic layer – PBMe_HikeIt Apr 18 '17 at 19:35

1 Answers1

1

Make your QABusinessLayer class abstract.

Change your startup configuration to:

kernel
    .Bind<SouthBusinessLayer>()
    .To<SouthBusinessLayer>()
    .InRequestScope();

kernel
    .Bind<NorthBusinessLayer>()
    .To<NorthBusinessLayer>()
    .InRequestScope();

Change your controller constructors to accept a concrete business layer type:

public class QANorthController : QAControllerBase
{
    public QANorthController(NorthBusinessLayer businessLayer) : base(businessLayer)
    {
    }
}

public class QASouthController : QAControllerBase
{
    public QASouthController(SouthBusinessLayer businessLayer) : base(businessLayer)
    {
    }
}

Few things:

  1. If Ninject auto-binds concrete types to the same type, then you don't need to manually configure the dependencies during startup.
  2. You may want to use an interface rather than just passing your concrete BusinessLayer type.
fbhdev
  • 526
  • 7
  • 17
  • thank you this was helpful. The QABusinessLayer is already abstract, but I am marking as answer since you suggested some implementation and indeed I could just place the concrete types in the controller constructors – PBMe_HikeIt Apr 18 '17 at 19:32