Archive

Archive for the ‘SharePoint Development’ Category

Custom Master Page and My Sites

December 11, 2009 oidatsmyleg Leave a comment

My Sites are often branded with a custom master page, using feature stapling to apply the customisations. The SharePoint Team Blog contains a good description of how to do this.

However, there is a gotcha with updating the MasterUrl and CustomMasterUrl properties of the My Site. The default value for these properties gives the full path to the master page catalogue and default.master page: /personal/user/_catalogs/masterpage/default.master

Therefore rather than replace the entire path when setting the master page simply replace the string “default.master” with your chosen custom master page:

var masterPageUrl = web.MasterUrl.ToLower();
masterPageUrl = masterPageUrl.Replace("default.master", "mycustom.master");
web.MasterUrl = masterPageUrl; 

Consuming Filter Web Parts with a Web Part containing a User Control

August 15, 2009 oidatsmyleg Leave a comment

When wrapping an ASP.NET User Control in a web part, the user control is usually loaded in the CreateChildControls() method as shown below:

protected override void CreateChildControls()
{
  if (!_error)
  {
    try
    {
       base.CreateChildControls();

       // Your code here...
       MyWebUserControl myControl =
         (MyWebUserControl)Page.LoadControl("~/_controltemplates/MyWebPart/MyWebUserControl.ascx");
       myControl.DataProperty = SomeProcessing();
       this.Controls.Add(myControl);
    }
    catch (Exception ex)
    {
       HandleException(ex);
    }
  }
}

Unfortunately this causes a problem when the embedded user control is to consume filter values coming from a web part connection because connections are evaluated after the CreateChildControls() method is executed. In the example above, when the SomeProcessing() method is executed no filter connections will have been created and therefore no filter values are available.

Therefore a mechanism is required to access the filter values later in the web part life cycle, perform filtering and update the user control with the filtered data.

The most obvious solution is to simply move the loading of the user control until after the connection has been created, for example in the OnPreRender() event handler. However this seems a little too ‘hacky’ to me. If anyone has a better suggestion please leave a comment below.

Notes

  1. Steven Van de Crean lists the order of execution for the ASP.NET web part in this blog post.

Cross-Site Collection Query, Almost

August 13, 2009 oidatsmyleg Leave a comment

This is a tale of getting close to one of SharePoint’s holy grails but not quite…

Site collections are the most scalable SharePoint container and they offer lots of advantages over building site heirarchies with layers of sub-sites. However information in one site collection can’t be made visible to another site collection using out-of-the-box SharePoint components. This is a major inconvenience when trying to aggregate content on a portal for example.

Solution (almost)

The solution evolves from first using the SPSiteDataQuery class to run a query upon each site collection and then aggregating the results.

The example below queries all Calendar lists for events within a date range:

// for each site collection
var currentApp = SPContext.Current.Site.WebApplication;
foreach (SPSite site in currentApp.Sites)
{
  var query = new SPSiteDataQuery()
  {
    RowLimit = 100,
    Lists = @"<Lists ServerTemplate='106' />",
    Webs = "<Webs Scope='SiteCollection' />",
    Query =
        String.Format(
        @"<Where>
            <And>
              <Geq>
                <FieldRef Name='EventDate' />
                <Value Type='DateTime'>{0}</Value>
              </Geq>
              <Leq>
                <FieldRef Name='EndDate' />
                <Value Type='DateTime'>{1}</Value>
              </Leq>
            </And>
          </Where>",
          startDate.ToString("yyyy-MM-dd"), endDate.ToString("yyyy-MM-dd")),
    ViewFields =
        "<FieldRef Name='Title' />
         <FieldRef Name='ID' />
         <FieldRef Name='EventDate' />
         <FieldRef Name='EndDate' />
         <FieldRef Name='Location' />
         <FieldRef Name='Description' />
         <FieldRef Name='fAllDayEvent' />
         <FieldRef Name='fRecurrence' />
         <FieldRef Name='FileRef' />"
  };
  var results = site.RootWeb.GetSiteData(query);

  // aggregate the results
  ...
}

