views:

140

answers:

2

Is there an easy way to dynamically discover all the XAMLs files within all the currently loaded modules (specifically of a Silverlight Prism application)? I am sure this is possible, but not sure where to start.

This has to occur on the Silverlight client: We could of course parse the projects on the dev machine, but that would reduce the flexibility and would include unused files in the search.

Basically we want to be able to parse all XAML files in a very large Prism project (independent of loading them) to identify all localisation strings. This will let us build up an initial localisation database that includes all our resource-binding strings and also create a lookup of which XAML files they occur in (to make editing easy for translators).

Why do this?: The worst thing for translators is to change a string in one context only to find it was used elsewhere with slightly different meaning. We are enabling in-context editing of translations from within the application itself.

Update (14 Sep):

The standard method for iterating assemblies is not available to Silverlight due to security restrictions. This means the only improvement to the solution below would be to cooperate with the Prism module management if possible. If anyone wants to provide a code solution for that last part of this problem there are points available to share with you!

Follow-up:

Iterating content of XAP files in a module-base project seems like a really handy thing to be able to do for various reasons, so putting up another 100 rep to get a real answer (preferably working example code). Cheers and good luck!

Partial solution below (working but not optimal):

Below is the code I have come up with, which is a paste together of techniques from this link on Embedded resources (as suggested by Otaku) and my own iterating of the Prism Module Catalogue.

  • Problem 1 - all the modules are already loaded so this is basically having to download them all a second time as I can't work out how to iterate all currently loaded Prism modules. If anyone wants to share the bounty on this one, you still can help make this a complete solution!

  • Problem 2 - There is apparently a bug in the ResourceManager that requires you to get the stream of a known resource before it will let you iterate all resource items (see note in the code below). This means I have to have a dummy resource file in every module. It would be nice to know why that initial GetStream call is required (or how to avoid it).

    private void ParseAllXamlInAllModules()
    {
        IModuleCatalog mm = this.UnityContainer.Resolve<IModuleCatalog>();
        foreach (var module in mm.Modules)
        {
            string xap = module.Ref;
            WebClient wc = new WebClient();
            wc.OpenReadCompleted += (s, args) =>
            {
                if (args.Error == null)
                {
                    var resourceInfo = new StreamResourceInfo(args.Result, null);
                    var file = new Uri("AppManifest.xaml", UriKind.Relative);
                    var stream = System.Windows.Application.GetResourceStream(resourceInfo, file);
                    XmlReader reader = XmlReader.Create(stream.Stream);
                    var parts = new AssemblyPartCollection();
                    if (reader.Read())
                    {
                        reader.ReadStartElement();
                        if (reader.ReadToNextSibling("Deployment.Parts"))
                        {
                            while (reader.ReadToFollowing("AssemblyPart"))
                            {
                                parts.Add(new AssemblyPart() { Source = reader.GetAttribute("Source") });
                            }
                        }
                    }
                    foreach (var part in parts)
                    {
                        var info = new StreamResourceInfo(args.Result, null);
                        Assembly assy = part.Load(System.Windows.Application.GetResourceStream(info, new Uri(part.Source, UriKind.Relative)).Stream);
                        // Get embedded resource names
                        string[] resources = assy.GetManifestResourceNames();
                        foreach (var resource in resources)
                        {
                            if (!resource.Contains("DummyResource.xaml"))
                            {
                                // to get the actual values - create the table
                                var table = new Dictionary<string, Stream>();
                                // All resources have “.resources” in the name – so remove it
                                var rm = new ResourceManager(resource.Replace(".resources", String.Empty), assy);
                                // Seems like some issue here, but without getting any real stream next statement doesn't work....
                                var dummy = rm.GetStream("DummyResource.xaml");
                                var rs = rm.GetResourceSet(Thread.CurrentThread.CurrentUICulture, false, true);
                                IDictionaryEnumerator enumerator = rs.GetEnumerator();
                                while (enumerator.MoveNext())
                                {
                                    if (enumerator.Key.ToString().EndsWith(".xaml"))
                                    {
                                        table.Add(enumerator.Key.ToString(), enumerator.Value as Stream);
                                    }
                                }
                                foreach (var xaml in table)
                                {
                                    TextReader xamlreader = new StreamReader(xaml.Value);
                                    string content = xamlreader.ReadToEnd();
                                    {
                                        // This is where I do the actual work on the XAML content
                                    }
                                }
                            }
                        }
                    }
                }
            };
            // Do the actual read to trigger the above callback code
            wc.OpenReadAsync(new Uri(xap, UriKind.RelativeOrAbsolute));
        }
    }
    
