Search code examples
c#nancytinyioc

Nancy IoC per request auto registration


We have an ASP.NET REST service implemented with Nancy (and TinyIoc), which interfaces with a quite large legacy application. We would like to transition to full dependency injection. At the moment most of our modules instantiate the controllers directly, and takes a context class which holds the request information using the NancyContext Context property. Something like this:

public FooModule(ILegacyService legacyService) {
    Get["/"] = parameters => new FooController(new RequestContext(Context), legacyService).GetFoo();
}

We would like to inject the controllers directly in the module, and get everything handled by the super-duper-happy-path :)

Our problems all stem from the fact that we need information from NancyContext. The url, headers etc. So we have attempted various approaches to get to dependency injection nirvana.

Attempting to inject controllers failed, since they're instantiated in application scope, and so any dependency to RequestContext would not have the current context information. Even registering RequestContext in ConfigureRequestContainer did not ripple through to all dependants, and they would hold references to an out of date RequestContext.

We have tried to property inject the context using IRequestStartup, and this seemed successful, until we ran into concurrency issues. Simultaneous requests in different threads would overwrite the application scope RequestContext.

We found out that we could call container.AutoRegister() in ConfigureRequestContainer, but this resulted in severe lag, since registrations takes seconds with the amount of types we have.

Using AsPerRequestSingleton() in ConfigureApplicationContainer seems like it will register once, and then instantiate per request, but there doesn't seem to be a way to get auto registration to adhere to this.

It seems like we need to register all types manually, and keep this configuration up to date manually. Is this the case? We really would prefer to have some type of auto registration with per request lifetime.

I have created a small test project (https://github.com/Rassi/NancyIocTest) in which I attempted some of these solutions.


Solution

  • Using DefaultNancyAspNetBootstrapper you can create your own auto registering like this:

    public class Bootstrapper : DefaultNancyAspNetBootstrapper
    {
        protected override void ConfigureApplicationContainer(TinyIoCContainer container)
        {
            var assemblyClasses = Assembly.GetExecutingAssembly().GetTypes().Where(type => type.IsClass);
            foreach (var assemblyClass in assemblyClasses)
            {
                var interfaces = assemblyClass.GetInterfaces();
                if (interfaces.Count() == 1)
                {
                    container.Register(interfaces[0], assemblyClass).AsPerRequestSingleton();
                }
            }
        }
    

    Everything is then instantiated per request, and you can inject context information using RequestStartup():

        protected override void RequestStartup(TinyIoCContainer container, IPipelines pipelines, NancyContext context)
        {
            base.RequestStartup(container, pipelines, context);
            var requestUrl = container.Resolve<IRequestUrl>();
            requestUrl.Context = context;
        }
    }
    

    This is a simple proof of concept, which will find classes which implement one interface, and register it to that. A couple of issues should probably be handled, such as: Registering classes implementing multiple interfaces (perhaps using the convention of registering Name to IName). And also handling multiple classes registering the same interface.