翼度科技»论坛 编程开发 .net 查看内容

我的Office Outlook插件开发之旅(一)

5

主题

5

帖子

15

积分

新手上路

Rank: 1

积分
15
目的

开发一款可以同步Outlook邮件通讯录信息的插件。
方案

  • VSTO 外接程序
  • COM 加载项
VSTO 外接程序对Outlook的支持,是从2010版本之后开始的。
VSTO 4.0 支持Outlook 2010以后的版本,所以编写一次代码,就可以在不同的版本上运行。
COM 加载项十分依赖于.NET Framework框架和Office的版本,之后讲到的时候你就明白。
VSTO 外接程序

VSTO,全称是Visual Studio Tools for Office,在微软的Visual Studio平台中进行Office专业开发。VSTO是VBA的替代产品,使用该工具包使开发Office应用程序变得更简单,VSTO还能使用Visual Studio开发环境中的众多功能。
VSTO依赖于.NET Framework框架,并且不能在.net core或者.net 5+以上的平台运行。
创建VSTO程序

使用Visual Studio 2013的新建项目,如果你使用更新版本的话,那么你大概率找不到。因为被移除了。比如Visual Studio 2019最低创建的Outlook 2013 外接程序

Office/SharePoint -> .Net Framework 4 -> Outlook 2010 外接程序
之后我们会得到,这样的项目结构


打开ThisAddIn.cs
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Xml.Linq;
  6. using Outlook = Microsoft.Office.Interop.Outlook;
  7. using Office = Microsoft.Office.Core;
  8. using Microsoft.Office.Interop.Outlook;
  9. using System.Windows.Forms;
  10. using System.Data;
  11. using System.Data.SqlClient;
  12. using System.IO;
  13. using System.Threading;
  14. using System.Collections;
  15. namespace ContactsSynchronization
  16. {
  17.     public partial class ThisAddIn
  18.     {
  19.         
  20.         private void ThisAddIn_Startup(object sender, System.EventArgs e)
  21.         {
  22.             // Outlook启动时执行
  23.             MessageBox.Show("Hello VSTO!");
  24.         }
  25.         private void ThisAddIn_Shutdown(object sender, System.EventArgs e)
  26.         {
  27.             // Outlook关闭时执行
  28.         }
  29.         #region VSTO 生成的代码
  30.         /// <summary>
  31.         /// 设计器支持所需的方法 - 不要
  32.         /// 使用代码编辑器修改此方法的内容。
  33.         /// </summary>
  34.         private void InternalStartup()
  35.         {
  36.             // 绑定声明周期函数
  37.             this.Startup += new System.EventHandler(ThisAddIn_Startup);
  38.             this.Shutdown += new System.EventHandler(ThisAddIn_Shutdown);
  39.         }
  40.         #endregion
  41.     }
  42. }
复制代码
启动试试看
到这里我们就已经把项目搭建起来了,但在写代码之前不如再认识认识Outlook的个个对象吧。
认识VSTO中常用对象

微软文档
https://learn.microsoft.com/zh-cn/dotnet/api/microsoft.office.interop.outlook.application?view=outlook-pia
常用类型

  • MAPIFolder表示Outlook中的一个文件夹
  • ContactItem 表示一个联系人
  • DistListItem 表示一个联系人文件夹中的群组
  • OlDefaultFolders 获取默认文件类型的枚举
  • OlItemType 获取文件夹子项类型的枚举
全局实例Application上挂载了我们用到大多数函数和属性。
  1. Application.Session;// 会话实例
  2. Application.Version;// DLL动态链接库版本
  3. Application.Name;// 应用名称
复制代码
Application.Session会话实例,可以获取Outlook的大多数状态,数据。如文件夹、联系人、邮件等。
Outlook文件夹结构

Outlook 按照邮件账号区分用户数据,即每个邮件账号都有独立的收件箱,联系人等。

Outlook 默认情况下的文件夹结构

