Get up to 80 % extra points for free! More info:

Working With Our Own Files in C# 2 - Saving And Opening Zip

In the last tutorial on custom file formats, we introduced options of zip archives and started the employee data form project. In this part we'll learn to work with ZIP folders, save and open files of our own type.

Assigning the values to the employee object

Now we have to assign the form values to the employee object. Let's move back to the form code. Add the KeyUp event handler to all text fields. In this event handler, assign the values from the text fields to the employee object. The method also updates the form title. We'll use KeyUp so that the event isn't triggered when we assign the values from the code to a field. This will be one of the other methods.

private void FieldSynchronization(object sender, KeyEventArgs e)
{
    employee.FirstName = txtFirstName.Text;
    employee.LastName = txtLastName.Text;
    employee.Email = txtEmail.Text;
    employee.Phone = txtPhone.Text;
    this.Text = txtFirstName.Text + " " + txtLastName.Text;
}

Only the dtbBirth element will use the ValueChanged event. Employees assign their value in the event handler.

private void dtbBirth_ValueChanged(object sender, EventArgs e)
{
    employee.DateOfBirth = dtbBirth.Value;
}

Now we'll need the opposite and that is to assign the values from the employee object to the form. We'll use this method when we'll open the employee file and want to load it. Therefore, we'll create the UpdateFormData method. The method assigns the values from the employee object to all fields. The method also updates the form title.

private void UpdateFormData()
{
    txtFirstName.Text = employee.FirstName;
    txtLastName.Text = employee.LastName;
    txtEmail.Text = employee.Email;
    txtPhone.Text = employee.Phone;
    dtpBirth.Value = employee.DateOfBirth;
    pcbPhoto.Image = employee.Photo;

    this.Text = txtFirstName.Text + " " + txtLastName.Text;
}

Saving the file

The employee will have the Save method that will accept a single parameter, the path to the *.employee file. The method will save the employee to a zipped folder with photo.jpg and info.xml files. For info.xml, we'll need a method that will generate xml and write xml to an archive. In the method we'll work with data streams quite often.

First we'll create a temporary folder where we'll save the data. Saving the data directly in an archive would be unnecessarily problematic. We'll use the Path.Combine method, which we'll combine with AppData, a folder for our application and a random name. We'll create this folder.

string tempPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
    @"employees\",
    Path.GetRandomFileName());

Directory.CreateDirectory(tempPath);

Using the Using construct, we'll create a data stream for the info.xml file in our temporary folder.

using (FileStream fs = new FileStream(Path.Combine(tempPath, "info.xml"), FileMode.Create))
{
    SaveInfo(fs);
}

We'll pass this stream to the private SaveInfo method, which will write XML to the stream.

private void SaveInfo(Stream stream)

It's up to you whether you write XML using SAX, DOM or serialization. Let's not go into details in this article. My export code looks like this.

XmlDocument doc = new XmlDocument();

XmlElement employees = doc.CreateElement("Employees ");
XmlElement employee = doc.CreateElement("Employee");
employees.AppendChild(employees);
XmlDeclaration dec = doc.CreateXmlDeclaration("1.0", "utf-8", null);
doc.AppendChild(dec);
doc.AppendChild(employees);

XmlElement FirstName = doc.CreateElement("FirstName");
FirstName.AppendChild(doc.CreateTextNode(this.FirstName));

XmlElement LastName = doc.CreateElement("LastName");
LastName.AppendChild(doc.CreateTextNode(this.LastName));

XmlElement Email = doc.CreateElement("Email");
Email.AppendChild(doc.CreateTextNode(this.Email));

XmlElement Phone = doc.CreateElement("Phone");
Phone.AppendChild(doc.CreateTextNode(this.Phone));

XmlElement DateOfBirth = doc.CreateElement("DateOfBirth");
DateOfBirth.AppendChild(doc.CreateTextNode(this.DateOfBirth.ToShortDateString()));

employee.AppendChild(FirstName);
employee.AppendChild(LastName);
employee.AppendChild(Email);
employee.AppendChild(Phone);
employee.AppendChild(DateOfBirth);

doc.Save(stream);

Now let's return to the Save method. We'll create another stream, this time for the photo.jpg file. We'll save the image using its Save method, which receives the data stream and output format. Thanks to the output format, we're able to convert the image. Even though, for example, the user uploads an BMP image, it'll always be saved as JPG in the archive.

using (FileStream fs = new FileStream(Path.Combine(tempPath, "photo.jpg"), FileMode.Create))
{
    this.Photo.Save(fs, ImageFormat.Jpeg);
}

