TL;DR:
cf.typeSpecified = true;
Clarifying the question:
The other answer mistakenly assumes there is an existing Custom Object and we are getting an error when creating new records for it.
The question is actually regarding use of the Salesforce Metadata SOAP API from C# to create a Custom Object that did not previously exist. It has nothing to do with creating new records in an existing Custom Object.
The pain persists:
It's nearly two years later, and I hit the same problem. Nothing I found gave a working answer, but I found three places online (including this SE question) where someone was asking how to solve this problem, and did not get a working answer. (I attempted to include the links here, but SE didn't like it, so you'll have to take my word for it. :) )
Why this solves the problem:
Microsoft Visual Studio (even 2015, which I'm using) has a "feature" (extremely poor design IMHO) where, when generating .NET code from a WSDL file for a SOAP web service, optional properties are botched in a workable but not-nice way.
You would expect that setting a value to the optional property would be all you need to do.
But actually, you need to set a value to the optional property, and also set the corresponding ...Specified property to "true".
On the CustomField object, the "type" property is optional. Due to this bug/quirk/design-flaw/"feature" in .NET's default handling of WSDL, setting the CustomField.type without also setting typeSpecified = true, has the same effect as not setting the type at all.
To get more technical, the problem occurs at the point of serialization to XML. The XML serializer will not output to XML, any optional property where the corresponding ...Specified property == false.
A C# code sample that actually works:
var sec = new SalesforceEnterpriseApi.SoapClient(); // Salesforce Enterprise Client
var loginResponse = sec.login(null, "[email protected]", "your.password.and.security.token");
var metadataSession = new SalesforceMetadataApi.SessionHeader();
metadataSession.sessionId = loginResponse.sessionId;
var smc = new SalesforceMetadataApi.MetadataPortTypeClient(); // Salesforce Metadata Client
smc.Endpoint.Address = new System.ServiceModel.EndpointAddress(loginResponse.metadataServerUrl);
var co = new CustomObject();
co.deploymentStatus = DeploymentStatus.InDevelopment;
co.deploymentStatusSpecified = true;
co.fullName = "AwesomeCustomObject__c";
co.label = "Awesome Custom Object";
co.pluralLabel = "Awesome Custom Objects";
co.sharingModel = SharingModel.ReadWrite;
co.sharingModelSpecified = true;
var cf = new CustomField();
cf.type = FieldType.Text;
cf.typeSpecified = true;
cf.label = "Name Field";
co.nameField = cf;
var upsertResponse = smc.upsertMetadata(metadataSession, null, null, new Metadata[] { co });
var readResponse = smc.readMetadata(metadataSession, null, "CustomObject", new string[] { "AwesomeCustomObject__c" });
Prerequisites:
- Download WSDL and generate service wrappers for Enterprise WSDL and Metadata WSDL. (Log into Salesforce, click "Setup" towards top right, then in search box at top of left sidebar menu, type "api" but do NOT press Enter. The left sidebar menu should shrink down to just one item called "API". Click into that, and then it's easy to download the two WSDL files I mentioned.)
- Put the sample code in a function/method, and in the same code file, import both the Enterprise API and Metadata API namespaces with appropriate "using" lines.
- Put your Salesforce username in place of "[email protected]".
- Obtain your Salesforce security token (log into SF, click your username in top bar and click "My Settings" from the resulting popup menu, then in left sidebar navigation, click "Personal", and from the submenu that expands, click "Reset My Security Token" and follow the instructions there).
- Append your Salesforce security token to your password (with nothing between them) and put that in place of "your.password.and.security.token".
I don't think I'm exaggerating to say that this simple actually working C# sample code for creating new Custom Objects through the Metadata API, including the simple, correct, instructions for connecting to the API in the first place, would've saved me a few days of effort. I hope it helps someone else!