Tuesday, 20 March 2007

How to debug SharePoint feature receivers

Just a quick one to detail how this is done in case someone finds it useful. For those still getting into working with SharePoint features, a feature receiver is a class which contains some code you've written to execute when a feature gets activated. Or deactivated, installed or uninstalled.

The key thing to note is that it's just standard ASP.Net debugging. The process is:-

  • deploy the assembly to the runtime location, either the GAC or the site bin directory. Note that if it's the bin directory your feature will also need appropriate CAS policy to grant the code the permissions it requires.
  • deploy the .pdb file to the same location. If this is the GAC, you can do the following:-

    - map a drive to the GAC folder i.e. C:\WINDOWS\assembly but using a UNC path such as [MachineName]\C$\WINDOWS\assembly. This allows you to browse the GAC without the shell which the framework puts on the folder, thus allowing you to see the actual structure of the files on disk.
    - locate the GAC_MSIL subfolder. In here you will see a directory for each assembly currently stored in the GAC. Find the directory for your assembly, and add the .pdb file so it sits next to the dll.

  • In Visual Studio, attach the debugger to the w3wp.exe process. Note that occasionally there will be 2 of these processes (e.g. when the process is being recycled), and it's possible to attach to the wrong one. Either do an IISReset to stop them both so that only 1 spins up with the next web request, or type 'iisapp' at the command prompt to get the process IDs of the running w3wp.exe processes. You can then match the correct one to the list which appears in the 'Attach debugger' dialog.
  • Activate the feature through the web UI (Site settings > Site Collection features/Site features). The debugger will now stop on any breakpoints you set.

And remember that the assembly must be built in debug mode so that the symbols are created.

Friday, 9 March 2007

Creating lookup columns as a feature

This is the second article in a series aimed at explaining the process of creating a MOSS site using SharePoint features. For the full series contents, see my introduction.

Last time we looked at the process of creating some SharePoint lists using VSeWSS. Sure, creating a list is a simple end-user task using the SharePoint UI, but in some scenarios such as when your site is a highly controlled internet/WCM site (or generally anywhere where we have multiple environments for dev/QA/staging/production) this deployment technique doesn't really cut it. Instead we probably want to use something more automated and repeatable than recreating such site artifacts manually each time. SharePoint 2007 supports this with Features.

Today we'll look at creating site columns which get their data from lists (lookup columns). This is a fairly common set-up, used for things like assigning metadata to a page or performing some other classification using a restricted set of values. Often the user would select the appropriate value using a dropdown shown when creating/editing a page.

Note that a similar method is to define a site column with several CHOICE elements, as below:-

However, this doesn't offer the same functionality as retrieving the values from a list. Consider the following:-

  • lists can have multiple columns whereas a choice element is effectively just one column
  • lists can have item-level permissions
  • lists can have events, workflow, versioning etc etc.

So it's clear many scenarios are better served from a site column which gets it's data from a list.

Now, when creating site artifacts as a feature, the developer will typically construct the definition in CAML, or allow VSeWSS to do this for him/her. Sometimes however, it's just not possible to do what you want with CAML. In these cases, the solution is to use a feature receiver. This is a class in compiled code (hopefully you still remember this ;-)) in which you override some methods and use the SharePoint API to define what should happen when the feature gets activated.

So why is it not possible to create a lookup column with CAML? After all, the following fragment successfully creates a site column which gets it's data from the list with the GUID specified in the 'List' attribute:-

<field id="{ae8e6ab8-b151-4fa4-8694-3cd24ad3b1bc}" type="Lookup" displayname="Locations" required="FALSE" list="{853CEC87-259E-47CA-97A7-42630F882FB7}" showfield="LinkTitleNoMenu" unlimitedlengthindocumentlibrary="FALSE" group="COB Metadata" sourceid="{8c066b26-5a3e-4e1b-85ec-7e584cf178d7}" staticname="Locations" name="Locations">

The answer is because list GUIDs are not deterministic. When a list gets created, whether through the UI, CAML or the API, it's GUID is assigned by SharePoint. There is no way to create a list with a GUID you have assigned. And if a list gets a new GUID each time it's created, this means it will have a different GUID in each of your dev/QA/staging/production environments. If this is your scenario suddenly that CAML fragment isn't so useful. Using this technique I would have to update the list GUID in my site column's <field> element and rebuild my site column feature every time the list got deployed to a new environment (or even redeployed). Clearly, this isn't pretty since, in addition to the extra effort, you're no longer deploying the exact same thing to live that has been tested in staging.

Hence I'd suggest any artifacts which reference a list should not refer to it in a declarative CAML. A better idea is to dynamically retrieve the list's GUID using the API (i.e. in a feature receiver), and create your site column in code. Briefly, the technique I use is this:

  • define site column in CAML using the fragment above
  • in my feature receiver, read this XML into memory
  • replace the list GUID with the real value retrieved from the API
  • create the site column using SPWeb.Fields.AddFieldAsXml(sXml) where sXml is the XML <field> element as a string
