Using MOSS to eliminate paper: multifunctionals and scanners

July 31st, 2007 by tonstegeman

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

July 23rd, 2007 by tonstegeman

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 12HIVETEMPLATE1033XML.
  • 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

July 23rd, 2007 by tonstegeman

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
Microsoft.SharePoint.SPFIeld ContentTypeId ContentTypeId Content Type Id Content Type Id
Microsoft.SharePoint.SPFieldCurrency Currency Currency Currency Currency ($, , ?)
Microsoft.SharePoint.SPFieldDateTime DateTime DateTime Date and Time Date and Time
Microsoft.SharePoint.SPFieldFile File File File File
Microsoft.SharePoint.SPField Guid Guid Guid Guid
Microsoft.SharePoint.SPFieldNumber Integer Integer Integer Integer
Microsoft.SharePoint.Portal.WebControls.BusinessDataField Invalid BusinessData Business data Business data
Microsoft.SharePoint.Publishing.Fields.ContentTypeIdFieldType Invalid ContentTypeIdFieldType Content Type Id Content Type Id
Microsoft.SharePoint.Publishing.Fields.HtmlField Invalid HTML Publishing HTML Full HTML content with formatting and constraints for publishing
Microsoft.SharePoint.Publishing.Fields.ImageField Invalid Image Publishing Image Image with formatting and constraints for publishing
Microsoft.SharePoint.Publishing.Fields.LayoutVariationsField Invalid LayoutVariationsField Variations Page Layout Variations
Microsoft.SharePoint.Publishing.Fields.LinkField Invalid Link Publishing Hyperlink Hyperlink with formatting and constraints for publishing
Microsoft.SharePoint.Publishing.Fields.PublishingScheduleEndDateField Invalid PublishingScheduleEndDateFieldType Publishing Schedule End Date Publishing Schedule End Date
Microsoft.SharePoint.Publishing.Fields.PublishingScheduleStartDateField Invalid PublishingScheduleStartDateFieldType Publishing Schedule Start Date Publishing Schedule Start Date
Microsoft.SharePoint.Publishing.Fields.SummaryLinkField Invalid SummaryLinks SummaryLinks Summary Links data
Microsoft.Office.Server.WebControls.FieldTypes.SPFieldTargetTo Invalid TargetTo Audience Targeting Audience Targeting
Microsoft.SharePoint.SPFieldLookup Lookup Lookup Lookup Lookup (information already on this site)
Microsoft.SharePoint.SPFieldLookup Lookup LookupMulti Lookup Lookup (information already on this site)
Microsoft.SharePoint.SPFieldNumber Number Number Number Number (1, 1.0, 100)
Microsoft.SharePoint.SPFieldRecurrence Recurrence Recurrence Recurrence Recurrence
Microsoft.SharePoint.SPFieldMultiLineText Note Note Multiple lines of text Multiple lines of text
Microsoft.SharePoint.SPFieldText Text Text Single line of text Single line of text
Microsoft.SharePoint.SPFieldUrl URL URL Hyperlink or Picture Hyperlink or Picture
Microsoft.SharePoint.SPFieldUser User User Person or Group Person or Group

The second column contains the value of SPField.Type. This is one of the values of the SPFieldType enumeration. All fields that have a Type other that “Invalid” can be created by using the Add method of a SPFieldCollection object and passing Name, FieldType (SPFieldType) and Required. The other fields can be added by instantiating an object of the specified class and adding that SPField object to the SPFieldCollection. Below you will find 2 samples:

    SPSite site = new SPSite("http://office2007");
    SPWeb web = site.OpenWeb();
    SPList list = web.Lists["Documenten"];
 
    // Add a new text field
    list.Fields.Add("NewTextField", SPFieldType.Text, false);
 
    // Add a new HTML field
    HtmlField htmlField = new HtmlField(
        list.Fields, "HTML", "NewHTMLField");
    list.Fields.Add(htmlField);

The string that is passed as the second parameter in the second sample, is the value in the “TypeAsString” column in the table.

Using SharePoint 2007 audiences in combination with MOSS Publishing placeholders

July 18th, 2007 by tonstegeman

