使用Exchange Web Services API提取Exchange 2007公共日历预约

24
我们在Exchange 2007公共文件夹中设置了公司的公共日历。我可以使用下面的代码检索当前日期的个人日历约会。我在网上搜索了很多,但找不到有人从公共文件夹日历中检索日历信息的例子。
这似乎是可行的,但我无法让它正常工作。我该如何修改下面的代码来访问日历?我不想通过asp.net创建任何约会,只想检索一个简单的列表。我也愿意接受任何其他建议。谢谢。
*添加赏金* - 我不可能是唯一需要这样做的人。让我们为未来的世代解决这个问题。
*由于无知而更新* - 我忘了提到我正在处理.NET 2.0项目(非常重要,你认为呢?)。
*添加我的代码解决方案* - 我已经用最终起作用的代码替换了原始代码示例。非常感谢Oleg提供了查找公共文件夹的代码,这是最困难的部分。我使用了这里的示例 http://msexchangeteam.com/archive/2009/04/21/451126.aspx 来使用更简单的FindAppointments方法修改了代码。
这个简单的示例返回一个HTML字符串,其中包含约会信息,但您可以将其用作自定义的基础。您可以在他下面的答案下看到我们的来回交流。
using System;
using Microsoft.Exchange.WebServices.Data;
using System.Net;

namespace ExchangePublicFolders
{
    public class Program
    {
        public static FolderId FindPublicFolder(ExchangeService myService, FolderId baseFolderId,
        string folderName)
        {

        FolderView folderView = new FolderView(10, 0);
        folderView.OffsetBasePoint = OffsetBasePoint.Beginning;
        folderView.PropertySet = new PropertySet(FolderSchema.DisplayName, FolderSchema.Id);

        FindFoldersResults folderResults;
        do
        {
            folderResults = myService.FindFolders(baseFolderId, folderView);

            foreach (Folder folder in folderResults)
                if (String.Compare(folder.DisplayName, folderName, StringComparison.OrdinalIgnoreCase) == 0)
                    return folder.Id;

            if (folderResults.NextPageOffset.HasValue)
                folderView.Offset = folderResults.NextPageOffset.Value;
        }
        while (folderResults.MoreAvailable);

        return null;
    }

    public static string MyTest()
    {
        ExchangeService myService = new ExchangeService(ExchangeVersion.Exchange2007_SP1);

        myService.Credentials = new NetworkCredential("USERNAME", "PASSWORD", "DOMAIN");
        myService.Url = new Uri("https://MAILSERVER/ews/exchange.asmx");

        Folder myPublicFoldersRoot = Folder.Bind(myService, WellKnownFolderName.PublicFoldersRoot);
        string myPublicFolderPath = @"PUBLIC_FOLDER_CALENDAR_NAME";
        string[] folderPath = myPublicFolderPath.Split('\\');
        FolderId fId = myPublicFoldersRoot.Id;
        foreach (string subFolderName in folderPath)
        {
            fId = Program.FindPublicFolder(myService, fId, subFolderName);
            if (fId == null)
            {
                return string.Format("ERROR: Can't find public folder {0}", myPublicFolderPath);

            }
        }

        Folder folderFound = Folder.Bind(myService, fId);
        if (String.Compare(folderFound.FolderClass, "IPF.Appointment", StringComparison.Ordinal) != 0)
        {
            return string.Format("ERROR: Public folder {0} is not a Calendar", myPublicFolderPath);

        }

        CalendarFolder AK_Calendar = CalendarFolder.Bind(myService, fId, BasePropertySet.FirstClassProperties);

        FindItemsResults<Appointment> AK_appointments = AK_Calendar.FindAppointments(new CalendarView(DateTime.Now,DateTime.Now.AddDays(1)));


        string rString = string.Empty;


        foreach (Appointment AK_appoint in AK_appointments)
        {

            rString += string.Format("Subject: {0}<br />Date: {1}<br /><br />",  AK_appoint.Subject, AK_appoint.Start);    
        }

        return rString;
    }

    }
}

嗨!代码 FindItemsResults<Appointment> 看起来不错。我觉得你忘记为 CalendarView 设置 PropertySet 了,这很重要。如果你只使用搜索结果中的 SubjectStart 属性,那么你应该设置这两个属性 ItemSchema.SubjectAppointmentSchema.Start。如果你不这样做,所有的 PropertySet.FirstClassProperties 都会从 Exchange 服务器发送过来。我在文件夹绑定中包含了这个属性集,只是为了展示有多少个属性。此外,我建议你继续使用分页。 - Oleg
顺便提一下,使用ItemViewFindItemsResults<Item>的原因是我修改了另一个枚举电子邮件的示例。在邮箱文件夹中,您不仅会找到电子邮件,还会找到符合约会要求的内容。因此,为了枚举邮箱,最好使用基础类Item。似乎在日历文件夹中您不会遇到这个问题,但请考虑这个可能的问题。祝一切顺利! - Oleg
@Oleg 我打算保留分页。我去掉了 PropertySet,因为我想尝试一下所有的属性。一旦我决定要使用哪些属性,我会把它加回来的。现在我只会使用日历,但如果我需要访问电子邮件邮箱,我会使用 Itemview。 - NinjaBomb
好的!我希望确保你正确理解代码不同更改的优缺点。 - Oleg
迟来的+1。我认为这让我朝着正确的方向前进了(在弄清楚我现有的设置需要.Net 4.0 FULL而不是客户端才能正确编译之后)。 - WernerCD
2个回答

