Question

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.

Was it helpful?

Solution

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");

OTHER TIPS

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

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top