Search code examples
c#nancytinyioc

NancyFx ConfigureRequestContainer


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.


Solution

  • 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.