获取第一个邮箱账号的默认联系人文件夹
  1. Application.Session.Stores.Cast<Outlook.Store()>.First().GetDefaultFolder(OlDefaultFolders.olFolderContacts);
复制代码
获取Outlook的状态信息

获取联系人信息
  1. MAPIFolder folder = Application.Session.GetDefaultFolder(OlDefaultFolders.olFolderContacts);//获取默认的通讯录文件夹
  2. IEnumerable<ContactItem> contactItems = folder.Items.OfType<ContactItem>(); // 获取文件夹下的子项,OfType<ContactItem>只拿联系人的
  3. foreach (ContactItem it in contactItems)
  4. {
  5.     // 拿联系人的各种信息
  6.     string fullName = it.FullName;
  7.     // 注意在此处修改联系人信息,再Save()是不生效的
  8. }
复制代码
添加联系人
  1. MAPIFolder folder = Application.Session.GetDefaultFolder(OlDefaultFolders.olFolderContacts);// 获取默认的联系人文件夹
  2. ContactItem contact = folder.Items.Add(OlItemType.olContactItem);// 新增联系人子项
  3. // 设置各种信息
  4. contact.FirstName = "三";
  5. contact.LastName = "张";
  6. contact.Email1Address = "zhangsan@163.com";
  7. // 存储联系人
  8. contact.Save();
复制代码
删除联系人
  1. Microsoft.Office.Interop.Outlook.MAPIFolder deletedFolder = application.Session.GetDefaultFolder(OlDefaultFolders.olFolderDeletedItems);// 默认的联系人文件夹
  2. int count = deletedFolder.Items.Count;// 获取子项数,包含联系人和群组
  3. for (int i = count; i > 0; i--)// 遍历删除
  4. {
  5.     deletedFolder.Items.Remove(i);
  6. }
