2

I have multiple, multilingual "websites" running under a single web application. Each "locale" has its own Solr core (with the same fields across all locales). I'm using SolrNet to perform my search queries.

In order to switch cores on a per-request basis, I am registering a named instance of ISolrOperations<SearchResult> in my Autofac container for each locale (for those unfamiliar with SolrNet, this is effectively my entry point into the library for query purposes).

I then have an ISearchService interface, with a concrete SolrSearchService implementation, whose constructor signature looks like this:

public SolrSearchService(ISolrOperations<SearchResult> solr)

In order to dynamically switch cores on each request, my controller takes an (injected) ISearchServiceFactory instead of simply an ISearchService. My SolrSearchServiceFactory looks like this:

public class SolrSearchServiceFactory : ISearchServiceFactory
{
    private readonly IComponentContext _ctx;

    public SolrSearchServiceFactory(IComponentContext ctx)
    {
        _ctx = ctx;
    }

    public ISearchService Create(string id)
    {
        var result = _ctx.ResolveNamed<ISolrOperations<SearchResult>>(id);
        return new SolrSearchService(result);
    }
}

id is simply the locale identifier. This is the best I've managed to do in order to decouple Autofac from my services/controllers but maintain the ability to switch cores per request (based on logic performed in the controller).

I'm still not very happy with this, however, as the factory itself is still coupled to Autofac. Is there a way around this (either from a SolrNet or Autofac perspective)?

I have looked at using Autofac's factory delegates but there doesn't seem to be a way to apply them in this instance.

Ant P
  • 24,820
  • 5
  • 68
  • 105
  • 1
    Do you absolutely have to have named instances? – DavidN Feb 05 '14 at 21:05
  • @DavidN Not necessarily named instances, but I *do* need a way of switching instances of the same type (with the same type argument). I've been down the rabbithole of trying to switch the endpoint of a single SolrNet connection and I'm not going back! Essentially the actual endpoint URL is injected into a dependency of a dependency of the `ISolrOperations` and the library itself is very heavily bootstrapped, so it's not exactly easy to construct on each request outside of the provided DI modules. – Ant P Feb 05 '14 at 21:07

2 Answers2

1

You can use keyed/named services and IIndex to do it, there's a good entry on the autofac page here.

Here's a quick example of what it'd look like:

    public class Factory
    {
        private readonly IIndex<string, ISearchService> _index;

        public Factory(IIndex<string, ISearchService> index)
        {
            _index = index;
        }

        public ISearchService Resolve(string id)
        {
            return _index[id];
        }
    }

        // Registrations
        var builder = new ContainerBuilder();
        builder.Register<ASearchService>(c => new ASearchService()).Keyed<ISearchService>("A");
        builder.Register<BSearchService>(c => new BSearchService()).Keyed<ISearchService>("B");
        builder.Register<Factory>(c => new Factory(c.Resolve<IIndex<string, ISearchService>>()));
        // as per autofac's page linked above, autofac automatically implements IIndex types

        // Usage

        var aSearchService = fact.Resolve("A");

EDIT: I thought we could do better than this, so here are a couple of extension methods to obviate the creation of an explicit factory class:

    public delegate TReturn AutoFactoryDelegate<TParam, TReturn>(TParam param);

    public class AutoFactory<TParam, TReturn>
    {
        private readonly IIndex<TParam, TReturn> _index;

        public AutoFactory(IIndex<TParam, TReturn> index)
        {
            _index = index;
        }

        public TReturn Resolve(TParam param)
        {
            return _index[param];
        }
    }

    public delegate TReturn AutoFactoryDelegate<TParam, TReturn>(TParam param);

    public static class AutofacExtensions
    {
        public static void RegisterAutoFactoryDelegate<TParam, TReturn>(this ContainerBuilder builder)
        {
            builder.Register(c => new AutoFactory<TParam, TReturn>(c.Resolve<IIndex<TParam, TReturn>>()));

            builder.Register<AutoFactoryDelegate<TParam, TReturn>>(c =>
               {
                 var fact = c.Resolve<AutoFactory<TParam, TReturn>>();
                 return fact.Resolve;
               });
        }
    }