+3  A: 

Use GetManifestResourceNames reflection and parse from there to get only those ending with .xaml. Here's an example of using GetManifestResourceNames: Enumerating embedded resources. Although the sample is showing how to do this with a seperate .xap, you can do this with the loaded one.

Otaku
@Otaku: Thanks for the assembly enumeration tip, but I really need a complete Prism answer that will process all *loaded* modules/assemblies. How do I access the collection of all loaded modules/assemblies in Prism? Cheers
Enough already
@Otaku: Ok, I have figured out a (not very neat) way of doing this using the ModuleCatalog and the sample you suggested and I will update the question to include the code. Hopefully someone will be able to point out a way of improving it as it is downloading modules already present. Thanks
Enough already
@HiTech Magic: Good to hear you got something working. I haven't worked that much with Prism, but would image the resulting code would be the same or nearly the same as you're looking for what is *currently loaded* and Resources contains all that offered by Prism as well.
Otaku
@Otaku: +1 (for now... more to come) for the embedded resource link. I have updated the question with the code I have worked out so far and listed the 2 problems that remain.
Enough already
@Otaku: +100. In the absence of a complete solution, you certainly provided very useful links and helped me produce a working first version. Thanks for that.
Enough already
@HiTech Magic: Thanks! Sorry I couldn't be of more assistance on the Prism side of things. I also looked at `var dummy = rm.GetStream("DummyResource.xaml");` and couldn't find a way around it, at least not easily discoverable. If you ever did get a solution that solves the two problems, it would be great if the code could be posted here.
Otaku
+2  A: 

I've seen people complain about some pretty gross bugs in Prism

Disecting your problems:

Problem 1: I am not familiar with Prism but from an object-oriented perspective your Module Manager class should keep track of whether a Module has been loaded and if not already loaded allow you to recursively load other Modules using a map function on the List<Module> or whatever type Prism uses to represent assemblies abstractly. In short, have your Module Manager implement a hidden state that represents the List of Modules loaded. Your Map function should then take that List of Modules already loaded as a seed value, and give back the List of Modules that haven't been loaded. You can then either internalize the logic for a public LoadAllModules method or allow someone to iterate a public List<UnloadedModule> where UnloadedModule : Module and let them choose what to load. I would not recommend exposing both methods simultaneously due to concurrency concerns when the Module Manager is accessed via multiple threads.

Problem 2: The initial GetStream call is required because ResourceManager lazily evaluates the resources. Intuitively, my guess is the reason for this is that satellite assemblies can contain multiple locale-specific modules, and if all of these modules were loaded into memory at once it could exhaust the heap, and the fact these are unmanaged resources. You can look at the code using RedGate's .NET Reflector to determine the details. There might be a cheaper method you can call than GetStream. You might also be able to trigger it to load the assembly by tricking it by loading a resource that is in every Silverlight assembly. Try ResourceManager.GetObject("TOOLBAR_ICON") or maybe ResourceManager.GetStream("TOOLBAR_ICON") -- Note that I have not tried this and am typing this suggestion as I am about to leave for the day. My rationale for it being consistently faster than your SomeDummy.Xaml approach is that I believe TOOLBAR_ICON is hardwired to be the zeroth resource in every assembly. Thus it will be read very early in the Stream. Faaaaaast. So it is not just avoiding needing SomeDummy.Xaml in every assembly of your project that I am suggesting; I am also recommending micro-optimizations.

If these tricks work, you should be able to significantly improve performance.


Additional thoughts:

I think you can clean up your code further.

IModuleCatalog mm = this.UnityContainer.Resolve<IModuleCatalog>();
    foreach (var module in mm.Modules)
    {

could be refactored to remove the reference to UnityContainer. In addition, IModuleCatalog would be instantiated via a wrapper around the List<Module> I mentioned in my reply to Problem 1. In other words, the IModuleCatalog would be a dynamic view of all loaded modules. I am assuming there is still more performance that can be pulled out of this design, but at least you are no longer dependent on Unity. That will help you better refactor your code later on for more performance gains.

That "pretty gross bug" you linked to was actually someone using Prism incorrectly, so don't dis' the technology based on that post :) By accident that other question does give a clue on how to get access to the current modules so +1 for that. Your other points are appreciated. As per my code, I already have a DummyResource.xaml (0 length) in all modules. Re your last point: I should actually use injection to provide the IModuleCatalog, but it was just sample code bolted into a class that already had the Unity manager. Thanks
Enough already
Using Prism correctly should be a bug, then :)