如何动态发现 Silverlight prism 应用程序中所有模块中的所有 XAML 文件

发布于 2024-09-18 14:07:01 字数 4507 浏览 3 评论 0原文

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

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

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

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

更新(9 月 14 日):

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

后续:

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

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

下面是我想出的代码,它是来自 有关嵌入式资源的链接(根据 Otaku 的建议)以及我自己对 Prism 模块目录的迭代。

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

  • 问题 2 - 显然存在错误 在资源管理器中需要 你得到一个已知的流 资源之前它会让你 迭代所有资源项(请参阅下面代码中的注释)。这意味着我必须在每个模块中都有一个虚拟资源文件。如果知道为什么需要初始 GetStream 调用(或者如何避免它),那就太好了。

    private void ParseAllXamlInAllModules()
    {
        IModuleCatalog mm = this.UnityContainer.Resolve();
        foreach(mm.Modules 中的 var 模块)
        {
            字符串 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, 文件);
                    XmlReader 阅读器 = XmlReader.Create(stream.Stream);
                    var parts = new AssemblyPartCollection();
                    if (读者.Read())
                    {
                        reader.ReadStartElement();
                        if (reader.ReadToNextSibling("Deployment.Parts"))
                        {
                            while (reader.ReadToFollowing("AssemblyPart"))
                            {
                                parts.Add(new AssemblyPart() { Source = reader.GetAttribute("Source") });
                            }
                        }
                    }
                    foreach(var 部分)
                    {
                        var info = new StreamResourceInfo(args.Result, null);
                        程序集 assy = part.Load(System.Windows.Application.GetResourceStream(info, new Uri(part.Source, UriKind.Relative)).Stream);
                        // 获取嵌入的资源名称
                        string[] resources = assy.GetManifestResourceNames();
                        foreach(资源中的var资源)
                        {
                            if (!resource.Contains("DummyResource.xaml"))
                            {
                                // 获取实际值 - 创建表
                                var table = new Dictionary();
                                // 所有资源的名称中都包含“.resources” – 因此请将其删除
                                var rm = new ResourceManager(resource.Replace(".resources", String.Empty), assy);
                                // 似乎这里有一些问题,但是没有获得任何真正的流,下一个语句就不起作用......
                                var dummy = rm.GetStream("DummyResource.xaml");
                                var rs = rm.GetResourceSet(Thread.CurrentThread.CurrentUICulture, false, true);
                                IDictionaryEnumerator 枚举器 = rs.GetEnumerator();
                                while (枚举器.MoveNext())
                                {
                                    if (enumerator.Key.ToString().EndsWith(".xaml"))
                                    {
                                        table.Add(enumerator.Key.ToString(), enumerator.Value as Stream);
                                    }
                                }
                                foreach(表中的 var xaml)
                                {
                                    TextReader xamlreader = new StreamReader(xaml.Value);
                                    字符串内容= xamlreader.ReadToEnd();
                                    {
                                        // 这是我对 XAML 内容进行实际工作的地方
                                    }
                                }
                            }
                        }
                    }
                }
            };
            // 执行实际的读取来触发上面的回调代码
            wc.OpenReadAsync(new Uri(xap, UriKind.RelativeOrAbsolute));
        }
    }
    

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

乖乖兔^ω^ 2024-09-25 14:07:01

使用 GetManifestResourceNames 反射并从那里解析以仅获取以 .xaml 结尾的内容。以下是使用 GetManifestResourceNames 的示例:枚举嵌入资源。尽管该示例展示了如何使用单独的 .xap 执行此操作,但您也可以使用加载的 .xap 执行此操作。

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.

凝望流年 2024-09-25 14:07:01

我看到人们抱怨 Prism 中的一些相当严重的错误

剖析你的问题:

< strong>问题1:我不熟悉Prism,但从面向对象的角度来看,您的模块管理器类应该跟踪模块是否已加载,如果尚未加载,则允许您使用递归加载其他模块List 上的映射函数或 Prism 用于抽象表示程序集的任何类型。简而言之,让您的模块管理器实现一个表示已加载模块列表的隐藏状态。然后,您的 Map 函数应该采用该列表已作为种子值加载的模块,并返回尚未加载的模块列表。然后,您可以内部化公共 LoadAllModules 方法的逻辑,或者允许某人迭代公共 List方法。其中 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 周围的包装器进行实例化。换句话说,IModuleCatalog 将是所有已加载模块的动态视图。我假设这个设计还可以发挥更多性能,但至少您不再依赖 Unity。这将帮助您稍后更好地重构代码以获得更多性能提升。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文