In one of our projects we had a requirement to target the content of a SharePoint Publishing HTML field to an audience. The content should only be displayed to users that are member of an audience. It was very easy to create this and below you can find the steps to create this yourself:

  • Create a new class that inherits from Microsoft.SharePoint.Publishing.WebControls.RichHtmlField
  • Add a public property that holds the name of the audience
  • Override the Onload and implement this as below:
        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
            SPSite site = SPControl.GetContextSite(Context);
            ServerContext context = ServerContext.GetContext(site);
            AudienceManager audienceManager = new AudienceManager(context);
            Audience audience = audienceManager.GetAudience(this.Audience);
            SPUser user = SPControl.GetContextWeb(Context).CurrentUser;
            if (audience == null ||
                !audience.IsMember(user.LoginName))
            {
                this.Visible = false;
            }
        }
  • Add a new Publishing HTML field to the Pages library called “MyTargetedField”.
  • Edit one of your Page Layouts and add a registration for your assembly :
    <%@ Register Tagprefix="EogWebControls" Namespace="TST.WebParts.AudienceRichHtmlField"
    Assembly="TST.WebParts, Version=1.0.0.0, Culture=neutral, PublicKeyToken=503edd7b21a430b3" %>
  • Add the new webpart to your page layout and set the Audience property to “MyAudience” :
    <EogWebControls:AudienceRichHtmlField id="PageContent4" Audience="MyAudience" FieldName="MyTargetedField" runat="server"/>
  • Create an audience “MyAudience” with some rules and compile it.
  • Create a page based on this page layout and test if the content you entered in the “MyTargetedField” only shows up for members of the “MyAudience” audience.

As you can see, it is very easy to do with just a few lines of code.

Erwin, thanks for preparing this!

Deploy a SharePoint list template to SharePoint MySites

July 10th, 2007 by tonstegeman

A collegue of mine prepared a list in her SharePoint 2007 MySite. Her question was how she could make this list available as a template in everybody's MySite. The issue with this is that every MySite is a separate Site Collection, and therefore every MySite has its own site columns, content types and template galleries. This makes it difficult to deploy to list template to 1 location from where it can be used.

There are a number of ways how you can get around this. I choose to create a feature that uploads STP files to the list template gallery when the feature gets activated. The feature gets activated in the site definition, so when a user creates his/her MySite for the first time, the STP files are uploaded to the list template gallery. In our case, this was only part of the solution, because the feature is not activated on existing MySites. I created a STSADM command that activates the feature if it is not yet activated. This is a generic command that you can use to (de)activate any site feature on all sites based on a specific template.

Step 1 – Create a FeatureReceiver

The first step is to create a FeatureReceiver that uploads the STP file if it does not exist. If the list template is already available in the list template gallery, the feature deletes the file and uploads the latests version. The feature checks the feature folder for a subfolder called “ListTemplates“. All STP files in this folder are uploaded to the List template gallery. First we get a reference to the site the feature gets activated for and the STP files in the folder:

    public override void FeatureActivated(SPFeatureReceiverProperties properties)
    {
        SPSite site = properties.Feature.Parent as SPSite;
 
        if (site != null)
        {
            try
            {
                string directory = properties.Definition.RootDirectory;
                if (!directory.EndsWith(@""))
                    directory += @"";
                directory += "ListTemplates";
                if (System.IO.Directory.Exists(directory))
                {
                    string[] templates = System.IO.Directory.GetFiles(
                        directory, "*.stp", System.IO.SearchOption.TopDirectoryOnly);
                    SPDocumentLibrary listTemplates =
                        site.GetCatalog(SPListTemplateType.ListTemplateCatalog)
                        as SPDocumentLibrary;
                    UploadTemplates(listTemplates, templates);
                }
            }
            finally
            {
                site.Dispose();
            }
        }
    }

