【问题标题】:How to dynamically discover all XAML files in all modules in a Silverlight prism app如何动态发现 Silverlight prism 应用程序中所有模块中的所有 XAML 文件
【发布时间】:2011-04-10 07:16:00
【问题描述】:

有没有一种简单的方法可以动态发现所有当前加载的模块(特别是 Silverlight Prism 应用程序)中的所有 XAML 文件?我相信这是可能的,但不确定从哪里开始.

这必须发生在 Silverlight 客户端上:我们当然可以在开发机器上解析项目,但这会降低灵活性并且会在搜索中包含未使用的文件。

基本上,我们希望能够解析非常大的 Prism 项目中的所有 XAML 文件(独立于加载它们)以识别所有本地化字符串。这将让我们建立一个初始本地化数据库,其中包括我们所有的资源绑定字符串,并创建一个查找它们出现在哪些 XAML 文件中(以便于翻译人员进行编辑)。

为什么要这样做?:对于翻译人员来说,最糟糕的事情是在一个上下文中更改一个字符串,却发现它在其他地方使用的含义略有不同。我们正在启用上下文中的翻译编辑在应用程序本身内。

更新(9 月 14 日):

由于安全限制,Silverlight 无法使用迭代程序集的标准方法。这意味着对以下解决方案的唯一改进是在可能的情况下与 Prism 模块管理合作。如果有人想为这个问题的最后一部分提供代码解决方案,可以与您分享一些要点!

跟进:

由于各种原因,在基于模块的项目中迭代 XAP 文件的内容似乎是一件非常方便的事情,因此再增加 100 个代表以获得真正的答案(最好是工作示例代码)。干杯,祝你好运!

下面的部分解决方案(工作但不是最佳):

下面是我想出的代码,它是来自this link on Embedded resources(由 Otaku 建议)和我自己迭代的 Prism 模块目录的技术的粘贴。

  • 问题 1 - 所有模块都是 已经加载所以这基本上是 不得不一秒钟下载它们 时间,因为我不知道怎么做 迭代所有当前加载的 Prism 模块。 如果有人想分享赏金 在这一点上,你仍然可以帮助制作 这是一个完整的解决方案!

  • 问题 2 - 显然存在错误 在需要的 ResourceManager 中 你得到一个已知的流 资源才会让你 迭代所有资源项(参见下面代码中的注释)。这意味着我必须在每个模块中都有一个虚拟资源文件。很高兴知道为什么需要初始 GetStream 调用(或如何避免它)。

    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));
        }
    }
    

【问题讨论】:

    标签: silverlight xaml mvvm prism


    【解决方案1】:

    我看到有人抱怨 Prism 中的一些 pretty gross bugs

    剖析你的问题:

    问题 1:我不熟悉 Prism,但从面向对象的角度来看,您的 Module Manager 类应该跟踪模块是否已加载,如果尚未加载,则允许您递归加载其他模块使用List&lt;Module&gt; 上的映射函数或 Prism 用来抽象表示程序集的任何类型。简而言之,让您的模块管理器实现一个隐藏状态,该状态代表已加载的模块列表。然后,您的 Map 函数应该将已加载的模块列表作为种子值,并返回尚未加载的模块列表。然后,您可以内部化公共 LoadAllModules 方法的逻辑,或者允许某人迭代公共 List&lt;UnloadedModule&gt; where UnloadedModule : Module 并让他们选择要加载的内容。当通过多个线程访问模块管理器时,出于并发问题,我不建议同时公开这两种方法。

    问题 2:需要初始 GetStream 调用,因为 ResourceManager 会延迟评估资源。直观地说,我猜这是因为附属程序集可以包含多个特定于语言环境的模块,如果所有这些模块一次加载到内存中,它可能会耗尽堆,而事实上这些都是非托管资源。您可以使用 RedGate 的 .NET Reflector 查看代码以确定详细信息。您可以调用比 GetStream 更便宜的方法。您还可以通过加载每个 Silverlight 程序集中的资源来欺骗它来触发它加载程序集。试试 ResourceManager.GetObject("TOOLBAR_​​ICON") 或者 ResourceManager.GetStream("TOOLBAR_​​ICON") -- 请注意,我没有尝试过这个,并且正在输入这个建议,因为我即将离开这一天。我认为它始终比您的 SomeDummy.Xaml 更快的理由 方法是我相信 TOOLBAR_​​ICON 被硬连线为每个程序集中的第零个资源。因此它将在 Stream 中很早就被读取。太棒了。因此,我建议的不仅仅是避免在项目的每个程序集中都需要 SomeDummy.Xaml;我还建议进行微优化。

    如果这些技巧奏效,您应该能够显着提高性能。


    其他想法:

    我认为你可以进一步清理你的代码。

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

    可以重构以删除对 UnityContainer 的引用。此外,IModuleCatalog 将通过我在对问题 1 的回复中提到的 List&lt;Module&gt; 的包装器进行实例化。换句话说,IModuleCatalog 将是所有已加载模块的动态视图。我假设还有更多性能可以从这个设计中提取出来,但至少你不再依赖于 Unity。这将帮助您在以后更好地重构代码以获得更多性能提升。

    【讨论】:

    • 您链接到的那个“相当严重的错误”实际上是有人错误地使用了 Prism,所以不要根据那篇帖子取消该技术 :) 意外的是,其他问题确实提供了有关如何使用 Prism 的线索获得对当前模块的访问权限,因此为此 +1。您的其他观点表示赞赏。根据我的代码,我已经在所有模块中有一个 DummyResource.xaml(0 长度)。关于你的最后一点:我实际上应该使用注入来提供 IModuleCatalog,但这只是将示例代码固定到已经拥有 Unity 管理器的类中。谢谢
    【解决方案2】:

    使用GetManifestResourceNames 反射并从那里解析以仅获取以.xaml 结尾的那些。这是使用GetManifestResourceNames 的示例:Enumerating embedded resources。尽管示例展示了如何使用单独的 .xap 执行此操作,但您可以使用加载的文件执行此操作。

    【讨论】:

    • @Otaku:感谢程序集枚举提示,但我真的需要一个完整的 Prism 答案来处理所有加载的模块/程序集。如何访问 Prism 中所有已加载模块/程序集的集合?干杯
    • @Otaku:好的,我已经找到了一种(不是很简洁)使用 ModuleCatalog 和您建议的示例的方法,我将更新问题以包含代码。希望有人能够指出一种改进它的方法,因为它正在下载已经存在的模块。谢谢
    • @HiTech Magic:很高兴听到你有一些工作。我没有用 Prism 做太多的工作,但是会想象生成的代码与您正在寻找的内容相同或几乎相同 当前加载 并且 Resources 包含 Prism 提供的所有内容好吧。
    • @Otaku:嵌入式资源链接+1(现在...更多内容)。我已经用我目前编写的代码更新了这个问题,并列出了剩下的 2 个问题。
    • @宅男:+100。在没有完整解决方案的情况下,您当然提供了非常有用的链接并帮助我制作了一个可以工作的第一个版本。谢谢你。
    猜你喜欢
    • 1970-01-01
    • 2011-03-24
    • 1970-01-01
    • 2013-11-12
    • 1970-01-01
    • 2013-07-01
    • 1970-01-01
    • 2016-01-25
    • 2018-10-03
    相关资源
    最近更新 更多