NancyFX integration

Apr 6, 2014 at 11:02 PM
Hello,

Do you guys support NancyFX? I've read the discussion here NancyFX and Simple Injector with Steven's comments, but I noticed they never completely answered. Has anyone here done an implementation?

Thanks,

Steven
Coordinator
Apr 7, 2014 at 6:14 AM
Edited May 6, 2016 at 9:50 AM
<UPDATE May 2016>
The information in this thread is outdated. Please refer to the Integration Guide in the Simple Injector documentation to find the latest advice on how to integrate Simple Injector with Nancy.
</UPDATE>




I’ve been trying to get the default Nancy VS template working with Simple Injector. There are a few things that I noticed:
  1. Nancy assumes the container natively understands or is configured to allow resolving types with multiple constructors and that it selects a constructor where all parameters are resolvable.
  2. Nancy assumes the container natively understands or is configured to allow resolving Func<T> types, where only the T itself is registered.
  3. Nancy allows users to add their own registrations by implementing a class with the IApplicationRegistrations interface. Unfortunately it registers those implementations in the container. This is absolutely a quirk in the design, because the rest of the application has no need to depend on IApplicationRegistrations.
    So Nancy absolutely expects some behavior from a container, and that makes it a leaky abstraction. Good thing is that we will be able to work around these issues.
Here’s my third try on building an SimpleInjectorNancyBootstrapper:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Nancy;
using Nancy.Bootstrapper;
using Nancy.Diagnostics;
using SimpleInjector;
using SimpleInjector.Advanced;
using SimpleInjector.Extensions.ExecutionContextScoping;

