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
   3:  Public Class KBFeatureReceiver
   4:      Inherits SPFeatureReceiver
   6:      Public Overrides Sub FeatureActivated(ByVal properties As SPFeatureReceiverProperties)
   8:      End Sub
  10:      Public Overrides Sub FeatureDeactivating(ByVal properties As SPFeatureReceiverProperties)
  12:      End Sub
  14:      Public Overrides Sub FeatureInstalled(ByVal properties As SPFeatureReceiverProperties)
  16:      End Sub
  18:      Public Overrides Sub FeatureUninstalling(ByVal properties As SPFeatureReceiverProperties)
  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
   6:      listID = web.Lists.Add("Category List", & _
   7:                             "List Description", "Lists/HICUPCats", & _
   8:                             "386CF027-6AC7-460a-A7D6-4E503B2B6293", " & _
   9:                             10800, 100)
  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()
  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"" />"
  14:          ' Using the format and apply the category list GUID to the string
  15:          Dim fieldXML As String = String.Format(fieldXMLFormat, list.ID.ToString())
  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.

No comments: