Is there a method to load a specific assembly?

Mar 7, 2013 at 1:20 PM
Edited Mar 7, 2013 at 4:12 PM
In a somewhat divergent path from my previous endeavors, I've set up a service which downloads .resx files, converts them to a binary .resources file, then using al.exe to create an assembly file out of it then loading the assembly at runtime with Assembly.Load(). All of that seems to work nicely, but I have not been successful in actually getting this extension to see it.

I found this method: WPFLocalizeExtension.Providers.ResxLocalizationProvider.Instance.UpdateCultureList()

I believe this method may not do what I expect it to do because within this line:
var possiblePrefixes = new List<string>(assembly.GetTypes().Select((t) => t.Namespace).Distinct());
assembly.GetTypes() returns 0 types. I expected the VS-generated assemblies to pass this but it does not so that leads me to believe that I am approaching this from the wrong direction.


*edited for additional info
Mar 8, 2013 at 4:44 PM
Edited Mar 8, 2013 at 4:52 PM
So, I really just needed this to work so I made some modifications to ResxLocalizationProviderBase.cs to support loading loading generated satellite assemblies at runtime which have no connection to any assemblies already loaded. GetResourceManager() now makes a huge assumption that we know what we're doing and creates a ResourceManager when we are asking to update the culture list with a satellite assembly that doesn't have any Type, static Class, or Namespace. This is most likely highly egregious and I hope the developers can chime in with a proper way to do this, but here it is for anybody who may be desperate.


tl;dr inverted the IF where we check if foundResource == null so we skip over much of the logic that is not needed if we are just creating a new ResourceManager.

ResxLocalizationProviderBase.GetResourceManager

    protected ResourceManager GetResourceManager(string resourceAssembly, string resourceDictionary) {
      PropertyInfo propInfo;
      MethodInfo methodInfo;
      Assembly assembly = null;
      ResourceManager resManager;
      string foundResource = null;
      string resManagerNameToSearch = "." + resourceDictionary + ResourceFileExtension;
      string[] availableResources;
      var resManKey = resourceAssembly + resManagerNameToSearch;

      if (!TryGetValue(resManKey, out resManager)) {
        // if the assembly cannot be loaded, throw an exception
        try {
          // go through every assembly loaded in the app domain
          var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies();
          foreach (var assemblyInAppDomain in loadedAssemblies) {
            // check if the name pf the assembly is not null
            if (assemblyInAppDomain.FullName != null) {
              // get the assembly name object
              AssemblyName assemblyName = new AssemblyName(assemblyInAppDomain.FullName);

              // check if the name of the assembly is the seached one
              if (assemblyName.Name == resourceAssembly) {
                // assigne the assembly
                assembly = assemblyInAppDomain;

                // stop the search here
                break;
              }
            }
          }

          // check if the assembly is still null
          if (assembly == null) {
            // assign the loaded assembly
#if SILVERLIGHT
                        var name = new AssemblyName(resourceAssembly);
                        assembly = Assembly.Load(name.FullName);
#else
            assembly = Assembly.Load(new AssemblyName(resourceAssembly));
#endif
          }
        } catch (Exception ex) {
          throw new Exception(string.Format("The Assembly '{0}' cannot be loaded.", resourceAssembly), ex);
        }

        // get all available resourcenames
        availableResources = assembly.GetManifestResourceNames();

        // The proposed approach of Andras (http://wpflocalizeextension.codeplex.com/discussions/66098?ProjectName=wpflocalizeextension)
        var possiblePrefixes = new List<string>(assembly.GetTypes().Select((t) => t.Namespace).Distinct());

        for (int i = 0;
          i < availableResources.Length;
          i++) {
          if (availableResources[i].EndsWith(resManagerNameToSearch)) {
            var matches = possiblePrefixes.Where((p) => availableResources[i].StartsWith(p + "."));
            if (matches.Count() != 0) {
              // take the first occurrence and break
              foundResource = availableResources[i];
              break;
            }
          }
        }

        // NOTE: Inverted this IF (nesting is bad, I know) so we just create a new ResourceManager.  -gen3ric
        if (foundResource != null) {
          // remove ".resources" from the end
          foundResource = foundResource.Substring(0, foundResource.Length - ResourceFileExtension.Length);

          //// Resources.{foundResource}.ResourceManager.GetObject()
          //// ^^ prop-info      ^^ method get

          try {
            // get the propertyinfo from resManager over the type from foundResource
            var resourceManagerType = assembly.GetType(foundResource);

            // check if the resource manager was found.
            // if not, assume that the assembly was build with VisualBasic.
            // in this case try to manipulate the resource identifier.
            if (resourceManagerType == null) {
#if SILVERLIGHT
                        var assemblyName = resourceAssembly;
#else
              var assemblyName = assembly.GetName().Name;
#endif
              resourceManagerType = assembly.GetType(foundResource.Replace(assemblyName, assemblyName + ".My.Resources"));
            }

            propInfo = resourceManagerType.GetProperty(ResourceManagerName, ResourceBindingFlags);

            // get the GET-method from the methodinfo
            methodInfo = propInfo.GetGetMethod(true);

            // get the static ResourceManager property
            object resManObject = methodInfo.Invoke(null, null);

            // cast it to a ResourceManager for better working with
            resManager = (ResourceManager)resManObject;
          } catch
            (Exception
              ex) {
            // this error has to get thrown because this has to work
            throw new InvalidOperationException("Cannot resolve the ResourceManager!", ex);
          }
        } else {
          resManager = new ResourceManager(resManagerNameToSearch, assembly);
        }
        // if no one was found, exception
        if (resManager == null) {
          throw new ArgumentException(string.Format("No resource manager for dictionary '{0}' in assembly '{1}' found! ({1}.{0})", resourceDictionary, resourceAssembly));
        }

        // Add the ResourceManager to the cachelist
        Add(resManKey, resManager);

        try {
#if SILVERLIGHT
                    var cultures = CultureInfoHelper.GetCultures();

                    foreach (var c in cultures)
                    {
                        var dir = c.Name + "/";

                        foreach (var p in Deployment.Current.Parts)
                            if (p.Source.StartsWith(dir))
                            {
                                AddCulture(c);
                                break;
                            }
                    }
#else
          var assemblyLocation = Path.GetDirectoryName(assembly.Location);

          // Get all directories named like a specific culture.
          var dirs = Directory.GetDirectories(assemblyLocation, "??-??").ToList();
          // Get all directories named like a culture.
          dirs.AddRange(Directory.GetDirectories(assemblyLocation, "??"));

          // Get the list of all cultures.
          var cultures = CultureInfo.GetCultures(CultureTypes.AllCultures);

          foreach (var c in cultures) {
            var dir = Path.Combine(assemblyLocation, c.Name);
            if (Directory.Exists(dir) &&
              Directory.GetFiles(dir, "*.resources.dll").Length > 0) {
              AddCulture(c);
            }
          }
#endif
        } catch {
          // This may lead to problems with Silverlight
        }
      }
      // return the found ResourceManager
      return resManager;
    }
