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

这篇具有很好参考价值的文章主要介绍了我的Office Outlook插件开发之旅(一)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目的

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

方案

  1. VSTO 外接程序
  2. 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 Outlook插件开发之旅(一)
Office/SharePoint -> .Net Framework 4 -> Outlook 2010 外接程序

之后我们会得到,这样的项目结构
我的Office Outlook插件开发之旅(一)

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

打开ThisAddIn.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using Outlook = Microsoft.Office.Interop.Outlook;
using Office = Microsoft.Office.Core;
using Microsoft.Office.Interop.Outlook;
using System.Windows.Forms;
using System.Data;
using System.Data.SqlClient;
using System.IO;
using System.Threading;
using System.Collections;

namespace ContactsSynchronization
{
    public partial class ThisAddIn
    {
        
        private void ThisAddIn_Startup(object sender, System.EventArgs e)
        {
            // Outlook启动时执行
            MessageBox.Show("Hello VSTO!");
        }

        private void ThisAddIn_Shutdown(object sender, System.EventArgs e)
        {
            // Outlook关闭时执行
        }

        #region VSTO 生成的代码

        /// <summary>
        /// 设计器支持所需的方法 - 不要
        /// 使用代码编辑器修改此方法的内容。
        /// </summary>
        private void InternalStartup()
        {
            // 绑定声明周期函数
            this.Startup += new System.EventHandler(ThisAddIn_Startup);
            this.Shutdown += new System.EventHandler(ThisAddIn_Shutdown);
        }

        #endregion
    }
}

启动试试看

到这里我们就已经把项目搭建起来了,但在写代码之前不如再认识认识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上挂载了我们用到大多数函数和属性。

Application.Session;// 会话实例
Application.Version;// DLL动态链接库版本
Application.Name;// 应用名称

Application.Session会话实例,可以获取Outlook的大多数状态,数据。如文件夹、联系人、邮件等。

Outlook文件夹结构

Outlook 按照邮件账号区分用户数据,即每个邮件账号都有独立的收件箱,联系人等。
我的Office Outlook插件开发之旅(一)
Outlook 默认情况下的文件夹结构
我的Office Outlook插件开发之旅(一)
获取第一个邮箱账号的默认联系人文件夹

Application.Session.Stores.Cast<Outlook.Store()>.First().GetDefaultFolder(OlDefaultFolders.olFolderContacts);

获取Outlook的状态信息

获取联系人信息

MAPIFolder folder = Application.Session.GetDefaultFolder(OlDefaultFolders.olFolderContacts);//获取默认的通讯录文件夹
IEnumerable<ContactItem> contactItems = folder.Items.OfType<ContactItem>(); // 获取文件夹下的子项,OfType<ContactItem>只拿联系人的
foreach (ContactItem it in contactItems)
{
    // 拿联系人的各种信息
    string fullName = it.FullName;
    // 注意在此处修改联系人信息,再Save()是不生效的
}

添加联系人

MAPIFolder folder = Application.Session.GetDefaultFolder(OlDefaultFolders.olFolderContacts);// 获取默认的联系人文件夹
ContactItem contact = folder.Items.Add(OlItemType.olContactItem);// 新增联系人子项
// 设置各种信息
contact.FirstName = "三";
contact.LastName = "张";
contact.Email1Address = "zhangsan@163.com";
// 存储联系人
contact.Save();

删除联系人

Microsoft.Office.Interop.Outlook.MAPIFolder deletedFolder = application.Session.GetDefaultFolder(OlDefaultFolders.olFolderDeletedItems);// 默认的联系人文件夹
int count = deletedFolder.Items.Count;// 获取子项数,包含联系人和群组
for (int i = count; i > 0; i--)// 遍历删除
{
    deletedFolder.Items.Remove(i);
}

成品代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using Outlook = Microsoft.Office.Interop.Outlook;
using Office = Microsoft.Office.Core;
using Microsoft.Office.Interop.Outlook;
using System.Windows.Forms;
using System.Data;
using System.Data.SqlClient;
using System.IO;
using System.Threading;
using System.Collections;

