Simon Fell > Its just code > January 2008

Thursday, January 31, 2008

A few people have been asking for a complete example of using the metadata API from .NET, If you've using the regular enterprise/partner API, then the patterns should look familiar. One wrinkle for .NET is the whole fooSpecified mess, this is more apparent in the metadata API because most of the fields aren't strings.

I created a new project in VS.NET 2005, imported the enterprise WSDL (as sforce) and imported the metadata WSDL (as metaforce), the code makes a login call with the enterprise API, creates a stub for the metadata API, configuring the sessionHeader and Url from the login result (just like for the regular enterprise stub). then goes on to setup a CustomObject structure and passes it to create. One difference to the enterprise API is that the process is async, so you get back an AsyncResult structure that indicates its current state, a guess at how long to wait before checking its state again and so on, so we sit in a loop (with the specified waits) until its done. If the object was created, then we call describeSObject on the enterprise API to check that it really did do something, here's the code.

using System;
using System.Collections.Generic;
using System.Text;

namespace metadataDemo
    class Program
        static void Main(string[] args)
            if (args.Length != 2)
                Console.WriteLine("useage: metadataDemo username password");
            MetadataCreator mc = new MetadataCreator(args[0], args[1]);

    class MetadataCreator
        private metaforce.MetadataService ms;
        private sforce.SforceService ss;

        public MetadataCreator(String username, String password)
            ss = new sforce.SforceService();
            sforce.LoginResult lr = ss.login(username, password);
            ss.Url = lr.serverUrl;
            ss.SessionHeaderValue = new sforce.SessionHeader();
            ss.SessionHeaderValue.sessionId = lr.sessionId;

            ms = new metaforce.MetadataService();
            ms.Url = lr.metadataServerUrl;
            ms.SessionHeaderValue = new metaforce.SessionHeader();
            ms.SessionHeaderValue.sessionId = lr.sessionId;

        public void Create()
            metaforce.CustomObject co = new metaforce.CustomObject();
            co.deploymentStatus = metaforce.DeploymentStatus.Deployed;
            co.deploymentStatusSpecified = true;
            co.description = "My Custom Object created from .NET";
            co.fullName = "DotNetCustomObject__c";
            co.label = "DotNet Custom Object";
            co.pluralLabel = "DotNet Custom Objects";
            co.sharingModel = metaforce.SharingModel.ReadWrite;
            co.sharingModelSpecified = true;
            co.nameField = new metaforce.CustomField();
            co.nameField.type = metaforce.FieldType.Text;
            co.nameField.label = "The Name";
            co.nameField.length = 100;
            co.nameField.lengthSpecified = true;

            metaforce.AsyncResult r = ms.create(new metaforce.Metadata[] { co })[0];
            while(!r.done) {
                System.Threading.Thread.Sleep(r.secondsToWait * 1000);
                r = ms.checkStatus(new string[] { })[0];
            if (r.state == metaforce.AsyncRequestState.Error) 
                Console.WriteLine("Error : {0} {1}", r.statusCode, r.message);
            else {
                Console.WriteLine("Created customObject {0}, calling describeSObject now", co.fullName);
                sforce.DescribeSObjectResult d = ss.describeSObject(co.fullName);
                Console.WriteLine("DescribeSobject for {0} ({1})",, d.label);
                foreach(sforce.Field f in d.fields)
                    Console.WriteLine("{0}\t{1}",, f.type);
And here's the output from a running it
Created customObject DotNetCustomObject__c, calling describeSObject now
DescribeSobject for DotNetCustomObject__c (DotNet Custom Object)
Id      id
OwnerId reference
IsDeleted       boolean
Name    string
CreatedDate     datetime
CreatedById     reference
LastModifiedDate        datetime
LastModifiedById        reference
SystemModstamp  datetime

Creating customFields is just as easy, just remember to specify the fully qualified fieldname as its fullName, e.g. if you wanted a new field on your custom object co__c, you set the name to be co__c.newField__c, or if you wanted a new custom field on Account, it'd be Account.newField__c. Share and Enjoy.

Wednesday, January 23, 2008

I seem to be collecting Office suites that don't quite do what i want. I upgraded to iWork '08 when it came out because i was looking for something better than Excel 2004, which is bloated and sluggish (even on my 4 core mac pro). However Numbers has no way as far as i can see to get external data into it, no support for AppleScript, and no equivalent to Excel's Web Query feature, so its largely sat on the sidelines gathering dust. So, my copy of Office 2008 turned up last week, I figured the new Intel native version would at least be snappier than its rosseta stone older sibling, but no, somehow it feels about as sluggish as the previous version. Now, i was expecting the loss of VBA macro's but i didn't realize that they'd completely abandoned any document integration at all, yes I can write apple script to manipulate spreadsheets, but these scripts aren't stored in the document, i can't assign an applescript to say a button in the document, there doesn't seem to be any sort of event integration, so i can run scripts on save, or load. I now have a single menu item with a (potentially) bucket-load of apple scripts and I've got to remember which one goes with which document. And just to rub salt in the wound, Web Queries seem to be broken, you can create new one's fine, but goto do a refresh and you'll get the cryptic and unhelpful message "selection can contain a single cell only", nice, you should be able to return broken software. Guess I'm stuck with Excel 2004 for a while longer, ho hum.

Wednesday, January 23, 2008

Despite all the buzz, Leopard is a fairly incremental release for users, Time Machine is nice, but still needs work, the unified UI is nice, spaces is equal parts useful and annoying. But for developers Leopard is quite the candy store, I've been tinkering with a number of the new APIs, and am starting to put it into practice with an update for SoqlXplorer. NSOperation/NSOperationQueue is particularly nice, as is the reams of code i can now delete and replace with system provided equivalents (NSGradient, HUDWindow, NSSplitView, the new style source list outline view, and of course Core Animation). Still, there's a few raw edges I've run into, this core data bug, and some of the docs still need updating (I ran into this one today, the docs for setDoubleAction on NSTableView are wrong for Leopard, I found confirmation that someone else spotted the same problem, the fix is fairly easy). Continues to be a nice change of pace from the day job.

Monday, January 21, 2008

At Thursdays Tour de Force conference, I ran into a few folks who asked for a more upto date build of the DataLoader for OSX. I put together a new build based on the 10.0 Apex Data Loader. I've tested this on Leopard, it should also work on Tiger if you have the Java 1.5 update installed (but I haven't tried it, if you have trouble the older version was built & tested on Tiger, details here). If you're still on Tiger, let me know how you get on with it.. And a reminder, you can find the complete list of my OSX/ tools at

Sunday, January 6, 2008

I updated the TextMate plug-in I wrote for Apex, the new version handles the keychain much better, and now supports servers other than (e.g. Sandbox or pre-release). Download the updated plugin bundle and drop it in your ~/Library/Application Support/TextMate/Bundles folder.

Thursday, January 3, 2008

Tour de Force - San Francisco is rapidly approaching, your chance to talk to other customers & developers, and to hear from and talk to the product managers and developers behind the platform, I'll be there, bring your Web Services questions.