programmatically change a dependency in Castle Windsor
Asked Answered
U

2

8

I have a class that calls out to an internet service to get some data:

public class MarketingService
{
    private IDataProvider _provider;
    public MarketingService(IDataProvider provider)
    {
        _provider = provider;
    }

    public string GetData(int id)
    {
        return _provider.Get(id);
    }
}

Currently I have two providers: HttpDataProvider and FileDataProvider. Normally I will wire up to the HttpDataProvider but if the external web service fails, I'd like to change the system to bind to the FileDataProvider . Something like:

public string GetData(int id)
{
    string result = "";

    try
    {
        result = GetData(id); // call to HttpDataProvider
    }
    catch (Exception)
    {
        // change the Windsor binding so that all future calls go automatically to the
        // FileDataProvier
        // And while I'm at it, retry against the FileDataProvider    
    }

    return result;
}

So when this has been executed all future instances of MarketingService will automatically be wired up to the FileDataProvider. How to change a Windsor binding on the fly?

Underground answered 29/4, 2013 at 19:27 Comment(0)
T
8

One solution would be using selector

public class ForcedImplementationSelector<TService> : IHandlerSelector
{
    private static Dictionary<Type, Type>  _forcedImplementation = new Dictionary<Type, Type>();

    public static void ForceTo<T>() where T: TService
    {
        _forcedImplementation[typeof(TService)] = typeof(T);
    }

    public static void ClearForce()
    {
        _forcedImplementation[typeof(TService)] = null;
    }

    public bool HasOpinionAbout(string key, Type service)
    {
        return service == typeof (TService);
    }

    public IHandler SelectHandler(string key, Type service, IHandler[] handlers)
    {
        var tService = typeof(TService);
        if (_forcedImplementation.ContainsKey(tService) && _forcedImplementation[tService] != null)
        {
            return handlers.FirstOrDefault(handler => handler.ComponentModel.Implementation == _forcedImplementation[tService]);
        }

        // return default
        return handlers[0];
    }
}

Test and usage

[TestFixture]
public class Test
{
    [Test]
    public void ForceImplementation()
    {
        var container = new WindsorContainer();

        container.Register(Component.For<IFoo>().ImplementedBy<Foo>());
        container.Register(Component.For<IFoo>().ImplementedBy<Bar>());

        container.Kernel.AddHandlerSelector(new ForcedImplementationSelector<IFoo>());

        var i = container.Resolve<IFoo>();
        Assert.AreEqual(typeof(Foo), i.GetType());

        ForcedImplementationSelector<IFoo>.ForceTo<Bar>();

        i = container.Resolve<IFoo>();
        Assert.AreEqual(typeof(Bar), i.GetType());


        ForcedImplementationSelector<IFoo>.ClearForce();

        i = container.Resolve<IFoo>();
        Assert.AreEqual(typeof(Foo), i.GetType());
    }
}
Thickhead answered 29/4, 2013 at 20:50 Comment(1)
We have used this implementation with success, however our top consuming threads were working with the dictionary so we changed it to a ConcurrentDictionary to make it thread safe as recommended here: blogs.msdn.microsoft.com/tess/2009/12/21/…Josuejosy
C
1

Alternatively you could create a proxy:

public class AutoSelectingDataProvider : IDataProvider 
{
    public AutoSelectingDataPovider(HttpDataProvider httpDataProvider, FallBackDataProvider fallBackProvider)
    {
        _httpDataProvider = httpDataProvider;
        _fallBackDataProvider = fallBackDataProvider;
    }


    public string GetData(int id)
    {
        try
        {
            return _httpDataProvider.GetData(id);
        }
        catch (Exception)
        {
            return _fallBackDataProvider.GetData(id);
        }
    return result;
    }
}


container.Register(
    Component.For<HttpDataProvider>(),
    Component.For<FallBackDataProvider>(),
    Component.For<IDataProvider>().ImplementedBy<FallBackDataProvider>());

This will always first try to get data from the HttpDataProvider if not succesfull use the fallback. If you want you can introduce state and after a failure always use the fallback. This way you can keep using the IDataProvider in your application without needing to obtain a new one from the container.

Crud answered 1/5, 2013 at 15:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.