Question

We have a job server the processes jobs submitted from websites. The website's assemblies are packaged up and submitted to the job server as part of the job (including all the satellite resource assemblies).

These assemblies are loaded via: Assembly.Load( byte[] ) before job processing starts.

During job processing, we get to a point where we want to snapshot a resource string (i.e. a validation message). Note, that simply storing the resource key isn't an option, because the validation rule/message may change over time and if they come back to view previously processed jobs/messages, I don't want the 'latest' resource string displayed. I need the value at the time of job processing displayed. And I need a snapshot of each supported culture's message so that I can display messages in the appropriate culture that the website thread is running on.

To try and retrieve the messages, the website assembly specifies which locales are supported, so on the job server, I do something like this to create the website assembly's ResourceManager:

var resourceType = Type.GetType( "BTR.Websites.MadHatter.Clients.ABCCorp.Resources.ClientStrings" );
var rm = resourceType != null ? new ResourceManager( resourceType ) : null;

This works fine an I get a ResourceManager, however, given an XElement validationRule and a string[] locales, the following code 'does not work':

validationRule.Add( 
    from l in locales
    let message = rm.GetString( p.Value.ToString(), new CultureInfo( l ) )
    select new XAttribute( string.Format( "Message.{0}", l ), message ) );

Essentially, no matter what CultureInfo I pass in, I get the default (en-US) culture's message.

AppDomain.CurrentDomain.GetAssemblies() shows both the website assembly and satellite assembly loaded:

{ABCCorp.ServiceModel, Version=6.0.4497.18334, Culture=neutral, PublicKeyToken=null} System.Reflection.Assembly {System.Reflection.RuntimeAssembly} {ABCCorp.ServiceModel.resources, Version=6.0.4497.18334, Culture=fr-FR, PublicKeyToken=null} System.Reflection.Assembly {System.Reflection.RuntimeAssembly}

But if I try the following in watch window (same as code above), I get the en-US version:

rm.GetString( "ResourceKey", new CultureInfo( "fr-FR" ) )

Lastly, presummably we are all familiar with LINQPad :) The following script 'works'. I do not have any references to ABCCorp's ServiceModel.dll or satellite dll.

Assembly.LoadFile( @"C:\BTR\Source\ABCCorp.ServiceModel.dll" );
Assembly.LoadFile( @"C:\BTR\Source\fr-FR\ABCCorp.ServiceModel.resources.dll" );
AppDomain.CurrentDomain.GetAssemblies().Dump();
var resourceType = Type.GetType( "BTR.Websites.MadHatter.Clients.ABCCorp.Resources.ClientStrings, ABCCorp.ServiceModel" ).Dump();
var rm = resourceType != null ? new ResourceManager( resourceType ) : null;
rm.GetString( "FlatMemberInformationBasicInformation_DateDeathProof", new CultureInfo( "fr-FR" ) ).Dump();

Any suggestions would be GREATLY appreciated.

UPDATE: Based on suggestions from Joe, here is the code we ended up with. byte[] content is a zip file containing website assembly and all satellite resource assemblies if present.

private Assembly LoadPackagedAssembly( byte[] content )
{
    var loaded = new Dictionary<string, Assembly>();

    AppDomain.CurrentDomain.AssemblyResolve += delegate( object sender, ResolveEventArgs args )
    {
        return loaded.ContainsKey( args.Name )
            ? loaded[ args.Name ]
            : null;
    };

    Assembly mainAssembly = null;

    using ( var stream = new MemoryStream( content ) )
    {
        using ( var zip = new ZipInputStream( stream ) )
        {
            ZipEntry entry;

            while ( ( entry = zip.GetNextEntry() ) != null )
            {
                using ( var buffer = new MemoryStream() )
                {
                    Streams.CopyStream( zip, buffer );

                    var assembly = Assembly.Load( buffer.ToArray() );

                    loaded[ assembly.FullName ] = assembly;

                    if ( mainAssembly == null ) mainAssembly = assembly;
                }
            }
        }
    }

    return mainAssembly;
}
Was it helpful?

Solution

This sounds like an assembly resolution problem. With Load(byte[]) and LoadFile, you end up with no binding context. In short, either use LoadFrom, or handle the AppDomain's AssemblyResolve event to deal with this.

Note that LINQPad handles the AssemblyResolve event to smooth over problems caused by indirectly referencing the same DLL in different locations. A side-effect of this is that it fuses the binding contexts in scenarios where a binding would otherwise fail, making things work that might not otherwise.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top