in

SharePoint Blogs

The Best Place for SharePoint-related Blogs

Ton Stegeman [MVP] weblog

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

    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

    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

    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

    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

    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.

    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 12HIVE\Resources. 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:\Inetpub\wwwroot\wss\VirtualDirectories\80\App_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 Files\Common Files\Microsoft Shared\web server extensions\12\Resources”
    • 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

    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

    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

    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}\r\n", contentType.Name);
            textBoxResult.Text += 
                string.Format("    Id=: {0}\r\n", 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}\r\n", layouts.Length.ToString());
     
            // Log details of content type parent
            textBoxResult.Text += 
                string.Format("    Parent=: {0}\r\n", contentType.Parent.Name);
            textBoxResult.Text += 
                string.Format("    ParentId=: {0}\r\n", 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}\r\n", layouts.Length.ToString());
     
            // Log the page layouts
            foreach (PageLayout layout in layouts)
                textBoxResult.Text += string.Format("        Page layout: {0}\r\n", layout.Name);
        }

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

    Content type: Article Page
        Id=: 0x010100C568DB52D9D0A14D9B2FDCC96666E9F2007948130EC3DB064584E219954237AF3900242457EFB8B24247815D688C526CD44D0075C2CA1B0566054FAB8DFC025454149B
        Associated Page layouts: 0
        Parent=: Article Page
        ParentId=: 0x010100C568DB52D9D0A14D9B2FDCC96666E9F2007948130EC3DB064584E219954237AF3900242457EFB8B24247815D688C526CD44D
        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

    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.

     

  • Using MOSS to eliminate paper: multifunctionals and scanners

    This is a repost from one of my collegues, Sjoert Ebben:

    Introduction (by Sjoert):

    This is the first post of a planned series of postings on the topic of using SharePoint to get rid of paper. My intention is to share gathered knowledge on the matter, which means that I don't have a clear idea on what to write in the following articles, but that I share knowledge as it comes along. I welcome all input from the community so please let me know your thoughts and experiences.


    This first posting is on the use of multifunctional devices (MFDs) and other scanning devices.

  • Associating the default page in a SharePoint 2007 publishing site with a custom content type and a custom page layout

    In the MOSS 2007 intranets we build for our customers, we try to encourage them to use custom content types to manage the documents and publishing pages. In these projects we always create new page layouts, new content types and associate the page layouts with the new page layouts. The page layout contains the desired layout of the pages and the usercontrols that make it easier for users to edit the metadata while creating content.

    For some reason we had never done this for the default page in a publishing site, which we had to do in one of our current projects. The issue here was that you need to set the content type and the page layout for the default.aspx in the site definition (onet.xml). At that stage our custom content type is not yet associated with the pages library, because that still is the out of the box Pages library. Once again SharePoint features are the solution here. More specific the “ContentTypeBinding” that is part of the “Elements” element helps here. In this post I will describe how I have set this up.

    • Create a site column called “Region” (this is just custom metadata to show the concept; it is a choice field)
    • Create a new content type called “MyArticleContentType”. It inherits from “Article Page” and has the extra Region site column.
    • Create a new Page Layout called “MyArticleLeft.aspx”. In my demo it is a copy of ArticleLeft. aspx and it has the extra user control for the Region field. I have added this control to the placeholder called “PlaceHolderMain” and added the control just between the ArticleStartDate and the ArticleByLine fields.

                          <SharePointWebControls:DropDownChoiceField 
                                FieldName="Region" 
                                runat="server" 
                                id="regionfield"
                          </SharePointWebControls:DropDownChoiceField>

    • Associate the page layout with the content type. Navigate to the Master page gallery in SharePoint find your page layout and select “Edit Properties” from the context menu. In the Associated Content Type section, select your custom content type, in my case “MyArticleContentType”.
      Pages1
    • Publish and approve your page layout.
    • Create a new feature (I called it “PagesContentTypeAssociation”). The feature.xml is just a reference to an elements manifest file:
    • <?xml version="1.0" encoding="utf-8" ?>
        <Feature  Id="9FAA95C2-E455-4a74-B9D5-C3C81DE8824A"
                Title="Association of content types for Pages Libraries"
                Scope="Web"
                Description="This feature associates content types to existing SharePoint lists."
                Version="1.0.0.0"
                xmlns="http://schemas.microsoft.com/sharepoint/">
        <ElementManifests>
          <ElementManifest Location="elements.xml"/>
        </ElementManifests>
      </Feature>
    • Create the elements manifest. In the Elements element, you need to add an ContentTypeBinding like in the example below:
      <?xml version="1.0" encoding="utf-8" ?>
      <Elements xmlns="http://schemas.microsoft.com/sharepoint/">
        <ContentTypeBinding 
          ContentTypeId="0x010100C568DB52D9D0A14D9B2FDCC96666E9F2007948130EC3DB064584E219954237AF3900242457EFB8B24247815D688C526CD44D0000951B99779D25428408CD40276C0358" 
          ListUrl="Pages" />
      </Elements>
      The ContentTypeId referenced here is the ID of your custom content type. Because I created the content type manually by using the SharePoint user interface, I have to lookup the ID in SharePoint. You can find it by navigating to the properties of your content type and copying the id from the address bar of your browser:
      Pages2
    • Install the feature on your SharePoint farm
    • Create a new site definition. I just copied the “PUBLISHING” folder in the SiteTemplates folder and created a new registration for my new site definition by creating a new xml file called “webtempmycustomsite.xml” in the folder 12HIVE\TEMPLATE1033\XML.
    • Activate our new feature in the onet.xml of the new site definition. It has to be in WebFeatures, because the feature has the Web scope (as we want to activate is for each subsite)

    •     <WebFeatures>
            <!-- Activate the feature to associate the content type to the Pages library -->
            <Feature ID="9FAA95C2-E455-4a74-B9D5-C3C81DE8824A">
            </Feature>
           </WebFeatures>

    • Change the options for the default.aspx in onet.xml. Set both the ContentType and the PublishingPageLayout properties of the File “default.aspx”:
    •     <Module Name="Home" Url="$Resources:cmscore,List_Pages_UrlName;" Path="">
            <File Url="default.aspx" Type="GhostableInLibrary" Level="Draft" >
              <Property Name="Title" Value="My Welcome Page" />
              <Property Name="PublishingPageLayout" 
                        Value="~SiteCollection/_catalogs/masterpage/MyArticleLeft.aspx" />
              <Property Name="ContentType" Value="MyArticlePage" />
            </File>
          </Module>

    • IISRESET to activate the registration of the new site definition
    • Test your new site definition.
      After creating a new site and putting the default page in edit mode, you will see that you page is based on the correct page layout.
      Pages3
      In the properties of the page you can see that it is now based on the new custom content type.

     

  • SharePoint field objects - classname, name and types

    In one of my SharePoint 2007 projects, I was working with SharePoint fields in code. I have spent quite a bit of time searching for all the classnames and fieldtypes that I needed to identify all different field types. In the table below you can find the most important fields.

    Classname Type TypeAsString TypeDisplayName TypeShortDescription
    Microsoft.SharePoint.SPFieldBoolean Boolean Boolean Yes/No Yes/No (check box)
    Microsoft.SharePoint.SPFieldCalculated Calculated Calculated Calculated Calculated (calculation based on other columns)
    Microsoft.SharePoint.SPFieldChoice Choice Choice Choice Choice (menu to choose from)
    Microsoft.SharePoint.SPFieldComputed Computed Computed Computed Computed