复制代码
成品代码
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Xml.Linq;
  6. using Outlook = Microsoft.Office.Interop.Outlook;
  7. using Office = Microsoft.Office.Core;
  8. using Microsoft.Office.Interop.Outlook;
  9. using System.Windows.Forms;
  10. using System.Data;
  11. using System.Data.SqlClient;
  12. using System.IO;
  13. using System.Threading;
  14. using System.Collections;
  15. namespace ContactsSynchronization
  16. {
  17.     public partial class ThisAddIn
  18.     {
  19.         
  20.         private void ThisAddIn_Startup(object sender, System.EventArgs e)
  21.         {
  22.             OperatorContact operatorInstance = new OperatorContact(this.Application);
  23.             operatorInstance.Task();
  24.         }
  25.         private void ThisAddIn_Shutdown(object sender, System.EventArgs e)
  26.         {
  27.         }
  28.         #region VSTO 生成的代码
  29.         /// <summary>
  30.         /// 设计器支持所需的方法 - 不要
  31.         /// 使用代码编辑器修改此方法的内容。
  32.         /// </summary>
  33.         private void InternalStartup()
  34.         {
  35.             this.Startup += new System.EventHandler(ThisAddIn_Startup);
  36.             this.Shutdown += new System.EventHandler(ThisAddIn_Shutdown);
  37.         }
  38.         #endregion
  39.     }
  40.     class OperatorContact
  41.     {
  42.         public OperatorContact(Microsoft.Office.Interop.Outlook.Application application)
  43.         {
  44.             this.application = application;
  45.         }
  46.         Microsoft.Office.Interop.Outlook.Application application = null; // outlook程序实例
  47.         private static string addressBookName = "汤石集团通讯录";// 通讯录名称
  48.         private Microsoft.Office.Interop.Outlook.MAPIFolder addressBookFolder = null; // 通讯录文件夹实例
  49.         public void Task()
  50.         {
  51.             new Thread(Run).Start();
  52.         }
  53.         /// <summary>
  54.         /// 开个新线程执行任务,不要堵塞原来的线程
  55.         /// </summary>
  56.         private void Run()
  57.         {
  58.             try
  59.             {
  60.                 if (NeedUpdate())
  61.                 {
  62.                     addressBookFolder = getAddressBookFolder();// 覆盖式创建通讯录
  63.                     List<Contact> remoteContacts = readRemoteContacts();// 读取远程邮箱通讯录
  64.                     if (remoteContacts == null) return;
  65.                     Adjust(remoteContacts);// 调整联系人和群组
  66.                     updateClientVersion();// 更新本地通讯录版本号
  67.                 }
  68.             }
  69.             catch (System.Exception ex)
  70.             {
  71.                 const string path = @"C:\TONS\email-plugin-error.log";
  72.                 FileInfo fileInfo = new FileInfo(path);
  73.                 long length = 0;
  74.                 if (fileInfo.Exists && fileInfo.Length != 0) length = fileInfo.Length / 1024 / 1024;
  75.                 if (length <= 3) File.AppendAllText(path, ex.Message + "\r\n");
  76.                 else File.WriteAllText(path, ex.Message + "\r\n");
  77.             }
  78.         }
  79.         /// <summary>
  80.         /// 覆盖式创建通讯录
  81.         /// </summary>
  82.         /// <returns>通讯录文件夹实例</returns>
  83.         private Microsoft.Office.Interop.Outlook.MAPIFolder getAddressBookFolder()
  84.         {
  85.             // 获取用户第一个PST档的通讯录文件夹的枚举器
  86.             IEnumerator en = application.Session.Stores.Cast<Outlook.Store>().First()
  87.                 .GetDefaultFolder(OlDefaultFolders.olFolderContacts)
  88.                 .Folders.GetEnumerator();
  89.             bool exits = false;
  90.             Microsoft.Office.Interop.Outlook.MAPIFolder folder = null;
  91.             // 遍历文件夹
  92.             while (en.MoveNext()) {
  93.                 Microsoft.Office.Interop.Outlook.MAPIFolder current = (Microsoft.Office.Interop.Outlook.MAPIFolder)en.Current;
  94.                 if (current.Name == addressBookName) {
  95.                     exits = true;
  96.                     folder = current;
  97.                 }
  98.             }
  99.             if (!exits)
  100.              {
  101.                  // 创建汤石集团通讯录,并映射成通讯录格式
  102.                  Microsoft.Office.Interop.Outlook.MAPIFolder newFolder = application.Session.Stores.Cast<Outlook.Store>().First()
  103.                           .GetDefaultFolder(OlDefaultFolders.olFolderContacts)
  104.                           .Folders.Add(addressBookName);
  105.                  newFolder.ShowAsOutlookAB = true;// 设置成“联系人”文件夹
  106.                  return newFolder;
  107.              }
  108.              else {
  109.                 // 返回已经存在的同时集团通讯录文件夹,并删除里面的内容
  110.                 int count = folder.Items.Count;
  111.                 for (int i = count; i > 0; i--)
  112.                 {
  113.                     folder.Items.Remove(i);
  114.                 }
  115.                 Microsoft.Office.Interop.Outlook.MAPIFolder deletedFolder = application.Session.GetDefaultFolder(OlDefaultFolders.olFolderDeletedItems);
  116.                 count = deletedFolder.Items.Count;
  117.                 for (int i = count; i > 0; i--)
  118.                 {
  119.                     deletedFolder.Items.Remove(i);
  120.                 }
  121.                 return folder;
  122.              }
  123.         }
  124.         /// <summary>
  125.         /// 更新本地的铜须录版本
  126.         /// </summary>
  127.         private void updateClientVersion()
  128.         {
  129.             String path = @"C:\TONS\email-plugin-version.conf";
  130.             string version = getRemoteVersion();
  131.             if (!File.Exists(path))
  132.             {
  133.                 File.WriteAllText(path,version);
  134.             }
  135.             else {
  136.                 File.WriteAllText(path, version);
  137.             }
  138.         }
  139.         /// <summary>
  140.         /// 判断是否需要更新
  141.         /// </summary>
  142.         /// <returns>boolean值</returns>
  143.         private bool NeedUpdate()
  144.         {
  145.             string remoteVersion = getRemoteVersion();
  146.             if (remoteVersion == null) return false;
  147.             string clientVersion = getClientVersion();
  148.             return !(clientVersion == remoteVersion);
  149.         }
  150.         /// <summary>
  151.         /// 读取服务器的通讯录版本
  152.         /// </summary>
  153.         /// <returns>通讯录版本</returns>
  154.         private string getRemoteVersion()
  155.         {
  156.             List<Dictionary<string, object>> items = SelectList(
  157.                 "SELECT TOP(1) [version] FROM TonsOfficeA..VersionControl WHERE applicationID = N'EmailContact'"
  158.                 , "Server=192.168.2.1;Database=TonsOfficeA;uid=sa;pwd=dsc");
  159.             if (items == null) return null;
  160.             return items[0]["version"].ToString();
  161.         }
  162.         /// <summary>
  163.         /// 获取本地的通讯录版本
  164.         /// </summary>
  165.         /// <returns>通讯录版本</returns>
  166.         private string getClientVersion()
  167.         {
  168.             String path = @"C:\TONS\email-plugin-version.conf";
  169.             if (!File.Exists(path)) return null;
  170.             return File.ReadAllText(path);
  171.         }
  172.         /// <summary>
  173.         /// 读取远程的通讯录
  174.         /// </summary>
  175.         /// <returns>联系人实例集合</returns>
  176.         private List<Contact> readRemoteContacts()
  177.         {
  178.             List<Contact> remoteContacts = new List<Contact>();
  179.             List<Dictionary<string, object>> items =
  180.                 SelectList(
  181.                     "select [emailAddress],[firstName],[lastName],[companyName],[department],[_group] as 'group',[jobTitle] from [TonsOfficeA].[dbo].[EmailContacts]",
  182.                     "Server=192.168.2.1;Database=TonsOfficeA;uid=sa;pwd=dsc");
  183.             items.ForEach(it =>
  184.             {
  185.                 Contact contact = new Contact();
  186.                 contact.email1Address = it["emailAddress"].ToString();
  187.                 contact.firstName = it["firstName"].ToString();
  188.                 contact.lastName = it["lastName"].ToString();
  189.                 contact.companyName = it["companyName"].ToString();
  190.                 contact.department = it["department"].ToString();
  191.                 if (it["jobTitle"] != null) contact.jobTitle = it["jobTitle"].ToString();
  192.                 contact.groups = it["group"].ToString().Split(',').ToList();
  193.                 remoteContacts.Add(contact);
  194.             });
  195.             return remoteContacts;
  196.         }
  197.         /// <summary>
  198.         /// 执行select语句
  199.         /// </summary>
  200.         /// <param name="sql">select语句</param>
  201.         /// <param name="connection">数据库链接语句</param>
  202.         /// <returns>List<Dictionary<string, object>>结果</returns>
  203.         /// <exception cref="System.Exception"></exception>
  204.         public List<Dictionary<string, object>> SelectList(string sql, string connection)
  205.         {
  206.             if (sql == null || connection == null || sql == "" || connection == "")
  207.                 throw new System.Exception("未传入SQL语句或者Connection链接语句");
  208.             List<Dictionary<string, object>> list = new List<Dictionary<string, object>>();
  209.             SqlConnection conn = new SqlConnection(connection);
  210.             SqlCommand cmd = new SqlCommand(sql, conn);
  211.             try
  212.             {
  213.                 conn.Open();
  214.                 SqlDataReader sqlDataReader = cmd.ExecuteReader(CommandBehavior.CloseConnection);
  215.                 if (sqlDataReader == null) return null;
  216.                 while (sqlDataReader.Read())
  217.                 {
  218.                     int count = sqlDataReader.FieldCount;
  219.                     if (count <= 0) continue;
  220.                     Dictionary<string, object> map = new Dictionary<string, object>();
  221.                     for (int i = 0; i < count; i++)
  222.                     {
  223.                         string name = sqlDataReader.GetName(i);
  224.                         object value = sqlDataReader.GetValue(i);
  225.                         map.Add(name, value);
  226.                     }
  227.                     list.Add(map);
  228.                 }
  229.                 conn.Close();
  230.                 return list;
  231.             }
  232.             catch (System.Exception)
  233.             {
  234.                 conn.Close();
  235.                 return null;
  236.             }
  237.         }
  238.         /// <summary>
  239.         /// 调整通讯录联系人
  240.         /// </summary>
  241.         /// <param name="remoteContacts">数据库导入的联系人信息的源</param>
  242.         private void Adjust(List<Contact> remoteContacts)
  243.         {            
  244.             // copy一份以来做群组
  245.             List<Contact> distListItems = new List<Contact>();
  246.             Contact[] tempItems = new Contact[remoteContacts.Count];
  247.             remoteContacts.CopyTo(tempItems);
  248.             tempItems.ToList().ForEach(it =>
  249.             {
  250.                 it.groups.ForEach(g =>
  251.                 {
  252.                     Contact con = new Contact
  253.                     {
  254.                         firstName = it.firstName,
  255.                         lastName = it.lastName,
  256.                         email1Address = it.email1Address,
  257.                         companyName = it.companyName,
  258.                         department = it.department,
  259.                         group = g
  260.                     };
  261.                     distListItems.Add(con);
  262.                 });
  263.             });
  264.            
  265.             // 添加联系人
  266.             remoteContacts.ForEach(it =>
  267.             {
  268.                 ContactItem contact = addressBookFolder.Items.Add();
  269.                 contact.FirstName = it.firstName;
  270.                 contact.LastName = it.lastName;
  271.                 contact.Email1Address = it.email1Address;
  272.                 contact.CompanyName = it.companyName;
  273.                 contact.Department = it.department;
  274.                 if (it.jobTitle != null) contact.JobTitle = it.jobTitle;
  275.                 contact.Save();
  276.             });
  277.             // 按群组分组,并创建群组保存
  278.             List<ContactStore> contactStores = distListItems
  279.                 .GroupBy(it => it.group)
  280.                 .Select(it => new ContactStore { group = it.Key, contacts = it.ToList() })
  281.                 .ToList();
  282.             contactStores.ForEach(it =>
  283.             {
  284.                 DistListItem myItem = addressBookFolder.Items.Add(OlItemType.olDistributionListItem);
  285.                 it.contacts.ForEach(contact =>
  286.                 {
  287.                     string id = String.Format("{0}{1}({2})", contact.lastName, contact.firstName,
  288.                         contact.email1Address);
  289.                     Recipient recipient = application.Session.CreateRecipient(id);
  290.                     recipient.Resolve();
  291.                     myItem.AddMember(recipient);
  292.                 });
  293.                 myItem.DLName = it.group;
  294.                 myItem.Save();
  295.             });
  296.         }
  297.         struct Contact
  298.         {
  299.             public string email1Address; // 邮箱
  300.             public string firstName; // 姓氏
  301.             public string lastName; // 姓名
  302.             public string companyName; // 公司名称
  303.             public string department; // 部门名称
  304.             public List<string> groups; // 分组集合
  305.             public string group; // 分组
  306.             public string jobTitle; // 职称
  307.         }
  308.         struct ContactStore
  309.         {
  310.             public string group;
  311.             public List<Contact> contacts;
  312.         }
  313.     }
  314. }
复制代码
打包、安装和卸载

右键项目 -> 发布

发布后你会看到这样的结构

点击setup.exe即可安装了
卸载需要使用VSTOInstaller.exe
  1. "C:\Program Files (x86)\Common Files\microsoft shared\VSTO\10.0\VSTOInstaller.exe" /u "你的.vsto文件目录"
复制代码
来源:https://www.cnblogs.com/heirem/archive/2023/10/26/17782541.html
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

举报 回复 使用道具