Simple C# Application to Remove/Clean Unused/Orphaned Resource file entries

In most of the present day code development, Application Localization is being achieved using RESX files. As time progress, we might not be able to keep track of which key-value pairs of a RESX file are being consumed by the application and which are orphaned. At present, it is very difficult to remove the orphan entries through automation and most of us stick to manual clean up. There are some tools which are capable of cleaning RESX files, but they come with price tag. In this quick tutorial, I am going to show how to clean a resource file through regular C# code.

Lets get started by creating a simple C# Console Application. Add System.Windows.Forms DLL as a reference to the project.

We are going to follow below steps –

  1. Read all existing RESX Entries.
  2. Scan all files in a given project to find if entry is in use or not. Log orphan entries.
  3. Generate new RESX file by ignoring orphan entries.

NOTE: We should not include RESX file or any DLL (bin/obj folder in solution) holding that RESX file in the project which is being scanned. If traces of RESX file present, then below code will find all RESX entries as in consumed state.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Resources;
using System.Collections;

namespace ResxCleanUp
{
    class Program
    {
        static void Main(string[] args)
        {
            // Define Root path
            var rootPath = @"C:\temp\";

            // Define a file where we can log both used/un-used entries 
            var logEntriesPath = rootPath + "entries.txt";

            // Define the Resource file which needs to be cleaned
            var existingResourceFilePath = rootPath + "testOld.resx";

            // Define a new Resource file which will be generated out of only used entries
            var newResourceFilePath = rootPath + "testNew.resx";

            // Define the project folder which will be scanned for each and every RESX file entry.
            // NOTE: We should not include the DLLs (in bin/obj) of the same project or existing RESX files because
            // if they are scanned against the RESX entries, then we cannot find orphan entries
            var projectPath = rootPath + "WebApplication2";


            // Iterate all the keys of existing resource file and put them in Hashtable for later processing
            var resourceEntries = new Hashtable();
            var reader = new ResXResourceReader(existingResourceFilePath);
            foreach (DictionaryEntry d in reader)
            {
                resourceEntries.Add(d.Key.ToString(), d.Value == null ? "" : d.Value.ToString());
            }
            reader.Close();


            // Output the total count of entries in RESX file and number of files to be scanned in a given project
            Console.WriteLine("Total Entries in RESX File : " + resourceEntries.Keys.Count);
            var files = Directory.GetFiles(projectPath, "*", SearchOption.AllDirectories);
            Console.WriteLine("Total Files to be scanned in project : " + files.Count());


            // Iterate all the keys from hashtable and check if the key exists in any of the file present in the project
            var orphanEntries = new List<string>();
            foreach (var key in resourceEntries.Keys)
            {
                var found = 0;
                foreach (var file in files)
                {
                    // If RESX Entry found in a given file
                    if (File.ReadAllText(file).Contains(key.ToString()))
                    {
                        // Log the entry
                        File.AppendAllText(logEntriesPath, key + "---" + Path.GetFileName(file));
                        File.AppendAllText(logEntriesPath, "\r\n");
                        Console.WriteLine(key + "---" + Path.GetFileName(file));
                        found = found + 1;
                        break;
                    }
                }

                // If RESX entry not found, then save it in orphans list
                if (found == 0)
                    orphanEntries.Add(key.ToString());
            }

            // Write orphan Entries to Console and log file
            Console.WriteLine("Unused - ");
            File.AppendAllText(logEntriesPath, "Unused Entries -- ");
            File.AppendAllText(logEntriesPath, "\r\n");

            foreach (var duplicate in orphanEntries)
            {
                File.AppendAllText(logEntriesPath, duplicate);
                File.AppendAllText(logEntriesPath, "\r\n");
                Console.WriteLine(duplicate);
            }

            // Generate a new Resource file by removing orphaned entries
            var resourceWriter = new ResXResourceWriter(newResourceFilePath);
            foreach (String key in orphanEntries)
            {
                resourceEntries.Remove(key);
            }
            foreach (String key in resourceEntries.Keys)
            {
                resourceWriter.AddResource(key, resourceEntries[key]);
            }
            resourceWriter.Generate();
            resourceWriter.Close();

            // CleanUp Completed and wait for further instructions
            Console.WriteLine("Cleanup completed");
            Console.ReadLine();
        }
    }
}

Lets create a simple Test MVC Application and a RESX File as shown below.

image

image

Use the keys – UsedString1 and UsedString2 in HomeController.cs as shown below.

public ActionResult Index()
{
    // simulate RESX entries usage
    ViewBag.UsedString1 = WebApplication2.testOld.UsedString1;
    ViewBag.UsedString2 = WebApplication2.testOld.UsedString2;
    return View();
}

Now remove Resource File and clean bin folder of the solution, take the solution folder with all other content and place it in C:/Temp/. The contents in C:/Temp/ is shown below –

image

Run the console application, output should be as shown below –

image

There will be new RESX file generated in the Temp folder –

image

If we open testNew.resx

image

We can tweak a little more in the code to make it more automated. One place where we can improve is instead of copying the project folder or the RESX files to the Temp folder, we can directly run the console app on the project folder.

Hope this tutorial helps, Happy Coding!!!

You may also like...