3

I am trying to figure out how the NancyFx request container works, so I created a little test project.

I created a this interface

public interface INancyContextWrapper
{
    NancyContext Context { get; }
}

With this implementation

public class NancyContextWrapper : INancyContextWrapper
{
    public NancyContext Context { get; private set; }

    public NancyContextWrapper(NancyContext context)
    {
        Context = context;
    }
}

Then in the bootstrapper I register it like this

protected override void ConfigureRequestContainer(TinyIoCContainer container, NancyContext context)
{
    base.ConfigureRequestContainer(container, context);
    container.Register<INancyContextWrapper>(new NancyContextWrapper(context));
}

I use this context wrapper in a class that does nothing but return the request url as a string.

public interface IUrlString
{
    string Resolve();
}

public class UrlString : IUrlString
{
    private readonly INancyContextWrapper _context;

    public UrlString(INancyContextWrapper context)
    {
        _context = context;
    }

    public string Resolve()
    {
        return _context.Context.Request.Url.ToString();
    }
}

And finally use this in a module

public class RootModule : NancyModule
{
    public RootModule(IUrlString urlString)
    {
        Get["/"] = _ => urlString.Resolve();
    }
}

When I do that the request is always null. Now I can more or less figure out that since IUrlString isn't configured in the request container configuration, TinyIoc resolved INancyContextWrapper at application startup before any request was made, and TinyIoc does not re-register dependencies that down the dependency graph relies on something configured in the request container configuration.

My question is what the best practice is for using ConfigureRequestContainer? Do I have to register everything that in any way relies on NancyContext explicitly in the request container configuration? That can very quickly become bloated and hard to maintain. I love how TinyIoc does assembly scanning, so having to do this is a bit of a setback.

Jay Pete
  • 4,123
  • 4
  • 35
  • 51
  • The simple answer is that you have registered an instance of NancyContextWrapper, which is created at the start of the request, before the request object itself is created. Don't register an instance, register the type and it will get created at a time when everything is setup. –  May 03 '15 at 19:12
  • Do you mean `container.Register(typeof(NancyContextWrapper));`? That doesn't work. – Jay Pete May 03 '15 at 20:20
  • The real trouble seems to be that what I am injecting into the module is `IUrlString`, which has a dependency on `INancyContextWrapper`. `IUrlString` is resolved by the application container using assembly scanning, and at that point there is no context to inject into `NancyContextWrapper`. Can I force `IUrlString` to be resolved again at request time in any other way than by doing it in `ConfigureRequestContainer`. – Jay Pete May 03 '15 at 21:03

1 Answers1

4

Assuming that the example above is just a simplification of what you actually want - i.e. something to carry around the nancy context during the request lifetime for some purpose, you may be better off to not use the bootstrapper at all, as it is dependent on the used IoC container.

Suggestions:

Change the implementation of the wrapper to not use ctor, but a property setter (you can always code so that the property can be set only once):

public interface INancyContextWrapper
{
    NancyContext Context { get; set; }
}

public class NancyContextWrapper : INancyContextWrapper
{
    private NancyContext _context;
    public NancyContext Context 
    { 
           get {return _context;} 
           set {_context = value;} //do something here if you want to prevent repeated sets
    }
}

Instead of using the container and the bootstrapper directly, use an IRegistration implementation (these are used by nancy, and are container independent)

public class NancyContextWrapperRegistrations : IRegistrations
{
    public IEnumerable<TypeRegistration> TypeRegistrations 
    {
        get 
        { 
            return new[]
            {
                new TypeRegistration(typeof(INancyContextWrapper), typeof(NancyContextWrapper), Lifetime.PerRequest),
                new TypeRegistration(typeof(IUrlString .... per request
            };
               // or you can use AssemblyTypeScanner, etc here to find
        }    

        //make the other 2 interface properties to return null
    }
}

Use IRequestStartup task (these are autodiscovered by nancy too) to setup the context

public class PrepareNancyContextWrapper : IRequestStartup
{
    private readonly INancyContextWrapper _nancyContext;
    public PrepareNancyContextWrapper(INancyContextWrapper nancyContext)
    {
        _nancyContext = nancyContext;
    }

    public void Initialize(IPipelines piepeLinse, NancyContext context)
    {
         _nancyContext.Context = context;
    }
}

Although the above looks overkill, it is extremely nice way to organize type registrations in a IoC independent way (i.e. if you replace TinyIoC with something else, you do not need to touch bootstrappers, etc.)

Also, it is very nice way to control what happens during request (or if you want - application) startup, w/o overriding anything in the bootstrapper, and it will work with whatever bootstrapper/container you decide to go.

Dunc
  • 18,404
  • 6
  • 86
  • 103
Sunny Milenov
  • 21,990
  • 6
  • 80
  • 106
  • That looks really interesting. Will try that out this weekend. Just out of curiosity, why would you want to prevent repeated sets on the context? – Jay Pete May 07 '15 at 22:17
  • One more question... how would I reference INancyContextWrapper from my other classes? Would I just inject like everything else, or is it available in some singleton, that only exists in the request scope? – Jay Pete May 08 '15 at 08:31
  • Ahh... I need to register everything that depends on IContextWrapper, anywhere in their dependency graph. I was hoping that wouldn't be necessary. Is there a way to register a request scoped singleton, that I could access from anywhere? – Jay Pete May 08 '15 at 18:08
  • 1
    That's not how IoC containers work. If you register something as a per-request, everything which needs it (depends on it), need to be per-request as well, as all these classes are "created" during the request startup. – Sunny Milenov May 11 '15 at 13:57
  • I was hoping that I could wrap the request nicely. I have a lot of controllers that would then have to be injected into every request, even though I only ever need one of them. What am I not understanding here? What do other people do? I feel like I have modelled things pretty bad here. – Jay Pete May 11 '15 at 17:30
  • A normal thing is to not make your business logic dependent on transport concerns - i.e. reconsider do you really need the context floating around all the time. Can't you just pass around only the important bits of information, etc. Or to isolate the parts which depend on the context in smaller part of your code. – Sunny Milenov May 11 '15 at 20:51
  • Well I have very little dependence on the request, but I need to know the URL, Headers and the body in all my controllers, otherwise I can't really do anything. But I don't want to access them directly on the request object I want to isolate that. But then I have to pass that isolation around, and then even though my controllers know nothing about the request or the context, they still need to be instantiated in the request container. I was hoping there was a way to get around that. – Jay Pete May 11 '15 at 23:40
  • Didn't get around to testing this until this evening. Works like a charm! – Jay Pete Aug 17 '15 at 22:36