本文使用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
1using System;
2using System.Collections.Generic;
3using System.Collections;
4using System.Linq;
5using System.Text;
6using System.IO;
7using System.Xml.Linq;
8using System.Xml;
9using Outlook = Microsoft.Office.Interop.Outlook;
10using Office = Microsoft.Office.Core;
11using System.Windows.Forms;
12using System.Diagnostics;
13
14namespace 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,不过我对这种在大部分情况只提高书写效率,并不会提高执行效率和调试效率的东西不太感冒(也可能是个人偏见,呵呵)。