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






4.63/5 (19投票s)
设置、配置和调用 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