Gracefully Uploading to SharePoint 2007 From BizTalk Server 2006 R1

Scenario: I want to allow BizTalk Server 2006 (R1) to send XML (InfoPath forms) to a MOSS 2007 document library without resorting to hacks.

Resolution: I can’t use the out-of-the-box BizTalk SharePoint adapter (only BizTalk Server 2006 R2 works natively with MOSS 2007) so I decided to utilize the available SharePoint web services to upload my file.  I wrote a wrapper web service to (a) encapsulate some additional logic (b) shield the BizTalk developer from understanding SharePoint services and (c) require no usage of the SharePoint 2007 object model.

What did this solution look like?  I decided to use the CopyIntoItems method available on the SharePoint Copy web service.  This allows you to send a byte array of data to a document library and have it appear as a new document.  To hit the WSDL for this service, you’d go to:

http://<your sharepoint base url>/sites/<site name>/_vti_bin/Copy.asmx

Here you’ll see the CopyIntoItems operation.  My wrapper service starts with a couple “using” statements …

using System.Net;   //for NetworkCredentials object
using System.Text;  //for encoding bytes
using System.IO;    //for stringwriter
using System.Xml;

Next I have my wrapper operation …

[WebMethod]
public string UploadXmlToSharePoint(string docToUpload,
string siteRoot,
string docLibrary,
string fileName,
string userName,
string password,
string domain)

I’m taking the XML document input as a string in order to make the schema easier in BizTalk, and, ensure I don’t lose my InfoPath processing instructions when transporting over the wire (which seemed to be happening when I used an XmlDocument type input parameter).  Also note that I’m taking in a user/password/domain combo.  This is to allow for reuse later down the line.  The account used to call the SharePoint service MUST be a site administrator, so I’m making it an explicit parameter. The first thing I do inside my operation is build up the destination Uri based on the input parameters.

//build full destination Url
string destinationPath = siteRoot + "/" + docLibrary + "/" + fileName;

Next I have to take the input string and convert it to the byte array required by the MOSS web service …

//convert string to byte array
byte[] fileIn = ConvertDocToBytes(docToUpload);
...
private byte[] ConvertDocToBytes(string xmlString)
    {
        ASCIIEncoding encoding = new ASCIIEncoding();

        return encoding.GetBytes(xmlString);
    }

Now I need to instantiate some values needed by the MOSS service. First we have the “result” object which conveys the state of the copy transaction. Then I have a “FieldInformation” array which can be used to pass in specific field values. Note that you CANNOT pass in a null value here, or else you get a cryptic error when calling the service. You can make it blank, but don’t use a null parameter in its place. Finally, I create a destination Uri array.

//holds MOSS service response values
SharePointSvc.CopyResult[] results;

//required fieldinformation array
SharePointSvc.FieldInformation fieldInfo =
    new SharePointSvc.FieldInformation();
SharePointSvc.FieldInformation[] fieldInfoArray = { fieldInfo };

//destination url (notice that it's an array, meaning
//multiple sites COULD be targeted
string[] destUri = { destinationPath };

Now I can actually call this puppy. After instantiating the web service proxy class (generated by the Add Web Reference command), I need to provide explicit credentials.

//create instance of web service proxy
SharePointSvc.Copy copy = new SharePointSvc.Copy();

//pass valid credentials
copy.Credentials = new NetworkCredential(userName, password, domain);

//call primary operation;  sourceUri, doesn't matter here
copy.CopyIntoItems(
     "http://none",
     destUri,
     fieldInfoArray,
     fileIn,
     out results);

The last step is to actually check the “result” object for errors and return any errors back to the caller.

//check for error and return final result
if (results[0].ErrorMessage != null)
  {
    return "Error: " + results[0].ErrorMessage;
  }
  else
  {
    return "Success";
  }

Sweet.  After building and deploying this service, I can call it from any client, BizTalk (2004/06) included.  I’ll obviously want to securely store my credentials (using Enterprise Single Sign On) and not embed those in my client directly.

So if I call my service, and pass in a plain old XML file, it shows up in my document library as expected.

Now, if I send an InfoPath document to a Forms Library set up with an InfoPath template, the result is this …