15

像承诺的那样,这里有一个代码示例。我使用了Microsoft Exchange Web Services (EWS) Managed API 1.0,并建议您也这样做。我在代码中包含了大部分注释。

using System;
using Microsoft.Exchange.WebServices.Data;
using System.Net;

namespace ExchangePublicFolders {
    class Program {
        static FolderId FindPublicFolder (ExchangeService myService, FolderId baseFolderId,
            string folderName) {

            // We will search using paging. We will use page size 10
            FolderView folderView = new FolderView (10,0);
            folderView.OffsetBasePoint = OffsetBasePoint.Beginning;
            // we will need only DisplayName and Id of every folder
            // se we'll reduce the property set to the properties
            folderView.PropertySet = new PropertySet (FolderSchema.DisplayName,
                FolderSchema.Id);

            FindFoldersResults folderResults;
            do {
                folderResults = myService.FindFolders (baseFolderId, folderView);

                foreach (Folder folder in folderResults)
                    if (String.Compare (folder.DisplayName, folderName, StringComparison.OrdinalIgnoreCase) == 0)
                        return folder.Id;

                if (folderResults.NextPageOffset.HasValue)
                    // go to the next page
                    folderView.Offset = folderResults.NextPageOffset.Value;
            }
            while (folderResults.MoreAvailable);

            return null;
        }

        static void MyTest () {
            // IMPORTANT: ExchangeService is NOT thread safe, so one should create an instance of
            // ExchangeService whenever one needs it.
            ExchangeService myService = new ExchangeService (ExchangeVersion.Exchange2007_SP1);

            myService.Credentials = new NetworkCredential ("MyUser@corp.local", "myPassword00");
            myService.Url = new Uri ("http://mailwebsvc-t.services.local/ews/exchange.asmx");
            // next line is very practical during development phase or for debugging
            myService.TraceEnabled = true;

            Folder myPublicFoldersRoot = Folder.Bind (myService, WellKnownFolderName.PublicFoldersRoot);
            string myPublicFolderPath = @"OK soft GmbH (DE)\Gruppenpostfächer\_Template - Gruppenpostfach\_Template - Kalender";
            string[] folderPath = myPublicFolderPath.Split('\\');
            FolderId fId = myPublicFoldersRoot.Id;
            foreach (string subFolderName in folderPath) {
                fId = FindPublicFolder (myService, fId, subFolderName);
                if (fId == null) {
                    Console.WriteLine ("ERROR: Can't find public folder {0}", myPublicFolderPath);
                    return;
                }
            }

            // verify that we found 
            Folder folderFound = Folder.Bind (myService, fId);
            if (String.Compare (folderFound.FolderClass, "IPF.Appointment", StringComparison.Ordinal) != 0) {
                Console.WriteLine ("ERROR: Public folder {0} is not a Calendar", myPublicFolderPath);
                return;
            }

            CalendarFolder myPublicFolder = CalendarFolder.Bind (myService,
                //WellKnownFolderName.Calendar,
                fId,
                PropertySet.FirstClassProperties);

            if (myPublicFolder.TotalCount == 0) {
                Console.WriteLine ("Warning: Public folder {0} has no appointment. We try to create one.", myPublicFolderPath);

                Appointment app = new Appointment (myService);
                app.Subject = "Writing a code example";
                app.Start = new DateTime (2010, 9, 9);
                app.End = new DateTime (2010, 9, 10);
                app.RequiredAttendees.Add ("oleg.kiriljuk@ok-soft-gmbh.com");
                app.Culture = "de-DE";
                app.Save (myPublicFolder.Id, SendInvitationsMode.SendToNone);
            }

            // We will search using paging. We will use page size 10
            ItemView viewCalendar = new ItemView (10);
            // we can include all properties which we need in the view
            // If we comment the next line then ALL properties will be
            // read from the server. We can see there in the debug output
            viewCalendar.PropertySet = new PropertySet (ItemSchema.Subject);
            viewCalendar.Offset = 0;
            viewCalendar.OffsetBasePoint = OffsetBasePoint.Beginning;
            viewCalendar.OrderBy.Add (ContactSchema.DateTimeCreated, SortDirection.Descending);

            FindItemsResults<Item> findResultsCalendar;
            do {
                findResultsCalendar = myPublicFolder.FindItems (viewCalendar);

                foreach (Item item in findResultsCalendar) {
                    if (item is Appointment) {
                        Appointment appoint = item as Appointment;
                        Console.WriteLine ("Subject: \"{0}\"", appoint.Subject);
                    }
                }

                if (findResultsCalendar.NextPageOffset.HasValue)
                    // go to the next page
                    viewCalendar.Offset = findResultsCalendar.NextPageOffset.Value;
            }
            while (findResultsCalendar.MoreAvailable);
        }
        static void Main (string[] args) {
            MyTest();
        }
    }
}