public abstract class SimpleInjectorNancyBootstrapper
    : NancyBootstrapperWithRequestContainerBase<
    NancyApplication1.SimpleInjectorNancyBootstrapper.IContainer>
{
    public interface IContainer
    {
    }

    private IEnumerable<ModuleRegistration> modules;

    private readonly List<Type> applicationRegistrations = new List<Type>();

    public SimpleInjectorNancyBootstrapper()
    {
        this.Container = new Container();

        this.Container.Options.ConstructorResolutionBehavior =
            new NancyConstructorResolutionBehavior(this.Container);

        AllowResolvingFuncFactoriesForNancyTypes(this.Container);
    }

    public Container Container { get; private set; }

    protected override IContainer CreateRequestContainer()
    {
        return new RequestContainer(this.Container.BeginExecutionContextScope());
    }

    protected override IEnumerable<INancyModule> GetAllModules(IContainer container)
    {
        return
            from module in this.modules
            select (INancyModule)this.Container.GetInstance(module.ModuleType);
    }

    protected override IContainer GetApplicationContainer()
    {
        return new RequestContainer(null);
    }

    protected override IEnumerable<IApplicationRegistrations> GetApplicationRegistrationTasks()
    {
        return this.applicationRegistrations
            .Select(Activator.CreateInstance)
            .Cast<IApplicationRegistrations>();
    }

    protected override IEnumerable<IApplicationStartup> GetApplicationStartupTasks()
    {
        return this.Container.GetAllInstances<IApplicationStartup>();
    }

    protected override IDiagnostics GetDiagnostics()
    {
        return this.Container.GetInstance<IDiagnostics>();
    }

    protected override INancyEngine GetEngineInternal()
    {
        return this.Container.GetInstance<INancyEngine>();
    }

    protected override INancyModule GetModule(IContainer container, Type moduleType)
    {
        return (INancyModule)this.Container.GetInstance(moduleType);
    }

    protected override void RegisterBootstrapperTypes(IContainer applicationContainer)
    {
        this.Container.RegisterSingle<INancyModuleCatalog>(this);
    }

    protected override void RegisterCollectionTypes(IContainer container,
        IEnumerable<CollectionTypeRegistration> collectionTypeRegistrations)
    {
        foreach (var registration in collectionTypeRegistrations)
        {
            if (registration.RegistrationType == typeof(IApplicationRegistrations))
            {
                applicationRegistrations.AddRange(registration.ImplementationTypes);
            }
            else
            {
                this.Container.RegisterAll(registration.RegistrationType,
                    from implementationType in registration.ImplementationTypes
                    select Lifestyle.Singleton.CreateRegistration(
                        registration.RegistrationType,
                        implementationType, this.Container));
            }
        }
    }

    protected override void RegisterInstances(IContainer container,
        IEnumerable<InstanceRegistration> instanceRegistrations)
    {
        foreach (InstanceRegistration registration in instanceRegistrations)
        {
            this.Container.RegisterSingle(registration.RegistrationType,
                registration.Implementation);
        }
    }

    protected override void RegisterRequestContainerModules(IContainer container,
        IEnumerable<ModuleRegistration> moduleRegistrationTypes)
    {
        if (this.modules == null)
        {
            this.modules = moduleRegistrationTypes;
        }
    }

    protected override void RegisterTypes(IContainer container,
        IEnumerable<TypeRegistration> typeRegistrations)
    {
        foreach (TypeRegistration registration in typeRegistrations)
        {
            this.Container.Register(
                registration.RegistrationType,
                registration.ImplementationType,
                Lifestyle.Singleton);
        }
    }

    private static void AllowResolvingFuncFactoriesForNancyTypes(Container container)
    {
        container.ResolveUnregisteredType += (sender, e) =>
        {
            if (e.UnregisteredServiceType.IsGenericType &&
                e.UnregisteredServiceType.GetGenericTypeDefinition() == typeof(Func<>))
            {
                Type serviceType = e.UnregisteredServiceType.GetGenericArguments()[0];

                if (serviceType.Assembly.FullName.StartsWith("Nancy"))
                {
                    InstanceProducer registration = container.GetRegistration(serviceType);

                    if (registration != null)
                    {
                        Type funcType = typeof(Func<>).MakeGenericType(serviceType);
                        var factory = Expression.Lambda(funcType,
                            registration.BuildExpression()).Compile();
                        e.Register(Expression.Constant(factory, funcType));
                    }
                }
            }
        };
    }

    private sealed class NancyConstructorResolutionBehavior : IConstructorResolutionBehavior
    {
        private readonly Container container;
        private readonly IConstructorResolutionBehavior wrapped;

        public NancyConstructorResolutionBehavior(Container container)
        {
            this.container = container;
            this.wrapped = container.Options.ConstructorResolutionBehavior;
        }

        private bool IsCalledDuringRegistrationPhase
        {
            [DebuggerStepThrough]
            get { return !this.container.IsLocked(); }
        }

        [DebuggerStepThrough]
        public ConstructorInfo GetConstructor(Type serviceType, Type implementationType)
        {
            var constructor = this.GetConstructorOrNull(implementationType);

            if (constructor != null)
            {
                return constructor;
            }

            return this.wrapped.GetConstructor(serviceType, implementationType);
        }

        [DebuggerStepThrough]
        private ConstructorInfo GetConstructorOrNull(Type type)
        {
            // Only allow multiple constructors on Nancy types. Having multiple
            // ctors in a LOB app is an anti-pattern: https://bit.ly/1qhGMlw.
            if (!type.Namespace.StartsWith("Nancy")) return null;
            
            // We prevent calling GetRegistration during the registration phase, 
            // because at this point not all dependencies might be registered, 
            // and calling GetRegistration would lock the container,
            // making it impossible to do other registrations.
            return (
                from ctor in type.GetConstructors()
                let parameters = ctor.GetParameters()
                orderby parameters.Length descending
                where this.IsCalledDuringRegistrationPhase ||
                    parameters.All(this.CanBeResolved)
                select ctor)
                .FirstOrDefault();
        }

        [DebuggerStepThrough]
        private bool CanBeResolved(ParameterInfo parameter)
        {
            return this.container.GetRegistration(parameter.ParameterType) != null ||
                this.CanBuildParameterExpression(parameter);
        }

        [DebuggerStepThrough]
        private bool CanBuildParameterExpression(ParameterInfo parameter)
        {
            return this.container.GetRegistration(parameter.ParameterType) != null;
        }
    }

    private class RequestContainer : IContainer, IDisposable
    {
        private readonly Scope scope;

        public RequestContainer(Scope scope)
        {
            this.scope = scope;
        }

        public void Dispose()
        {
            if (this.scope != null)
            {
                this.scope.Dispose();
            }
        }
    }
}
Let me know how this works out.
Marked as answer by dot_NET_Junkie on 4/15/2014 at 1:50 AM
Apr 8, 2014 at 1:46 AM
Thank you Steven. I've inherited my bootstrapper from this sample code
public class Bootstrapper : SimpleInjectorNancyBootstrapper
And nothing else. For some reason the call to GetAllModules returns no Modules and I definitely have one
    public class IndexModule : NancyModule
    {
        public IndexModule(myService service)
        {
            Get["/"] = _ => View["index"];
        }
    }
