我想写一个简单的WCF服务,这个服务能够发送带有多个大附件的邮件。这个服务运行在一个专属服务器上而且被不同平台的不同应用服务所使用。此例子的源码已经修改以便使用 Gmail。
为了通过WCF服务去发送一个二进制的文件,这个文件的内容被转换成为Base64。文件不应该在服务端被重建。所有的工作都在内存中进行。
这边文章也包含了发送大容量文件的服务端和客户端的WCF服务。
创建一个新的WCF服务项目并且命名为EmailServices.添加名称为calledFilAttachmentwhich新的类,它的工作是保存每个附件的内容和信息。
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); } }我们定义的接口对外公开了一个名称为 calledSendEmailwhich 的方法并且返回一个整数。我倾向于返回一个整数而不是true/false值,因为当错误发生的时候,服务很难被调试并且包含太多的代码信息。
接着,重命名theService1class 为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: emp"; 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(Simple Mail Transfer Protocol简单邮件传输协议)地址,邮政编码和临时文件夹位置,但是更合适的方法是将这些信息写入配置文件中并且使用WebConfigurationManager在运行时进行解析。
这些代码对基本信息做了简单的校验确认。
有趣的地方在于项目重新创建了这些文件在临时文件夹中。我使用Guid.NewGuid()方法去生成一个文艺的文件夹名称来避免冲突。这个方法的问题在于当文件需要附加信息的时候,你必须去改变文件名称和其他信息,这就是whereFileInfocomes 的便利。
你还需要保存所有已创建临时文件的列表,这样在email发送出以后就可以删除它们。记住在你删除临时文件之前一定要先释放MailMessage对象,否则的话你会获得一个该文件仍在使用中的错误警告。
还有,在这个服务运行于IIS的时候,运行帐户需要有读/写/修改临时文件夹的权限。我只是给其用户组赋予了所有权限。
右键点击Mail.svc文件,选择"View Markup"(视图标记)。确认服务名是EmailServices.Mail —— 而不是not 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="http://localhost: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>
这个是我的调试配置。我给email大小限定上限为10MB,超时时间为1分钟。在你的机器上面端点地址和端口数字可能与此不相同。如果你现在点击F5运行这个项目的话,ASP.NET开发伺服器(ASP.NET Development Server)将会被激活——注意它运行时的端口数字,并据此相应的调整你的配置。
现在,让我们来测试我们的电子邮件服务。
确保程序运行在ASP.NET开发环境下或IIS环境下 ,然后你可以在你的浏览器访问 http://localhost:4280/Mail.svc 。
在一个独立的解决方案创建一个新的控制台应用程序项目。
参考在解决方案资源管理器中的项目中上右击鼠标,选择“添加服务引用...”
粘贴http://localhost:4280/Mail.svc在地址栏,然后点击“转到”按钮。我们的服务应该会出现在服务列表中。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="http://localhost: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:
我已经意识到, 你不需要重新建立附件服务器上的文件。 这是更好地方式使用MemoryStreamto在内存中完成这项任务: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); } }本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接。 2KB翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
2KB项目(www.2kb.com,源码交易平台),提供担保交易、源码交易、虚拟商品、在家创业、在线创业、任务交易、网站设计、软件设计、网络兼职、站长交易、域名交易、链接买卖、网站交易、广告买卖、站长培训、建站美工等服务