views:

167

answers:

3

I have a search form that can search in different provider. I started out by having a base controller

public SearchController : Controller
{

    protected readonly ISearchService _searchService

    public SearchController(ISearchService searchService)
    {
        _searchService= searchService;
    }

    public ActionResult Search(...)
    {
        // Use searchService to query and return a view.
    }

}

And child controllers

TwitterController : SearchController
{
    ...
}

NewsController : SearchController
{
    ...
}

I use StructureMap to insert all my dependencies in the controller. With this setup, I was able to change the SearchService depending on the type of the controller being instanciated.

x.For<ISearchService>().ConditionallyUse(o =>
      {
            o.TheDefault.Is.OfConcreteType<NewsSearchService>();

            o.If(c => c.ParentType == typeof(TwitterController))
             .ThenIt.Is.OfConcreteType<TwitterSearchService>();

             ...

      });

That even allowed me to set different Views for each controller, (just putting the corresponding folder (Twitter, News...) and the Parent controller is still handling all the Search, with a simple

return View(results) 

which is displaying the correct view specific to twitter, news, or other

Now that was cool and looked great, I a single form and the different views are displayed in tabs on the same page. That's where it starts to get complicated with this approach. The form has to post to /Twitter to search in twitter, to /News to search in news... which means I should change the action parameter of the form depending on which tab I am and display the correct tab on when the form returns depending on.. the url? craziness follows.

If you have built something like this already or know what's the best approach to this, please advices are welcome.

Now I think I would have less pain using a parameter in the form and posting to a single controller. I am thinking of injecting the correct SearchService depending on this parameter. What would be the best approach? I thought of using a model binder,

So I would have my ActionMethod that look like this:

public ActionResult Search(ISearchService service, Query query)
{
    var results = service.Find(query);
}

But I think would need to make a call like this in the ModelBinder

ObjectFactory.GetInstance(...);

Based on the querystring parameter that describe which provider to use, and that doesn't seem more elegant to me. I feel stuck, help :(.

+4  A: 

Whenever you need to vary a dependency based on a run-time value, Abstract Factory is the general solution.

Instead of injecting ISearchService into your Controllers, inject an ISearchServiceFactory:

public SearchController : Controller 
{ 
    private readonly ISearchServiceFactory searchServiceFactory;

    public SearchController(ISearchServiceFactory searchServiceFactory) 
    { 
        if (searchServiceFactory == null)
        {
            throw new ArgumentNullException("searchServiceFactory");
        }

        this.searchServiceFactory = searchServiceFactory; 
    } 

    public ActionResult Search(...) 
    { 
        // Use searchServiceFactory to create an ISearchService based on
        // run-time values, and use it to query and return a view. 
    } 
} 

It is not entirely clear to me which run-time value you need to vary on, but assuming that it's the Query, ISearchServiceFactory might be defined like this:

public interface ISearchServiceFactory
{
    ISearchService Create(Query query);
}
Mark Seemann
Erf, Actually That doesn't really fit well. I would have a number of factories because my SearchService has more dependencies that were resolved by structure map as well. I would lose all the benefit by using this factory and I would end up doing manually all the work that structuremap is doing for me..
Stephane
see my other suggestion. What do you think?
Stephane
Is that really the way I should go? that would just remove all the use of my IOC... and I would wire up all my compnents manually based on a parameter in the abstract factory?
Stephane
Using an Abstract Factory doesn't preclude use of a DI Container. You can inject all the fixed dependencies into the factory implementation instead. See this list of various uses of Abstract Factory related to DI: http://stackoverflow.com/questions/2280170/why-do-we-need-abstract-factory-design-pattern/2280289#2280289
Mark Seemann
Thanks for the link, the solution I implemented works perfectly and solved all my problems (see accepted answer). I use the abstract factory pattern, but I inject the ioc container into the factory.this way, the behaviour of my factory is also defined by the container. the factory is 5 lines of code.I still have one single place to change how my components are wired up. I love it :)
Stephane
A: 

This is more an extensive comment than an answer to explain why an AbstractFactory seems complicated. Here is something that looks closer from the reality:

class StatServiceFactory : IStatServiceFactory
{
    public IStatService Create(string provider)
    {
        switch(provider)
        {
            case "blog":
                return new  StatService(IFacetRepository,ISearchManager,IConfigManager,BooleanQueryBuilder);
                           //How to resolve the Config, the SearchManager, and BooleanQueryBuilder?   
                           //Add more abstract factories? It starts to get messy in my opinion...
        }
    }
}

The FacetRepository is the same for any provider, but the SearchManager changes, the ConfigManager changes, and the BooleanQueryBuilder is an abstract class with different implementation for different provider (because every API doesnt use the same keyword for their queries) All those dependencies are currently resolved by structuremap, based on the type of the controller.

I would really like to keep the benefit of StructureMap here, rather than using factories all the way, for each different pieces.'

Please see my edit at the end of my question for another suggestion to my problem.

Stephane
+1  A: 

I was trying to figure out how to use the abstract factory pattern and still let structuremap resolve all the dependencies of my components.

I believe that is the way I am going to implement it, but I submit this here to get some feedback if someone would read this.

As explain in the previous answer, I do not want to build the whole object graph depending on which provider I need in the Abstract factory.

ie :

class StatServiceFactory : IStatServiceFactory
{
    public IStatService Create(string provider)
    {
        switch(provider)
        {
            case "blog":
                return new  StatService(IFacetRepository,ISearchManager,IConfigManager,BooleanQueryBuilder);
                       //How to resolve the Config, the SearchManager, and BooleanQueryBuilder?   
                       //Add more abstract factories? It starts to get messy in my opinion...
         }
    }

}

What I can do is have the abstract factory use my container to create an instance of my search managers depending on a parameter (coming from the querystring in my case)

Structuremap allows to create named instances this way :

x.For<ISearchManager>().Use<AbcSearchManager>().Named("Abc");
x.For<ISearchManager>().Use<DefSearchManager>().Named("Def");

I need a way to inject the container in my Abstract factory. I would probably wrap the container in a wrapper defined like this. That would keep me from leaking Structuremap into my project. I dont need more that those 2 features within the abstract factory anyway, but it is not necessary:

public interface IContainerWrapper
{
    object GetInstance<T>();
    object GetNamedInstance<T>(string key);
}

and the implementation :

public class ContainerImpl : IContainerWrapper
{
     private readonly Container _container
     public ContainerImpl(Container container)
     {
          _container = container;
     }

     ...
}

And setup StructureMap to resolve dependencies to my abstract factory like that :

x.For<IContainer>.Use(new ContainerImpl(this));
x.For<IFactory>.Use<Factory>()

My factory would be then much simpler and would create my instance like that :

public class SearchmanagerFactory
{
    private readonly IContainerWrapper _container;

    public SearchmanagerFactory(IContainerProvider containerProvider)
    {
        _container = containerProvider;
    }

    public ISearchManager Create(string provider)
    {
       //eed to handle the bad input for provider.
        return (ISearchManager)
            _container.Resolve<ISearchManager>(provider);
    }
}

That seems pretty clean this way :). Thoughts?

Stephane