It seems as I'm missing some registration logic for the handlers, but it also doesn't seem I should have to do this since "Nancy Native" does it automagically.
Coordinator
Apr 8, 2014 at 3:41 PM
I updated the code my previous reply. Can you try it again with this code?
Apr 8, 2014 at 4:01 PM
That worked!

So does this "Fix" the issue of registering IApplicationRegistrations in the container? I guess no. Like you I'm not sure I see the ultimate value in this and they admit it's mostly for backward compat. Still, it'd be nice to know what the gotcha's are.
Apr 8, 2014 at 4:05 PM
Also, can you comment further on your comment here
            // I don't think we have to do here anything, since the supplied types here 
            // are concrete; Simple Injector will be able to resolve them, unless those 
            // modules must have a per-web-request lifestyle, instead of transient, in 
            // that case we must either register an ResolveUnregisteredType
            // for this or or do some registration during startup.
What would a PerRequest registration look like?
Coordinator
Apr 8, 2014 at 7:47 PM
It depends a bit on how Nancy handles Modules, but since modules are root types, it is usually fine to register them as transient. Only when there is a circular reference in the object graph that points back at the module and that module is requested from the container, that's when you need a different lifestyle. But that seems a very unlikely scenario.

More likely however is to have other services registered with a scoped lifestyle. Good example of this is a Unit of Work (such as Entity Framework's DbContext). You often want the same instance throughout the entire graph. To be able to do this you need to define a scope somewhere. With Simple Injector, some scoped lifestyles are applied implicitly, such as the WebRequestLifestyle. I assume however, that with Nancy, there is (not always) a web request, just as is the case with Web API. Nancy might also be asynchronous, just like Web API. In that case you're best of using either the WebApiRequestLifestyle or the ExecutionContextScopeLifestyle (which are basically the same thing).

I updated the bootstrapper in my initial post to include a 'request container'. This container starts an ExecutionContextScope and will dispose it at the end of the request. You can now make the following registration:
var scopedLifestyle = new ExecutionContextScopeLifestyle();

this.Container.Register<IUnitOfWork, MyUnitOfWork>(scopedLifestyle);
This ensures that the MyUnitOfWork is disposed at the end of the Nancy request.
Aug 23, 2014 at 7:13 AM
Edited Aug 23, 2014 at 7:16 AM
A new commit broke the SimpleInjectorNancyBootstrapper

My attemp to fix it:

A supported NuGet package for SimpleInjector.Nancy would be awesome :-).
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Nancy;
using Nancy.Bootstrapper;
using Nancy.Diagnostics;
using SimpleInjector;
using SimpleInjector.Advanced;
using SimpleInjector.Extensions.ExecutionContextScoping;

    public abstract class SimpleInjectorNancyBootstrapper
        : NancyBootstrapperWithRequestContainerBase<SimpleInjectorNancyBootstrapper.IContainer>
    {
        public interface IContainer
        {
        }

        private IEnumerable<ModuleRegistration> modules;

        private readonly List<Type> applicationRegistrations = new List<Type>();

        protected SimpleInjectorNancyBootstrapper()
        {
            this.Container = new Container();

            this.Container.Options.ConstructorResolutionBehavior =
                new NancyConstructorResolutionBehavior(this.Container);

            AllowResolvingFuncFactoriesForNancyTypes(this.Container);
        }

        public Container Container { get; private set; }

        protected override IContainer CreateRequestContainer()
        {
            return new RequestContainer(this.Container.BeginExecutionContextScope());
        }

        protected override IEnumerable<INancyModule> GetAllModules(IContainer container)
        {
            return
                from module in this.modules
                select (INancyModule)this.Container.GetInstance(module.ModuleType);
        }

        protected override IContainer GetApplicationContainer()
        {
            return new RequestContainer(null);
        }

        protected override IEnumerable<IRequestStartup> RegisterAndGetRequestStartupTasks(IContainer container, Type[] requestStartupTypes)
        {
            foreach (var requestStartupType in requestStartupTypes)
            {
                this.Container.RegisterSingle(typeof(IRequestStartup), requestStartupType);
            }

            return this.Container.GetAllInstances<IRequestStartup>();
        }

        protected override IEnumerable<IRegistrations> GetRegistrationTasks()
        {
            return this.applicationRegistrations
                .Select(Activator.CreateInstance)
                .Cast<IRegistrations>();
        }

        protected override IEnumerable<IApplicationStartup> GetApplicationStartupTasks()
        {
            return this.Container.GetAllInstances<IApplicationStartup>();
        }

        protected override IDiagnostics GetDiagnostics()
        {
            return this.Container.GetInstance<IDiagnostics>();
        }

        protected override INancyEngine GetEngineInternal()
        {
            return this.Container.GetInstance<INancyEngine>();
        }

        protected override INancyModule GetModule(IContainer container, Type moduleType)
        {
            return (INancyModule)this.Container.GetInstance(moduleType);
        }

        protected override void RegisterBootstrapperTypes(IContainer applicationContainer)
        {
            this.Container.RegisterSingle<INancyModuleCatalog>(this);
        }

        protected override void RegisterCollectionTypes(IContainer container,
            IEnumerable<CollectionTypeRegistration> collectionTypeRegistrations)
        {
            foreach (var registration in collectionTypeRegistrations)
            {
                if (registration.RegistrationType == typeof(IRegistrations))
                {
                    applicationRegistrations.AddRange(registration.ImplementationTypes);
                }
                else
                {
                    this.Container.RegisterAll(registration.RegistrationType,
                        from implementationType in registration.ImplementationTypes
                        select Lifestyle.Singleton.CreateRegistration(
                            registration.RegistrationType,
                            implementationType, this.Container));
                }
            }
        }

        protected override void RegisterInstances(IContainer container,
            IEnumerable<InstanceRegistration> instanceRegistrations)
        {
            foreach (InstanceRegistration registration in instanceRegistrations)
            {
                this.Container.RegisterSingle(registration.RegistrationType,
                    registration.Implementation);
            }
        }

        protected override void RegisterRequestContainerModules(IContainer container,
            IEnumerable<ModuleRegistration> moduleRegistrationTypes)
        {
            if (this.modules == null)
            {
                this.modules = moduleRegistrationTypes;
            }
        }

        protected override void RegisterTypes(IContainer container,
            IEnumerable<TypeRegistration> typeRegistrations)
        {
            foreach (TypeRegistration registration in typeRegistrations)
            {
                this.Container.Register(
                    registration.RegistrationType,
                    registration.ImplementationType,
                    Lifestyle.Singleton);
            }
        }

        private static void AllowResolvingFuncFactoriesForNancyTypes(Container container)
        {
            container.ResolveUnregisteredType += (sender, e) =>
            {
                if (e.UnregisteredServiceType.IsGenericType &&
                    e.UnregisteredServiceType.GetGenericTypeDefinition() == typeof(Func<>))
                {
                    Type serviceType = e.UnregisteredServiceType.GetGenericArguments()[0];

                    if (serviceType.Assembly.FullName.StartsWith("Nancy"))
                    {
                        InstanceProducer registration = container.GetRegistration(serviceType);

                        if (registration != null)
                        {
                            Type funcType = typeof(Func<>).MakeGenericType(serviceType);
                            var factory = Expression.Lambda(funcType,
                                registration.BuildExpression()).Compile();
                            e.Register(Expression.Constant(factory, funcType));
                        }
                    }
                }
            };
        }

        private sealed class NancyConstructorResolutionBehavior : IConstructorResolutionBehavior
        {
            private readonly Container container;
            private readonly IConstructorResolutionBehavior wrapped;

            public NancyConstructorResolutionBehavior(Container container)
            {
                this.container = container;
                this.wrapped = container.Options.ConstructorResolutionBehavior;
            }

            private bool IsCalledDuringRegistrationPhase
            {
                [DebuggerStepThrough]
                get { return !this.container.IsLocked(); }
            }

            [DebuggerStepThrough]
            public ConstructorInfo GetConstructor(Type serviceType, Type implementationType)
            {
                var constructor = this.GetConstructorOrNull(implementationType);

                if (constructor != null)
                {
                    return constructor;
                }

                return this.wrapped.GetConstructor(serviceType, implementationType);
            }

            [DebuggerStepThrough]
            private ConstructorInfo GetConstructorOrNull(Type type)
            {
                // Only allow multiple constructors on Nancy types. Having multiple
                // ctors in a LOB app is an anti-pattern: https://bit.ly/1qhGMlw.
                if (!type.Namespace.StartsWith("Nancy")) return null;

                // We prevent calling GetRegistration during the registration phase, 
                // because at this point not all dependencies might be registered, 
                // and calling GetRegistration would lock the container,
                // making it impossible to do other registrations.
                return (
                    from ctor in type.GetConstructors()
                    let parameters = ctor.GetParameters()
                    orderby parameters.Length descending
                    where this.IsCalledDuringRegistrationPhase ||
                        parameters.All(this.CanBeResolved)
                    select ctor)
                    .FirstOrDefault();
            }

            [DebuggerStepThrough]
            private bool CanBeResolved(ParameterInfo parameter)
            {
                return this.container.GetRegistration(parameter.ParameterType) != null ||
                    this.CanBuildParameterExpression(parameter);
            }

            [DebuggerStepThrough]
            private bool CanBuildParameterExpression(ParameterInfo parameter)
            {
                try
                {
                    this.container.Options.ConstructorInjectionBehavior
                        .BuildParameterExpression(parameter);
                    return true;
                }
                catch (ActivationException)
                {
                    return false;
                }
            }
        }

        private class RequestContainer : IContainer, IDisposable
        {
            private readonly Scope scope;

            public RequestContainer(Scope scope)
            {
                this.scope = scope;
            }

            public void Dispose()
            {
                if (this.scope != null)
                {
                    this.scope.Dispose();
                }
            }
        }
    }
