Как добавить вложения файлов в электронное письмо .NET SmtpClient MailMessage из отправки формы

#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 объекты в своем исходном коде, поэтому я бы не стал слишком зацикливаться на этом.