目的
開(kāi)發(fā)一款可以同步Outlook郵件通訊錄信息的插件。
方案
- VSTO 外接程序
- COM 加載項(xiàng)
VSTO 外接程序?qū)utlook的支持,是從2010版本之后開(kāi)始的。
VSTO 4.0 支持Outlook 2010以后的版本,所以編寫(xiě)一次代碼,就可以在不同的版本上運(yùn)行。
COM 加載項(xiàng)十分依賴于.NET Framework框架和Office的版本,之后講到的時(shí)候你就明白。
VSTO 外接程序
VSTO,全稱是Visual Studio Tools for Office,在微軟的Visual Studio平臺(tái)中進(jìn)行Office專業(yè)開(kāi)發(fā)。VSTO是VBA的替代產(chǎn)品,使用該工具包使開(kāi)發(fā)Office應(yīng)用程序變得更簡(jiǎn)單,VSTO還能使用Visual Studio開(kāi)發(fā)環(huán)境中的眾多功能。
VSTO依賴于.NET Framework框架,并且不能在.net core或者.net 5+以上的平臺(tái)運(yùn)行。
創(chuàng)建VSTO程序
使用Visual Studio 2013的新建項(xiàng)目,如果你使用更新版本的話,那么你大概率找不到。因?yàn)楸灰瞥?。比如Visual Studio 2019最低創(chuàng)建的Outlook 2013 外接程序
Office/SharePoint -> .Net Framework 4 -> Outlook 2010 外接程序
之后我們會(huì)得到,這樣的項(xiàng)目結(jié)構(gòu)
打開(kāi)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啟動(dòng)時(shí)執(zhí)行
MessageBox.Show("Hello VSTO!");
}
private void ThisAddIn_Shutdown(object sender, System.EventArgs e)
{
// Outlook關(guān)閉時(shí)執(zhí)行
}
#region VSTO 生成的代碼
/// <summary>
/// 設(shè)計(jì)器支持所需的方法 - 不要
/// 使用代碼編輯器修改此方法的內(nèi)容。
/// </summary>
private void InternalStartup()
{
// 綁定聲明周期函數(shù)
this.Startup += new System.EventHandler(ThisAddIn_Startup);
this.Shutdown += new System.EventHandler(ThisAddIn_Shutdown);
}
#endregion
}
}
啟動(dòng)試試看
到這里我們就已經(jīng)把項(xiàng)目搭建起來(lái)了,但在寫(xiě)代碼之前不如再認(rèn)識(shí)認(rèn)識(shí)Outlook的個(gè)個(gè)對(duì)象吧。
認(rèn)識(shí)VSTO中常用對(duì)象
微軟文檔
https://learn.microsoft.com/zh-cn/dotnet/api/microsoft.office.interop.outlook.application?view=outlook-pia
常用類型
- MAPIFolder表示Outlook中的一個(gè)文件夾
- ContactItem 表示一個(gè)聯(lián)系人
- DistListItem 表示一個(gè)聯(lián)系人文件夾中的群組
- OlDefaultFolders 獲取默認(rèn)文件類型的枚舉
- OlItemType 獲取文件夾子項(xiàng)類型的枚舉
全局實(shí)例Application
上掛載了我們用到大多數(shù)函數(shù)和屬性。
Application.Session;// 會(huì)話實(shí)例
Application.Version;// DLL動(dòng)態(tài)鏈接庫(kù)版本
Application.Name;// 應(yīng)用名稱
Application.Session
會(huì)話實(shí)例,可以獲取Outlook的大多數(shù)狀態(tài),數(shù)據(jù)。如文件夾、聯(lián)系人、郵件等。
Outlook文件夾結(jié)構(gòu)
Outlook 按照郵件賬號(hào)區(qū)分用戶數(shù)據(jù),即每個(gè)郵件賬號(hào)都有獨(dú)立的收件箱,聯(lián)系人等。
Outlook 默認(rèn)情況下的文件夾結(jié)構(gòu)
獲取第一個(gè)郵箱賬號(hào)的默認(rèn)聯(lián)系人文件夾
Application.Session.Stores.Cast<Outlook.Store()>.First().GetDefaultFolder(OlDefaultFolders.olFolderContacts);
獲取Outlook的狀態(tài)信息
獲取聯(lián)系人信息
MAPIFolder folder = Application.Session.GetDefaultFolder(OlDefaultFolders.olFolderContacts);//獲取默認(rèn)的通訊錄文件夾
IEnumerable<ContactItem> contactItems = folder.Items.OfType<ContactItem>(); // 獲取文件夾下的子項(xiàng),OfType<ContactItem>只拿聯(lián)系人的
foreach (ContactItem it in contactItems)
{
// 拿聯(lián)系人的各種信息
string fullName = it.FullName;
// 注意在此處修改聯(lián)系人信息,再Save()是不生效的
}
添加聯(lián)系人
MAPIFolder folder = Application.Session.GetDefaultFolder(OlDefaultFolders.olFolderContacts);// 獲取默認(rèn)的聯(lián)系人文件夾
ContactItem contact = folder.Items.Add(OlItemType.olContactItem);// 新增聯(lián)系人子項(xiàng)
// 設(shè)置各種信息
contact.FirstName = "三";
contact.LastName = "張";
contact.Email1Address = "zhangsan@163.com";
// 存儲(chǔ)聯(lián)系人
contact.Save();
刪除聯(lián)系人文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-711339.html
Microsoft.Office.Interop.Outlook.MAPIFolder deletedFolder = application.Session.GetDefaultFolder(OlDefaultFolders.olFolderDeletedItems);// 默認(rèn)的聯(lián)系人文件夾
int count = deletedFolder.Items.Count;// 獲取子項(xiàng)數(shù),包含聯(lián)系人和群組
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>
/// 設(shè)計(jì)器支持所需的方法 - 不要
/// 使用代碼編輯器修改此方法的內(nèi)容。
/// </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程序?qū)嵗?
private static string addressBookName = "湯石集團(tuán)通訊錄";// 通訊錄名稱
private Microsoft.Office.Interop.Outlook.MAPIFolder addressBookFolder = null; // 通訊錄文件夾實(shí)例
public void Task()
{
new Thread(Run).Start();
}
/// <summary>
/// 開(kāi)個(gè)新線程執(zhí)行任務(wù),不要堵塞原來(lái)的線程
/// </summary>
private void Run()
{
try
{
if (NeedUpdate())
{
addressBookFolder = getAddressBookFolder();// 覆蓋式創(chuàng)建通訊錄
List<Contact> remoteContacts = readRemoteContacts();// 讀取遠(yuǎn)程郵箱通訊錄
if (remoteContacts == null) return;
Adjust(remoteContacts);// 調(diào)整聯(lián)系人和群組
updateClientVersion();// 更新本地通訊錄版本號(hào)
}
}
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>
/// 覆蓋式創(chuàng)建通訊錄
/// </summary>
/// <returns>通訊錄文件夾實(shí)例</returns>
private Microsoft.Office.Interop.Outlook.MAPIFolder getAddressBookFolder()
{
// 獲取用戶第一個(gè)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)
{
// 創(chuàng)建湯石集團(tuán)通訊錄,并映射成通訊錄格式
Microsoft.Office.Interop.Outlook.MAPIFolder newFolder = application.Session.Stores.Cast<Outlook.Store>().First()
.GetDefaultFolder(OlDefaultFolders.olFolderContacts)
.Folders.Add(addressBookName);
newFolder.ShowAsOutlookAB = true;// 設(shè)置成“聯(lián)系人”文件夾
return newFolder;
}
else {
// 返回已經(jīng)存在的同時(shí)集團(tuán)通訊錄文件夾,并刪除里面的內(nèi)容
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>
/// 讀取服務(wù)器的通訊錄版本
/// </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>
/// 讀取遠(yuǎn)程的通訊錄
/// </summary>
/// <returns>聯(lián)系人實(shí)例集合</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>
/// 執(zhí)行select語(yǔ)句
/// </summary>
/// <param name="sql">select語(yǔ)句</param>
/// <param name="connection">數(shù)據(jù)庫(kù)鏈接語(yǔ)句</param>
/// <returns>List<Dictionary<string, object>>結(jié)果</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語(yǔ)句或者Connection鏈接語(yǔ)句");
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>
/// 調(diào)整通訊錄聯(lián)系人
/// </summary>
/// <param name="remoteContacts">數(shù)據(jù)庫(kù)導(dǎo)入的聯(lián)系人信息的源</param>
private void Adjust(List<Contact> remoteContacts)
{
// copy一份以來(lái)做群組
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);
});
});
// 添加聯(lián)系人
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();
});
// 按群組分組,并創(chuàng)建群組保存
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;
}
}
}
打包、安裝和卸載
右鍵項(xiàng)目 -> 發(fā)布
發(fā)布后你會(huì)看到這樣的結(jié)構(gòu)
點(diǎn)擊setup.exe即可安裝了
卸載需要使用VSTOInstaller.exe
文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-711339.html
"C:\Program Files (x86)\Common Files\microsoft shared\VSTO\10.0\VSTOInstaller.exe" /u "你的.vsto文件目錄"
到了這里,關(guān)于我的Office Outlook插件開(kāi)發(fā)之旅(一)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!