Adding dynamic redirects. Fix for #793

Fix for #793 

*Do not merge this yet!!!!*

This change removes all static binding redirects in app.config, and introduces dynamic redirects in core. The way this is done is by listening to the AssemblyResolve event on the AppDomain, and loading / returning the assembly to redirect to. It turns out using this technique you can swap on the fly and give a different assembly than what was requested.

At startup, it builds a map in map in memory based on the assemblies that scriptcs ships with ensuring that any requests for the same names will be redirected to it's version. It also ensures that any packages in the scriptcs packages folder take precedence over what the script pack / packages assemblies were actually compiled against.

Assembly black magic at its finest....

It appears to work so far :-)

This does not fix the Service Stack script pack issue, as there appears to be no magical fix for that other than updating the script pack. However, I was able to get the old Web Api script pack to work with the latest Web Api (see comments)


OK @adamralph so in trying to implement the locking, it turns out the
easiest way is to do something along the lines of what you are suggesting.
 I decided not to use exceptions and instead to log a warning.

The new logic change is this

If an assembly is added, AND a corresponding assembly was already loaded
(i.e. with the same assembly name) AND the assembly being added has a
higher version THEN a warning will be logged, and the assembly will not be
remapped.

If someone tries to add an assembly that is using an earlier version of
what is already mapped and loaded, it doesn't matter as it is no-op, as we
only care about the latest version.

There is still some brittleness here as one could be in a broken state if
they tried to add a newer assembly, after an earlier one was loaded and
created, but if that happens and it's completely incompatible interface
wise it should blow up, if it is compatible, then it will work fine.

If we see in the wild we need to harden this up more we can, i.e. we can
say once any assembly that exists is resolved, we lock down.

I am ok with trying this....


On Thu, Jul 24, 2014 at 10:57 AM, Glenn Block <glenn.block@gmail.com> wrote:

> This is the last change I am making to this for now. I am calling v1 done.
> It can be evolved in future releases.
>
> Thanks.
>
> On Thursday, July 24, 2014, Adam Ralph <notifications@github.com> wrote:
>
>> In src/ScriptCs.Core/AppDomainAssemblyResolver.cs:
>>
>> > +
>> > +        public void AddAssemblyPaths(IEnumerable<string> assemblyPaths)
>> > +        {
>> > +            foreach (var assemblyPath in assemblyPaths)
>> > +            {
>> > +                var info = new AssemblyInfo {Path = assemblyPath};
>> > +                var name = AssemblyName.GetAssemblyName(assemblyPath);
>> > +                info.Version = name.Version;
>> > +
>> > +                AssemblyInfo foundInfo = null;
>> > +                var found = _assemblyInfoMap.TryGetValue(name.Name, out foundInfo );
>> > +
>> > +                if (!found || foundInfo.Version.CompareTo(info.Version) < 0)
>> > +                {
>> > +                    _logger.DebugFormat("Mapping Assembly {0} to version:{1}", name.Name, name.Version);
>> > +                    _assemblyInfoMap[name.Name] = info;
>>
>> That's probably good enough, but AppDomain.CurrentDomain.AssemblyResolve
>> will only fire after some assembly has failed to resolve. This is why a
>> host may see suprising behaviour if they are running more than one script
>> and adding some specific assemblies before the execution of each one. I.e.
>> it may succeed for the first n scripts but then suddenly fail on n+1
>> because an assembly falied to resolve during execution of script n.
>>
>> —
>> Reply to this email directly or view it on GitHub
>> <https://github.com/scriptcs/scriptcs/pull/798/files#r15343484>.
>>
>