#c# #attachment #smtpclient
#c# #вложение #smtpclient
Вопрос:
У меня есть форма MVC, в которой есть 3 поля ввода файла. Если в этих полях ввода есть значения, я хочу добавить их в качестве вложения в электронное письмо с уведомлением. Пожалуйста, обратите внимание, что в приведенном ниже примере addedFiles
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 amp;amp; 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")) amp;amp; file.ContentLength > 0 amp;amp; 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));
}
}
В настоящее время происходит создание электронного письма и прикрепление файлов. Размер файла указан правильно во вложении электронного письма (если не на несколько КБ больше, чем оригинал). Однако при попытке открыть файл я получаю сообщение о том, что он поврежден. Тестовый файл представляет собой файл .docx. Я проверил исходный файл, чтобы убедиться, что он не поврежден, и я могу его открыть, поэтому я знаю, что это не файл. Я уверен, что я упускаю что-то глупое. Просто нужно небольшое руководство.
Обновить
Проблема только с файлами docx. Файлы Pdf и doc в порядке. Я не уверен, почему только файл docx отображается как поврежденный. Есть идеи?
Ответ №1:
Вы неправильно используете потоки. Вам нужно использовать отдельный Stream
для каждого Attachment
.
В качестве изящного трюка (я полагаю) вам не нужен промежуточный поток или буфер, но вы можете передавать потоки загрузки файлов непосредственно Attachment
конструкторам при условии MailMessage
, что они будут отправлены до отправки ASP.NET Жизненный цикл запроса/Ответа заканчивается. (Обратите внимание, что Attachment
он становится владельцем потока, переданного в его конструктор, поэтому вам не нужно самостоятельно удалять поток вложения при условии, что родительский MailMessage
элемент также удален).
Также есть несколько вещей, которые не выглядят правильно в вашем коде (например, жесткое кодирование 3
для количества файлов) и выполнение await Task.Run( ... )
для неасинхронной операции.
Поскольку вы используете System.Web
версию ASP.NET (т.е. не используя ASP.NET Ядро) Я не рекомендую использовать какие-либо async
API, потому что это нарушает жизненный цикл запроса / ответа.
Попробуйте это:
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 ) ) amp;amp; file.ContentLength > 0 amp;amp; file.ContentLength < 10485760;
if( isOK )
{
Attachment att = new Attachment( file.InputStream, name: file.FileName );
emailMessage.Attachments.Add( att );
}
}
}
mailClient.Send( emailMessage );
}
Если вам нужно, чтобы MailMessage
пережить ASP.NET жизненный цикл запроса / ответа, или если вы хотите проверить или обработать загруженные файлы перед их прикреплением, тогда вам нужно будет буферизировать их по отдельности, например:
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 ) ) amp;amp; file.ContentLength > 0 amp;amp; 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 );
}
Комментарии:
1. Спасибо! Я обновлю свой код. Я буквально весь день работал над чем-то, что должно было быть простым. Я продолжал использовать файл docx в качестве теста. Проблема заключается в типе файла docx. Pdf и doc указаны правильно. Есть идеи, почему может быть поврежден ТОЛЬКО файл docx?. Кроме того, я использую async и фактически отправляю 3 электронных письма на 3 разных адреса электронной почты. Я получал ошибки, пока не переключился на это.
2. @Mikeyp Я понимаю, что форматы файлов PDF и (не -OOXML) DOC допускают «ненужные» данные в конце своих файлов, но новые форматы менее снисходительны. Я чувствую, что есть также некоторое недетерминированное поведение в том, как вы неправильно использовали
Stream
объекты в своем исходном коде, поэтому я бы не стал слишком зацикливаться на этом.