Click here to Skip to main content
15,912,578 members
Articles / Programming Languages / C#

An Easy Implementation of the ResourceManager and ResourceProvider

Rate me:
Please Sign up or sign in to vote.
3.67/5 (4 votes)
25 Apr 2011CPOL4 min read 16.6K   13   3
A class that makes the .NET ResourceManager easier to use and more versatile than straight out of the box.

Introduction

Resource files in Visual Studio are a good way of adding multicultural support to your application, as well as bundling images and other assets you don't want to distribute as loose files. However, using resource files in your code can often be a frustrating experience. To alleviate this pain, I created a ResourceProvider service that I use to abstract away the interaction with the resource files. It will make the ResourceManager more flexible and easy to use.

Using the code

Before we get into the code, let's quickly go over how to create a resource file in the first place. Create a new project in Visual Studio and call it ResourceManagerDemo. (BTW, it's best to keep your resources isolated in a single project and use the ResourceProvider to gain access to them. This helps speed up builds because these resource files take a long time to build and rarely change. In mature systems, I go so far as to create a separate solution just for the resource file, then just link to the resulting DLL in the real app.)

In your new project, right click the project and select Add New Item.

Example - Add New Item

Then choose General -> Resources File.

Example - Add New Resource File

For the demo, you can take the default name of Resources1.resx. Double click this file to open it. For this demo, we are going to just add a few strings, so show strings in the resource file and add a few.

Example - Show Strings in Resource File

It's best to use constants to refer to your resource strings to avoid encountering runtime errors should a resource not exist or you mistype a name. Constants are not only a good programming practice so you don't have "magic strings" throughout your app, but in our case, it can also help with our unit testing. Note that the values of the constants must match exactly the names of the resources you added to the resource file.

Create a class called ResourceNames and paste in the following code. Note that the values of the string need to match the names of the strings in the resource file.

C#
namespace ResourceManagerDemo
{
    public static class ResourceNames
    {
        /// <summary>
        /// These values MUST match the names of the items in the resource file
        /// </summary>
        public const string StringResource1 = "StringResource1";
        public const string StringResource2 = "StringResource2";
    }
}

Now paste the following code into a ResourceProvider.cs file.

C#
using System.Reflection;
using System.Resources;
 
namespace ResourceManagerDemo
{
    public class ResourceProvider
    {
        internal Assembly _currAssembly;
 
        internal ResourceManager StringManager
        {
            get { return _stringManager ?? 
                (_stringManager = new ResourceManager(
                    "ResourceManagerDemo.Resource1", _currAssembly)); }
        }
        internal ResourceManager _stringManager;
 
        public ResourceProvider()
        {
            _currAssembly = Assembly.GetExecutingAssembly();
        }
 
        public string GetString(string pIconName)
        {
            var obj = StringManager.GetObject(pIconName);
            var str = obj as string;
            return str;
        }
    }
}

Most of the code is standard except for a few interesting things. First, we have an on-demand property that resolves the IconManager. We could have resolved this in the constructor of the class, but that means it would be created and the resource file opened even if no resources were used. Opening these files can be an expensive operation, especially when we are creating a new ResourceProvider in every test we are going to write. Having the on-demand property resolve it only when needed can save time when you have thousands of tests to run.

The main reason for this demo, however, is the resource manager usage, so let's look at that. There are two parameters to the ResourceManager constructor that we need to pass in. The first is the name of the resource file, including the namespace. A convenient way to find the proper name is to build the project and look in the obj\debug folder.

Where to find the compiled Resource File

See that file with the type "resources"? That name (without the "resources" extension) is what you need to put into the first parameter.

The second parameter is the assembly that contains the resources. Assuming your provider lives in the same project that the resources live in, you can just get the currently executing assembly.

Now, let's make sure our provider works. Create a file called ResourceProviderTest.cs and paste in the following code (you will need a reference to the NUnit.Framework DLL to use this test).

C#
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using ResourceManagerDemo;
 
namespace Finnav.Shared.Resources.UnitTest
{
    [TestFixture]
    public class ResourceProviderTest
    {
        private ResourceProvider _provider;
 
        [SetUp]
        public void Setup()
        {
            _provider = new ResourceProvider();
        }
 
        [Test]
        public void Setup_Succeeds()
        {
            Assert.IsTrue(true);
        }
 
        [Test]
        public void Constructor_whenCalled_CurrAssemblyIsNotNull()
        {
            Assert.IsNotNull(_provider._currAssembly);
        }
 
        [Test]
        public void Constructor_whenCalled_IconManagerLocalVarIsNull()
        {
            Assert.IsNull(_provider._stringManager);
        }
 
        [Test]
        public void IconManager_WhenReferenced_CanBeResolved()
        {
            Assert.IsNotNull(_provider.StringManager);
        }
 
        [Test]
        public void GetIcon_WhenCalledWithBadIconName_ReturnsEmptyString()
        {
            var str = _provider.GetString("this icon does not exist");
            Assert.IsTrue(string.IsNullOrEmpty(str));
        }
 
        [Test]
        public void GetIcon_WhenCalledWithValidIconName_ReturnsIcon()
        {
            var str = _provider.GetString(ResourceNames.StringResource1);
            Assert.IsFalse(string.IsNullOrEmpty(str));
        }
 
        [Test]
        public void ResourceIcons_ForAllDefinedIcons_IconIsReturned()
        {
            var errLst = new List<string>();
            foreach (var field in typeof(ResourceNames).GetFields())
            {
                var str = _provider.GetString(field.Name);
                if (str == null) errLst.Add(field.Name);
            }
            var errStr = errLst.Aggregate(
                string.Empty, (current, s) => current + (s + " ;"));
            Assert.AreEqual(0, errLst.Count, errStr);
        }
    }
}

Most of these are standard tests, but let's look more closely at the last one. This one is important because it ensures that all of the constants you have defined in your constant file can actually be resolved. Using Reflection, we loop through all the constants and try to resolve each one, gathering a list of the offenders. Finally, if that list has any entries, our test fails and we print a message listing which of the constants has no corresponding resource.

So, there you have it. We've abstracted the references to the ResourceManager to make it easier. Notice that it would not take much work to internationalize this so that it returns the proper string for the current culture. Also, you can separate out the resource files for images to make them easier to manage, while still using this single class to get access.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Chief Technology Officer Financial Navigator
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralMy vote of 3 Pin
abdurahman ibn hattab1-May-11 7:25
abdurahman ibn hattab1-May-11 7:25 
GeneralNice article Pin
kornman0025-Apr-11 13:55
kornman0025-Apr-11 13:55 
GeneralRe: Nice article Pin
bradirby26-Apr-11 2:12
bradirby26-Apr-11 2:12 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.