namespace ContactsSynchronization
{
    public partial class ThisAddIn
    {
        

        private void ThisAddIn_Startup(object sender, System.EventArgs e)
        {
            OperatorContact operatorInstance = new OperatorContact(this.Application);
            operatorInstance.Task();
        }

        private void ThisAddIn_Shutdown(object sender, System.EventArgs e)
        {
        }

        #region VSTO 生成的代码

        /// <summary>
        /// 设计器支持所需的方法 - 不要
        /// 使用代码编辑器修改此方法的内容。
        /// </summary>
        private void InternalStartup()
        {
            this.Startup += new System.EventHandler(ThisAddIn_Startup);
            this.Shutdown += new System.EventHandler(ThisAddIn_Shutdown);
        }

        #endregion
    }

    class OperatorContact
    {
        public OperatorContact(Microsoft.Office.Interop.Outlook.Application application)
        {
            this.application = application;
        }

        Microsoft.Office.Interop.Outlook.Application application = null; // outlook程序实例

        private static string addressBookName = "汤石集团通讯录";// 通讯录名称

        private Microsoft.Office.Interop.Outlook.MAPIFolder addressBookFolder = null; // 通讯录文件夹实例

        public void Task()
        {
            new Thread(Run).Start();
        }

        /// <summary>
        /// 开个新线程执行任务,不要堵塞原来的线程
        /// </summary>
        private void Run()
        {
            try
            {
                if (NeedUpdate())
                {
                    addressBookFolder = getAddressBookFolder();// 覆盖式创建通讯录
                    List<Contact> remoteContacts = readRemoteContacts();// 读取远程邮箱通讯录
                    if (remoteContacts == null) return;
                    Adjust(remoteContacts);// 调整联系人和群组
                    updateClientVersion();// 更新本地通讯录版本号
                } 
            }
            catch (System.Exception ex)
            {
                const string path = @"C:\TONS\email-plugin-error.log";
                FileInfo fileInfo = new FileInfo(path);
                long length = 0;
                if (fileInfo.Exists && fileInfo.Length != 0) length = fileInfo.Length / 1024 / 1024;
                if (length <= 3) File.AppendAllText(path, ex.Message + "\r\n");
                else File.WriteAllText(path, ex.Message + "\r\n");
            }
        }

        /// <summary>
        /// 覆盖式创建通讯录
        /// </summary>
        /// <returns>通讯录文件夹实例</returns>
        private Microsoft.Office.Interop.Outlook.MAPIFolder getAddressBookFolder()
        {
            // 获取用户第一个PST档的通讯录文件夹的枚举器
            IEnumerator en = application.Session.Stores.Cast<Outlook.Store>().First()
                .GetDefaultFolder(OlDefaultFolders.olFolderContacts)
                .Folders.GetEnumerator();
            bool exits = false;
            Microsoft.Office.Interop.Outlook.MAPIFolder folder = null;

            // 遍历文件夹
            while (en.MoveNext()) {
                Microsoft.Office.Interop.Outlook.MAPIFolder current = (Microsoft.Office.Interop.Outlook.MAPIFolder)en.Current;
                if (current.Name == addressBookName) {
                    exits = true;
                    folder = current;
                }
            }

            if (!exits)
             {
                 // 创建汤石集团通讯录,并映射成通讯录格式
                 Microsoft.Office.Interop.Outlook.MAPIFolder newFolder = application.Session.Stores.Cast<Outlook.Store>().First()
                          .GetDefaultFolder(OlDefaultFolders.olFolderContacts)
                          .Folders.Add(addressBookName);
                 newFolder.ShowAsOutlookAB = true;// 设置成“联系人”文件夹
                 return newFolder;
             }
             else {
                // 返回已经存在的同时集团通讯录文件夹,并删除里面的内容
                int count = folder.Items.Count;
                for (int i = count; i > 0; i--)
                {
                    folder.Items.Remove(i);
                }
                Microsoft.Office.Interop.Outlook.MAPIFolder deletedFolder = application.Session.GetDefaultFolder(OlDefaultFolders.olFolderDeletedItems);
                count = deletedFolder.Items.Count;
                for (int i = count; i > 0; i--)
                {
                    deletedFolder.Items.Remove(i);
                }
                return folder;
             }
        }



        /// <summary>
        /// 更新本地的铜须录版本
        /// </summary>
        private void updateClientVersion()
        {
            String path = @"C:\TONS\email-plugin-version.conf";
            string version = getRemoteVersion();
            if (!File.Exists(path))
            {
                File.WriteAllText(path,version);
            }
            else {
                File.WriteAllText(path, version);
            }
        }

        /// <summary>
        /// 判断是否需要更新
        /// </summary>
        /// <returns>boolean值</returns>
        private bool NeedUpdate()
        {
            string remoteVersion = getRemoteVersion();
            if (remoteVersion == null) return false;
            string clientVersion = getClientVersion();
            return !(clientVersion == remoteVersion);
        }

        /// <summary>
        /// 读取服务器的通讯录版本
        /// </summary>
        /// <returns>通讯录版本</returns>
        private string getRemoteVersion()
        {
            List<Dictionary<string, object>> items = SelectList(
                "SELECT TOP(1) [version] FROM TonsOfficeA..VersionControl WHERE applicationID = N'EmailContact'"
                , "Server=192.168.2.1;Database=TonsOfficeA;uid=sa;pwd=dsc");
            if (items == null) return null;
            return items[0]["version"].ToString();
        }

        /// <summary>
        /// 获取本地的通讯录版本
        /// </summary>
        /// <returns>通讯录版本</returns>
        private string getClientVersion()
        {
            String path = @"C:\TONS\email-plugin-version.conf";
            if (!File.Exists(path)) return null;
            return File.ReadAllText(path);
        }

        /// <summary>
        /// 读取远程的通讯录
        /// </summary>
        /// <returns>联系人实例集合</returns>
        private List<Contact> readRemoteContacts()
        {
            List<Contact> remoteContacts = new List<Contact>();
            List<Dictionary<string, object>> items =
                SelectList(
                    "select [emailAddress],[firstName],[lastName],[companyName],[department],[_group] as 'group',[jobTitle] from [TonsOfficeA].[dbo].[EmailContacts]",
                    "Server=192.168.2.1;Database=TonsOfficeA;uid=sa;pwd=dsc");
            items.ForEach(it =>
            {
                Contact contact = new Contact();
                contact.email1Address = it["emailAddress"].ToString();
                contact.firstName = it["firstName"].ToString();
                contact.lastName = it["lastName"].ToString();
                contact.companyName = it["companyName"].ToString();
                contact.department = it["department"].ToString();
                if (it["jobTitle"] != null) contact.jobTitle = it["jobTitle"].ToString();
                contact.groups = it["group"].ToString().Split(',').ToList();
                remoteContacts.Add(contact);
            });
            return remoteContacts;
        }

        /// <summary>
        /// 执行select语句
        /// </summary>
        /// <param name="sql">select语句</param>
        /// <param name="connection">数据库链接语句</param>
        /// <returns>List<Dictionary<string, object>>结果</returns>
        /// <exception cref="System.Exception"></exception>
        public List<Dictionary<string, object>> SelectList(string sql, string connection)
        {
            if (sql == null || connection == null || sql == "" || connection == "")
                throw new System.Exception("未传入SQL语句或者Connection链接语句");
            List<Dictionary<string, object>> list = new List<Dictionary<string, object>>();
            SqlConnection conn = new SqlConnection(connection);
            SqlCommand cmd = new SqlCommand(sql, conn);
            try
            {
                conn.Open();
                SqlDataReader sqlDataReader = cmd.ExecuteReader(CommandBehavior.CloseConnection);
                if (sqlDataReader == null) return null;
                while (sqlDataReader.Read())
                {
                    int count = sqlDataReader.FieldCount;
                    if (count <= 0) continue;
                    Dictionary<string, object> map = new Dictionary<string, object>();
                    for (int i = 0; i < count; i++)
                    {
                        string name = sqlDataReader.GetName(i);
                        object value = sqlDataReader.GetValue(i);
                        map.Add(name, value);
                    }
                    list.Add(map);
                }

                conn.Close();
                return list;
            }
            catch (System.Exception)
            {
                conn.Close();
                return null;
            }
        }

