November Happy Hour will be moved to Thursday December 5th.

UnifiedSearchRegistry throws LoaderExceptions with missing assemblies

Vote:
 

Hi, 


I am in the process of upgrading an EPiServer site, with EPiServer Find from 9.6.1 to 11, and I have stumbled upon a problem I haven't been able to figure out.

When running the following code in an initialization module, we get some LoaderException errors.

    [ModuleDependency(typeof (EPiServer.Web.InitializationModule))]
    public class ContentIndexerInitialization: IInitializableModule
    {
        public void Initialize(InitializationEngine context)
        {
            // Some code before this running fine.
            SearchClient.Instance.Conventions.UnifiedSearchRegistry.ForInstanceOf();
        }

        public void Uninitialize(InitializationEngine context)
        {
        }
       
    }

Turning on the Fusion log tells me the following:

Assembly manager loaded from:  C:\Windows\Microsoft.NET\Framework64\v4.0.30319\clr.dll
Running under executable  c:\windows\system32\inetsrv\w3wp.exe
--- A detailed error log follows. 

=== Pre-bind state information ===
LOG: DisplayName = System.Collections.Immutable
 (Partial)
WRN: Partial binding information was supplied for an assembly:
WRN: Assembly Name: System.Collections.Immutable | Domain ID: 2
WRN: A partial bind occurs when only part of the assembly display name is provided.
WRN: This might result in the binder loading an incorrect assembly.
WRN: It is recommended to provide a fully specified textual identity for the assembly,
WRN: that consists of the simple name, version, culture, and public key token.
WRN: See whitepaper http://go.microsoft.com/fwlink/?LinkId=109270 for more information and common solutions to this issue.
LOG: Appbase = file:///C:/Sites/Medlemsportal/Medlemsportal/Pdk.Mp2.Website/
LOG: Initial PrivatePath = C:\Sites\Medlemsportal\Medlemsportal\Pdk.Mp2.Website\bin
LOG: Dynamic Base = C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Temporary ASP.NET Files\root\9dcaf1ff
LOG: Cache Base = C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Temporary ASP.NET Files\root\9dcaf1ff
LOG: AppName = 69b46dd
Calling assembly : StructureMap, Version=4.5.1.0, Culture=neutral, PublicKeyToken=null.

I am able to install System.Collections.Immutable 1.1.37 (not 1.2.0 which it asked for in anoter fusion log) and create a binding redicret telling it to use 1.1.37 if asking for a 1.2.0

The problem is that I still get a LoaderException error:

Assembly manager loaded from:  C:\Windows\Microsoft.NET\Framework64\v4.0.30319\clr.dll
Running under executable  c:\windows\system32\inetsrv\w3wp.exe
--- A detailed error log follows. 

=== Pre-bind state information ===
LOG: DisplayName = System.Runtime.Loader, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
 (Fully-specified)
LOG: Appbase = file:///C:/Sites/Medlemsportal/Medlemsportal/Pdk.Mp2.Website/
LOG: Initial PrivatePath = C:\Sites\Medlemsportal\Medlemsportal\Pdk.Mp2.Website\bin
LOG: Dynamic Base = C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Temporary ASP.NET Files\root\9dcaf1ff
LOG: Cache Base = C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Temporary ASP.NET Files\root\9dcaf1ff
LOG: AppName = 69b46dd
Calling assembly : Microsoft.CodeAnalysis.Scripting, Version=2.4.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35.

And this assembly I can't resolve.
As far as I understand, this Nuget package is only for .NET Core projects, and therefore cannot be intsalled into a .NET framework 4.6.1 / 4.7.2 project.

I have been able to add the Nuget package, but it never gets into the project references. And it does not produce a .dll file which I can use.

The versions I am upgrading from and to:

  • EPiServer.CMS 9.6.1 -> 11.5.4
  • EPiServer.CMS.Core 9.6.1 -> 11.5.4
  • EPiServer.CMS.UI 9.6.1 -> 11.5.4
  • EPiServer.CMS.UI.Core 9.6.1 -> 111.5.4
  • EPiServer.Framework 9.6.1 -> 11.5.4
  • EPiServer.Find 12.0.1 -> 12.7.0 (forced by EPiServer.CMS.Core)
  • EPiServer.Find.Cms 12.0.1 -> 12.7.0 (forced by EPiServer.CMS.Core)
  • EPiServer.Find.Cms.AttachementFilter 12.0.1 -> 12.7.0 (forced by EPiServer.CMS.Core)
  • EPiServer.Find.Framework 12.0.1 -> 12.7.0 (forced by EPiServer.CMS.Core)

