【问题标题】:How to read a resource file within a Portable Class Library?如何读取可移植类库中的资源文件?
【发布时间】:2012-06-09 19:06:01
【问题描述】:

我有一个用于 Windows Phone 应用程序的便携式库。在同一个便携式库中,我有几个内容文件(Build Action = Content)。

我在便携式库中创建了一个类DataReader,它应该向我返回内容文件的流。但是,使用下面的代码,我一直在从GetManifestResourceStream 返回null。我做错了什么?

public class DataReader
{
    public static Stream GetStream(string code)
    {
        string path = string.Format("./data/code-{0}.dat", code);
        return Assembly.GetExecutingAssembly().GetManifestResourceStream(path);
    }
}

【问题讨论】:

    标签: c# .net embedded-resource


    【解决方案1】:

    你的路径是错误的。您正在使用斜杠,但在嵌入式清单资源名称中,斜杠在构建期间被转换为句点。 此外,根据您的 PCL 目标平台,您甚至可能无法致电 Assembly.GetExecutingAssembly()

    你可以这样做:

    var assembly = typeof(AnyTypeInYourAssembly).GetTypeInfo().Assembly;
    
    // Use this help aid to figure out what the actual manifest resource name is.
    string[] resources = assembly.GetManifestResourceNames();
    
    // Once you figure out the name, pass it in as the argument here.
    Stream stream = assembly.GetManifestResourceStream("Some.Path.AndFileName.Ext");
    

    【讨论】:

    • 这绝对有效,但仍然希望他满足的做事方式能够奏效
    • 什么是 AnyTypeInYourAssembly?没有人解释这个选项是什么。甚至在 Xamarin 文档中也没有。
    • @Dpedrinha:它可以替代您程序集中的任何类型。随便挑一个吧。
    【解决方案2】:

    来自http://social.msdn.microsoft.com/Forums/windowsapps/en-US/386eb3b2-e98e-4bbc-985f-fc143db6ee36/read-local-file-in-portable-library#386eb3b2-e98e-4bbc-985f-fc143db6ee36

    无法在 Windows 应用商店应用程序和 Windows 之间进行文件访问 电话 8 应用程序。您将不得不使用特定于平台的代码来打开文件 并获取流。然后,您可以将流传递到 PCL。

    如果您使用内容构建操作构建它,则 XML 不在 DLL 内。它在文件系统上,无法从 PCL 内部获取。这就是为什么所有答案都将构建操作设置为 Embedded Resource。它将文件放入MyPCL.DLL\Path\To\Content.xml

    然而,如果您将构建操作设置为 Content 并将复制类型设置为 Copy if newer,它会将您的文件放入与可执行文件相同的目录。

    因此,我们可以在 PCL 中放置一个用于读取文件的接口。在启动我们的不可移植代码时,我们将一个实现注入 PCL。

    namespace TestPCLContent
    {
        public interface IContentProvider
        {
            string LoadContent(string relativePath);
        }
    }
    
    namespace TestPCLContent
    {
        public class TestPCLContent
        {
            private IContentProvider _ContentProvider;
            public IContentProvider ContentProvider
            {
                get
                {
                    return _ContentProvider;
                }
                set
                {
                    _ContentProvider = value;
                }
            }
    
            public string GetContent()
            {
                return _ContentProvider.LoadContent(@"Content\buildcontent.xml");
            }
        }
    }
    

    现在 PCL 已在上面定义,我们可以在不可移植的代码中创建我们的接口实现(如下):

    namespace WPFBuildContentTest
    {
        class ContentProviderImplementation : IContentProvider
        {
            private static Assembly _CurrentAssembly;
    
            private Assembly CurrentAssembly
            {
                get
                {
                    if (_CurrentAssembly == null)
                    {
                        _CurrentAssembly = System.Reflection.Assembly.GetExecutingAssembly();
                    }
    
                    return _CurrentAssembly;
                }
            }
    
            public string LoadContent(string relativePath)
            {
                string localXMLUrl = Path.Combine(Path.GetDirectoryName(CurrentAssembly.GetName().CodeBase), relativePath);
                return File.ReadAllText(new Uri(localXMLUrl).LocalPath);
            }
        }
    }
    

    在应用程序启动时,我们注入实现,并演示加载内容。

    namespace WPFBuildContentTest
    {
        //App entrance point. In this case, a WPF Window
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
            }
    
            private void Window_Loaded(object sender, RoutedEventArgs e)
            {
                ContentProviderImplementation cpi = new ContentProviderImplementation();
    
                TestPCLContent.TestPCLContent tpc = new TestPCLContent.TestPCLContent();
                tpc.ContentProvider = cpi; //injection
    
                string content = tpc.GetContent(); //loading
            }
        }
    }
    

    编辑:为简单起见,我将其保留为字符串而不是 Streams。

    【讨论】:

    • 这很有趣。我对这种注射很熟悉。但问题是——我不需要 WinRT 或 WP8 应用程序来调用 PCL 并获取资源。我需要 PCL 本身来访问其内部的“Build Action = Content”资源。一旦有了它,它就会对它做一些事情(例如,在视图模型中填充一个属性)。
    • @ToddMain,我对这个例子有点忘乎所以,但这只是一个例子。 PCL 是一个 DLL。这意味着无论如何您都是从不可移植的代码中调用它。一旦您注入了接口实现,PCL 就可以在内部加载文件并对其进行任何操作。
    • @ToddMain,你明白了吗?
    • 经过大量研究,我相信带有内容选项的注入方法可能是唯一真正的选择。尽管指导说图像/视频/等资源。不应该保存在 PCL 中,我认为在非便携式应用程序中维护重复或三份副本没有多大意义,特别是如果需要复制到所有非便携式应用程序的资源发生变化。因此,您在上面提出的建议可能是唯一真正好的选择。感谢您将这些放在一起。
    【解决方案3】:

    只是响应赏金请求。首先,使用 Build Action = Content 实际上根本不会影响构建。它是其他工具可以读取的项目项属性。例如,安装程序构建器使用它来确定文件需要包含在安装程序中并部署到用户的机器上。

    使用 Build Action = Embedded Resource 如被赞成的问题所述是 OP 的疏忽。这实际上指示 MSBuild 将文件作为资源嵌入到程序集清单中,使用 Assembly.GetManifestResourceStream() 在运行时检索它。

    但从赏金评论中可以清楚地看出您也不想要那样。回退是仅将文件复制到目标机器上。它会耐心地坐在哪里,直到你需要它。值得注意的是,这不会以任何方式改变用户从商店下载的包的大小。无论是在程序集内部还是包中的单独文件,它都占用相同数量的空间。

    因此,将其作为前进的一种方式。

    它在运行时确实有所不同,整个程序集在加载时被映射到虚拟内存中。因此,具有资源的程序集将占用更多的虚拟内存空间。但是“虚拟”这个词很重要,它占用手机的资源很少。对于资源中的每 4096 个字节,页面映射表中只有几个字节。在虚拟内存被访问之前,您不会开始为虚拟内存付费。此时手机操作系统需要将其从虚拟内存实际转换为物理内存。或者换句话说,将资源的字节加载到 RAM 中。这与加载文件没有什么不同,当你打开它时它也会被加载到 RAM 中。

    因此,将其作为前进的一种方式。

    我们已经没有充分的理由实际这样做了,Microsoft 确实选择了默认方式来处理资源作为最佳实践。它是。但有时您不得不将内容部署为文件,仅仅是因为它太大了。一个推 2 GB 或更多,消耗 32 位操作系统上的所有虚拟内存,因此不可能映射到 VM。该程序根本无法启动。这不是电话用户会非常满意的那种程序,真的。

    然后,您需要专注于解决方案的打包构建阶段,这是构建手机应用程序的最后一步。已编译解决方案中的所有项目并创建上传到商店并由用户下载的唯一文件的文件。

    是的,那里有一个问题,MSBuild 不够聪明,无法看到使用该资源的 PCL 库。 Build action = Content 应该足够好,就像安装程序一样,但这不起作用。它将打包 DLL,而不是资源。它假定您已嵌入它,这是最佳实践解决方案。

    您需要做的是覆盖包清单。在this MSDN article 中描述。非常,非常丑陋,你正在看着一个空白的闪烁光标。这是我没有好的建议的地方,这是不应该做的。

    【讨论】:

    • 我想我不确定如何处理这个回复。它可能是一篇精彩的论文,也可能什么都不是我需要的。也许我可以让这更简单——我有一个 WP8 应用程序、一个 WinRT 和一个 PCL。该应用程序有大量的视频、音频、图像和 xml 文件,RT/WP8 应用程序都需要定期使用这些文件。大约 700 MB。我问自己为什么不把它们放在 PCL 中?但是,如果我嵌入它们,PCL 的 DLL 现在真的会是 700 MB 吗?我一直需要那个 DLL 来做其他事情。该怎么办? Add as Build Action = Content 将它们保留在 PCL 之外,直到被访问。哎呀,没有更多的空间可以输入...
    • 问题是你希望你的 PCL 做一些不应该由 PCL 做的事情,文件访问不能跨平台移植,所以如果你将文件标记为内容,你需要访问将它们与任何其他文件一样,一种解决方法是嵌入它们,但这样做会使您的 DLL 膨胀并可能崩溃任何使用您的 dll 的应用程序,这就是 Hans 试图告诉您的。
    【解决方案4】:

    将您的文件添加到可移植资源并将构建操作设置为嵌入式资源。例如文件夹CountryFlags下的文件GB.pngUS.png

    使用这样的代码添加一个 getter 函数(这里是针对我们的 countryflag getter 图像的)。

    public class CountryFlags {
        public static Stream GetFlagStream(string countryIsoCode2ch)
        {
            var flagname = "Full.DLL.Name.CountryFlags.{0}.png";
            var rs = Assembly.GetExecutingAssembly().GetManifestResourceStream(
                       string.Format(flagname, countryIsoCode2ch));
    
            return rs;
        }
    }
    

    这里的Full.DLL.Name 是生成的可移植库的一部分,位于.dll 扩展之前。 (注意:Anything.Resources.dll 是一个不好的库名称,因为至少在生成 XAP 等时它会被 Visual Studio 忽略;例如,Anything.PortableResource.dll 会起作用)。

    【讨论】:

    • Assembly.GetExecutingAssembly() 在 PCL 中不可用(至少在针对现代平台时)。
    • @AndrewArnott 你确定吗?支持 4.5 下的 PCL:msdn.microsoft.com/en-us/library/… 此代码也适用于 .NET 4、4.5 和 Silverlight(通过 PCL)。
    • 我猜目标Win8+Net45+WP80时不可用
    • 我检查了我们的 PR 项目,它是针对 Net45、SL5、WP8 和 W8 应用商店的(除了 xbox 之外的所有应用商店)。 VS2012 专业版。你确定你没有先用其他东西覆盖 Assembly 吗?
    • 非常肯定,是的。创建以 Win8+Net45+WP80 为目标的新 PCL 可为您提供排除 Assembly.GetExecutingAssembly() 的参考程序集。这违反直觉,但添加 更多 个目标平台实际上可以增加 可用表面积。当我提到的目标是唯一包含的目标时,故意减少它们,因为它是来自System.Runtime 而不是mscorlib 的“新表面积”。但是对于那些以减少表面积为目标的人,也可以在此页面上查看我的答案,因为一种获取程序集对象的方法。
    【解决方案5】:

    如果您已将文件添加为资源,请检查您的 .Designer.cs,每个资源都会有一个属性。您可以从中访问。

    这里是 dat 文件资源的示例自动生成属性

       internal static byte[] MyDatFile {
            get {
                object obj = ResourceManager.GetObject("MyDatFile", resourceCulture);
                return ((byte[])(obj));
            }
    

    您可以将 dat 文件读取为

        System.Text.UTF8Encoding enc = new System.Text.UTF8Encoding();
        var str = enc.GetString(Resource1.MyDatFile);
    

    【讨论】:

    • 我正在尝试读取构建操作设置为内容的文件。
    【解决方案6】:
    var assembly = typeof(PegHelper).GetTypeInfo().Assembly;
    using (var stream = assembly.GetManifestResourceStream("Parsers.Peg.SelfDef.xml"))
    using (var reader = new StreamReader(stream))
    {
        string xmlText = reader.ReadToEnd();
        return XDocument.Parse(xmlText);
    }
    

    【讨论】:

    • 什么是 PegHelper?没有人解释这个选项是什么。甚至在 Xamarin 文档中也没有。
    【解决方案7】:

    首先,像这样检索您的程序集(DataLoader 是您 PCL 程序集中的一个类):

    var assembly = typeof(DataLoader).GetTypeInfo().Assembly;
    

    将您的文件添加到可移植资源并将构建操作设置为嵌入式资源

    然后你可以像这样检索你的资源:

    string resourceNam= "to be filled";
    var assembly = typeof(DataLoader).GetTypeInfo().Assembly;
    var compressedStream = assembly.GetManifestResourceStream(resourceName));
    

    例如,如果我在程序集“TvShowTracker.Helpers”的文件夹“Assets/Logos”中有一个文件 logo.png,我将使用以下代码:

    string resourceNam= "TvShowTracker.Helpers.Assets.Logos.logo.png";
    var assembly = typeof(DataLoader).GetTypeInfo().Assembly;
    var compressedStream = assembly.GetManifestResourceStream(resourceName));
    

    编码愉快:)

    【讨论】:

      【解决方案8】:

      您需要使用 Application.GetResourceStream 方法而不是使用 GetManifestResource 流

      参考:http://msdn.microsoft.com/en-us/library/ms596994%28v=vs.95%29.aspx

      var albumArtPlaceholder =  
          Application.GetResourceStream( 
              new Uri("Images/artwork.placeholder.png", UriKind.Relative)); 
      

      【讨论】:

      • 无法从类库访问应用程序静态类。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-09-05
      • 2012-11-22
      • 1970-01-01
      • 2015-12-16
      • 1970-01-01
      相关资源
      最近更新 更多