Question

I'm trying to zip an XML tree and use it as an email attachment. The sending of the email with an attachment succeeds, but the zip file created is always corrupt – it is not a valid zip file but does contain binary data.

The problem is recreated as follows, see specifically BuildAttachment():

static void Main(string[] args)
{
    try
    {
        var report = new XElement("Report",
            new XElement("Product",
                new XElement("ID", "10000001"),
                new XElement("Name", "abcdefghijklm"),
                new XElement("Group", "nopqrstuvwxyz")
            )
        );
        var mailMessage = BuildMessage(report);
        EmailMessage(mailMessage);
        Thread.Sleep(10000);
    }
    catch (Exception e) { Console.WriteLine(e.Message); }
}
static MailMessage BuildMessage(XElement report)
{
    string from = "email1@address.com";
    string to = "email2@address.com";
    var message = new MailMessage(from, to, "Subject text", "Body text");

    var attachment = BuildAttachment(report);
    message.Attachments.Add(attachment);

    return message;
}
static Attachment BuildAttachment(XElement report)
{
    var inStream = new MemoryStream();
    report.Save(inStream);
    inStream.Position = 0;

    var outStream = new MemoryStream();
    var compress = new DeflateStream(outStream, CompressionMode.Compress);
    inStream.CopyTo(compress);

    outStream.Position = 0;
    return new Attachment(outStream, "report.zip", "application/zip");
}
static void EmailMessage(MailMessage message)
{
    var smtpClient = new SmtpClient("127.0.0.1");
    smtpClient.SendCompleted += SendCompletedCallback;
    smtpClient.SendAsync(message, null);
}
static void SendCompletedCallback(object sender, AsyncCompletedEventArgs e)
{
    if (e.Error != null)
        Console.WriteLine(e.Error.ToString());
}

To put the problem in context: It’s part of a windows service application so I don’t want to create files on disk, and the email message also contains xslt-transformed alternate views of the xml tree so I don’t want a completely different solution.

Any suggestions why the zip file is corrupt?

Was it helpful?

Solution

You're not creating a valid zip file, you're just creating a compressed stream and writing it to a file that has a *.zip extension. You should use a .NET zip library instead of DeflateStream.

You can use something like DotNetZip Library:

DotNetZip is an easy-to-use, FAST, FREE class library and toolset for manipulating zip files or folders. Zip and Unzip is easy: with DotNetZip, .NET applications written in VB, C# - any .NET language - can easily create, read, extract, or update zip files. For Mono or MS .NET.

If you have constraints and cannot use an external library, you could try to use GZipStream and add the attachment with an extension of *.gz which would be supported by common compression tools.

As useful information for future reference, .NET 4.5 will finally introduce native support for zip archiving through the ZipArchive class.

OTHER TIPS

I know this is an old question, but it turned up when I was searching for the same.

Here is my solution to add a compressed (zip) attachment using System.IO.Compression.ZipArchive (requires .NET 4.5 or higher) [based on the answer of acraig5075]:

byte[] report = GetSomeReportAsByteArray();
string fileName = "file.txt";

using (MemoryStream memoryStream = new MemoryStream())
{
    using (ZipArchive zipArchive = new ZipArchive(memoryStream, ZipArchiveMode.Update))
    {
        ZipArchiveEntry zipArchiveEntry = zipArchive.CreateEntry(fileName);
        using (StreamWriter streamWriter = new StreamWriter(zipArchiveEntry.Open()))
        {
            streamWriter.Write(Encoding.Default.GetString(report));
        }
    }

    MemoryStream attachmentStream = new MemoryStream(memoryStream.ToArray());

    Attachment attachment = new Attachment(attachmentStream, fileName + ".zip", MediaTypeNames.Application.Zip);
    mail.Attachments.Add(attachment);
}

For future reference, the alternate GZipStream method suggested in the accepted answer, to produce a .gz archive is achieved by changing the above code as follows.

static Attachment BuildAttachment(XElement report)
{
    var inStream = new MemoryStream();
    report.Save(inStream);
    inStream.Position = 0;

    var outStream = new MemoryStream();
    using (var compress = new GZipStream(outStream, CompressionMode.Compress))
    {
        inStream.CopyTo(compress);
        compress.Close();
    }

    var ms = new MemoryStream(outStream.ToArray());
    Attachment attachment = new Attachment(ms, "report.xml.gz", "application/gzip");
    return attachment;
}

The using block and the ToArray() call are required.

Thanks to "Neils. R"'s answer, I have created a static C# function to manage attachments and to zip them up if greater than 1Mb. Hope this helps anyone.

public static Attachment CreateAttachment(string fileNameAndPath, bool zipIfTooLarge = true, int bytes = 1 << 20)
{
    if (!zipIfTooLarge)
    {
        return new Attachment(fileNameAndPath);
    }

    var fileInfo = new FileInfo(fileNameAndPath);
    // Less than 1Mb just attach as is.
    if (fileInfo.Length < bytes)
    {
        var attachment = new Attachment(fileNameAndPath);

        return attachment;
    }

    byte[] fileBytes = File.ReadAllBytes(fileNameAndPath);

    using (var memoryStream = new MemoryStream())
    {
        string fileName = Path.GetFileName(fileNameAndPath);

        using (var zipArchive = new ZipArchive(memoryStream, ZipArchiveMode.Create))
        {
            ZipArchiveEntry zipArchiveEntry = zipArchive.CreateEntry(fileName, CompressionLevel.Optimal);

            using (var streamWriter = new StreamWriter(zipArchiveEntry.Open()))
            {
                streamWriter.Write(Encoding.Default.GetString(fileBytes));
            }
        }

        var attachmentStream = new MemoryStream(memoryStream.ToArray());
        string zipname = $"{Path.GetFileNameWithoutExtension(fileName)}.zip";
        var attachment = new Attachment(attachmentStream, zipname, MediaTypeNames.Application.Zip);

        return attachment;
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top