Bundle a folder with a .Net application - c#

How can I bundle a folder with a one click application and reference those files/folders after?
Seems rather simple but I just can't figure out how.
As in, I had the file index.html in the folder UI and I wanted to package that with the application, then I want to get the stream for that file with the string "/UI/index.html" but instead of just index.html, an entire website.

Add the folder to your VS Project, right-click on it and select "embed as resource". That will make the files in the folder be embedded in the .NET assembly. To get the file contents in your program, you can use something like this:
public class ReadResource
{
public string ReadInEmbeddedFile (string filename) {
// assuming this class is in the same assembly as the resource folder
var assembly = typeof(ReadResource).Assembly;
// get the list of all embedded files as string array
string[] res = assembly.GetManifestResourceNames ();
var file = res.Where (r => r.EndsWith(filename)).FirstOrDefault ();
var stream = assembly.GetManifestResourceStream (file);
string file_content = new StreamReader(stream).ReadToEnd ();
return file_content;
}
}
In the above function I assume your files a text/html files; if not, you can change it not to return string but byte[], and use a binary stream reader for that.
I also select the files by file.EndsWith() which is enough for my needs; if your folder has a deep nested structure you need to modify that code to parse for folder levels.

Perhaps there is a better way, but given the content is not too large you can embed binaries directly into your program as a base64 string. In this case it would need to be an archive of the folder. You would also need to embed the dll used for unzipping that archive (If I understood correctly you want to have single .exe and nothing more).
Here is a short example
// create base64 strings prior to deployment
string unzipDll = Convert.ToBase64String(File.ReadAllBytes("Ionic.Zip.dll"));
string archive = Convert.ToBase64String(File.ReadAllBytes("archive.zip"));
string unzipDll = "base64string";
string archive = "probablyaverylongbase64string";
File.WriteAllBytes("Ionic.zip.dll", Convert.FromBase64String(unzipDll));
File.WriteAllBytes("archive.zip", Convert.FromBase64String(archive);
Ionic.Zip.ZipFile archive = new Ionic.Zip.ZipFile(archiveFile);
archive.ExtractAll("/destination");
The unzipping library is DotNetZip. It's nice because you need just a single dll. http://dotnetzip.codeplex.com/downloads/get/258012
Edit:
Come to think of it, as long as you write the Ionic.dll to the working directory of the .exe you shouldn't need to use the dynamic dll loading so I removed that part to simplify the answer (it would still need to be written before you reach the method it is in though).

Related

Package a Folder in C#

I'm looking to make a program to make my life easier, I need to be able to easily select a folder, which I can do, I don't need help with that. I want to take the directory of a folder, and put that folder into a new folder with a specified name, and then zip up that folder into a zip format in which I can change the name and filetype of. Is this possible in vanilla C#? I've only ever done files for text and I've never looked at moving and packaging files. SO I'm really clueless, I'd just like to be guided into the right direction.
Edit: I found this code online, but I need to put the folder inside another folder, may I adapt upon this to do so?
string startPath = #"c:\example\start";
string zipPath = #"c:\example\result.zip";
string extractPath = #"c:\example\extract";
ZipFile.CreateFromDirectory(startPath, zipPath);
ZipFile.ExtractToDirectory(zipPath, extractPath);
So, after an extended chat discussion, here's what we've established.
The goal is to put the contents of a source directory into a zip with the following structure:
- Payload
|- name of source
|-- contents of source
Okay, so, starting from an input path called startPath:
var parent = Path.GetDirectoryName(startPath);
var payload = Path.Combine(parent, "payload");
Directory.CreateDirectory(payload); // ensure payload ex
Directory.Move(startPath, Path.Combine(payload, Path.GetFileName(startPath));
var zipPath = Path.Combine(parent, "export.zip");
File.Delete(zipPath);
ZipFile.CreateFromDirectory(payload , zipPath, CompressionLevel.Optimal, true);
The key is that true in the CreateFromDirectory call, that puts the entries in the archive under a directory with the same name as the directory being zipped (in this case, "payload"). Feel free to change CompressionLevel to other values if you want.
Now, this has the side effect of actually physically moving the source directory, which might not be the best user experience. If you want to avoid that, you'll have to basically do what ZipFile.CreateFromDirectory does by hand, which is to enumerate the source directory yourself and then copy the files into the zip archive (in which case you can name those files whatever you want):
var parent = Path.GetDirectoryName(startPath);
var zipPath = Path.Combine(parent, "export.zip");
File.Delete(zipPath);
using var zip = ZipFile.Open(zipPath, ZipArchiveMode.Create);
foreach(var file in Directory.EnumerateFiles(startPath, "*", SearchOption.AllDirectories))
{
// get the path of the file relative to the parent directory
// this gives us a path that starts with the source directory name
// e.g. C:\example\start\file.txt -> start\file.txt
var relativePath = Path.GetRelativePath(parent, file);
// construct the path of the entry in the archive
// this is "Payload", and then the relative path of the file
// we need to fix up the separators because zip entries use /
// e.g. start\file.txt -> Payload/start/file.txt
var entryPath = Path.Combine("Payload", relativePath).Replace(Path.DirectorySeparatorChar, '/');
// put the file in the archive
// to specify a compression level, pass it as the third parameter here
zip.CreateEntryFromFile(file, entryPath);
}

cannot find the path to the resource file c#

firstly apology if this has already been answered and I am duplicating the question. I have tried to find the answer to my issue but have failed and none of the auto-suggestions answers my problem.
I have my main project (XAML) and also a class library project called FileStore for files. The class library project is referenced into the main project and I have images and icon file in the class library project that I can access with no issues in my main project, however, I struggle to get the content of a txt file from the CL project to display in a label on the main project. I get the error: the system could not find the file and from the error, I can see that it is trying to look for a file in the main project bin\debug folder
I tried to follow this previous post which seemed to partly answer my issue but to no avail sadly.
Get relative file path in a class library project that is being referenced by a web project
The txt file Build action is set to: Resource and Copy to Output Directory set to: Copy Always.
As I mentioned I have the FileStore project referenced in my main project and the images work fine.
Below is the code I am using, I have tried different variations such as:
\Resources\textFile.txt and \textFile.txt, still no luck.
'''
public static string ReadFileinClLibr()
{
var buildDir =
Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
var filePath = buildDir + #"\textFile.txt";
return File.ReadAllText(filePath);
}
'''
For comparition here is the path for the image files that works, but I cannot get it to work with the txt file, as the error reads: the given paths format is not supported..
'''
#"pack://application:,,,/FileStore;component/Resources\textFile.txt"
'''
I want to be able to input the content of the text file from the class library project to the label in the main xaml project.
At the moment compiler keeps looking for this file in a debug folder of the main project, what I want is, for the compiler to look for the txt file in a CL FileStore project
In order to access the file all the time, we have to have the file copied to the debug folder. Right click the file from solution explorer change the properties then try to access the file from the executing assembly location.
StringBuilder bodyContent = new StringBuilder();
string fileName = "myfile.txt";
try
{
string filePath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), fileName);
using (StreamReader sr = new StreamReader(filePath))
{
// Read the stream.
bodyContent.Append(sr.ReadToEnd());
}
}
catch(Exception ex)
{
Console.WriteLine(String.Format("{0} # {1}", "Exception while reading the file: " + ex.InnerException.Message, DateTime.Now));
throw ex;
}
Thanks to the post from #Sreekanth Gundlapally I have managed to fix my issues. I have mostly drawn on from the answer provided by #Sreekanth Gundlapally but there is one important bit missing. The string fileName should include any subfolders that the resource file is within in the Class Library Project, for example in my case the folder was named 'Resources' so the code should look like this:
string fileName = #"Resources/myfile.txt";
try
{
string filePath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), fileName);
using (StreamReader sr = new StreamReader(filePath))
{
// Read the stream.
bodyContent.Append(sr.ReadToEnd());
}
I have also cleaned and rebuilt solution after which it all worked a charm.
Also a side note, anyone trying this and getting funny characters make sure your file's encoding is set to UTF-8 as this is the default encoding used by StreamReader, otherwise your file content may not be read correctly if it contains signs such as apostrophe.

