Sunday, 16 August 2009

Invalid data has been used to update the list item. The field you are trying to update may be read only.

Apparently, in my specific case today, this error message actually meant the equivalent of a runtime type mismatch.

In the context of a list event receiver, I was attempting to update one of the list item’s properties. AfterProperties is an SPItemEventDataCollection, which seems to really be a collection of strings. In trying to cram a Guid in there, I forgot to call ToString () or Convert.ToString (); the compiler didn’t complain but at runtime my friendly Guid was jailed in the bowels of SharePoint’s exception handlers. SharePoint simply returned

Invalid data has been used to update the list item. The field you are trying to update may be read only.

with nothing but a menacing smile on its horrible, scarred face.

Sunday, 9 August 2009

Event receiver doesn’t fire

It’s been a long, busy weekend and the brain’s gone to mush—so I’m making silly mistakes.

Starting with an existing, functional SPItemEventReceiver attached to a content type, I needed to add an extra event handler. I had ItemAdding and ItemUpdating wired up and debuggable but wanted to add a new ItemAdded handler.

On my first attempt, I simply added the extra handler method to my existing class derived from SPItemEventReceiver. When that didn’t work, I recalled originally wiring the individual handler methods within the content type definition so I added an extra Receiver element.

I realised my mistake when it dawned on me I was testing my changes against an existing list instance: having been provisioned using the original content type definition with only the ItemAdding and ItemUpdating event handlers, SharePoint will never update that instance retroactively without some custom code.

After creating a new list from the modified template, all three event handlers fire correctly.

Retrieving a list item’s attachments

As you might expect, accessing a list item’s attachments in SharePoint isn’t straightforward. While SPListItem.Attachments returns a string-based list of attachment names, you’ll probably need to translate that into a URL at the very least, if not manipulate the attachment itself.

It helps to understand where SharePoint stores attachments after they’re uploaded. As usual, SharePoint Manager 2007 provides some insight:List Item Attachments Location

Notice in the screenshot, I’m inspecting a list named “Announcements” and that list has one item (Hello Attachment—highlighted at the bottom). You’ll also notice there’s an Attachments folder sitting among the list items and it’s been expanded with to reveal another folder with the odd name of 2; below that you can actually see the attachment itself (My First Attachment.txt).

Before I get any further, I’ll point out the magical “2” folder isn’t magical at all: it relates to the list item in this list with the Id of 2—in this case, the Hello Attachment item (the first item in the list was deleted but, if it had attachments, there would be a corresponding “1” folder). It’s sensible to assume this structure exists to accommodate multiple attachments.

If you were to now try to access the Attachments folder in code using SPListItem.Folder, you’d be faced with a null reference exception. Instead, you need to ignore what SPM tells you about getting to the Attachments folder and arrive there instead using the parent list’s RootFolder property.

Assume you’re working within the scope of a foreach or have retrieved a list item via some other means:

SPList announcements = …Lists [“Announcements”]

foreach (SPListItem currentItem in announcements)
{
// we’re here
}

Before you do anything else, you’ll probably want to muck around and ensure you can actually access a list of attachments in some form. To identify the attachments by name, SPListItem.Attachment (SPAttachmentCollection) will return a collection of strings:

foreach (string currentAttachmentUrl in currentItem.Attachments)
{
}

At this point you’ve retrieved, from the example discussed earlier, a string like “My First Attachment.txt—not of much use on its own. As mentioned, you’re probably after the full URL at the very least.

To access the actual attachment and not just it’s name, retrieve the list item’s parent list (SPListItem.ParentList) and then the parent list’s RootFolder. From there you can use the SubFolders collection to retrieve the “Attachments” SPFolder. Beware the SubFolders indexer expects the URL of the folder, not its name.

Finally, you need to retrieve the numbered folder specific to the list item. Here’s the complete mess:

SPFolder attachments = currentItem.ParentList.RootFolder.SubFolders ["Attachments"].SubFolders [currentItem.ID.ToString ()]

The SPFolder object’s URL property can now be prefixed to the attachment’s name retrieved previously or you can enumerate the SPFiles within the SPFolder.

Back in the day, Eric Shupps posted a way to do this by walking down the structure from the Lists folder. I think the above code is a slight improvement since the code’s a bit more manageable and you don’t need to retrieve the Lists folder or the parent list by name.

Ps. RootFolder is actually quite interesting as its Name property is actually the internal name of parent SPList object.

 

Saturday, 8 August 2009

Tourism WA CIO Position Advertised

