Outlook: MAPI fields versus User Properties

Hi,

I want to improve an outlook addin which is using mapi fields (a lot) and this is generating abnormal behaviors in outlook, so now I'm in doubt to quit all use of mapi fields fo User Properties but I need some recomendatios or comments about using "mapi fields vs user properties" (or viceversa).

Thanks,

Mauricio.




Answer this question

Outlook: MAPI fields versus User Properties

  • LAPM

    .NET languages have no support for accessing MAPI properties in versions prior to Outlook 2007. Therefore, the UserProperties collection would be the supported approach.

    What kind of behaviors are you seeing as abnormal


  • Bawaj

    Hi.

    Now I tried following your suggests but it did not solve the problem, the abnormal behavior continue :(

    --------------------------------------------------------------

    System.Runtime.InteropServices.Marshal.ReleaseComObject(lmsg)
    lmsg = Nothing
    GC.Collect()

    ---------------------------------------------------------------

    So, if CDO 1.21 with .NET and Office2003 does not work (or generate abnormal behaviors) :

    how could I do to persist metadata associated to a mail object (user properties are not an option because I don't have a mapi form associated with all mails)

    thanks a lot for your support,

    Mauricio.



  • Pockey

    For one thing, using CDO 1.21 in .NET code is not supported and can cause all sorts of problems. For another you should be releasing the object Imsg by possibly calling Marshal.ReleaseCOMObject on it after setting it to Nothing and then possibly calling GC.Collect().
  • Ravel

    Hi!

    Now I tried to do this:

    -------------------------------------------------------------------------

    .NET code:

        Public Sub SetCampoMAPI_Mejorado(ByRef Item As Outlook.MailItem, ByVal Campo As String, ByVal valor As String)
            Dim lwrapMAPI As Object, lsEntryID As String, lsStoreID As String
            Try
                lsEntryID = Item.EntryID
                lsStoreID = Item.Parent.StoreID
                lwrapMAPI = CreateObject("prjWorkMAPI.clsWorkMAPI")  'I allready try with an interop of this DLL
                lwrapMAPI.SetCampoMAPI(lsEntryID, lsStoreID, Campo, valor)
                lwrapMAPI.Dispose()
                System.Runtime.InteropServices.Marshal.ReleaseComObject(lwrapMAPI)
                lwrapMAPI = Nothing
            Catch ex As System.Exception
                MsgBox(ex.Message & vbCrLf & ex.Source & vbCrLf & ex.StackTrace, MsgBoxStyle.Critical, "Atencion")
                If Not lwrapMAPI Is Nothing Then
                    lwrapMAPI.Dispose()
                    System.Runtime.InteropServices.Marshal.ReleaseComObject(lwrapMAPI)
                    lwrapMAPI = Nothing
                End If
            End Try
            GC.Collect()
            GC.WaitForPendingFinalizers()
        End Sub

    ------------------------------------------------------------------------

    as you see, I made a VB6's dll which open a mapi session and modify or create a mapi field in a message. In this dll I'm doing early binding with CDO and set to nothing all objects.

    ------------------------------------------------------------------------

    VB6 dll code:

    Option Explicit

    Private mSessionObj As MAPI.Session

    Public Sub SetCampoMAPI(ByVal xsEntryID As String, ByVal xsStoreID As String, _
                            ByVal Campo As String, ByVal valor As String)
        Dim lmsg As MAPI.Message
        Dim loFields As MAPI.Fields, loField As MAPI.Field, lbExiste As Boolean
        Dim lsTest As String
        On Error GoTo ErrHand
       
        Set lmsg = MSession.GetMessage(xsEntryID, xsStoreID)
        Set loFields = lmsg.Fields
        If loFields.Count > 0 Then
            For Each loField In loFields
                If UCase(Trim("" & loField.Name)) = UCase(Trim("" & Campo)) Then
                    loField.Value = valor
                    lbExiste = True
                    Exit For
                End If
            Next
        End If
        If Not lbExiste Then
            loFields.Add Campo, vbString, valor
        End If
       
        lmsg.Update True, True

        Set loFields = Nothing
        Set lmsg = Nothing
       
    Exit Sub
    ErrHand:
        Err.Raise Err.Number, "SetCampoMAPI" & Err.Source, Err.Description
    End Sub

    Public Function MSession() As MAPI.Session
        If mSessionObj Is Nothing Then
            Set mSessionObj = New MAPI.Session
            mSessionObj.Logon "Outlook", "", False, False
            MsgBox "Cree la sesion MAPI:" & mSessionObj.CurrentUser().Name
        End If

        Set MSession = mSessionObj
    End Function


    Public Sub Dispose()
        If Not mSessionObj Is Nothing Then
            mSessionObj.Logoff
            Set mSessionObj = Nothing
        End If
    End Sub

    ------------------------------------------------------------------------

    But the unwanted behavior still here :(

    when I want to save a message which was in draft folder, the warning message appear ("...this message has been changed by another user or in another window...."), only when I set mapi fields of course.

    reproduce this behavior is very simple, did you try

    thanks for your help,

    Mauricio.



  • inias

    well, I just trying to do the same that I'm doing with .net 2003 but now I try to do it with VB6, here is the code (in red is my customized code):

    ---------------------------------------------------------------------------------------------------------

    Option Explicit

    Public FormDisplayed As Boolean
    Public VBInstance As VBIDE.VBE
    Dim mcbMenuCommandBar As Office.CommandBarControl
    Dim mfrmAddIn As New frmAddIn
    Public WithEvents MenuHandler As CommandBarEvents 'command bar event handler

    Public WithEvents OLKApplication As Outlook.Application
    Public WithEvents ColInspectors As Inspectors
    Private mSessionObj As MAPI.Session

    Sub Hide()

    On Error Resume Next

    FormDisplayed = False
    mfrmAddIn.Hide

    End Sub

    Sub Show()

    On Error Resume Next

    If mfrmAddIn Is Nothing Then
    Set mfrmAddIn = New frmAddIn
    End If

    Set mfrmAddIn.VBInstance = VBInstance
    Set mfrmAddIn.Connect = Me
    FormDisplayed = True
    mfrmAddIn.Show

    End Sub

    '------------------------------------------------------
    'this method adds the Add-In to VB
    '------------------------------------------------------
    Private Sub AddinInstance_OnConnection(ByVal Application As Object, ByVal ConnectMode As AddInDesignerObjects.ext_ConnectMode, ByVal AddInInst As Object, custom() As Variant)
    On Error GoTo error_handler

    'save the vb instance
    'Set VBInstance = Application
    Set OLKApplication = Application
    Set ColInspectors = OLKApplication.Inspectors

    ' 'this is a good place to set a breakpoint and
    ' 'test various addin objects, properties and methods
    ' Debug.Print VBInstance.FullName
    '
    ' If ConnectMode = ext_cm_External Then
    ' 'Used by the wizard toolbar to start this wizard
    ' Me.Show
    ' Else
    ' Set mcbMenuCommandBar = AddToAddInCommandBar("My AddIn")
    ' 'sink the event
    ' Set Me.MenuHandler = VBInstance.Events.CommandBarEvents(mcbMenuCommandBar)
    ' End If
    '
    ' If ConnectMode = ext_cm_AfterStartup Then
    ' If GetSetting(App.Title, "Settings", "DisplayOnConnect", "0") = "1" Then
    ' 'set this to display the form on connect
    ' Me.Show
    ' End If
    ' End If

    Exit Sub

    error_handler:

    MsgBox Err.Description

    End Sub

    '------------------------------------------------------
    'this method removes the Add-In from VB
    '------------------------------------------------------
    Private Sub AddinInstance_OnDisconnection(ByVal RemoveMode As AddInDesignerObjects.ext_DisconnectMode, custom() As Variant)
    On Error Resume Next

    'delete the command bar entry
    mcbMenuCommandBar.Delete

    'shut down the Add-In
    If FormDisplayed Then
    SaveSetting App.Title, "Settings", "DisplayOnConnect", "1"
    FormDisplayed = False
    Else
    SaveSetting App.Title, "Settings", "DisplayOnConnect", "0"
    End If

    Unload mfrmAddIn
    Set mfrmAddIn = Nothing

    End Sub

    Private Sub IDTExtensibility_OnStartupComplete(custom() As Variant)
    If GetSetting(App.Title, "Settings", "DisplayOnConnect", "0") = "1" Then
    'set this to display the form on connect
    Me.Show
    End If
    End Sub

    'this event fires when the menu is clicked in the IDE
    Private Sub MenuHandler_Click(ByVal CommandBarControl As Object, handled As Boolean, CancelDefault As Boolean)
    Me.Show
    End Sub

    Function AddToAddInCommandBar(sCaption As String) As Office.CommandBarControl
    Dim cbMenuCommandBar As Office.CommandBarControl 'command bar object
    Dim cbMenu As Object

    On Error GoTo AddToAddInCommandBarErr

    'see if we can find the Add-Ins menu
    Set cbMenu = VBInstance.CommandBars("Add-Ins")
    If cbMenu Is Nothing Then
    'not available so we fail
    Exit Function
    End If

    'add it to the command bar
    Set cbMenuCommandBar = cbMenu.Controls.Add(1)
    'set the caption
    cbMenuCommandBar.Caption = sCaption

    Set AddToAddInCommandBar = cbMenuCommandBar

    Exit Function

    AddToAddInCommandBarErr:

    End Function


    Private Sub ColInspectors_NewInspector(ByVal Inspector As Outlook.Inspector)
    Dim lMailItem As Outlook.MailItem

    Set lMailItem = Inspector.CurrentItem
    If Not lMailItem Is Nothing Then
    SetCampoMAPI lMailItem.EntryID, lMailItem.Parent.StoreID, "Prueba", "MPV"

    End If
    Set lMailItem = Nothing

    End Sub


    Public Sub SetCampoMAPI(ByVal xsEntryID As String, ByVal xsStoreID As String, _
    ByVal Campo As String, ByVal valor As String)
    Dim lmsg As MAPI.Message
    Dim loFields As MAPI.Fields, loField As MAPI.Field, lbExiste As Boolean
    Dim lsTest As String
    On Error GoTo ErrHand

    Set lmsg = MSession.GetMessage(xsEntryID, xsStoreID)
    Set loFields = lmsg.Fields
    If loFields.Count > 0 Then
    For Each loField In loFields
    If UCase(Trim("" & loField.Name)) = UCase(Trim("" & Campo)) Then
    loField.Value = valor
    lbExiste = True
    Exit For
    End If
    Next
    End If
    If Not lbExiste Then
    loFields.Add Campo, vbString, valor
    End If

    lmsg.Update True, True

    Set loFields = Nothing
    Set lmsg = Nothing

    Dispose
    Exit Sub
    ErrHand:
    Err.Raise Err.Number, "SetCampoMAPI" & Err.Source, Err.Description
    End Sub

    Public Function MSession() As MAPI.Session
    If mSessionObj Is Nothing Then
    Set mSessionObj = New MAPI.Session
    mSessionObj.Logon "Outlook", "", False, False
    MsgBox "Cree la sesion MAPI:" & mSessionObj.CurrentUser().Name
    End If

    Set MSession = mSessionObj
    End Function

    Public Sub Dispose()
    If Not mSessionObj Is Nothing Then
    mSessionObj.Logoff
    Set mSessionObj = Nothing
    End If
    End Sub

    ---------------------------------------------------------------------------------------------------------

    and the result is exactly the same that I have with .net addin......... when I modify a mapi field and after I want to save the mail, I get the warning message.

    I don't realise if are something wrong it that code, I think that I release all objects but abnormal behavior is still here :(

    So, after all of this tests I assume that this behavior will happen allways that you want to change or add mapi fields in a mail item.

    could you post the code that you used to test this behavior and didn't get the error

    thanks a lot,

    Mauricio.



  • katokay

    When you work with MAPI properties, you need to make sure you release items promptly so that you don't get conflicts between an item still held in memory and the item the user sees in the UI. It may even be necessary to force GC.Collect.
  • Saurabh G

    I think you need to set an object to Nothing before calling ReleaseComObject.

    The availability of the UserProperties collection is not dependent on a custom form being associated with a message. That said, using UserProperties on messages can create problems for forwarded messages. Lower level third-party libraries like Outlook Redemption and MAPI33 can provide access to features that normally are available only with Extended MAPI. Another approach would be to use VB6 to create your own COM component using CDO 1.21 that your .NET application can call when it needs that functionality.


  • P.Johansson

    Hi,

    yes I get this warning in inbox folder too ("The item could not be saved because it has been changed by another user o in another window.Do you want to make a copy in the default folder for the item ").

    but, did you trying do something like I'm doing (addin in .net 2003, calling a VB6 dll and using the MailItem variable in .net code which is in module level because I need events handling, to catch mail close for example)

    I'm going to do some tests without event handling and without module level variables, later I'll tell you about this.

    thanks for your patience,

    Mauricio.



  • Puil

    No, I didn't exactly replicate your setup, I just tested using some Outlook VBA code with CDO 1.21.

    I'd first suggest using a piggyback logon to CDO and not using a profile logon. That could be part of the problem. I would logon as follows: mSessionObj.Logon "", "", False, False. Since Outlook is already running that lets CDO share the same session as Outlook.

    I'd also not do everything you're doing in NewInspector. The object reference you can get in NewInspector is a pretty weak reference and I only use it for CurrentItem.Class, .MessageClass. I wait until Inspector.Activate fires before I try to manipulate any properties such as the Fields collection to make sure the item in the Inspector is fully instantiated.

    Using Update(True, True) from a call from NewInspector might be part of the problem along with the other things I mentioned.

    See if changing those things helps.


  • ron nash

    No, I don't get that error in either VB6 or VBA code. Do you get it if the Message is saved elsewhere, other than Drafts

    Do you have any open references to the Imsg item You're passing the EntryID and StoreID for Imsg, are you releasing any object references to that object before passing the ID strings


  • tenchyz

    Does it work any better if you do this

    iMsg = Nothing

    GC.Collect()

    GC.WaitForPendingFinalizers


  • Thymen

    ok, I post one method of many which I'm using:

    Public Shared Sub CampoOculto_Agregar(ByRef Item As Outlook.MailItem, ByVal Campo As String, ByVal valor As String)
    Dim lmsg As MAPI.Message
    Dim lsTest As String
    If IsDate(Left(Item.BillingInformation, 10)) Then
    SetCampoOm(Item.BillingInformation, Campo, valor)
    Else
    Dim larValor As Array = Array.CreateInstance(GetType(String), 1)
    larValor(0) = valor
    lmsg = MSession.GetMessage(Item.EntryID, Item.Parent.StoreId)
    Try
    lsTest = lmsg.Fields(Campo).Name

    If TypeName(lmsg.Fields(Campo).Value) = "Object()" Then
    lmsg.Fields(Campo).Value = larValor
    ElseIf TypeName(lmsg.Fields(Campo).Value) = "String" Then
    lmsg.Fields(Campo).Delete()
    lmsg.Fields.Add(Campo, vbArray + vbString, larValor)
    Else
    MsgBox("error de tipo al actualizar campo: " & Campo)
    End If
    Catch ex As System.Exception
    Try
    'lFields = lmsg.Fields()
    'lFields.Add(campo,vbArray + vbstring
    lmsg.Fields.Add(Campo, vbArray + vbString, larValor)

    Catch ex1 As System.Exception
    MsgBox(ex1.Message)
    End Try
    End Try
    lmsg.Update(True, True)
    lmsg = Nothing
    End If

    What is wrong in this code (talking about work with MAPI fields, of course)

    I really appreciate your help, thanks.

    Mauricio.



  • adiash

    if I set to nothing the object before calling ReleaseComObject, this method throw an error because object is nothing..., so I assume that set to nothing goes after ReleaseComObject.

    I can't to use UserProperties without a custom form because I need persist the values on mail item on forward and reply. When user close Outlook and open it again I need the value on mail still there too.

    So, now I will try to do something with redemption or mapi33 or else make my own vb6 dll.

    thanks a lot again,

    Mauricio.

    PD: later I will post my tests result and maybe new questions ;)



  • IanG

    Somethings like warnings when closing a mail saying: "The message has changed, do yo want to save...."

    or if you save a message in drafts and later open it and make some changes, when yo want save it, a warning appear saying that a copy will be made in origin folder, so you have a copy in inbox too, later if yo close outlook , when you open it again and open the message in drafts folder the last changes does not appear, but in the copy in inbox the changes are.......

    Behaviors like this happen in PCs which have my outlook addin installed, I assume that work with mapi fields are the problem because if I ommit set mapi fields this behaviors does not happen.

    Thanks for your help,

    Mauricio



  • Outlook: MAPI fields versus User Properties