Aidan Garnish

Collaboration Not Competition

Creating MOSS 2007 features - a reference

Some useful links for creating MOSS 2007 features that I have found helpful:

Creating Features

Deploying Features

Removing Features

Creating new site permissions

When using SPS 2003 some of the business coordinators requested a way to allow users to manage permissions whilst not being able to create a new sub-site. This was not possible out of the box as the administrator role allowed the user to create new sub-sites as well as manage permissions.

 In MOSS 2007 this is now possible by ceating a new site permission.

   1. Go to Site Action Menu.
   2. On that menu, go to Site Settings.
   3. Choose People and Groups
   4. On the quick launch bar, there is a link to Site Permissions.
   5. On the Settings menu, select Permission Levels.
   6. On the Permission Level page, select new to create a new permission level.
   7. Add the permission that you want (in this case, Manage Permissions but not Create Subsites).
   8. Name it Permissions Manager
   9. You can now assign users to that permission level 

 

Retrieving items from a MOSS 2007 list using web services

A quick code snippet to remind me how to access a list using the in built web services: 

                MOSSWebRef.Lists lists = new MOSSWebRef.Lists();
               
                lists.Credentials = new System.Net.NetworkCredential("username", "password");

                string exportList = "NameOfList";

                XmlNode node = lists.GetListItems(
                    exportList,                   
                    string.Empty,
                    null,
                    null,
                    ConfigurationManager.AppSettings["MaxRecords"].ToString(),
                    null);

                XmlTextReader xr = new XmlTextReader(node.OuterXml, XmlNodeType.Element, null);          
                string strValue = "";
                while (xr.Read())
                {
                    if (xr.ReadToFollowing("z:row"))
                    {
                         if (xr["ows_nodeName"] != null)
                        {
                           strValue = xr["ows_nodeName"].ToString();
                        }

                    }

               }

MOSS 2007 Content Deployment

MOSS 2007 provides a mechanism to deploy content from one farm to another. This is most useful when you are using the publishing site template to produce Internet sites and allows you to set up deployment schedules for the content that web editors are producing.

First you need to set up a path that tells MOSS what to deploy and where to deploy it to. A job is then created that uses the path and allows you to select specific content and how often it should be deployed.

The first time I ran a job I got an error saying that some lists and libraries already existed and the job failed. This is because you have to deploy to a site collection that was created using the blank site template ie. it has no template at all.

After a site has been deployed for the first time if a site column is removed from a content type then the same column on the destination site collection will also need to be removed otherwise the following error is reported and the deployment fails:

Site columns which are included in content types or on lists cannot be deleted. Please remove all instances of this site column prior to deleting it.

For a more comprehensive list of potential issues see - http://soerennielsen.wordpress.com/2007/06/19/the-long-path-to-content-deployment/

Also see: http://www.sharepointnutsandbolts.com/2008/04/recipe-for-successful-use-of-content.html

Space in column names

When referencing columns programatically or in CAML that have a space in the name replace the spaces with _x0020_

For example "End Use" becomes "End_x0020_Use"

The format required to access non-alphanumeric column titles is this (IN LOWER CASE):

_x00[the hex code]_

Here are a few more examples:

Char         Code 

[space]      20      i.e. _x0020_
<            3C      i.e. _x003c_
>            3E
#            23
%            25
{            7B
}            7D
|            7C
\            5C
^            5E
~            7E
[            5B
]            5D
`            60
;            3B
/            2F
?            3F
:            3A
@            40
=            3D
&            26
$            24

To reference a column called “e-mail” you would need e_x002d_mail.

This page has a list of all the Hex Codes of some other symbols:http://www.asciitable.com/

Hit enter, fire a web part event the easy way

I have a custom web part search box that has a button next to it that directs the user to the search results page for the query that has been entered. What I also want to do is have that same code fire when the user presses enter. With the asp:textbox control this didn't look as if it was going to be very simple as the text changed event does not fire on every key press. Instead it fires when the control loses focus. 

Fortunately pressing enter causes the control to lose focus so it turns out that it is simple as calling the executequery method in the text changed event like this:

 void txtSearch_TextChanged(object sender, EventArgs e)
        {
              btnExecuteQuery_OnClick(this, e);
        }

Now, whenever a user hits enter the execute query event fires. Testing shows that clicking away from the textbox on a link or tabbing away from the textbox does not fire the event. Result!

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.