So this is kind of a cross between creating the site column in CAML and creating it in code. This gives a certain amount of flexibility since other properties of the column can be changed without having to recompile the code (simply change the value in the CAML, next time the feature runs it will read the modified values).

Note one other thing you are likely to need to do is to set the LookupWebId property on the column. This allows your column to be used in different sites in your site collection, yet still correctly reference the same list in your (for example) root site.

I'll post some sample code to do this in forthcoming post.

Assuming your CAML and the code to find the ID of your list was valid, you should see that you now have a site column which gets it's data from the list you specified when the feature is activated:-

One thing to note is that it's not really possible to delete the site column when the feature is deactivated. Generally, tidying up in this way is something you should do, but a site column cannot be deleted when it is has been provisioned on a list and has data. In this case, it's valid to not do any work on feature deactivation.

We're now well on our way to having page layouts which use content types with lookup columns. Phew! Next time, creating content types as a feature.

Friday, 2 March 2007

Creating lists with VSeWSS

This is the first article in a series aimed at explaining the process of creating a MOSS site using SharePoint features. For the full series contents, see my introduction.

Here, we'll look at creating SharePoint lists. For something so simple and core to SharePoint, they're surprisingly difficult to create as a feature. Fortunately Visual Studio extensions for WSS simplifies the process dramatically. If you've not come across this yet I'd recommend trying it - WseWSS is basically a huge help for some SharePoint development scenarios. The following articles are good background:-

Briefly, the process of creating a list in VSeWSS is as follows:

  1. Create a blank Visual Studio project. N.B. if you're actually creating a SharePoint site definition, you should select 'Team Site Definition' or 'Blank Site Definition', as WSeWSS will set you up with provisioning code which will execute whenever a site gets created from this definition.

  2. Select 'Add New Item' on the Project menu, then select 'List Definition' from the SharePoint section.

  3. Typically you should select 'Custom list' as the base list definition from the dropdown. Only select another choice if you're actually extending that list type and want to retain the original's columns. Assuming you want to create the actual list in addition to a list template (more on this later), check the 'Create an instance of this list' box. Note also the option to 'Add with event receiver' - this sets up code which which can be used to handle events on the list, for example when list items are added/edited/deleted.

  4. After clicking OK in the dialog, name your list appropriately. My recommendation would be to avoid spaces or other characters which might get encoded somewhere - the list will have a display name which you can be freer with.

    WSeWSS has now created several files in your VS project.

    We can see the following:-

    • some aspx files, the pages users will use to work with your list. Note these can be customised if you want to deviate from the standard behaviour.

    • instance.xml - this is the file to edit to add initial data to your list. Since this file is automatically linked to the appropriate schema, it's fairly straightforward to edit as VS will tell you the valid nodes as you type (shown below).

    • two .cs files containing method stubs for event handlers. These are ready for you to your implementation to.

    • schema.xml - this contains the CAML which dictates which content types the list stores, any custom views and also has references to the .aspx files we mentioned
  5. If you want to add default items to your list (and you probably do if you created an instance of it), you can edit the instance.xml file. This is made simple because VSeWSS has hooked up instance.xml to the appropriate XML schema (C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\XML\wss.xsd), and you can then take advantage of VS's Intellisense which is inferred from the schema as you type:-

  6. After making any customisations such as editing instance.xml to add list items, go to the properties of your VS project and examine the 'SharePoint Solution' tab. Here you'll see VSeWSS has generated two features for each list. One is for the list instance, the other is for the list template. This can be used by users to create new lists on the site, using the columns you've defined.

  7. Go to the 'Debug' tab on the project properties, and enter the URL of your SharePoint development site. The solution will be deployed to this URL so it's important this value is entered.

  8. Hit F5 and WSeWSS will create and deploy the solution. Note that the .wsp file gets generated in the bin\Debug or bin\Release directory of your project. Keep an eye on the VS status bar in the bottom left to monitor progress. If the deploy succeeded, your list should now be visible in the site specified on the 'Debug' tab.

To anyone who has created SharePoint solutions/features by hand, VSeWSS offers a compelling alternative. No messing around with ddf files and makecab.exe, just hit F5. However, there are a couple of irritations:-

  • Since VSeWSS rewrites the feature.xml file on each deploy, there's no way to customise this file to add something which can't be entered on the 'SharePoint Solutions' area of the project properties. The major example is a feature receiver class to specify code which should run when the feature is activated/deactivated, should you want to do something here. Of course, you could edit the file at the end of the development cycle, but you know damn well there's always further tweaks and re-pasting the XML into feature.xml gets tiresome.

  • VSeWSS recreates the feature GUIDs on each deployment. This is great for development, but means you cannot have other features with feature dependencies on your list features. This can be a pain if you're trying to create a feature hierarchy or rationalise your features so that activating one will activate all required features for a given part of your solution.

Nevertheless, you now have your solution with a feature for each list you added. Well actually you have 2 features for each list you added. Why? Because one is for the list instance and the other is for the list template. Note that you can deactivate the feature for the list template if you don't want your users to be able to create lists from this one.

Next in the series - how to create site columns (fields) which get their data from lists.