views:

121

answers:

3

Hi there,

I have an interface to resolve and one of the mapped object's dependencies has a property on it which I would like to set with a value that I only have available when I resolve the top level object.

There's no valid default value for the property. If its not set it should be null and it should only be set if the value that I have available at resolve time is not null.

Is this conditional property injection possible?

I tried this...

container.RegisterType<ProductInstanceValidatorBase, CartItemPurchaseTypeValidator>("CartItemPurchaseTypeValidator", new InjectionProperty("AccountEntity", null);

... but it said I couldn't use a null value!

I also tried this on the resolve...

container.Resolve<ProductInstanceValidatorBase>(new PropertyOverride("AccountEntity", value));

...but this throws an exception when the value is null. It says,

Parameter type inference does not work for null values. Indicate the parameter type explicitly using a properly configured instance of the InjectionParameter or InjectionParameter classes. Parameter name: parameterValue

Basically I'm looking to register a property that is only set with an override and then only if the override value is non-null. Any ideas? Surely from a semantic point of view, property injection should be optional.

Cheers, Ian.

+2  A: 

One potential fix to this problem is to implement a Null Object Pattern and pass Account.Empty when you haven't got a valid account. Here is what a class could look like:

public class Account {
   public static readonly Account Empty = new Account();
}

//at resolution time
Account account = null;
if (HasAccount) 
  account = GetAccount();
else 
  account = Account.Empty;

container.Resolve<ProductInstanceValidatorBase>(new PropertyOverride("AccountEntity", account));

This way the account is never null and Entity framework wouldn't complain.

However I sense that you might have a larger problem with the design of the system. IoC container allows a more loosely coupled system, where the wiring of the components is defined before the program is run, not while it's run. Judging by the name, AccountEnyity is an entity class, not a service class, and you normally do not register entities with the IoC container, only services.

In the light of the above I would suggest that AccountEntity shouldn't be an injectable property, but instead a normal property. You then externalize the ProductInstanceValidatorBase creation into a factory and let it take care of setting AccountEntity property

public interface IProductInstanceValidatorFactory{
  ProductInstanceValidatorBase Create(Account account);
}
public class ProductInstanceValidatorFactory : IProductInstanceValidatorFactory{
  public ProductInstanceValidatorBase Create(Account account){
     var validator = new ProductInstanceValidator();
     validator.AccountEnity = account;
     return validator;
  }
}

You don't even need to register ProductInstanceValidatorBase with Unity, but instead register the factory. This is how using the factory would look like:

Account account = null;
container.Resolve<IProductInstanceValidatorFactory>().Create(account);
Igor Zevaka
Hi Igor, thanks for the reply. I avoided the type inference problem by wrapping the overriding account value with a typed InjectionParameter and I used a null object that could be checked for in the setter.I think you're right about there being a problem with the design. The top level object actually takes an array of ProductInstanceValidatorBase's and only one concrete type of these has an account parameter so I came to the conclusion that this was a leaky abstraction. I may try and put the account reference on a sub-type of the product instance's being validated.
Ian Warburton
If the constructor of a custom factory (not using IoC) that creates an array of these validators requires a long list of dependencies in order to satisfy the dependencies of all the different validators in the resulting array then how does one stop the factory's constructor parameter list from being really long?
Ian Warburton
A: 

I would tend to agree with Igor. However, if you absolutely must resolve the Entity, why not just take your IoC container as a constructor dependency and then attempt to Resolve in the constructor?

arootbeer
Why the down vote?
arootbeer
Wasn't me! But perhaps because doing your suggestion would pollute one's code with the Service Locator anti-pattern?
Ian Warburton
That's basically what I figured - I tried to soften that up by keeping it in the context of the current design (which we apparently all agree was somewhat flawed). I guess making something differently flawed is probably worth the downvote :)
arootbeer
A: 

You can use the InjectionParameter with null values, but you must specify the type. For your two scenarios I am assuming that your AccountEntity parameter is of type or derived from IAccount:

container.RegisterType<ProductInstanceValidatorBase, CartItemPurchaseTypeValidator>("CartItemPurchaseTypeValidator", new InjectionProperty<IAccount>(null);

Note that I have dropped the name as it is not specifically needed in the above scenario but it is needed for the scenario below.

container.Resolve<ProductInstanceValidatorBase>(new PropertyOverride("AccountEntity", new InjectionProperty<IAccount>(null)));
Robert MacLean