Mike Murphy Mike Murphy - 2 months ago 11
C# Question

How to attach a PDF from a ConnectStream to an Email

I am calling a WebAPI that returns a PDF in a ConnectStream. I write that ConnectStream to a file and then read it back in to a memorystream and I can attach it to an email. How can I accomplish the same thing without the IO overhead?

System.Net.HttpWebResponse webResponse = (System.Net.HttpWebResponse)webRequest.GetResponse();

System.IO.Stream stream = webResponse.GetResponseStream();

System.IO.StreamReader reader = new System.IO.StreamReader(stream, Encoding.Default);

string finalPath = System.IO.Path.Combine(outputPath, $"{startDate:yyyy-MM-dd}_{endDate:yyyy-MM-dd}.pdf");
System.IO.Stream fileStream = System.IO.File.OpenWrite(finalPath);

using (System.IO.StreamWriter sw = new System.IO.StreamWriter(fileStream, Encoding.Default))
{
sw.Write(reader.ReadToEnd());
sw.Flush();
sw.Close();
}

using (MemoryStream ms = new MemoryStream(File.ReadAllBytes(finalPath)))
{
using (MailMessage mailMessage = new MailMessage())
{
mailMessage.From = new MailAddress("noreply@somedomain.com");

mailMessage.To.Add("someone@somedomain.com");

mailMessage.Attachments.Add(new Attachment(ms, new System.Net.Mime.ContentType(System.Net.Mime.MediaTypeNames.Application.Pdf)));
SmtpClient smtpClient = new SmtpClient() { DeliveryMethod = System.Net.Mail.SmtpDeliveryMethod.SpecifiedPickupDirectory, PickupDirectoryLocation = outputPath };
smtpClient.Send(mailMessage);
}
}

Answer

First, the request stream is a one-way read only stream and cannot be passed to most methods that allow a stream, so you need to read it into something you can manipulate:

    public byte[] ReadStreamBinary(Stream stream, int bufferSize)
    {
        using (var ms = new MemoryStream())
        {
            var buffer = CreateBuffer(bufferSize);
            var finished = false;
            while (!finished)
            {
                buffer.Initialize();
                var bytes = stream.Read(buffer, 0, buffer.Length);
                if (bytes > 0)
                {
                    ms.Write(buffer, 0, bytes);
                }
                else
                {
                    finished = true;
                }
            }
            return ms.ToArray();
        }
    }

Then you can just create your MemoryStream from this array of bytes.

Since the default internal buffer for a stream is 4k, I almost always use that as my buffer size (4096). In your case, it may be easier to just modify the method to return the MemoryStream directly.

If you decide to return the stream, you'll need to remove the using (so the stream won't get closed/disposed) and return the stream pointer to the beginning.

    public MemoryStream ReadStreamBinary(Stream stream, int bufferSize)
    {
        var ms = new MemoryStream();
        var buffer = CreateBuffer(bufferSize);
        var finished = false;
        while (!finished)
        {
            buffer.Initialize();
            var bytes = stream.Read(buffer, 0, buffer.Length);
            if (bytes > 0)
            {
                ms.Write(buffer, 0, bytes);
            }
            else
            {
                finished = true;
            }
        }

        ms.Seek(0, SeekOrigin.Begin);
        return ms;
    }

Just remember to close/dispose of the stream in the calling method.

Oops. almost forgot to include the CreateBuffer code, just put this anywhere in your class:

public static Func<int, byte[]> CreateBuffer = x => (byte[])Array.CreateInstance(typeof(byte), x);
Comments