Custom Calendar Providers for Outlook 2007
So if you were using Stephen Toub's Custom Calendar Provider sample for Outlook 2003, and you are testing out the awesome new Outlook 2007 features, you may have noticed that the code sample he provided doesn't work any more. Stephen is planning to update the article for 2007 eventually, but until then, here's the fix.
The GetList web service method is supposed to return a <Fields> node filled with <Field> nodes that describe each field in that list, including type information, display names, read-only properties, etc. In the sample above, Stephen is not returning the <Fields> node as a part of the response. The reason is that Outlook 2003 ignored this information when it was performing the sync. Outlook 2007 is a little more intelligent in this regard and it does need this information returned. It's not a big deal when linking to a SharePoint Events list, because SharePoint always returns that data. In an effort to keep the code simple, Stephen ignored it.
So the answer is that we need to add that information back in. So what you'll want to do is edit the WssListResponse.cs file as follows:
Add a new private static method called AddFieldElement as follows:
/// <summary>
/// Adds a Field node to the Fields parent node
/// </summary>
/// <param name="parentDoc">XmlDocument parent doc used for creating the new element</param>
/// <param name="parentNode">The parent Fields node to which to add the new Field element</param>
/// <param name="type">Type attribute</param>
/// <param name="colName">ColName attribute</param>
/// <param name="name">Name attribute</param>
/// <param name="displayName">DisplayName attribute</param>
/// <param name="readOnly">ReadOnly attribute</param>
/// <param name="hidden">Hidden attribute</param>
/// <param name="fromBaseType">FromBaseType attribute</param>
/// <returns></returns>
private static XmlElement AddFieldElement(XmlDocument parentDoc,XmlNode parentNode,string type, string colName, string name, string displayName,bool readOnly, bool hidden, bool fromBaseType)
{
XmlElement fieldEl = parentDoc.CreateElement(null,"Field",_wssns);
parentNode.AppendChild(fieldEl);if(type != null) AddAttribute(fieldEl,null,"Type",null,type);
if(colName != null) AddAttribute(fieldEl,null,"ColName",null,colName);
if(name != null) AddAttribute(fieldEl,null,"Name",null,name);
if(displayName != null) AddAttribute(fieldEl,null,"DisplayName",null,displayName);
if(readOnly) AddAttribute(fieldEl,null,"ReadOnly",null,"TRUE");
if(hidden) AddAttribute(fieldEl,null,"Hidden",null,"TRUE");
if(fromBaseType) AddAttribute(fieldEl,null,"FromBaseType",null,"TRUE");return fieldEl;
}Modify the GetListDescription Method as follows:
public static XmlNode GetListDescription(string title, Guid id)
{
// Create the response document
XmlDocument doc = new XmlDocument();
// Add a List element
XmlNode list = doc.CreateElement(null, "List", _wssns);
doc.AppendChild(list);
// Add attributes about the list to the List element
AddAttribute(list, null, "DocTemplateUrl", null, "");
AddAttribute(list, null, "DefaultViewUrl", null, "/Lists/" + id.ToString("N") + "/AllItems.aspx");
AddAttribute(list, null, "ID", null, id.ToString("B"));
AddAttribute(list, null, "Title", null, title);
AddAttribute(list, null, "Description", null, title);
AddAttribute(list, null, "ImageUrl", null, "/_layouts/images/itevent.gif");
AddAttribute(list, null, "Name", null, id.ToString("N"));
AddAttribute(list, null, "BaseType", null, "0");
AddAttribute(list, null, "ServerTemplate", null, "106");
AddAttribute(list, null, "Created", null, DateTime.MinValue.AddDays(2).ToString("s").Replace("T", " "));
AddAttribute(list, null, "Modified", null, DateTime.MinValue.AddDays(3).ToString("s").Replace("T", " "));
AddAttribute(list, null, "LastDeleted", null, DateTime.MinValue.AddDays(3).ToString("s").Replace("T", " "));
AddAttribute(list, null, "Version", null, "0");
AddAttribute(list, null, "Direction", null, "none");
AddAttribute(list, null, "ThumbnailSize", null, "");
AddAttribute(list, null, "WebImageWidth", null, "");
AddAttribute(list, null, "WebImageHeight", null, "");
AddAttribute(list, null, "Flags", null, "4096");
AddAttribute(list, null, "ItemCount", null, "1"); // isn't used, so no point in recomputing size of list
AddAttribute(list, null, "AnonymousPermMask", null, "");
AddAttribute(list, null, "RootFolder", null, "/Lists/" + id.ToString("N"));
AddAttribute(list, null, "ReadSecurity", null, "1");
AddAttribute(list, null, "WriteSecurity", null, "1");
AddAttribute(list, null, "Author", null, "1");
AddAttribute(list, null, "AnonymousPermMask", null, "");
AddAttribute(list, null, "EventSinkAssembly", null, "");
AddAttribute(list, null, "EventSinkClass", null, "");
AddAttribute(list, null, "EventSinkData", null, "");
AddAttribute(list, null, "EmailInsertsFolder", null, "");
AddAttribute(list, null, "AllowDeletion", null, "TRUE");
AddAttribute(list, null, "AllowMultiResponses", null, "FALSE");
AddAttribute(list, null, "EnableAttachments", null, "TRUE");
AddAttribute(list, null, "EnableModeration", null, "FALSE");
AddAttribute(list, null, "EnableVersioning", null, "FALSE");
AddAttribute(list, null, "Hidden", null, "FALSE");
AddAttribute(list, null, "MultipleDataList", null, "FALSE");
AddAttribute(list, null, "Ordered", null, "FALSE");
AddAttribute(list, null, "ShowUser", null, "TRUE");
//Create Fields node
XmlElement fieldsNode = doc.CreateElement(null, "Fields", _wssns);
list.AppendChild(fieldsNode);
XmlElement tmpEl;
//Append Fields
tmpEl = AddFieldElement(doc,fieldsNode,"Counter","tp_ID","ID","ID",true,false,true);
tmpEl = AddFieldElement(doc,fieldsNode,"Text","nvarchar1","Title","Title",false,false,true);
tmpEl = AddFieldElement(doc,fieldsNode,"DateTime","tp_Modified","Modified","Modified",true,false,true);
AddAttribute(tmpEl,null,"StorageTZ",null,"TRUE");
tmpEl = AddFieldElement(doc,fieldsNode,"DateTime","tp_Created","Created","Created",true,false,true);
AddAttribute(tmpEl,null,"StorageTZ",null,"TRUE");
tmpEl = AddFieldElement(doc,fieldsNode,"User","tp_Author","Author","Created By",true,false,true);
AddAttribute(tmpEl,null,"List",null,"UserInfo");
tmpEl = AddFieldElement(doc,fieldsNode,"User","tp_Editor","Editor","Modified By",true,false,true);
AddAttribute(tmpEl,null,"List",null,"UserInfo");
tmpEl = AddFieldElement(doc,fieldsNode,"Integer","tp_Version","owshiddenversion","owshiddenversion",true,true,true);
AddAttribute(tmpEl,null,"SetAs",null,"owshiddenversion");
// tmpEl = AddFieldElement(doc,fieldsNode,"Attachments","tp_HasAttachment","Attachments","Attachments",true,false,true);
// tmpEl = AddFieldElement(doc,fieldsNode,"ModStat","tp_ModerationStatus","_ModerationStatus","Approval Status",true,true,true);
// AddAttribute(tmpEl,null,"CanToggleHidden",null,"TRUE");
// AddAttribute(tmpEl,null,"Required",null,"FALSE");
// tmpEl = AddFieldElement(doc,fieldsNode,"Note","ntext1","_ModerationComments","Approver Comments",true,true,true);
// AddAttribute(tmpEl,null,"CanToggleHidden",null,"TRUE");
// AddAttribute(tmpEl,null,"Sortable",null,"FALSE");
// tmpEl = AddFieldElement(doc,fieldsNode,"Computed",null,"Edit","Edit",true,false,true);
// AddAttribute(tmpEl,null,"Sortable",null,"FALSE");
// AddAttribute(tmpEl,null,"Filterable",null,"FALSE");
// AddAttribute(tmpEl,null,"AuthoringInfo",null,"(link to edit item)");
// tmpEl = AddFieldElement(doc,fieldsNode,"Computed",null,"LinkTitleNoMenu","Title",true,false,true);
// AddAttribute(tmpEl,null,"AuthoringInfo",null,"(linked to item)");
// AddAttribute(tmpEl,null,"Dir",null,"");
// AddAttribute(tmpEl,null,"DisplayNameSrcField",null,"Title");
// tmpEl = AddFieldElement(doc,fieldsNode,"Computed",null,"LinkTitle","Title",true,false,true);
// AddAttribute(tmpEl,null,"AuthoringInfo",null,"(linked to item with edit menu)");
// AddAttribute(tmpEl,null,"DisplayNameSrcField",null,"Title");
// AddAttribute(tmpEl,null,"ClassInfo",null,"Menu");
// tmpEl = AddFieldElement(doc,fieldsNode,"Computed",null,"SelectTitle","Select",true,true,true);
// AddAttribute(tmpEl,null,"AuthoringInfo",null,"(web part connection)");
// AddAttribute(tmpEl,null,"Dir",null,"");
// AddAttribute(tmpEl,null,"Sortable",null,"FALSE");
// AddAttribute(tmpEl,null,"CanToggleHidden",null,"TRUE");
// tmpEl = AddFieldElement(doc,fieldsNode,"Integer","tp_InstanceID","InstanceID","InstanceID",true,true,true);
// AddAttribute(tmpEl,null,"Sortable",null,"TRUE");
// AddAttribute(tmpEl,null,"Filterable",null,"TRUE");
// AddAttribute(tmpEl,null,"Min",null,"0");
// AddAttribute(tmpEl,null,"Max",null,"99991231");
// tmpEl = AddFieldElement(doc,fieldsNode,"Number","tp_ItemOrder","Order","Order",false,true,true);
tmpEl = AddFieldElement(doc,fieldsNode,"Guid","tp_Guid","GUID","GUID",true,true,true);
tmpEl = AddFieldElement(doc,fieldsNode,"DateTime","datetime1","EventDate","Begin",false,false,true);
AddAttribute(tmpEl,null,"Format",null,"DateTime");
AddAttribute(tmpEl,null,"Sealed",null,"TRUE");
AddAttribute(tmpEl,null,"Required",null,"TRUE");
AddAttribute(tmpEl,null,"Filterable",null,"FALSE");
AddAttribute(tmpEl,null,"FilterableNoRecurrence",null,"TRUE");
tmpEl = AddFieldElement(doc,fieldsNode,"DateTime","datetime2","EndDate","End",false,false,true);
AddAttribute(tmpEl,null,"Format",null,"DateTime");
AddAttribute(tmpEl,null,"Sealed",null,"TRUE");
AddAttribute(tmpEl,null,"Filterable",null,"FALSE");
AddAttribute(tmpEl,null,"FilterableNoRecurrence",null,"TRUE");
tmpEl = AddFieldElement(doc,fieldsNode,"Note","ntext2","Description","Description",false,false,false);
AddAttribute(tmpEl,null,"Sealed",null,"TRUE");
AddAttribute(tmpEl,null,"Sortable",null,"False");
tmpEl = AddFieldElement(doc,fieldsNode,"Text","nvarchar","Location","Location",false,false,false);
AddAttribute(tmpEl,null,"Sealed",null,"TRUE");
tmpEl = AddFieldElement(doc,fieldsNode,"Recurrence","bit1","fRecurrence","Recurrence",false,false,false);
AddAttribute(tmpEl,null,"DisplayImage",null,"recur.gif");
AddAttribute(tmpEl,null,"HeaderImage",null,"recur.gif");
AddAttribute(tmpEl,null,"ClassInfo",null,"Icon");
AddAttribute(tmpEl,null,"Sealed",null,"TRUE");
AddAttribute(tmpEl,null,"NoEditFormBreak",null,"TRUE");
// tmpEl = AddFieldElement(doc,fieldsNode,"CrossProjectLink","bit2","WorkspaceLink","Workspace",false,false,false);
// AddAttribute(tmpEl,null,"Format",null,"EventList");
// AddAttribute(tmpEl,null,"DisplayImage",null,"mtgicon.gif");
// AddAttribute(tmpEl,null,"HeaderImage",null,"mtgicnhd.gif");
// AddAttribute(tmpEl,null,"ClassInfo",null,"Icon");
// AddAttribute(tmpEl,null,"Title",null,"Meeting Workspace");
// AddAttribute(tmpEl,null,"Filterable",null,"TRUE");
// AddAttribute(tmpEl,null,"Sealed",null,"TRUE");
tmpEl = AddFieldElement(doc,fieldsNode,"Integer","int1","EventType","Event Type",false,true,false);
AddAttribute(tmpEl,null,"Sealed",null,"TRUE");
tmpEl = AddFieldElement(doc,fieldsNode,"Guid","uniqueidentifier1","UID","UID",false,true,false);
AddAttribute(tmpEl,null,"Sealed",null,"TRUE");
tmpEl = AddFieldElement(doc,fieldsNode,"DateTime","datetime3","RecurrenceID","Recurrence ID",false,true,false);
AddAttribute(tmpEl,null,"Sealed",null,"TRUE");
AddAttribute(tmpEl,null,"CalType",null,"1");
AddAttribute(tmpEl,null,"Format",null,"ISO8601Gregorian");
tmpEl = AddFieldElement(doc,fieldsNode,"Boolean","bit3","EventCanceled","Event Canceled",false,true,false);
AddAttribute(tmpEl,null,"Sealed",null,"TRUE");
tmpEl = AddFieldElement(doc,fieldsNode,"Integer","int2","Duration","Duration",false,true,false);
AddAttribute(tmpEl,null,"Sealed",null,"TRUE");
tmpEl = AddFieldElement(doc,fieldsNode,"Note","ntext3","RecurrenceData","RecurrenceData",false,true,false);
AddAttribute(tmpEl,null,"Sealed",null,"TRUE");
tmpEl = AddFieldElement(doc,fieldsNode,"Integer","int3","TimeZone","TimeZone",false,true,false);
AddAttribute(tmpEl,null,"Sealed",null,"TRUE");
tmpEl = AddFieldElement(doc,fieldsNode,"Note","ntext4","XMLTZone","XMLTZone",false,true,false);
AddAttribute(tmpEl,null,"Sealed",null,"TRUE");
// tmpEl = AddFieldElement(doc,fieldsNode,"Integer","int4","MasterSeriesItemID","MasterSeriesItemID",false,true,false);
// AddAttribute(tmpEl,null,"Sealed",null,"TRUE");
// tmpEl = AddFieldElement(doc,fieldsNode,"URL","nvarchar3","Workspace","WorkspaceUrl",false,true,false);
// AddAttribute(tmpEl,null,"Sealed",null,"TRUE");
// AddAttribute(tmpEl,null,"ColName2",null,"nvarchar4");
// Return the XML
return doc;
}
You'll notice that I have commented out a few lines in this method. There are probably more lines that can be commented out. We're not returning all the information that SharePoint would return, but it's not all useful to Outlook, so why clog the ethernet lines with useless data?!
Anyway, once you've made those modifications, you can recompile the web application and you should be golden!
Comments
Anonymous
November 21, 2006
I've received several emails from folks who have been successfully using my Outlook Calendar ProviderAnonymous
December 05, 2006
I made the changes listed, and it still works fine in Outlook 2003, but in Outlook 2007 I still get: Task 'SharePoint' reported error (0x8000FFFF) : 'An error occurred either in Outlook or Windows SharePoint Services. Contact the Windows SharePoint Services site administrator.' The wss log gives a fairly unhelpful error of: <?xml version="1.0" ?>
- <errors> <error>An error occurred either in Outlook or Windows SharePoint Services. Contact the Windows SharePoint Services site administrator.</error> </errors> I have emailed you a more verbose "bug report" and am hopeful you will be able to assist.
Anonymous
December 05, 2006
Nigel, That's the same error you get without the Fields node added at all. My guess is that Outlook is expecting your GetList response to be formatted slightly differently. I haven't received your email yet, so I'm not sure what your "bug report" contains. Are you implementing the Application event log sample? Does the View Events link work?Anonymous
December 05, 2006
Can you please use the contact form to send your "bug report"? I did not receive your email.Anonymous
December 08, 2006
It occurred to me that Outlook 2007 was launched in November, so there was likely a later version than my beta 2 build 4017. I am now running with build 4518 and am pleased to say that your changes work :o) Not only do they work, but I was able to apply them to Stephen Toub's OutlookContactProvider too, and am serving up my own custom data. I'm told Outlook 2007 supports writing to WSS lists, so how do you specify that the contacts/events folder is writable, which presumably would then show the outlook edit form, and result in calls to UpdateListItems(). At the moment if I try to make changes Outlook tells me "This SharePoint list is read-only in Outlook". Thanks for your help thus far :o)Anonymous
January 18, 2007
"The GetList web service method is supposed to return a <Fields> node filled with <Field> nodes" Where can you see the documentation about what is supposed to be returned ? I am trying to make the Contact list work in Outlook 2007 but fails (I do not get any error message, but simply never sees any data coming into outlook from my web service, which is based on the outlook 2003 example at http://blogs.msdn.com/toub/archive/2004/12/22/330125.aspx ). How can I see what kind of data that actually gets sent when you are using a real sharepoint contact list ? (it works without problem so if I can see the data being sent back and forth, then maybe I can see if I will need some "Fields" elements for the contacts also)Anonymous
January 19, 2007
What I did for the example above was that I called the GetList web service with javascript, and got the response back and examined it. I just used a standard Events list with no added columns from a standard Team site. You could probably do the same thing with a standard Contacts list.Anonymous
January 26, 2007
Following you example I can get it working with Office 2007 but I still have a problem: In Office 2003 I can see all contact info In Office 2007 I can see anly the FullName filed Can you help me? Many thanks. ClaudioAnonymous
January 26, 2007
Claudio, You realize the example above is for a custom Calendar provider, right? For a contacts list, obviously the Fields you would return would be different.Anonymous
January 31, 2007
I understand my troubles but how should I modify the GetListDescription method to work as Outlook Contact Provider ? Many thanksAnonymous
January 31, 2007
You will have to figure that out in a way that makes sense for what you're trying to do. You could extend that method to take an additional parameter that tells you what type of list you are asking for and then construct the XML data based on that, or you could just store the XML for the different types of lists separately in an XML file, load them up and return them. This was just a hard-coded sample to show you the method to use in taking advantage of the stssync capabilities of Outlook. It's up to you to run with it and test it out. If you're asking how to get the list data for a Contacts list, just call the GetList web service on a standard contacts list in SharePoint and observe the return value. SharePoint is going to return all the data about that list including field information and View information. At this point, it's not important to include the View information, because Outlook doesn't care about it. In fact, the reason that I created this post was that in Outlook 2003, it didn't even care about the Fields collection, it just assumed certain fields were present. Outlook 2007 got a little smarter about it and started paying attention to the Fields that GetList returns. That's why we needed to add that node into the response that our web service is returning to include the Fields node. Hope that helps.Anonymous
January 31, 2007
The comment has been removedAnonymous
January 31, 2007
Thanks, James, for taking the time to do the leg work on this one!Anonymous
September 07, 2007
The comment has been removedAnonymous
September 21, 2007
Thanks, all of you for your work, that helped a lot. I combined Calendar and Contacts into one ASP.NET 2.0 application and added the tweaks for OL 2007. Tested with 2003 went perfectly OK, test with 2007 is still a todo. I can post the code, if there is interest but I'd like to add a couple of aditional calendar fields. Has anyone found the definitions for the 'show as' (Free, Busy, ...) and Category (I want to set the colors)? Would be great, MichaelAnonymous
October 09, 2007
I would like to know if it's possible to made with post folders. I have an Sharepoint 2007 discussion list and I want to edit the default view to include more fields present in Sharepoint. Is it possible to this in same way as for contacts. ThanksAnonymous
October 17, 2007
Francois, I'm not sure what you're asking. Are you asking if it's possible to use stssync with a Discussion List to display the items in your Outlook contacts folder, or are you asking how to create a custom view in SharePoint? If the former, I suppose it's possible as long as you handle the mapping from the different fields into fields that Outlook would expect to see for a contact. If the latter, then I'd point you to http://msdn.microsoft.com/sharepoint. Look for the documentation on CAML. You can create and edit SharePoint views with CAML, but it is not easy at all. It may be easier to use a tool such as SharePoint Designer or FrontPage. I'm not sure if this answers your question though.Anonymous
October 19, 2007
Is there any way of putting the events in the main calendar? My impression is that this is needed if the consumer wants to sync with a mobile device.Anonymous
November 05, 2007
To the best of my knowledge, that is correct, Stefan. And to answer your question, there's not a way to directly have the stssync put appointments into the calendar. Outlook creates a wrapped PST specifically for SharePoint synchronization. The only way you'd be able to do it is if you then have code that synchronizes between your calendar and the SharePoint events calendar in the PST. This is not an easy undertaking. If you just need the one-way sync, you may be able to just wipe/replace from a new calendar folder in your mailbox. That would be easier code to write, but only allows for one-way sync, and any changes made by the end user would be erased at the next sync.Anonymous
December 27, 2007
Michael I didn't see any other person posting the request, so I will. Can you post your code for Calendar and Contacts into one ASP.NET 2.0? Thank you in advance, JamesAnonymous
April 08, 2008
Many of you have been using the Custom Calendar Provider samples created by Stephen Toub and I: StephenAnonymous
April 10, 2008
Hi, got the web service running fine now.. but I have problem getting the Category field working for the contact. AddAttribute(node, Nothing, "ows_Category", Nothing, "Business") AddAttribute(node, Nothing, "ows_Categories", Nothing, "Business") but both doesn't seems to work.. does anyone know what is the correct name for the Category ows_ field for sharepoint? I am running XP Office 2003 thanksAnonymous
April 10, 2008
Hi, got the web service running fine now.. but I have problem getting the Category field working for the contact. AddAttribute(node, Nothing, "ows_Category", Nothing, "Business") AddAttribute(node, Nothing, "ows_Categories", Nothing, "Business") but both doesn't seems to work.. does anyone know what is the correct name for the Category ows_ field for sharepoint? my os is XP with office 2003 thanksAnonymous
April 11, 2008
Hi Wilson, remember that there are a fixed set of fields that are synchronized. If the field is not part of the original schema for the Contacts list, then it may not be automatically synchronized. The list of fields increases with Outlook 2007, but it's still fixed. Check out my latest blog entry where we have published exactly how to build these types of services. There may be more detail in the specification. (I haven't had time to read it yet myself.) :) http://blogs.msdn.com/pcreehan/archive/2008/04/08/stssync-for-the-masses-toub-s-revenge.aspxAnonymous
June 10, 2008
The comment has been removedAnonymous
March 23, 2009
I want to implement the outlook calnedar in C#. Can you please tell me how can i do this. thanks