• Wiz/dumb

    Custom Calendar Providers for Outlook 2007

    • 26 Comments

    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:

    1. Add a new private static method called AddFieldElement as follows:
    2.   /// <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;
         
        }

    3. 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!

  • Wiz/dumb

    HOWTO: Get Email Messages from Exchange to SharePoint

    • 19 Comments

    Many people ask about how to do this, so I thought I'd write up a sample. This sample basically searches the inbox of a user and then does an HTTP GET against the DAV:href to get the EML file and then does an HTTP PUT to upload it to a SharePoint document library.


    using
    System;
    using
    System.Web;
    using
    System.Xml;
    using
    System.Net;
    using
    System.Text;
    namespace
    UploadEmlToSharePoint 

        /// <summary> 
        /// Summary description for Class1. 
        /// </summary> 
        class Class1 
        { 
            static System.Net.CredentialCache MyCredentialCache; 
            /// <summary> 
            /// The main entry point for the application. 
            /// </summary> 
            [STAThread] 
            static void Main(string[] args) 
            { 
                System.Net.HttpWebRequest Request; 
                System.Net.WebResponse Response; 
                string strRootURI = "http://exchangeserver/exchange/User1/Inbox/"; 
                string strSPSRootURI = "http://wss/Shared%20Documents/"; 
                string strUserName = "User1"; 
                string strPassword = "Passw0rd"; 
                string strDomain = "DOMAIN"; 
                string strQuery =""; 
                byte[] bytes = null
                System.IO.Stream RequestStream =
    null
                System.IO.Stream ResponseStream =
    null
                XmlDocument ResponseXmlDoc =
    null
                XmlNodeList ResponseNodes =
    null
                XmlNamespaceManager xmlnsm =
    new XmlNamespaceManager(new NameTable()); 
                xmlnsm.AddNamespace("a","DAV:"); 
                try 
                { 
                    // Build the SQL query. 
                    strQuery = "<?xml version=\"1.0\"?><D:searchrequest xmlns:D = \"DAV:\" >" 
                        + "<D:sql>SELECT \"DAV:displayname\" FROM \"" + strRootURI + "\"" 
                        + "WHERE \"DAV:ishidden\" = false AND \"DAV:isfolder\" = false" 
                        + "</D:sql></D:searchrequest>"; 
                    // Create a new CredentialCache object and fill it with the network 
                    // credentials required to access the server. 
                    MyCredentialCache =
    new System.Net.CredentialCache(); 
                        MyCredentialCache.Add(
    new System.Uri(strRootURI), 
                        "NTLM", 
                        new System.Net.NetworkCredential(strUserName, strPassword, strDomain) 
                    ); 
                    MyCredentialCache.Add(
    new System.Uri(strSPSRootURI), 
                        "NTLM", 
                        new System.Net.NetworkCredential(strUserName, strPassword, strDomain)); 
                    // Create the HttpWebRequest object. 
                    Request = (System.Net.HttpWebRequest)HttpWebRequest.Create(strRootURI); 
                    // Add the network credentials to the request. 
                    Request.Credentials = MyCredentialCache; 
                    // Specify the method. 
                    Request.Method = "SEARCH"; 
                    // Encode the body using UTF-8. 
                    bytes = Encoding.UTF8.GetBytes((
    string)strQuery); 
                    // Set the content header length. This must be 
                    // done before writing data to the request stream. 
                    Request.ContentLength = bytes.Length; 
                    //Set the translate header to false 
                    Request.Headers.Add("Translate","f"); 
                    // Get a reference to the request stream. 
                    RequestStream = Request.GetRequestStream(); 
                    // Write the SQL query to the request stream. 
                    RequestStream.Write(bytes, 0, bytes.Length); 
                    // Close the Stream object to release the connection 
                    // for further use. 
                    RequestStream.Close(); 
                    // Set the content type header. 
                    Request.ContentType = "text/xml"; 
                    // Send the SEARCH method request and get the 
                    // response from the server. 
                    Response = (HttpWebResponse)Request.GetResponse(); 
                    // Get the XML response stream. 
                    ResponseStream = Response.GetResponseStream(); 
                    // Create the XmlDocument object from the XML response stream. 
                    ResponseXmlDoc =
    new XmlDocument(); 
                    ResponseXmlDoc.Load(ResponseStream);


                    ResponseNodes = ResponseXmlDoc.GetElementsByTagName("a:response"); 
                    if(ResponseNodes.Count > 0) 
                    { 
                        Console.WriteLine("Non-folder item hrefs..."); 
                        // Loop through the display name nodes. 
                        for(int i=0; i<ResponseNodes.Count; i++) 
                        { 
                            // Display the non-folder item displayname. 
                            XmlNode responseNode = ResponseNodes[i]; 
                            XmlNode hrefNode = responseNode.SelectSingleNode("a:href",xmlnsm); 
                            XmlNode displayNameNode = responseNode.SelectSingleNode("a:propstat/a:prop/a:displayname",xmlnsm);


                            //Downloads the EML file from the specified URL 
                            byte[] emlFile = GetBytesFrom(hrefNode.InnerText);


                            //Uploads the EML file to the SharePoint document library 
                            UploadToSPS(emlFile,strSPSRootURI + System.Web.HttpUtility.UrlPathEncode(displayNameNode.InnerText)); 
                        } 
                    } 
                    else 
                    { 
                        Console.WriteLine("No non-folder items found..."); 
                    } 
                    // Clean up. 
                    ResponseStream.Close(); 
                    Response.Close(); 
                } 
                catch(Exception ex) 
                { 
                    // Catch any exceptions. Any error codes from the SEARCH 
                    // method request on the server will be caught here, also. 
                    Console.WriteLine(ex.Message); 
                } 
                Console.WriteLine("Done."); 
                Console.Read(); 
            } 
            static byte[] GetBytesFrom(string DavURL) 
            { 
                Console.WriteLine(DavURL); 
                byte[] buffer; 
                System.Net.HttpWebRequest Request; 
                System.Net.HttpWebResponse Response; 
                System.IO.Stream ResponseStream; 
                Request = (HttpWebRequest)HttpWebRequest.Create(DavURL); 
                Request.Credentials = MyCredentialCache; 
                Request.Headers.Add("Translate","f"); 
                Response = (HttpWebResponse)Request.GetResponse(); 
                ResponseStream = Response.GetResponseStream(); 
                buffer =
    new byte[Response.ContentLength]; 
                ResponseStream.Read(buffer,0,(
    int)Response.ContentLength); 
                ResponseStream.Close(); 
                Response.Close(); 
                return buffer; 
            } 
            static void UploadToSPS(byte[] fileBytes, string URL) 
            { 
                Console.WriteLine("Uploading " + fileBytes.Length.ToString() + " bytes to " + URL); 
                System.Net.HttpWebRequest Request; 
                Request = (HttpWebRequest)HttpWebRequest.Create(URL); 
                Request.Credentials = MyCredentialCache; 
                Request.ContentLength = fileBytes.Length; 
                Request.Method = "PUT"; 
                System.IO.Stream str = Request.GetRequestStream(); 
                str.Write(fileBytes,0,fileBytes.Length); 
                str.Close(); 
                Request.GetResponse(); 
            } 
        }
    }

  • Wiz/dumb

    Two-way STSSync Protocol Server for Outlook 2007

    • 22 Comments

    So I've finally done it. I made the first stab at a two-way stssync protocol server. You can get it here:

    http://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).

  • Wiz/dumb

    VSTO add-ins and the NewInspector Event

    • 28 Comments

    VSTO add-ins for Outlook are great. It's much easier than implementing the IDTExtensibility2 interface. Basically, the way it works is this (actually, Andrew Whitechapel does a much better job explaining than I will, so here you go): the AddinLoader.dll keeps a "dormant" list of add-ins that tried to load when the Outlook.exe process is started.

    If there's UI (if there are any Explorers or Inspectors visible), then it fires the _Startup event. If there is no UI (like when using the Outlook View Control or ActiveSync), then it doesn't fire _Startup until the first Inspector or Explorer is made visible. Once the last UI element is closed, then VSTO fires the _Shutdown event.

    A typical operation one would usually perform (and a reason for creating an add-in in the first place) is to add some sort of UI to an Inspector or Explorer, such as a button on a command bar or a menu item. In a traditional COM add-in, the way you would do this would be to hook in to the Inspectors.NewInspector event and in the handler for that event, you'd add your UI. And that works great. It works great in VSTO also, except the first time.

    Because VSTO itself is waiting until the NewInspector event fires (in scenarios where Outlook.exe is started with no UI) to fire the _Startup event on your VSTO add-in, it is too late to bind to the event yourself (it's already been fired). So what happens is that the first time an Inspector opens, your button is not added to the CommandBar, but the second time it is!

    The way to get around this problem is simple. Remember that the _Startup event is fired during the NewInspector event. So in your _Startup code, just check the Inspectors.Count property and if it's greater than 0, go ahead and add your button (or whatever)! In subsequent calls to the NewInspector, your code will fire.

    Here's an example:

    private Microsoft.Office.Interop.Outlook.Inspectors objInspectors;

    private void ThisApplication_Startup(object sender, System.EventArgs e)
    {
       objInspectors = this.Inspectors;
       objInspectors.NewInspector += new Microsoft.Office.Interop.Outlook.InspectorsEvents_NewInspectorEventHandler(objInspectors_NewInspector);
       if(objInspectors.Count >= 1)
          AddCustomUI();
    }

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

    }

    private void objInspectors_NewInspector(Microsoft.Office.Interop.Outlook.Inspector Inspector){
       AddCustomUI();
    }

    private void AddCustomUI(){
          //...do your add button thing or whatever here
    }

  • Wiz/dumb

    HOWTO: Exposing your VSTO 2005 SE Add-In to External Code

    • 2 Comments

    So with regular COM AddIns in Outlook, to expose your COM Add-In to external applications involved setting the Application.ComAddins.Item("ProgID").Object property equal to an instance of the object. Typically, in your OnConnection event handler, you'd do something like this:

    Application.ComAddins.Item("PROGID").Object = Me

    That allowed external code to get a reference to your COM addin class through its Object property and call public functions on your addin. With VSTO 2005 SE with Outlook 2007, we have a new (better) way of handling this scenario.

    There is a new virtual method which you can override and specify ANY object to be set in that value when the add-in is created.

    You do this by overriding the RequestComAddinAutomationService method in your VSTO add in. See the following MSDN article: http://msdn2.microsoft.com/en-us/library/microsoft.office.tools.addin.requestcomaddinautomationservice(VS.80).aspx

    The article on MSDN actually has a good example of what you need to do. Just realize that you do have to include the attributes on your interface class to make them IDispatch and ComVisible.

Page 1 of 14 (70 items) 12345»