24 September 2009

End-To-End Solution – Custom List Definition

Today, I will continue to discuss the end-to-end solution I’ve been building for my organization. In Part 1, I began by describing my requirements and started building my solution. In the last post, I began building a feature receiver that fires when the feature is activated on my site collection.

In the last few posts, I think I became guilty of trying to cover too much at a time, so I’ve decided to limit this post the definition of my custom list. This is so I slow down and spend more time discussing the parts of the solution. I hope this change will allow me to make some things a little more understandable.

In the solution’s FEATURE folder, a new XML file is created that will contain the definition of my custom list. Again, I will set the schema of this file to use the wss.xsd located in the [12 Hive]/TEMPLATES/XML/ folder. To do this, in the properties window of the file and locate the Schema property. Click on the ellipses to show all of the currently available schema definitions. Click on Add and navigate the the [12 HIVE]/TEMPLATES/XML/wss.xsd file. This will enable intellisense for the document.

This is the definition of the list template:

  1: <Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  2:   <ListTemplate Type="10800"
  3:                 BaseType="0"
  4:                 Name="CategoryList"
  5:                 DisplayName="Category List"
  6:                 Description="Contains a list of categories"
  7:                 SecurityBits="11"
  8:                 VersioningEnabled="false"
  9:                 Hidden="TRUE"
 10:                 DisableAttachments="true"
 11:                 Category="Custom Lists"
 12:                 Sequence="100"/>
 13: </Elements>

Since this file will define a new element in SharePoint, the root node of my XML file is Elements. Inside the Elements node, the ListTemplate node is used to define a new list template to be used on the SharePoint site. The list templates show up when creating a new content for the SharePoint site.

I will be honest, I’m not sure what some of the attributes are for and welcome feedback from any that do, but I will discuss the ones that I do know.

First, Type is an identifier for the type of list. Microsoft recommends that you use any five digit number over 10000. I randomly selected 10800 because our organization plans to start at 10800 for any custom lists defined through features.

Name is the internal name of the list. This will also be the name of the folder that contains the schema file for the list. DisplayName is the list that will be displayed on the content creation screen in SharePoint. Description contains the description that will be displayed when the list is highlighted when creating content.

VersioningEnabled will turn on versioning in this list by default when it is created. Because this list will be synchronized with an external database, versioning will help make sure that any category can be rolled back to a previous version if needed.

Hidden will determine if this list type shows up to be created in the user interface. By setting this to TRUE, the list definition will not be displayed. This is being done because there should only be one Category list on the site, and that one will be created when the feature is activated.

DisableAttachments when set to true will turn off attachments on any instances of this list. Attachments will not be needed in this list, so they will be turned off by default.

Category is one of the visible category columns on the create content page. Sequence sets the display order in the UI. Since this will not be used in the current solution, they will not be necessary and are here for demonstration purposes only.

A new folder needs to be added to the root of the current solution folder. This folder should be the same name at the name used in the list definition, found on line 4.

  4:                 Name="CategoryList"

My solution hierarchy should be:

  • Project
    • 12
      • TEMPLATE
        • FEATURES
          • KBRepository
            • CategoryList

To begin with the schema.xml file used for the list, start by copying the schema.xml file from [12 Hive]/TEMPLATE/FEATURES/CustomList/CustomList/schema.xml. Place a copy of this file into the CategoryList folder and begin making customizations to the list. You can then follow the information provided in Andrew Connell’s blog on a quicker way to create list definitions.

Finally, the FEATURE.XML file needs to be updated to contain a reference to the new files that have been added to the solution.

  1: <Feature xmlns="http://schemas.microsoft.com/sharepoint/"
  2:          Scope="Site"
  3:          Creator="Software Development"
  4:          Id="386CF027-6AC7-460a-A7D6-4E503B2B6293"
  5:          Description="Sets up this site collection for KB"
  6:          Title="KB Repository"
  7:          Version="1.0.0.0">
  8:   <ElementManifests>
  9:     <ElementManifest Location="KBItemFields.xml"/>
 10:     <ElementManifest Location="KBContentTypes.xml"/>
 11:     <ElementManifest Location="CategoryList.xml"/>
 12:   </ElementManifests>
 13: </Feature>

