Search code examples
dependency-injectionravendbconstructor-injection

RavenDB and Constructor Injection


In my project, I have the following PageCache entity, which is being stored in RavenDB:

public class PageCache
{
    private readonly IHtmlDocumentHelper htmlDocumentHelper;

    public string Id { get; set; }
    public string Url { get; set; }

    public PageCache(IHtmlDocumentHelper htmlDocumentHelper, string url)
    {
        this.htmlDocumentHelper = htmlDocumentHelper;
        this.Url = url;
    }
}

I am using Castle Windsor to inject the IHtmlDocumentHelper implementation at runtime. This member is used in methods defined inside the PageCache class, which I stripped from the above snippet for the sake of simplicity.

When I create a PageCache object using the constructor, everything works fine. But elsewhere in my code, I load PageCache objects back from RavenDB:

public PageCache GetByUrl(string url)
{
    using (var session = documentStore.OpenSession())
    {
        return session.Query<PageCache>()
                      .Where(x => x.Url == url)
                      .FirstOrDefault();
    }
}

My issue is that the objects I get back from RavenDB don't have the htmlDocumentHelper member set, rendering the PageCache methods that depend on it unuseable.

In other words: when I load objects back from documents stored in RavenDB, it won't use my constructor to build the objects, thus not initializing the private members through constructor injection.

Am I doing something wrong here? How would you solve such an issue?


I ended up using the solution proposed by Ayende below. The circular dependency issue I mentioned in the comments only appeared when I registered the DocumentStore in Windsor with UsingFactoryMethod(). The issue strangely disappeared when I used Windsor's DependsOn() and OnCreate() to configure and initialize the DocumentStore directly inside the Register().

My container is now being initialized as follows:

WindsorContainer container = new WindsorContainer();

container.Register(
    // Register other classes, such as repositories and services.
    // Stripped for the sake of clarity.
    // ...

    // Register the CustomJsonConverter:
    Component.For<CustomJsonConverter>().ImplementedBy<CustomJsonConverter>(),

    // The following approach resulted in an exception related to the circular
    // dependencies issue:
    Component.For<IDocumentStore>().UsingFactoryMethod(() =>
        Application.InitializeDatabase(container.Resolve<CustomJsonConverter>()))

    // Oddly enough, the following approach worked just fine:
    Component.For<IDocumentStore>().ImplementedBy<DocumentStore>()
        .DependsOn(new { Url = @"http://localhost:8080" })
        .OnCreate(new Action<IDocumentStore>(store =>
            store.Conventions.CustomizeJsonSerializer = serializer =>
                serializer.Converters.Add(container.Resolve<CustomJsonConverter>())))
        .OnCreate(new Action<IDocumentStore>(store =>
            store.Initialize()))
        .OnDestroy(new Action<IDocumentStore>(store =>
            store.Dispose()))
    );

Although it seems to be working fine, I feel odd having to call container.Resolve<CustomJsonConverter>() from inside the container.Register() method.

Is this a legal approach to register the dependencies?


Solution

  • Christian, We can't use your ctor, we don't know what to put in there.

    Instead, you can use this technique to tell RavenDB how to create your objects: http://james.newtonking.com/projects/json/help/CustomCreationConverter.html

    Then you can wire this in using documentStore.Conventison.CustomizeSerializer