Having worked at Tourism WA in various capacities for three and a half years, I’m currently working for my third TWA CIO. As the current CIO is acting in that position, Corporate and Business Services has decided to advertise the position again (it was advertised earlier in the year until being withdrawn after the second round of interviews). If you’re interested in working at Tourism and has what it takes to fill the CIO role—and you want to work with moi), then be sure to apply:

http://search.jobs.wa.gov.au/page.php?pageID=160&windowUID=0&AdvertID=84697

Friday, 31 July 2009

Storing SharePoint List meta data

When I recently wanted to store some information about a list (i.e., meta data) I couldn’t find an obvious way using just SPList. SPList does expose the PropertiesXml property but doesn’t expose a setter. What I wanted was a property bag or hashtable of key/value pairs. I’ve seen suggestions to add an extra column for this purpose but that could lead to redundant data, extra maintenance, and wasted space and essentially becomes meta data about the list items. In my opinion, columns define the structure of the data and aren’t meta data (but feel free to debate that among yourselves!).

Alex Angas was kind enough to provide an answer to a question I posted on StackOverflow on this subject. His solution was to make use of the Properties hashtable hanging off the list’s RootFolder object (SPFolder) and here’s my code to do just that:

// Set the custom property
SPList myList = myWeb.Lists [“MyList”];

myList.RootFolder.Properties [“MyKey”] = “MyValue”;
myList.RootFolder.Update ();

// Retrieve the custom property
myList = myWeb.Lists [“MyList”];

string myValue = myList.RootFolder.Properties [“MyKey”] as string;

Nice. It’s worth pointing out that using SharePoint Manager 2007 the RootFolder can be found within the site collection root; expand the RootFolder node to locate the folder named after the list in question.

SPFolder RootFolder Properties

Update: Although RootFolder.Properties is a System.Collections.Hashtable, you’ll likely receive a SPInvalidPropertyException if you attempt to use anything other than a string, int, or DateTime. The debugger told me so ;-)

In answer to the same question Paul-Jan also suggested using a separate, hidden list to track meta data about a parent list (or lists, I suppose). I quite like this idea as a centralised “list meta list” and it could be as simple as three column list to identify a list by ID and store a key/value pair. On the other hand, I prefer keeping stuff like meta data with the object in question so my preference would be SPList.Properties; the RootFolder solution seems like a passable alternative.

Saturday, 25 July 2009

Content Type ID Structure

Creating a custom content type in a feature requires you specify the ID attribute.

Content type IDs are constructed hierarchically and you can trace the lineage of a content type through its ID. A custom content type will often derive from the Item content type—the most basic content type you can use. Item is derived from the System content type and contains the ever-present Title field (Title can be hidden if necessary). Note the System content type is sealed, meaning it can’t be derived from in your code.

The ID of the System content type is 0x and the Item content type appends 01, giving you 0x01. A separator must be used between all subsequent IDs appended to the base string; 00 is used for that purpose, giving you 0x0100.

A custom content type will need its own ID appended to the base system-item-separator ID and the best way to do that is using a GUID without the braces and hyphens (use guidgen.exe, included with Visual Studio).

Here’s an example with the base system-item-separator ID combination bolded for clarity:

0x0100C1FA419A4F94429C8F40DB576B211FD7

To create additional custom content types derived from an existing custom content type, add an addition separator and GUID:

0x0100C1FA419A4F94429C8F40DB576B211FD700B87CFA11-7687-4171-B67B-E1DFE20BBB86

Content type IDs are limited in length to 1024 characters so if you’re running out of room or just don’t like using GUIDs, a content type ID can optionally be specified with two digits other than 00. WSS uses this convention so be wary of any clashes with your own custom content types.

If you’re working with pages, note the Page content type derives from System Page content type, which in turn derives from Document which in turn derives from Item. The Page content type ID is therefore

0x010100C568DB52D9D0A14D9B2FDCC96666E9F2007948130EC3DB064584E219954237AF39

Additional Resources:

http://msdn.microsoft.com/en-us/library/aa543822.aspx

Tuesday, 21 July 2009

DHCP-assigned IP address and DNS host name clash - duh

Mental note #1: if using host headers with MOSS (or IIS) ensure the web server is configured with a static IP address or the NIC’s MAC address is reserved within DHCP.

Mental note #2: Just because it’s SharePoint, don’t forget the easy things! No, really!!!

I came across an unusual problem today after a MOSS server was imaged and restored to a new physical blade: Central Admin, the default SSP web app, and a specially-created sanity check web app were all working beautifully but the single site configured with a host header refused to load.

