'Outlook Add-in: Am I not aware of some basic, conceptual theories of C# programming?

Win 10 - VS 2019 - Office365 - Exchange Online

My first post - woo hoo!!! (Although I have been lurking forever.)

I am writing my first Outlook Add-in with my first experience with C#. I have been hacking on computers since the 1980s and have had experience with about 10 different languages but am very new to this C# and .NET environment.

What I have will build without complaint and works as intended - most of the time.

namespace RoomReservations
{
    public partial class ThisAddIn
    {
        private Outlook.Inspectors inspectors = null;
        private Outlook.UserProperty objUserPropertyEventId = null;

        private void ThisAddIn_Startup(object sender, System.EventArgs e)
        {
            inspectors = this.Application.Inspectors;
            inspectors.NewInspector += new Microsoft.Office.Interop.Outlook.InspectorsEvents_NewInspectorEventHandler(Inspectors_NewInspector);
        }

        private void ThisAddIn_Shutdown(object sender, System.EventArgs e)
        {
            // Note:      Outlook no longer raises this event. If you have code that 
            //              must run when Outlook shuts down, see https://go.microsoft.com/fwlink/?LinkId=506785
        }

        void Inspectors_NewInspector(Microsoft.Office.Interop.Outlook.Inspector Inspector)
        {
            #pragma warning disable IDE0019 // Use pattern matching
            Outlook.AppointmentItem appt = Inspector.CurrentItem as Outlook.AppointmentItem;
            #pragma warning restore IDE0019 // Use pattern matching
            // is it an AppointmentItem
            if (appt != null)
            {
                // is it a meeting request
                if (appt.MeetingStatus == Outlook.OlMeetingStatus.olMeeting)
                {
                    appt.Save();

                    // save EventId as UserProperty
                    Outlook.AppointmentItem mtg = null;
                    mtg = (Outlook.AppointmentItem)Inspector.CurrentItem;
                    mtg.MeetingStatus = Outlook.OlMeetingStatus.olMeeting;
                    if (mtg != null)
                    {
                        if (mtg is Outlook.AppointmentItem)
                        {
                            string strEntryId = mtg.EntryID;

                            Outlook.UserProperties objUserProperties = mtg.UserProperties;
                            objUserPropertyEventId = objUserProperties.Add("MeetingEntryId", Outlook.OlUserPropertyType.olText, true, 1);
                            objUserPropertyEventId.Value = strEntryId;
                        }
                    }
                    if (mtg != null)
                        Marshal.ReleaseComObject(mtg);
                }
                if (appt != null)
                    Marshal.ReleaseComObject(appt);
            }
            // define event handler for ItemSend
            Application.ItemSend += new Outlook.ApplicationEvents_11_ItemSendEventHandler(Application_ItemSend);
        }

        public void Application_ItemSend(object Item, ref bool Cancel)
        {
            // use EventId to identify current meeting request
            var app = new Microsoft.Office.Interop.Outlook.Application();
            var ns = app.Session;
            Outlook.AppointmentItem meeting = ns.GetItemFromID(objUserPropertyEventId.Value);

            // is ItemSend a request or a cancel
            if (meeting.MeetingStatus != Outlook.OlMeetingStatus.olMeetingCanceled)
            {
                // ItemSend is a request
                MessageBox.Show("Not Cancelled.");
                if (meeting is Outlook.AppointmentItem)
                {
                    if (meeting.MeetingStatus == Outlook.OlMeetingStatus.olMeeting)
                    {
                        // if a location was provided
                        if (meeting.Location != null)
                        {
                            Cancel = true;
                            bool blnTorF = false;
                            Outlook.Recipient recipConf;
                            // has the user included "JHP - Room Reservations" for placement
                            // on the shared calendar for all conference room availability
                            foreach (Outlook.Recipient objUser in meeting.Recipients)
                            {
                                if (objUser.Name == "JHP - Room Reservations")
                                    blnTorF = true;
                            }
                            // add "JHP - Room Reservations" if not already included
                            if (blnTorF == false)
                            {
                                recipConf = meeting.Recipients.Add("JHP - Room Reservations");
                                recipConf.Type = (int)Outlook.OlMeetingRecipientType.olRequired;
                                if (recipConf != null)
                                    Marshal.ReleaseComObject(recipConf);
                            }

                            // add reservarion to specific conference room calendar
                            String strLocation = meeting.Location;
                            Outlook.Recipient recipRoomUser;
                            if (strLocation.Contains("142"))
                            {
                                recipRoomUser = meeting.Recipients.Add("[email protected]");
                                recipRoomUser.Type = (int)Outlook.OlMeetingRecipientType.olRequired;
                            }
                            if (strLocation.Contains("150"))
                            {
                                recipRoomUser = meeting.Recipients.Add("[email protected]");
                                recipRoomUser.Type = (int)Outlook.OlMeetingRecipientType.olRequired;
                            }
                            if (strLocation.Contains("242"))
                            {
                                recipRoomUser = meeting.Recipients.Add("[email protected]");
                                recipRoomUser.Type = (int)Outlook.OlMeetingRecipientType.olRequired;
                            }
                            if (strLocation.Contains("248"))
                            {
                                recipRoomUser = meeting.Recipients.Add("[email protected]");
                                recipRoomUser.Type = (int)Outlook.OlMeetingRecipientType.olRequired;
                            }

                            // send request
                            meeting.Recipients.ResolveAll();
                            meeting.Send();
                        }
                    }
                }
            }
            else
            {
                // ItemSend is a cancel
                MessageBox.Show("Meeting is cancelled.");
            }
        }

