3

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.

Rassi
  • 1,612
  • 14
  • 21
  • 1
    I took a look at your test solution. My first thought is that you would be well served by isolating your web (Nancy) code from your application code (IGreeter etc.) I would build the Nancy Module to be responsible to extract all relevant info from the web requests (paths, query string params, headers etc.) and pass it to the application code, and finally to take the results of your application code and format it back into a web format (headers, payload, status codes etc.). The application can then be web agnostic with no references to Nancy or any need to understand URLs Headers etc. – ScottS Sep 12 '15 at 05:27
  • I PR'd some ideas to your github example. Kudos for sharing a thorough and small example of your problem. – ScottS Sep 12 '15 at 06:27
  • Thank you for your comments and example PR Scott. Greatly appreciated. I agree that it would be great to remove dependencies on Nancy/web code from internal application code, and my example was a quick way of trying to show my predicament. But even if we abstract away dependencies on NancyContext etc, we still need URL/header information in various services throughout our code. (continued) – Rassi Sep 13 '15 at 08:09
  • As far as I can see, your example results in more or less the same situation as the one posed in my question. Namely that we will need to carry web context information with us through our call chain. In my question example it is RequestContext, and in your example it is GreetingCommand. I tried simplifying our situation by squeezing in a layer (Greeter) between the module, and the class actually using the context information (GreetingMessageService). And so GreetingCommand needs to be carried with us through Greeter into GreetingMessageService. (continued) – Rassi Sep 13 '15 at 08:10
  • In our application this call chain is very large, and it is this plumbing code we would like to avoid, if we find ourselves in need of context information. And this is where dependency injection comes in and should be able to save the day. In the meantime I have been able to create a proof of concept, how it is possible to auto register all classes AsPerRequestSingleton (https://github.com/Rassi/NancyIocTest/pull/2). I will add this as a possible answer. – Rassi Sep 13 '15 at 08:10

1 Answers1

1

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.

Rassi
  • 1,612
  • 14
  • 21