Search code examples
c#winformsresxoneclick

C# System.IO.FileNotFoundException when trying to load resources (.Net Framework 4.8)


I have a project (it started as legacy code) that I have been working on sometime. Recently I needed to publish the latest version so that it could be used on a separate computer. Long story short using the one-click publisher via visual studios, I kept getting a System.IO.FileNotFoundException. I used the VS debugger to track down the location of the bug. After attaching an assemblyResolve handler I discovered the issue was coming from this line of code:

this.imlProcesses.ImageStream = ((System.Windows.Forms.ImageListStreamer)(resources.GetObject("imlProcesses.ImageStream")));

The code path to get here is as follows. Main() runs frmMain (a partial class of MainForm that represents a windows form). This function then calls the InitalizeComponent(). One of the very first few lines of code the componentResourceManager is set up using the line:

System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(frmMain));

Then it goes through, doing normal window form designer set up, loading and instantiating all the GUI components until it gets to the imlProcesses line shown above. It gets here, and this is where it crashes.

From my understanding, this is caused by the resource (.resx) file not being located. I went to try and mess around in the publishing settings to see if I could ensure the MainForm.resx was indeed included. While doing such, I went to test the application, and ran it in debug mode (note the only thing I have changed so far was build settings, no code) and I now get the same IO.FileNotFoundException, in the same spot! So I revert all the changes I made, cleaned, and rebuilt the program, however I still get the error!

The code that is causing the issues currently is legacy code, and has had no problem up until now. I can still pull my last version control save, and I matched the resx properties from the old version to the new version and still, nothing fixed. At this point I am feeling lost on a solution to this one. Even if I just update my last version control save to be at the same functionality as my current project is at, I still run into the issue of not being able to export the project to a different laptop.

I am new to C# and .net, and any or all thoughts/suggestions are more than appreciated, as I am out of ideas. Thanks!

Some things I have tried: Reverted .resx properties back to previous version control saves.

Attempted different variations of properties. (The original save had build action set to Embedded Resource and Copy to output as Do Not Copy. This is what the current project is set at atm)

There has been a whole lot more, however it was all trial and error and not really sure what Ive gone through

------------------------------------------ Updates --------------------------------------------------

So I have tracked the issue down to being related to the auto generated code from winforms. The ComponentResourceManager as well as imlProcess are both auto generated lines.

I used DotPeek to check resources that are included in the project. Under PixyControl->Resources->EA.PixyControl.frmMain.resources was indeed included (the name space I am referencing from is EA.PixyControl).

My current solution is replacing any refrence to the resources.GetObject with the following code:

using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("EA.PixyControl.frmMain.resources"))
            {
                using (var resourceReader = new ResourceReader(stream))
                {
                    var resourceDict = resourceReader.Cast<DictionaryEntry>().ToDictionary(entry => entry.Key.ToString(), entry => entry.Value);
                    this.imlProcesses.ImageStream = (System.Windows.Forms.ImageListStreamer)resourceDict["imlProcesses.ImageStream"];
                }
            }

I would like to find a different solution as any time I update the window forms, my code is deleted (as it is modifying the auto generated portion).

I feel like the answer is in my error System.IO.FileNotFoundException: 'Could not find file 'PixyControl.resources'.

I would think it would be searching in PixyControl.Resources and not PixyControl.resources.

Here is a screen shot of the dotPeek breakdown for the resources I am trying to access: Current break down of resources attempted to be accessed


Solution

  • I have discovered the solution if anyone is curious. It turns out this is a known bug in C# .Net Framework 4.8 and a fix was put out in the .Net core 5. However I had the unfortunate case of needing to stick with 4.8.

    See the following githubs outlining the bug:

    Summary of the bug (in my limited understanding): C# is supposed to look for the resources they create in local assemblies (may be satallite, not too sure). Anyways, for whatever reason, it may end up starting to search in the wrong area first. Internally, this is supposed to be handled by C# to go to the other locations, and check in there four the resource files. However, for whatever reason, the error isn't handled and it is passed back up to the user.

    Solution:

    In my Main() I added an assembly resolve event handler:

    AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
    

    The event handler function:

    private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
        {
            var resourceName = new AssemblyName(args.Name).Name + ".dll";
            var resource = Array.Find(Assembly.GetExecutingAssembly().GetManifestResourceNames(), element => element.EndsWith(resourceName));
    
            using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resource))
            {
                if (stream == null) return null;
                var assemblyData = new byte[stream.Length];
                stream.Read(assemblyData, 0, assemblyData.Length);
                return Assembly.Load(assemblyData);
            }
        }
    

    The idea is that if the error of not finding the right assembly occurs, to load the resources manually. Simple solution that took way too long to figure out. Gotta love programming:)