Is it possible to embed binary data in a .NET assembly

Is it possible to embed binary data (as a resource, or by some other mean) in the C# assembly and then read the binary data from assembly during run-time and write it as a file.
I am making a DRM application and purpose is that the data must be hidden in the assembly as embedded resource, or a password protected ZIP file. So, I will try to embed the resource and if not possible then will look for a ZIP / UN-ZIP library with password protection to save DRM data.
I am writing a program in C# in which should have a binary data and it is added in the assembly during compile just like images, icons are added in assembly when we compile, and then when the assembly is executed by user then the binary data is read and saved as an external file.
Is it possible? then how to do it?
Yes. If you are using resources, you can include files too, which are represented as a byte array. Else you can include a file and set the Build Action to Embedded Resource, which include it as a resource too, which you can manually read.
public byte[] ExtractResource(Assembly assembly, string resourceName)
{
if (assembly == null)
{
return null;
}
using (Stream resFilestream = assembly.GetManifestResourceStream(resourceName))
{
if (resFilestream == null)
{
return null;
}
byte[] bytes = new byte[resFilestream.Length];
resFilestream.Read(bytes, 0, bytes.Length);
return bytes;
}
}
Then use it like this:
byte[] bytes = this.ExtractResource( Assembly.GetExecutingAssembly()
, "Project.Namespace.NameOfFile.ext"
);
yeah, its possible. Just add the file in the project, Select the file, Go to property and select Embedded Resource in Build Action property.
Here's the code=
private Stream GetStream(string fileName)
{
var asm = Assembly.GetExecutingAssembly();
Stream stream = asm.GetManifestResourceStream("NameSpace." + fileName);
return stream;
}
For clarification of sv88erik doubts-
as you can see in picture here, embedded resources are a part of the assembly itself and having a name as NameSpace.FileName
Background: When you build your application, the linked and embedded resource data is compiled directly into the application assembly (the .exe or .dll file).
To access the resources, use the class Resources contained in Resources.Designer.cs which is nested under the Resources.resx file in Solution Explorer. The Resources class encapsulates all your project resources into static readonly get properties. For example, a string resource “Bill” is accessed by Properties.Resources.Bill. You can access a Text file resource also as a string property. Binary files are referenced as properties of type byte[].
Double-click Resources.resx. Select Add Resource/Add Existing File and scroll to the file you want to be included.
For binaries, the class Resources has a property of type byte[] that is named after the included file. Assume the file name to be MyApp.dll, then the property should have the name MyApp. You find the exact name in the code file Resources.Designer.cs nested under the Resources.resx file in Solution Explorer.
You can access the resource as Properties.Resources.MyApp. For example, you can save the resource as a binary file with File.WriteAllBytes(PathAndName, Properties.Resources.MyApp);.