Nice! The document is recognized as an InfoPath document (see the icon), and, the promoted columns are properly loaded.

So, if you’re interested in a fairly easy way to programmatically upload documents to a MOSS 2007 library, without having to use the SharePoint object model or BizTalk adapter, this web service might just work for you.

Technorati Tags: ,



Categories: BizTalk, SharePoint, SOA

10 replies

  1. Hi Richard,

    Nice article, I havent used the sharepoint integration before but this looks good. The one thing I did wonder however is can you flow the credentials of the host for the BizTalk send port to your web service, or from the client through BizTalk and to sharepoint.

    Think this would be nicer than passing username, pwd etc in the parameters. This is something ive looked at before with the WSE adapter but there isnt much guidance around this within the community.

  2. Hey Mike,

    I’d like to flow host credentials, but then again, you may want more granular security on the SharePoint site without having to create a lot of different hosts.

  3. Hi Richard

    In your example, the simpleXmlFile is uploaded without copied any field to sharepoint. but the simpleInfopathFile is uploaded with all the fields. I want to upload all the field from simpleXmlFile(i,e FirstName, LastName, DOB) but not from simpleInfopathFile. how can i do this. i am keep trying but it only upload the simpleXmlFile , not the fields. Appreciate your help. Thanks.

  4. Hi Richard, don’t know if you’re still paying any attention to this post, but i just implemented a solution with great help from your post (with a few amendments) using MOSS 2010 and BTS 2010. Is there any way you can think of to send the bytes directly from BTS to MOSS without packaging the sender in a separate WCF (or other) applicaiton?

  5. Hi Richard,

    I’ve more or less done it. Thanks for the answer.

    In 2010, you can actually just consume the services!

    I consumed the service directly from http://SharePointServerlocation/_vti_bin/Copy.asmx. (The CopyIntoItems operation). I then used a series of simple transforms.

    First I converted the entire initial xml message into a string and stored it in a Cdata node, and I set the file name node using a message assignment shape and some variables inside an orchestration (it was needed as part of a complex application).

    I then mapped the final result into the CopyIntotitems operation of the web service schema (I mapped the string message into a byte array using inline c# in the map, used concatenation to set the destination uris, used value mapping for the http//none as based on your post, and technically, it should work.

    I am still in the process of finalising as I’m getting a bad request message from SP at the moment, but having checked each stage of my transformation and everything looks fine except the actual byte array, I am sure the process should work technically, I just need to check that the byte array is fine somehow. Still haven’t figured that bit out. I can just see System.Byte[] in the node at the moment.

    I tried your approach and that worked like a charm too by the way, once I got the SP permissions issues out of the way.

  6. The problem is not with the byte array… It’s the Fieldinformation set… Just can’t seem to figure out how to map it correctly. Everything seems to either give a bad request error, or a soap error. If I leave the any of the two required nodes (Type and id) blank, I get a soap error. If I nil the whole fieldinformaiton node, I get a bad request error, If I add a new guid, a converted (from string) guid or a string in guid format, I get a bad request…

    I have used the GetItem call to retrieve the values based on a form I published directly, and still, using those values is fine for the type, but nothing seems to work for the guid. aaargh! If you have any ideas on something I am missing, pls let me know.

    Basically, I would like to use a BTS map to directly mimic the way you fed in the fieldinformaiton in your c# code like below …

    //required fieldinformation array
    SharePointSvc.FieldInformation fieldInfo =
    new SharePointSvc.FieldInformation();
    SharePointSvc.FieldInformation[] fieldInfoArray = { fieldInfo };

  7. Sometimes we can be so engrossed in complexity that the simple eludes us… I have solved the issue.

    For anyone who comes across this, map the value 00000000-0000-0000-0000-000000000000 to the id, and the value “Invalid” to the Type. Ignore the other fieldinformation attributes as they are not required. Make sure your byte stream converts properly and you’re all good.

  8. Hi
    I’m facing much the same issue at the moment. Trying to upload files from BizTalk 2010 to Sharepoint 2013… I’m stuck on the actual bindings and adapter used on the send port. Just can’t seem to get it right.
    If you are still paying attention to this post I would very much like some additional information from Stephen on how you set your send port up.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: