IoC with value type and object type dependencies
A

5

8

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?

Archfiend answered 14/11, 2011 at 16:23 Comment(2)
Please specify the container you're using.Aleen
Hi, I'm still evaluating which one will be the best to use but so far i'm leaning towards castle.Archfiend
M
4

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.

Mascia answered 14/11, 2011 at 17:36 Comment(7)
Hi Mark, this looks like it can work and i assume that you call resolve and pass a dictionary with the name and value you like? I dont have that much experience with all the containers. As it is theres about 10 or more so how to choose becomes hard. Also you have to go quite deep into the containers to solve these edge cases, so i was looking for a design which would keep me as agnostic as possible to the concrete framework.Archfiend
This design it totally container-agnostic. However, how you wire up the container (if any) requires a bit of knowledge of the container API. When you call resolve, you pass nothing to the resolve method. Rather, you should use the RRR pattern: blog.ploeh.dk/2010/09/29/TheRegisterResolveReleasePattern.aspxMascia
Hi Mark, I have already read most of your blogs, but back to the problem in your provided Resolver you are always returning foo, i want to resolve and get my name into the name property like "somevalue" i cannot see how to do this using the provided example, if i use the following: var service = container.Resolve<Service>(new Dictionary<string, object>() { { "name", "someValue" } }); it just works, BUT the resolver is totally ignored, so just works and looks the same as structuremap, so looks like this is the most common wayArchfiend
Hi Mark, do you agree that Adams method seems to work better in my particular example, because I tested the dictionary to work with castle, and this would be compatible with structuremap too and i assume other containers?Archfiend
No, I can't agree that a static Service Locator is a better option. If you don't know the value of the name until run-time, then an Abstract Factory is a better option: #1944076Mascia
Hi Mark, Ok i think i see where you are going, so I create an interface i call IServiceFactory or something with a method that takes only a name argument, then register a concrete type that implements it and declare all the dependencies on the factory that might be needed, then the factory constructs the concrete service and supply all the arguments resolved from the container, thereby not needing resolvers/dictionaries or any other special container features?Archfiend
Yes, it's much cleaner that way.Mascia
E
3

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.

Egarton answered 14/11, 2011 at 16:27 Comment(4)
Hi Adam thanks for this example which will be handy if i try to use structuremap. But how would you resolve the object by supplying the argument?Archfiend
To supply an argument you can also use the fluent syntax ObjectFactory.With<string>("paramater1").EqualTo("someValue").GetInstance<ICatalogAdminService>(); structuremap.net/structuremap/RetrievingServices.htm#section5Dionedionis
I like this method as it seems to be the most compatible with most containers, I am thinking I can probably create a builder class that uses fluent API as above to construct a dictionary, then i can create a compatibility layer that works with both castle or structuremap, or another container if i choose.Archfiend
That sounds like it should work. I'm not sure building an abstraction layer over your IoC container is worth the effort though. Just really think through the costs and benefits before going through that trouble. What kind of project is this? Web? Web MVC? WinForms?Egarton
M
2

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.

Mcardle answered 14/11, 2011 at 16:45 Comment(2)
This is another way i thought of which will work well for things like configuration settings. I think you will still run into the specified problem if you need to get arguments into the configuration object?Archfiend
At my current project, settings object is reading configuration from database, so there is no such problem, it could also read them from file. Otherwise you could use some equivalent to binding annotations, your gain here would be that you encapsulate contexless strings into configuration object.Mcardle
A
1

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.

Aleen answered 14/11, 2011 at 16:39 Comment(2)
I like this option, i still need to go and try this out but if i understand it correctly you dont need to provide an implementation and castle will generate one for you?Archfiend
I can't get this to work btw I followed the information on the page you provided but I keep getting an error:Component Castle.TypedFactory.DefaultInterfaceFactoryComponentSelector could not be resolved. Make sure you didn't misspell the name, and that component is registered.Archfiend
E
1

Design it like you always would, keeping good engineering practices in mind (SOLID etc). Then if your container of choice constraints you, you're either not using it properly, or you're using wrong container.

In Windsor's case you can easily provide inline, hardcoded dependencies to components at registration time:

container.Register(Component.For<Foo>().DependsOn(new{name = "Stefan"});

You can also provide more dynamic dependencies or depend on values from your XML config if you need to change them post-compilation.

If the value is optional then make it optional, either by using constructor that specifies a default value for it, by using constructor overloads or as a property. Again, good container will handle all of those cases, if the one you're currently using doesn't perhaps switch to a better one.

Erasmo answered 14/11, 2011 at 21:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.