This will work, however it clearly doesn’t scale well as it will query many sites and webs each time it is run.

The next step was to consider the CrossListQueryCache class which provides the ability to cache the results. In reality, apart from introducing the cache, this class doesn’t do much more than wrap the SPSiteDataQuery class and the call to SPWeb.GetSiteData().

Continuing the same example, swap out the SPSiteDataQuery with:

var query = new CrossListQueryInfo
  {
    UseCache = true,
    RowLimit = 100,
    Lists = @"<Lists ServerTemplate='106' />",
    Webs = "<Webs Scope='SiteCollection' />",
    Query =
        String.Format(
        @"<Where>
            <And>
              <Geq>
                <FieldRef Name='EventDate' />
                <Value Type='DateTime'>{0}</Value>
              </Geq>
              <Leq>
                <FieldRef Name='EndDate' />
                <Value Type='DateTime'>{1}</Value>
              </Leq>
            </And>
          </Where>",
          startDate.ToString("yyyy-MM-dd"), endDate.ToString("yyyy-MM-dd")),
    ViewFields =
        "<FieldRef Name='Title' />
         <FieldRef Name='ID' />
         <FieldRef Name='EventDate' />
         <FieldRef Name='EndDate' />
         <FieldRef Name='Location' />
         <FieldRef Name='Description' />
         <FieldRef Name='fAllDayEvent' />
         <FieldRef Name='fRecurrence' />
         <FieldRef Name='FileRef' />"
  };

var cache = new CrossListQueryCache(query);
var results = cache.GetSiteData(site, site.RootWeb.ServerRelativeUrl);

Unfortunately attempting to use the cache for each site collection query causes problems – any attempt to query a site collection beyond the current one results in a error:

There is no Web named "/sites/WebSite".

This is due to the way that CrossListQueryCache class is written – as revealed by examining Microsoft.SharePoint.Publishing with .NET Reflector. During the execution of the GetSiteData method, a call is made to the getWeb method of the ContentByQueryWebPart:

using (SPWeb web = ContentByQueryWebPart.getWeb(webUrl))
{
    return this.GetSiteData(web);
}

So when the cached query is executed it uses the getWeb method to get the reference to the SPWeb object to run the query against. The problem is that this method uses the context of the calling code, via SPContext, to open the web site specified by the URL:

internal static SPWeb getWeb(string webUrl)
{
    SPSite site = SPContext.Current.Site;
    SPWeb web = null;
    web = site.OpenWeb(webUrl);
    bool isRootWeb = web.IsRootWeb;
    return web;
}

Thus, even though the caller has passed through the correct SPSite to use when opening the SPWeb against which the query is to be run, the CrossListQueryCache ignores this and instead uses the context of the caller.

Close but no cigar…

The net result is that a cross-site collection query is possible using SPSiteDataQuery but clearly this could have a serious performance impact with querying multiple site collections and sub-sites. It would be nice to be able to have the query results cached however this doesn’t seem possible using CrossSiteQueryCache.

It’s somewhat infuriating that a relatively simple change to the CrossListQueryCache class would enable cached cross-site collection queries.

Wrap a User Control inside a Web Part using WSPBuilder

July 23, 2009 oidatsmyleg 3 comments

This post will describe how to wrap an ASP.NET User Control in a Web Part for deployment to SharePoint using WSPBuilder. Sharp-eyed readers will notice the similarity with the SharePoint Guidance package article How to: Wrap a User Control Inside of a Web Part for SharePoint which is intential only in an attempt to provide a useful comparison between the different tools, VSeWSS and WSPBuilder.

Creating the Web Part

This procedure demonstrates how to create an ASP.NET Web Part to wrap the user control.