// Registration
var builder = new ContainerBuilder();
builder.Register<ASearchService>(c => new ASearchService()).Keyed<ISearchService>("A");
builder.Register<BSearchService>(c => new BSearchService()).Keyed<ISearchService>("B");
builder.RegisterAutoFactoryDelegate<string, ISearchService>();

// Usage
// fact is an AutoFactoryDelegate<string, ISearchService>
var aSearchService = fact("A");

EDIT 2: After looking at this again, we don't need the Factory classes, since IIndex is in effect the factory already. This leads to quite a simplification from the implementation side:

public static void RegisterAutoFactoryDelegate<TParam, TReturn>(this ContainerBuilder builder)
{
    builder.Register(c =>
                        {
                            var iindex = c.Resolve<IIndex<TParam, TReturn>>();
                            return (Func<TParam, TReturn>) (id => iindex[id]);
                        });
}

// Registration
var builder = new ContainerBuilder();
builder.Register<ASearchService>(c => new ASearchService()).Keyed<ISearchService>("A");
builder.Register<BSearchService>(c => new BSearchService()).Keyed<ISearchService>("B");
builder.RegisterAutoFactoryDelegate<string, ISearchService>();

// Usage
// fact is an Func<string, ISearchService>
var aSearchService = fact("A");
DavidN
  • 5,117
  • 2
  • 20
  • 15
  • This is along the lines I was thinking with the auto-factories and may well be the missing link - I'll take a look tomorrow and get back to you. Thanks! – Ant P Feb 05 '14 at 21:32
  • Not *quite* what I ended up doing, but pointed me in the right direction. I registered a `Func` that just calls `c.ResolveNamed>(id)` and instantiates a new instance of `SolrSearchService`. Injected this into my existing factory so my controller doesn't take a Func (which I think is a tad ugly). – Ant P Feb 06 '14 at 18:13
  • If I understand your solution correctly, then you have a Func which closes on c, which in autofac is a no-no. Did you get around that somehow? – DavidN Feb 06 '14 at 22:21
  • I guess I did have a closure on `c`, yes. I didn't really want to amend the Autofac module that comes with SolrNet to add an index of `ISearchService` instances. Since it seems there's no way around that, I've now gone with it and produced the following: – Ant P Feb 06 '14 at 23:17
  • `builder.Register( c => { var index = c.Resolve>>(); return (Func)(id => new SolrSearchService(index[id])); });` – Ant P Feb 06 '14 at 23:18
  • Scratch that - turns out that named services can be resolved as `IIndex` so there's no need for the explicit key as the default SolrNet module names the services anyway. – Ant P Feb 06 '14 at 23:27
  • So if I understand your last comment correctly, you end up injecting an IIndex into your factory class? – DavidN Feb 07 '14 at 08:54
  • Nope, doing exactly as per my last comment and injecting the `Func` into the factory, just not using `Keyed` explicitly when registering the ISolrOperations as using `Named` already produces the index. – Ant P Feb 07 '14 at 09:40
  • Ah so pretty much exactly what my extension method in my last edit is doing. – DavidN Feb 07 '14 at 10:14
  • Pretty much - that's where I got the code, after all :) – Ant P Feb 07 '14 at 10:18
1

I have not used Autofac, but based on how Windsor does it and what I just found you should be able to do something like this

builder.Register(c => 
    new SolrSearchServiceFactory(HttpContext.Current.GetIdFromUrlOrSomething)
    )
    .As<ISearchServiceFactory>()
    .InstancePerHttpRequest();

If you can get the Id from the http context then autofac should wire the factory up for each request.

/Michael

Michael Skarum
  • 2,448
  • 3
  • 18
  • 20