SP2010 – Quick writeup about list referential integrity

There’s a new and much requested feature in SP2010 and that’s referential integrity on (custom) lists. This blog post is a quick writeup of that new feature just to document what I’ve found so far. Hopefully it’s helpful for someone else as well.

Right, we have our SharePoint site which contains two custom lists:

- ParentList
- ChildList

ParentList has the following columns: Id, Title, Name, Address and City
ChildList has the following columns: Id, Title

We’re going to use a lookup column to relate the two lists together, just like it works in SharePoint 2007.

Parent List with some sample data

Parent List with some sample data

The screenshot above shows the parent list, including some sample data we’re going to display in our child list. First, we’re going to add the lookup column to the client list, so we can relate the two lists.

Child List - New Lookup column

Child List - New Lookup column

Nothing new here :)

Lookup Column Additional Info

Lookup Column Additional Info

The thing that’s new here, if I recall correctly, is that you can now select multiple columns from the parent last that you want to include in the child list. I’ve selected to display the Title, Name, Address and City of the parent list, as you can see in the screenshot. The screenshot shows a relationship on ParentList:ID, but I’ve changed that to Title in my own setup.

The new feature is depicted in the screenshot below. This feature allows you to specify whether you want to enforce a relationship on this data and what kind of action should be taken when an item in the parent list is deleted. In our example I’m going to restrict the deletion, which I’ll demonstrate later. You could select cascade delete, and the item in the child list will be deleted upon deletion of the parent item, just like in a relational database. 

Lookup Column Relationship

Lookup Column Relationship

However, when you try to add the column, SharePoint will likely come up with an alert, saying that the parent column needs to be indexed before the relationship can be enforced.

Lookup column index

Lookup column index

Just click OK here, to index the column. So, now that we’ve added the lookup column, the additional columns we’ve selected, are also displayed in the child list, like this:

Client list after adding the lookup column

Client list after adding the lookup column

You can see here that the additional columns have been added in a format of <lookup column name>:<parent column name>, so this allows for easy distinction between columns that are coming from the parent list and the ones that exist on the child list.

Adding a new item will result in the following form:

New client item

New client item

 
As you can see here, you can select the parent item, which is nothing new obviously. The client list will look like this once the item has been added.

Client list - item added

Client list - item added

So, there you see the info that comes from the parent list. Now, let’s try to delete the linked item in the parent list to see what will happen.

Delete parent item

Delete parent item

As expected, you will get an error message, indicating that you can’t delete the item because of a relationship constraint between the items. The error could have been a bit more user friendly I suppose, but I’ve learned to expect stuff like this from SharePoint :)

Parent list item delete error

Parent list item delete error

So, there you go, some quick information about relational integrity in SharePoint 2010. I think this should help us all utilize custom lists for scenarios where you’d rather use a database. Obviously, using a regular database brings a lot more advantages, but it’s really nice to have this functionality in SharePoint now, without having to code your own event receivers and stuff. :)

SP2010 – Now possible to change your page layout on the fly?

Wow, this seems like an interesting and much requested feature I just discovered in SharePoint 2010. It seems you can now change the page layout of an existing page! Maybe this is old news already, but I hadn’t heard anything about it yet.

So we have our regular article page right here:

SP2010 Article Page

SP2010 Article Page

When you expand the Page Layout button on the ribbon, you get this:  

SP2010 Change Page Layout

SP2010 Change Page Layout

 Let’s for example select Blank Web Part page as the new layout for our page. SharePoint then takes the new setting, doesn’t perform a post-back anymore, AJAX is very nicely integrated now:

SP2010 - Changing Page Layout

SP2010 - Changing Page Layout

When SharePoint is done processing the changes, our page is now a web part page instead of the article page it was before!

SP2010 - Web Part Page
SP2010 – Web Part Page

 
Edit:

The functionality isn’t completely bug-free yet. After playing around with a couple of different page layouts, I’m suddenly getting this error:

SP2010 Change Page Layout Error
SP2010 Change Page Layout Error

Obviously the product is still in beta, I’m sure this will be fixed in the final version :)

SP2010 – SPUserCodeV4 – Your new web part project isn’t deploying?

So you’ve just gone through all the trouble to get the new SharePoint 2010 beta installed with the new Visual Studio 2010 beta alongside of it. You’ve created your first web part project and you’re ready to deploy!

Only one minor issue though, when you try to deploy, you’re getting an error, saying that “Cannot start service SPUserCodeV4 on computer <computername>”.

Visual Studio 2010 - SharePoint project deployment error

Visual Studio 2010 - SharePoint project deployment error

Although the screenshot shows an error with retracting, I was getting the same error when deploying. I figured, maybe it works if I deploy it again, but alas :)

The error is related to a service not being started on the SharePoint server. In order to start it, do the following:

- Go to Central Administration -> System Settings -> Manage services on server
- Locate the service “Microsoft SharePoint Foundation User Code Service”

SP2010 User Code Service

SP2010 User Code Service

On my server it wasn’t started by default. I’m not sure if something went wrong during installation and that this service should be enabled by default. Just click the “Start” link button to start the service.

Now go back to Visual Studio 2010 and retry deploying your solution. In my case, this did the trick; the project was deployed successfully deployed!

VS2010 Sharepoint Project Deployed

VS2010 Sharepoint Project Deployed

 
Thinking about it, it might have something to do with the order of installation. I installed Visual Studio 2010 after I installed and configured SharePoint 2010. Maybe for some reason SharePoint only picks this up when it can find a Visual Studio installation or something like that. Not too bothered to find out though, the installation took a few precious hours and I don’t really feel like doing all that again :)

Making a custom Outlook ribbon group appear in another inspector – bugged?

For a small assignment for a client, I’ve created this custom ribbon for Outlook, which holds a new group with a couple of custom controls relevant for a new mail message. They’d like to add a couple of extra MIME headers if any of the recipients is outside their domain. So far, this was relatively easy to do (still, I find the documentation about Outlook and VSTO a bit scarce, required quite the amount of Googling). However, since meeting requests can also have external recipients, the custom ribbon group should appear there as well.

Luckily, I found a blog post on a very good blog which explains how to do this. The post can be found here.

So, I’ve followed the instructions there, created an event handler for the New Inspector event and built in a check to see if the new item is an meeting item. If so, set the OfficeId to “NewAppointment”.

This generally seems to work, except when you open new items in a specific order. Then all of a sudden the custom group doesn’t show up in the new meeting form anymore.

Following these steps seems to work just fine:

  • Debug the application
  • Compose a new meeting (the custom group appears)
  • Compose a new mail message (the custom group appears)
  • Compose a new meeting item again (the custom group appears)

However, when I change the steps to the following, the custom group doesn’t appear anymore in a new meeting:

  • Debug the application
  • Compose a new mail message (the custom group appears)
  • Compose a new meeting item (now all of a sudden the group doesn’t show up anymore!)

I can’t really find out why it doesn’t work. When adding a breakpoint to the Load event of the Ribbon, I did found out that for some reason the Load event doesn’t fire anymore for a meeting in scenario #2. It works just fine if I follow the procedure in scenario #1 however.

The way I’ve currently solved it is by creating a duplicate of the ribbon and setting the OfficeId to “NewAppointment”. Obviously, this doesn’t deserve any prizes for elegance, but for now it does the trick…

I’ll be sure to write an update to this post should I find out how to deal with this weird issue.

Anonymous access settings on a list greyed out

Today I was trying to help out a client to set up one of their existing lists for anonymous access. I followed the usual procedure of editing the list permissions and then navigating to the anonymous access settings. However, all the checkboxes on the page were greyed out.

I checked the anonymous access settings on both the site collection and the website and they all seemed fine. I checked another existing list and that seemed fine as well. Then I created a new list based on the same template as the list I was trying to configure, and that worked as well! Weird…

So I started searching on Google and came across a blog post about anonymous access on surveys here: http://www.teuntostring.net/blog/2007/07/using-surveys-on-anonymous-access.html. It’s mentioned there that in order for anonymous access to work, the setting for ‘Read Access’ needs to be ‘All Responses’, otherwise the checkboxes will be greyed out. Then I remembered that we’re using a similar setting our list, for privacy concerns.

I went back to the list, changed the setting for ‘Read Access’ to ‘All items’, went back to the anonymous access settings page, and voila! The checkboxes were no longer greyed out.

Using the property bag of an SPWeb for storing configuration settings

Each web site in SharePoint 2007 has a property bag that can be accessed from the object model. This property bag is ideal for storing configuration settings for a web. I’m going to show you how to use this in combination with serializing a settings object and by using the Provider design pattern. The Provider pattern is in here so that you can also easily switch to storing the configuration settings in for example a SharePoint list.

I’m first going to outline the requirements for our solution:

  • A mechanism needs to be in place that allows for storing custom site configuration settings for a web;
  • Site collection wide settings should also be stored. These settings are different from the settings on a web site;
  • The storage location of these settings should be easily swappable;
  • The configuration settings should be accessible by using object properties;
  • The consumers of these settings should not have to care about retrieving and storing the settings;

Since the data that is stored for each type is different, a container class for each type is created. To define that they are both configuration setting objects, they both implement a public interface called “IConfigurationSettings”.

1
2
3
4
5
6
7
8
public interface IConfigurationSettings 
{ 
    SPWeb Web 
    { 
       get; 
       set; 
    } 
}

Both types of site settings are stored in a property bag of the corresponding web. Site collection settings are stored on the root web. You can also have any other settings type, just as long as they implement the interface above. In our example we have:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[Serializable()] 
public class SiteConfigurationSettings : IConfigurationSettings 
{ 
    [NonSerialized()] 
    public SPWeb Web 
    { 
        get; 
        set; 
    } 
}       
 
[Serializable()] 
public class SiteCollectionConfigurationSettings : IConfigurationSettings 
{ 
    [NonSerialized()] 
    public SPWeb Web 
    { 
        get; 
        set; 
    } 
}

For the sake of keeping this blog post brief, I’m not going to outline the different properties for each of these classes, I’m sure you can use your imagination. :)

The configuration values are persisted to the underlying data store by using the Provider design pattern. Should the storage location be changed in the future, for example to a custom list, this pattern can easily facilitate such a change. It would only require the development of a provider for the custom list. The container classes and management objects remain unchanged.

Each provider has to implement the interface “IConfigurationProvider”. This interface dictates that any implementing class should at least provide methods for retrieving and storing configuration settings. It uses generics to cope with the different kind of configuration classes. Although an interface could be returned here, generics make more sense since they immediately return the proper type, instead of an interface.

To ease the process of converting the stored data into usable objects and vice versa, the objects are serialized when they are persisted to the data store. Should any new configuration settings be added in the future, then no change to the data storage is required.

1
2
3
4
5
6
7
8
internal interface IConfigurationProvider 
{ 
    void Clear(SPWeb web);        
 
    T GetConfiguration<T>(SPWeb web) where T : IConfigurationSettings, new();        
 
    void SaveConfiguration<T>(SPWeb web, T settings); 
}

The following is the actual implementation used by the property bag provider:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
internal class PropertyBagConfigurationProvider : IConfigurationProvider 
{ 
    private readonly string _propertyBagSettingsKey = "ConfigurationSettings";     
 
    #region IConfigurationProvider Members     
 
    public void Clear(SPWeb web) 
    { 
        web.Properties[_propertyBagSettingsKey] = null; 
        web.Properties.Update(); 
    }     
 
    public T GetConfiguration<T>(SPWeb web) where T : IConfigurationSettings, new() 
    { 
        if (web == null) 
        { 
            throw new ArgumentNullException("web"); 
        }     
 
        string propertyData = web.Properties[_propertyBagSettingsKey]; 
        T deserializedConfig = new T(); 
        deserializedConfig.Web = web;     
 
        if (!string.IsNullOrEmpty(propertyData)) 
        { 
            object obj = null;    
 
            using (StringReader sr = new StringReader(propertyData)) 
            { 
                byte[] b = Convert.FromBase64String(propertyData); 
                Stream stream = new MemoryStream(b);    
 
                try 
                { 
                    BinaryFormatter bf = new BinaryFormatter(); 
                    obj = (object)bf.Deserialize(stream); 
                } 
                catch 
                { 
                    throw; 
                }    
 
                deserializedConfig = (T)obj; 
            } 
        }     
 
        return deserializedConfig; 
    }     
 
