Two-way STSSync Protocol Server for Outlook 2007
So I've finally done it. I made the first stab at a two-way stssync protocol server. You can get it here:
https://www.codeplex.com/stssyncprovider
I'd love to have some of you start using it and report issues you find with it so I can continue to improve it. I know there are issues already, but it should be mostly there to at least get you started.
This was largely inspired by Stephen Toub's original Custom Calendar Provider sample. I'd recommend reading that article for background on this idea. I later updated it to work with Outlook 2007, but this was still just using the ver=1.0 sync (one-way) . Now, with the release of the SharePoint protocol documents which I announced some months back and troubleshooting logging in Outlook, I've been able to construct a stssync protocol server, which, like Toub's sample, allows for you to simply create a provider that snaps into the engine.
My original design goal was to make it so that provider developers would only have to implement a single interface which returned a dataset of records when Outlook requests them and accepted a dataset of updates when Outlook makes changes and the rest of the plumbing would be handled by the provider engine ("the engine"). I came pretty close. Here is the interface as it stands so far:
public interface IProvider
{
DataSet GetEmptyDataSet(Guid ProviderID);
DataRow GetSingleRow(Guid ProviderID, int id);
DataRow Update(Guid ProviderID, DataRow updateRow);
DataSet GetUpdatesSinceToken(Guid ProviderID, ChangeKey changeKey);
ListType GetProviderType(Guid ProviderID);
StringDictionary GetFieldMappingsForListType(Guid ProviderID, ListType listType);
}
So here's the explanation for the interface.
- GetEmptyDataSet: this is mostly used so that the engine can get a feel for what columns are in the table you're returning. You simply return an empty typed dataset or a standard dataset with a single (empty) table that contains the same columns you would later return filled.
- GetSingleRow: Pretty easy - I give you an ID, you give me back the row associated with it.
- Update: I give you the datarow with changes made and you pass me back the updated row (some columns may change as a result of doing the update - like Modified time, for example)
- GetUpdatesSinceToken: I give you a changekey from which you can extract a timestamp, or just call ToString to use the changekey as a watermark for getting changes since the last sync. You return your DataSet filled with any rows that have changed since the provided watermark (changeKey).
- GetProviderType: You return a value from the ListType enumeration. This tells the engine whether you're a contacts provider versus a calendar provider, task provider, discussion list provider, or document library provider (no doc library or attachment support is currently in the provider - it's tbi).
- GetFieldMappingsForListType - In this method you simply return a StringDictionary filled with any field name mappings the engine can use to map the columns in your dataset to column names it's familiar with. For example, you might do something like oSDict.Add("myContactIDField","ID");
Each method is passed the ProviderID specified in the web.config for this provider. If you want to implement two different providers in the same class, you can distinguish between them using this ID, otherwise, your implementation can largely ignore this parameter - it is mostly used by the engine to get a reference to your interface.
Once you've implemented the interface, just go to the web.config file for the engine and add a new Provider element. Here's a sample:
<ProviderProxy>
<Providers>
<Provider Name="AdventureWorks Contacts" ID="{7765B84F-6D32-4d31-B28E-6BC615D2F187}" Type="AdventureWorksProvider.Contacts" Assembly="AdventureWorksProvider" />
</Providers>
</ProviderProxy>
- Name = just the display name you want to appear on the list in Outlook and on default.aspx
- ID = this contains a Guid that is used in invoking methods on your IProvider interface.
- Type = the Namespace.ClassName of the type of your IProvider interface.
- Assembly = the qualified assembly name of the the assembly that contains the Type specified.
Copy your assembly to the bin directory of the engine web site and the engine will automatically find your assembly. I modified the Pre- and Post-Build events on my AdventureWorks provider sample to automatically do this to simplify the debugging process.
This is just a very rough draft. I wanted to have a sample up there that folks could start to work with and get some ideas. I'd love to hear your feedback and for you to help mold the project into something that is good over time. Please contact me with any issues you find or any suggestions (I know of some myself, already). I'll do my best to put out regular releases as the code base improves. I'd also love to hear what you do with it and what unique stores you start syncing with Outlook.
Just as a note, in this initial release, I do not support one-way sync, just two-way (seems backwards, but the goal was to get a two way sync working).
Also, I may need to change the IProvider interface definition from one release to the next as bugs/feature-completeness dictate probably in the early phases. It doesn't make sense to have an interface that's incomplete. Once I'm able to declare feature-completeness on the IProvider interface, any new enhancements or additional functionality will come in the form of additional/augmented interfaces (IProvider2, etc).
Comments
Anonymous
September 05, 2008
Sounds really cool.. Seems strange there is so little interest in it! I'm gonna try some things with it.. Thanks for the effort..Anonymous
September 05, 2008
Thanks. I already know one (major) bug with it and it has to do with not updating the versionhistory when changes are made on the database side. I hope to be able to fix it sometime soon.Anonymous
September 05, 2008
The comment has been removedAnonymous
September 07, 2008
small bug: the listtype Events should be renamed to Calendar..Anonymous
September 08, 2008
Arnoud, Thanks for your participation with this. If you'd like, you can enter any issues you find on the codeplex issue tracker tab for the project. If you are logged in, you should be able to do so. Let me know if you cannot. In response to your questions:
- Yes, integrated Windows authentication is used - there's no support for forms based authentication.
- You could theoretically just provide a different List ID in your stssync: link to each person and map that to a user. Or you could just get the user context from the Request object in the web service method and use that to get different data based on user.
- Yes, look in the protocol documents (http://msdn.microsoft.com/en-us/library/cc313169.aspx)
- I believe if you include a URL in the title or description of a task item, MSO will convert it to a hyperlink. Is this what you're referring to?
Anonymous
September 11, 2008
Hi Patrick. Thanks so much for this. It is a great hidden feature. It is a pity MS are not advertising that fact. After a lot of time tinkering I came across the bug you mention above about items not updating in outlook when you change them on the database side. You say this is because you are not updating the version history. You seem to know the solution please can you explain more so I can fix it myself.Anonymous
September 12, 2008
Yeah, you can look on the codeplex issue tracker tab for some more details on the bug. Basically, Outlook looks for your provider to be updating the vti_versionhistory property when changes are made on the provider side. (Likewise, Outlook also updates the property.) The vti_versionhistory is basically a list of guids and ints. Each "client" (Outlook being one and your provider being another) map to one of the Guid values. When a change is made by your "client", that client should set the integer value corresponding to his guid to the highest value in the list. The implementation of this is complicated. I'm thinking that the next version of my sync provider will actually abstract out all the metainfo and versionhistory logic and keep a separate "sync database" for managing this type of information. For more information on how the vti_versionhistory should work, look in the protocol documents I link to above - specifically [MS-OUTSPS].pdf. Additionally, the owshiddenversion property needs to be incremented each time there is an update on the server side - this is simpler logic to implement, however, since Outlook will not change this value.Anonymous
September 23, 2008
Thanks for your help. I managed to get the version history working and it is updating fine now. However for some reason delete never seems to be triggered. Deleting an item in outlook never seems to call the UpdateListItems ws method in your sample and mine. It works fine using a linked list from sharepoint. Am I missing something???Anonymous
September 24, 2008
If you find an issue, please create a bug (or "issue" or whatever) on codeplex.com so i can look at it.Anonymous
December 10, 2008
Great work! now, regarding the one-way sync - what is the issue? I.e. what is needed to enable it? I am trying to figure out how to merge Stephen's and your code for our own purpose (i.e. creating multiple dedicated contact lists). So far it works, but removal of items on the server does not result in removal of items in Outlook,Anonymous
February 04, 2009
I've tried to implement a provider for calendars in this project but run into some problems... First off it seems that the ListType was set to "Meetings", but the correct value should be "Calendar". Can you confirm this? Also, my Outlook 2007 seems to call the GetListItemChangesSinceToken method with null as rowLimit when I use the calendar ListType, which returns empty records. Anyone else run into this?Anonymous
February 23, 2009
while running your sample i am getting following error- Could not load file or assembly 'Microsoft.VisualStudio.QualityTools.HostAdapters.ASPNETAdapter, Version=9.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' or one of its dependencies. The system cannot find the file specified. but the assembly Microsoft.VisualStudio.QualityTools.HostAdapters.ASPNETAdapter exists in my system on following path- C:Program FilesMicrosoft Visual Studio 9.0Common7IDEPrivateAssemblies Could you help me in this regards?Anonymous
February 23, 2009
is it compatible with .net 2.0?Anonymous
February 24, 2009
Samir, That is most likely an artifact of having built with Visual Studio 2008. I don't know why it wouldn't be backwards compatible with 2.0, but I haven't tried.Anonymous
February 24, 2009
djones, You can file a bug on http://www.codeplex.com/stssyncprovider/WorkItem/List.aspx if you find something wrong with the provider and I'll evaluate it.Anonymous
March 09, 2009
Would this code work for Outlook 2003 as well? If not, can you guide me how can i achieve this.Anonymous
March 16, 2009
Nope - 2 way is only for Outlook 2007.Anonymous
September 25, 2009
Thank you, this thing works! Sucessfully got tasks in outlook from datatable. However, if I understand correctly, Outlook Body field requires RTF to do the formatting. Can this be switched to html somehow?Anonymous
October 11, 2009
Problem with Implementing Calendar Hello. I m trying creating calendar and I have issue when I try to synch outlook with database. Outlook get to 79 present and then stack up like it goes to endless loop . Do you have any idea What can be wrong outlook doesn’t shows any error it just keep working . Thank youAnonymous
October 20, 2009
Deng, You should enable troubleshooting logging and examine the SOAP requests and responses Outlook is making. Your provider may be indicating there are more changes coming so Outlook is continuously requesting the next batch. (just a guess)Anonymous
October 27, 2009
Thanks Patrick. Now i can t figure out why when i call GetListItemChangesSinceToken() and add record into my Database i don t see it in outlook calendar but if i call GetListItemChanges() i do see changes in outlook calendar but i can't add or delete any records.Anonymous
November 23, 2009
confused what the two-way means. is that the code can transfer/sync items/events from db to outlook and from outlook to db? it seems to me that it only transfer/sync items/events from db to outlook. If I want to transfer outlook events to db, how could I do that? Thanks very much for your time. Haiyao