Coordinator
Aug 23, 2014 at 9:50 AM
It would be interesting to know which commit btoke the bootstrapper and why. Can you tell anything about that?
Aug 23, 2014 at 10:14 AM
Edited Aug 23, 2014 at 3:12 PM
dot_NET_Junkie wrote:
It would be interesting to know which commit btoke the bootstrapper and why. Can you tell anything about that?
Yes, this commit changed some methods:
protected override IEnumerable<IRegistrations> GetApplicationRegistrationTasks()
was renamed to
protected override IEnumerable<IRegistrations> GetRegistrationTasks()
This commit added a new method which was overridden in Autofac / Unity container implementations:
protected override IEnumerable<IRequestStartup> RegisterAndGetRequestStartupTasks(TinyIoCContainer container, Type[] requestStartupTypes)
{
        return container.ResolveAll<IRequestStartup>();
}
I have overridden it with:
protected override IEnumerable<IRequestStartup> RegisterAndGetRequestStartupTasks(IContainer container, Type[] requestStartupTypes)
{
    foreach (var requestStartupType in requestStartupTypes)
    {
        this.Container.RegisterSingle(typeof(IRequestStartup), requestStartupType);
    }

    return this.Container.GetAllInstances<IRequestStartup>();
}
And in this commit IApplicationRegistrations was renamed to IRegistrations