Zipping folders in C# .NET

.NET Framework 4.5 and higher includes classes for working with file zipping. They're in the System.IO.Com­pression namespace. However, to see these namespaces, you must add 2 references to the project. In Solution Explorer, right-click References > Add New Reference.

Files and I/O in C# .NET

In the search box, type System.IO, and then select System.IO.Com­pression and System.IO.Com­pression.File­System references.

Files and I/O in C# .NET

Confirm the dialog. Then type Using and add links to these libraries.

The ZipFile class has the static CreateFromDirectory method that accepts two parameters. The first parameter is the path of a folder to be zipped and the second one is the resulting file. This file doesn't necessarily have the zip extension and we'll take advantage of it. However, we'll simply assign paths to the method.

ZipFile.CreateFromDirectory(tempPath, file);

Let's go back to the form. Create a Click event for the btnSave object. We'll create a dialogue again. Because we'll use the employee suffix filter twice (open and save), we'll pass it to the Employee class as a static member.

public static string filter = "The Employee file (*.employee)|*.employee";

In the dialog, we'll set the default file name, which will be based on employee's first and last name. Let's open the dialog. If it returns OK, we'll save the employee.

private void btnSave_Click(object sender, EventArgs e)
{
    SaveFileDialog dialog = new SaveFileDialog();
    dialog.Filter = Employee.filter;
    dialog.FileName = employee.FirstName + " " + employee.LastName;
    if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK) {
        employee.Save(dialog.FileName);
    }
}

Run the application. Choose a photo, fill in the text fields and a date. Try saving the employee. Try changing its extension to zip and see what's inside. There will be the photo and the info.xml file.

Files and I/O in C# .NET

Opening files in the application

We'll deal with file opening in the second overloaded constructor. The path to the employee file will be passed to the constructor as a parameter. The constructor needs to get a data stream from this file, which we'll pass in the Using construct to the ZipArchive class constructor.

public Employee(string file)
{
    using (FileStream fs = new FileStream(file, FileMode.Open))
    {
        using (ZipArchive zip = new ZipArchive(fs))
        {

        }
    }

}

ZipArchive has the GetEntry method that returns ZipArchiveEntry. It contains a file saved in the archive. ZipArchiveEntry then has the Open method that returns a data stream to the file. We'll load the Info.xml file in the LoadInfo method, which we'll pass a data stream to and it'll process XML.

ZipArchiveEntry info = zip.GetEntry("info.xml");
LoadInfo(info.Open());

You can create the LoadInfo method in multiple ways, I used DOM. My code looks like this.

private void LoadInfo(Stream stream)
{
    XmlDocument doc = new XmlDocument();
    using (XmlReader r = XmlReader.Create(stream))
    {
        doc.Load(r);
        this.FirstName = doc.GetElementsByTagName("FirstName")[0].FirstChild.Value;
        this.LastName = doc.GetElementsByTagName("LastName")[0].FirstChild.Value;
        this.Email = doc.GetElementsByTagName("Email")[0].FirstChild.Value;
        this.Phone = doc.GetElementsByTagName("Phone")[0].FirstChild.Value;
        this.DateOfBirth = DateTime.Parse(doc.GetElementsByTagName("DateOfBirth")[0].FirstChild.Value);
    }
}

We'll load the photo similarly, we'll get the image from the data stream using the static Image.FromStream method.

ZipArchiveEntry photo = zip.GetEntry("photo.jpg");
this.Photo = Image.FromStream(photo.Open());

Let's go back to the form and create a Click event handler for the Open button. We'll create a dialog again and load a filter again. Open it and compare its return value with OK. If the user chooses a file, we'll create a new employee and pass the path to the file in the constructor. Finally, we'll update the form data so that the changes from the employee are reflected in the form. The whole method could look like this.

private void btnLoad_Click(object sender, EventArgs e)
{
    OpenFileDialog dialog = new OpenFileDialog();
    dialog.Filter = Employee.filter ;
    if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK) {
        employee = new Employee(dialog.FileName);
        UpdateFormData();
    }
}

Now the application is complete, you can try it.

Files and I/O in C# .NET

Try out the application and next time we'll look at possible improvements. We'll sanitize errors and tweak files in Windows Explorer. In the code below the article, there's a fully valid employee John Smith attached for testing.


 

Download

By downloading the following file, you agree to the license terms

Downloaded 4x (56.36 kB)
Application includes source codes in language C#

 

All articles in this section
Files and I/O in C# .NET
Article has been written for you by Filip Smolík
Avatar
User rating:
No one has rated this quite yet, be the first one!
Activities