How to read a resource file within a Portable Class Library?

I have a Portable Library which I am using for a Windows Phone application. In that same Portable Library, I have a couple of content files (Build Action = Content).

I created a class DataReader in the Portable Library which is supposed to return me a stream to the content file. However, with the code below I am consistently getting back null from GetManifestResourceStream. What am I doing wrong?

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

Solution 1:

Your path is wrong. You're using slashes, but in the embedded manifest resource names slashes were converted to periods during the build. Also depending on your PCL targeted platforms, you may not even be able to call Assembly.GetExecutingAssembly().

Here is what you can do:

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

Solution 2:

From 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

File access cannot be done portably between Windows Store apps and Windows Phone 8 apps. You will have to use platform specific code, to open the file and acquire a stream. You can then pass the stream into the PCL.

If you build it with the Content build action, the XML is not inside of the DLL. It's on the filesystem, and there's no way to get it from inside of the PCL. That is why all of the answers set the build action to Embedded Resource. It places the file inside MyPCL.DLL\Path\To\Content.xml.

However, if you set the build action to Content and set the copy type to Copy if newer, it will place your files in the same directory as the executable.

Solution Explorer, Properties, and Windows Explorer

Therefore, we can just place an interface for reading the file in our PCL. On startup of our nonportable code, we inject an implementation into the 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");
        }
    }
}

Now that the PCL is defined above, we can create our interface implementation in nonportable code (below):

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

On application startup, we inject the implementation, and demonstrate loading contents.

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

EDIT: I kept it strings instead of Streams for simplicity.

Solution 3:

Just responding to the bounty request. First off, using Build Action = Content does not actually affect the build at all. It is a project item property that other tooling can read. An installer builder uses it for example to figure out that the file needs to be included in the setup program and deployed to the user's machine.

Using Build Action = Embedded Resource as noted in the upvoted question was the OP's oversight. That actually instructs MSBuild to embed the file as a resource in the assembly manifest, using Assembly.GetManifestResourceStream() retrieves it at runtime.

But it is pretty clear from the bounty comment that you don't want that either. The fallback is to just copy the file onto the target machine. Where it will sit patiently until you need it. Notable about that is this does not in any way alter the size of the package that the user downloads from the Store. It takes the same amount of space, whether it it inside the assembly or a separate file in the package.

So scratch that as a way to get ahead.

It does make a difference at runtime, the entire assembly gets mapped into virtual memory when it gets loaded. So an assembly with a resource will take more virtual memory space. But the word "virtual" is very important, it takes very few resources of the phone. Just a few bytes in the page mapping tables for every 4096 bytes in the resource. You don't start paying for virtual memory until it gets accessed. At which point the phone operating system needs to actually turn it from virtual into physical memory. Or in other words, load the bytes of the resource into RAM. This is not different from loading a file, it also gets loaded into RAM when you open it.

So scratch that as a way to get ahead.

We're running out of good reasons to actually do this, Microsoft certainly did pick the default way to handle resources as a best-practice. It is. But sometimes you have to deploy content as a file, simply because it is too large. One that's pushing 2 gigabytes or more, consuming all virtual memory on a 32-bit operating system so cannot possibly be mapped to VM. The program simply won't be able to start. This is not the kind of program a phone user is going to be very pleased with, really.

You then need to focus on the packing build phase of the solution, the last step when a phone app gets built. The one where all of the projects in the solution have been compiled and the one-and-only file that's uploaded to the Store, and is downloaded by the user, is created.

And yes, there's a problem there, MSBuild is not smart enough to see the PCL library using the resource. The Build action = Content ought to be good enough, like it is for an installer, but that doesn't work. It will only package the DLL, not the resource. It was made to assume you'd embed it, the best practice solution.

What you have to do is to override the package manifest. Described in this MSDN article. Very, very ugly, you're looking at a blank blinking cursor. Which is where I'm running out of good advice, this was made to not do.