0

In Visual Studio 2010, I am including a zip file into my executable as an Embedded Resource and extracting it for use at runtime. Such a file cannot be included as a file resource (text or binary) because a file resource is always linked as a separate item, which is not what I want. Consequently I cannot reference the file in my code through Properties.Resources.

To extract the zip file, I have to hardcode its name in my code as follows:

stream = Assembly.GetEntryAssembly().GetManifestResourceStream("myembeddedfile.zip");

I noticed that the zip file is referenced in the .csproj file as follows:

<ItemGroup>
    <EmbeddedResource Include="myembeddedfile.zip" />
    <None Include="packages.config" />
</ItemGroup>

However, I don't think that the above can be referenced from code. (I know about the None usage from SO question #1060870.)

Is there any way I can include this file as a bonafide project/solution item and prevent having to hardcode the filename?

EDIT:

A non-hardcoded way to reference the file in code would be something like the following:

GetManifestResourceStream(Properties.Resources.EmbeddedFile);

But as I have mentioned, the above is not possible because an embedded resource is not included in the Properties.Resources object.

EDIT AFTER FOLLOW-UP EDIT BY DRapp TO HIS ACCEPTED ANSWER:

The scheme involving enums by DRapp below is the ultimate solution to this problem, with one modification: when there are multiple resource files being embedded, the resource filenames will be returned by GetManifestResourceNames() in alphabetical order. Therefore, the enum names must be arranged accordingly. Before the following snapshot taken during debugging, the three embedded files were added manually in the following sequence: 1_FIRST, 3_THIRD, 2_SECOND, but as can be observed, the internal storage is alphabetical:

For the above example with three files, the corresponding enum would be defined as:

public enum MyResources
{
    EmptyZipTest_1_FIRST_zip,
    EmptyZipTest_2_SECOND_zip,
    EmptyZipTest_3_THIRD_zip
}
Community
  • 1
  • 1
Sabuncu
  • 4,529
  • 4
  • 37
  • 75
  • I'm confused about what you are trying to do. If you don't want to get the resource by hard coded name, are you trying to enumerate the resources at runtime to get the name? – Bradley Uffner Mar 20 '15 at 17:57
  • @BradleyUffner I have edited my question for clarification. – Sabuncu Mar 20 '15 at 18:14

1 Answers1

1

With binary write stream credit from Jon Skeet's answer for the CopyStream function and calling it... You CAN embed a zip file.

Also, as hintham mentioned, what you need to do (and I did and tested), I created an empty zip file and added it to my project. I called it "EmptyZipFile.zip". On this file, for the properties, click on "Build Action" and change to "Embedded Resource". The name of my project (and namespace) is "EmptyZipTest"

You were very close with getting your resource, but must include the fully qualified namespace PLUS the file name as it is embedded. To see a quick list of everything in your resources..

StringBuilder sb = new StringBuilder();
foreach (string s in Assembly.GetEntryAssembly().GetManifestResourceNames())
   sb.AppendLine(s);

MessageBox.Show(sb.ToString());

By doing this, it would have exposed (per my sample project) a resource of

"EmptyZipTest.EmptyZipFile.zip"

This is the file IN your executable. Now, if you want to copy this baseline zip file OUT to some new file name, create a simple function that may get another file name / location and provide that to create the file and write the zip file out.

public void WriteNewZipFile( string newFilePathAndName )
{
   Stream zipStream = Assembly.GetEntryAssembly().GetManifestResourceStream(
      "EmptyZipTest.EmptyZipFile.zip" );

   if (zipStream != null)
   {
      using (Stream outStream = File.Create( newFilePathAndName ))
      {
         CopyStream(zipStream, outStream);
      }
   }
}

public static void CopyStream(Stream input, Stream output)
{
   byte[] buffer = new byte[8 * 1024];
   int len;
   while ((len = input.Read(buffer, 0, buffer.Length)) > 0)
   {
      output.Write(buffer, 0, len);
   }
}   

Obviously I don't have error checking / validation in this quick bit, but it SHOULD get you what you need. The internal file name will ALWAYS be a fixed value... What you write that stream OUT to is different...

OPTION vs Hardcoding strings.

I think I see your point, and you could try this... Create an "enum" instance and put in your resources there, but substitute "." for "_" in the enum as you can't have periods in the object names... such as

   public enum MyResources
   {
      EmptyZipTest_EmptyZipFile_zip,
      EmptyZipTest_SomeOtherFile_jpg,
      EmptyZipTest_AnotherFile_ini
   }

Then, having that enum as a parameter, you won't accidentally misspell the hardcode names all over the place, but be required to have a proper referenced instance. Then, in your function that process the getting of the streamed resource, you can .REPLACE() the "_" to "." such as

   public Stream GetMyResource( MyResources myRes )
   {
      string actualResource = myRes.ToString().Replace( "_", "." );
      // This would convert the sample enums above to
      // EmptyZipTest.EmptyZipFile.zip,
      // EmptyZipTest.SomeOtherFile.jpg,
      // EmptyZipTest.AnotherFile.ini
   }
Community
  • 1
  • 1
DRapp
  • 43,407
  • 11
  • 68
  • 131
  • DRapp, thank you for your efforts, the `GetManifestResourceNames` method indeed returns the name of the embedded file. However, if there is more than one embedded resource, there is no way to tell which is which. I am thinking that maybe I need to create a build event that would be triggered after the Build Action for a resource is changed to Embedded Resource, which would then register the name of the file as a string that would be accessible through `Properties.Resources`. I don't know if such a build action is even possible. – Sabuncu Mar 20 '15 at 18:22
  • @Sabuncu, you can have a lot of embedded resources in the app. Each one will have their own distinct names... and you would be in control of knowing which one you needed to work with... – DRapp Mar 21 '15 at 00:14
  • I agree, each resource will have its own name, but if you check for a name using a string (e.g. `if sb.ToString() == "EmptyZipTest.zip` ...), that would defeat the purpose of not hardcoding the names into the code. – Sabuncu Mar 21 '15 at 11:46
  • @Sabuncu, see revision about option vs hardcoding and utilizing enums which may help your situation / design... – DRapp Mar 21 '15 at 15:44
  • DRapp, thank you so much for your follow-up. Please see my edit above. I really think that your enum scheme is the only portable, non-hardcoded solution to this problem anywhere on the web. – Sabuncu Mar 21 '15 at 16:31