本文使用VS2010,VSTO40和 .Net Framework 3.5做一个校验Outlook 2007/2010发送邮件的收件人和标题。
[版权所有,转载请告知,并保存原作者名及文章在博客园的链接]
原因
最近收发的邮件越来越敏感。虽然自己小心翼翼但是有时候难免会发错,不过好记性不如烂笔头。决定花点时间研究一下如何在Outlook发邮件之前做一些必要的校验。要检验的是两个问题:
1. 我的工作环境是公司有自己的邮件系统,客户有另外一个邮件系统。因为我们在工作中和客户的系统是紧密相联的,部门中包括VP都有客户给的邮箱。而且,客户的邮件系统和Outlook结合的更好。所以,往往为了省事,大部分员工优先使用客户邮箱沟通所有的事情。但是,有些内容会涉及到本公司内部的信息,这是不应该使用客户邮箱来沟通的。
2. 发工作邮件没有标题是很糟糕的。虽然我极少犯此错误(印象中没有试过),但是犯错一次可能会有严重的不良影响。
工具
VS2010beta2,Outlook 2007/2010beta,VSTO 40,.Net Framework 3.5/4.0 beta2
VS2010beta2已经完成所有的功能开发,剩下的工作只是性能调优。所以,生成的代码应该可以无缝地RTM中继续使用。虽然不知道 .Net Framework 4.0什么时候发布,但VS2010是可以创建以前版本的工程文件。所以,为了使用者不需要安装临时的 .Net 4.0 beta,可以用 .Net 3.5配合 VSTO40创建功能。用户可以减少安装负担。
VSTO 40是随VS2010发布的针对Office的interop组件。
实现
其实没有什么技术含量。虽然从来没有使用过VSTO,网上的资料也不是很全,但2个小时基本上调试完成。共享出来的原因也就是为了减少大家搜索的时间而已。

ThisAddin.cs
1
using System;
2
using System.Collections.Generic;
3
using System.Collections;
4
using System.Linq;
5
using System.Text;
6
using System.IO;
7
using System.Xml.Linq;
8
using System.Xml;
9
using Outlook = Microsoft.Office.Interop.Outlook;
10
using Office = Microsoft.Office.Core;
11
using System.Windows.Forms;
12
using System.Diagnostics;
13
14
namespace MailReminderForOutlook20072010N35
15

