You are here: Home

MOSS 2007 Filter webparts part 3 – Adding and connecting SharePoint filter webparts in code and a the page layout

Posted by tonstegeman
No Comments »

On my weblog on tonstegeman.com, I just published the 3rd article in the series on SharePoint filter webparts. It shows you can add filter providers and consumers to SharePoint pages through code. It also shows how to connect the 2 and how to configure the connection.

The last part of the article shows how you can add and connect the same webparts directly in a page layout.

You can find it here.

Querying SharePoint for content – using SPSiteDataQuery and CrossListQueryInfo

Posted by tonstegeman
No Comments »

On my new weblog, I just posted a new article on querying sharepoint sites using the SPSiteDataQuery objects and the CrossListQueryInfo objects. I have also described some of the issues that I ran into while writing the Content By Type webpart.

Moving my weblog to SharePoint

Posted by tonstegeman
No Comments »

At the moment I am busy moving my weblog to a SharePoint weblog. Most articles are already available on the new weblog. You can find my new weblog on this url. I will post links to the new articles on this blog, but it might be better to update your reader settings. If you are using my feedburner url (http://feeds.feedburner.com/tonstegeman) you should be fine, because that is updated to the new url.

Introducing the Content By Type webpart

Posted by tonstegeman
No Comments »

Update 07–11–2007: The beta period is now closed. I will continue to work on the feedback of users who are currently testing the webpart. On my new weblog I will post the updates and the 1.0 release in the coming weeks.

I have been working on a new web part that aggregates content of a specific content type into a gridview. Much like the Content Query web part, but easier to use by end-users. They just need to select the content type and the columns they want to be displayed (that’s why I called it “Content by Type”).

The most important features are:

– Works in WSS and MOSS
– Casting / formatting of column data types
– Links to items and its context in contextmenu
– Links to lookup / person fields
– Filter items using MOSS filter web parts
– Supports grouping/sorting and paging

Image001

The screenshot above shows the Content By Type webpart in action. The view displays 4 columns of this content type. The view is grouped by content type and sorted by Modified date. When configuring the webpart, users simply choose a content type and the web part lets them pick the columns they want. The screenshot below shows part of the configuration. Users can (un)select the available content type fields and configure the links for these fields. After configuring the other web part properties (scope, grouping, sorting, list type, etc.) the web part will query for items that match the selected content type(s) and display the results in a grid.
Image003

Export user information to Excel using "Export to spreadsheet" in SharePoint 2007

Posted by tonstegeman
No Comments »

In mosts lists in SharePoint the Actions menu has an option “Export to spreadsheet”. Using this option you can easily export the contents of the current view to an Excel spreadsheet.I got the question why this link is not available in the User Information list. This is the list that you can access from “People and Groups” – “All People”.

     Userinfo1

The  Actions menu on this page does not have the option “Export to spreadsheet”. You can still export the content of this list by using the url below:

http://[SITEURL]/_vti_bin/owssvr.dll?CS=109&Using=_layouts/query.iqy&List=[LISTID]&View=[VIEWID]&CacheControl=1

In this url, you will need to change these 3 options:

  • [SITEURL] – the url of your top level site
  • [LISTID] – The Guid of the User Information List
  • [VIEWID] – The Guid of the view that you want to export.

The easiest way to get the [LISTID] and [VIEWID] is to select “List Settings” in the “Settings” menu from the screenshot above. First you need to configure a view to have the columns and filters etc. to get the users that you want to export. This is exactly the same process as in any other SharePoint list. While you are configuring your view, the address bar of your browser contains the IDs that you will need:

     Userinfo2

Copy the List and View querystring parameters from this url to the url mentioned above.

Then copy the full url with all correct values in your browser address bar, and the user information will show up in Excel:

     Userinfo3

In my case the full url looks like this:

http://office2007/_vti_bin/owssvr.dll?CS=109&Using=_layouts/query.iqy&List=%7B299130F0%2D2E0B%2D4A02%2D8AF0%2D43BA54C79B28%7D&View=%7B45A446EB%2DCFA4%2D4DEE%2D8616%2D5B43AFBC7B31%7D&CacheControl=1

At this point I thought, I might as well create a feature that add this action as an item to the Actions menu for the list. So I created a feature file and an elements file and added a “CustomAction” element. I set the RegistrationType to List and the RegistrationId to 112, which is the list template type id for this list. After registering and activating the feature, my new menu item did not show up in the actions menu. It looks like this list looks like a normal SharePoint list, but it is not 100%. Although the first screenshot shows an Actions and Settings menu, these items seem not to be created as in all the other lists. If you go to the List Settings of the list, select one of the views and click OK, you will see this screenshot:

     Userinfo4

It looks like the normal Toolbar was removed from the list schema and the menus are added in another way. The difference is that in the screenshot above, we are in the page /_catalogs/users/simple.aspx and in the first screenshot we are on page /_layouts/people.aspx. This people.aspx page contains the menu items for our actions menu. Because it is not recommended to change the SharePoint pages directly, I gave up trying to add my item to the Actions menu.

Instead I created an item in the site settings page. In the “Users and Permissions” sections users now have an opion “Export User Information”:

   Userinfo5

The XML file for my feature:

<Feature 
  xmlns="http://schemas.microsoft.com/sharepoint/"
  Id="E9189036-3847-4D63-8A13-483FDAE44973"
  Title="Export User Information to spreadsheet"
  Description="Menu item to export user information to a spreadsheet."
  Scope="Site">
  <ElementManifests>
    <ElementManifest Location="elements.xml" />
  </ElementManifests>
</Feature>

And the XML file for the elements.xml file: Please note that you will need to replace [LISTID] and [VIEWID] with the appropriate values (see above)

<?xml version="1.0" encoding="utf-8" ?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <CustomAction Id="ADD173BC-C9B2-48EE-A1AF-3529C2338C06"
      GroupId="UsersAndPermissions"
      Location="Microsoft.SharePoint.SiteSettings"
      Sequence="100"
    Title="Export User Information"
      Description="Export User Information to spreadsheet.">
    <UrlAction Url="/_vti_bin/owssvr.dll?CS=109&amp;Using=_layouts/query.iqy&amp;List=[LISTID]&amp;View=[VIEWID]&amp;CacheControl=1"/>
  </CustomAction>
</Elements>

After installing and activating the feature at the site collection features, the menu option is available.

Localizing SharePoint 2007 site columns using resource files.

Posted by tonstegeman
No Comments »

The site columns that are deployed by SharePoint are localized out of the box. In a Dutch SharePoint site, the displayname of field “Date Picture Taken” is “Afbeelding gemaakt op”. In this post I will show you how to do this for your own . The XML definition for the displayname of this field (from the fields feature looks like this:

DisplayName="$Resources:core,Date_Picture_Taken;"

This string is a reference to the resource files. These files can be found in folder 12HIVEResources. The part of the string in the example after “$Resources” references the resource file. In this case, this is “core”. If we take a look in the resources folder, we see there are 3 resource files:

  • core.resx
  • core.en-US.resx
  • core.nl-nl.resx

In our Dutch site, the resource strings are loaded from “core.nl-nl.resx”. In the name of this file, “nl-nl” is the language locale. Please note however that SharePoint does not load all resources from this location at runtime. Resources used in ASPX pages are loaded from the App_GlobalResources folder under your inetpub folder (see this item by Shane Perran and this item by Mikhail Dikov). In most cases this folder is “C:InetpubwwwrootwssVirtualDirectories80App_GlobalResources”. In case of the site columns created in a feature, the resources are loaded from the Resources folder in the 12HIVE.

We can use exact the same mechanism for our own site columns (this is not limited to site columns, but I am using this as an example). Here are the steps:

  • Create a resource file called “tst.resx”
  • Add these 2 items:
      <data name="MyColumns" xml:space="preserve">
        <value>My custom fields</value>
      </data>
      <data name="TST_RegionField" xml:space="preserve">
        <value>Region field description</value>
      </data>
  • Create a resource file called “tst.nl-nl.resx”
  • Add these 2 items:
      <data name="MyColumns" xml:space="preserve">
        <value>Mijn gedefinieerde kolommen</value>
      </data>
      <data name="TST_RegionField" xml:space="preserve">
        <value>Beschrijving regio veld</value>
      </data>
  • Copy the 2 resx files to the Resources folder in the 12 folder:
    “C:Program FilesCommon FilesMicrosoft Sharedweb server extensions12Resources”
  • Create a new feature that add a new site column in the ElementManifest file:
    <?xml version="1.0" encoding="utf-8"?>
    <Elements xmlns="http://schemas.microsoft.com/sharepoint/">
      <Field 
        ID="{373AC34A-AD29-48CF-8E20-D352CACC602D}" 
        Type="Note"
        Name="emfRegionField" 
        StaticName="emfRegionField" 
        Group="$Resources:tst,MyColumns;"
        DisplayName="$Resources:tst,TST_RegionField;"
        Required="FALSE" 
        NumLines="25" 
        RichText="FALSE" 
        Sortable="FALSE" 
        RowOrdinal="0"/>
    </Elements>
  • According to the naming we have used in the first steps, our resource strings now have to be defined like this:
    • $Resources: – identify as resource string
    • tst, – the filename prefix of the resx file
    • TST_RegionField; – identifier for the string
  • Please note that only the displayname and the groupname are localized. The staticname and internal name are equal for all languages.
  • Deploy and activate the feature
  • IISRESET
  • In an English site my site columns now look like this:
         Localize1
  • And the same screenshot from a Dutch site:
         Localize2

Creation and deployment of site columns through features is not covered in this post. There are several good articles out there that can help you with this. One of them is my own, to get you started.

 

MOSS 2007 Filter webparts part 2 – providing and consuming a default value and using Excel Services

Posted by tonstegeman
No Comments »

In this post I described a custom filter provider and consumer webpart for Microsoft Office SharePoint Server 2007. In this post I show how you can make the provider send a default value to other filter webparts. The second step is to show how you can make a webpart accept an incoming default value from another filter webpart. At the end of this post I will give an example of how you can use your filter provider in combination with Excel Services.

Step 1 – Provide a default value

We will change the behaviour of our provider webpart so that it is able to send filter values and a default value to other webparts. Please note that a default value is always single valued. For a webpart that supports multiple values, multiple default values seem not to be supported by the framework. The first thing to do is implement the IDefaultFilterValue interface:

    public class FilterProvider :
        System.Web.UI.WebControls.WebParts.WebPart,
        ITransformableFilterValues, 
        IDefaultFilterValue

In the implementation the value of the first selected checkbox is sent to the consumer of the default value. This is just the first selected value, because the framework only support single valued default values:

    public string DefaultValue
    {
        get
        {
            for (int i = 0; i < _regions.Items.Count; i++)
            {
                if (_regions.ItemsIdea.Selected)
                    return _regions.ItemsIdea.Value;
            }
            return null;
        }
    }

The last thing to do is notify the WebPartManager that we now support default values:

    [ConnectionProvider(
        "Default region", 
        "UniqueIDForRegionDefault", 
        AllowsMultipleConnections = true)]
    public IDefaultFilterValue SetDefaultValueConnection()
    {
        return this;
    }

As described in the previous post, the ConnectionProvider attribute has 3 parameter. The first in de snippet above is the displayname. This shows up when you connect your provider to another filter webpart:

     Filters7

Step 2 – Test the default value provider

The easiest way to test your filter provider is to use the Text Filter WebPart as the consumer. Add both webparts to the page and send the “Default region” to the text filter webpart. After selecting a value you webparts will look like this:

     Filters8

Step 3 – Consume default values

In this step we will change the provider webpart to make it support incoming default values. It will support multiple connections, so that we can setup multiple webparts on our page that can pass a default value to our webpart. Because we support multiple incoming default values, we setup a private member that will hold our connections:

    private List<IDefaultFilterValue> _defaultValues;
 
    private List<IDefaultFilterValue> DefaultValues
    {
        get { return _defaultValues; }
    }
 
    public FilterProvider()
    {
        _defaultValues = new List<IDefaultFilterValue>();
    }

The only other thing to do is to notify the WebPartManager that we now support incoming default values.

    [ConnectionConsumer(
        "default region", 
        "UniqueIDForRegionDefaultConsumer", 
        AllowsMultipleConnections = true)]
    public void SetFilter(IDefaultFilterValue defaultFilterValue)
    {
        if (defaultFilterValue != null)
        {
            DefaultValues.Add(defaultFilterValue);
        }
    }

Again, this attribute takes 3 parameter, where the displayname is used in setting up the connection when initiated from the consuming webpart:

     Filters11

In the OnPreRender of our provider webpart, we need to handle the incoming default values:

    protected override void OnPreRender(EventArgs e)
    {
        foreach (IDefaultFilterValue defaultValue in DefaultValues)
        {
            if (!string.IsNullOrEmpty(defaultValue.DefaultValue))
            {
                foreach (ListItem region in _regions.Items)
                    if (string.Compare(region.Value, defaultValue.DefaultValue, true) == 0)
                        region.Selected = true;
            }
        }
        base.OnPreRender(e);
    }

 

Step 4 – Test the default value consumer

To test the we add the provider webpart to the page and also a Current User Filter webpart. The current user filter webpart reads the value of one of the profile properties and passes that as the default value to our region selector. By managing the profile property for our users, users now automatically have their default region selected when they hit the page.

The Current User Filter webpart is a context filter webpart and therefore is not visible at runtime. When the page is in edit mode, it looks like this:

     Filters10

In this screenshot you see 3 webpart:

  • Current user filter – reads the value from my user profile
  • FilterProvider – gets the default value from the first webpart and passes its value as the default value to the text filter webpart
  • Default region – text filter webpart that gets a default value from the FilterProvider.

Of course this testscenario is not very useful, but it shows that you can use MOSS filter webparts to build pages that make it easy for users to select the content they wish to see. By using default values it makes it even easier. I have used the “Office” property of my profile to set the default value for the users:

     Filters9

The screenshot below shows that our default value consumer supports multiple default values. By using this mechanism, we can set multiple default values in our webpart, but just 1 for each incoming connection. This page has 2 text filter webparts that both send their value as the default value to our FilterProvider webpart:

     Filters12

Step 5 – Test our provider with Excel Services

Filter webparts can also be used to send input to Excel sheets rendered in an Excel Web Access webpart. In the screenshot below you see our provider webpart that sends the selected values to a parameter in the Excel sheet. This Excel sheet shows a pivot table the gets it data from the Analysis Services demo cube Adventureworks.

     Filters15

The Excel Web Access webpart supports 3 types of connections. I used the connection type “Get Filter Values From”. When configuring the connection in the second step (see screenshot below), you can select the named parameters in the sheet.

     Filters14

You can name these parameters when you publish the Excel sheet. When I published my report, I created a parameter called “Geo”:

     Filters16

The only thing I changed to the provider are the available options. Because we now are sending values to Excel Services that connects to Analysis Services, we need to send MDX instead of plain text. My provider is now setup like this. The exact syntax of the MDX of course depends on the dimensions in the cube.

    protected override void CreateChildControls()
    {
        base.CreateChildControls();
        _regions = new CheckBoxList();
        _regions.Items.Add(new ListItem("Australia", "[Geography].[Geography].[Country].&[Australia]"));
        _regions.Items.Add(new ListItem("Canada", "[Geography].[Geography].[Country].&[Canada]"));
        _regions.Items.Add(new ListItem("France", "[Geography].[Geography].[Country].&[France]"));
        _regions.Items.Add(new ListItem("Germany", "[Geography].[Geography].[Country].&[Germany]"));
        _regions.Items.Add(new ListItem("United Kingdom", "[Geography].[Geography].[Country].&[United Kingdom]"));
        _regions.AutoPostBack = true;
        this.Controls.Add(_regions);
    }

By using Excel combined with the out of the box SharePoint filter web parts and your own, you have very flexible, powerful reporting mechanism.

MOSS 2007 Filter webparts part 1 – create your own provider and consumer

Posted by tonstegeman
No Comments »

Microsoft Office SharePoint Server 2007 offers a number of filter webparts. These can be used to gather filtering options from a number of different sources. The filter value(s) selected by the users can be sent to other SharePoint webparts. This connection is supported through the normal webpart connections. The webpart that sends the filter values is called the provider. The webpart that receives and handles the values is called the consumer. Providers can normally send values to filter the consumer or to set the default value for the consumer. Examples of filter providers are:

  • Business Data Catalog Filter
  • Choice Filter
  • Current User Filter
  • SharePoint List Filter

Examples of filter consumers are:

  • SharePoint ListView WebPart
  • Excel Web Access

In this post I will show you how you can write your own provider and consumer.

Step 1 – Create the provider webpart

Our provider webpart is a very simple webpart that just show 4 checkboxes with 4 regions. When the user selects the checkboxes, the selected values are sent to the connected consumers. It exposes just 1 parameter called “Region”. When added to the page, our provider looks like this (page is in edit mode):

     Filters1

The first thing to do is create a new webpart that implements the ITransformableFilterValues interface (from the Microsoft.SharePoint.WebPartPages namespace):

    public class FilterProvider :
        System.Web.UI.WebControls.WebParts.WebPart, 
        ITransformableFilterValues 

We add a private member to our class for the CheckBoxList and setup the control in CreateChildControls:

    private CheckBoxList _regions;
    protected override void CreateChildControls()
    {
        base.CreateChildControls();
        _regions = new CheckBoxList();
        _regions.Items.Add(new ListItem("North"));
        _regions.Items.Add(new ListItem("South"));
        _regions.Items.Add(new ListItem("West"));
        _regions.Items.Add(new ListItem("East"));
        _regions.AutoPostBack = true;
        this.Controls.Add(_regions);
    }

 

To implement the interface, we add these properties/methods:

    public bool AllowEmptyValue
    {
        get { return false; }
    }
    public bool AllowAllValue
    {
        get { return true; }
    }
    public bool AllowMultipleValues
    {
        get { return true; }
    }
    public string ParameterName
    {
        get { return "Region"; }
    }
    public ReadOnlyCollection<string> ParameterValues
    {
        get
        {
            EnsureChildControls();
            List<string> regions = new List<string>();
            for (int i = 0; i < _regions.Items.Count; i++)
            {
                if (_regions.ItemsIdea.Selected)
                {
                    regions.Add(_regions.ItemsIdea.Value);
                }
            }
            ReadOnlyCollection<string> result = new ReadOnlyCollection<string>(regions);
            return result;
        }
    }

Our webpart does not support empty values (we don’t send values if nothing is selected). Our webpart supports the “All” value . The first two properties therefore return false. Because we have 4 checkboxes, we do support multiple values and therefore AllowMultipleValues returns true. These properties are important when the connection to the consumer is made. These options either show or hide connection parameters. The property ParameterName returns the name of the parameter that is used when the provider is connected to the consumer:

     Filters2

In our case this is “Region”. The last property ParameterValues returns the selected values as a readonly collection of strings. The last thing to do is create a new method in our webpart with the “ConnectionProvider” attribute. This makes the WebPartManager expose the connection to available consumers.

    [ConnectionProvider(
        "Region", 
        "UniqueIDForRegionConnection", 
        AllowsMultipleConnections = true)]
    public ITransformableFilterValues SetConnection()
    {
        return this;
    }

This attribute takes (in the overload I used) three parameters:

  • The displayname. This is the name that you will see when setting up the connection:
         Filters3
    I have set this to “Region”, as you can see in the screenshot above.
  • ID – a unique id for the provider connection point
  • Named Parameters – this allows you to set AllowsMultipleConnections. My providers can provider its values to multiple consumers, so I have set this to true.

Step 2 – Test the provider

After compiling the assembly and deploying the webpart, we are ready to test the provider. I have created a new document library called “MyDocs” and added a new multivalued Choice field called “DocumentRegion”. After that I added a ListView webpart and configured it to show the DocumentRegion field. After connection the 2 webparts, selecting one of the checkboxes will filter the list of documents:

     Filters4

Warning: although our provider supports and sends multiple values, the list view webpart seems not to handle this correctly.

Step 3 – Create the consumer webpart

Our provider is now ready to send filters to consumers and we will now create our own provider. This again is a very simple webpart that does nothing but render all values for incoming filters. Again we start to create a new webpart:

    public class FilterConsumer :
        System.Web.UI.WebControls.WebParts.WebPart

We create a private member to hold the incoming filter values (IFilterValues from the Microsoft.SharePoint.WebPartPages namespace). We also add a property for the and initialize it in the constructor:

    private List<IFilterValues> _filterProviders;
 
    private List<IFilterValues> FilterProviders
    {
        get { return _filterProviders; }
    }
 
    public FilterConsumer()
    {
        _filterProviders = new List<IFilterValues>();
    }

 In this example I do not handle the incoming filters in a specific way, but just render the incoming values in the Render method:

    protected override void Render(System.Web.UI.HtmlTextWriter writer)
    {
        foreach (IFilterValues filter in FilterProviders)
        {
            writer.WriteLine(string.Format("Parameter: {0} <br>", filter.ParameterName));
            if (filter.ParameterValues != null)
            {
                foreach (string value in filter.ParameterValues)
                    if (!string.IsNullOrEmpty(value))
                        writer.WriteLine(string.Format("  value: {0} <br>", value));
            }
        }
        base.Render(writer);
    }

The last thing to do to get the consumer working is add a method to configure the connection. We need to add the ConnectionConsumer attribute (System.Web.UI.WebControls.WebParts namespace). This attribute takes the same 3 parameters:

  • displayname – I set this to “filter”. This displayname is used to configure the connection when we initiate the connection from the consumer instead of the provider webpart:
         Filters5
  • id – a unique name assigned to the consumer connection point. 
  • Named parameter – In case our webpart needs to support multiple incoming connections, we pass true for the AllowsMultipleConnections parameter. 
    [ConnectionConsumer(
        "filter", 
        "UniqueIDForConsumer", 
        AllowsMultipleConnections = true)]
    public void SetFilter(IFilterValues filterValues)
    {
        if (filterValues != null)
        {
            EnsureChildControls();
            List<ConsumerParameter> parameters = new List<ConsumerParameter>();
            parameters.Add(new ConsumerParameter(
                "Region", 
                ConsumerParameterCapabilities.SupportsMultipleValues | 
                ConsumerParameterCapabilities.SupportsAllValue));
            parameters.Add(new ConsumerParameter(
                "Status", 
                ConsumerParameterCapabilities.SupportsMultipleValues | 
                ConsumerParameterCapabilities.SupportsAllValue));
            filterValues.SetConsumerParameters(
                new System.Collections.ObjectModel.ReadOnlyCollection<ConsumerParameter>(parameters));
            this.FilterProviders.Add(filterValues);
        }
    }

The SetFilter method above serves 2 purposes. It stored details about the incoming connections and values. It also exposes the parameters the our consumer exposes and the properties of these parameters. Matching combinations of these parameters and the options of the provider webpart allow you to connect the provider and consumer. If these options do not match, the parameter will not show up when configuring the connection. In my example, the consumer webpart exposes 2 parameters; Region and Status. The options used for these parameters (SupportsMultipleValues and SupportsAllValue) allow me to connect to our own provider, to the Choice Filter webpart and the Text Filter provider webpart. A provider webpart that supports the “All” “value for example can never connect to a consumer parameter the does not have the SupportsAllValue option. This parameter simply does not show up in the “Configure Connection” dialog.

Step 4 – Test the consumer

To test the consumer, I have added our own provider, a Text Filter webpart and a Choice Filter webpart that supports multiple values to the page. These are all connected to our custom consumer. After selecting values in each of the 3 providers, our consumer looks like this:

     Filters6

Filter webparts are a very flexible, very powerful way to organize content on your pages. In the next post, I will describe how you can use filter providers to pass a default value to other webparts and how you can provide a value to a parameter in an Excel sheet in the EWA webpart. If you are looking for more information, this page is an excellent start:

 

Getting the associated page layouts from a SharePoint document library

Posted by tonstegeman
No Comments »

In MOSS 2007 you can associate a page layout with a content type. The MOSS publishing feature uses this mechanism to select the content type for the new page that you create by using the “Create Page” option from the Site Actions menu. By selecting a page layout, you directly select the content type for your content page.

In code I needed to find out what Page Layouts are associated with the content types that are associated with a document library. In this example I created a very simple WinForms application that lists the available page layouts for the content types associate with the “Pages” library in the “News” site of a publishing portal.

This document library has these content types associated:

    Pagelayouts1

By navigating to the Master Page gallery, we can find out what page layouts are associated to the “Article Page” content type:

      Pagelayouts2

In this case we have 5 page layouts associated to our content type. This is exactly what we want to achieve, but now in code. The first thing to do is get the SPList object for the document library:

    SPSite site = new SPSite(textBoxWeb.Text);
    SPWeb web = site.OpenWeb();
    SPList list = web.Lists[textBoxLibrary.Text];

The namespace Microsoft.SharePoint.Publishing contains a class called “PublishingWeb”. By using this line of code we can get a reference to the publishing web:

    PublishingWeb publishingWeb = PublishingWeb.GetPublishingWeb(web);

Now we can iterate through the associated content types and find the page layouts associated to the content type using the GetAvailablePageLayouts method. The thing to remember here first is that  the content type associated to the document library is not the content type that we associated to the page layout. When a content type is associated to a list, SharePoint automatically generates a new content type that inherits from the content type we choose and associates that to the list. So instead of trying to find what page layouts using the content type, we need to use the parent of our content type. The output log of our code below demonstrates this. First the code:

    foreach (SPContentType contentType in list.ContentTypes)
    {
        // Log details of content type
        textBoxResult.Text += 
            string.Format("Content type: {0}
", contentType.Name);
        textBoxResult.Text += 
            string.Format("    Id=: {0}
", contentType.Id);
 
        // Try to find associated page layouts and log number found
        PageLayout[] layouts = 
            publishingWeb.GetAvailablePageLayouts(contentType.Id);
        textBoxResult.Text += 
            string.Format("    Associated Page layouts: {0}
", layouts.Length.ToString());
 
        // Log details of content type parent
        textBoxResult.Text += 
            string.Format("    Parent=: {0}
", contentType.Parent.Name);
        textBoxResult.Text += 
            string.Format("    ParentId=: {0}
", contentType.Parent.Id);
 
        // Try to find associated page layouts for parent and log number found
        layouts = publishingWeb.GetAvailablePageLayouts(contentType.Parent.Id);
        textBoxResult.Text += 
            string.Format("    Associated Page layouts: {0}
", layouts.Length.ToString());
 
        // Log the page layouts
        foreach (PageLayout layout in layouts)
            textBoxResult.Text += string.Format("        Page layout: {0}
", layout.Name);
    }

This results in this output for the Article Page content type:

Content type: Article Page
    Id=: 0×010100C568DB52D9D0A14D9B2FDCC96666E9F2007948130EC3DB064584E219954237AF3900242457EFB8B24247815D688C526CD44D0075C2CA1B0566054FAB8DFC025454149B
    Associated Page layouts: 0
    Parent=: Article Page
    ParentId=: 0×010100C568DB52D9D0A14D9B2FDCC96666E9F2007948130EC3DB064584E219954237AF3900242457EFB8B24247815D688C526CD44D
    Associated Page layouts: 5
        Page layout: PageFromDocLayout.aspx
        Page layout: ArticleLeft_copy(1).aspx
        Page layout: ArticleLeft.aspx
        Page layout: ArticleRight.aspx
        Page layout: ArticleLinks.aspx

By looking at the Id you can see that a new content type called Article Page is attached to the Pages library and we can find the page layouts by using the Parent. 

Creating custom editor parts for a SharePoint webpart

Posted by tonstegeman
No Comments »

For one of my SharePoint webparts I created a custom EditorPart to edit the properties of the webpart. I had some issues with this editorpart that caused me a headache.My webpart is an ASP.NET 2.0 webpart (System.Web.UI.WebControls.WebParts) that implements the IWebEditable interface. Here is the code of my webpart:

    public class TestEditorPart : System.Web.UI.WebControls.WebParts.WebPart, IWebEditable
    {
        private string _myMessage;
 
        [WebBrowsable(false)]
        [Personalizable(PersonalizationScope.Shared)]
        public string MyMessage
        {
            get { return _myMessage; }
            set { _myMessage = value; }
        }
 
        protected override void Render(HtmlTextWriter writer)
        {
            base.Render(writer);
            writer.WriteLine(string.Format("Message: {0}.", MyMessage));
        }
 
        EditorPartCollection IWebEditable.CreateEditorParts()
        {
            List<EditorPart> editors = new List<EditorPart>();
            editors.Add(new MyEditorPart());
            return new EditorPartCollection(editors);
        }
 
        object IWebEditable.WebBrowsableObject
        {
            get { return this; }
        }
 
    }

In CreateEditorParts() my custom editorpart is created and returned in a new collection. My editor part is called MyEditorPart. I will not discuss the code in detail, because it is pretty straight forward.

    public class MyEditorPart : EditorPart
    {
        private TextBox _message;
        protected override void CreateChildControls()
        {
            base.CreateChildControls();
            _message = new TextBox();
            Controls.Add(_message);
        }
 
        public override bool ApplyChanges()
        {
            EnsureChildControls();
            TestEditorPart webPart = WebPartToEdit as TestEditorPart;
            if (webPart != null)
            {
                webPart.MyMessage = _message.Text;
            }
 
            return true;
        }
 
        public override void SyncChanges()
        {
            EnsureChildControls();
            TestEditorPart webPart = WebPartToEdit as TestEditorPart;
            if (webPart != null)
            {
                _message.Text = webPart.MyMessage;
            }
        }
    }

After compiling the assembly, deploying it to my SharePoint 2007 server and registering the webpart, I added the webpart to the page. After clicking the “Modify”Shared Web Part””, the page crashes with message “An unexpected error has occurred.”:

                Editorpart1

After adding a constructor to MyEditorPart and setting an ID for my editor part, this problem is solved.

        public MyEditorPart()
        {
            this.ID = "MyEditorPart";
        }

While testing the webpart, I used this webpart twice on the same page. I changed the message for the first webpart to “Message 1”. Then I set the message for the other webpart to a different text and switched back to the properties of the first webpart. Although I have the correct webpart selected (see screenshot below) the textbox in the editor part shows the message of the other webpart. See the screenshot below.

                Editorpart2

After some serious debugging I found out that the ID of the editor part has to be unique for each instance of your webpart. I changed the constructor of my EditorPart to take the ID of the webpart as a parameter. This solved my problem.

        public MyEditorPart(string webPartID)
        {
            this.ID = "MyEditorPart" + webPartID;
        }

In the webpart code I changed CreateEditorParts to pass the ID in the constructor: 

        EditorPartCollection IWebEditable.CreateEditorParts()
        {
            List<EditorPart> editors = new List<EditorPart>();
            editors.Add(new MyEditorPart(this.ID));
            return new EditorPartCollection(editors);
        }

You probably already know this if you develop SharePoint webpart, but I didn’t and after nearly getting crazy because my text boxes displayed the wrong values in the editor part, I decided to share it.