Catalin
Catalin

Reputation: 11731

Ninject: injecting inside extension methods

I have an extension class for a Product object.

Is there any way of injecting the service classes i need without passing them as method parameters?

public static class ProductExtensionMethods
{
    public static void CalculatePrice(this Product product, 
        ICalculateProductPriceService _calculatePriceService, 
        IUnitOfWork _unitOfWork)
    {
        // Can I inject the unitOfWork and Service without 
        // passing them as parameters?

        product.Price = _calculatePriceService.Calculate();
        _unitOfWork.Commit();
    }
}

Upvotes: 1

Views: 2474

Answers (2)

Steven
Steven

Reputation: 172835

It seems as if you are implementing use cases inside extension methods. I see no benifits for doing this over creating normal classes. So instead of using an extension method, simply make this method non-static, wrap it in an non-static class, and inject the dependencies through the constructor.

This could look like this:

public class CalculateProductPriceUseCase
{
    private ICalculateProductPriceService _calculatePriceService;
    private IUnitOfWork _unitOfWork;

    public CalculateProductPriceUseCase(
        ICalculateProductPriceService calculatePriceService,
        IUnitOfWork unitOfWork)
    {
        _calculatePriceService = _calculatePriceService;
        _unitOfWork = unitOfWork;
    }

    public void Handle(Product product)
    {
        product.Price = _calculatePriceService.Calculate();

        _unitOfWork.Commit();
    }
}

What still bothers me about this solution however is the Commit call. Why should the use case itself have to call commit. That seems like an infrastructure thing to me and it is easy to forget to implement this. Especially since the class doens't create the unitOfWork itself, but gets it from the outside.

Instead you can wrap this use case in a decorator that does this for you, but... if you do that, you'll need to add a proper abstraction so you can wrap all your use cases in such decorator. It could look like this:

public class CalculateProductPrice
{
    public int ProductId { get; set; }
}

public class CalculateProductPriceUseCaseHandler
    : IUseCase<CalculateProductPrice>
{
    private ICalculateProductPriceService _calculatePriceService;
    private IUnitOfWork _unitOfWork;

    public CalculateProductPriceUseCaseHandler(
        ICalculateProductPriceService calculatePriceService,
        IUnitOfWork unitOfWork)
    {
        _calculatePriceService = _calculatePriceService;
        _unitOfWork = unitOfWork;
    }

    public void Handle(CalculateProductPrice useCase)
    {
        var product = _unitOfWork.Products.GetById(useCase.ProductId);

        product.Price = _calculatePriceService.Calculate();
    }
}

Consumers can now depend on an IUseCase<CalculateProductPrice> and you can give them either an CalculateProductPriceUseCaseHandler instance, or wrap that instance in a decorator. Here's an example of such decorator:

public class TransactionUseCaseDecorator<T> : IUseCase<T>
{
    private IUnitOfWork _unitOfWork;
    private IUseCase<T> _decoratedInstance;

    public TransactionUseCaseDecorator(IUnitOfWork unitOfWork,
        IUseCase<T> decoratedInstance)
    {
        _unitOfWork = unitOfWork;
        _decoratedInstance = decoratedInstance;
    }

    public void Handle(T useCase)
    {
        // Example: start a new transaction scope 
        // (or sql transaction or what ever)
        using (new TransactionScope())
        {
            _decoratedInstance.Handle(useCase);

            // Commit the unit of work.
            _unitOfWork.Commit();
        }
    }
}

Now you have one single generic decorator that can be wrapped around any IUseCase<T> implementation.

Upvotes: 3

Stephen Byrne
Stephen Byrne

Reputation: 7505

You can't do this with any DI container afaik because although you can define a constructor on a static class, that constructor must be parameterless.

The only way I could see this working for you would be to either

  • define CalculatePrice on instances of Product but the instance function simply passes through the call to the static method. Inject the dependencies into the instance via constructor and pass them via the static call within the instance call.

  • Create a "helper" class that does the same thing (e.g ProductPriceHelper) and implement a Setup or Initialise method on it taking the dependencies, but you still will not* be able to have DI auto-inject for you - you'll have to do it manually somewhere in your composition rot (i.e wherever you currently do all your Ninject binding).

  • Secret door #3: I would be inclined to rearrange how your price calcuation works; I would humbly suggest that your ICalculateProductPriceService should have a method that accepts a Product instance and performs its magic therein. The ICalculateProductPriceService dependency is then injected into the Product during construction and you call a method Product.GetPrice() which invokes ICalculateProductPriceService.Calculate(this)...if you can't or won't inject the service during ctor (e.g if it is an EF entity, etc) then you can make it a required param dependency on GetPrice.

I realise that having said this someone will no doubt come up with a technically excellent workaround to allow this, but it will always be a hack...

Upvotes: 3

Related Questions