I promised a couple weeks ago
that I'd start a series where I would discuss little known features of the .NET Framework. In this first installment, I'll be talking about including resources in a .NET assembly. To be honest, I don't know why people don't use this feature more often than they do. Resources contained within binaries is not a new feature introduced in .NET after all... they've been the basis of Windows programming since before Petzold wrote his first book. What is newer in .NET however is the variety of files that you can include as a resource, and also the ease with which you can gain access to them. When I say variety... I mean variety. You can include literally any type of file as a resource that you'd like to. How's that for flexible?
So before I go into how you do this... let's answer another basic question. Why would you want to include a file as a resource? There are several reasons why you might want to which I'll go over (and this is by no means exhaustive).
Internationalization: .NET includes an easy way to internationalize your text, and part of that is having separate assemblies for each locale. But what if you have more than just string constants that need to be translated? Perhaps you have images that include text in the image, or you have a rich text file that you want to display that needs to be translated. Those can be included inline in the translated assembly, allowing you to make use of .NET's ability to load the correct version of your assembly for the locale of the system, and get at the correct translated resource.
Ease of Installation and Safety: Some of the more annoying parts of writing software is making sure that you get all the proper components on the target system, and then having to deal with those files being removed when they shouldn't. By including required files that don't need to be modified (like an XML schema) in your assembly, you reduce the number of files that need be copied to the target system, and you also don't have to worry about those files going missing later. There's nothing worse than dealing with people who decide they want to clear up 50 KB on a computer that has a 50 GB hard drive by removing "unnecessary" files.
Default Configurations: This is one of my favorite uses. I generally don't like to use the .NET config file mechanism for storing configuration settings, because I find it ugly. It's really not that hard to roll your own simpler XML scheme, and sometimes you might be required not to use the .NET config file mechanism(as I have been in the past). But what happens if that settings file does go missing? You'd like to still include a set of default settings... so how do you do it? Well, one possibility is to sprinkle those defaults across all your classes in the form of constants, and lines of code in constructors. The problem with that solution is that its a pain in the butt to go around and find them later if they need to change. Wouldn't it be smarter to have two versions of your configuration file? One that has the defaults, and one that has the user settings? That way you can load your defaults using the code that you wrote to read in user settings, and then they're also all centralized. As you'll see in a second, you can put that defaults file in the assembly as a resource, and then read it just like any other file later.
So how is this done? Well, the first thing you want to do is make sure that you have your project's default namespace setup correctly. The Visual Studio Compiler uses this to set the namespace of your resource in the assembly, which is what you'll use to gain access to it.
Then, add each file that you want to be a resource to your project, and for each one set it's build action to embedded resource. In this example, the file has to be be referenced as "SummerCampProductions.ResourceDemo.Config.xml". If you add a folder to your project, then the folder name will be used as a child namespace, and any files under that will be in that child namespace.
That's it... that's all there is to adding a file as a resource to your assembly. But now how do you gain access to it in your code? Here is some example code that checks for a configuration file in the executable directory, and if it's missing, loads the defaults from an embedded resource file.
Stream SettingsStream = null;
string UserSettings = Path.Combine( Environment.CurrentDirectory, "Config.xml" );
SettingsStream = new FileStream( UserSettings, FileMode.Open, FileAccess.Read );
string DefaultSettings = "SummerCampProductions.ResourceDemo.Config.xml";
SettingsStream = Assembly.GetEntryAssembly().GetManifestResourceStream( DefaultSettings );
XmlDocument SettingsDoc = new XmlDocument();
SettingsDoc.Load( SettingsStream );
// Load Settings Here