We also have the following EPiServer packages installed:

  • EPiServer.Labs.LanguageManager 1.5.2.9000 -> 3.1.6
  • EPiServer.LiveMonitor 8.3.1.90000 -> 8.5.0
  • EPiServer.Logging.Log4net 2.0.0 -> 2.2.2
  • EPiServer.Packaging 3.2.4 -> 3.4.0
  • EPiServer.Packaging.UI 3.2.4 -> 3.4.0
  • EPiServer.VisitorGroupsCriteriaPack 1.2.0.9000 -> 2.0.1

Does anyone have any ideas how to solve / debug this problem any further?

#197562
Oct 08, 2018 7:48
Vote:
 

Update:

I have sucessfully updated the project to use EPiServer 11.1.0, and the initialization module ran without any problems.

I then tried to update EPiServer.ServiceLocation.Structuremap, which introduced some structuremap changes, form signed to unsigned.

  • structuremap-signed -> Removed
  • structuremap.web-signed -> Removed
  • StructureMap 4.5.2 -> Installed
  • structuremape.web 4.0.0.315 -> Installed

I followed this article during the upgrade process:

https://world.episerver.com/blogs/Johan-Bjornfot/Dates1/2017/11/episerver-servicelocation-structuremap-2/

Which means I did the following steps:

  • Remove structuremap-signed & structuremap.web-signed from all projects referencing these (5 projects)
  • Remove EPiServer.ServiceLocation.Structuremap 1.0.0 with 'Force uninstall & Remove dependencies' options (1 project had this installed)
  • Installed EPiServer.ServiceLocation.Structuremap 2.0.0 in all projects which previously referenced structuremap-signed & structuremap.web-signed (the 5 projects)

This lead to the initialization module breaking once agan, with the same errors.

#197573
Edited, Oct 08, 2018 13:34
Vote:
 

As you wrote and known, upgrading Find from 9-11 causes upgrading CMS also. And there are a lot of breaking changes inside that.

I suggest you should create a support ticket and send your project to our support and they will help you.

/Son Do

#197819
Oct 15, 2018 9:57
Vote:
 

Mads Gosvig, can you please share how you were able to get past the System.Collections.Immutable and System.Runtime.Loader issues? I am too upgrading a site similar to yours with Epi Find NestedConventions using ForInstancesOf.

I implemented the binding redirects, but I am still stuck on getting past the "Could not load type 'System.Runtime.Loader.AssemblyLoadContext' from assembly 'System.Runtime.Loader, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a." runtime error.

I also removed old StructureMap packages and updated to 4.5.2 and Web 4.0.xxx and still no luck.

Thanks in advance for your assistance. 

#198147
Oct 22, 2018 23:44
Vote:
 

Hi Tom,

Actually I never resolved the issue.

I have reported it to EPiServer Support, and they are currently looking into the problem.

I will post an update as soon as I hear from them.

#198148
Oct 23, 2018 7:32
Vote:
 

To anyone interested in this:

I have debugged this myself, and are in contact with Episerver support to see if I am correct in my assumptions:

The root cause of this problem is these 2 packages:

  • Microsoft.CodeDom.Providers.DotNetCompilerPlatform
  • Microsoft.Net.Compilers

When installed I get the error. When I uninstall them the code does not throw an error, but of course the Razor views cannot be compiled do to C# 6.0 features.

So I tried to install these versions:

  • Microsoft.CodeDom.Providers.DotNetCompilerPlatform (1.0.0)
  • Microsoft.Net.Compilers (1.0.0)

Which we had before the upgrade from 9.6 to 11.X - And this actually removes the error!

So I tried to install these versions:

  • Microsoft.CodeDom (1.0.3) 
  • Microsoft.Net.Compilers (1.3.2) -> This is a dependency.

And once again I get the error.

I have looked into the "ForInstanceOf<>" code from Episerver:

public static UnifiedSearchTypeRulesBuilder<T> ForInstanceOf<T>(this IUnifiedSearchRegistry registry)
{
    Type type = typeof(T);
    // This is okay.
    var assemblies = ((IEnumerable<Assembly>) AppDomain.CurrentDomain.GetAssemblies());

    // this is okay.
    var assembliesWhere = assemblies.Where<Assembly>((Func<Assembly, bool>) (x => !x.IsDynamicAssembly()));

    var assembliesWhereAsList = assembliesWhere.ToList();

    var listOfTestAssemblies = new List<Type[]>();
    foreach (var testAssembly in assembliesWhereAsList)
    {
        // Somewhere in here a System.Runtime.Loader missing dll is thrown.
        var localTestAssemblies = testAssembly.GetTypes();
        listOfTestAssemblies.Add(localTestAssemblies);
        
    }

    return new UnifiedSearchTypeRulesBuilder<T>(listOfTestAssemblies);
}

