Calling the Salesforce Metadata API from VB.NET

The Metatdata API is used by many products to push and pull code to and from Salesforce.  It is found by going to App Setup, Develop, API and choosing the Metadata API.  The Metadata can be added to a VB.Net project by following these steps.

1) Open up VB.NET 2010
2) Click on File, New Project…
3) Choose a Windows Forms Application; Give it a name; and Click on OK
4) Rename Form1 to something more meaningful.
5) Download the Metatdata API from Salesforce
a. Log into Salesforce
b. Click on Your Name, Setup, App Setup, Develop, API
c. Click on Generate Metadata WSDL
d. Save the generated page as an XML file.
6) In VB.NET click on the ‘Show All Files’ button in the Solution Explorer
7) Right-click on the References node and select ‘Add Service Reference’
8) Click on the ‘Advanced…’ button
9) Click on the ‘Add Web Reference…’ button
10) In the URL combo box type in the path to your saved wsdl.  For me the path looked like this: C:\Users\terry.luschen\Documents\Salesforce Info\Whitepapers\November 2012 Whitepaper – Title\metadata.wsdl.xml
11) Change the ‘Web reference name’ to something more reasonable like ‘SFMetaDataAPI’
12) Now repeat the same steps for the Partner API.  It is the ‘Generate Partner WSDL’ in Salesforce.
13) Drop a button on your form, rename it to something appropriate and double-click on it to get to the code.

We are now at the point where we can login using the Partner API so our Metatdata API can be used.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
Private sfPartner As New SFPartnerAPI.SforceService()
Private sfMetaData As New SFMetadataAPI.MetadataService()
    Private Sub btnSourceControl_Click(sender As System.Object, e As System.EventArgs) Handles btnSourceControl.Click
        LogInToSalesforce()
        DoDeploy()
        DoRetrieve() ' I will work through this in my next blog post
        MsgBox("Action Complete")
    End Sub
    Private Sub LogInToSalesforce()
        Dim sfSLoginResult As SFPartnerAPI.LoginResult
        sfPartner.Url = "https://login.salesforce.com/services/Soap/u/21.0"
        sfSLoginResult = sfPartner.login("email", "passwordSecurityToken")
        sfPartner.SessionHeaderValue = New SFPartnerAPI.SessionHeader()
        sfPartner.SessionHeaderValue.sessionId = sfSLoginResult.sessionId
        sfMetaData.SessionHeaderValue = New SFMetadataAPI.SessionHeader()
        sfMetaData.SessionHeaderValue.sessionId = sfSLoginResult.sessionId
        sfMetaData.Url = sfSLoginResult.metadataServerUrl
        sfPartner.Url = sfSLoginResult.serverUrl
    End Sub

