C# 从 Windows 服务中保存 Exchange .EML 文件
C# Saving Exchange .EML file from within Windows Service
我目前正在编写一个 Windows 服务来登录到特定的 Exchange 帐户,获取任何新电子邮件,解析它们,并将电子邮件保存在适当的文件夹中。
一切正常,除了保存电子邮件。
相关代码块(try / catch 块和不相关的内容已删除以保持简短):-
设置服务
ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2013_SP1);
service.Credentials = new WebCredentials(emailAddress, password);
service.AutodiscoverUrl(emailAddress, RedirectionUrlValidationCallback);
CheckForNewEmails(service);
获取并检查新邮件
private static void CheckForNewEmails(ExchangeService service)
{
int offset = 0;
int pageSize = 50;
bool more = true;
ItemView view = new ItemView(pageSize, offset, OffsetBasePoint.Beginning);
view.PropertySet = PropertySet.IdOnly;
FindItemsResults<Item> findResults;
List<EmailMessage> emails = new List<EmailMessage>();
while (more)
{
findResults = service.FindItems(WellKnownFolderName.Inbox, view);
foreach (var item in findResults.Items)
{
emails.Add((EmailMessage)item);
}
more = findResults.MoreAvailable;
if (more)
{
view.Offset += pageSize;
}
}
if (emails.Count > 0)
{
PropertySet properties = (BasePropertySet.FirstClassProperties);
service.LoadPropertiesForItems(emails, properties);
var mailItems = new List<DatabaseService.MailItem>();
var dbService = new DatabaseService();
var defaultUser = dbService.GetDefaultUser(defaultUserID);
foreach (var email in emails)
{
var mailItem = new DatabaseService.MailItem();
mailItem.mail = email;
mailItem.MessageID = email.InternetMessageId;
mailItem.Sender = email.Sender.Address;
dbService.FindLinks(service, ref mailItem, defaultUser);
mailItems.Add(mailItem);
LogMessage += (string.Format("Message ID : {1}{0}Sent : {2}{0}From : {3}{0}Subject : {4}{0}Hash : {5}{0}{6}{0}{0}", Environment.NewLine,
mailItem.MessageID ,
email.DateTimeSent.ToString("dd/MM/yyyy hh:mm:ss"),
email.Sender,
email.Subject,
mailItem.Hash,
mailItem.LinkString
));
}
}
}
查找应该链接到谁
public void FindLinks(ExchangeService service, ref MailItem mailItem, User defaultUser)
{
string address = mailItem.Sender;
// get file hash
var tempPath = Path.GetTempPath();
var fileName = GetFilenameFromSubject(mailItem);
var fullName = Path.Combine(tempPath, fileName);
SaveAsEML(service, mailItem, fullName);
var sha = new SHA256Managed();
mailItem.Hash = Convert.ToBase64String(sha.ComputeHash(File.OpenRead(fullName)));
File.Delete(fullName);
using (var db = DatabaseHelpers.GetEntityModel())
{
// Do all the linking stuff
}
}
最后,问题区域:将文件保存到磁盘(在本例中为临时文件夹,以便我可以获得文件哈希值,以检查它是否重复(例如抄送等))
查看 Whosebug 和其他各种来源,似乎有两种方法可以做到这一点:-
1) 使用MailItem.Load方法
private string SaveAsEML(ExchangeService service, MailItem mailItem, string savePath)
{
using (FileStream fileStream = File.Open(savePath, FileMode.Create, FileAccess.Write))
{
mailItem.mail.Load(new PropertySet(ItemSchema.MimeContent));
fileStream.Write(mailItem.mail.MimeContent.Content, 0, mailItem.mail.MimeContent.Content.Length);
}
}
使用上面的代码,文件被创建并具有正确的内容。
但是,在此之后尝试访问任何电子邮件属性会导致 Null Exception 崩溃(也是大崩溃,没有 Try/Catch 或 UnhandledException 陷阱会捡起它,只是杀死服务)
在上面的代码中,这一行使整个服务崩溃:-
LogMessage += (string.Format("Message ID : {1}{0}Sent : {2}{0}From : {3}{0}Subject : {4}{0}Hash : {5}{0}{6}{0}{0}", Environment.NewLine,
mailItem.MessageID ,
email.DateTimeSent.ToString("dd/MM/yyyy hh:mm:ss"),
email.Sender,
email.Subject,
mailItem.Hash,
mailItem.LinkString
));
具体参考email.DateTimeSent
我直接在这一行之前添加了一些诊断代码:-
if (email == null) { Log it's null; } else { Log NOT null;}
if (email.DateTimeSent == null) { Log it's null; } else { Log NOT null; }
第一行记录 NOT null,所以电子邮件仍然存在。
但是,第二行立即崩溃并出现空异常错误,没有记录任何内容。
如果我注释掉 SaveAsEML(service, mailItem, fullName);从 FindLinks 然后一切正常(当然除了文件没有保存)。
2) 绑定一个属性集合
private string SaveAsEML(ExchangeService service, MailItem mailItem, string savePath)
{
using (FileStream fileStream = File.Open(savePath, FileMode.Create, FileAccess.Write))
{
PropertySet props = new PropertySet(EmailMessageSchema.MimeContent);
var email = EmailMessage.Bind(service, mailItem.mail.Id, props);
fileStream.Write(mailItem.mail.MimeContent.Content, 0, mailItem.mail.MimeContent.Content.Length);
}
}
这样做,没有崩溃,它可以很好地处理每封电子邮件,并且可以引用 email.DateTimeSent 和所有其他属性。
不幸的是,它创建了零长度文件,没有内容。
为了这个我已经用头撞墙好几个小时了(我花了一个小时到处添加诊断只是为了追踪引用属性时发生的崩溃),这无疑是我忽略的一些微不足道的事情,所以如果哪位好心人可以指出我的愚蠢,我将不胜感激!
编辑:
通过在使用之前简单地保存属性的值,我能够很容易地解决上述问题:-
foreach (var email in emails)
{
var dateTimeSent = email.DateTimeSent;
var sender = email.Sender;
var subject = email.Subject;
var mailItem = new DatabaseService.MailItem();
mailItem.mail = email;
mailItem.MessageID = email.InternetMessageId;
mailItem.Sender = email.Sender.Address;
dbService.FindLinks(service, ref mailItem, defaultUser);
mailItems.Add(mailItem);
LogMessage += (string.Format("Message ID : {1}{0}Sent : {2}{0}From : {3}{0}Subject : {4}{0}Hash : {5}{0}{6}{0}{0}", Environment.NewLine,
mailItem.MessageID,
dateTimeSent.ToString("dd/MM/yyyy hh:mm:ss"),
sender,
subject,
mailItem.Hash ?? "No Hash",
mailItem.LinkString ?? "No Linkstring"
));
}
这让我可以使用保存文件的 MailItem.Load 方法,从而完成我在这种特定情况下所追求的。
然而,该服务还需要做其他事情,我真的不想保存每一个 属性 我需要访问的副本。
失败的原因
private string SaveAsEML(ExchangeService service, MailItem mailItem, string savePath)
{
using (FileStream fileStream = File.Open(savePath, FileMode.Create, FileAccess.Write))
{
PropertySet props = new PropertySet(EmailMessageSchema.MimeContent);
var email = EmailMessage.Bind(service, mailItem.mail.Id, props);
fileStream.Write(mailItem.mail.MimeContent.Content, 0, mailItem.mail.MimeContent.Content.Length);
}
}
是不是您使用了 Bind 来加载电子邮件变量中带有 MimeContent 的消息,然后您还没有使用它。 mailItem.mail 根本不会链接到作为此操作的一部分创建的电子邮件变量(即使它们是服务器上的同一对象)。在本地,这些只是两个独立的变量。 EWS 是 client/server,因此您发出请求,Managed API 将 return 表示操作结果的本地对象。但是该对象已断开连接,因此当您在上面进行绑定时,它只会生成另一个客户端对象来表示该操作的结果。例如,以上应该是
private string SaveAsEML(ExchangeService service, MailItem mailItem, string savePath)
{
using (FileStream fileStream = File.Open(savePath, FileMode.Create, FileAccess.Write))
{
PropertySet props = new PropertySet(EmailMessageSchema.MimeContent);
var email = EmailMessage.Bind(service, mailItem.mail.Id, props);
fileStream.Write(email.MimeContent.Content, 0, email .MimeContent.Content.Length);
}
}
我目前正在编写一个 Windows 服务来登录到特定的 Exchange 帐户,获取任何新电子邮件,解析它们,并将电子邮件保存在适当的文件夹中。
一切正常,除了保存电子邮件。
相关代码块(try / catch 块和不相关的内容已删除以保持简短):-
设置服务
ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2013_SP1);
service.Credentials = new WebCredentials(emailAddress, password);
service.AutodiscoverUrl(emailAddress, RedirectionUrlValidationCallback);
CheckForNewEmails(service);
获取并检查新邮件
private static void CheckForNewEmails(ExchangeService service)
{
int offset = 0;
int pageSize = 50;
bool more = true;
ItemView view = new ItemView(pageSize, offset, OffsetBasePoint.Beginning);
view.PropertySet = PropertySet.IdOnly;
FindItemsResults<Item> findResults;
List<EmailMessage> emails = new List<EmailMessage>();
while (more)
{
findResults = service.FindItems(WellKnownFolderName.Inbox, view);
foreach (var item in findResults.Items)
{
emails.Add((EmailMessage)item);
}
more = findResults.MoreAvailable;
if (more)
{
view.Offset += pageSize;
}
}
if (emails.Count > 0)
{
PropertySet properties = (BasePropertySet.FirstClassProperties);
service.LoadPropertiesForItems(emails, properties);
var mailItems = new List<DatabaseService.MailItem>();
var dbService = new DatabaseService();
var defaultUser = dbService.GetDefaultUser(defaultUserID);
foreach (var email in emails)
{
var mailItem = new DatabaseService.MailItem();
mailItem.mail = email;
mailItem.MessageID = email.InternetMessageId;
mailItem.Sender = email.Sender.Address;
dbService.FindLinks(service, ref mailItem, defaultUser);
mailItems.Add(mailItem);
LogMessage += (string.Format("Message ID : {1}{0}Sent : {2}{0}From : {3}{0}Subject : {4}{0}Hash : {5}{0}{6}{0}{0}", Environment.NewLine,
mailItem.MessageID ,
email.DateTimeSent.ToString("dd/MM/yyyy hh:mm:ss"),
email.Sender,
email.Subject,
mailItem.Hash,
mailItem.LinkString
));
}
}
}
查找应该链接到谁
public void FindLinks(ExchangeService service, ref MailItem mailItem, User defaultUser)
{
string address = mailItem.Sender;
// get file hash
var tempPath = Path.GetTempPath();
var fileName = GetFilenameFromSubject(mailItem);
var fullName = Path.Combine(tempPath, fileName);
SaveAsEML(service, mailItem, fullName);
var sha = new SHA256Managed();
mailItem.Hash = Convert.ToBase64String(sha.ComputeHash(File.OpenRead(fullName)));
File.Delete(fullName);
using (var db = DatabaseHelpers.GetEntityModel())
{
// Do all the linking stuff
}
}
最后,问题区域:将文件保存到磁盘(在本例中为临时文件夹,以便我可以获得文件哈希值,以检查它是否重复(例如抄送等))
查看 Whosebug 和其他各种来源,似乎有两种方法可以做到这一点:-
1) 使用MailItem.Load方法
private string SaveAsEML(ExchangeService service, MailItem mailItem, string savePath)
{
using (FileStream fileStream = File.Open(savePath, FileMode.Create, FileAccess.Write))
{
mailItem.mail.Load(new PropertySet(ItemSchema.MimeContent));
fileStream.Write(mailItem.mail.MimeContent.Content, 0, mailItem.mail.MimeContent.Content.Length);
}
}
使用上面的代码,文件被创建并具有正确的内容。
但是,在此之后尝试访问任何电子邮件属性会导致 Null Exception 崩溃(也是大崩溃,没有 Try/Catch 或 UnhandledException 陷阱会捡起它,只是杀死服务)
在上面的代码中,这一行使整个服务崩溃:-
LogMessage += (string.Format("Message ID : {1}{0}Sent : {2}{0}From : {3}{0}Subject : {4}{0}Hash : {5}{0}{6}{0}{0}", Environment.NewLine,
mailItem.MessageID ,
email.DateTimeSent.ToString("dd/MM/yyyy hh:mm:ss"),
email.Sender,
email.Subject,
mailItem.Hash,
mailItem.LinkString
));
具体参考email.DateTimeSent
我直接在这一行之前添加了一些诊断代码:-
if (email == null) { Log it's null; } else { Log NOT null;}
if (email.DateTimeSent == null) { Log it's null; } else { Log NOT null; }
第一行记录 NOT null,所以电子邮件仍然存在。
但是,第二行立即崩溃并出现空异常错误,没有记录任何内容。
如果我注释掉 SaveAsEML(service, mailItem, fullName);从 FindLinks 然后一切正常(当然除了文件没有保存)。
2) 绑定一个属性集合
private string SaveAsEML(ExchangeService service, MailItem mailItem, string savePath)
{
using (FileStream fileStream = File.Open(savePath, FileMode.Create, FileAccess.Write))
{
PropertySet props = new PropertySet(EmailMessageSchema.MimeContent);
var email = EmailMessage.Bind(service, mailItem.mail.Id, props);
fileStream.Write(mailItem.mail.MimeContent.Content, 0, mailItem.mail.MimeContent.Content.Length);
}
}
这样做,没有崩溃,它可以很好地处理每封电子邮件,并且可以引用 email.DateTimeSent 和所有其他属性。
不幸的是,它创建了零长度文件,没有内容。
为了这个我已经用头撞墙好几个小时了(我花了一个小时到处添加诊断只是为了追踪引用属性时发生的崩溃),这无疑是我忽略的一些微不足道的事情,所以如果哪位好心人可以指出我的愚蠢,我将不胜感激!
编辑:
通过在使用之前简单地保存属性的值,我能够很容易地解决上述问题:-
foreach (var email in emails)
{
var dateTimeSent = email.DateTimeSent;
var sender = email.Sender;
var subject = email.Subject;
var mailItem = new DatabaseService.MailItem();
mailItem.mail = email;
mailItem.MessageID = email.InternetMessageId;
mailItem.Sender = email.Sender.Address;
dbService.FindLinks(service, ref mailItem, defaultUser);
mailItems.Add(mailItem);
LogMessage += (string.Format("Message ID : {1}{0}Sent : {2}{0}From : {3}{0}Subject : {4}{0}Hash : {5}{0}{6}{0}{0}", Environment.NewLine,
mailItem.MessageID,
dateTimeSent.ToString("dd/MM/yyyy hh:mm:ss"),
sender,
subject,
mailItem.Hash ?? "No Hash",
mailItem.LinkString ?? "No Linkstring"
));
}
这让我可以使用保存文件的 MailItem.Load 方法,从而完成我在这种特定情况下所追求的。
然而,该服务还需要做其他事情,我真的不想保存每一个 属性 我需要访问的副本。
失败的原因
private string SaveAsEML(ExchangeService service, MailItem mailItem, string savePath)
{
using (FileStream fileStream = File.Open(savePath, FileMode.Create, FileAccess.Write))
{
PropertySet props = new PropertySet(EmailMessageSchema.MimeContent);
var email = EmailMessage.Bind(service, mailItem.mail.Id, props);
fileStream.Write(mailItem.mail.MimeContent.Content, 0, mailItem.mail.MimeContent.Content.Length);
}
}
是不是您使用了 Bind 来加载电子邮件变量中带有 MimeContent 的消息,然后您还没有使用它。 mailItem.mail 根本不会链接到作为此操作的一部分创建的电子邮件变量(即使它们是服务器上的同一对象)。在本地,这些只是两个独立的变量。 EWS 是 client/server,因此您发出请求,Managed API 将 return 表示操作结果的本地对象。但是该对象已断开连接,因此当您在上面进行绑定时,它只会生成另一个客户端对象来表示该操作的结果。例如,以上应该是
private string SaveAsEML(ExchangeService service, MailItem mailItem, string savePath)
{
using (FileStream fileStream = File.Open(savePath, FileMode.Create, FileAccess.Write))
{
PropertySet props = new PropertySet(EmailMessageSchema.MimeContent);
var email = EmailMessage.Bind(service, mailItem.mail.Id, props);
fileStream.Write(email.MimeContent.Content, 0, email .MimeContent.Content.Length);
}
}