65.9K
CodeProject 正在变化。 阅读更多。
Home

WCF 服务发送带附件的电子邮件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.63/5 (19投票s)

2012年11月11日

CPOL

4分钟阅读

viewsIcon

87134

downloadIcon

20

设置、配置和调用 WCF 服务以发送带有多个文件附件的电子邮件。

介绍 

我想创建一个简单的 WCF 服务,以便能够发送带有多个大型附件的电子邮件。该服务在专用的 Web 服务器上运行,并由在不同应用程序服务器上运行的各种应用程序使用。本示例修改了源代码以使用 GMail。 

为了将二进制文件发送到 WCF 服务,文件内容被转换为 Base64 文本字符串。然后,文件在服务器上重新组装并附加到电子邮件。您需要在服务器上有一个具有读/写/修改权限的用户组的临时/缓冲文件夹。文件附件不需要在服务器上重新创建。  所有操作都在内存中完成(请参阅末尾的“更好的处理附件的方法”部分)。 

本文还将介绍 WCF 服务的服务器端和客户端配置,以便能够发送大文件。 

让我们开始吧 

创建一个新的 WCF 服务应用程序,并将其命名为 EmailServices。添加一个名为 FileAttachment 的新类,它将保存文件内容和有关每个文件附件的信息。

sing System.Runtime.Serialization;
using System.IO;
 
namespace EmailServices
{
    [DataContract]
    public class FileAttachment
    {
        [DataMember]
        public string FileContentBase64 { get; set; }
 
        [DataMember]
        public FileInfo Info { get; set; }
    }
}

IService1.cs 重命名为 IMail.cs,并将其内容替换为以下代码

using System.ServiceModel;
 
namespace EmailServices
{
    [ServiceContract]
    public interface IMail
    {
 
        [OperationContract]
        int SendEmail(string gmailUserAddress, string gmailUserPassword, string[] emailTo, 
          string[] ccTo, string subject, string body, bool isBodyHtml, FileAttachment[] attachments);
 
    }
}

我们服务的接口公开了一个名为 SendEmail 的方法,该方法返回一个整数。我更喜欢返回一个整数而不是 true/false 值,因为 Web 服务很难调试,并且数字代码可以提供更多关于错误发生位置的信息。

接下来,将 Service1 类重命名为 Mail 并将其内容替换为以下代码: 

using System;
using System.Collections.Generic;
using System.Net.Mail;
using System.IO;
using System.Net.Mime;
using System.Net;
 
namespace EmailServices
{
    public class Mail : IMail
    {
        private static string SMTP_SERVER = "smtp.gmail.com";
        private static int SMTP_PORT = 587;
        private static string TEMP_FOLDER = @"C:\temp\";
 
        public int SendEmail(string gmailUserAddress, string gmailUserPassword, string[] emailTo, 
          string[] ccTo, string subject, string body, bool isBodyHtml, FileAttachment[] attachments)
        {
            int result = -100;
            if (gmailUserAddress == null || gmailUserAddress.Trim().Length == 0)
            {
                return 10;
            }
            if (gmailUserPassword == null || gmailUserPassword.Trim().Length == 0)
            {
                return 20;
            }
            if (emailTo == null || emailTo.Length == 0)
            {
                return 30;
            }
 
            string tempFilePath = "";
            List<string> tempFiles = new List<string>();
 
            SmtpClient smtpClient = new SmtpClient(SMTP_SERVER, SMTP_PORT);
            smtpClient.EnableSsl = true;
            smtpClient.DeliveryMethod = SmtpDeliveryMethod.Network;
            smtpClient.UseDefaultCredentials = false;
            smtpClient.Credentials = new NetworkCredential(gmailUserAddress, gmailUserPassword);
 
            using (MailMessage message = new MailMessage())
            //Message object must be disposed before deleting temp attachment files
            {
                message.From = new MailAddress(gmailUserAddress);
                message.Subject = subject == null ? "" : subject;
                message.Body = body == null ? "" : body; 
                message.IsBodyHtml = isBodyHtml;
 
                foreach (string email in emailTo)
                {
                    //TODO: Check email is valid
                    message.To.Add(email);
                }
 
                if (ccTo != null && ccTo.Length > 0)
                {
                    foreach (string emailCc in ccTo)
                    {
                        //TODO: Check CC email is valid
                        message.CC.Add(emailCc);
                    }
                }
 //There is a better way!!! See bellow...
                if (attachments != null && attachments.Length > 0)
                {
                    foreach (FileAttachment fileAttachment in attachments)
                    {
                        if (fileAttachment.Info == null || fileAttachment.FileContentBase64 == null)
                        {
                            continue;
                        }
 
                        tempFilePath = CreateTempFile(TEMP_FOLDER, fileAttachment.FileContentBase64);
 
                        if (tempFilePath != null && tempFilePath.Length > 0)
                        {
                            Attachment attachment = new Attachment(tempFilePath, MediaTypeNames.Application.Octet); 
                            ContentDisposition disposition = attachment.ContentDisposition;
                            disposition.FileName = fileAttachment.Info.Name;
                            disposition.CreationDate = fileAttachment.Info.CreationTime;
                            disposition.ModificationDate = fileAttachment.Info.LastWriteTime;
                            disposition.ReadDate = fileAttachment.Info.LastAccessTime;
                            disposition.DispositionType = DispositionTypeNames.Attachment;
                            message.Attachments.Add(attachment);
                            tempFiles.Add(tempFilePath);
                        }
                        else
                        {
                            return 50;
                        }
                    }
                }
 
                try
                {
                    smtpClient.Send(message);
                    result = 0;
                }
                catch
                {
                    result = 60;
                }
            }
 
            DeleteTempFiles(tempFiles.ToArray());
            return result;
        }
 

 
        private static string CreateTempFile(string destDir, string fileContentBase64)
        {
            string tempFilePath = destDir + (destDir.EndsWith("\\") ? 
              "" : "\\") + Guid.NewGuid().ToString();
 
            try
            {
                using (FileStream fs = new FileStream(tempFilePath, FileMode.Create))
                {
                    byte[] bytes = System.Convert.FromBase64String(fileContentBase64); ;
                    fs.Write(bytes, 0, bytes.Length);
                }
            }
            catch
            {
                return null;
            }
 
            return tempFilePath;
        }
 