How I am generating an assembly and updating Localization:
      var psi = new ProcessStartInfo(Path.Combine(workingDir, "al.exe")) {
        CreateNoWindow = true,
        RedirectStandardOutput = true,
        UseShellExecute = false,
        Arguments = args
      };
      var process = new Process {
        StartInfo = psi
      };
      ThreadPool.QueueUserWorkItem(delegate {
        process.Start();
        while (!process.StandardOutput.EndOfStream) {
          Debug.WriteLine(process.StandardOutput.ReadLine());
        }
        if (process.WaitForExit(15000)) {
          // completed
          Debug.WriteLine("Completed");
          var asm = Assembly.LoadFrom({{path to resources assembly}}));
          // NOTE: These ResourceAssembly & ResourceDictionary strings might be a little confusing for the ignorant. -gen3ric
          WPFLocalizeExtension.Providers.ResxLocalizationProvider.Instance.UpdateCultureList("Namespace.resources", "Localization.UI.{0}".FormatWith(file.LanguageTag));          
        } else {
          // timeout
          Debug.WriteLine("Timeout");
        }
      });
Coordinator
Mar 15, 2013 at 8:20 AM
Hi, thanks for the modification. Would you be so kind and send us a pull request for this?

https://github.com/SeriousM/WPFLocalizationExtension

Thanks!
Coordinator
Apr 5, 2013 at 10:13 AM
Hi GEN3RIC,

I've built in your addition. Thank you.

Uwe
Apr 5, 2013 at 1:09 PM
Ergh, sorry for not following up. Not being fully versed in the project, I am curious to know if this passes any unit tests you have for this. I have only utilized it in my own capacity so far.
Coordinator
Apr 5, 2013 at 1:14 PM
As far as it is an addition to the existing code it does not hit the usual test cases.
I would appreciate if you could break down your specific test case to a simple demo app that we can include in our tests.