The code snippet also gets reference to the List template gallery. This gallery is a SharePoint document library and can be found using the GetCatalog function of our SPSite object. The method `UploadTemplates' actually takes care of uploading the STP file:

 

    private void UploadTemplates(SPDocumentLibrary templateGallery, string[] templateFiles)
    {
        if (templateGallery != null)
        {
            foreach (string template in templateFiles)
            {
                System.IO.FileInfo fileInfo = new System.IO.FileInfo(template);
                SPQuery query = new SPQuery();
                query.Query = string.Format(
                    "<Where><Eq><FieldRef Name='FileLeafRef'/>" +
                    "<Value Type='Text'>{0}</Value></Eq></Where>", fileInfo.Name);
                SPListItemCollection existingTemplates = templateGallery.GetItems(query);
 
                int[] Ids = new int[existingTemplates.Count];
                for (int i = 0; i < existingTemplates.Count; i++)
                {
                    Ids[ i ] = existingTemplates[ i ].ID;
                }
                for (int j = 0; j < Ids.Length; j++)
                {
                    templateGallery.Items.DeleteItemById(Ids[j]);
                }
 
                byte[] stp = System.IO.File.ReadAllBytes(template);
                templateGallery.RootFolder.Files.Add(fileInfo.Name, stp);
            }
        }
    }

This method first checks the template gallery if a template with the same filename exists. If it exists, it is deleted first. Then the STP is uploaded to  the rootfolder of the gallery. This turned out very handy, because just after I installed this to our production environment and uploaded the STP to all MySites, there was a change in the schema of the list… It was very easy to redeploy the new list template:

  • Deactivate the feature on all MySites using my custom STSADM command (see step 4)
  • Copy the new STP file to the ListTemplates folder in the feature folder
  • Activate the feature on all existing MySites

The class you create should inherit from Microsoft.SharePoint.SPFeatureReceiver. You will have to compile this into an assembly and install that to your SharePoint server(s).

Step 2 – Create the feature XML

The next step is to create the XML for the feature and install the feature using STSADM. The xml for my feature looks like this:

  <?xml version="1.0" encoding="utf-8" ?>
  <Feature  Id="3DB2BE7A-E5DD-400d-B853-EB1F59E57E85"
            Title="Template deployment for list templates"
            Description="This feature uploads list templates to the Template Gallery of a site collection."
            Version="1.0.0.0"
            Scope="Site"
            ReceiverAssembly="TST.FeatureReceivers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=503edd7b21a430b3"
            ReceiverClass="TST.FeatureReceivers.TemplateDeployment"
            xmlns="http://schemas.microsoft.com/sharepoint/">
  </Feature>

This xml needs to be saved in a file called “feature.xml” that is copied into a subfolder of the TEMPLATEFEATURES folder in the 12 hive. The feature can be installed using STSADM.

Step 3 – Create a Stapler for the feature

After installing the feature we need to activate it. Normally you create a new site definition and activate the feature from the site definition (in onet.xml). For the MySite this is difficult, because it is not a very good idea to modify the existing SharePoint site definitions. SharePoint always uses the SPSPERS site definition and that cannot be changed. This is where feature stapling can help. Read this blog by Chris O'Brian if you want to learn more about stapling, or read this item by Steve Peschka. You simply create a new feature folder with a feature.xml file like below. 

  <?xml version="1.0" encoding="utf-8" ?>
  <Feature  Id="ED23C370-9FC1-4d76-8495-F3BAEFE931CA"
            Title="Stapler for TemplateDeployment feature"
            Scope="Farm"
            xmlns="http://schemas.microsoft.com/sharepoint/">
    <ElementManifests>
      <ElementManifest Location="elements.xml"/>
    </ElementManifests>
  </Feature>

This is a feature that is “Farm” scoped and adds our TemplateDeployment feature to the site collection features to be activated with the SPSPERS site definition. In the elements.xml that is referenced in this feature file, you add this xml. Please note that the Guid in the Id attribute of the FeatureSiteTemplateAssociation element is the same as the Id of the feature (see xml in step 2).

  <?xml version="1.0" encoding="utf-8" ?>
  <Elements xmlns="http://schemas.microsoft.com/sharepoint/">
    <FeatureSiteTemplateAssociation 
      Id="3DB2BE7A-E5DD-400d-B853-EB1F59E57E85"
      TemplateName="SPSPERS#0" />
  </Elements>

Then you install the feature using STSADM.

Step 4 – Create a STSADM command

The next step is to create the STSADM command that activate/deactivates site scoped features. To do this, you create a new class that inherits from ISPStsadmCommand. This base class is available in the Microsoft.SharePoint.StsAdmin namespace. In your class you will need to implement GetHelpMessage and a Run method. In the first method, you return the help text for users that ask for help on the command prompt. The Run method has 3 parameters:

  • command – the command that the STSADM user is using (that is entered after “-o”).
  • keyValues – a StringCollection containing the options that the user has entered
  • output – an output variable that you can set to give feedback to the user about the result of your command.

An example of my STSADM command:

     stsadm" -o activatesitefeature -featurefolder MySiteListTemplates -template SPSPERS

In this case “activatesitefeature” is the command in the Run method and “featurefolder” and “template” are the settings that I need. They are part of the keyValues. Below you will find my implementation of the Run command. Just below the code you will find some comments on this piece of code.

    public int Run(string command, System.Collections.Specialized.StringDictionary keyValues, out string output)
    {
        if (command != "activatesitefeature" && command != "activatesitefeature")
        {
            throw new ArgumentException("Invalid command");
        }
        string folder = keyValues["featurefolder"];
        if (string.IsNullOrEmpty(folder))
            throw new ArgumentException("Parameter 'featurefolder' must have a value.");
 
        string template = keyValues["template"];
        if (string.IsNullOrEmpty(template))
            throw new ArgumentException("Parameter 'template' must have a value.");
 
        SPFeatureDefinition activateFeature = null;
 
        int errors = 0;
        SPSecurity.RunWithElevatedPrivileges(delegate()
        {
            foreach (SPFeatureDefinition feature in SPFarm.Local.FeatureDefinitions)
            {
                if (feature.RootDirectory.ToLower().EndsWith(folder.ToLower()))
                {
                    activateFeature = feature;
                    break;
                }
            }
            if (activateFeature == null)
            {
                throw new ArgumentException(string.Format(
                    "Feature in folder {0} not found. Please install the feature first.", 
                    folder));
            }
            if (activateFeature.Scope != SPFeatureScope.Site)
            {
                throw new ArgumentException(string.Format(
                    "The scope for feature in folder {0} is not correct; should be Site.", 
                    folder));
            }
 
            SPWebService service = SPFarm.Local.Services.GetValue<SPWebService>("");
            foreach (SPWebApplication webApplication in service.WebApplications)
            {
                for (int i = 0; i < webApplication.Sites.Count; i++)
                {
                    using (SPSite site = webApplication.SitesIdea)
                    {
                        using (SPWeb web = site.RootWeb)
                        {
                            if (string.Compare(web.WebTemplate, template, true) == 0)
                            {
                                bool activate = true;
                                Guid remove = Guid.Empty;
                                foreach (SPFeature checkFeature in site.Features)
                                {
                                    if (checkFeature.DefinitionId == activateFeature.Id)
                                    {
                                        switch (command.ToLower())
                                        {
                                            case "activatesitefeature":
                                                activate = false;
                                                break;
                                            case "deactivatesitefeature":
                                                remove = checkFeature.DefinitionId;
                                                break;
                                        }
                                    }
                                }
                                switch (command.ToLower())
                                {
                                    case "activatesitefeature":
                                        if (activate)
                                        {
                                            try
                                            {
                                                site.Features.Add(activateFeature.Id);
                                            }
                                            catch (Exception ex)
                                            {
                                                errors++;
                                                LogMessage(string.Format("Error: {0}", ex.Message));
                                            }
                                        }
                                        break;
                                    case "deactivatesitefeature":
                                        if (remove != Guid.Empty)
                                        {
                                            try
                                            {
                                                site.Features.Remove(remove, true);
                                            }
                                            catch (Exception ex)
                                            {
                                                errors++;
                                                LogMessage(string.Format("Error: {0}", ex.Message));
                                            }
                                        }
                                        break;
                                }
                            }
                        }
                    }
                }
            }
        });
        output = string.Format("Deactivation completed with {0} errors.", errors);
        return 0;
    }

First all input parameters (command and options) are validated. After that you will see that I used SPSecurity.RunWithElevatedPrivileges, this is because the STSADM command will run in the context of the user who is logged on to the SharePoint server to run the command. This user typically is a farm administrator, but this person probably does not have the permissions to activate a feature on a MySite. By running the code with elevated privileges, the code runs with Full Control. The next step is to search for the featuredefinition that the user wants to activate. If this feature cannot be found, or the scope of the feature is wrong, an error message is raised. In the next step I start to iterate through all web applications on the SPWebService service. For each SPSite in all web applications, I check if the WebTemplate of the RootWeb is equal to the value of the template parameter passed by the user. If these are equeal, the site feature gets activated (if it is not yet activated) or deactivated, depending on the command. Activating a feature is done by using the Add method of the Features collection of the site and passing the guid of the featuredefinition.

Step 5 – Register the STSADM command

After compiling and deploying the assembly, the STSADM command needs to be registered. This can be done by creating a xml file with the xml below. The file should be named “stsadmcommands[myname].xml” where [myname] is your unique name. In this file you register the commands and link them to the assembly:

  <?xml version="1.0" encoding="utf-8" ?>
  <commands>
    <command
      name="activatesitefeature"
      class="TST.STSADM.FeatureActivation, 
          TST.STSADM, Version=1.0.0.0, Culture=Neutral, 
          PublicKeyToken=503edd7b21a430b3"
      />
    <command
      name="deactivatesitefeature"
      class="TST.STSADM.FeatureActivation, 
          TST.STSADM, Version=1.0.0.0, Culture=Neutral, 
          PublicKeyToken=503edd7b21a430b3"
      />
  </commands>

This xml needs to be copied to the CONFIG folder in the 12 hive.

Step 6 – Create an installer

The last step I took was to create a solution deployment package. This makes it much easier to install all bits to the server. There are multiple items about creating and installing such a package, so I won't show this here.

I hope this post helps you if you have a similar scenario. I also use this same mechanism to deploy site templates (STP). Otherwise I hope it helps you realize SharePoint features are very valuable and powerful.

 

SharePoint 2007 Filter webparts – using the Page Field Filter webpart on a Lookup field

May 17th, 2007 by tonstegeman

This post describes an issue we were having using the SharePoint PageFieldFilter webpart in combination with a Lookup field. First the issue is explained, then a solution is provided.

The issue

In our intranet we were setting up a product catalog. The documentation for the products are stored in 1 document library. This doclib has a (multivalued) lookup field to the products list. We wanted to create 1 page per product in the same publishing site. This page will the the landing page for a product. It will show general information and the available documentation. To do this we added a lookup field to the Pages library to the same product list and added the PageFieldFilter webpart to the page. Turns out this does not work for a lookup field (the same applies to a user/group field).

Here is how you recreate this (it also shows the reasons why it doesn’t work):

  • Create a Publishing site
  • Create a list “Products” and add some items
  • Create a document library “Documentation” and add some documents
  • Add a (multivalued) lookup field to the document library
  • Select product(s) for the documentation:
    Filters1
  • Add a (singlevalued) lookup field to the Pages library
  • Add a new page and select one of your products:
    Filters2
  • Add the “Documentation” library to the page and show the name and product fields.
  • Add the Page Field Filter webpart to the page and select the “Product” field.
  • Connect the filter webpart to the Documentation list.
  • Check-in and notice that the list shows no documents:
    Filters3

The filter webpart does not provide the correct value to the consuming webparts. In the screenshot below you will see why.After adding a TextFilter webpart to the page, you can set the default value for this text filter from the Page Field Filter webpart.

Filters4

This shows our problem; the value is not the title of the selected item, but it is the value that SharePoint uses internally to store the value. This applies to both Lookup and Person or Group fields.

The solution

The solution for this is to build our own custom Page Field Filter webpart. You can do this by creating a new webpart that inherits from the ContextFilterWebPart in the Microsoft.SharePoint.Portal.WebControls namespace. Below I describe the most important bits to include in your code. My webpart is called “WebpageFieldFilter”.

  • Create a private member and a property of type Guid (called “PageFieldID”)
  • Create a new EditorPart that renders a dropdown showing all field in the pages library. These fields can be added directly from the context:
           SPContext.Current.List.Fields

    Also implement the ApplyChanges and SyncChanges methods:

        public override bool ApplyChanges()
        {
            WebpageFieldFilter webpart = WebPartToEdit as WebpageFieldFilter;
            webpart.PageFieldID = new Guid(_selectField.SelectedValue);
            return true;
        }
     
        public override void SyncChanges()
        {
            EnsureChildControls();
            WebpageFieldFilter webpart = WebPartToEdit as WebpageFieldFilter;
            _selectField.SelectedValue = webpart.PageFieldID.ToString("B");
        }
  • Implement the IWebEditable interface on your webpart. Implement CreateEditorParts. This should return the new EditorPart we just created, but also the EditorParts that are generated by the base webpart, otherwise we won’t have our filter options:
    .cf { font-family: Courier New; font-size: 9pt; color: black; background: white; } .cl { margin: 0px; } .cb1 { color: teal; } .cb2 { color: blue; }
        EditorPartCollection IWebEditable.CreateEditorParts()
        {
            List<EditorPart> newEditors = new List<EditorPart>();
     
            EditorPartCollection editors = base.CreateEditorParts();
            foreach (EditorPart part in editors)
                newEditors.Add(part);
     
            newEditors.Add(new SelectListFieldEditor());
     
            return new EditorPartCollection(newEditors);
        }
  • In the webpart class, override FilterConfigured and return true if our PageFieldID has a valid Guid value.
  • In the webpart class, override DesignTimeHtml to return the correct text for your webpart in design mode.
  • Override GetRawFilterValues to return the values for the field that was chosen by the user:
        protected override string[] GetRawFilterValues()
        {
            SPListItem listItem = SPContext.Current.ListItem;
            if (listItem != null && PageField != null)
            {
                SPField field = SPContext.Current.List.Fields[PageFieldID];
                return GetFieldValues(listItem[PageFieldID], field);
            }
            return null;
        }

    In GetFieldValues I implemented the code to extract the value for the lookup and user field and return a string array with the value or the values in case of a multi valued field. You can find the code to do this in the ZIP file (see attachments).

After installing the webpart, you can add the new WebpageFieldFilter to the page and connect that to the Documentation library. When modifying the properties of the webpart, things look a bit different, because of the implementation of our custom EditorPart:
Filters6

And when we look at the page, the documentation is now filtered as expected:

Filters5

The webpart also works if you add a multivalued lookup field to the Pages library. It then provides all selected products in a string array to the consuming webparts. When I was testing this it looked like handling these multiple valued filters was not always correct. I’ll investigate this a bit further.

Enjoy!

You can find a WSP file containing a working version of the webpart. If you don’t know what to do with a wsp file, start looking at Chris Johnson’s blog, or use the SharePoint Solution Installer. Attached to this post, you will also find the full code for the webpart. Both are in the same ZIP file. Do whatever you like to do with them, but please let me know if you use them and what you think of it. Might give me some ideas to improve it. Enjoy!

Using Analysis Services data in Excel Services – Overview and updates

April 30th, 2007 by tonstegeman

Some time ago I wrote a small series on setting up Excel Services to connect to SQL Services Analysis Services using Kerberos. Based on feedback from my customer and people in the community, I realized something was wrong in the first part on setting up the AD. I have just rewritten the first part. The inital version had the wrong SPNs registered.

In the image below I have created a schematic overview of my setup. Click to view a larger version.

Excel Services - setup

Part 1 – Preparing the AD for Kerberos
Part 2 – Preparing the MOSS server
Part 3 – Create and test an Excel sheet
Part 4 – Overview and updates

Thanks to the people in the community who helped me out!

What a great day: I got an MVP Award!!

April 1st, 2007 by tonstegeman

Today I got an e-mail telling me that I was awarded MOSS MVP!! Needless to say that I am very excited about that! Thank you very much those who nominated me!

SharePoint 2007 development exams: the results

March 27th, 2007 by tonstegeman

Last week I got the e-mails I was waiting for since January; I passed both beta SharePoint 2007 exams that I took :-) .

So I am now a Microsoft Certified Solution Specialist in:

  • Microsoft  Windows SharePoint Services 3.0: Application Development
  • Microsoft Office SharePoint Server 2007 Application Development

Renee Hulsman, who is one of my collegues, also passed his MOSS 2007 Application Development exam. Nice!

Document templates and SharePoint 2007 content types

March 27th, 2007 by tonstegeman

One of the most powerful new features in WSSv3 (and of course MOSS 2007) are content types. One of the benefits of having content types is that we now can have multiple document templates in one single document library. Each content type can have a document template. By attaching multiple content types to a library, we can offer multiple templates to our users. Just as in the previous version of SharePoint we can still set a document template for a specific document library.

Document templates can be set in the SharePoint user interface, but it can also be done in code. This post will show some examples that will help you to get started.

Example 1 – Template for a document library

This example shows how you can upload a document to SharePoint and use that as the document template for your document library. First the code checks if a document library called ‘MyDocuments’ is available. If not it is created. If the document is not yet uploaded to the Forms folder in the library, it is uploaded. The last step in this codesample sets the DocumentTemplateUrl property of the library.

    string libraryName = "MyDocuments";
    string templateName = "MyTemplate.doc";
 
    SPSite site = new SPSite("http://office2007");
    SPWeb web = site.OpenWeb();
    SPDocumentLibrary doclib = null;
    foreach (SPList list in web.Lists)
    {
        if (list.Title == libraryName)
        {
            doclib = (SPDocumentLibrary)list;
            break;
        }
    }
    if (doclib == null)
    {
        System.Guid doclibid = web.Lists.Add(libraryName, string.Empty, web.ListTemplates["Document Library"]);
        doclib = (SPDocumentLibrary)web.Lists[doclibid];
    }
    SPFolder formsFolder = doclib.RootFolder.SubFolders["Forms"];
    SPFile template = null;
    foreach (SPFile file in formsFolder.Files)
    {
        if (file.Name == templateName)
        {
            template = file;
            break;
        }
    }
    if (template == null)
    {
        byte[] doc = System.IO.File.ReadAllBytes(string.Format(@"c:{0}", templateName));
        template = formsFolder.Files.Add(templateName, doc);
    }
    string templateUrl = string.Format("{0}/Forms/{1}", libraryName, templateName);
    if (doclib.DocumentTemplateUrl != templateUrl)
    {
        doclib.DocumentTemplateUrl = templateUrl;
        doclib.Update();
    }

The document (MyTemplate.doc) is uploaded to the ‘Forms’ folder that is available in every document library. After running this code your Forms folder and advanced options will look like this:

    Template1

    Template2

Example 2 – Template for a content type

The second example basically does the same thing as the first example, but now for a content type. This sample requires a content type called ‘Proposal’ to be present in your site before it will run.

    string contentTypeName = "Proposal";
    string proposalTemplateName = "MyProposal.doc";
    SPContentType proposal = web.ContentTypes[contentTypeName];
    SPFolder cts = web.Folders["_cts"];
    SPFolder proposalFolder = cts.SubFolders[contentTypeName];
    SPFile proposalTemplate = null;
    foreach (SPFile file in proposalFolder.Files)
    {
        if (file.Name==proposalTemplateName)
        {
            proposalTemplate = file;
            break;
        }
    }
    if (proposalTemplate == null)
    {
        byte[] proposalDoc = System.IO.File.ReadAllBytes(string.Format(@"c:{0}", proposalTemplateName));
        proposalTemplate = proposalFolder.Files.Add(proposalTemplateName, proposalDoc);
    }
    if (proposal.DocumentTemplate != proposalTemplateName)
    {
        proposal.DocumentTemplate = proposalTemplateName;
        proposal.Update();
    }

This code will upload a template document for the proposal content type and then set the DocumentTemplate property of the SPContentType. When you use the SharePoint interface to upload a new template document, this document is uploaded to a subfolder called ‘_cts’. All content types in a SPWeb have their own subfolder in _cts. Our ‘’MyProposal.doc’ is therefore uploaded to the folder ‘/_cts/proposal’. My content type is in the root site, so therefore this _cts folder is a subfolder of the rootsite. If you create your content type in a site deeper down in the hierarchy it will be a sub folder of this sub site. The screenshot below shows SharePoint Designer showing this folder. The screenshot below that shows the advanced settings for our content type. Please note the difference between the document library and the content type. The content type expects just the filename, the document library expects you the set the full relative url to the template.

     Template3

    Template4

 And after writing this post I should now return to finishing my article on custom policies for the Dutch .NET Magazine….