        /// <summary>
        /// 调整通讯录联系人
        /// </summary>
        /// <param name="remoteContacts">数据库导入的联系人信息的源</param>
        private void Adjust(List<Contact> remoteContacts)
        {            
            // copy一份以来做群组
            List<Contact> distListItems = new List<Contact>();
            Contact[] tempItems = new Contact[remoteContacts.Count];
            remoteContacts.CopyTo(tempItems);
            tempItems.ToList().ForEach(it =>
            {
                it.groups.ForEach(g =>
                {
                    Contact con = new Contact
                    {
                        firstName = it.firstName,
                        lastName = it.lastName,
                        email1Address = it.email1Address,
                        companyName = it.companyName,
                        department = it.department,
                        group = g
                    };
                    distListItems.Add(con);
                });
            });
           
            // 添加联系人
            remoteContacts.ForEach(it =>
            {

                ContactItem contact = addressBookFolder.Items.Add();
                contact.FirstName = it.firstName;
                contact.LastName = it.lastName;
                contact.Email1Address = it.email1Address;
                contact.CompanyName = it.companyName;
                contact.Department = it.department;
                if (it.jobTitle != null) contact.JobTitle = it.jobTitle;
                contact.Save();
            });

            // 按群组分组,并创建群组保存
            List<ContactStore> contactStores = distListItems
                .GroupBy(it => it.group)
                .Select(it => new ContactStore { group = it.Key, contacts = it.ToList() })
                .ToList();
            contactStores.ForEach(it =>
            {
                DistListItem myItem = addressBookFolder.Items.Add(OlItemType.olDistributionListItem);
                it.contacts.ForEach(contact =>
                {
                    string id = String.Format("{0}{1}({2})", contact.lastName, contact.firstName,
                        contact.email1Address);
                    Recipient recipient = application.Session.CreateRecipient(id);
                    recipient.Resolve();
                    myItem.AddMember(recipient);
                });
                myItem.DLName = it.group;
                myItem.Save();
            });
        }

        struct Contact
        {
            public string email1Address; // 邮箱
            public string firstName; // 姓氏
            public string lastName; // 姓名
            public string companyName; // 公司名称
            public string department; // 部门名称
            public List<string> groups; // 分组集合
            public string group; // 分组
            public string jobTitle; // 职称
        }

        struct ContactStore
        {
            public string group;
            public List<Contact> contacts;
        }
    }
}

打包、安装和卸载

右键项目 -> 发布
我的Office Outlook插件开发之旅(一)
发布后你会看到这样的结构
我的Office Outlook插件开发之旅(一)
点击setup.exe即可安装了
卸载需要使用VSTOInstaller.exe文章来源地址https://www.toymoban.com/news/detail-711339.html

"C:\Program Files (x86)\Common Files\microsoft shared\VSTO\10.0\VSTOInstaller.exe" /u "你的.vsto文件目录"

