Plug-in code for SharePoint List creation

Like most of you know, Microsoft has created a demo VPC which you can download from partnersource. On this demo there are some interesting features. For instance, if you do create an account, then automatically a sharepoint folder is created and linked to the account. This has been done by using a plugin. The plug-in code has been added below. Of course you will need to change some things related to URLs and credentials, but this should get you started very quickly.


public void Execute(IPluginExecutionContext context)
{
DynamicEntity entity = (DynamicEntity) context.InputParameters.Properties["Target"];
string listName = entity["name"].ToString();
string g = context.OutputParameters.Properties["id"].ToString();
int templateID = 0x65;
Lists lists = new Lists();
lists.Url = "http://sharepoint/crmdocs/_vti_bin/Lists.asmx";
lists.Credentials = new NetworkCredential("administrator", "pass@word1", "contoso");
string str3 = lists.AddList(listName, listName + " Document Library", templateID).InnerXml.ToString();
DynamicEntity entity2 = new DynamicEntity();
entity2.Name = EntityName.account.ToString();
entity2["accountid"] = new Key(new Guid(g));
entity2["new_sharepointdocumentlibraryurl"] = "http://sharepoint/crmdocs/" + listName + "/Forms/AllItems.aspx";
context.CreateCrmService(true).Update(entity2);
}



Create Folder in SharePoint using Web Services

Thanks to some other bloggers on the web, especially David Klein, I've been able to quickly build code which allows me to create and/or ensure that a folder in a particular document library does exist. It took a while to get everything working exactly as it should, but in this post I do paste the code as it works.

First you would need to create a web reference to the following webservice:
http://localhost/_vti_bin/Lists.asmx
Or another location if you do have a specific server or location.

Then copy and paste the code below and this should work. The listName should be the name of the document library and the destinationFolder can be any desired folder in SharePoint.


static string FOLDER_EXISTS = "0x8107090d";
static string SUCCESS = "0x00000000";

private void CreateFolder(string listName, string destinationFolder)
{
string[] folders = destinationFolder.Split('/');
if (folders.Length > 0)
{
string path = folders[0];
for (int i = 1; i < folders.Length; i++)
{
CreateFolder(listName, path, folders[i]);
path = path + "/" + folders[i];
}
}
}

/// <summary>
/// As checked in U2U CAML query builder, this is valid in
/// <Batch PreCalc='TRUE' OnError='Continue' RootFolder='/Purchase Order Forms V2'>
///<Method ID='1' Cmd='New'><Field Name='ID'>New</Field><Field Name='FSObjType'>1</Field><Field Name='BaseName'>DDkTest2</Field></Method></Batch>
/// THIS Works for Subfolders
/// <Batch PreCalc='TRUE' OnError='Continue' RootFolder='/Purchase Order Forms V2/2010_01_January'>
/// <Method ID='1' Cmd='New'><Field Name='ID'>New</Field><Field Name='FSObjType'>1</Field><Field Name='BaseName'>DDkTest2</Field></Method></Batch>
/// </summary>
private void CreateFolder(string listName, string rootSubFolderName, string newFolderName)
{
SharePointListService.Lists listService = new SharePoint_Tests.SharePointListService.Lists();
listService.Credentials = System.Net.CredentialCache.DefaultCredentials;

//Correct invalid characters
newFolderName = newFolderName.Replace(":", "_");
string rootFolder = rootSubFolderName.Length > 0 ? string.Format("/{0}/{1}", listName, rootSubFolderName) : listName;
string xmlCommand = string.Format("<Method ID='1' Cmd='New'><Field Name='ID'>New</Field><Field Name='FSObjType'>1</Field><Field Name='BaseName'>{1}</Field></Method>", rootFolder, newFolderName);
XmlDocument doc = new XmlDocument();
System.Xml.XmlElement batchNode = doc.CreateElement("Batch");
batchNode.SetAttribute("OnError", "Continue");
//Insert / to front as it is required by web service.
if (!rootFolder.StartsWith("/"))
rootFolder = string.Format("/{0}",rootFolder);

batchNode.SetAttribute("RootFolder", rootFolder);
batchNode.InnerXml = xmlCommand;
XmlNode resultNode = listService.UpdateListItems(listName, batchNode);
if ((resultNode != null) && (resultNode.FirstChild.FirstChild.InnerText == FOLDER_EXISTS) || (resultNode.FirstChild.FirstChild.InnerText == SUCCESS))
{
// success
}
else{
//failure
throw new Exception("Create new folder failed for: " + newFolderName + ". Error Details: " + resultNode.OuterXml);
}
}



