Aidan Garnish

Collaboration Not Competition

Downloading and uploading documents to SharePoint with metadata

I need to copy some documents from a SharePoint 2003 server to a MOSS 2007 server to make them available on our Internet sites. This is quite easily achieved using the object model but what I also need is to move the metadata associated with the document so I can filter correctly on the Internet site.

To do this I have started by creating a couple of methods. One to download the document from SharePoint to a file system folder and create an xml document that contains the metadata in the same folder. The second method uploads the document with it the metadata to a destination SharePoint document library.

private void DownloadDocumentFromLibrary()
  {
   try
   {
    //get site
    SPSite oSite = new SPSite("http://Localhost");
    
    using(oSite)
    {
     //get the rootSite
     SPWeb oWeb = oSite.RootWeb;
    
     SPList oList = oWeb.Lists["Shared Documents"];

     foreach(SPListItem oItem in oList.Items)
     {
      //get meta-data for the item
      string strXML = oItem.Xml.ToString();
      TextWriter tw = new StreamWriter("c:\\MyDownloadFolder\\"+oItem["Name"]+".xml");
      //write xml to file
      tw.WriteLine(strXML);
      tw.Close();

      //get document
      byte[] binFile = oItem.File.OpenBinary();
      System.IO.FileStream fstream = System.IO.File.Create("c:\\MyDownloadFolder\\" + oItem["Name"]); 
      
      //write document to folder
      fstream.Write(binFile, 0, binFile.Length);
     } 
    }
   }
   catch(Exception err)
   {
    Console.WriteLine(err.Message);
   }
  }

The second method then takes the downloaded files and uploads them to the Internet server document library. You may need to find a way to physically move the files across the network using FTP or something similar depending on where your Internet server is and what security surrounds it.

