jump to navigation

Be very, very careful when you use SPSite.OpenWeb()! September 27, 2009

Posted by jasear in Uncategorized.
add a comment

I recently came across what can only be described as a shocking and dangerous bug in the SharePoint API.

According to the MSDN documentation the SPSite.OpenWeb() method:

“Returns the site that is associated with the URL that is used in an SPSite constructor.”

Below is an example of how it is very commonly used:

using (SPSite site = new SPSite("http://www.myserver.com/parentSite/childSite"))
{
using (SPWeb web = site.OpenWeb())
{
//Do your stuff
}
}

Going by the MSDN documentation the code above should return the web site located at http://www.myserver.com/parentSite/childSite. This is further confirmed by the following statement:

“When used in conjunction with an SPSite constructor, the OpenWeb method returns the lowest-level site specified by the URL that is passed as parameter for the constructor.”

This is all true if the site located at the specified url exists. But what if it doesnt exist?

Consider a scenario where an SPWeb with the url http://www.myserver.com/parentSite exists but an SPWeb with the url http://www.myserver.com/parentSite/childSite does not exist. In this scenario you would expect the code above to throw an exception, however, it does no such thing instead it ends up opening a completely different SPWeb. In this specific scenario it returns an SPWeb with the url http://www.myserver.com/parentSite. If an SPWeb didnt exist at this url as well then it would have returned the RootWeb! (i.e. http://www.myserver.com if it existed)

In other words you would end up opening and performing actions on a totally different SPWeb! As you can imagine this can have very dangerous consequences as I only very recently found!

How to change the Author of a Document that is stored in a Document Library? April 20, 2009

Posted by jasear in MOSS 2007.
Tags: , , , , , , ,
1 comment so far

Apparently there is’nt a straight forward way to achieve this. Below is how you can do it though:

SPList list = web.Lists["myList"];

list.Fields["Author"].ReadOnlyField = false;

SPListItem item = list.Items.Add();

item["Author"] = “value”;

item.SystemUpdate(false);

list.Fields["Author"].ReadOnlyField = true;

WebPartPages: Programmatically adding a new web part page April 5, 2009

Posted by jasear in C#, MOSS 2007.
Tags: , , , , , , ,
1 comment so far

How do you add a WebPartPage or a BasicPage programmatically?

WebPartPages are stored in document libraries. It is simply a matter of adding the page to the document library you want. However, if you need to add a WebPartPage programmatically the same way SharePoint allows you to do via the UI then you can use the following method:


private void AddWebPartPage(string fileTitle, SPWeb web, SPList list, int webPartPageTemplate, string pageType, string folder)
{
    string postInformation =
    "<?xml version="1.0" encoding="UTF-8"?>" +
    "<Method>" +
    "<SetList Scope="Request">" + list.ID + "</SetList>" +
    "<SetVar Name="ID">New</SetVar>" +
    "<SetVar Name="Cmd">NewWebPage</SetVar>" +
    "<SetVar Name="Type">" + pageType + "</SetVar>" +
    "<SetVar Name="WebPartPageTemplate">" + webPartPageTemplate + "</SetVar>" +
    "<SetVar Name="Title">" + fileTitle + "</SetVar>" +
    "<SetVar Name="Overwrite">true</SetVar>" +
    "</Method>";
    string processBatch = web.ProcessBatchData(postInformation);
    if (processBatch.Equals("<Result ID="" Code="0">rn</Result>n"))
    {
        SPFile file = web.GetFile(list.RootFolder.Url + "/" + fileTitle + ".aspx");
        //if page was in subfolder then move it there
        if (!String.IsNullOrEmpty(folder))
        {
            file.MoveTo(fileTitle,
true);
        }
    }
}

Below are the WebPartPage templates you can specify:

  1. Full Page, Vertical
  2. Header, Footer, 3 Columns
  3. Header, Left Column, Body
  4. Header, Right Column, Body
  5. Header, Footer, 2 Columns, 4 Rows
  6. Header, Footer, 4 Columns, Top Row
  7. Left Column, Header, Footer, Top Row, 3 Columns
  8. Right Column, Header, Footer, Top Row, 3 Columns