The next post will complete the feature receiver.

Posts in series:

22 September 2009

End-To-End Solution – Feature Receiver Part 1

As promised, this post will be focused on creating a feature receiver that will allow me to perform some actions when the feature is activated or deactivated on the site. I plan to go a bit slower now that I’m posting code so that I make sure I explain both the code and the decision process behind the code. As stated earlier, I welcome feedback, especially if I’m missing an easier or “best-practices” way of performing my task. Please be sure to check out my first post in this series for more information on the scope of this project and the initial feature definition.

In the existing project, a new class file was created. This was accomplished by right clicking on the project node in solution explorer and choosing add new class. I want the code file to exist outside of the 12 folder since I do not want the code file to be included in my solution file. I named the class KBFeatureReceiver.

At this point, references to the SharePoint assemblies need to be added. If you are developing on a machine with SharePoint installed (highly recommended) you can simply right click on the project node and choose to add references. Browse the .Net tab for Windows SharePoint Services and add this as a new reference to the project.

Inside the newly created class file, you will need to add some Imports (C# Using) statements to remove the necessity of typing full namespaces.

   1:  Imports Microsoft.SharePoint

The class will need to inherit from the SPFeatureReciever class located in the SharePoint assembly. There are four methods that you must override to implement a custom feature receiver:

  1. FeatureActivated – occurs when the feature is activated. This is the place where I will want to create my custom list and special site column.
  2. FeatureDeactivating – occurs when the feature is deactivated. Here I will place code that will clean-up any customizations.
  3. FeatureInstalled – occurs when the feature is installed on the server.
  4. FeatureUninstalling – occurs when the feature is uninstalled from the server.

Thus far, this is the code I have:

   1:  Imports Microsoft.SharePoint
   2:   
   3:  Public Class KBFeatureReceiver
   4:      Inherits SPFeatureReceiver
   5:   
   6:      Public Overrides Sub FeatureActivated(ByVal properties As SPFeatureReceiverProperties)
   7:   
   8:      End Sub
   9:   
  10:      Public Overrides Sub FeatureDeactivating(ByVal properties As SPFeatureReceiverProperties)
  11:   
  12:      End Sub
  13:   
  14:      Public Overrides Sub FeatureInstalled(ByVal properties As SPFeatureReceiverProperties)
  15:   
  16:      End Sub
  17:   
  18:      Public Overrides Sub FeatureUninstalling(ByVal properties As SPFeatureReceiverProperties)
  19:   
  20:      End Sub
  21:  End Class

Personally, I like to segment my code into smaller areas of responsibility, so I’m going to create several methods to perform additional tasks. In order to maintain a reference to the SPWeb object, I will make this a parameter of each method. Based on my requirements, I need a method to create a list, remove a list, create a site column and remove a site column. Remember, as you work with the SPWeb and SPSite objects, it is very important to make sure these objects are disposed of properly.

Notice that each of the overridden methods has a single parameter – properties. Using this parameter, you can gain a reference to the calling SPSite object via properties.Feature.Parent. The call will return a generic object that should be cast into an SPSite object. From this point, the SharePoint OM is available to the code. The FeatureActivated method now looks like the following:

   1:  Public Overrides Sub FeatureActivated(ByVal properties As SPFeatureReceiverProperties)
   2:      Using mSite As SPSite = CType(properties.Feature.Parent, SPSite)
   3:         Using mWeb As SPWeb = mSite.RootWeb
   4:           Dim list As SPList = CreateCategoryListInstance(web)
   5:           CreateCategoryLookupSiteColumn(web, list)
   6:         End Using
   7:     End Using
   8:  End Sub

The method makes use of Using statements. This statement will help by automatically disposing of disposable objects. On line 4 a call is made to create a new category list instance and return the newly created list. The reference to the newly created list is then passed as a parameter to the method that creates the category lookup column.

Here the the CreateCategoryListInstace method:

   1:  Protected Function CreateCategoryListInstance(ByVal web As SPWeb) As SPList
   2:      ' Create a new list for the categories and store the GUID for use later.
   3:      Dim list As SPList
   4:      Dim listID As Guid
   5:   
   6:      listID = web.Lists.Add("Category List", & _
   7:                             "List Description", "Lists/HICUPCats", & _
   8:                             "386CF027-6AC7-460a-A7D6-4E503B2B6293", " & _
   9:                             10800, 100)
  10:   
  11:      ' Store the GUID of the new list for clean-up later if needed.
  12:      If web.AllProperties.ContainsKey("HICUPCategoryList") = True Then
  13:          web.AllProperties("HICUPCategoryList") = listID.ToString()
  14:      Else
  15:          web.AllProperties.Add("HICUPCategoryList", listID.ToString())
  16:      End If
  17:      web.Update()
  18:   
  19:      ' Return the list
  20:      Return list
  21:  End Function

First, a new list is created (Lines 6-9). The creation of the list makes use of a custom list definition that is defined in the feature (more about this in a future article). For simplicity, you can create replace 10800 on line 9 with the TypeID for a custom list (I believe it is 100).

After the list is created, the GUID for the list is stored as a property of the SPWeb.AllProperties HashTable. This will allow code that will be developed later to quickly find this list without the need of iterating through the lists on the site or hoping the name of the list hasn’t been changed. Line 17 makes a call to SPWeb.Update() which will apply the settings up to this point.

Here is the final code that will be discussed for this post, CreateCategoryLookupSiteColumn:

   1:  Protected Function CreateCategoryLookupSiteColumn(ByVal web As SPWeb, & _
   2:                                                    ByVal list As SPList) As Boolean
   3:     Try
   4:          ' Define the format for this lookup field
   5:          Dim fieldXMLFormat As String
   6:          fieldXMLFormat = "<Field ID=""{{3324B767-6CB6-4789-A0ED-60FB39BAC648}}""" & _
   7:                           " Name=""SWBTS_KBCategory""" & _
   8:                           " StaticName=""SWBTS_KBCategory""" & _
   9:                           " DisplayName=""Category"" Type=""Lookup"" List=""{0}""" & _
  10:                           " ShowField=""Title"" LCID=""1033""" & _ 
  11:                           " Group=""HICUP Knowledge Base Columns""" & _
  12:                           " UnlimitedLengthInDocumentLibrary=""false"" />"
  13:                           
  14:          ' Using the format and apply the category list GUID to the string
  15:          Dim fieldXML As String = String.Format(fieldXMLFormat, list.ID.ToString())
  16:   
  17:          ' Add the field to the site columns collection
  18:          web.Fields.AddFieldAsXml(fieldXML)
  19:          web.Update()
  20:          Return True
  21:      Catch ex As Exception
  22:          Return False
  23:      End Try
  24:  End Function

Lines 6-12 are used to create the XML that will be used to define the lookup column. This information follows the standard field definition schema that is used in feature definitions. Line 15 adds the GUID of the Category List to the XML. Line 18 adds the field to the current SPWeb and the all important SPWeb.Update() method is called in line 19 to apply these changes.

The original idea was to use a feature to create this field definition, but experimentation never resulted in a successful lookup field. This was the best way in the time allotted to accomplish the goal.

In my next post I plan on defining the deactivating methods, updating my feature with a custom list definition and adding the lookup site column to the KB Article Content Type defined in the previous post.

Again, please provide any feedback or comments.

21 September 2009

SharePoint 2010 Preview

Several weeks ago, Microsoft released some information through videos for the up and coming release of SharePoint 2010. If you haven’t already viewed these videos, what are you waiting for?

In short, one of the features that has been announced for developers is a first class development environment for SharePoint. One can only hope we either have better remote debugging support or the ability to install a “SharePoint Developer Version” on our Windows 7 desktops, but the ability to visually design web parts is a huge plus in my book. (I know, I know, some of you hard-core server control developers are disappointed).

Much more information should become available throughout the SharePoint conference – and while we’re on that topic, if you want to buy me a ticket to the conference I would gladly accept the opportunity to go!

SharePoint Development Solution End-To-End

Over the next few weeks I plan on putting together an end-to-end solution that I've been working on for one of our internal clients. The goal is to demonstrate some of the things that I've learned (and continue to learn) while building a solution for SharePoint. I welcome feedback and tips as I'm sure there may be better ways to accomplish some of my goals.

The project is to build a document management system for the authoring and distribution of Information Technology Knowledge Base Articles using SharePoint and Microsoft Word. These articles will be linked to a custom in-house ticket management system. We must also move over 400 existing articles into the SharePoint site and attach the appropriate metadata.

The current system works well for simple tasks, but when assembling complex documents with screenshots, tables and reports, the current system can be limited and cumbsome. For documents that will contain screenshots or images, the author must prepare all of their screenshots or images ahead of time, upload them to the system and then create the links to those images in the current system. While this process works, the workflow is very cumbersome and takes more time than it should.

Here are the primary goals of this project:

  1. Leverage SharePoint Document Authoring, Workflow, Search and Metadata for the creation and management of Knowledge Base Articles
  2. Define and collect required metadata for KB documents to make key information available for previewing.
  3. Standardize document creation process by using a predefined template.
  4. Maintain existing documentation and update links from the work orders to the knowledge base articles.
  5. Synchronize categories between existing system and SharePoint and keep them up-to-date.
  6. Leverage SharePoint Search Web Service to find and link KB documents in SharePoint to work orders.

To create the solution, I will be using WSPBuilder to create and maintain my feature. This Codeplex project does much of the heavy lifting of preparing and deploying the solution package. I have decided to deploy a feature as the current internal work order system may eventually be used by multiple internal clients to manage their work orders. By deploying the SharePoint side of the project as a SharePoint Solution (wsp), any existing or new sites in SharePoint can be made a container for Knowledge Base Articles. I had considered just creating a site definition and adding it to SharePoint, but I would rather have the option of either creating a new site or extending an existing site to contain these items. If I had decided to do a site definition, I would only be able to create new sites and would be unable to extend the current site collection.

To start this series, I created the layout of my feature in Visual Studio 2008. In visual studio, I created a new WSPBuilder Project. This project template is installed with the WSPBuilder project from Codeplex. The new project template will create a new project with a signing key, the 12 hive root folder and a solutionid.txt file. On the 12 hive, I created the TEMPLATES and FEATURES folders. These are the folders in the 12 hive that contain all of the configuration information for SharePoint. Inside the features folder, I added my custom folder (KBRepository) that will contain the XML definition files for my custom feature.

When done, my folder stucture appears as follows:

  • Solution
    • VS Project
      • 12
        • TEMPLATE
          • FEATURES
            • KBRepository
    • KBRepository.snk
    • solutionid.txt

Inside the KBRepository folder, I added the Feature.xml file that will contain the definition of my new feature. To make authoring the XML file a little easier, locate Schemas in your Properties window and click the ellipses. Click Add and then navigate to your 12 hive/TEMPLATE/XML and click on wss.xsd. Visual Studio will now provide intellisense to help define the feature. Here is the feature definition:


<Feature xmlns="http://schemas.microsoft.com/sharepoint/"
Scope="Site"
Creator="Software Development"
Id="386CF027-6AC7-460a-A7D6-4E503B2B6293"
Description="Sets up this site collection to author and manage KB Articles"
Title="KB Repository"
Version="1.0.0.0">
<ElementManifests>
<ElementManifest Location="KBItemFields.xml"/>
<ElementManifest Location="KBContentTypes.xml"/>
</ElementManifests>
</Feature>

Notice I have two element manifest files. These two files will be used to create site columns and content types that will be used by other parts of my solution. The KBItemFields.xml and KBContentTypes.xml files were also created in the root of the KBRepository folder.

I opened KBItemFields.xml and added the following XML. (Remember, if you click on Schemas and associate the wss.xsd you'll get intellisense support).



<?xml version="1.0" encoding="utf-8" ?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<Field ID="{B180626E-4DD1-44ca-9157-00639A0945DA}"
Name="WSSAPPDEV_CategoryID"
StaticName="WSSAPPDEV_CategoryID"
DisplayName="Reference Category ID"
Type="Number"
Min="0"
Decimals="0"
Commas="false"
LCID="1033"
Indexed="false"
Group="Category Columns"/>
<Field ID="{58AD2A67-B08C-4803-956F-82F425201D6B}"
Name="WSSAPPDEV_CatDescription"
StaticName="WSSAPPDEV_CatDescription"
DisplayName="Category Description"
Type="Note"
NumLines="5"
LCID="1033"
RichText="false"
Indexed="false"
Group="Category Columns"/>
<Field ID="{793E8479-066B-41be-B494-0D4207B8BD0A}"
Name="WSSAPPDEV_Active"
StaticName="WSSAPPDEV_Active"
DisplayName="Active Category"
Type="Boolean"
LCID="1033"
Indexed="false"
Group="HICUP Category Columns"/>
<Field ID="{A4F4E2EA-80CB-4a16-ACC5-060E922EB06C}"
Name="WSSAPPDEV_CustomerCat"
StaticName="WSSAPPDEV_CustomerCat"
DisplayName="Customer Category"
Type="Boolean"
LCID="1033"
Indexed="false"
Group="Category Columns"
/>
<Field ID="{DFE30371-501F-4dd3-B11A-54C762AFEE09}"
Name="WSSAPPDEV_Keywords"
StaticName="WSSAPPDEV_Keywords"
DisplayName="Keyword List"
Type="Text"
MaxLength="1000"
Indexed="true"
LCID="1033"
Group="Knowledge Base Columns"/>
<Field ID="{C486FB10-F649-476a-8606-495F900DD383}"
Name="WSSAPPDEV_ReviewDate"
StaticName="WSSAPPDEV_ReviewDate"
DisplayName="Review Needed Date"
Type="DateTime"
Format="DateOnly"
Indexed="false"
LCID="1033"
Group="Knowledge Base Columns"/>
</Elements>

Now in the KBContentTypes.xml, I provided the following XML:


<?xml version="1.0" encoding="utf-8" ?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<!-- CATEGORY -->
<ContentType ID="0x0100aa4bb14f2f8746db941ef9b5e52b4cDF" Name="KB Category" Description="A category contained in the KB system"
Group="KB" Hidden="FALSE" Version="1">
<FieldRefs>
<FieldRef ID="{B180626E-4DD1-44ca-9157-00639A0945DA}" Name="WSSAPPDEV_CategoryID" ShowInDisplayForm="TRUE" ShowInEditForm="FALSE" Required="TRUE" ShowInNewForm="FALSE" />
<FieldRef ID="{58AD2A67-B08C-4803-956F-82F425201D6B}" Name="WSSAPPDEV_CatDescription" ShowInDisplayForm="TRUE" ShowInEditForm="FALSE" Required="FALSE" ShowInNewForm="FALSE"/>
<FieldRef ID="{793E8479-066B-41be-B494-0D4207B8BD0A}" Name="WSSAPPDEV_Active" ShowInDisplayForm="TRUE" ShowInEditForm="FALSE" Required="TRUE" />
<FieldRef ID="{A4F4E2EA-80CB-4a16-ACC5-060E922EB06C}" Name="WSSAPPDEV_CustomerCat" ShowInDisplayForm="TRUE" ShowInEditForm="FALSE" Required="TRUE"/>
</FieldRefs>
</ContentType>
<!-- KNOWLEDGE BASE -->
<ContentType ID="0x010100aa4bb14f2f8746db941ef9b5e52b4cF0" Name="IT Knowledge Base Article" Description="Knowledge Base Information Article for KB"
Group="KB" Hidden="FALSE" Version="1">
<FieldRefs>
<FieldRef ID="{DFE30371-501F-4dd3-B11A-54C762AFEE09}" Name="WSSAPPDEV_Keywords" Required="TRUE" />
<FieldRef ID="{C486FB10-F649-476a-8606-495F900DD383}" Name="WSSAPPDEV_ReviewDate" Required="TRUE" />
</FieldRefs>
</ContentType>
</Elements>

I'm ready to deploy and test my budding solution. If you right click on the project node in your solution explorer, there is a new WSPBuilder Menu. From this menu, select Deploy and it will deploy to the SharePoint instance on the local machine.

Upon successful deployment, navigate to site and activate the feature by going to Site Actions > Site Collection Features and choosing to activate the feature. The feature has been scoped as Site, so this places it into the Site Collection Features. After activation, the new site columns and content types have been added to the root level of the site.

Congratulations, you have successfully built and deployed your first feature to SharePoint. In my next installment, I am going to create a feature receiver and programatically create some new lists to contain the categories and KB documents. I will also create a site column that performs a lookup into my category list and add the new site column to the IT Knowledge Base Article content type.

15 September 2009

BDC Application Definition Designer ShowInPicker Quirks

Today I was doing some work using the BDC Application Definition Designer for SharePoint 2007 and discovered some very odd behavior. My current project has me defining a business data catalog that points to our help desk work order system to gather categories for a list of knowledge base articles. I completed the design of my BDC application and deployed it into SharePoint, created a new column in my document library to pick the desired entity only to get the message "There are no Business Data Types loaded in the Catalog". Oddly, the data type was there prior to changing a single property on one of my type descriptors: ShowInPicker.

I had set this value on the name of the category since the default item displayed in the picker is the identifier of the entity. It is very difficult for users to pick a category when all that is displayed is a series of numbers, thus driving me to set the value of ShowInPicker to true for my category name field. So, I examined the XML created and discovered:

<TypeDescriptor TypeName="System.String" Name="CategoryName" DefaultDisplayName="Category Name">

<LocalizedDisplayNames>

<LocalizedDisplayName LCID="1033">Category Name</LocalizedDisplayName>

</LocalizedDisplayNames>

<Properties>

<Property Name="ShowInPicker" Type="System.Boolean">true</Property>

</Properties>

</TypeDescriptor>

I remove the line and imported the definition once more. Now my business data type showed up again. After searching, I discovered an article by Paul Galvin describing how to set the ShowInPicker value. In his article, I discovered another property that I wasn't including in my definition: DisplayByDefault.

I went back to the Application Definition Designer and added this under the properties node for my category name and exported the LOB definition. Again, I imported this into SharePoint and received the same error message - "There are no Business Data Types loaded in the Catalog". Upon exploration of the XML, I discovered the following:

<TypeDescriptor TypeName="System.String" Name="CategoryName" DefaultDisplayName="Category Name">

<LocalizedDisplayNames>

<LocalizedDisplayName LCID="1033">Category Name</LocalizedDisplayName>

</LocalizedDisplayNames>

<Properties>

<Property Name="ShowInPicker" Type="System.Boolean">true</Property>

<Property Name="DisplayByDefault" Type="System.Boolean">true</Property>

</Properties>

</TypeDescriptor>

The new property had been added, but the order was different from Paul's, so I changed the order and imported the definition again. This time, everything worked as properly and I could view the category name in the picker.

Moral of the story: Use the Application Definition Designer to get you started in defining your BDC application definition and then go back to make tweaks like this by hand.