22

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);
    }
}
andr
  • 15,970
  • 10
  • 45
  • 59
Martin
  • 39,309
  • 62
  • 192
  • 278

8 Answers8

35

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");
Andrew Arnott
  • 80,040
  • 26
  • 132
  • 171
9

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.

Millie Smith
  • 4,536
  • 2
  • 24
  • 60
  • This is interesting. I'm familiar with this kind of injection. But here's the issue - I don't need either the WinRT or WP8 app to call the PCL and get a resource. I need the PCL itself to access the "Build Action = Content" resource that is inside itself. Once it has it, it will do something with it (like populate a property in a view model, for example). – Todd Main Nov 01 '13 at 01:17
  • 1
    @ToddMain, I kind of got carried away in the example, but it was just an example. The PCL is a DLL. That means you're calling it from the nonportable code anyway. Once you've injected the interface implementation, the PCL can internally load the files and do whatever it wants with them. – Millie Smith Nov 01 '13 at 02:07
  • @ToddMain, did you figure it out? – Millie Smith Nov 06 '13 at 02:38
  • After much research, I believe your injection method with the content option may be the only real option out there. Despite guidance that says resources like images/video/etc. shouldn't be kept in a PCL, I don't find much point in maintaining duplicate or triplicate copies in non-portable applications, especially if there are to be changes in those resources that need to replicate to all non-portable applications. So what you propose above may be the only really good option available. Thanks for putting this together. – Todd Main Nov 06 '13 at 17:35
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.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • 1
    I guess I'm not sure what do with this response. It could either be a brilliant dissertation or nothing at all that I need. Maybe I could be make this simpler - I have a WP8 app, a WinRT and a PCL. The app has tons of video, audio, image and xml files that both RT/WP8 apps need to use periodically. Around 700 MB. Why not put them in PCL I ask myself? But will the DLL for the PCL really now be 700 MB if I embed them? I need that DLL all the time for other things. What to do? Add as Build Action = Content keeps them out of the PCL until accessed. Oops, no more space to type... – Todd Main Nov 01 '13 at 01:11
  • The problem is that you want your PCL to do something that's not supposed to be done by a PCL, file access is not portable across platforms, so if you mark your files as Content, you need to access them as any other file, a workaround is to embed them, but doing so inflates your DLL and may collapse any application that uses your dll, that's what Hans is trying to tell you. – Rafael Nov 04 '13 at 20:27
2

Add your file to portable resource and set build action to Embedded Resource. For example files GB.png, US.png under folder CountryFlags.

Add a getter function with code like this (here it's specific for our countryflag getter image).

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

Here Full.DLL.Name is the part of generated portable library that is before .dll extension. (Note: Anything.Resources.dll is a bad name for a library because it gets ignored by Visual Studio at least when generating XAP etc.; instead for example Anything.PortableResource.dll will work).

andr
  • 15,970
  • 10
  • 45
  • 59
Pasi Savolainen
  • 2,460
  • 1
  • 22
  • 35
  • `Assembly.GetExecutingAssembly()` isn't available in a PCL (at least when targeting the modern platforms). – Andrew Arnott Dec 07 '13 at 20:30
  • @AndrewArnott are you sure? PCL under 4.5 is marked supported: http://msdn.microsoft.com/en-us/library/system.reflection.assembly.getexecutingassembly%28v=vs.110%29.aspx This code has also on worked .NET 4, 4.5 and Silverlight (through PCL). – Pasi Savolainen Dec 07 '13 at 21:51
  • I guess it's unavailable when target Win8+Net45+WP80 – Andrew Arnott Dec 08 '13 at 07:59
  • I checked our PR project and it is Net45, SL5, WP8 and W8 appstore targeted (everything but xbox). VS2012 pro fwiw. Are you sure you didn't override Assembly with something else first? – Pasi Savolainen Dec 08 '13 at 18:18
  • Quite sure, yes. Creating a new PCL that targets Win8+Net45+WP80 gives you reference assemblies that exclude `Assembly.GetExecutingAssembly()`. It's counter-intuitive, but adding *more* target platforms than this can actually *increase* the available surface area. The targets I mention are deliberately reduced when they are the only ones included because it's the "new surface area" from `System.Runtime` instead of `mscorlib`. But for those targeting the reduced surface area, see my answer also on this page, because there *is* a way to get the assembly object. – Andrew Arnott Dec 20 '13 at 16:22
0

if you have added files as resources, check your .Designer.cs, there will be a property for each resource. you can access from that.

here is the sample auto generate property for dat file resources

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

you can read the dat file as

    System.Text.UTF8Encoding enc = new System.Text.UTF8Encoding();
    var str = enc.GetString(Resource1.MyDatFile);
Damith
  • 62,401
  • 13
  • 102
  • 153
0
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);
}
0

First of all, retrieve your assembly like this (DataLoader being a class in your PCL assembly) :

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

Add your file to portable resource and set build action to Embedded Resource.

Then you can retrieve your ressource like this :

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

For example if I have a file logo.png in a folder "Assets/Logos" in an assembly "TvShowTracker.Helpers" I will use this code :

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

Happy coding :)

Oleg Grishko
  • 4,132
  • 2
  • 38
  • 52
Jonathan ANTOINE
  • 9,021
  • 1
  • 23
  • 33
-1

You need to use Application.GetResourceStream method instead of using GetManifestResource stream

Reference: 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)); 
Sandeep Singh Rawat
  • 1,637
  • 1
  • 14
  • 27