到了这里,关于我的Office Outlook插件开发之旅(一)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Office如何通过VSTO进行PPT插件开发?

      VSTO(Visual Studio Tools for Office )是VBA的替代,是一套用于创建自定义Office应用程序的Visual Studio工具包。VSTO可以用Visual Basic 或者Visual C#扩展Office应用程序(例如Word、Excel、PPT)。本文通过VSTO进行PPT插件开发总结,并进行记录。    (1)安装Visual Studio   在百度等浏览器搜

    2024年02月17日
    浏览(40)
  • Office如何通过VSTO进行WORD插件开发?

      VSTO(Visual Studio Tools for Office )是VBA的替代,是一套用于创建自定义Office应用程序的Visual Studio工具包。VSTO可以用Visual Basic 或者Visual C#扩展Office应用程序(例如Word、Excel、PPT)。本文通过VSTO进行Word插件开发总结,并进行记录。    (1)安装Visual Studio   在百度等浏览器

    2024年02月16日
    浏览(34)
  • Dell笔记本更换系统主板后出现Microsoft Office Outlook Exchange 错误 80090016

    原因:由于**更换系统主板 **,Outlook Exchange 验证失败。 解决方法: 注销受影响的用户账号,登录具有管理员权限的账号。也可以使用网络共享操作。 进入 C:users\\\"username\\\"AppDataLocalPackages 文件夹。 将 Microsoft.AAD.BrokerPlugin_cw5n1h2txyewy 修改为 Microsoft.AAD.BrokerPlugin_cw5n1h2txyewy.old

    2024年02月11日
    浏览(46)
  • 我的AI之旅开始了

    知道重要,但是就是不动。 今天告诉自己,必须开始学习了。 用这篇博文作为1月份AI学习之旅的起跑点吧。 从此,无惧AI,无惧编程。 AI之路就在脚下。 AI,在我理解,就是让机器变得更加智能,能够以人思考和行为的方式去实行某种操作,更大更快更强。 编程和AI的关系

    2024年01月16日
    浏览(44)
  • 我的单片机入门之旅

    单片机作为现代电子技术的重要组成部分,广泛应用于各个领域。而作为一个初学者,我对单片机一无所知。但是,通过不断的学习和实践,我逐渐掌握了单片机的基本概念和使用方法。在我的单片机入门之旅中,经历了许多困难和挑战,但也取得了很大的进步和收获。 在开

    2024年03月22日
    浏览(41)
  • 我的苹果手机的越狱之旅

    最近因为业务需要,需要一台越狱手机;就把测试机6plus拿来做越狱使用,在此之前先大致说明一下越狱的原理、应用、流程以及可能存在的问题: 越狱是指通过一些技术手段,使iOS设备可以访问到iOS系统的全部控制权,从而可以实现更多的自定义和操作。以下是苹果手机越

    2024年02月11日
    浏览(41)
  • 我的世界Bukkit插件开发-第一章-初始环境搭建-搭建基于spigot核心的服务器-并连接客户端......

    基于Spigot核心的插件开发 本章实现本地成功搭建私服并连接客户端 前置开发工具:IDEA JDK环境-JKD-17 构建工具:maven 必备idea插件:Minecraft Development 服务器核心: Spigot-1.20.jar mc客户端 小部分内容来自AI大模型,如需深入,请联系博主或自行了解 手工不易,且看且珍惜 首次开始

    2024年03月21日
    浏览(52)
  • 我的C++奇迹之旅相遇:支持函数重载的原理

    函数重载概念 函数重载:是函数的一种特殊情况, C++ 允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表( 参数个数 或 类型 或 类型顺序 )不同,常用来处理实现功能类似数据类型不同的问题。 为什么C++支持函数重载,而C语言不支持函数重载呢?

    2024年04月15日
    浏览(44)
  • 三秒绘画!我的AI绘画之旅——Adobe体验

    首发于微信公众号:AI执剑人(微信号: AISwordholder ),欢迎大家订阅关注! 你敢相信下面这幅图只用了三秒就画出来了吗? 画画如此简单,这都是源于AIGC的快速发展,所谓AIGC,就是使用人工智能来生成内容,是现在人工智能中最为火热的领域之一!你只需要告诉人工智能

    2024年02月09日
    浏览(45)
  • 宋绪杰:我的大数据成长之旅 | 提升之路系列(三)

    导读 为了发挥清华大学多学科优势,搭建跨学科交叉融合平台,创新跨学科交叉培养模式,培养具有大数据思维和应用创新的“π”型人才,由清华大学研究生院、清华大学大数据研究中心及相关院系共同设计组织的“清华大学大数据能力提升项目”开始实施并深受校内师生

    2024年02月21日
    浏览(58)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包