As using Sphinx from a web application is probably the most common use
case, I thought I’d post some guidelines and examples on how to use the
fluent API in an ASP.NET MVC application with regards to setup and
proper handling of IFulltextStore and IFulltextSession. The
documentation already mentions that there should (usually) be one
instance of the FulltextStore per application and one
IFulltextSession per thread/(web-) request. Let’s take a look at a few
different approaches to this:
Using Lazy
This approach makes use of the Lazy<T> class that was introduced
with .NET 4.0. We create a base controller that holds the
IFulltextStore instance which will be initialized upon the first
access. Lazy<T> will make sure that the FulltextStore is created
only once in a thread-safe way.
public abstract class SearchController : Controller
{
private static readonly Lazy<IFulltextStore> Store = new Lazy<IFulltextStore>(() =>
{
IFulltextStore fulltextStore = new FulltextStore().Initialize();
fulltextStore.ConnectionString.IsThis("pooling=true");
return fulltextStore;
});
protected static IFulltextStore FulltextStore
{
get { return Store.Value; }
}
protected IFulltextSession FulltextSession { get; private set; }
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
FulltextSession = FulltextStore.StartSession();
}
protected override void OnActionExecuted(ActionExecutedContext filterContext)
{
if (filterContext.IsChildAction || FulltextSession == null)
return;
using (FulltextSession)
{
if (filterContext.Exception != null)
return;
FulltextSession.FlushChanges();
}
}
}
The IFulltextSession for every request is created in an override of
OnActionExecuting by assigning the result of StartSession to the
FulltextSession property. This way, every controller that inherits from
SearchController automatically gets an open session that is ready for
use. In the override of OnActionExecuted we tell the FulltextSession
to flush all pending changes. The using statement ensures that it is
properly disposed of.
Using an IoC-Container
Following is an example installer for Castle Windsor:
public class SphinxConnectorInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(Component.For<IFulltextStore>().
Instance(new FulltextStore().Initialize()).
LifestyleSingleton(),
Component.For<IFulltextSession>().
UsingFactoryMethod(kernel =>
kernel.Resolve<IFulltextStore>().StartSession()).
LifestylePerWebRequest());
}
}
In this example, we setup Castle Windsor so that it can create both
IFulltextStore and IFulltextSession. If you wanted to create
IFulltextSession yourself (by injecting IFulltextStore into your
classes and calling StartSession), you could remove the corresponding
code from the installer.
We instruct Windsor to use the Singleton lifestyle for
IFulltextStore, which means that Windsor will create one instance per
container*.* In fact, Windsor uses Singleton is the default lifestyle,
but in cases like this I’d like to make that explicit, so that
developers that are not familiar with Windsor immediately see what’s
going on. For IFulltextSession we set LifestylePerWebRequest so
that Windsor will create an instance for each request; it will also
automatically call Dispose at the end of each request, so we don’t
have to worry about that. If you wanted Windsor to also call
FlushChanges, you could do so with the help of Windsor’s OnDestroy
method.
Initialization at Application Startup
Like with the first approach, we create a base controller, this time
with a static property hat holds the IFulltextStore instance. The
instance is initialized in the Global.asax.cs file in
Application_Start:
public abstract class SearchController : Controller
{
public static IFulltextStore FulltextStore { get; set; }
protected IFulltextSession FulltextSession { get; private set; }
//Overrides of OnActionExecuting and OnActionExecuted omitted
}
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
InitFulltextStore();
}
private static void InitFulltextStore()
{
IFulltextStore fulltextStore = new FulltextStore().Initialize();
fulltextStore.ConnectionString.IsThis("pooling=true");
SearchController.FulltextStore = fulltextStore;
}