How to access batch file that I saved as a resource?

I saved some batch file as a resource on my application.
I want to access this file on run time - so I trying to file this file on the Resource folder but I get an exception that the
"resource folder is not there"
I trying to find the resource file by this code
var allBatchFiles = Directory.GetFiles( string.Format( #"..\..\Resources\" ) );
So how to make this work ?
Note that, when you run your application in Visual Studio, it is executed from the bin subfolder, which changes relative paths.
However, if you want to embed the batch file into your application, you are entirely on the wrong track. The resource is compiled into your EXE, and you need to use a different method to retrieve it. The following MSDN article gives an example on how this can be done:
How to embed and access resources by using Visual C#
There are at least two types of resources you might be referring to.
First, if you are referring to a RESX file, then usually you can access resources directly. So if you have a RESX file called "MyRes.resx" with a resource in it called "MyString" then you can use:
string contents = Resources.MyRes.MyString;
If you are adding files to the solution and marking them as Embedded Resources, then you can use Assembly.GetManifestResourceStream to access the data. Here's the utility functions I use:
public static Stream GetResourceStream(string pathName, string resName, Assembly srcAssembly = null)
{
if (srcAssembly == null) srcAssembly = Assembly.GetCallingAssembly();
var allNames = srcAssembly.GetManifestResourceNames();
return srcAssembly.GetManifestResourceStream(pathName + "." + resName);
}
public static string GetResourceString(string pathName, string resName, Assembly srcAssembly = null)
{
if (srcAssembly == null) srcAssembly = Assembly.GetCallingAssembly();
StreamReader sr = new StreamReader(GetResourceStream(pathName, resName, srcAssembly));
string s = sr.ReadToEnd();
sr.Close();
return s;
}
The pathName is a bit tricky - it's the name of the project plus any folder names in your project. So if you have a project "MyApp" with a folder called "MyResources" with a file called "Batch.txt" marked as a resource, then you would access the contents with:
string contents = GetResourceString("MyApp.MyResources", "Batch.txt");

Using SharpZipLib to update a zip - something bad if the entry name includes folder

This is my code to update an existing zip, the callers pass in the ZipFile and have a finally block to close the zipfile.
private static void AddFiles(ZipFile zipFile, string path, string filesEntryLocation, string pattern = #"*") {
zipFile.BeginUpdate();
string[] files = Directory.GetFiles(path, pattern);
foreach (string filename in files) {
zipFile.Add(filename, (filesEntryLocation + filename.Split(new[] { '\\' }).Last()).Replace('\\','/'));
}
zipFile.CommitUpdate();
}
As you can see I'm adding entries into the zip and setting the entryname to be in a specific part of the zip folder hierarchy.
We are doing this to inject a product into a 'framework' web package - the framework supports loosely coupled products.
The result zip is fine, I can navigate it in Windows, I can extract it...
BUT MSDeploy comes along and where ever a new entry resulted in an addition to zip folder hierarchy, I get errors from msdeploy saying it couldn't open the zip - BUT only at that specific i.e. the zip is not completely corrupt, it's only where msdeploy starts navigating done a 'new' folder.
Now, if I extract the changed zip, and then re-zip it (using 7zip), and ask msdeploy to execute against that - no problem it works.
SO - is this SharpZipLib, or am I doing something wrong in adding to the zip folder hierarchy?
You should also add the folder entries to archive (if they do not exist before).

Categories