您需要更新字符串myPublicFolderPath的值为您的公共日历文件夹。我设置了myService.TraceEnabled = true,它会生成包含调试信息的长输出。您当然应该在生产环境中删除此行。 更新:您可以在创建新的Exchange OWA日历系统支持中找到一些额外的链接。如果您还没有看过视频并且想要使用Exchange Web Services,我建议您观看这些视频。它可以节省您将来的时间。

谢谢!我在问题中遗漏了一个非常重要的信息。我的项目被卡在.NET 2.0上。对此我感到很抱歉。由于这是我的错,而且您提供的解决方案非常全面,花费了一些时间来准备,所以我会给您发放奖励。由于我无法将dll添加到项目中而不进行升级,您会怎么做?我的第一个想法是拥有一个完全独立的网站,为其提供Web服务以获取日历数据,但这听起来有点过度设计。您认为呢? - NinjaBomb
@NinjaBomb:如果您查看生成我的测试程序的调试输出(myService.TraceEnabled = true),您可以看到EWS Managed API 1.0发送与WebProxy发送的相同请求。例如,请参阅http://msdn.microsoft.com/en-us/library/aa493892(v=EXCHG.140).aspx。您当然不能为公共文件夹使用`FolderQueryTraversalType.Deep`,但是您可以轻松修改代码,使其执行与我的程序中的`FindPublicFolder`相同的操作。 - Oleg
@NinjaBomb:通过查看http://msdn.microsoft.com/en-us/library/bb402172(v=EXCHG.140).aspx和http://msdn.microsoft.com/en-us/library/aa563918(v=EXCHG.140).aspx等网页,您可以在EWS代理中找到相应的代码示例(请参见http://msdn.microsoft.com/en-us/library/exchangewebservices(v=EXCHG.140).aspx),并将其实现到您的程序中。同时,还可以查看Exchange Web Services操作(网址:http://msdn.microsoft.com/en-us/library/bb409286(v=EXCHG.140).aspx)。 - Oleg
@Oleg 我已经成功将一个 .net 3.5 项目添加到我的解决方案中。我可以将其引用添加到我的 .net 2.0 网站中。由于服务器上安装了这两个框架,所以一切看起来都应该能够正常工作。你现在使用的公共文件夹路径是什么?虽然我已经尝试了所有可能的路径变化,但我还是会遇到“找不到公共文件夹”的错误。我正在使用管理员帐户。 - NinjaBomb
@Oleg 明白了!只需要使用日历名称即可!现在我会稍微修改一下,只返回当前日期。如果可以的话,我会在答案中发布您代码的自定义版本。再次感谢! - NinjaBomb
@NinjaBomb:非常好的消息!使用.NET 3.5,一切都会变得简单。在我的示例中,有一个名为Calender的公共文件夹,路径为@"OK soft GmbH (DE)\Gruppenpostfächer\_Template - Gruppenpostfach\_Template - Kalender"(请参见变量myPublicFolderPath)。那么日历文件夹的路径是什么?我强烈建议您首先在您的环境中使我的控制台程序正常工作。FindPublicFolder中显示的调试信息将对您有所帮助。如果您遇到问题,请向我发布调试输出和您的环境中完整的日历公共文件夹路径。 - Oleg

1

谢谢提供的链接。我看到了geekswithblogs.net上关于在每次搜索中搜索公共文件夹的帖子。除非我完全错过了,否则我没有看到他实际从公共文件夹日历中提取任何日历约会。我将尝试WebDAV方法,但我还没有找到任何Exchange 2007日历使用它的示例或者它是否可行。 - NinjaBomb
WebDAV已经过时,不包括在Exchange 2010中(请参见http://msdn.microsoft.com/en-us/library/dd877032.aspx)。可以使用Exchange Web Services访问Exchange 2007公共文件夹。如果我有时间,稍后会发布一个示例。 - Oleg
Oleg,您发布了一个从公共文件夹日历中提取约会的好例子,这在我的端上运行良好,您将获得悬赏!我已经查看了微软为Exchange Web Services提供的每个示例,但它们都没有涉及公共文件夹。我会尽快测试您发布的内容。 - NinjaBomb
@NinjaBomb:我已经发布了我承诺的代码。如果您有任何问题,我可以对不同的步骤进行评论。我编写这段代码主要是为了演示。还有一点需要注意的是,请在我的名字前面加上@符号,如果您要给我写评论(请参见http://meta.stackexchange.com/questions/43019/how-do-comment-replies-work)。 - Oleg
1
尝试让Exchange 2007与WebDAV配合工作,结果让我把桌子上的东西弄坏了。 - NinjaBomb

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接