DEBUG and RELEASE modeI'm trying to use Nancy within an Owin Middleware component that will completely hide Nancy using ILRepack. So I don't want to use auto-registration at all and carefully list the dependencies and modules it needs to use at run-time. The below PR shows the custom bootstrapper I use. However, after merging everything together, my unit tests fail with:
Nancy.TinyIoc.TinyIoCResolutionException : Unable to resolve type: Nancy.Diagnostics.DefaultDiagnostics
---- Nancy.TinyIoc.TinyIoCResolutionException : Unable to resolve type: Nancy.ModelBinding.DefaultModelBinderLocator
-------- Nancy.TinyIoc.TinyIoCResolutionException : Unable to resolve type: Nancy.ModelBinding.DefaultBinder
------------ Nancy.TinyIoc.TinyIoCResolutionException : Unable to resolve type: Nancy.ModelBinding.BindingDefaults
---------------- System.Collections.Generic.KeyNotFoundException : The given key was not present in the dictionary.
Stack Trace:
at Nancy.TinyIoc.TinyIoCContainer.ConstructType(Type requestedType, Type implementationType, ConstructorInfo constructor, NamedParameterOverloads parameters, ResolveOptions options)
at Nancy.TinyIoc.TinyIoCContainer.ConstructType(Type requestedType, Type implementationType, ConstructorInfo constructor, ResolveOptions options)
at Nancy.TinyIoc.TinyIoCContainer.SingletonFactory.GetObject(Type requestedType, TinyIoCContainer container, NamedParameterOverloads parameters, ResolveOptions options)
at Nancy.TinyIoc.TinyIoCContainer.ResolveInternal(TypeRegistration registration, NamedParameterOverloads parameters, ResolveOptions options)
at Nancy.TinyIoc.TinyIoCContainer.Resolve(Type resolveType)
at Nancy.TinyIoc.TinyIoCContainer.Resolve[ResolveType]()
at Nancy.DefaultNancyBootstrapper.GetDiagnostics()
at Nancy.Bootstrapper.NancyBootstrapperBase`1.Initialise()
at Nancy.Owin.NancyMiddleware.UseNancy(NancyOptions options)
at Owin.AppBuilderExtensions.UseNancy(IAppBuilder builder, NancyOptions options)
at LiquidProjections.Owin.MiddlewareExtensions.<>c__DisplayClass0_0.<UseStatisticsHttpApi>b__0(IAppBuilder a)
See https://github.com/liquidprojections/LiquidProjections/pull/99/files#diff-7c0d67bcc3ae212bacab1bc7f8779e39R18. Run the build.ps1 to reproduce the errors.
It's really weird that it's unable to find the types. I think this must have something to do with the IL merging. Nancy doesn't really scan for its internal types. They're set in its internal configuration.
Also, if you don't want to use the TinyIoC AutoRegister magic, you shouldn't call base.ConfigureApplicationContainer.
Nancy is not scanning for those dependencies since they probably ending up in some other namespace or "non-Nancy assembly". Also if you want a bare-minimum, then there should be no need to register the DefaultDiagnostics, simply implement your own IDiagnostics that more or less does nothing
Oh, I see... Looks like you're trying to manually register some (default?) Nancy internal types. If you want to override internal Nancy services, you should try overriding the InternalConfiguration property.
Also, these points from the wiki might also be valid:
- The default Nancy bootstrapper will "autoregister" everything it can in its default implementation of ConfigureApplicationContainer.
- By calling "base" _after_ you've made a manual registration you are effectively copying over your original registration by autoregister. Either don't call base, or call it before you do your manual registrations.
Oh, I see... Looks like you're trying to manually register some (default?) Nancy internal types.
Actually, I only tried that to see if I could resolve the dependency problem. Even if I remove those explicit registrations, the error happens. In fact, I traced it down to the DefaultBindings having a dependency on INancyEnvironment which TinyIoc can't find.
Nancy doesn't really scan for its internal types.
But if I don't call the base, the container is completely empty except for the stuff I added to it. That doesn't sound right.
Just for the record, I don't want Nancy to do any assembly scanning. My IL-merged package could be hosted multiple times on different end-points within the same AppDomain.
But if I don't call the base, the container is completely empty except for the stuff I added to it. That doesn't sound right.
Hmm. That sounds _really_ weird. There's tons and tons of apps that doesn't call base in ConfigureApplicationContainer that works perfectly fine. I even think we have examples of this in this repo.
Just to be clear; ILMerging Nancy isn't really a supported scenario and not something we (at least myself) has done or heard about in the past. You're basically in unknown territory here... Now, that doesn't necessarily mean it can't work - it probably can, as Nancy allows you to swap any internals you may wish - but Nancy is fundamentally built around looking inside loaded assemblies for modules and services out of the box.
I guess what I'm saying is that Nancy can probably do what you want, but don't expect it to just work out of the box. You're basically pushing the limits of what Nancy was written for.
My IL-merged package could be hosted multiple times on different end-points within the same AppDomain.
This is also probably not a good idea, for several reasons. Are you sure Nancy isn't a bit overkill for this? It sounds like you're trying to shoot sparrows with a cannon here. Nancy is built for full applications and not to run as multiple instances inside the same AppDomain. Sure, we've been doing work to be able to run as isolated as possible, but it's still not a recommended scenario.
Can I ask what this middleware will do?
Just to be clear; ILMerging Nancy isn't really a supported scenario and not something we (at least myself) has done or heard about in the past. You're basically in unknown territory here... Now, that doesn't necessarily mean it can't work - it probably can, as Nancy allows you to swap any internals you may wish - but Nancy is fundamentally built around looking inside loaded assemblies for modules and services out of the box.
So you're saying that Nancy will specifically look for a Nancy assembly? And is there anything _static_ that will prevent Nancy from being started more than once? I got the impression that it was build out of composable parts that you can replace and/or remove as desired.
This is also probably not a good idea, for several reasons. Are you sure Nancy isn't a bit overkill for this? It sounds like you're trying to shoot sparrows with a cannon here. Nancy is built for full applications and not to run as multiple instances inside the same AppDomain. Sure, we've been doing work to be able to run as isolated as possible, but it's still not a recommended scenario.
Actually, I started with ASP.NET WebAPI and Swashbuckle to build a dependency-free OWIN middelware component that can be used as an extension to my new library LiquidProjections. It provides an HTTP end-point on top of some Event Sourcing/Projections stuff. But that combination was way too heavy and caused all kinds of version conflicts related to Newtonsoft. After evaluating Nancy I got the impression it was build the right way.
So you're saying that Nancy will specifically look for a Nancy assembly?
Yes. Kinda. Nancy looks for types using the ITypeCatalog and IAssemblyCatalog. If you're using the net452 assembly, it'll use the AppDomainAssemblyCatalog and if you're using the netstandard1.6 assembly, it'll use the DependencyContextAssemblyCatalog, which is based on DependencyContext (same as ASP.NET Core MVC).
As you can see, it looks for assemblies referencing an assembly called Nancy. If you ILMerge Nancy into another assembly, this probably no longer works, so it might be an idea to at least implement your own (static) version of IAssemblyCatalog that returns (at least) the assembly your ILMerging Nancy into. You supply it to Nancy by overriding the AssemblyCatalog property in the bootstrapper.
And is there anything static that will prevent Nancy from being started more than once?
We've done a lot of work for 2.0 to remove all statics in Nancy (there used to be quite a bit of static configuration), but I can't guarantee that everything's gone. If you hit something, let us know.
I got the impression that it was build out of composable parts that you can replace and/or remove as desired.
Yes and no. You can replace stuff, but there are fundamental parts of Nancy that has to be there, so it's not necessarily that easy to remove bigger chunks.
It provides an HTTP end-point on top of some Event Sourcing/Projections stuff.
Nancy sounds _really_ heavy for an endpoint like this. Think about it; you're only using Nancy for dealing with a thin layer of HTTP. Nancy is built for much more complex scenarios than this. I'm not saying you shouldn't use it, but I think I'd consider just rolling something very lightweight myself.
This is the config class that shows all the usages of the type catalog that @khellang mentioned above:
https://github.com/NancyFx/Nancy/blob/master/src/Nancy/Bootstrapper/NancyInternalConfiguration.cs
And this is a spike of a "container free" bootstrapper I did, which may or may not be a useful reference:
https://gist.github.com/grumpydev/c93281668b32e295ef967e2f966f4e6a
OK, it seems that the 'default' behavior of IL-Repack changing all merged classes to internal is the main culprit. I discovered this because the ApplicationStartup showed me 106 registrations in the container in the successful scenario, and only 63 after merging the IL. So apparently there's something in Nancy that scans for types at startup. Now where is that code...
They're here https://github.com/NancyFx/Nancy/issues/2770#issuecomment-315034058 and here https://github.com/NancyFx/Nancy/issues/2770#issuecomment-315034880 馃槃
Using a custom implementation of IAssemblyCatalog and ITypeCatalog allowed me to ILMerge Nancy and keep its classes internal. Thanks for the help guys!
馃
@dennisdoomen glad we could help. Maybe you'd be up for adding a page on our wiki on instructions on how you did all of this?
I will definitely write a blog post on https://www.continuousimprover.com. I need this solution to build embedded microservices like I discussed in https://www.slideshare.net/dennisdoomen/decomposing-the-monolith-using-microservices-that-dont-give-you-pain
Most helpful comment
Yes. Kinda. Nancy looks for types using the
ITypeCatalogandIAssemblyCatalog. If you're using thenet452assembly, it'll use theAppDomainAssemblyCatalogand if you're using thenetstandard1.6assembly, it'll use theDependencyContextAssemblyCatalog, which is based onDependencyContext(same as ASP.NET Core MVC).As you can see, it looks for assemblies referencing an assembly called
Nancy. If you ILMerge Nancy into another assembly, this probably no longer works, so it might be an idea to at least implement your own (static) version ofIAssemblyCatalogthat returns (at least) the assembly your ILMerging Nancy into. You supply it to Nancy by overriding theAssemblyCatalogproperty in the bootstrapper.We've done a lot of work for 2.0 to remove all statics in Nancy (there used to be quite a bit of static configuration), but I can't guarantee that everything's gone. If you hit something, let us know.
Yes and no. You can replace stuff, but there are fundamental parts of Nancy that has to be there, so it's not necessarily that easy to remove bigger chunks.
Nancy sounds _really_ heavy for an endpoint like this. Think about it; you're only using Nancy for dealing with a thin layer of HTTP. Nancy is built for much more complex scenarios than this. I'm not saying you shouldn't use it, but I think I'd consider just rolling something very lightweight myself.