Now that we are logged in we are ready to deploy our code.  The example below has some hard-coded file names, but hopefully the structure of how to call the Metadata API is clear.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
Private Sub DoDeploy()
        Dim deployOpts As New SFMetadataAPI.DeployOptions
        Dim asyncResultItem As New SFMetadataAPI.AsyncResult
        Dim newDeployResult As SFMetadataAPI.DeployResult
        Dim newRetrieveResult As SFMetadataAPI.RetrieveResult
        Dim myFileProperties() As SFMetadataAPI.FileProperties
        Dim deployMessageItem() As SFMetadataAPI.DeployMessage
        Dim byteFile As Byte()
        Dim i As Integer
        Dim newZipFile As String
        deployOpts.runAllTests = False
        deployOpts.runTests = Nothing
        deployOpts.autoUpdatePackage = False
        deployOpts.allowMissingFiles = False
        deployOpts.checkOnly = False
        deployOpts.ignoreWarnings = False
        deployOpts.performRetrieve = True
        deployOpts.purgeOnDelete = False
        deployOpts.rollbackOnError = False
        deployOpts.singlePackage = True
        newZipFile = "C:\Deploy Test2\packageToSend.zip"
        If System.IO.File.Exists(newZipFile) Then
            System.IO.File.Delete(newZipFile)
        End If
        'Build a Zip File to Send to Salesforce - <a href="http://dotnetzip.codeplex.com/">http://dotnetzip.codeplex.com/</a> project
        Using zip As Ionic.Zip.ZipFile = New Ionic.Zip.ZipFile
            zip.AddFile("C:\Deploy Test2\package.xml", "")
            zip.AddFile("C:\Deploy Test2\classes\startHereController.cls", "classes")
            zip.AddFile("C:\Deploy Test2\classes\startHereController.cls-meta.xml", "classes")
            zip.Save(newZipFile)
        End Using
        'This needs to be a zip file.  The zip file can have any file name
        ' In the root directory there must be a package.xml file
        ' If you have classes there must be a classes folder and if you have triggers there must be a triggers folder
        byteFile = LoadFile(newZipFile)
        asyncResultItem = sfMetaData.deploy(byteFile, deployOpts)
        If asyncResultItem.done = False Then
            waitUntilDone(asyncResultItem.id)
        End If
        newDeployResult = sfMetaData.checkDeployStatus(asyncResultItem.id)
        If newDeployResult.success Then
            MsgBox("Success sending files")
            If deployOpts.performRetrieve Then
                'If performRetrieve is set then we get a zip file back of all of the contents
                newRetrieveResult = newDeployResult.retrieveResult
                myFileProperties = newRetrieveResult.fileProperties
                For i = myFileProperties.GetLowerBound(0) To myFileProperties.GetUpperBound(0)
                    MsgBox("File " & i & " - Name: " & myFileProperties(i).fullName & " - Last Modified By: " & myFileProperties(i).lastModifiedByName & _
                            " - Last Modified Date: " & myFileProperties(i).lastModifiedDate)
                Next
                System.IO.File.WriteAllBytes("C:\Deploy Test2\packageRetrieved.zip",
                                                 newDeployResult.retrieveResult.zipFile())
            End If
        Else
            MsgBox("Failed sending files")
            deployMessageItem = newDeployResult.messages
            For i = deployMessageItem.GetLowerBound(0) To deployMessageItem.GetUpperBound(0)
                MsgBox("Message " & i & ": " & deployMessageItem(i).problem + " " + deployMessageItem(i).fileName)
            Next
        End If
End Sub
Private Function LoadFile(ByVal fileLocation As String) As Byte()
        Dim fInfo As New System.IO.FileInfo(fileLocation)
        Dim fs As System.IO.FileStream = System.IO.File.OpenRead(fileLocation)
        Dim buffer(Convert.ToInt32(fInfo.Length)) As Byte
        fs.Read(buffer, 0, buffer.Length)
        Return buffer
End Function

The hardest parts to get the code going was to 1) Understand the structure of the zip file and 2) To find a .NET utility that could zip a file for me.

I found a .NET zip utility at http://dotnetzip.codeplex.com and through various readings I figured out the file structure for the zip file. There needs to be a package.xml file in the root directory of the zip file. This package.xml file is structured just like the same files in the directory structure of your Eclipse project. Then there needs to be the same folders as there are in your Eclipse project like ‘classes’ and ‘triggers’.  Once that zip file is set up it is as easy as calling the LoadFile function to generate the corresponding byte array for the zip file.  From there the ‘deploy’ and ‘checkdeploystatus’ calls have to be made and your Apex code should be uploaded.  The code would need to be more generic and have more error handling to make it production ready, but the point here is to help you get started on your own project with a working example.

I hope to write about retrieving metadata zip files next week.

This entry was posted in Technology and tagged . Bookmark the permalink.

2 Responses to Calling the Salesforce Metadata API from VB.NET

  1. calling list says:

    Hi there would you mind stating which blog platform you’re using? I’m going to start
    my own blog in the near future but I’m having a difficult time making a decision between BlogEngine/Wordpress/B2evolution and Drupal. The reason I ask is because your layout seems different then most blogs and I’m looking for something completely
    unique. P.S Apologies for getting off-topic but
    I had to ask!

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>