        #region VSTO generated code

        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InternalStartup()
        {
            this.Startup += new System.EventHandler(ThisAddIn_Startup);
            this.Shutdown += new System.EventHandler(ThisAddIn_Shutdown);
        }
        #endregion
    }
}

When working correctly, a meeting request will put 4 separate entries on 4 separate calendars. A meeting cancel will remove 4 separate entries from 4 separate calendars. The erratic behavior that occurs for a request includes placing the meeting event on only 2 calendars. Unusual behavior that occurs for a cancel includes deleting the meeting event from only 2 calendars. Or the meeting cancel has to be sent out 3 or 4 times before the meeting event is removed from all calendars. But then, I send another meeting request without changing any code and it is placed on all 4 calendars, as expected. And a cancel of that request will remove it from all 4 calendars on the first try. This odd behavior is common within our test group of three users, each running the identical add-in.

I feel like I understand how the native functions [if, foreach, etc.] work. But I don't know what I don't know. I have added some housekeeping [Marshal.ReleaseComObject()]. I'm not sure if it is everywhere it needs to be. I'm not sure if there are other means of housekeeping I am not aware of that could be causing this behavior. I am having a difficult time understanding why this will work, then not work, then work - all without changing any code.

Thanks for any leads that point in the direction of discovery, Chris



Solution 1:[1]

The best way to understand how the code works is to run it under the debugger. Just set some breakpoints and run the solution hitting F5 in Visual Studio. When your breakpoints are hit you can go line-by-line and see intermediate results.

First of all, there is no need to subscribe to the ItemSend event in the NewInspector event handler. That is an Application-wide event, so you need to subscribe only once at startup.

private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
            inspectors = this.Application.Inspectors;
            inspectors.NewInspector += new Microsoft.Office.Interop.Outlook.InspectorsEvents_NewInspectorEventHandler(Inspectors_NewInspector);
            // define event handler for ItemSend
            Application.ItemSend += new Outlook.ApplicationEvents_11_ItemSendEventHandler(Application_ItemSend);
}

Second, the item which is being sent is passed as a parameter to the ItemSend event handler, so you can use it instead of getting a separate one. So, the following code doesn't make sense:

 public void Application_ItemSend(object Item, ref bool Cancel)
        {
            // use EventId to identify current meeting request
            var app = new Microsoft.Office.Interop.Outlook.Application();
            var ns = app.Session;
            Outlook.AppointmentItem meeting = ns.GetItemFromID(objUserPropertyEventId.Value);

Instead, use the parameter:

public void Application_ItemSend(object Item, ref bool Cancel)
{
            Outlook.AppointmentItem meeting = Item as Outlook.AppointmentItem;

Third, there is no need to create a new Outlook Application instance in the ItemSend event handler, use the Application property of your add-in class instead

Fourth, if you need to change the outgoing item there is no need to call the Send method in the ItemSend event handler.

meeting.Send();

Use the second parameter if you want to allow or cancel any further processing of that item. For example, if the event procedure sets this argument to true, the send action is not completed and the inspector is left open.

Fifth, I'd recommend using the try/catch blocks to log exceptions and release underlying COM objects in the finally, so you could be sure that everything went well and all objects were released. For example:

private void CreateSendItem(Outlook._Application OutlookApp)
{
    Outlook.MailItem mail = null;
    Outlook.Recipients mailRecipients = null;
    Outlook.Recipient mailRecipient = null;
    try
    {
        mail = OutlookApp.CreateItem(Outlook.OlItemType.olMailItem)
           as Outlook.MailItem;
        mail.Subject = "A programatically generated e-mail";
        mailRecipients = mail.Recipients;
        mailRecipient = mailRecipients.Add("Eugene Astafiev");
        mailRecipient.Resolve();
        if (mailRecipient.Resolved)
        {
            mail.Send();
        }
        else
        {
            System.Windows.Forms.MessageBox.Show(
                "There is no such record in your address book.");
        }
    }
    catch (Exception ex)
    {
        System.Windows.Forms.MessageBox.Show(ex.Message,
            "An exception is occured in the code of add-in.");
    }
    finally
    {
        if (mailRecipient != null) Marshal.ReleaseComObject(mailRecipient);
        if (mailRecipients != null) Marshal.ReleaseComObject(mailRecipients);
        if (mail != null) Marshal.ReleaseComObject(mail);
    }
}

You may find the How To: Create and send an Outlook message programmatically article helpful.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1