Jim McCurdy's Tech Blog

Insights into C#/XAML Development; Windows 8 (WinRT), WPF, Windows Phone, Xamarin, Silverlight

Easy access to Silverlight file resources with my ResourceFile class

Silverlight application developers have several choices when deploying file resources for a Silverlight application.  For my own Silverlight application, YinYangMoney, I developed a ResourceFile class to make access to those resource files flexible and simple - regardless of the deployment choice.  I will be sharing that class with you in this blog post.

Background on Silverlight File Resources

As I talk about Silverlight File Resources here, do not confuse them with Silverlight StaticResources defined and used in your XAML files:

<Application.Resources>
    <System:String x:Key="Greeting">Hello World!</System:String>
</Application.Resources>

and can be accessed in XAML as:

<TextBlock Text="{StaticResource Greeting}" />

or in C# code as:

string greeting = Application.Current.Resources["Greeting"];

Silverlight File Resources are different.  They are typically non-executable data files used by your Silverlight application.  This includes resources such as text, XAML, XML, HTML, font, image (JPG or PNG), audio, or video files.  You can organize these resource files in folders, sub-folders, or even package them in ZIP files.  My ResourceFile class will get you to these resources regardless of the file type, size, or folder organization.

Get Started

Start by using Visual Studio to add the resource files to your Silverlight application or library projects.  Organize them in any project folders you want, but I would typically root them under a common Content or Resource folder.  Secondly, configure the files for deployment by setting the Build Action property in the files' Visual Studio Properties Window.  Set the Build Action as appropriate:

  • Content. This build action will include the file in the application XAP package.

  • Resource. This build action will embed the file in the project assembly. This applies to project assemblies that you deploy either inside or outside the application XAP package.

  • None. Use this build action for resource files that you want to retrieve on demand.  You will typically deploy on-demand files at the same server location as your application package.  I am not aware of any case where using this build action is practical.

The Build Action drop-down list also offers several other settings.  However, as of this writing, you can only use Resource, Content, and None with Silverlight projects.  For example, do not use the Embedded Resource build action, which uses a format that Silverlight cannot recognize.

Accessing Silverlight File Resources

Accessing Silverlight file resources that are deployed with different Build Actions (and in or out of ZIP files), normally require different access methods.  However, my ResourceFile class makes these differences transparent to the application, so you can change any file's Build Action without breaking your code's access to its resources.  It also makes accessing file resources in a library assembly very easy.

ResourceFile is a static class with public methods to read entire file resources, or get file streams to read them yourself.  I also added a little helper class which I called From.  The From class is used to indicate the assembly that contains resource files.

The code below has been tested and used with Silverlight 3 and Silverlight 4.  You can also find my simple test project here: ResourceFileTest.zip

using System;
using System.IO;
using System.Reflection;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Markup;
using System.Windows.Media.Imaging;
using System.Windows.Resources;
using System.Xml;

namespace ClassLibrary
{
	// Get resource files, or resource file streams, either from...
	//  o  an assembly in the XAP (Resource build action)
	//  o  the XAP itself (Content build action)
	//
	// Typical usage:
	//
	// Get content directly: bitmaps, file text, fonts, XAML, or XML
	//
	//		BitmapImage bitmap1 = ResourceFile.GetBitmap("Images/Clouds.jpg", From.App);
	//
	// Get content from a ZIP:
	//
	//		BitmapImage bitmap2 = ResourceFile.GetBitmapFromZip("Content/Zips/Files.zip", "Clouds.jpg", From.App);
	//
	// Get content streams:
	//
	//		Stream bitmapStream = ResourceFile.GetStream("Images/Clouds.jpg", From.App);
	//		Stream zipStream = ResourceFile.GetStream("Content/Zips/Files.zip", From.App);

	internal static class ResourceFile
	{
		// Get a bitmap from an assembly or the XAP
		internal static BitmapImage GetBitmap(string relativeUri, From assembly)
		{
			Stream stream = GetStream(relativeUri, assembly);
			return GetBitmapFromStream(stream);
		}

		// Get a bitmap from a zip file in an assembly or the XAP
		internal static BitmapImage GetBitmapFromZip(string relativeZipUri, string relativeUri, From assembly)
		{
			Stream stream = GetStreamFromZip(relativeZipUri, relativeUri, assembly);
			return GetBitmapFromStream(stream);
		}

		// Get a bitmap from a stream
		internal static BitmapImage GetBitmapFromStream(Stream stream)
		{
			if (stream == null)
				return null;

			BitmapImage bitmap = new BitmapImage();
			bitmap.SetSource(stream);
			return bitmap;
		}

		// Get file text from an assembly or the XAP
		internal static string GetFileText(string relativeUri, From assembly)
		{
			Stream stream = GetStream(relativeUri, assembly);
			return GetFileTextFromStream(stream);
		}

		// Get file text from a zip file in an assembly or the XAP
		internal static string GetFileTextFromZip(string relativeZipUri, string relativeUri, From assembly)
		{
			Stream stream = GetStreamFromZip(relativeZipUri, relativeUri, assembly);
			return GetFileTextFromStream(stream);
		}

		// Get file text from a stream
		internal static string GetFileTextFromStream(Stream stream)
		{
			if (stream == null)
				return null;

			using (StreamReader reader = new StreamReader(stream))
			{
				if (reader == null)
					return null;
				return reader.ReadToEnd();
			}
		}

		// Get a font from an assembly or the XAP
		internal static FontSource GetFont(string relativeUri, From assembly)
		{
			Stream stream = GetStream(relativeUri, assembly);
			return GetFontFromStream(stream);
		}

		// Get a font from a zip file in an assembly or the XAP
		internal static FontSource GetFontFromZip(string relativeZipUri, string relativeUri, From assembly)
		{
			Stream stream = GetStreamFromZip(relativeZipUri, relativeUri, assembly);
			return GetFontFromStream(stream);
		}

		// Get a font from a stream
		internal static FontSource GetFontFromStream(Stream stream)
		{
			if (stream == null)
				return null;

			return new FontSource(stream);
		}

		// Get XAML from an assembly or the XAP
		internal static UIElement GetXaml(string relativeUri, From assembly)
		{
			Stream stream = GetStream(relativeUri, assembly);
			return GetXamlFromStream(stream);
		}

		// Get XAML from a zip file in an assembly or the XAP
		internal static UIElement GetXamlFromZip(string relativeZipUri, string relativeUri, From assembly)
		{
			Stream stream = GetStreamFromZip(relativeZipUri, relativeUri, assembly);
			return GetXamlFromStream(stream);
		}

		// Get XAML from a stream
		internal static UIElement GetXamlFromStream(Stream stream)
		{
			if (stream == null)
				return null;

			return XamlReader.Load(GetFileTextFromStream(stream)) as UIElement;
		}

		// Get an XML Reader from an assembly or the XAP
		internal static XmlReader GetXmlReader(string relativeUri, From assembly)
		{
			Stream stream = GetStream(relativeUri, assembly);
			return GetXmlReaderFromStream(stream);
		}

		// Get an XML Reader from a zip file in an assembly or the XAP
		internal static XmlReader GetXmlReaderFromZip(string relativeZipUri, string relativeUri, From assembly)
		{
			Stream stream = GetStreamFromZip(relativeZipUri, relativeUri, assembly);
			return GetXmlReaderFromStream(stream);
		}

		// Get an XML Reader from a stream
		internal static XmlReader GetXmlReaderFromStream(Stream stream)
		{
			XmlReaderSettings settings = new XmlReaderSettings();
			settings.DtdProcessing = DtdProcessing.Ignore;
			settings.IgnoreWhitespace = false;
			settings.IgnoreProcessingInstructions = true;
			settings.IgnoreComments = true;
			try
			{
				return XmlReader.Create(stream, settings);
			}
			catch (Exception ex)
			{
				MessageBox.Show(ex.Message, "Alert", MessageBoxButton.OK);
				return null;
			}
		}

		// Get an XML text string from an XmlReader
		internal static string GetXmlFromXmlReader(XmlReader xmlReader)
		{
			return (xmlReader != null ? xmlReader.ReadOuterXml() : null);
		}

		// Get a resource file stream info from an assembly or the XAP
		internal static StreamResourceInfo GetStreamInfo(string relativeUri, From assembly)
		{
			StreamResourceInfo streamInfo = Application.GetResourceStream(new Uri(relativeUri, UriKind.Relative));
			if (streamInfo != null) // found in the XAP
				return streamInfo;

			string componentFormat = assembly.Name + ";component/{0}";
			string assemblyUri = string.Format(componentFormat, relativeUri);
			streamInfo = Application.GetResourceStream(new Uri(assemblyUri, UriKind.Relative));
			if (streamInfo != null) // found in the assembly
				return streamInfo;

			// Last chance!
			// Resource files added to a VS project as "links" (Add / Existing Item / Add As Link)
			// must be accessed without a path
			int pathMarker = relativeUri.LastIndexOf('/');
			if (pathMarker < 0)
				return null;

			assemblyUri = string.Format(componentFormat, relativeUri.Substring(pathMarker + 1));
			streamInfo = Application.GetResourceStream(new Uri(assemblyUri, UriKind.Relative));
			if (streamInfo != null) // found in the assembly
				return streamInfo;

			return null; // not found
		}

		// Get a resource file stream from an assembly or the XAP
		internal static Stream GetStream(string relativeUri, From assembly)
		{
			StreamResourceInfo streamInfo = GetStreamInfo(relativeUri, assembly);
			if (streamInfo == null)
				return null;

			return streamInfo.Stream;
		}

		// Get a resource file stream from inside a ZIP stream
		internal static Stream GetStreamFromZip(string relativeZipUri, string relativeUri, From assembly)
		{
			Stream zipStream = GetStream(relativeZipUri, assembly);
			if (zipStream == null)
				return null;

			return GetStreamFromZipStream(zipStream, relativeUri);
		}

		// Get a resource file stream from inside a ZIP stream
		internal static Stream GetStreamFromZipStream(Stream zipStream, string relativeUri)
		{
			if (zipStream == null)
				return null;

			StreamResourceInfo zipInfo = new StreamResourceInfo(zipStream, null);
			StreamResourceInfo streamInfo = Application.GetResourceStream(zipInfo, new Uri(relativeUri, UriKind.Relative));
			if (streamInfo == null)
				streamInfo = Application.GetResourceStream(new Uri(relativeUri, UriKind.Relative));
			if (streamInfo == null)
				return null;

			return streamInfo.Stream;
		}
	}

	// Calls to access file resources are assisted by a tiny static class called "From"
	// which is used to indicate the assembly that contains resource files.
	//	From.App - the current application assembly
	//	From.This - the caller's assembly
	//	From.Library - a library assembly
	//	From.Type<ClassLibrary> - an assembly containing a known type
	internal class From
	{
		internal Assembly Assembly { get; set; }
		internal string Name { get { return (Assembly != null ? Assembly.FullName.Substring(0, Assembly.FullName.IndexOf(',')) : null); } }

		// From current application assembly
		internal static From App { get { return new From() { Assembly = Application.Current.GetType().Assembly }; } }

		// From the caller's assembly
		internal static From This { get { return new From() { Assembly = Assembly.GetCallingAssembly() }; } }

		// From a library assembly
		internal static From Library { get { return new From() { Assembly = Assembly.GetExecutingAssembly() }; } }

		// From an assembly containing a known type
		internal static From Type<TT>() { return new From() { Assembly = typeof(TT).Assembly }; }
	}
}

As a side note, I discovered a shortcut to accessing embedded font resources directly from XAML:

	<TextBlock x:Name="x_TestText" FontFamily="CacMoose.ttf#CAC Moose" /> 
or… 
	<TextBlock x:Name="x_TestText" FontFamily="Content/Zips/Files.zip#CAC Moose" />


You will need to set the CacMose.ttf file's Build Action to Resource.

If you want to read more about Silverlight resource files, check this out from MSDN: http://msdn.microsoft.com/en-us/library/cc296240(VS.95).aspx

Comments (5) -

  • James

    4/21/2010 4:08:54 PM |

    Hi Jim,

    Thank you for posting this helpful utility class. There doesn't seem to be a license associated with this class... I just want to be sure that I have permission to use/modify it in my applications.

    Cheers,
    James

    • Jim McCurdy

      4/21/2010 4:48:27 PM |

      James,

      Yes, you are free to use the code as you wish.  Let me know if you come up with any fixes or improvements.

      Jim

  • Pablo

    8/27/2010 11:52:32 AM |

    Excellent!

    keep up with the good work James.

  • Joseph Gershgorin

    10/11/2010 8:58:41 AM |

    Thanks for the helper Jim!

    I modified some a few signatures to be more generic. I need to get a ResourceDictionary from a XAML source, not a UI Element.

    For example:

    // Get XAML from an assembly or the XAP
        public static T GetXaml<T>(string relativeUri, From assembly) where T : class
        {
          Stream stream = GetStream(relativeUri, assembly);
          return GetXamlFromStream<T>(stream);
        }



        // Get XAML from a zip file in an assembly or the XAP
        public static T GetXamlFromZip<T>(string relativeZipUri, string relativeUri, From assembly) where T : class
        {
          Stream stream = GetStreamFromZip(relativeZipUri, relativeUri, assembly);
          return GetXamlFromStream<T>(stream);
        }

        // Get XAML from a stream
        public static T GetXamlFromStream<T>(Stream stream) where T: class
        {
          if (stream == null)
            return null;

          return XamlReader.Load(GetFileTextFromStream(stream)) as T;
        }

    Then I can call it like this:
    var resourceDictionary = ResourceFile.GetXaml<ResourceDictionary>("Resources/MyResourceFile.xaml", From.This);

  • Astrodonkey

    12/24/2010 9:27:21 PM |

    Dude.  Well done.  This should have been part of Silverlight.

Comments are closed