Type could have the following possible values:

  1. WebPartPage
  2. BasicPage

For further details click here.

ListViewWebPart: Programmatically setting the ToolbarType property April 5, 2009

Posted by jasear in MOSS 2007.
Tags: , , , , , , ,
1 comment so far

While working on a migration project we came across a requirement whereby ListViewWebPart’s had to be added to a page programmatically. The ListViewWebPart class has a property called toolbartype. Setting the toolbartype property sounds simple enough. I mean how difficult can it be to set a property?

The problem is that it is a read only property. We thought maybe we can set it via reflection. After searching a bit on google we found a couple of suggestions on how it could be done (most of them were solutions on how to set it to none). One method seemed to have solved our problem until we installed the latest infrastructure updates. Then it completely stopped working. However luckily for us Tony Stegeman and Brian Farhill came up with a solution.

Below you can see the complete solution that worked for us:


private static void SetToolbarType(SPView spView, string toolBarType)
{
    spView.GetType().InvokeMember(
"EnsureFullBlownXmlDocument",
    BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMethod,
    null, spView, null, System.Globalization.CultureInfo.CurrentCulture);
    PropertyInfo nodeProp = spView.GetType().GetProperty("Node",
    BindingFlags.NonPublic | BindingFlags.Instance);
    XmlNode node = nodeProp.GetValue(spView, null) as XmlNode;
    XmlNode toolbarNode = node.SelectSingleNode("Toolbar");
    if (toolbarNode != null)
    {
        toolbarNode.Attributes[
"Type"].Value = toolBarType;
        // If the toolbartype is Freeform (i.e. Summary Toolbar) then we need to manually 
        // add some CAML to get it to work.
        if (String.Compare(toolBarType, "Freeform", true, System.Globalization.CultureInfo.InvariantCulture) == 0)
        {
            string newItemString = "";
            XmlAttribute positionNode = toolbarNode.OwnerDocument.CreateAttribute("Position");
            positionNode.Value =
"After";
            toolbarNode.Attributes.Append(positionNode);
            switch (spView.ParentList.BaseTemplate)
            {
                case SPListTemplateType.Announcements:
                    newItemString =
"announcement";
                    break;
                case SPListTemplateType.Events:
                    newItemString =
"event";
                    break;
                case SPListTemplateType.Tasks:
                    newItemString =
"task";
                    break;
                case SPListTemplateType.DiscussionBoard:
                    newItemString =
"discussion";
                    break;
                case SPListTemplateType.Links:
                    newItemString =
"link";
                    break;
                case SPListTemplateType.GenericList:
                    newItemString =
"item";
                    break;
                case SPListTemplateType.DocumentLibrary:
                    newItemString =
"document";
                    break;
                default:
                    newItemString =
"item";
                    break;
            }
            if (spView.ParentList.BaseType == SPBaseType.DocumentLibrary)
            {
                newItemString =
"document";
            }
            // Add the CAML
            toolbarNode.InnerXml = @"<IfHasRights><RightsChoices><RightsGroup PermAddListItems=""required"" /></RightsChoices><Then><HTML><![CDATA[ <table width=100% cellpadding=0 cellspacing=0 border=0 > <tr> <td colspan=""2"" class=""ms-partline""><IMG src=""/_layouts/images/blank.gif"" width=1 height=1 alt=""""></td> </tr> <tr> <td class=""ms-addnew"" style=""padding-bottom: 3px""> <img src=""/_layouts/images/rect.gif"" alt="""">&nbsp;<a class=""ms-addnew"" ID=""idAddNewItem"" href=""]]></HTML><URL Cmd=""New"" /><HTML><![CDATA["" ONCLICK=""javascript:NewItem(']]></HTML><URL Cmd=""New"" /><HTML><![CDATA[', true);javascript:return false;"" target=""_self"">]]></HTML><HTML>Add new " + newItemString + @"</HTML><HTML><![CDATA[</a> </td> </tr> <tr><td><IMG src=""/_layouts/images/blank.gif"" width=1 height=5 alt=""""></td></tr> </table>]]></HTML></Then></IfHasRights>";
        }
        spView.Update();
}

Programmatically getting the SPField object you just added April 3, 2009

Posted by jasear in Uncategorized.
Tags: , , , , , , ,
1 comment so far

When you add an SPField to an SPList programmatically using the SharePoint Object Model you can get the SPField object representing the newly added field via SPList.Fields[string displayName]. But this assumes that no other field with the same display name as the newly added SPField exists in the SPList.

The work around is listed below:

 

Guid fieldGuid = Guid.NewGuid();
string newFieldCAML =
 "<Field Type="Text" DisplayName="MyNewField" ID="+fieldGuid+"/>";
splist.Fields.AddFieldAsXml(newFieldCAML, false, SPAddFieldOptions.AddFieldInternalNameHint);
SPField newField = spList.Fields[fieldGuid];

You can use the AddFieldAsXml to add the new SPField. It takes a CAML string as a parameter in which you can specify your own Guid. Later you can use this Guid to retrieve the SPField you just added.

Meeting Workspace: How to programatically add tabs April 3, 2009

Posted by jasear in MOSS 2007, Uncategorized.
Tags: , , , , , , ,
1 comment so far

When you add a page via the UI it adds it as a WebPartPage to a hidden list called “Workspace Pages”. It also does something internally to create the Tab because simply progammatically adding the WebPartPage to the hidden list doesnt seem to work.

After spending a lot of time investigating this issue I finally found a solution. The solution is listed below:
 

string newPage = string.Empty;
Type cmType = typeof(SPMeeting);
SPMeeting mtg = (SPMeeting)
Activator.CreateInstance(cmType,
BindingFlags.NonPublic | BindingFlags.Instance,
null, null, System.Globalization.CultureInfo.CurrentCulture,
null);
Type t = mtg.GetType();
t.InvokeMember(
"m_Web", BindingFlags.NonPublic
|
BindingFlags.Instance | BindingFlags.SetField,
null, mtg, new Object[] { web });
mtg.AddPage(
"JunkYard", 1, out newPage);

The SPMeeting class has a method “AddPage”. This class has no public constructors therefore you cannot instantiate it the normal way. Once you instantiate it you need to set, via reflection, m_Web which is a private member and holds the SPWeb object. The final step is to call the AddPage method passing it the name of the page you want to add, an instanceid (not sure what this is), and an out string parameter.

SharePoint issues and work arounds! April 3, 2009

Posted by jasear in MOSS 2007.
Tags: , , , , , , ,
add a comment

Cannot set Internal Name of an SPField:

The InternalName property is read only. According to the documentation the StaticName property of an SPField should:

“Get or set the internal name of the field. “

It doesnt! We had a requirement where we needed to set the InternalName of an SPField. We tried everything! various methods, reflection you name it but to no avail!

Trouble with getting SPField object after programmatically adding it:

There are a few ways you can add an SPField to a List.:

  1. SPField.Add() has three overloads
  2. SPField.CreateNewField()
  3. AddFieldAsXml() has 2 overloads

Using SPField.Add you can add an SPField by providing the DisplayName, Type and a bool specifying whether the field is required or optional. But how do you get a reference to the SPField you just added when you use this method?

You could use:

  1. SPField[int index] but you dont know the index
  2. SPField[Guid id] but you dont know the Guid
  3. SPField[string DisplayName] you do know the DisplayName but what if there is another field with the same DisplayName since there is no restriction on having more than 1 fields with the same DisplayName?
  4. SPField.GetField(string displayName) same problem as above
  5. SPField.GetFieldByInternalName(string internalName) but we dont know the internal name (linked with the issue mentioned above. This is why we needed to be able to set the InternalName).

If there was a GetFieldByStaticName it could have solved our problem. One of the overloads of the SPField.Add() method takes an SPField. We thought we would instantiate and setup our SPField object, pass it to that method to add to the List and we will then have a proper reference of our newly added SPField object right? No wrong! The SPField object is just a means to send in the data required to set up the new SPField.

It would be useful to have a method that will add an SPField and return an SPField object representing the SPField you just added. However in the absence of this there is a way around this whole issue. The way around it is to use the AddFieldAsXml() method. It takes a string argument representing the CAML for the SPField you are trying to add. In that you can specify the Guid you want for your new field. Later you can retrieve your field by using SPField[Guid id].

You can find the solution here.

Basic issue all over the place in the SharePoint Object Model:

You are adding an SPList programmatically. You want to find out whether an SPList with that name already exists. What do you do? You could try:

  1. SPList[string listName]
  2. SPWeb.GetList(string listName)
  3. SPWeb.GetListFromUrl(string pageUrl)

If the list exists it will get you the SPList object representing that list. If it doesnt it throws an exception! So if you dont have a try catch block when checking if a List with a particular name already exists your application will crash!

I think there is a need for a method which will return a particular value based on whether an SPList with that name exists (could return a bool, or the SPList object if it exists or null if it doesnt).

When adding an SPList you need to check whether an SPList with the same name exists or not because if it does then the SPList.Add() method will throw an exception. In other words it all works perfectly if you add a List and by chance the name is unique. If it is not unique then an error will be thrown and must be caught to prevent your application from crashing.  Surely, you should be able to check whether a list exists without having to catch an error!

This issue manifests itself in a lot of places in the object model. You have the same problem when trying to add an SPField, SPView e.t.c.

While programmatically adding a ListViewWebPart you cannot set the ToolbarType property:

There is a property called ToolbarType on a ListViewWebPart which is read only. There is a way around this issue which involves using reflection.

The work around for this is discussed here.

No clear way of adding Tabs to a Meeting Workspace Site:

When you create a site from the OTB Meeting workspace site definitions you are able to add pages to the site. The pages are stored in a hidden list called Workspace pages. When you add a page it adds a tab for that page at the top. However adding a WebPart page to this list programmatically doesnt solve the problem as it will not add the tab. There is a very painful way around this and it involves Reflection!

The solution is here.

Adding a WebPartPage to an SPList sub folder:

There is a way of adding it to the root folder but there doesnt seem to be a direct way of adding it to a sub folder. Again there is a way around this. You add the WebPartPage to the root folder of the SPList and then move it to the sub folder you actually wanted to add it to.

You can find a solution for this here.

Cannot get WebParts from a MySite (SPS 2003):

Not sure if its the same on MOSS 2007 but when we tried to get WebParts from the default page on a Personal Site (unless its your Personal Site) you wont be able to get an instance of the WebParts that exist on the page. Raised this issue with Microsoft who said this was for security reasons. However no such restrictions when trying to get data from the lists on the Personal Site :) Run with elevated priviledges makes no difference either. The only way around this is to impersonate the user whose Personal Site it is or just give up!

.NET WebControl: TextBox with a countdown counter April 3, 2009

Posted by jasear in ASP.NET, C#.
Tags: , , , , , , ,
add a comment

Came across a need for a TextBox control with a countdown counter. Since I couldnt find one that would fit my requirements I decided to write one up.

It is in a very basic format at the moment and there is quite a lot that can be done with it. Feel free to improve, enhance, extend it.

Here is a screen shot of what it looks like:

TextBox with a countdown counter.

TextBox with a countdown counter.

It is a very handy control to have.  You can use it in all three of the TextBox mode’s i.e. SingleLine, MultiLine, Password.

You can get the control and source code from here:

http://textboxwithcounter.codeplex.com/

Below is an example of how you can use this WebControl:

protected void Page_Load(object sender, EventArgs e)
{
    TextBoxWithCounter tbwCounter =  
    new TextBoxWithCounter(200, 5, 25, TextBoxMode.MultiLine);
    this.Form.Controls.Add(tbWcounter);
}

You update a PageLayout, re-deploy it but you cant see your changes April 2, 2009

Posted by jasear in MOSS 2007.
Tags: , , , , , , ,
add a comment

Have you had this problem? Well you are not alone. While working on a News Publishing site that utilised the built in MOSS 2007 Publishing Infrastructure Feature we too came across this issue.

My colleague, who was tasked with doing the PageLayout part, had suffered for weeks with this as every time he made a change to the PageLayout and re-deployed it, he then had to delete the Site Collection and re-create a brand new Site Collection to pick up the effects of the changes he had made.

Deleting the SPWeb (Site) will not make a difference. The reason for this is that the PageLayout .aspx files are stored in an SPList called “Master Page Gallery” which exists in the RootWeb of the Site Collection.

For some reason the PageLayout pages are “customised” i.e. stored in the Database. So regardless of what changes you make to the file that is stored on the file system it wont take effect because SharePoint is getting the file from the Database.

Luckily there is a way around this which is to get the instance of the file from the “Master Page Gallery” and call the RevertContentStream() method on it. Calling this method Returns the file to its original uncustomized state so that its logic becomes cached in memory (also known as “ghosted”) rather than stored in the database.

Since the PageLayout is deployed as a Feature you can add a Feature Receiver and add the following code on the “FeatureActivated” event: 


public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
    if (properties == null)
    {
        throw new ArgumentNullException("properties");
    }
    using (SPWeb web = ((SPWeb)properties.Feature.Parent))
    {
        using (SPWeb rootWeb = web.Site.RootWeb)
        {
            // Uncustomises the pagelayout file so that we dont have to tear down the whole site collection
            // every time we make a change to the pagelayout page.
            SPQuery query = new SPQuery();
            StringBuilder queryBuilder= new StringBuilder();
            queryBuilder.Append(
" <Where>");
            queryBuilder.Append(
" <Eq>");
            queryBuilder.Append(
" <FieldRef Name="FileLeafRef" />");
            queryBuilder.Append(
" <Value Type="Text">PageLayout.aspx</Value>");
            queryBuilder.Append(
" </Eq>");
            queryBuilder.Append(
" </Where>");
            // This is the Master Page Gallery List
            SPList pageLayoutList = rootWeb.GetCatalog(SPListTemplateType.MasterPageCatalog);
            query.Query = queryBuilder.ToString();
            SPListItemCollection items = pageLayoutList.GetItems(query);
            items[0].File.RevertContentStream();
        }
}

Programmatically adding a Web Part to the Web Part Gallery. July 6, 2008

Posted by jasear in ASP.NET, C#, MOSS 2007, SQL.
Tags: , , , , , , ,
add a comment

I came across a problem whereby I needed to add a Web Part to the Web Part Gallery. The Web Part had been deployed successfully but as of yet it did not appear in the Web Part Gallery SPList. This needed to be done programmatically by using the SharePoint object model.

How do we programmatically add a Web Part (that has been deployed) to the Web Part Gallery. Using the SharePoint UI this is done via the NewDwp.aspx page.

The problem is that the list you see on the NewDwp.aspx page is not an SPList! Which makes life a bit tough! The other problem is that the .dwp files displayed in there do not exist until you select them and then click “Populate” by which time they are already in the Web Part Gallery.

I believe the NewDwp.aspx page builds the list of Web Parts, available to be added to the Gallery, from the web.config safe control enteries. Once you click “Populate” the page dynamically builds the .dwp file and adds it to the Gallery.

To add the Web Part to the Gallery programmatically you have to do the same i.e.

  1. Create the .dwp xml file dynamically
  2. Add it to the Web Part Gallery SPList

Below is the code to achieve this:

 
private static void AddWebPartToGallery()
{
    using (SPSite site = new SPSite("http://yoursite.com"))
    {
        using (SPWeb web = site.OpenWeb())
        {
            CreateDwpFile();
            
FileInfo fInfo = new FileInfo("myFile.dwp");
            FileStream fStream = fInfo.Open(FileMode.Open, FileAccess.Read);    
            web.AllowUnsafeUpdates =
true;
            site.AllowUnsafeUpdates =
true;    
            SPList list = web.Lists["Web Part Gallery"];
            SPFolder root = list.RootFolder;
            SPFile spFile = root.Files.Add("ContentEditor.dwp", s);
            spFile.Update();
        }
    }
}

The CreateDwpFile() method dynamically creates the .dwp file. Then open the Web Part Gallery List and the newly created .dwp file and add it to the root folder of the list.