private void UploadDocumentFromFileShare()
  {  
   try
   {
    //get site
    SPSite oSite = new SPSite("
http://Localhost");

    // Process the list of files found in the directory.
    string [] fileEntries = Directory.GetFiles(@"c:\mydownloadfolder");
    foreach(string fileName in fileEntries)
    {
     //check file type to see if it is an xml document
     int iFileNameLength = fileName.Length;
     string strFileExtension = fileName.Substring(iFileNameLength-4,4);
    
     if(strFileExtension != ".xml")
     {
      //Load the local file into a stream
      FileStream stream = File.OpenRead(fileName);
      byte[] content = new byte[stream.Length];
 
      //Read the file into a byte array
      stream.Read(content,0,(int)stream.Length);
      stream.Close();

      using(oSite)
      {
       //Get the rootSite
       SPWeb oWeb = oSite.RootWeb;
       //Get the folder that should store the document
       SPFolder folder = oWeb.Folders["upload"];

       string[] strSplitFileName = fileName.Split('\\');
       string strFileName = strSplitFileName[strSplitFileName.Length-1];
       //add document to library
       folder.Files.Add(folder.Url + "/" + strFileName, content,true);
 
       //get list
       SPList oList = oWeb.Lists["upload"];
       //create query to get added document item
       SPQuery oQuery = new SPQuery();
       oQuery.Query = "<Where><Eq><FieldRef Name='FileLeafRef' /><Value Type='File'>"+strFileName+"</Value></Eq></Where>";
   
       SPListItemCollection oItems = oList.GetItems(oQuery);

       //get xml for document properties
       XmlTextReader reader = new XmlTextReader(fileName+".xml");
    
       XmlDocument doc = new XmlDocument();
       doc.Load(reader);
       XmlElement root = doc.DocumentElement;
       XmlNode node = root.Attributes.GetNamedItem("ows_Column1");
       string strColumn1= node.Value.ToString();

       foreach(SPListItem oItem in oItems)
       {
        string strTitle = "";
        strTitle = strFileName.Substring(0,strFileName.Length-4);
        oItem["Title"] = strTitle;
        oItem["Column1"] = strColumn1;

        oItem.Update();
       }
      }
     } 
    }
   }
   catch(Exception err)
   {
    Console.WriteLine(err.Message);
   }
  }

It is also possible to download attachments from an item using the following code:

 private void DownloadAttachmentFromItem()
  {
   //download attachment from a list to file system
   SPWeb web = new SPSite("
http://localhost").OpenWeb();
   
   //Open List
   SPList list = web.Lists["Doc List"];
      
   foreach(SPListItem oItem in list.Items)
   {
    //get meta-data for the item
    string strXML = oItem.Xml.ToString();
    TextWriter tw = new StreamWriter("c:\\MyDownloadFolder\\"+oItem["Title"]+".xml");
    tw.WriteLine(strXML);
    tw.Close();

    //Get the folder
    SPFolder folder = web.Folders["Lists"].SubFolders["Doc List"].SubFolders["Attachments"].SubFolders[oItem.ID.ToString()];
    //download attachment
    foreach(SPFile oFile in folder.Files)
    {
     byte[] binaryFile = oFile.OpenBinary();
     System.IO.FileStream fstream = System.IO.File.Create("c:\\MyDownloadFolder\\" + oItem["Title"]); 
     fstream.Write(binaryFile, 0, binaryFile.Length);
    }
   }
  }

Web part custom property - dropdown list

It isn't immediately obvious how to include a drop down list as a custom property on a web part. To do this you simply use an enum.

Eg.

//create enum

public enum ProductByEnum

{Type = 0,

Brand,

Chemistry};

//create get/set for the property

protected ProductByEnum productsBy;

[Personalizable(PersonalizationScope.User),WebBrowsable, WebDisplayName("Products By"),WebDescription("Use this property to change web part grouping")]public ProductByEnum ProductsBy

{

get { return productsBy; }

set { productsBy = value; }

}

A dropdown list will now appear in the miscellaneous section of your web part custom properties containing the enum values.

 

Creating XHTML compliant ASP.Net server controls

ASP.Net is great but many of the controls use table tags when they render. I am currently in the process of developing some Internet sites using MOSS 2007 which need to meet accessibility guidelines so these controls, in their out of the box state, are no use to me. We have tried applying the CSS adapters that get rid of the tables but this also had the effect of removing tables from the site settings screens making them difficult to use.

The solution we are going for is to create new controls that inherit from the base control. The new control then overrides the render event to remove the non-compliant tags and replaces them with compliant ones.

We are also using this method to add script to the master pages which removes table tags from web part zones etc.

DISCLAIMER - I have created a non-tabular control for the SPGridView. You could argue that the SPGridView presents tabular data so does not need any changes to the rendered HTML, you would probably be right but this example shows what can be done. :-)

using System;
using System.Collections.Generic;
using System.Text;
using System.Web;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
using System.Web.UI;
using System.Text.RegularExpressions;
using System.IO;

namespace XHTMLCompliantMOSSControls
{
    public class CompliantSPGridView : SPGridView
    {
        protected override void RenderContents(HtmlTextWriter output)
        {
            //get the rendered HTML
            StringBuilder sb = new StringBuilder();
            StringWriter sw = new StringWriter(sb);
            HtmlTextWriter hw = new HtmlTextWriter(sw);

            base.RenderContents(hw);
 
            //remove tables
            string str = sb.ToString();
          
            str = Regex.Replace(str, "<table[^>]*>", "<div class=\"mainSPGridView\">");
            str = Regex.Replace(str, "<tr>", "<div class=\"normalSPGridView\">");
            str = Regex.Replace(str, "<tr class=\"ms-alternating\">", "<div class=\"alternatingSPGridView\">");
            str = Regex.Replace(str, "</tr>", "</div>");
            str = Regex.Replace(str, "<td[^>]*>", "<div class=\"itemSPGridView\">");
            str = Regex.Replace(str, "</td>", "</div>");
            str = Regex.Replace(str, "<tr class=\"ms-viewheadertr\">", "<div class=\"headerSPGridView\">");
            str = Regex.Replace(str, "<th class=\"ms-vh2-nofilter ms-vh2-gridview\" scope=\"col\">", "<div class=\"itemSPGridView\">");
            str = Regex.Replace(str, "</th>", "</div>");
          
            output.Write(str);
        }
    }
}

If you then apply a bit of CSS using the classes that have been added it is possible to get it looking just like the tabular SPGridView.

This approach could be used with any server control to make it compliant with XHTML.

MOSS 2007 end user training is released

Microsoft have released end user training materials for MOSS 2007. This can be downloaded as a stand alone version that can be run from the desktop or as a version that can be installed and distributed as a set of SharePoint sites. The advantage of installing it to SharePoint is that as a trainer you can direct people towards content and get reports on who has successfully completed which modules.

Download the training here

Setting custom properties on web parts

When creating web parts it is often useful to allow users to supply some additional parameters to the web part. This can be done by adding custom attributes which can then be set by the user and used in your code.

To do this use the following syntax:

   [Personalizable(PersonalizationScope.User),
         WebBrowsable, WebDisplayName("Products List"),
         WebDescription("Use this property to change list used to retrive products from")]
        public string ProductList
        {
            get { return strProductsList; }
            set { strProductsList = value; }
        }

In this case, rather than hard coding the name of the products list, the user is able to specify which list to use.

Using SPQuery and CAML to filter and order

To get a collection of items from a list you can use SPQuery and CAML to filter and order the selected list:

            SPSite oSite = SPContext.Current.Site;
            SPWeb oWeb = oSite.OpenWeb();
            SPList oList = oWeb.Lists["ListName"];
            SPQuery oQuery = new SPQuery();
            oQuery.Query = "<OrderBy><FieldRef Name='Title' /></OrderBy><Where>"
                + "<FieldRef Name='Title' /><Value Type='Text'></Value></Where>";
            SPListItemCollection oItems = oList.GetItems(oQuery);

To filter on date use the following to get any item that has been modified in the last 7 days:

string dateString = Microsoft.SharePoint.Utilities.
SPUtility.CreateISO8601DateTimeFromSystemDateTime(DateTime.Today.AddDays(-7));

oQuery.Query = <Where><Geq><FieldRef Name=\"Modified\"/><Value Type=\"DateTime\">"+ dateString+"</Value></Geq></Where>