    public void SaveConfiguration<T>(SPWeb web, T settings) 
    { 
        if (web == null) 
        { 
            throw new ArgumentNullException("web"); 
        }     
 
        if (settings == null) 
        { 
            throw new ArgumentNullException("settings"); 
        }     
 
        string result;    
 
        using (Stream stream = new MemoryStream()) 
        { 
            try 
            { 
                BinaryFormatter bf = new BinaryFormatter(); 
                bf.Serialize(stream, settings); 
            } 
            catch 
            { 
                throw; 
            }    
 
            stream.Position = 0; 
            byte[] b = new byte[stream.Length]; 
            stream.Read(b, 0, (int)stream.Length);    
 
            result = Convert.ToBase64String(b, 0, b.Length); 
        }    
 
        web.Properties[_propertyBagSettingsKey] = result; 
        web.Properties.Update(); 
    }     
 
    #endregion 
}

The provider objects are not exposed through the public API, since the calling client doesn’t necessarily have to be aware of how data storage and retrieval is done. To abstract this concept, a generic “Configuration” object is created. When an instance of this object is created, it also creates an instance of the configured provider. Any calls to the storage and retrieval methods are redirected to the provider.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
public class Configuration<T> where T : IConfigurationSettings, new() 
{ 
    #region Fields   
 
    private static Configuration<T> _instance; 
    private SPWeb _web = null; 
    private IConfigurationProvider _provider = null; 
    private T _settings = default(T);   
 
    #endregion   
 
    #region Properties   
 
    public T Settings 
    { 
        get 
        { 
            if (_provider != null &amp;&amp; _web != null) 
            { 
                _settings = _provider.GetConfiguration<T>(_web); 
            } 
            else 
            { 
                _settings = new T(); 
            }   
 
            return _settings; 
        } 
    }   
 
    #endregion   
 
    #region Constructors   
 
    internal Configuration() 
    {   
 
    }   
 
    internal Configuration(SPWeb web) 
    { 
        _web = web;   
 
        // Note that this can also be changed to read a setting from a configuration file so that it can be changed without touching the code. 
        _provider = Activator.CreateInstance(typeof(PropertyBagConfigurationProvider)) as IConfigurationProvider; 
    }   
 
    #endregion   
 
    #region Public Methods   
 
    public void Clear() 
    { 
        _provider.Clear(_web); 
    }   
 
    public void SaveSettings() 
    { 
        _provider.SaveConfiguration<T>(_web, _settings); 
    }   
 
    #endregion   
 
    #region Public Static Methods   
 
    internal static Configuration<T> GetConfigurationSettings(SPWeb web) 
    { 
        _instance = new Configuration<T>(web);   
 
        return _instance; 
    }   
 
    #endregion 
}

By using this Configuration class, we can abstract the behaviour of the provider and hide it from the caller. This way the caller doesn’t have to care about what’s going on with the data store, he only sees the methods to retrieve and save the configuration settings.

In order to improve usability of the application, a final wrapper class is created to provide easy access to each of the different settings. The wrapper class exposes properties for SiteConfigurationSettings and SiteCollectionConfigurationSettings. These properties will look for settings in the web SPContext.Current.Web. The exposed methods can be used to retrieve settings from a different web, or when you’re calling the code from a console application for example, where the SPContext does not exist.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
public static class ConfigurationManager 
{ 
    public static SiteConfigurationSettings WebSettings 
    { 
        get 
        { 
            if (SPContext.Current != null &amp;&amp; SPContext.Current.Web != null) 
            { 
	    return ConfigurationManager.OpenSiteConfiguration(SPContext.Current.Web).Settings; 
            }   
 
             return null; 
        } 
    }   
 
    public static SiteCollectionConfigurationSettings SiteCollectionSettings 
    { 
        get 
        { 
            if (SPContext.Current != null &amp;&amp; SPContext.Current.Site != null) 
            { 
	    return ConfigurationManager.OpenSiteCollectionConfiguration(SPContext.Current.Site).Settings; 
            }   
 
            return null; 
        } 
    }   
 
    public static string CustomCssUrl 
    { 
        get 
        { 
            if (SPContext.Current != null &amp;&amp; SPContext.Current.Web != null) 
            { 
                return StyleConfiguration.CustomCssLocation + String.Format(StyleConfiguration.CustomCssFileFormat, SPContext.Current.Web.ID.ToString()); 
            }   
 
            return String.Empty; 
        } 
    }   
 
    public static Configuration<sitecollectionconfigurationsettings></sitecollectionconfigurationsettings> OpenSiteCollectionConfiguration(SPSite siteCollection) 
    { 
        if (siteCollection == null) 
        { 
            throw new ArgumentNullException("siteCollection"); 
        }   
 
        return OpenSiteCollectionConfiguration(siteCollection.RootWeb); 
    }   
 
    public static Configuration<sitecollectionconfigurationsettings></sitecollectionconfigurationsettings> OpenSiteCollectionConfiguration(SPWeb rootWeb) 
    { 
        if (rootWeb == null) 
        { 
            throw new ArgumentNullException("rootWeb"); 
        }   
 
        return Configuration<sitecollectionconfigurationsettings></sitecollectionconfigurationsettings>.GetConfigurationSettings(rootWeb); 
    }   
 
    public static Configuration<siteconfigurationsettings></siteconfigurationsettings> OpenSiteConfiguration(SPWeb web) 
    { 
        if (web == null) 
        { 
            throw new ArgumentNullException("web"); 
        }   
 
        if (web.IsRootWeb) 
        { 
            // Return an empty settings object, since this method cannot be used on a root web; the type conversion would fail. 
            return new Configuration<siteconfigurationsettings></siteconfigurationsettings>(); 
        }   
 
        return Configuration<siteconfigurationsettings></siteconfigurationsettings>.GetConfigurationSettings(web); 
    } 
}

Invalide parameter: ContentType. Expect a number. ‘SomeContentType’ is given instead.

So, you’ve created your custom advanced search box for your SharePoint 2007 site, but now you’re running into an error:

Invalide parameter: ContentType. Expect a number. ‘SomeContentType’ is given instead.

There is a good chance you forgot to include two imporant hidden fields in your control:

  • ASB_TextDT_Props
  • ASB_DateTimeDT_Props

I’m not entirely sure about the purpose of these fields, but I suspect that the entries in these fields define the data type of the property fields in the advanced search box. You can include them in your control like this:

protected override void OnPreRender(EventArgs e) 
{ 
  base.OnPreRender(e);     
 
  this.Page.ClientScript.RegisterHiddenField("ASB_TextDT_Props", "ContentType#;#Path#;#FileName#;#Description#;#Title#;#Author#;#DocSubject#;#DocKeywords#;#DocComments#;#Manager#;#Company#;#CreatedBy#;#ModifiedBy");
  this.Page.ClientScript.RegisterHiddenField("ASB_DateTimeDT_Props", "Write#;#Created#"); 
}

Also, be sure to include any custom managed properties you might have and wish to search on.

WebPartPageUserException: This page has encountered a critical error.

Did you ever try using a console application to programmatically activate a feature on a web and ran into the following error?

Microsoft.SharePoint.WebPartPages.WebPartPageUserException: This page has encountered a critical error. Contact your system administrator if this problem persists.

This doesn’t really give you much to go on. There is no InnerException object that gives you more information about what exactly is going on. I’ll first briefly outline what exactly I am trying to do here.

I have a page layout that contains several web part zones. This page layout is contained in a feature with scope “Site” and is deployed to the master page gallery upon activation. Now, I have another feature with scope “Web” that is stapled to the custom site definition we’re using. This feature takes care of creating an instance of that page layout and is supposed to add several web parts specified in a config file. The code for this is contained in a feature receiver. The web parts I’m adding are stored in *.webpart and *.dwp files, which are part of my SharePoint solution.

Now, this is where I’m running into the error mentioned in the beginning of this post. I am using the SPLimitedWebPartManager.ImportWebPart() method with an XmlReader to load the web part definition that should be imported. Whenever this method was hit (I was using the debugger to see what’s going on), it would throw an exception. Funny detail here is that the ImportWebPart() method has an out parameter that should contain the error message when something goes wrong. Strangely enough the exception I was running into, was not contained in the parameter, but thrown as an actual exception.

For those of you not interested in the outline of what took me an entire day to figure out, I will first describe what solved the error in my case. Thinking about it, I should have probably noticed this in the first place, but at first I didn’t connect the dots. Checking the event viewer, I noticed an error there:

Safe mode did not start successfully. Could not load file or assembly ‘Microsoft.SharePoint.ApplicationPages, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c’ or one of its dependencies. The system cannot find the file specified.

Initially I figured this to be related to the weird way my virtual machine with the development environment was behaving, but it turned out that this was exactly what was causing the problem. Since I am running the code from a console application and not within one of the SharePoint web application pools, I had of course also no access to the web application "_app_bin" folder. Apparently the DLL mentioned in the error is not added to the Global Assembly Cache, but is stored in the aforementioned folder. So, when I would run my console application, it would of course not be able to load the file, because it doesn’t know that it should look in that folder. I copied the DLL to my console application’s bin folder and it worked!

For anyone interested in how I figured this out without using the error message in the event log. The stack trace of the exception looked like this:

"Microsoft.SharePoint.WebPartPages.WebPartPageUserException: This page has encountered a critical error. Contact your system administrator if this problem persists. 
at Microsoft.SharePoint.ApplicationRuntime.SafeControls.RethrowExceptionIfNeeded() 
at Microsoft.SharePoint.ApplicationRuntime.SafeControls.IsSafeControl(Type type, String&amp; unsafeErrorMessage) 
at Microsoft.SharePoint.WebPartPages.WebPartImporter.CreateWebPart(Boolean clearConnections) 
at Microsoft.SharePoint.WebPartPages.WebPartImporter.Import(SPWebPartManager manager, XmlReader reader, Boolean clearConnections, Uri webPartPageUri, SPWeb spWeb) 
at Microsoft.SharePoint.WebPartPages.WebPartImporter.Import(SPWebPartManager manager, XmlReader reader, Boolean clearConnections, SPWeb spWeb) 
at Microsoft.SharePoint.WebPartPages.SPWebPartManager.ImportWebPart(XmlReader reader, String&amp; errorMessage) 
at Microsoft.SharePoint.WebPartPages.SPLimitedWebPartManager.ImportWebPart(XmlReader reader, String&amp; errorMessage) 
  at ConsoleTest.WebPartConfigurator.ConfigureWebPartsForPage(SPWeb web, String pageUrl, String webPartsLocation) in e:\Development\Sandbox\ConsoleTest\ConsoleTest\WebPartConfigurator.cs:line 68"

Since I wasn’t getting anything specific from the exception, I figured I’d start .NET Reflector to disassemble the WebPartImporter.CreateWebPart() method to see where it would crash. Somewhere in that method the imported web part is being validated to see whether it’s actually a safe control, hence the call to SafeControls.IsSafeControl() The SafeControls object is an internal property of the SPWeb object. When I opened the SafeControls.IsSafeControls() method, I noticed that the last method in the call stack (where the exception is thrown) is actually SafeControls.RethrowExceptionIfNeeded(). This appeared to be the first method that’s called in SafeControls.IsSafeControls(). In this method, it checks to see if a private field called _configException is not null. If not, it would throw the exception.

So I figured that since an exception already occured before it could actually check to see if the control is safe, I decided to see if I could find the place in the code where the exception was be thrown. Since SharePoint uses resource files for all of its text, I could not just search on the exception text. With Reflector, I opened Microsoft.SharePoint.intl.dll and found the resource entry with my exception text, with key “SafeModeFailedInitialize”.

Since I had already concluded that the exception already occurred before doing validation, I tried to figure out where the actual list of safe controls is being built. Opening the constructor of the SafeControls class showed the following:

this._configException = new WebPartPageUserException(SPResource.GetString("SafeModeFailedInitialize", new object[0]));

It appeared that I had found the source of the problem! Now only to figure out why it would do that…

The SafeControlsList object that’s being initialized there is trying to load the web application config file in order to parse the SafeControls section. At first I thought: “Maybe it cannot find the config file because I’m running the code from a console application”. However, there is already some code in place that looks for the config file if it’s not executed in the web application context, so it had to be something else.

Further down in the constructor, the method InitSafeControlsInfoFromConfig() is called. Scrolling through the code I noticed the following line:

throw new WebPartPageUserException(SPResource.GetString("SafeModeFailedInitialize", new object[0]));