In addition, the Application event log was full of errors suggesting a database server connectivity issue:

The start address http://site.com cannot be crawled. The item could not be crawled because the crawler could not connect to the repository.

The database connection wasn’t the problem but nevertheless, SharePoint couldn’t find what it was looking for. The WFE’s NIC changed as part of the migration, which meant a new MAC address, and therefore a new DHCP lease for a different IP address that DNS knew nothing about.

Friday, 17 July 2009

PDF iFilter not indexing content

Don’t forget: installing and configuring an iFilter to allow SharePoint to search PDF documents won’t index graphical PDF content. In other words, if search is returning PDF results based on title or other meta data but not PDF content, double-check whether that content can be highlighted and copied to notepad as text—and watch out for scanned documents!!!

Conditional Post-Build Event Command for x64 sgen

I’ve bitten the bullet and decided to run Windows Server 2008 Hyper-V on my workstation; in addition to virtualising my MOSS dev environment, I’ve also chosen to virtualise my workstation environment on Windows Server 2008 x64; work won’t let me get away with Windows 7 RC1 and there’s no “supported” upgrade path to the RTM anyway.

You may have read my previous post on the subject of x64 targeting with Visual Studio setup projects; today, however, is all about just getting VS to compile a project with a post-build event where Visual Studio 2008 is running on Windows Server 2008 x64 with SP2. In our case, the post-build event needs to run sgen.exe from the correct location since some of our devs are still on Vista x32. And yeah, I know, post-build events are old school but it’s an existing solution…

Windows Server 2008 with Visual Studio 2008 SP1 and .NET 3.5 SP1 installed does not include sgen.exe for some reason. Why not?!? Good question—you can install it by downloading the Windows SDK for Windows Server 2008 and .NET Framework 3.5. v6.0a does seem to be installed by VS2008 on 32-bit/W2k3 environments (more about this). Kicking off the SDK installer, I chose to install the .NET Development Tools only (Developer Tools\Windows Development Tools\.NET Development Tools).

This all seems good but sgen will be installed to a different location than you may be used to: %programfiles%\Microsoft SDKs\Windows\v6.1\Bin\x64 (the 64-bit Program Files folder, that is). To accommodate this difference from the 32-bit world, I need some conditional logic in my post-build event.

At first glance, using the %processor_architecture% environment variable makes sense—it returns “x86” in my current XP 32-bit environment, presumably the same in Vista x32, and “AMD64” in Hyper-V… not quite “x64” but enough to branch on. As we all know, however, Visual Studio is only available as a 32-bit application and seems to do some additional environment variable setting of is own: echo %processor_architecture% in a post-build event prints out “x86”, obviously the same as our 32-bit environment. No good.

To work around and take advantage of this, my post-build event compares the %ProgramFiles% variable against “C:\Program Files”; when queried from a post-build event in a 64-bit environment, it reliably returns “C:\Program Files (x86)” which I know is the 32-bit Program Files folder; in that case, my script uses the 64-bit sgen. Note the same variable will return “C:\Program Files” in a normal command window so be careful and test in VS directly.

In the end, here is my post-build event:

REM Use the 64-bit sgen from the Win 2008 and .NET 3.5 SDK in a 64-bit dev environment
REM ProgramFiles variable is set to 'Program Files (x86)’ in a x64 environment
REM Processor_Architecture variable returns x86 in both an x86 and x64 environment within VS

if /I "%ProgramFiles%" == "C:\Program Files" (
set ToolPath="C:\Program Files\Microsoft
SDKs\Windows\v6.0A\Bin\sgen.exe"
) else (
set ToolPath="C:\Program Files\Microsoft
SDKs\Windows\v6.1\Bin\x64\sgen.exe"
)

%ToolPath% /compiler:"\"/keyfile:$(ProjectDir)
MyKeyFile.snk"\" /force "$(TargetPath)"

Thursday, 9 July 2009

Joel Oleson at the Perth SharePoint User Group

After a great showing from Greg Low last month, the Perth SharePoint User Group boys have pulled SharePoint superstar Joel Oleson on his way home from the NZ Community SharePoint Conference 2009.

Joel’s presentation is “Preparing for Upgrade to SharePoint 2010.” Here’s some pre-reading: http://www.sharepointjoel.com/Lists/Posts/Post.aspx?ID=238

This promises to be the local SharePoint event of the year so get your RSVP into SharePoint Sezai via the user group web site if you haven’t already and block out 12:30pm in your calendar!