{
16
public partial class ThisAddIn
17
{
18
/**//// <summary>
19
/// key is composed by sender domain and receiver domain;
20
/// val is exception list, there is a pair of seperators to wrap the exceptional address.
21
/// That is, the exception value should start with the seperator and end with the seperator.
22
/// For example: 1. "!#~[alias@domain.com]!#~"
23
/// 2. "!#~[alias1@domain.com]!#~[alias2@domain.com]!#~"
24
/// </summary>
25
private static SortedList<string, string> _violations =
26
new SortedList<string, string>(2);
27
private static readonly string SPT = "!#~";
28
private static readonly string SETTING_XML
29
= "MailReminderForOutlook.xml";
30
private static readonly string docFolder =
31
Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
32
private static readonly string xmlFile =
33
Path.Combine(docFolder, SETTING_XML);
34
private static readonly string NODE_CONFIG = "configuration";
35
private static readonly string NODE_VIOLATIONS = "violations";
36
37
private void LoadConfiguraton()
38
{
39
if (File.Exists(xmlFile))
40
{
41
XmlDocument doc = new XmlDocument();
42
doc.Load(xmlFile);
43
XmlNodeList list =
44
doc.SelectSingleNode(
45
NODE_CONFIG + "/" + NODE_VIOLATIONS).ChildNodes;
46
for (int i = 0; i < list.Count; ++i)
47
{
48
_violations.Add(
49
list[i].Name.ToLower(),
50
list[i].InnerText == null ? String.Empty : list[i].InnerText);
51
}
52
}
53
else
54
{
55
_violations.Add("domain1.comdomain2.com", "!#~sample@domain2.com!#~");
56
_violations.Add("domain2.comdomain1.com", "!#~sample@domain1.com!#~");
57
XmlDocument doc = new XmlDocument();
58
XmlNode baseNode = (XmlNode)doc.CreateElement(NODE_CONFIG);
59
doc.AppendChild(baseNode);
60
XmlNode violationNode =
61
(XmlNode)doc.CreateElement(NODE_VIOLATIONS);
62
baseNode.AppendChild(violationNode);
63
XmlElement first =
64
doc.CreateElement("domain1.comdomain2.com");
65
XmlElement second =
66
doc.CreateElement("domain2.comdomain1.com");
67
violationNode.AppendChild((XmlNode)first);
68
violationNode.AppendChild((XmlNode)second);
69
doc.Save(xmlFile);
70
doc = null;
71
}
72
}
73
74
private void ThisAddIn_Startup(object sender, System.EventArgs e)
75
{
76
try
77
{
78
LoadConfiguraton();
79
this.Application.ItemSend +=
80
new Outlook.ApplicationEvents_11_ItemSendEventHandler(
81
Application_ItemSend);
82
}
83
catch (Exception exp)
84
{
85
ShowError(exp);
86
}
87
}
88
89
private void ViolateVITSensitive(Outlook.MailItem item, StringBuilder msg)
90
{
91
bool violated = false;
92
String sender = item.SendUsingAccount.SmtpAddress;
93
string senderDomain =
94
sender.Substring(sender.IndexOf("@") + 1).ToLower();
95
96
StringBuilder tip = new StringBuilder(64);
97
foreach (Outlook.Recipient rec in item.Recipients)
98
{
99
string receiverDomain =
100
rec.AddressEntry.Address.Substring(
101
rec.Address.IndexOf("@") + 1).ToLower();
102
string key = senderDomain + receiverDomain;
103
if (_violations.ContainsKey(key))
104
{
105
if (_violations[key].IndexOf(
106
SPT + rec.AddressEntry.Address.ToLower() + SPT) < 0)
107
{
108
violated = true;
109
tip.AppendFormat(" {0}", rec.Address);
110
}
111
}
112
}
113
if (violated)
114
{
115
msg.AppendFormat(
116
"Your Address:{0}\r**These Addresses violate mail sensitive policy:\r{1}.",
117
sender, tip);
118
}
119
}
120
121
private void IsWithoutTitle(Outlook.MailItem item, StringBuilder msg)
122
{
123
if (null == item.Subject || item.Subject.Length < 1)
124
{
125
msg.AppendFormat(
126
"{0}**There is no title of this thread!",
127
msg.Length > 0 ? "\r\r" : String.Empty);
128
}
129
}
130
131
void Application_ItemSend(object Item, ref bool Cancel)
132
{
133
try
134
{
135
Cancel = false;
136
Outlook.MailItem item = (Outlook.MailItem)Item;
137
StringBuilder msg = new StringBuilder(256);
138
ViolateVITSensitive(item, msg);
139
IsWithoutTitle(item, msg);
140
if (msg.Length > 0)
141
{
142
DialogResult res = MessageBox.Show(
143
msg.ToString(),
144
"Do you want to send this thread anyway?",
145
MessageBoxButtons.YesNo,
146
MessageBoxIcon.Warning,
147
MessageBoxDefaultButton.Button2);
148
if (DialogResult.Yes != res)
149
{
150
Cancel = true;
151
}
152
}
153
}
154
catch (Exception exp)
155
{
156
ShowError(exp);
157
}
158
}
159
160
private void ShowError(Exception e)
161
{
162
MessageBox.Show(
163
String.Format(
164
"Click 'ctrl+c' to copy this error and send to XXXX@domain1.com.\rError:{0}\rStack:{1}",
165
e.Message, e.StackTrace),
166
"Mail Warning Outlook Plug-in Error",
167
MessageBoxButtons.OK,
168
MessageBoxIcon.Error);
169
}
170
171
private void ThisAddIn_Shutdown(object sender, System.EventArgs e)
172
{
173
}
174
175
VSTO generated code#region VSTO generated code
176
177
/**//// <summary>
178
/// Required method for Designer support - do not modify
179
/// the contents of this method with the code editor.
180
/// </summary>
181
private void InternalStartup()
182
{
183
this.Startup += new System.EventHandler(ThisAddIn_Startup);
184
this.Shutdown += new System.EventHandler(ThisAddIn_Shutdown);
185
}
186
187
#endregion
188
}
189
}
190
整个代码没什么神奇的地方。不再做详细的解释。这段代码也可以直接用在 .Net Framework 4.0的工程中。两者在本例并没有区别。
ViolateVITSensitive用来校验是否有违规的邮件地址。比如发件者的邮箱是alias@domain1.com。那么就不允许出现XXX@domain2.com 的收件人。发现违规会弹出提示,用户可以取消或者强制发送。
代码有不少可以优化的地方,比如所有的校验函数都可以做成DI(IoC)的模式(不过暂时没有必要。而且,这样只会扰乱只关心怎么用VSTO的读者)。
XML部分可以用lambda,不过我对这种在大部分情况只提高书写效率,并不会提高执行效率和调试效率的东西不太感冒(也可能是个人偏见,呵呵)。