SharePoint error message 0x8107026f

Interesting. For the first time in quite a while I'm programming agains SharePoint again. I am working on a possibility to upload files to a specific location in WSS, so the steps I need to make sure are:
- Check if the destination folder exist
- Create the destination folder if necessary
- Upload the file

In the process of creating the destination folder I do encounter a couple of error messages. The most interesting one is message 0x8107026f. Apparently nobody else on the internet has ever posted a question about this code before. My biggest online friends, Bing and Google, were not able to help me out here. But, I have been able to locate the issue and would like to share the cause and solution here.

The location where I do get this error message is in the last line of code:


batchNode.SetAttribute("RootFolder", rootFolder);
batchNode.InnerXml = xmlCommand;
XmlNode resultNode = listService.UpdateListItems(listName, batchNode);

the rootFolder in this case has been concatenated from the current location and the new desired destination folder. Apparently the UpdateListItems call can only create the last folder of the rootFolder. If you need to create more folders like a tree, then you would need to call this function multiple times. For example. If your root node is "My Files/Campaigns/Campaign x/Mail Merge Files" and the document library My Files does exist and in the Doc Lib there is a folder for Campaigns, but there is no folder for "Campaign x" yet, then this will need to be created first before the UpdateListItems could be called for "Mail Merge Files"

Some related error messages:

0x8107026f: The destination file or folder does not exist
0x81020073: The file or folder name contains characters that are not permitted
0x8107090d: The file or folder name does already exist
0x00000000: Succes
0x81020020: Invalid URL value A URL field contains invalid data. Please check the value and try again

The error code 0x81020020 can be caused because you are using a sitename in the webservice URL, but you have omitted to use the same name in the root folder.



Retrieving emails from ExactTarget based on a folder

There are some interesting documentation issues with regards to ExactTarget. For instance, if you are programming against ExactTarget to retrieve all emails which belong to a specific folder, then you would expect that you can filter on the 'folder' attribute of an email. Apparently that doesn't work, but you will have to use the CategoryID attribute. But then again, filtering against the CategoryID doesn't filter the result on the folder.

According to ExactTarget:
There is a known issue with the MSCRM integrated accounts when using a Simple Filter Part on CategoryId via the API. You can use sfp.Property=”fkCategoryid’ and that should work. So you can above method or as a workaround, you can do the filtering in the client application (so your application once you retrieve all the data).

So far the theoretical information. Now the useful summary:


public ArrayList GetEmails(int folderId)
{
// Intialize variables
String requestID;
APIObject[] results;

// Create Filter
SimpleFilterPart sfp = new SimpleFilterPart();
sfp.Property = "fkCategoryID";
sfp.SimpleOperator = SimpleOperators.equals;
sfp.Value = new string[] { folderId.ToString() };

// Create RetrieveRequest
RetrieveRequest rr = new RetrieveRequest();
rr.QueryAllAccounts = true;
rr.QueryAllAccountsSpecified = true;
rr.ObjectType = "Email";
rr.Properties = new string[] { "Name", "ID", "CategoryID" };
rr.Filter = sfp;

// Execute RetrieveRequest
String status = _exactTarget.Retrieve(rr, out requestID, out results);

ArrayList alEmail = new ArrayList();
for (int i = 0; i < results.Length; i++)
{
Email email = (Email)results[i];
alEmail.Add(email);
}
return alEmail;
}



Error: End element 'Fault' from namespace '' expected

When connecting to a WSE 3.0 service, like ExactTarget, you might encounter this exception:


"End element 'Fault' from namespace 'http://www.w3.org/2003/05/soap-envelope' expected. Found element 'detail' from namespace ''

This does basically mean that the soap version of the client doesn't match the server. You might be able to change this in the web.config. For ExactTarget they require you to add a custom binding to the webconfig which specifies the soap version. This is the piece which ExactTarget asks developers to add to their web.config:

<customBinding>
<binding name="SoapBinding">
<security authenticationMode="UserNameOverTransport"></security>
<textMessageEncoding messageVersion="Soap12WSAddressingAugust2004"/>
<httpsTransport/>
/binding>
</customBinding>

In this binding you can easily change the messageVersion to "Soap11WSAddressingAugust2004". This will cause the message to be read correctly.