To create the ASP.NET Web Part

  1. In Visual Studio, point to New on the File menu, and then click Project.
  2. In the Project types pane, click WSPBuilder. In the Templates pane, click WSPBuilder Project. In the Name box, type MyWebPart, and then click OK.
  3. Right-click the MyWebPart project, point to Add, and then click New Item…
  4. In the Categories pane, click WSPBuilder. In the Templates pane, click Web Part Feature. In the Name box, type MyWebPart, and then click OK.
  5. In the Feature Settings dialog, type My Web Part for the Title, A web part built using WSPBuilder for the Description and set the Scope to be Web. Click OK.
  6. Open the elements.xml file and edit the values for the Group and QuickAddGroups properties. The following code shows the corrected values:
  7. <Property Name="Group" Value="My Group"></Property>
    <Property Name="QuickAddGroups" Value="My Group" />
    
  8. Right-click on the MyWebPart soluction, then select Build Solution. Note the PublicKeyToken for the compiled assembly.
  9. Optional: Add the following property to the Properties block in the MyWebPart.webpart file to prevent users from closing the web part once it is on a page:
  10. <property name="AllowClose" type="boolean">False</property>

Adding ASP.NET Project Types to the WSPBuilder Project

This procedure demonstrates how to add the ASP.NET web application templates to a WSPBuilder project. This is required to permit the use of the ASP.NET User Control within the WSPBuilder project.

To add the ASP.NET templates

  1. In Visual Studio, right-click on the MyWebPart project and select Unload Project.
  2. Right-click on the MyWebPart project again and select Edit MyWebPart.csproj. Locate the ProjectTypeGuids element and add the ASP.NET web application project type guid {349C5851-65DF-11DA-9384-00065B846F21}. The following code shows the corrected element:
  3. <ProjectTypeGuids>{349C5851-65DF-11DA-9384-00065B846F21};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
  4. Save and then close the MyWebPart.csproj file.
  5. Right-click on the MyWebPart project and select Reload Project.
  6. Right-click on the MyWebPart project and select Properties On the Application tab, change the Target Framework to be .NET Framework 3.5. Save the changes.

Creating the ASP.NET User Control

This procedure demonstrates how to create an ASP.NET user control that uses SharePoint.

To create the ASP.NET user control

  1. In Visual Studio, right-click the TEMPLATE folder, select Add then click New Folder. Name the folder CONTROLTEMPLATES.
  2. Right-click the CONTROLTEMPLATES folder, select Add then click New Folder. Name the folder MyWebPart
  3. Right-click the MyWebPart folder, select Add then click New Item.
  4. In the Categories pane, click Web. In the Templates pane, click Web User Control. Name the control MyWebUserControl.ascx, and then click Add.
  5. Delete the CodeBehind attribute in the MyWebUserControl.ascx file. Replace the Inherits attribute with MyWebPart.MyWebUserControl, MyWebPart, Version=1.0.0.0, Culture=neutral, PublicKeyToken=[your PublicKeyToken]. The following code shows the corrected file:
  6. <%@ Control Language="C#" AutoEventWireup="true" Inherits="MyWebPart.MyWebUserControl, MyWebPart, Version=1.0.0.0, Culture=neutral, PublicKeyToken=9410bf5b454f3bbd" %>
  7. Open the MyWebUserControl.ascx file in the designer view. Click the Toolbox, and then add a Label using a drag-and-drop operation. Switch to the code view and rename the ID property to be MyLabel.
  8. Open the MyWebUserControl.ascx.cs file and the MyWebUserControl.ascx.designer.cs file. Change the namespace to MyWebPart in both files.
  9. Open the MyWebUserControl.ascx.cs file. Add a public string property named DisplayText to the MyWebUserControl class. The following code demonstrates this:
  10. public string DisplayText { get; set; }
  11. Add the following code to the Page_Load method. This will display the value of the DisplayText property using the MyLabel control.
  12. protected void Page_Load(object sender, EventArgs e)
    {
        MyLabel.Text = DisplayText;
    }

Wrapping the User Control and Connecting the Properties

This procedure demonstrates how to wrap the user control inside the web part and connect the properties between SharePoint, the web wart and the user control.

To wrap the user control and connect the properties

  1. Open the MyWebPart.cs file.
  2. Edit the attributes of the MyProperty property. The code below shows the changes:
  3. [Personalizable(PersonalizationScope.Shared)]
    [WebBrowsable(true)]
    [System.ComponentModel.Category("Custom")]
    [WebDisplayName("MyProperty")]
    [WebDescription("Message to be displayed within Web Part")]
    
  4. Within the CreateChildControls() method, delete the single line of code under the // Your code here... comment. Enter code to load the MyWebUserControl, assign the value of MyProperty to the DisplayText property of the control and then add the control to the web part’s Controls collection. The code below demonstrates how to do this:
  5. // Your code here...
    MyWebUserControl myControl =
       (MyWebUserControl)Page.LoadControl("~/_controltemplates/MyWebPart/MyWebUserControl.ascx");
    myControl.DisplayText = MyProperty;
    this.Controls.Add(myControl);
    
  6. Right-click on the MyWebPart solution and select Rebuild Solution.

The completed solution structure is shown below:

SolutionStructure

Deploying the Web Part and Testing Functionality

This procedure demonstrates how to deploy the web part and the user control, and how to test their functionality. This procedure assumes that there is a local instance of SharePoint to deploy to.

To deploy the web part

  1. Right-click on the MyWebPart project, select WSPBuilder and then Build WSP. Follow the progress of the build in the Output window and wait for the process to complete.
  2. Right-click on the MyWebPart project, select WSPBuilder and then Deploy. Follow the progress of the build in the Output window and wait for the process to complete.
  3. Browse to the local SharePoint site.
  4. In the Site Actions drop-down box, click Site Settings. On the Site Settings page, click on the Site features link.
  5. Click on the Activate button next to the My Web Part feature.
  6. Click on the link to the default home page for the SharePoint site.
  7. In the Site Actions drop-down box, click Edit Page.
  8. Click Add a Web Part in one of the Web Part zones on the page, click the My Web Part Web Part (located in the My Group section), and then click Add.
  9. Click Exit Edit Mode. You should now see the default “Hello SharePoint” message.
  10. To change the text displayed, click the drop-down box on the Web Part, and then click Modify Shared Web Part.
  11. Expand the Custom group, enter the new display message in the My Property box, and then click Apply.
  12. Click Exit Edit Mode. You should now see the new message.

Notes

  1. Obtaining the PublicKeyToken from the compiled assembly is detailed in numerous places however I recommend Todd Bleeker’s blog.
  2. The MyWebPart.cs file produced by WSPBuilder contains much more “helper” code than that created by VSeWSS. Although this is useful in that it provides an example of a browsable web part property and some hints on where to place customisations, most of the code can be stripped out if required. Only the CreateChildControls() method is mandatory.
  3. WSPBuilder will deploy to all web applications within the local SharePoint farm.
  4. Take care over providing the correct path to the user control when calling the LoadControl() method.

Alternatives

As an alternative to modifying the ProjectTypeGuid property, it is possible to add an ASP.NET Web Application project to the solution and then xcopy the project outputs to the correct location within the WSPBuilder project.

Greg Galipeau provides a excellent walkthrough of this approach.

Windows 2008 & SQL Server 2008 Development Environment Tips

Given the requirements for SharePoint 2010, I suspect it will be increasingly common for SharePoint development environments to move to 64-bit Windows 2008 and SQL Server 2008.

Here are a few tips to help get your favourite development working as they should:

Turn off User Access Control
Most developers log into their development environment using an account with local administrator privileges. User Access Control (UAC) however means that the account can only perform administrative operations when applications are explicitly run as an administrator and doesn’t permit spawned applications to inherit the administrator permission.

This is especially important for WSPBuilder which executes a command-line tool to build and deploy the WSP file. With UAC turned on you may encounter the following “pre-flight” errors:

  • SharePoint is not installed on [host]
  • Microsoft Office Server is not installed on [host]
  • Microsoft SharePoint Services Administration is not running on [host]
  • Microsoft SharePoint Services Timer is not running on [host]

