3

Currently I have a Process that all users of a website undergo. (Process covers multiple controllers and views).

I have a request to use the same process overall (but with variations) for a separate type of Customer. Rather than fill my affected controllers with if thens I can see I have one of 2 options.

1) Create variations on the controller (backed by a common abstract class for the common features), and figure out how to call a specific controller based on customer type, or keep the controller structure simple, and pass in a dependency that contains the functionality that will vary.

I am leaning towards the second option, but this means I will need to be able to tell simple injector to register different classes with the same interface, and then, depending on a parameter which won't be known until a customer logs in, instantiate the correct class.

ie (I know this code won't work as is)

//in Simple Injector Initialize
 container.Register<ICustomerProcess, RetailCustomer>(Lifestyle.Scoped);
 container.Register<ICustomerProcess, CommercialCustomer>(Lifestyle.Scoped);

And then, when a Customer is Loaded and Authenticated, then directed to a controller that needs ICustomerProcess, Simple Injector will pass in the appropriate class, RetailCustomer or CommercialCustomer

What I can't see from the Simple Injector documentation is how this achieved. So is it even possible (and if so, can someone explain how as my knowledge of Simple Injector is limited and right now I keep going round in circles!

Steven
  • 166,672
  • 24
  • 332
  • 435
Matt
  • 1,596
  • 2
  • 18
  • 32
  • Is it the Contextual Injection you seek? http://simpleinjector.readthedocs.io/en/latest/advanced.html?highlight=conditional#context-based-injection Otherwise, what determines the condition for the injected dependency? Would a factory help in this scenario? – Andez Mar 07 '18 at 12:42
  • I am a little sketchy on SimplieInjector so I'd need to read it up. The condition for the injected dependency would be a parameter on a loaded CustoemrProfile, though as a CustomerProfile is not Loaded until we are in a controller, it may be that I am barking up the wrong tree, and I just need to go back to creating a CustomerProcessFactory, and invoking it whenever I need to vary the process. – Matt Mar 07 '18 at 12:48
  • 1
    See [this answer](https://stackoverflow.com/a/32415954). – NightOwl888 Mar 07 '18 at 12:55
  • I'll see if I can figure out what its doing, hopefully unity and SimpleInjector are similar enough – Matt Mar 07 '18 at 14:38
  • 1. Do all users of all customers use the same web application instance, or do you have a web application per customer? In other words, do you need to switch between `ICustomerProcess` implementations per request, or do you have one used implementation per running application instance? 2. How do you determine to which customer a user belongs? – Steven Mar 07 '18 at 15:24
  • If I understand you correctly, then all customers use the same website, so we would need to identify which customer type was involved in each request. The more I think about this the less inclined I am that DI is the way to go. – Matt Mar 07 '18 at 15:33

1 Answers1

1

As I see it, the Proxy pattern is the solution to your problem, since:

  • You don't want the consumer (the controller) to know anything about the existence of multiple implementations.
  • You don't want to introduce an additional interface, like ICustomerProcessStrategy, ICustomerProcessFactory or something similar.

The proxy pattern can help, because it allows creating an implementation of the same abstraction (ICustomerProcess) and decide at runtime to which implementation it should forward the call.

Such CustomerProcessProxy might look as follows:

public class CustomerProcessProxy : ICustomerProcess
{
    private readonly ICustomerProcess retail;
    private readonly ICustomerProcess commercial;
    private readonly ICustomerContext context;

    // This constructor requires the two original implementations, and an ICustomerContext.
    // The ICustomerContext allows requesting information about 
    public CustomerProcessProxy(
        RetailCustomer retail, CommercialCustomer commercial, ICustomerContext context)
    {
        this.retail = retail;
        this.commercial = commercial;

        // Important note: in the constructor you should ONLY store incoming dependencies,
        // but never use them. That's why we won't call context.IsRetailCustomer here.
        this.context = context;
    }

    // ICustomerProcess methods
    // Each method will request the ICustomerProcess and forward the call to the
    // returned implementation.
    public object DoSomething(object input) => GetProcess().DoSomething(input);

    // Allows returning the correct ICustomerProcess based on runtime conditions.
    private ICustomerProcess GetProcess()
        => this.context.IsRetailCustomer ? this.retail : this.commercial;
}

Now all you'll have to do is register your CustomerProcessProxy and ICustomerContext implementation and you're done.

container.Register<ICustomerProcess, CustomerProcessProxy>();
container.Register<ICustomerContext, AspNetCustomerContext>();

Obviously you will have to implement an ICustomerContext and how to do this depends on how you would retrieve information about the customer, but I can imagine an implementation for ASP.NET that uses the Session to store whether or not the user is a retail customer. Such implementation might look as follows:

public class AspNetCustomerContext : ICustomerContext
{
    public bool IsRetailCustomer => HttpContext.Current.Session["IsRetail"] != null;
}

That's all you need. Now when a controller calls DoSomething on the injected ICustomerProcess, it ends up calling CustomerProcessProxy, which will dispatch the call to either RetailCustomer or CommercialCustomer.

Steven
  • 166,672
  • 24
  • 332
  • 435
  • Thanks for the detailed and comprehensive answer! I will try this early next week and feed back, but it looks like the way forwards – Matt Mar 09 '18 at 11:46