Interesting, that’s exactly the same error message as I discovered earlier! I searched the code and found out that this exception is thrown when one of the assemblies in the SafeControls section is null. Then I remembered the error in the event log of which I didn’t think anything in the beginning. Adding things up, I quickly drew the conclusion that the missing DLL must be the source of my problem. So, I checked the Global Assembly Cache and couldn’t find the assembly there, which made sense, otherwise I wouldn’t be getting the error. Since the code was working when I activated the feature through the Site Settings page, the DLL had to be in the _app_bin folder of the web application.

And indeed, the file was indeed in that folder. I copied the file to the bin folder of my console application and tried to run it again. This time it completed without any exceptions!

It took me pretty much the entire day to figure out what was going on, that’s why I’m writing this blog post. Two reasons: I hope it helps anyone who’s running into the same problem and a brain dump for myself, should I ever run into the same issue again.

Change the welcome page of your SharePoint 2007 site programmatically

Today I was trying to figure out how to change the welcome page of a SharePoint 2007 site programmatically. I couldn’t really find useful links on Google, so I figured I’d go and play around a bit with the object model.

Apparently the PublishingWeb object has a property called DefaultPage, which takes an SPFile object as a value. I ended up with the following code to change the welcome page:

using(SPSite siteCollection = new SPSite("http://yourserver")) 
{ 
    SPWeb targetWeb = siteCollection.AllWebs["YourWebName"]; 
    SPFile newWelcomePage = null; // Change the code here to set the SPFile object to your new welcome page
 
    if(PublishingWeb.IsPublishingWeb(targetWeb)) 
    { 
        PublishingWeb publishingWeb = PublishingWeb.GetPublishingWeb(targetWeb); 
        publishingWeb.DefaultPage = newWelcomePage; // this sets the new welcome page 
        publishingWeb.Update(); 
    } 
}

That should do the trick :) . I hope this post is useful for anyone who’s also trying to achieve the same thing!

Can’t find that newly saved list template when creating a new list?

Ever came across that situation where you just saved an existing list as a template? And then you tried creating a new list based on that template? No matter how hard you looked, you just couldn’t find the template saved earlier when trying to create a new list?

Chances are that your list was provisioned using a feature and that the Hidden property of the <ListTemplate> element was set to True. That property prevents the list template from showing up on the page where you create a new list. Unfortunately, this also makes it a little bit more difficult to create that list through the SharePoint user interface.

Yes, I said a little more difficult, not impossible. There happens to be a work-around that allows you to still perform that action through the UI. This little trick was inspired on something that worked in SharePoint Portal Server 2003 as well. Sometimes the UI doesn’t allow you to perform a certain action (for example, it hides the action). However, the page that performs the actual action, could be fooled in performing the hidden action, by passing the correct query string parameters.

In this case, we will do exactly the same thing to create our list. Navigate to the site where you want to create your list and go to View All Site Content. On that page, click the Create button. As you already found out, the template you just saved isn’t here. Now, click the link to create a new Custom List.

Notice the address bar in your browser. The new.aspx page takes a FeatureId and ListTemplate as a parameter. The FeatureId parameter matches with the feature which installed this list on the SharePoint environment. The ListTemplate parameter probably speaks for itself; it specifies the template ID of the list.

To create a list based on a saved template, you need to specify an additional parameter to new.aspx. This parameter is called NewPageFilename and its value should be the name of your STP file.

Now you should look up the feature that installed the list you’re trying to create and copy its ID. Take this value and replace the value for FeatureId in the query string. Also, look up the <ListTemplate> element that is defined in the feature and copy its Type attribute. This value corresponds with the ListTemplate parameter in the URL, so replace the value in the query string with the value you just copied.

When you’re done, your URL should look something like this (without the square brackets):

/_layouts/new.aspx?NewPageFilename=[YourTemplateName]%2Estp&FeatureId={735DE88F-B6C1-4571-8D5F-08BF22D6CF40}&ListTemplate=130185

There you go! This URL should allow you to create your list. Navigate to the URL above and fill out the form like you normally do and you’re done :) This trick probably also works with hidden lists that don’t have a saved template. In that case, just omit the NewPageFilename parameter.