Thursday, 30 October 2008

Uploading Files to MOSS 2007 via the SharePoint Object Model

I've blogged on a similar topic about a year ago, but the previous post was using the Copy.asmx web service directly (see http://ddkonline.blogspot.com/2008/01/uploading-files-to-moss-2007-via.html). Here is the equivalent using the SharePoint object model when you have the luxury of being hosted on the WSS/SharePoint box. One thing to note is that you will chew up memory if you don't dispose your SPWeb and SPSite objects correctly (either via using or by doing explicit Dispose() calls) - you cannot rely on .NET Framework garbage collection to clean them up for you. See inline comments below on this.

Background: The current Management App I'm working on has an IIS application hosted in Atlanta as the frontend to a Windows SharePoint Services (WSS) document store hosted in Australia. The problem with this was that the upload would do a massive round-trip overseas and back again - so a large file (say 20MB would take well over an hour). The workaround to this was to host the page as an IFrame in our application and to host it on a WSS Server so uploads of large files could be done directly to WSS rather than via the IIS Server overseas. I used the following code in the hosted page to perform the uploads using the SharePoint 2007 object model.

/// <summary>
/// Upload documents using MOSS Object Model Directly, updates database with document information
/// After the upload of each document is complete.
/// </summary>
/// <param name="projectId"></param>
/// <param name="sourceStream"></param>
/// <param name="fileName"></param>
/// <returns></returns>
public static VoidResponse UploadDocument(UploadedFile uploadedFile,
string projectNumber, int projectId, int activityId, int documentTypeLookupId,
int constructionAuthorisationId, string subfolderPath)
{
//Core document information to be passed into MOSS
//and into the .NET Web Service to update the database
DocumentDto newDocument = new DocumentDto();
newDocument.ProjectNumber = projectNumber;
newDocument.Name = uploadedFile.GetName();
newDocument.SharepointListName = ConstructSharepointListName(newDocument.ProjectNumber);
//Snapshot path as combination of both the
newDocument.SharepointSnapshotPath = CurrentSnapshotPath;

if (string.IsNullOrEmpty(newDocument.SharepointFileRelativePath))
{
newDocument.SharepointFileRelativePath = newDocument.Name;
}

string queryOptions = FormatHelper.GetQueryOptions(projectNumber, CurrentSnapshotPath, subfolderPath);

//To Allow return of uploaded file details
VoidResponse response = new VoidResponse();

//Housing SPSite and SPWeb in using blocks so they are disposed correctly as per best practices
//in http://msdn.microsoft.com/en-us/library/aa973248.aspx
using (SPSite site = new SPSite(SharePointParameter.SharePointSiteUrl))
{
using (SPWeb web = site.OpenWeb()) //The SPSite already has the site url value.
{
try
{
web.AllowUnsafeUpdates = true; //Using AllowUnsafeUpdates because we are creating an SPWeb -
//otherwise will result in exception
//as per http://hristopavlov.wordpress.com/2008/05/16/what-you-need-to-know-about-allowunsafeupdates/
SPList list = web.Lists[newDocument.SharepointListName];

SPFileCollection fileCollection = null;

//Using query options as recommended by
//http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.splist.aspx,
//same as the Web Services do in the LL framework.
SPQuery spQuery = new SPQuery();
spQuery.Query = queryOptions;
SPListItemCollection folderCollection = list.GetItems(spQuery);

//Folders are really just SPListItems with type of FilSPFileSystemObjectType.Folder
//Defensive: Folder not found exception
if (folderCollection.Count == 0) //Error condition - folder not found
throw new ConfigurationErrorsException(string.Format(Messages.Pcp_MOSS_FolderNotFoundError,
newDocument.SharepointSnapshotPath));

fileCollection = folderCollection[0].Folder.Files;

//Only perform duplicate check when it is turned on in configuration settings.
if (SharePointParameter.CheckForDuplicateFilesInMOSS)
{
//Check if file doesn't already exist.
foreach (SPFile file in fileCollection)
{
if (file.Name == newDocument.Name)
{
//Duplicate detected
response.IsSuccessful = false;
response.Messages.Add(new Message(string.Format(Messages.Pcp_DuplicateFileDetected,
file.Name)));
return response;
}
}
}

Stream fileStream = uploadedFile.InputStream;
byte[] contents = new byte[fileStream.Length];
fileStream.Read(contents, 0, (int)fileStream.Length);
fileStream.Close();

//Upload document itself. This will also give us the new Id to store in the database.
SPFile currentFile = null;

currentFile = fileCollection.Add(newDocument.Name, contents);
System.Security.Principal.WindowsImpersonationContext impersonationContext = null;

try
{
if (GetUseCurrentUserCredentialsForMOSSFlag()) //Impersonate if uploading document
{
impersonationContext =
((System.Security.Principal.WindowsIdentity)
HttpContext.Current.User.Identity).Impersonate();
}

//Upload file details to main PCPData database
PcpDataService.PcpDataService pcpDataService = new PcpDataService.PcpDataService();
pcpDataService.Url = DDKOnline.WssHostedWeb.Shared.SystemParameter.DDKOnlineDataServiceUrl;
pcpDataService.AddProjectDocument(projectId, activityId, constructionAuthorisationId,
currentFile.Item.ID.ToString(), newDocument.SharepointListName,
newDocument.SharepointSnapshotPath, newDocument.SharepointFileRelativePath,
documentTypeLookupId, newDocument.Name);
}
finally
{
if (GetUseCurrentUserCredentialsForMOSSFlag() && impersonationContext != null)
{
impersonationContext.Undo();
}
}
//Successful upload completed.
response.IsSuccessful = true;
response.Messages.Add(new Message(Messages.Pcp_SharePointUploadComplete));
}
catch (SPException spException)
{
//Error ocurred during upload
response.Errors.Add(new DDKOnline.Framework.Common.Response.Error(spException.Message));

response.IsSuccessful = false;
}
catch (Exception ex)
{
response.Errors.Add(
new Error(string.Format(Messages.Pcp_SharePointAccessFailed)));
ExceptionPolicy.HandleException(ex, SERVICE_EXCEPTION_POLICY);
response.IsSuccessful = false;
}
finally
{
if (web != null) web.AllowUnsafeUpdates = false;
}
}
}
return response;
}


/// <summary>
/// Generate query options to support storage within subfolders of current list
/// </summary>
public class FormatHelper
{
internal static string GetQueryOptions(string projectNumber, string CurrentSnapshotPath, string subFolderPath)
{
string folderPath = VirtualPathUtility.RemoveTrailingSlash(string.Format("{0}/{1}/{2}", projectNumber, CurrentSnapshotPath, subFolderPath));
string queryOptions = string.Format("<Query/><QueryOptions><Folder>{0}</Folder></QueryOptions>", folderPath);
return queryOptions;
}
}

No comments: