-
How to: define module-specific resource dictionaries in Prism
No CommentsResource dictionaries are a nice way to specify styles, data templates, and similar items in WPF/Silverlight applications. In Prism modules, some times one would expect to define resource dictionaries that are specific to a certain module, as this would avoid exposing module specifics to the rest of the application. Yet, this might not be totally trivial, because resource dictionaries are usually defined in the App.xaml, and the only App.xaml that is used in most Prism applications is the one that belongs to the Shell project. I thought of a possible way to overcome this problem, which I’ll explain below.
But first, let’s examine what happens with those resource dictionaries when they are specified in the Shell’s App.xaml, and why views can consume them even if they’re defined on a separate project. Resources defined in the App.xaml become available in the Application.Resources ResourceDictionary of the current Application class (which can be accessed through the Application.Current static property.) Once they’re available there, they can be consumed from within the XAML as static resources. Since modules are loaded into the same application domain as the Shell project, views defined there can access resources as long as they can be found in the corresponding Application.Resources dictionary.
You may have noticed that some times modules also contain an App.xaml file. This file isn’t loaded when Prism modules are loaded; only one Application class is instantiated, and that’s the one in the Shell project.

So, while defining the resources in the module’s App.xaml file would make them show in the designer, they wouldn’t work when the application is ran. Going backwards a little, Application.Current.Resources can be accessed from anywhere inside the Prism application (even modules), and the ResourceDictionary class supports adding new entries! So you might see where I’m going with this…
The approach
I tried programmatically adding new entries to the Application.Current.Resources dictionary and it worked! So this leaves us with two more needs in my opinion:
- A way to get the resources to add from a XAML resource dictionary file (in the module, of course.)
- A comfortable way to specify that the resources in that file should be loaded.
The first one was actually simple. We can create a new instance of the ResourceDictionary class passing the file’s URI as the source. As for the second one, I thought that perhaps abstracting the whole thing with an attribute might be a good idea. So, I created an attribute named ModuleResourceAttribute, which has a constructor that accepts a string as a parameter; this string should be the location of the XAML file, e.g. “/ModuleA;component/ModuleResources/Resources.xaml”. The idea is that this attribute could be specified on a module, like this:
1 [ModuleResource("/ModuleA;component/ModuleResources/Resources.xaml")] 2 public class ModuleA : IModule 3 { 4 (...) 5 }Then, I created a simple service to load resources into the Application.Current.Resources dictionary:
1 public interface IResourceLoader 2 { 3 void LoadModuleResources(Uri resourceUri); 4 } 5 6 public class ResourceLoader : IResourceLoader 7 { 8 public void LoadModuleResources(Uri resourceUri) 9 { 10 var resources = new ResourceDictionary { Source = resourceUri }; 11 12 foreach (var key in resources.Keys) 13 { 14 Application.Current.Resources.Add(key, resources[key]); 15 } 16 } 17 }Finally, I created a modified implementation of the ModuleInitializer so that it checks if the module has any ModuleResourceAttributes to load the necessary resources prior to module initialization:
1 public class ModifiedModuleInitializer : IModuleInitializer 2 { 3 (...) 4 5 6 public void Initialize(ModuleInfo moduleInfo) 7 { 8 if (moduleInfo == null) throw new ArgumentNullException("moduleInfo"); 9 10 IModule moduleInstance = null; 11 try 12 { 13 moduleInstance = this.CreateModule(moduleInfo); 14 this.LoadResources(moduleInstance); 15 moduleInstance.Initialize(); 16 } 17 catch (Exception ex) 18 { 19 this.HandleModuleInitializationError( 20 moduleInfo, 21 moduleInstance != null ? moduleInstance.GetType().Assembly.FullName : null, 22 ex); 23 } 24 } 25 26 (...) 27 28 protected virtual void LoadResources(IModule moduleInstance) 29 { 30 var attributes = moduleInstance.GetType().GetCustomAttributes(typeof(ModuleResourceAttribute), true).Cast<ModuleResourceAttribute>(); 31 if (attributes != null) 32 { 33 foreach (var attribute in attributes) 34 { 35 var resourceUri = new Uri(attribute.ResourceLocation, UriKind.RelativeOrAbsolute); 36 resourceLoader.LoadModuleResources(resourceUri); 37 } 38 } 39 } 40 }I also created a little sample that portrays this, which can be found in my SkyDrive account under the name ModuleSpecificResourceDictionaries.
I hope you find this handy!
-
Leave a comment
Your email address will not be published.
