I am looking for suggestions as to the best way to design objects for IoC
Suppose I have an object (Service) that has a dependency to a DataContext which is registered with Ioc.
But it also requires a name property, i could design the object like this:
class Service
{
public Service(IDataContext dataContext,
string name)
{
this._dataContext = dataContext;
this._name = name
}
public string Name
{
get
{
return _name;
}
}
}
The problem is it becomes very complicated to use with Ioc containers as a string object such as name is not easy to register and the usage becomes complicated with the Ioc container: So resolution becomes confusing:
var service = Ioc.Resolve<Service>( ?? )
Another approach is to design it as follows:
class Service
{
public Service(IDataContext dataContext)
{
this._dataContext = dataContext;
}
public string Name { get; set; }
}
The resolution is now easier:
var service = Ioc.Resolve<Service>();
service.Name = "Some name";
The only downsite is specifying the name is no longer required. I would like to hear from DI or IoC experts how they would go about designing this and still stay fairly agnostic to the concrete Ioc container technology.
I know that a lot depends on how you want to use this, option 2 would be perfect if name really was optional. But in the case where name is required you could add the validation step at another point in code, but rather go for the design to make Ioc simpler.
Thoughts?
Dependency Injection is the method of providing the dependencies and Inversion of Control is the end result of Dependency Injection. IoC is a design principle where the control flow of the program is inverted.
The IoC container that is also known as a DI Container is a framework for implementing automatic dependency injection very effectively. It manages the complete object creation and its lifetime, as well as it also injects the dependencies into the classes.
Your choice of DI Container shouldn't dictate the design of your API. If name
isn't optional, it should be part of the constructor's signature (thus making it mandatory).
The next question then becomes how to configure the container without incurring tons of overhead. How to do that depends on the container. Here's how to implement a convention around a string argument in Castle Windsor:
public class NameConvention : ISubDependencyResolver
{
public bool CanResolve(CreationContext context,
ISubDependencyResolver contextHandlerResolver,
ComponentModel model, DependencyModel dependency)
{
return dependency.TargetType == typeof(string)
&& dependency.DependencyKey == "name";
}
public object Resolve(CreationContext context,
ISubDependencyResolver contextHandlerResolver,
ComponentModel model, DependencyModel dependency)
{
return "foo"; // use whatever value you'd like,
// or derive it from the provided models
}
}
Then register the NameConvention
with the container like this:
container.Kernel.Resolver.AddSubResolver(new NameConvention());
If your container doesn't have the appropriate extensibility points, pick a container that does.
The problem is it becomes very complicated to use with Ioc containers as a string object such as name is not easy to register and the usage becomes complicated with the Ioc container
Most good IoC containers will provide easy ways to supply constructor arguments when you do your configuration.
Your first example—constructor injection—is usually considered the preferred way. Think of your constructor as a contract to be followed, which, once satisfied, renders a valid object.
Your second code sample—property injection—is usually considered less preferable to constructor injection. Either way though, IoC containers will usually give you the ability to provide values for constructor parameters or properties at configuration, which will be supplied each time you ask your IoC to create you that object.
I'm not sure which IoC container you're looking to use, but here's a sample of code used to configure StructureMap, and provide string values for various services. Unless I'm misreading your question, this seems to be what you're looking to do.
ObjectFactory.Initialize(x =>
{
x.For<ICatalogAdminService>().Use<DLinkCatalogAdminService>()
.Ctor<string>("catalogConnectionString").Is(ConfigurationManager.ConnectionStrings["myCompany_AdminConnectionString"].ConnectionString)
.Ctor<string>("contentConnectionString").Is(ConfigurationManager.ConnectionStrings["myCompany_AdminConnectionString"].ConnectionString)
.Ctor<string>("webCatalogConnectionString").Is(ConfigurationManager.ConnectionStrings["myCompany_WebConnectionString"].ConnectionString)
.Ctor<string>("dlinkPromotionAdminConnectionString").Is(ConfigurationManager.ConnectionStrings["myCompany_AdminConnectionString"].ConnectionString);
x.For<IContentManagementAdminService>().Use<DLinkContentManagementAdminService>()
.Ctor<string>("contentConnectionString").Is(ConfigurationManager.ConnectionStrings["myCompany_AdminConnectionString"].ConnectionString)
.Ctor<string>("webCatalogConnectionString").Is(ConfigurationManager.ConnectionStrings["myCompany_WebConnectionString"].ConnectionString)
.Ctor<string>("dlinkPromotionConnectionString").Is(ConfigurationManager.ConnectionStrings["myCompany_AdminConnectionString"].ConnectionString);
x.For<IPromotionAdminService>().Use<DLinkPromotionAdminService>()
.Ctor<string>("catalogConnectionString").Is(ConfigurationManager.ConnectionStrings["myCompany_AdminConnectionString"].ConnectionString)
.Ctor<string>("promotionConnectionString").Is(ConfigurationManager.ConnectionStrings["myCompany_AdminConnectionString"].ConnectionString);
x.For<ISearchService>().Use<Extractor>();
x.For<IImporter>().Use<Importer>();
x.For<IOrderAdminService>().Use<DLinkOrderAdminService>()
.Ctor<string>("contentConnectionString").Is(ConfigurationManager.ConnectionStrings["myCompany_AdminConnectionString"].ConnectionString)
.Ctor<string>("orderConnectionString").Is(ConfigurationManager.ConnectionStrings["myCompany_OrdersConnectionString"].ConnectionString);
});
EDIT
Answering the comment, if you wanted to actually supply a constructor argument manually, it would look like this:
ObjectFactory.GetInstance<ICatalogAdminService>(new ExplicitArguments(
new Dictionary<string, object>() { { "parameter1", "someValue" } }));
Obviously this can get ugly fast, so you may want to whip up some factory/helper methods if you find yourself doing this often.
The approach I usually do in such situation is injecting settings object instead of string and then ask in constructor for property representing that string. Or in some cases even better whenever I need that string property I took it out of that settings, so that it can be changed (useful if it's really a program setting).
The other option is to use something like binding annotation. I don't know which dependency injection framework you are using, but here is how it can be done in guice (java) framework, which I am currently working with.
If you are using Castle Windsor you can use typed factories, which you can read about here. Essentially, typed factories allow you to create an interface that looks like this.
public interface IServiceFactory
{
IService Create(string name);
}
Injecting this and calling Create()
with the name of your choice Windsor will return a constructed IService
implementation.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With