so i changed the following line:
........
foreach (var registration in collectionTypeRegistrations)
{
    if (registration.RegistrationType == typeof(IRegistrations))
    {
        applicationRegistrations.AddRange(registration.ImplementationTypes);
     } 
........
Aug 28, 2014 at 3:24 PM
Edited Aug 28, 2014 at 3:37 PM
Just a question. Where should Verify() be called in this setup? Any ides?
Would also be nice with a review of this! :-)
Coordinator
Aug 28, 2014 at 5:38 PM
Edited Aug 28, 2014 at 8:20 PM
@thomas, I'm not sure where Verify() can be called with Nancy. I don't think your RegisterAndGetRequestStartupTasks method will work, because you can't do any registrations in the container after the first call to GetInstance as explained here.

What's missing from nancy IMO is a mechanism to leave the framework's default initialization mechanism in tact, and just plug in your own INancyModuleFactory of some sort (just as you do with MVC and Web API), since normally those are the only types you are really interested in. This would result in a framework that is much more DI friendly as explained by Mark Seemann.

I tried to achieve this with the default DefaultNancyBootstrapper, but methods like GetModule are sealed. I think in most cases there are only two things you need to do and that is override the default Module creation behavior and a way to begin and end a per-request scope. Both don't seem possible at the moment. So unfortunately that leaves us with implementing our own SimpleInjectorNancyBootstrapper.
Aug 27, 2015 at 6:53 PM
Hi Steven,

After moving to SimpleInjector 3, the CanBuildParameterExpression function broke, specifically the

container.Options.ConstructorInjectionBehavior.BuildParameterExpression(parameter).

Error returned was "In v3, the IConstructorVerificationBehavior and IConstructorInjectionBehavior interfaces have been replaced with the single IDependencyInjectionBehavior interface. Please use the DependencyInjectionBehavior property to override Simple Injector's constructor injection behavior."

I tried:

container.Options.DependencyInjectionBehavior.BuildExpression but not sure how to build InjectionConsumerInfo from ParameterInfo.

Could you help point me in the right direction?

Thanks!

Jacob
Coordinator
Aug 27, 2015 at 6:58 PM
Hi Jacob,

Could you please move this question to Github and explain there what it exactly is you are trying to achieve? Some code examples of your old solution would be helpful.

Cheers