Reputation: 105
I have an MVC form which has 3 file input fields on it. If those input fields have values on them, I want to add them as an attachment to a notification email. Please note that in the example below addedFiles
is a HttpFileCollectionBase
.
var smtpServer = Sitecore.Configuration.Settings.GetSetting("MailServer");
var smtpPort = Sitecore.Configuration.Settings.GetSetting("MailServerPort");
using (var stream = new MemoryStream())
using (var mailClient = new SmtpClient(smtpServer, Convert.ToInt16(smtpPort)))
using (var emailMessage = new MailMessage(fromAddress, toAddress, subject, message))
{
if (addedFiles != null && addedFiles.Count > 0)
{
//for some reason, the first file field was getting repeated at the end. Workaround.
for (int i = 0; i < 3; i++)
{
string fileName = addedFiles.Keys[i];
HttpPostedFileBase file = addedFiles[fileName];
if ((file.FileName.Contains(".pdf") ||
file.FileName.Contains(".doc")) && file.ContentLength > 0 && file.ContentLength < 10485760)
{
var fStream = file.InputStream;
fStream.Position = 0;
fStream.CopyTo(stream);
var s = stream.ToArray();
stream.Write(s, 0, file.ContentLength);
stream.Position = 0;
emailMessage.Attachments.Add(new Attachment(stream, file.FileName));
}
}
await Task.Run(() => mailClient.Send(emailMessage));
}
}
What is currently happening is the email is generated and the files do get attached. The size of the file is correct in the email attachment (if not a few KB larger than the original). However, upon attempting to open the file, I get a message that it is corrupted. The test file is a .docx file. I have tested the original file to ensure it is not corrupted and I am able to open it so I do know it's not the file. I'm sure I'm missing something silly. Just need a little guidance.
UPDATE
The issue is only with docx files. Pdf and doc files are fine. I'm not sure why only docx file come through as corrupted. Any ideas?
Upvotes: 0
Views: 3868
Reputation: 155608
You're using the Streams incorrectly. You need to use a separate Stream
for each Attachment
.
As a neat trick, (I believe) you don't need an intermediate stream or buffer - but you can pass the file-upload streams directly to the Attachment
constructors provided the MailMessage
will be sent before the ASP.NET Request/Response lifecycle ends. (Note that Attachment
takes ownership of the stream passed into its constructor, so you don't need to dispose of the attachment's stream yourself provided that the parent MailMessage
is also disposed).
Also there's a few things that don't look right in your code (such as hardcoding 3
for the number of files) and doing await Task.Run( ... )
for a non-async operation.
As you're using the System.Web
version of ASP.NET (i.e. not using ASP.NET Core) I don't recommend using any async
APIs because that messes with the request/response lifecycle.
Try this:
HttpFileCollectionBase addedFiles = ...
using( SmtpClient mailClient = new SmtpClient( smtpServer, Convert.ToInt16( smtpPort ) ) )
using( MailMessage emailMessage = new MailMessage( fromAddress, toAddress, subject, message ) )
{
if( addedFiles?.Count > 0 )
{
foreach( HttpPostedFileBase file in addedFiles )
{
Boolean isOK = ( file.FileName.EndsWith( ".pdf", StringComparison.OrdinalIgnoreCase ) || file.FileName.EndsWith( ".doc", StringComparison.OrdinalIgnoreCase ) ) && file.ContentLength > 0 && file.ContentLength < 10485760;
if( isOK )
{
Attachment att = new Attachment( file.InputStream, name: file.FileName );
emailMessage.Attachments.Add( att );
}
}
}
mailClient.Send( emailMessage );
}
If you do need to have the MailMessage
outlast the ASP.NET request/response lifecycle, or if you want to inspect or process the uploaded files before attaching them, then you'll need to buffer them individually, like so:
HttpFileCollectionBase addedFiles = ...
using( SmtpClient mailClient = new SmtpClient( smtpServer, Convert.ToInt16( smtpPort ) ) )
using( MailMessage emailMessage = new MailMessage( fromAddress, toAddress, subject, message ) )
{
if( addedFiles?.Count > 0 )
{
foreach( HttpPostedFileBase file in addedFiles )
{
Boolean isOK = ( file.FileName.EndsWith( ".pdf", StringComparison.OrdinalIgnoreCase ) || file.FileName.EndsWith( ".doc", StringComparison.OrdinalIgnoreCase ) ) && file.ContentLength > 0 && file.ContentLength < 10485760;
if( isOK )
{
MemoryStream copy = new MemoryStream( capacity: file.ContentLength );
file.InputStream.CopyTo( copy );
// Rewind the stream, this is important! (You cannot rewind ASP.NET's file.InputStream, hence why we use a MemoryStream copy).
copy.Seek( 0, SeekOrigin.Begin );
DoSomethingWithFileStream( copy );
// Rewind the stream again, this is important!
copy.Seek( 0, SeekOrigin.Begin );
Attachment att = new Attachment( copy, name: file.FileName );
emailMessage.Attachments.Add( att );
}
}
}
mailClient.Send( emailMessage );
}
Upvotes: 1