        private static void DeleteTempFiles(string[] tempFiles)
        {
            if (tempFiles != null && tempFiles.Length > 0)
            {
                foreach (string filePath in tempFiles)
                {
                    if (File.Exists(filePath))
                    {
                        try
                        {
                            File.Delete(filePath);
                        }
                        catch { } //Do nothing
                    }
                }
            }
        }
    }
}

这就是我们服务的核心。现在让我们来了解一下。 

在这个练习中,我硬编码了 SMTP 地址、端口号和临时文件夹位置,但正确的方法是把这些信息放在 Web 配置文件中,并使用 WebConfigurationManager 在运行时从中提取它。

代码在开始时进行一些基本的验证,以检查是否提供了所需的信息。 

有趣的部分是程序在临时文件夹中重新创建每个文件。我使用 Guid.NewGuid() 生成一个唯一的文件名来避免冲突。这种方法的问题是,当它附加到消息时,您需要更改文件名和其他属性。这就是 FileInfo 发挥作用的地方。

您还需要保留一个已创建的所有临时文件的列表,以便在发送电子邮件后将其删除。请记住,必须先释放 MailMessage 对象,然后才能尝试删除临时文件,否则您将收到文件仍在使用的错误。 

此外,当服务在 IIS 上运行时,它所使用的帐户需要具有对临时文件夹的读/写/修改权限。我只向用户组授予了所有权限。

右键单击 Mail.svc 文件并选择“查看标记”。确保服务名称是 EmailServices.Mail - 而不是  EmailServices.Service1。 

<%@ ServiceHost Language="C#" Debug="true" Service="EmailServices.Mail" CodeBehind="Mail.svc.cs" %>

现在是关于 web.config 文件的棘手部分(感谢 David Casey 帮助我)

<?xml version="1.0"?>
<configuration>
  <system.web>
    <compilation debug="true" targetFramework="4.0" />
    <httpRuntime executionTimeout="60" maxRequestLength="10240" />
  </system.web>
 
  <system.serviceModel>
    <services>
      <service name="EmailServices.Mail">
        <endpoint address="https://:4280/Mail.svc"
                  contract="EmailServices.IMail"
                  binding="basicHttpBinding"
                  bindingConfiguration="EmailBinding" />
      </service>
    </services>
 
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <serviceMetadata httpGetEnabled="true"/>
          <serviceDebug includeExceptionDetailInFaults="false"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
 
    <bindings>
      <basicHttpBinding>
        <binding name="EmailBinding"
                 closeTimeout="00:01:00"
                 openTimeout="00:01:00"
                 receiveTimeout="00:01:00"
                 sendTimeout="00:01:00"
                 allowCookies="false"
                 bypassProxyOnLocal="false"
                 hostNameComparisonMode="StrongWildcard"
                 maxBufferSize="2147483647"
                 maxBufferPoolSize="2147483647"
                 maxReceivedMessageSize="2147483647"
                 messageEncoding="Text"
                 textEncoding="utf-8"
                 transferMode="Buffered"
                 useDefaultWebProxy="true">
          <readerQuotas
                 maxDepth="32"
                 maxStringContentLength="2147483647"
                 maxArrayLength="20971520"
                 maxBytesPerRead="4096"
                 maxNameTableCharCount="16384" />
          <security mode="None">
            <transport clientCredentialType="None" proxyCredentialType="None"
                realm="" />
            <message clientCredentialType="UserName" algorithmSuite="Default" />
          </security>
        </binding>
      </basicHttpBinding>
    </bindings>
  </system.serviceModel>
 
  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true"/>
  </system.webServer>
 