Check permissions within SQL Server 2008
Basic I know, but it is worth reviewing the security settings on the SQL Server 2008 instance to make sure that the account used to deploy solutions has sufficient permission. In the development environment you are master of your own domain, so go wild and give it sysadmin privileges.

Some symptoms of not having sufficient permission include:

  • WSPBuilder
    User [account] does not have installation permissions on [host]
  • VSeWSS
    VSeWSS Service Error: No SharePoint Site exists at the specified URL.

Further thoughts on VSeWSS…

Having maligned the previous versions of VSeWSS, I feel it’s only fair to provide more feedback on the latest version… I’ve been using the March CTP version of VSeWSS 1.3 for a while now and it is a vast improvement on the previous versions.

However I still have issues with the solution, manifest and feature XML files being hidden under the pkg folder and VSeWSS automagically updating them on every build. Care needs to be taken in editing these XML files to avoid losing changes or ending up with multiple features, and supporting folders, being created with _1 (or _2 or _3 if you’re really unlucky) appended to the end.

Fortunately there is some guidance on the supported modifications to these files in the latest release notes.

However, in summary, I think VSeWSS 1.3 is very useful SharePoint development tool and I’m looking forward to the full release.

…the alternatives…

One of the advantages offered by VSeWSS is the tight integration with Visual Studio however with the advent of the WSPBuilder Extensions for Visual Studio the game has changed.

Using the WSPBuilder Extensions add-in, SharePoint projects can be created in Visual Studio just as easily as with VSeWSS. WSPBuilder offers a slightly different set of pre-configured SharePoint development projects; these are well described by Tobias Zimmergren on his blog.

WSPBuilder gives the developer access to the feature and element XML files and doesn’t seek to obfuscate them; which is good. The solution and manifest files are generated automatically by WSPBuilder and only visible by extracting the contents of the WSP package. This could prove problematic if any manual edits are required.

It appears that the WSPBuilder extensions still uses the command-line version of the tool to produce the WSP file and therefore can’t use files added to the Visual Studio project as a link because they don’t exist in the physical file structure of the project.

The ability to add linked files is very useful when developing an ASP.NET application to be hosted within SharePoint as the actual .aspx files can be added to the SharePoint solution without xcopying them from the ASP.NET web application’s solution. It is also the method used by the patterns and practices group in their SharePoint Guidance for wrapping an ASP.NET user control in a web part.

Finally, it’s somewhat annoying that WSPBuilder targets all web applications within the farm by default and I’ve not been able to change this behaviour yet.

…and the future

And, as a last word, Visual Studio 2010 should offer built-in SharePoint development projects. Once I’ve had a play with the CTP hopefully I’ll be in a position to offer an opinion on how these compare.

Musings on Site Columns

April 15, 2009 oidatsmyleg Leave a comment

For some reason it doesn’t appear possible to create a Site Column of type “Integer”, even though it appears as a valid Field type according to the Field Element (List – Definition) documentation on MSDN.

Integer doesn’t appear as an option for the type on the New Site Column page and, if created via an XML defintion file, then it will not appear on the Site Columns gallery. It can however be used when defining a custom Content Type or List Definition.

More details are to be found in the Community Content at the foot of the page linked to above.

External HDDs are your friend

Developing SharePoint solutions often involves running a virtual machine with a server operating system, SQL Server instance and SharePoint installed – all of this on hardware designed to run a client operating system. As you can imagine, this isn’t going to run very quickly.

However there is a simple and inexpensive way in which you can dramatically improve system performance – use a second HDD.

SharePoint and SQL Server talk incessently to one another and most operations involve some measure of disk IO; things are made worse as soon as the host operating system also wants to use the disk. A fast multi-core CPU and RAM aren’t going to help much if the system is suffering from long disk read/write times.

A simple solution to this is to have a second physical hard-disk dedicated to the virtual environment. Even if this is an external hard-disk connected via USB or e-SATA, it can still make a considerable difference.

