Thursday 23 April 2009

Fix to my Config Store framework and list provisioning tips

Had a couple of reports recently of an issue with my Config Store solution, which provides a framework for using a SharePoint list to store configuration values. If you're using the Config Store this article will definitely be of interest to you, but I've also picked up a couple of general tips on list provisioning which I want to pass on. I have to thank Richard Browne (no blog) of my old company cScape, as the fix and several of the tips have come from him - as well as alerting me to the problem, he also managed to fix it before I did, so many thanks and much kudos mate :-)

Config Store problem

Under some circumstances, fields in the Config Store list were not editable because they no longer appeared on the list edit form (EditForm.aspx). So instead of having 4 editable fields, only the 'Config name' field shows in the form:

ConfigStoreMissingFields

I've not fully worked out the pattern, but I think the problem may only appear if you provision the list on a server which has the October or December Cumulative Update installed - either that or it's a difference between Windows 2003 and Windows 2008 environments (which would be even more bizarre). Either way, it seems something changed in the way the provisioning XML was handled somewhere. This is why the problem was undetected in the earlier releases.

I had seen this problem before - but only when the list was moved using Content Deployment (e.g. using the Content Deployment Wizard) - the original 'source' list was always fine. We managed to work around this by writing some code which 're-added' the fields to the list from the content type, since they were always actually present on the content type and the data was still corrected stored. Having to run this code every time we deployed the list was an irritation rather than critical, but something I wanted to get to the bottom of - however, on finding some folks were running into this in 'normal' use meant that it became a bigger issue.

The cause

I always knew the problem would be down to a mistake in the provisioning XML, but since I'd looked for it on previous occasions I knew it was something I was seeing but not seeing. In my case, Richard spotted that I was using the wrong value in my FieldRef elements under the ContentType element - I was mistakenly thinking that the 'Name' attribute needed to match up with the ''StaticName' attribute given to the field; the documentation says this attribute contains the internal name of the field. So my FieldRefs looked like this:

<ContentType ID="0x0100E3438B2389F84cc3965600BC16BF32E7" Name="Config item" 
Group="Config Store content types" Description="Represents an item in the config store." Version="0">
<FieldRefs>
<FieldRef ID="{33F5C8B4-A6BB-41a4-AB24-69F2152974C5}" Name="ConfigCategory" Required="TRUE" />
<FieldRef ID="{BD413479-48AB-41f5-8040-918F32EBBCC5}" Name="ConfigValue" Required="TRUE" />
<FieldRef ID="{84D42C64-D0BD-4c76-8ED3-0A9E0D261111}" Name="ConfigItemDescription" />
</FieldRefs>
</ContentType>

..to match up with fields which looked like this:

<Field ID="{33F5C8B4-A6BB-41a4-AB24-69F2152974C5}"
Name="Config category"
DisplayName="Config category"

StaticName="ConfigCategory"
....
....
/>

The CORRECTED version looks like this (note the change in value for the Name attribute of FieldRefs):


<ContentType ID="0x0100E3438B2389F84cc3965600BC16BF32E7" Name="Config item"
Group="Config Store content types" Description="Represents an item in the config store." Version="0">
<FieldRefs>
<FieldRef ID="{33F5C8B4-A6BB-41a4-AB24-69F2152974C5}" Name="Config category" Required="TRUE" />
<FieldRef ID="{BD413479-48AB-41f5-8040-918F32EBBCC5}" Name="Config value" Required="TRUE" />
<FieldRef ID="{84D42C64-D0BD-4c76-8ED3-0A9E0D261111}" Name="Config item description" />
</FieldRefs>
</ContentType>

So, the main learning I got from this is to remember that the 'Name' of the FieldRef attribute needs to match the 'Name' of the Field attribute - that simple. Why did it work before? No idea unfortunately.

However, I also picked up a few more things I didn't know about, partly from Richard (this guy needs a blog!) and partly from some other reading/experimenting..

Some handy things to know about list provisioning

  • To make a field mandatory on a list, the 'Required' attribute must be 'TRUE'. Not 'True' or 'true' - this is one of the cases where the provisioning framework is pernickety about that 6-choice boolean ;-)
  • FieldRefs need an ID and Name as a minimum (which must match the values in the 'Field' declaration), but you can override certain other things here like the DisplayName - this mirrors what is possible in the UI.
  • You don't have to include the list .aspx files (DispForm.aspx, EditForm.aspx and NewForm.aspx) in your Feature if you use the 'SetupPath' attribute in the 'Form' element in schema.xml (assuming you don't need to associate custom list forms).
  • You can use the 'ContentTypeRef' element to associate your content type with the list (specify just content type ID), rather than using the 'ContentType' element which needs to redeclare all the FieldRefs.
  • It's safe to remove all the default 'system' fields from the 'Fields' section of schema.xml

Going further than these tips, the best thing I found on this is Oskar Austegard's MOSS: The dreaded schema.xml which shows how you can strip a ton of stuff out of schema.xml. I've not tried it yet, but I'm sure that will be my starting point for the next list I provision declaratively. If you're interested in the nuts and bolts of list provisioning, I highly recommend you read it.

Happy XML'ing..

8 comments:

Marshal Nagpal said...

Hello Chris,
We were have same issue with config and lang store after DEC CU... and we got it resolved by removing config content type from list and re-attaching it to list from browser....

Andy Burns said...

Nice to know that even MVPs can have problems with the CAML schema!

Regarding point 5, isn't it just that you don't have to include fields that are being inherited from a parent content type? Therefore, all the 'System' ones (which come from the 'Item' content type, or higher) aren't required, but are implied?

Also, you can use the ContentTypeBinding element to attach one to a list.

Chris O'Brien said...

@Marshal,

Interesting - think that's effectively what our code workaround did. Assume there's no data loss doing it this way?

Seems to point more and more that the December CU was 'less tolerant' of mistakes in the provisioning XML perhaps?

Thanks,

C.

P.S. Good to see you're using both Config Store and Language Store :-) I'll backport this fix to Language Store over the next week or so and release a new version.

Chris O'Brien said...

@Andy,

:-)

On point 5, yes I agree that's the reason why it's safe to remove those fields - I should have been clearer there.

Interesting you raise ContentTypeBinding. I use this in other areas (e.g. associating content types with Pages libraries). Don't think it had occurred to me that I could use it here also.

Which kinda makes me wonder why the schema provides two ways of doing the same thing, but that could lead to whole 'nother discussion ;-)

Cheers,

C.

Andy Burns said...

I had a bit of a look out of curiousity. It seems that the ContentTypeRef is for when you initially define the list, and the ContentTypeBinding is for when you want to add one later.

Chris O'Brien said...

Interesting :-)

So having read your article, do we conclude you can use either ContentTypeRef or ContentTypeBinding if deploying a list + content type in the same Feature?

C.

Andy Burns said...

I reckon. Though I don't know what happens if the List doesn't have *any* content type defined for it. Maybe you have to have at least one ContentTypeRef'd content type, but you can then bind other ones later.

And you would need a ListInstance node in the feature too. So, I think you'd need:

- ContentType node for your content type
- List node to define the type of list
- ListInstance create on of your lists
- ContentTypeBinding to attach the content type to your newly created list.

Actually, I suppose that highlights the main difference - the ContentTypeRef connects the content type when the list is defined, and so it's provisioned already attached, while the ContentTypeBinding is attaching one AFTER the list exists.

Michael Hanes said...

An interesting discussion. I've found both ContentTypeRef and ContentTypeBinding are required for everything to hang together "properly":

http://blog.mediawhole.com/2009/11/contenttyperef-vs-contenttypebinding.html