NOTICE: I have written this code by looking into Episerver code.

The error is thrown when the <assembly>.GetTypes() function is called on this specific assembly:

  • Microsoft.CodeAnalysis.Scripting

This assembly as far as I understand, have started to use System.Runtime.Loader in on of its assemblies.

If you look at the documentation for <assembly>.GetTypes(): 
https://docs.microsoft.com/en-us/dotnet/api/system.reflection.assembly.gettypes?view=netframework-4.7.2

Under exceptions the following is writen:

ReflectionTypeLoadException:

The assembly contains one or more types that cannot be loaded. The array returned by the Types property of this exception contains a Type object for each type that was loaded and null for each type that could not be loaded, while the LoaderExceptions property contains an exception for each type that could not be loaded.

So it seems to me like this behavior is expected from <assembly>.GetTypes(). 
And the fix could be to throw a try/catch block around your call to <assembly>.GetTypes(). 
Otherwise the LoaderException will be thrown (which I don't think is the correct behavior)

Maybe this will be helpful for others.

I will update this post once I hear back from support.

#198932
Nov 09, 2018 7:50
Vote:
 

Thanks for the information!

#198935
Nov 09, 2018 8:19
Vote:
 

Another update:

I resolved my issues on my local development environment by doing as described in my last post.

But after a deploy to the test environment some new LoaderExceptions appeared.

So I have decided to copy the code from the ForInstanceOf<>() and implement it myself, with the try/catch blocks described earlier.

public static class CustomUnifiedSearchRegistryExtension
    {
        public static UnifiedSearchTypeRulesBuilder<T> ForInstanceOfWithErrorHandling<T>(this IUnifiedSearchRegistry registry)
        {
            var logger = ServiceLocator.Current.GetInstance<ILogger>();
            Type type = typeof(T);

            var assemblies = (IEnumerable<Assembly>) AppDomain.CurrentDomain.GetAssemblies();

            var assembliesWhere = assemblies.Where(x => !EPiServer.Find.UnifiedSearch.AssemblyExtension.IsDynamicAssembly(x)).ToList();

            var listOfGetTypes = new List<Type>();
            foreach (var assembly in assembliesWhere)
            {
                try
                {
                    var getMany = assembly.GetTypes() as IEnumerable<Type>;
                    listOfGetTypes.AddRange(getMany);
                }
                catch(ReflectionTypeLoadException reflectionTypeLoadException)
                {
                    // Some assemblies could not be loaded - These are null in Types property.
                    // https://docs.microsoft.com/en-us/dotnet/api/system.reflection.assembly.gettypes?view=netframework-4.7.2

                    logger.Error($"Error: GetTypes() failed to load some Types in Assembly: {assembly.FullName}. Continuing with the ones it could load.", reflectionTypeLoadException);

                    var typesWhichCouldBeLoaded = reflectionTypeLoadException.Types.Where(x => x != null);
                    listOfGetTypes.AddRange(typesWhichCouldBeLoaded);
                }
                catch (Exception e)
                {
                    logger.Error($"Error: GetTypes() failed on the following Assembly: {assembly.FullName}", e);
                }
            }

            var assembliesSelectManyWhere = listOfGetTypes.Where(x =>
            {
                if (type.IsAssignableFrom(x))
                {
                    return !registry.Contains(x);
                }
                return false;
            });

            foreach (var assemblyType in assembliesSelectManyWhere)
            {
                registry.Add(assemblyType);
            }

            return new UnifiedSearchTypeRulesBuilder<T>(registry);
        }
    }

This catches the ReflectionTypeLoaderExceptions and in the property Types should be a list of types that could be loaded, and null entries for Types that could not be loaded. (accordion to the documentation of GetTypes).

This "fixes" my problem, but there are of course still LoaderExceptions but they are handled gracefully.

I have asked Episerver support if this is an acceptable solution.

On a side note:

I looked into what was returned/added to registry.

It turns out that it finds 200 assemblies, and almost 70.000 types, only to find 1 type which is AssignableFrom and that is the Type that the extension was called upon.

So ...ForInstanceOf<TestPage>() only returns Type TestPage.

#198984
Edited, Nov 12, 2018 13:08
Vote:
 

Thanks for the info Mads

#198985
Nov 12, 2018 13:28
Vote:
 

Episerver support have responded, and they agree with my solution to the problem.

So for now the fix for me is to override the ForInstanceOf<>().

#199044
Nov 14, 2018 8:41
This topic was created over six months ago and has been resolved. If you have a similar question, please create a new topic and refer to this one.
* You are NOT allowed to include any hyperlinks in the post because your account hasn't associated to your company. User profile should be updated.