</configuration>

这是我的调试配置。我将电子邮件大小限制为 10MB,超时时间限制为 1 分钟。端点地址和端口号在您的计算机上可能不同。如果您现在按 F5 运行该项目,则 ASP.NET 开发服务器应该启动 - 请注意它运行的端口号,并相应地调整您的配置。

现在让我们测试我们的电子邮件服务。

确保该服务在 ASP.NET 开发服务器或 IIS 上运行,并且您可以在浏览器中访问 https://:4280/Mail.svc。在单独的解决方案中创建一个新的控制台应用程序项目。右键单击解决方案资源管理器中的“引用”文件夹,然后选择“添加服务引用...” 

https://:4280/Mail.svc 粘贴到地址字段中,然后单击“转到”按钮。我们的服务应该出现在“服务”列表中。在命名空间字段中键入 EmailServRef,然后单击“确定”。以下代码显示了调用我们服务的一个示例: 

using System;
using System.IO;
using System.Collections.Generic;
 
namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            string emailFrom = "my_email@gmail.com";
            string password = "myPassword";
            string emailTo = "mybestfriend@emailaddress.com";
            string fileAttachmentPath = @"C:\TextFile.txt";
            int result = -1;
 
            try
            {
                using (EmailServRef.MailClient client = new EmailServRef.MailClient())
                {
                    List<EmailServRef.FileAttachment> allAttachments = new List<EmailServRef.FileAttachment>();
                    EmailServRef.FileAttachment attachment = new EmailServRef.FileAttachment();
                    attachment.Info = new FileInfo(fileAttachmentPath);
                    attachment.FileContentBase64 = Convert.ToBase64String(File.ReadAllBytes(fileAttachmentPath));
                    allAttachments.Add(attachment);
 
                    result = client.SendEmail(emailFrom, password, new string[] { emailTo }, null, 
                      "It works!!!", "Body text", false, allAttachments.ToArray());
                    Console.WriteLine("Result: " + result);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            Console.ReadLine();
        }
    }
}

您还需要调整 app.config 文件

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <system.serviceModel>
        <bindings>
            <basicHttpBinding>
                <binding name="BasicHttpBinding_IMail" 
                         closeTimeout="00:01:00"
                         openTimeout="00:01:00" 
                         receiveTimeout="00:10:00" 
                         sendTimeout="00:01:00"
                         allowCookies="false" 
                         bypassProxyOnLocal="false" 
                         hostNameComparisonMode="StrongWildcard"
                         maxBufferSize="2147483647" 
                         maxBufferPoolSize="2147483647" 
                         maxReceivedMessageSize="2147483647"
                         messageEncoding="Text" 
                         textEncoding="utf-8" 
                         transferMode="Buffered"
                         useDefaultWebProxy="true">
                    <readerQuotas 
                         maxDepth="32" 
                         maxStringContentLength="2147483647" 
                         maxArrayLength="20971520"
                         maxBytesPerRead="4096" 
                         maxNameTableCharCount="16384" />
                    <security mode="None">
                        <transport clientCredentialType="None" proxyCredentialType="None"
                            realm="" />
                        <message clientCredentialType="UserName" algorithmSuite="Default" />
                    </security>
                </binding>
            </basicHttpBinding>
        </bindings>
        <client>
            <endpoint address="https://:4280/Mail.svc" binding="basicHttpBinding"
                bindingConfiguration="BasicHttpBinding_IMail" contract="EmailServRef.IMail"
                name="BasicHttpBinding_IMail" />
        </client>
    </system.serviceModel>
</configuration>

根据您的需要进行任何必要的调整,然后运行该应用程序。它奏效了吗? 

现在剩下的就是将电子邮件服务部署在 IIS 上,并给它一个合适的 Web 地址,比如 http://EmailServices/Mail.svc

就是这样。如果您有关于如何改进它的好主意,请告诉我。

更好的处理附件的方法

添加于 2013-11-10

我意识到,您不需要在服务器上重新创建附件文件。最好使用 MemoryStream 在内存中完成此任务

if (attachments != null && attachments.Length > 0)
{
    foreach (FileAttachment fileAttachment in attachments)
    {
        byte[] bytes = System.Convert.FromBase64String(fileAttachment.FileContentBase64);
        MemoryStream memAttachment = new MemoryStream(bytes);
        Attachment attachment = new Attachment(memAttachment, fileAttachment.Info.Name);
        message.Attachments.Add(attachment);
    }
} 

历史

  • 创建于 2012-11-11
  • 添加了更好的处理附件的方法 2013-11-10
© . All rights reserved.