如何从表单提交向 .NET SmtpClient MailMessage 电子邮件添加文件附件

How to add file attachments to .NET SmtpClient MailMessage email from a form submission

我有一个 MVC 表单,上面有 3 个文件输入字段。如果这些输入字段上有值,我想将它们添加为通知电子邮件的附件。请注意,在下面的示例中 addedFilesHttpFileCollectionBase

        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));                  
            }
        }

当前发生的情况是生成了电子邮件并且确实附加了文件。电子邮件附件中的文件大小正确(如果不是比原始文件大几 KB)。但是,在尝试打开该文件时,我收到一条消息,提示它已损坏。测试文件是一个 .docx 文件。我已经测试了原始文件以确保它没有损坏并且我能够打开它所以我知道它不是文件。我确定我错过了一些愚蠢的东西。只需要一点指导。

更新

问题仅与 docx 文件有关。 pdf 和 doc 文件都可以。我不确定为什么只有 docx 文件会损坏。有什么想法吗?

您对流的使用不正确。您需要为每个 Attachment.

使用单独的 Stream

作为一个巧妙的技巧,(我相信)您不需要中间流或缓冲区 - 但您可以将文件上传流直接传递给 Attachment 构造函数 提供MailMessage 将在 ASP.NET Request/Response 生命周期结束 之前发送。 (请注意,Attachment 拥有传递给其构造函数的流的所有权,因此您无需自行处理附件流 前提是父级 MailMessage 也已处理).

还有一些事情在您的代码中看起来不正确(例如对文件数量进行硬编码 3)以及对非异步操作执行 await Task.Run( ... )

因为您使用的是 ASP.NET 的 System.Web 版本(即 而不是 使用 ASP.NET 核心)我不建议使用任何 async API,因为这会扰乱 request/response 生命周期。

试试这个:

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 );
}

如果您确实需要 MailMessage 比 ASP.NET request/response 生命周期更长,或者如果您想在附加文件之前检查或处理上传的文件,那么您将需要单独缓冲它们,像这样:

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 );
}