Another advantage of the dedicated hard-disk is that it can be quickly defragmented. The large files used by virtual machines very quickly become fragmented and degrade system performance. Schedule a defrag of the dedicated drive in the morning before starting up the virtual envrionment; grab a coffee and read some email whilst it completes then enjoy the increased performance during the working day.

Using VSeWSS to create a List Definiton based on a Content Type

April 1, 2009 oidatsmyleg 3 comments

Having created a custom content type, I wanted to use it in a series of list definitions and chose to use the VSeWSS List Definition from Content Type template to acheive this. This VSeWSS template provides a straightforward way to create list definitions based upon a custom content type within the same solution.

Unfortunately, although the lists could be provisioned successfully, it proved interesting to effect any edits to the schema.xml file. Whilst I was able to determine cause and solve the issues, I thought it may be of value to record the symptoms in case anyone else runs into the same problems and therefore can apply the same corrective measures…

Symptoms
Any changes to the All Items view appeared to be ignored and only the default ‘LinkTitle’ and ‘Attachments’ fields are displayed. If, on the other hand, I created a distinct content type for each list definition then it was possible to specify additional fields in the view. I had however hoped to re-use the same content type for all lists.

Also it didn’t appear to be possible to change the display name of the field from that defined within the source site column. Even though the Content Type specified a different display name for the site column, the actual provisioned list only ever used the value defined in the list definition. This appeared to be a result of having to specify the site column in the list definition using a <Field> rather than <FieldRef> element.

When specifying the list definition using a <ContentTypeRef> element within the schema.xml file, it appeared that whatever was set for the first list in the VSeWSS solution effected all lists that used that combination of Content Type and Site Columns.

Solution
The reason the above behaviour was experienced is because I’d forgotten to change the Type property to a unique value for each of the lists. Thus a change to the first list definition was reflected in all lists.

The Type property is set to 100 by default for all lists created using VSeWSS; it is essential that each list should have a unique Type so this value should be one of the first things that you edit in the ListDefinition.xml and Schema.xml files.

Alternative
Alternatively using the SharePoint Solution Generator to produce a list definition from a list created via the UI based upon the custom content type does appear to work (changing the display name and editing the columns displayed in the All Items view) but this is a long route for what should be a simple enough task.

Examining the schema.xml produced by the SharePoint Solution Generator shows that the list defines it’s own list content type based upon the parent site content type; thus it uses a <ContentType> section rather than a <ContentTypeRef> section.

However it is worth noting that the SharePoint Solution Generator also sets the Type to be 100 and this should be set to a unique value within the scope of your farm.

Notes
Regardless of how the list definition uses the content type, whenever the site column is updated the changes overwrite whatever is defined in the site content type and effects the list directly.

Tip for custom Quick Launch Navigation

March 11, 2009 oidatsmyleg Leave a comment

When creating custom headings within the Quick Launch navigation, whether using the object model or a site definition, always ensure that there is a target URL for each navigation element otherwise the custom heading will be displayed as highlighted.

By way of example, the Microsoft SharePoint Guidance uses the following code to create a custom heading called ‘Dashboards’ in the Quick Launch navigation:

groupNode = new SPNavigationNode("Dashboards", String.Empty);
web.Navigation.QuickLaunch.AddAsFirst(groupNode);

Note that there is no target for this navigation heading, instead String.Empty is being passed in. This results in the ‘Dashboards’ being highlighted within Quick Launch regardless of the actual mouse pointer focus:

Highlighted heading within Quick Launch

Sometimes, as in the case above, there isn’t a obvious target for the heading. However it would still be better to supply a URL rather than leave the target blank as thus avoid the heading being permanently highlighted. To do this, use some code similar to the following:

groupNode = new SPNavigationNode("Dashboards", web.Url, true);

Note the use of the third parameter that specifies that the link is an external node, this is required to make use of the SPWeb.Url property which returns the absolute URL of the web site.