質問

My application uses the "SignalR" client/server comms framework. If you aren't familiar with it, the server-side app typically contains one or more "hub" classes (similar to asmx web services), each providing methods that can be called by a client. During startup, the client needs to first create a connection, then create a "proxy" for each hub that it will need to talk to, e.g.:-

var hubConnection = new HubConnection("http://...");
var fooHubProxy = hubConnection.CreateHubProxy("FooHub");
var barHubProxy = hubConnection.CreateHubProxy("BarHub");
...etc...

The string parameter passed to CreateHubProxy() is the name of the server-side hub class. The method return type is IHubProxy.

It feels like I should be able to utilise Windsor here, but I'm struggling to find a solution. My first thought was to instantiate the hub proxies and register these instances with Windsor (by name), e.g.

var fooHubProxy = hubConnection.CreateHubProxy("FooHub");
container.Register(Component.For<IHubProxy>().Instance(fooHubProxy).LifestyleSingleton().Named("FooHub"));
...etc...

The problem is that when a class needs a hub proxy, the only way to resolve it by name is to use service locator pattern, which isn't recommended. What other Windsor features (e.g. typed factories, etc.) might be useful here?

Edit

I've just found Windsor's .UsingFactoryMethod, and am wondering if this would work, to simplify hub registration:

container.Register(Component.For<IHubProxy>()
                   .UsingFactoryMethod((kernel, context) => hubConnection.CreateHubProxy("FooHub"))
                   .LifestyleSingleton()
                   .Named("FooHub"));

I guess I still have the problem of how to resolve by name though.

役に立ちましたか?

解決 2

Okay, I think I've found a possible solution, partly using the approach detailed here which shows how it is possible to register Func<>s with Windsor.

First, I register a delegate (Func<>) that uses the container to resolve by name:-

Container.Register(Component.For<Func<string, IHubProxy>>()
                   .Instance(name => Container.Resolve<IHubProxy>(name))
                   .LifestyleSingleton());

Think of this as an IHubProxy "factory".

Next, I register my hub proxies as detailed in my original question:-

container.Register(Component.For<IHubProxy>()
               .UsingFactoryMethod((kernel, context) => hubConnection.CreateHubProxy("FooHub"))
               .LifestyleSingleton()
               .Named("FooHub"));
container.Register(Component.For<IHubProxy>()
               .UsingFactoryMethod((kernel, context) => hubConnection.CreateHubProxy("BarHub"))
               .LifestyleSingleton()
               .Named("BarHub"));

Here is an example of a class that needs instances of the hub proxies:-

public class SomeClass
{
    private IHubProxy _fooHub;
    private IHubProxy _barHub;

    public SomeClass(Func<string, IHubProxy> hubProxyFactory)
    {
        _fooHub = hubProxyFactory("FooHub");
        _barHub = hubProxyFactory("BarHub");
    }
}

Untried so far, but it looks promising. It's a clever solution but injecting the Func<> feels a little hacky, so I would still be keen to hear of other possible solutions to my problem.

他のヒント

Two years later, but I have a more elegant solution for other people that stummble accross this problem too. It is possible to use TypedFactory facility and adapt it to you needs like here. first create the factory interface (only! no need for the actual implementation, castle will take care of that):

public interface IHubProxyFactory
{
   IHubProxy GetProxy(string proxyName);
}

Now we need a class that extend the default typed facotory and retreives the component's name from the input (proxyName):

class NamedTypeFactory : DefaultTypedFactoryComponentSelector
{
    protected override string GetComponentName(MethodInfo method, object[] arguments)
    {
        string componentName = null;
        if (arguments!= null && arguments.Length > 0)
        {
            componentName = arguments[0] as string;
        }

        if (string.IsNullOrEmpty(componentName))
            componentName = base.GetComponentName(method, arguments);

        return componentName;
    }
}

And then register the factory with castle and specify that your NamedTypeFactory will be used:

Component.For<IHubProxyFactory>().AsFactory(new NamedTypeFactory())

Now every class can get the factory interface in its constructor:

public class SomeClass
{
    private IHubProxy _fooHub;
    private IHubProxy _barHub;

    public SomeClass(IHubProxyFactory hubProxyFactory)
    {
        _fooHub = hubProxyFactory.GetProxy("FooHub");
        _barHub = hubProxyFactory.GetProxy("BarHub");
    }
}

I just used a similar method to yours. I use a typed Factory. Advantage is I have type safety for my hubs. Registering the hubs is the same. The rest differs a bit but is technical the same.

IServiceFactory { 
   IHubProxy GetFooHub();
   IHubProxy GetBarHub();
}

And Registration:

Container.AddFacility<TypedFactoryFacility>();
Container.Register(Component.For<IServiceFactory>().AsFactory());

Usage:

public class SomeClass
{
    private IHubProxy _fooHub;
    private IHubProxy _barHub;

    public SomeClass(IServiceFactry hubProxyFactory)
    {
        _fooHub = hubProxyFactory.GetFooHub();
        _barHub = hubProxyFactory.GetBarHub();
    }
}

Btw. Factory.Get"Name"() resolves by name.

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top