Sunday, May 16, 2010

SharePoint 2010 explorer (using the Client object model)

This is a small WinForm tool again using the TreeView-PropertyGrid combo very much like the popular SharePoint Manager 2010 tool (which became popular with its 2007 version), but instead using the client object model which was introduced in SharePoint 2010. This is actually the second tool in a row using the TreeView-PropertyGrid setup that I release – the first one being the WebPart manager tool, that I demonstrated several weeks ago – check here (both tools actually reuse the same core visual components).

The tool can be downloaded from here (sources).

spx1

So as you can see from the screen-shot the tool shows the client object model hierarchy in its TreeView control in the left pane and when an object is selected there its properties are displayed in the PropertyGrid control in the right. Since the client object model is used, only the hierarchy below the site collection level is displayed (as opposed to the “server” object model where you have everything starting with the farm level). On the other hand the client object model which utilizes internally the SharePoint web services  allows you to connect remotely to multiple SharePoint farms as long as you have the appropriate access rights.

The tool uses extensively – first and foremost reflection – the object model hierarchy in the TreeView is actually a one-to-one mapping with the real object model hierarchy since the child nodes are actually the real member properties of the class of the parent object. Secondly – the available commands/actions for every object in the hierarchy are actually the class methods of that object which are retrieved and eventually executed with reflection. The other thing used extensively in the tool is the PropertyGrid control itself – it is used not only to visualize the properties of the currently selected object but also in the execute action dialog where it gets populated with and allows the setting of the parameters of the underlying client object class method.

System requirements and installation notes: you need .NET 3.5 and SharePoint 2010 on the machine that you use the tool on. Note that the tool uses only two SharePoint assemblies – Microsoft.SharePoint.Client and Microsoft.SharePoint.Client.Runtime – so you will be able to run the tool on a non-SharePoint machine if you have these two copied on that machine – I am not sure though whether Microsoft allow these to be distributed in this way. The tool consists of 4 files: SPx.exe, SPx.Core.dll, SPx.SharePoint.Client.dll and spxclient.xml – there’re no special installation steps, you can just copy the files to the target machine and start the executable.

General usability notes: here is a small list of some useful notes and tips for using the tool:

  • the tool’s PropertyGrid is an extended version of the original PropertyGrid control. One very useful addition to it is the ability to edit multi-line text in string properties – you can use that when you right click a grid item containing a string property of the selected object – then a small dialog box will open in which the text value of the property will appear in a multi-line text box. This is handy especially in cases when you want to edit the Field.SchemaXml or the View.ViewQuery properties.

    spx6
  • when you select a node in the TreeView the tool calls the ClientContext.Load and ClientContext.ExecuteQuery methods to load the corresponding object and its properties. Various errors can occur during loading and these are displayed in a small strip just below the PropertyGrid control: 

    spx2 
    Most notifications reflect normal error conditions in the client object model – for example the Web.AssociatedMemberGroup property may be null in the “server” object model so when you try to load it with the client object model the latter will raise an exception to that effect. Another common notification is the “Info: Not all properties are available.” one – you will see it for all Folder objects which represent non-list folders (e.g. Web.RootFolder) – in this case the Folder.ConentTypeOrder and Folder.UniqueConentTypeOrder properties are invalid in the “server” object model and consequently cannot be retrieved using the client model.
  • when you right click a node in the TreeView control a context menu with all public instance methods of the object’s class will appear (the same menu for the currently selected object is available in the toolbar below the “Actions” button):

    spx3 
    when you click on one of the “reflected” method commands a dialog box will pop up – in the dialog a PropertyGrid control will be populated with the selected method’s parameters which you can use to provide appropriate values to execute the method:

    spx4 
  • a BIG NOTE here – though all public instance methods of the selected object are available in the context menu in many cases you won’t be able to execute the selected client object method – there’re two main reasons for that – 1st – the limitations of the UI of the tool – you may simply not be able to provide values in the PropertyGrid control for the method’s parameters – note that you can insert values in the grid items only for primitive types and not for class types. Secondly – there’re cases when you need to call several client object methods consecutively (and pass the returned objects from the preceding to the next method calls) before committing them with ClientContext.ExecuteQuery – the tool normally calls the ClientContext.ExecuteQuery method immediately after you click the “OK” button in the parameters dialog, so generally you won’t be able handle more complex scenarios with several method invocations (the tool though has a switch that can be used to disable temporarily the automatic call of ClientContext.ExecuteQuery – see below).
  • despite the above mentioned limitations you can use the tool in at least these cases: create, update and delete operations for Web, List, Field, View and Folder objects; upload, delete, check in/out and publish files; add and delete content types to lists; etc.
  • the tool provides two enhancements for calling methods containing parameters of types inheriting Microsoft.SharePoint.Client.ClientObject  and Microsoft.SharePoint.ClientValueObject. In the first case you have the option to click the grid item ellipses button which will display a small popup with the same object hierarchy tree that you have in the tool’s right pane from which you will be able to select an existing in the hierarchy ClientObject that can be used in the method’s parameter:

    spx5 
    as you see in the screen-shot this is very useful when you call the List.ContentTypes.AddExistingContentType method and provide a reference to a site content type so that you can add that content type to the list.
    In the second case the tool “flattens” the parameter and instead of a single grid item for the object it displays separate grid items for every property of the ClientValueObject inheriting class. A small example for that – when you call the WebCollection.Add method to create a new sub-web, in the method’s parameters PropertyGrid instead of a single grid item containing a null WebCreationInformation object, you will see six grid items containing the properties of the WebCreationInformation class – Url, Title, Description, Language, WebTemplate and UseSamePermissionsAsParentSite.
  • the “Add return object to tree” option in the method invocation dialog – this is available for all non-void methods and allows you to dynamically add the object returned from the method call to the hierarchy tree in the right pane – the object will appear as a child node of the currently selected node (all “dynamic” nodes will disappear if you refresh the parent or any of the ancestor nodes). In most cases you won’t need to use this option since most “get” methods will return an object that you already have in the client collection object and most “add” methods will return an object which will automatically appear in the client collection (the tool will internally refresh the collection object). There’re several cases however in which this is a very handy option and the only way to get certain objects – for instance List.GetItems, List.GetItemById, File.GetLimitedWebPartManager, etc.

Commands overview:

The tool provides a pretty simple command user interface – a toolbar at the top of the main window with three menu buttons:

  • Actions menu – this is context dependant – it displays the available commands/actions for the currently selected object. You have the “Refresh” command at the top, available for all nodes and below it commands for the available public instance methods of the object’s class (see above in the “General usability notes” section)
  • Connections menu – you have two commands here: “Connect …” and “Disconnect”. The former displays a dialog in which you should provide the URL to the local or remote site collection that should be opened in the tool. You can optionally provide user credentials and authentication mode here. You can open many different site collections from different SharePoint farms. The disconnect command removes the site collection of the currently selected node from the TreeView.
  • Settings menu – you have three commands here that operate as switches – you can check and uncheck them:
    • “Add method return object to tree” – when this is checked the check box switch with the same label in the method call dialog will be checked by default. Also handy for methods without parameters for which the method invocation dialog doesn’t get launched.
    • “Suppress display object in property grid” – this will effectively disable the PropertyGrid in the left pane, the selected object won’t be displayed there. This can be handy in some advanced situations when the very querying of the object’s properties may affect its state.
    • “Suppress ExecuteQuery” – also handy for some advanced situations. Note that the tool calls ClientContext.ExecuteQuery after each node selection in the TreeView and after each method call selected from the context menu. If you use this option to disable temporarily the ExecuteQuery calls you will be able to execute several client object methods consecutively and only after that commit them with ClientContext.ExecuteQuery. Note that the objects that you use for these calls should have been loaded in advance otherwise you will receive various “not initialized” errors for collections, objects and properties. Basically you should have a very good understanding of the client object model to be able to play with this option and overall there are not that many scenarios in which it can be helpful.

Thursday, May 6, 2010

How to display all versions in a SharePoint list view page – part 2

Several months ago I wrote a small posting describing how you can trick the standard list view web part to display all versions of the list items in a SharePoint list – link. One problem though with this was that even if the list view columns display the values of the relevant item versions the “Name” column in document libraries links always to the current version of document (the same holds for the “Title” column in non-library lists and also for the commands in the context menu of the item). In one of the comments of the original posting there was a request to me to demonstrate a solution or work-around for that which is what I am going to elaborate on in this posting. The solution is applicable for document libraries only and uses a custom computed field which renders as a link to the document which when clicked opens the correct version of the selected document. The link actually calls a small custom application page (that should be deployed to the “12/TEMPLATE/LAYOUTS” folder of the SharePoint installation) which with several lines of code using the SharePoint object model redirects either to the current document version or to one of its previous ones (with the _VTI_HISTORY URL pattern).

And the implementation itself, the schema XML of the computed field:

<Field ID="{ccccdddd-3710-4374-b433-61cb4a686c12}"

       ReadOnly="TRUE"

       Type="Computed"

       Name="VersionLinkFilename"

       DisplayName="Version link"

       DisplayNameSrcField="FileLeafRef"

       Filterable="FALSE"

       AuthoringInfo="(linked to version)"

       SourceID="http://schemas.microsoft.com/sharepoint/v3"

       StaticName="VersionLinkFilename" FromBaseType="TRUE">

  <FieldRefs>

    <FieldRef Name="FileLeafRef" />

    <FieldRef Name="FileRef" />

    <FieldRef Name="_UIVersion" />

  </FieldRefs>

  <DisplayPattern>

    <HTML><![CDATA[<a href=']]></HTML>

    <HttpVDir CurrentWeb="TRUE" ></HttpVDir><HTML><![CDATA[/_layouts/versionredir.aspx?FileRef=%2f]]></HTML><LookupColumn Name="FileRef" URLEncode="TRUE" /><HTML><![CDATA[&Version=]]></HTML><Column Name="_UIVersion" />

    <HTML><![CDATA['>]]></HTML>

    <LookupColumn Name="FileLeafRef" />

    <HTML><![CDATA[</a>]]></HTML>

  </DisplayPattern>

</Field>

The easiest way to add a computed filed to your document library is using code (or a custom tool), basically you can use the SPFieldCollection.AddFieldAsXml method (calling it on the Fields property of your SPList instance) providing the schema of the field in the first parameter.

And the code of the custom application page:

<%@ Page Language="C#" Debug="true" %>

<%@ Register Tagprefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %> <%@ Register Tagprefix="Utilities" Namespace="Microsoft.SharePoint.Utilities" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>

<%@ Import Namespace="Microsoft.SharePoint" %>

<script runat="server">

    protected override void OnLoad(EventArgs e)

    {

        base.OnLoad(e);

 

        string fileRef = this.Request.QueryString["FileRef"];

        int version = int.Parse(this.Request.QueryString["Version"]);

 

        SPWeb web = SPContext.Current.Web;

        SPFile file = web.GetFile(fileRef);

        SPListItem item = file.Item;

 

        string redirUrl = null;

        if ((int)item["_UIVersion"] == version) redirUrl = web.Url.TrimEnd('/') + "/" + file.Url;

        else

        {

            foreach (SPListItemVersion v in item.Versions)

            {

                if ((int)v["_UIVersion"] == version)

                {

                    redirUrl = web.Url.TrimEnd('/') + "/_vti_history/" + version + "/" + file.Url;

                    break;

                }

            }

        }

        if (redirUrl != null)

        {

            Response.Redirect(redirUrl);

        }

    }

</script>

The page should be saved as “VersionRedir.aspx” in the LAYOUTS folder of your SharePoint hive.

Friday, April 30, 2010

Updating read-only fields with the Lists web service

This is a known limitation (from a user’s perspective that is, obviously Microsoft had a good reason to design it this way) – you cannot edit fields like the Created, Modified, Created By, Modified By, etc using the standard SharePoint Lists web service and its UpdateListItems method (Updated 2011-1-20 – the Created By (Author) and Modified By(Editor) fields can be updated in this manner only for non-library SharePoint lists). On the other hand using the object model with SPListItem.UpdateOverwriteVersion or SPWeb.ProcessBatchData you can easily modify the values of read-only fields. And back to the Lists web service – I recently found a small work-around to this issue – the idea is quite simple – if the problem is with read-only fields why just not change the ReadOnly attribute in the field’s schema and check to see whether this will work. I tested the schema changing and it turned out that the UpdateListItems method was now able to change the values of the modified fields. The next question is whether it is possible to make this field schema change with a web service – the answer is again yes – the Lists web service has the UpdateList method which allows among other things to modify the schema of the fields in the list. So, combining the two methods it is possible to first call UpdateList and change the fields whose values should be modified to non read-only, then call UpdateListItems and update the values in the list items and finally call UpdateList again to have the read-only attribute set back to the original value (check the sample code snippet below). Note here that setting the ReadOnly schema attribute back to TRUE is necessary otherwise you will see some side effects like the fields appearing in the new and edit forms of the list, the Modified field will stop reflecting the last modified date, etc. Another important note – if the field is sealed (its “Sealed” attribute is set to TRUE) – the Lists.UpdateList cannot update its schema so the whole approach is unusable in this case (unless you set the “Sealed” attribute to false with the object model or a tool which further complicates the issue).

And now several words about the disadvantages of this work-around (a little attempt to discourage you from using it) – let me start with the schema changes – it is obviously not natural to change the schema of the list (or its children) to get an update of its list items right, another thing is that these schema changes and reverts will become a real mess if you try to run several updates simultaneously. So my advice would be that this approach should be used only in a one-time batch updates and/or when there is really no alternative to using the standard SharePoint web services (no option for using the object model or to create and deploy a custom web service).

And here is a small sample code snippet that demonstrates this work-around:

        public void Test()

        {

            string webUrl = "http://myserver";

            string listName = "docs";

            Lists.ListsSoapClient listsClient = this.GetListsClient(webUrl);

 

            // 1st a call to Lists.GetList - we need the list's version - it is returned in the Version attribute

            XElement listData = XElement.Parse(listsClient.GetList(listName).OuterXml);

            string listID = listData.Attribute("ID").Value;

            string version = listData.Attribute("Version").Value;

            // in the updateFields parameter of Lists.UpdateList the full schema of the fields should be provided

            string updateFields = @"<Fields>

   <Method ID='1'>

      <Field ID='{28cf69c5-fa48-462a-b5cd-27b6f9d2bd5f}' ColName='tp_Modified' RowOrdinal='0' ReadOnly='FALSE' Type='DateTime' Name='Modified' DisplayName='Modified' StorageTZ='TRUE' SourceID='http://schemas.microsoft.com/sharepoint/v3' StaticName='Modified' FromBaseType='TRUE' Version='4' ShowInNewForm='FALSE' ShowInEditForm='FALSE' />

   </Method>

   <Method ID='2'>

      <Field ID='{8c06beca-0777-48f7-91c7-6da68bc07b69}' ColName='tp_Created' RowOrdinal='0' ReadOnly='FALSE' Type='DateTime' Name='Created' DisplayName='Created' StorageTZ='TRUE' SourceID='http://schemas.microsoft.com/sharepoint/v3' StaticName='Created' FromBaseType='TRUE' Version='4' ShowInNewForm='FALSE' ShowInEditForm='FALSE' />

   </Method>

</Fields>";

            XmlDocument doc = new XmlDocument();

            doc.LoadXml(updateFields);

            // Lists.UpdateList: set fields to not read-only

            XElement result = XElement.Parse(listsClient.UpdateList(listID, null, null, doc.DocumentElement, null, version).OuterXml);

            // get updated version from the result XML - for the second call of Lists.UpdateList

            version = result.Elements().Where(el => el.Name.LocalName == "ListProperties").First().Attribute("Version").Value;

 

            // prepare the XML for the list item update

            string updateDates = @"<Batch OnError='Continue'>

  <Method ID='M0' Cmd='Update'>

    <Field Name='ID'>1</Field>

    <Field Name='FileRef'>/docs/zt.txt</Field>

    <Field Name='Modified'>2010-04-04T22:17:00Z</Field>

    <Field Name='Created'>2010-01-01T00:05:00Z</Field>

  </Method>

</Batch>";

 

            doc.LoadXml(updateDates);

            // Lists.UpdateListItems: update Created & Modified

            result = XElement.Parse(listsClient.UpdateListItems(listID, doc.DocumentElement).OuterXml);

 

            // revert the fields' schema

            updateFields = updateFields.Replace("ReadOnly='FALSE'", "ReadOnly='TRUE'");

            doc.LoadXml(updateFields);

            // Lists.UpdateList: set fields back to read-only

            result = XElement.Parse(listsClient.UpdateList(listID, null, null, doc.DocumentElement, null, version).OuterXml);

        }

The code demonstrates how to update the Created and Modified fields of a document library item (check the comments inside the code for more details). Note that this is a sample quality code and you shouldn’t use it directly without serious improvements – for example using a try-finally block with the second call to the Lists.UpdateList method placed in the “finally” block; adding an extra call to Lists.GetList to get a fresh value of the “Version” attribute of the list, just before the second list update, etc.

Thursday, April 15, 2010

SharePoint 2010 version of the WebPart Manager tool

I created a SharePoint 2010 version of the WinForm WebPart Manager tool that I demonstrated in my previous posting. Sources and binaries of the 2007 and 2010 versions are now available in codeplex.

wpmanager1

The 2010 version works by default with the new XsltListViewWebPart instead of the older ListViewWebPart. It also has the additional option to save the web parts’ ID-s when exporting a module element feature – this is necessary for web parts which are placed in “rich content” editable fields.

I also added a new node level in both versions – below ListViewWebPart-s (or XsltListViewWebPart-s for SharePoint 2010) the associated with the web part SPView object now appears.

Wednesday, April 7, 2010

SharePoint WebPart Manager tool

After the many WinForm tools that I’ve created using the TreeView & PropertyGrid combo (much like the SharePoint Manager 2007 tool), I finally decided to create a reusable core allowing various plug-ins for the tree hierarchy structure and for the commands available for the different nodes. And here’s the first tool based on this little framework of mine – the WebPart Manager tool (download):

WPManager1

WPManager2

As you see from the screenshots the tool displays a hierarchical view of all web applications, site collections and sites down to the folder structure of each site with all files and web parts in web part page files. The property grid is populated only for the web part level (this way the tool is much faster and the display options for the different levels are actually configurable in the accompanying configuration XML files of the tool). The tool supports the standard web part operations normally available through the standard SharePoint web UI – save, export, delete, close, open and move a web part and on the file node level another set of useful commands is exposed – starting with the file specific actions – check in/out, overwrite check-in, discard check-out, publish (these are really handy especially for publishing pages since you need to have the file checked out first before you can modify or add web parts to the page); to batch web part oriented commands like: export all web parts, import/add/delete web parts and some really nice goodies – export module feature and import web parts from module element file. The UI of the tool includes a common dialog interface with a dialog form using again the PropertyGrid control in conjunction with several custom UIEditor classes to capture the required parameters for the selected command (check the tool to get an impression of it).

And here is a short list of the available commands in the tool:

For the web part level:

  • Save web part – saves the selected web part after you have changed its properties in the PropertyGrid control.
  • Export web part – exports a .webpart or .dwp file with the web part data to the file system.
  • Delete web part – deletes the web part from the page.
  • Close web part – closes the web part.
  • Open web part – opens a previously closed web part (available only for closed web parts).
  • Move web part – allows you to change the web part zone and zone order of the web part.

For the file level:

  • Check out – checks out the selected file (available only for files in libraries and if the file is not already checked out)
  • Check in minor – checks in minor the selected file (available only for files in libraries and if the file is checked out)
  • Check in major – checks in major the selected file (available only for files in libraries and if the file is checked out)
  • Check in overwrite– checks in with overwrite the selected file (available only for files in libraries and if the file is checked out)
  • Discard check out – discards the check-out of the selected file (available only for files in libraries and if the file is checked out)
  • Publish – publishes the selected file (available only for files in libraries and if the file was previously checked in to a draft state)
  • Export all web parts – exports all web parts in the selected file to a specified folder (saving the web parts as .webpart and .dwp files)
  • Import web part(s) – imports web parts from .webpart and .dwp files from a selected folder in the file system (you can specify one or several files). You need to also specify the target web part zone ID on the page and optionally the zone index for the first web part to be imported (the consecutive parts will be assigned incrementing indices)
  • Add web part(s) – adds web parts from a list of available web parts for the current site – the list consists of all non-system SharePoint lists (resulting in adding a ListView web part) in the site and all web parts available in the web part gallery of the site. You need to also specify the target web part zone ID on the page and optionally the zone index for the first web part to be added.
  • Delete web part(s) – deletes web parts from the page – you can select to delete all web parts on the page or just select one or several of them to be deleted.
  • Export module feature – exports a module feature with the current file to a specified folder. It exports a feature.xml and an elements.xml file together with the file of the page. The elements file contains a Module element with a single File element containing the web part definitions of all web parts on the page. There is an option to export only the elements file instead of the full feature which is handy when you want to quickly copy all web parts of one page to another one using the “Import web parts from module file” command. For publishing pages the File element is exported with all relevant Property elements.
  • Import web parts from module file – imports web parts from an elements file (containing a Module element with one or many File elements) of an existing module feature or one created with the “Export module feature” command. The command shows a dialog which allows you to select the elements file to be imported and after the file is chosen a drop down list gets populated with the available File elements in the Module elements file so that you can choose which File definition to be imported. The command automatically resolves any resource tokens and ~Site/~SiteCollection tokens if present in the Module file.

For the folder level:

  • Export module feature – analogous to the command with the same name on the file level with the difference that you can choose whether to export all files in the current folder or just a group of selected files. The same command is available on the site level too – in this case it exports all/selected files from the root folder of the site.

Monday, March 8, 2010

ContentTypeBinding vs ContentTypeRef and the fields’ redefinition issue

These two elements do basically the same thing – attaching site content types to a SharePoint list. So there’s been the question now and then which one to use and what are the advantages and disadvantages of the two approaches.

So, let me start with several words about the two elements - first the ContentTypeBinding element:

  • the ContentTypeBinding element is actually a feature manifest element (like the ListInstance or ListTemplate ones) while the ContentTypeRef is just an XML element from the list schema file of a list definition (which may be used directly by a ListInstance element as specified in the CustomSchema attribute – see my previous posting on that)
  • it attaches site content types to existing SharePoint lists – this also means that the element is updateable – if you activate the feature containing the ContentTypeBinding element to a site a second or consecutive times with the force parameter set and the content type is missing in the list it will be attached again.
  • this one is an important advantage – all fields of the content type that are not already present in the destination list are automatically provisioned.
  • and one disadvantage – you can attach content types but you cannot delete a content type from a list or change the content type order or visibility of the content types in the list declaratively – basically this means that after you attach your content type to a list you need to bother about the default “Item” or “Document” or “Page” that remains in the list or document library (unless you need the default content type as well).

Second - the ContentTypeRef element:

  • It is actually an XML element in the list schema file as I mentioned above
  • One ugly thing about it is that you specify a site content type to be attached to the list based on that list definition but the framework doesn’t provision the fields in the content type if they are missing in the list – so you need to add manually all content type’s fields in the Fields element of the list schema file. This is actually what I called the fields’ redefinition issue in the posting’s title and it can get pretty unpleasant if you have a site content type used in many list definitions – then for a change in one of the content type’s fields you will need to make changes not only in the site column definition but in all list schema files that use the containing content type.
  • The list schema files in SharePoint 2010 can be used not only in list definition feature elements but directly in a ListInstance elements via the CustomSchema attribute, which is a pretty nice new feature.

So, having said that in my opinion the ContentTypeRef element which is packed directly into the list schema file is the neater approach having it not been for the ugly and uneconomical thing with the fields’ redefinition. And actually the thing is that this issue is quite solvable and I will mention two workarounds for it:

  1. I have some doubts about this approach since it seems like some sort of a side effect not a deliberate feature (I maybe wrong here though) – so it is actually pretty simple – when you define your content type you just add the new Overwrite attribute to its definition – when set to true this attribute forces the using of the object model for the content type creation – so the content type gets created directly into the content database:

<ContentType ID="0x0100678499b7e7024385820d8586270c1a75"

               Name="MyContentType"

               Group="Custom Content Types"

               Description="My Content Type"

               Overwrite="TRUE" >

    <FieldRefs>

      <FieldRef ID="{fa564e0f-0c70-4ab9-b863-0177e6ddd247}" Name="Title" Required="TRUE" ShowInNewForm="TRUE" ShowInEditForm="TRUE" />

      <FieldRef ID="{9da97a8a-1da5-4a77-98d3-4bc10456e700}" Name="Comments" DisplayName="Description" Required="FALSE" />

    </FieldRefs>

  </ContentType>

So, you see the Overwrite attribute set to true and another important thing – all fields that you want to appear in the item forms should be explicitly added in the definition – the content type “inheritance” for fields doesn’t seem to work in this case. So with these small modifications to the content type you don’t have to redefine the content type fields in the list schema file, though it doesn’t seem right to me that in order to fix one artifact you need to modify another. And it’s not only that – there is another side effect to this “side effect” – the site content type that you specify with the ContentTypeRef element gets added with the default “Item” name and default “Item” description. This may not be a problem in some cases (leaving declarative definitions only it’s a matter of a line of code to get that fixed in a feature receiver or just leave it as it is) but this is a serious problem when you want to attach more than one content types – then you simply receiver an error that two content types with the same name cannot be added – and this makes this approach practically unusable with more than one content types attached in the list schema (to be frank with you – I found a solution to that (wholly declarative), but it’s a bit dirty so I won’t mention it).

And then we come to the second approach of solving the redefinition issue:

  1. This one uses code – I just created a small method which provided with the SPFeatureReceiverProperties parameter of the feature receiver’s FeatureActivated method does the job. It simply iterates all ListInstance manifest elements with CustomSchema attribute using list schema files that you have defined in that feature (this doesn’t work for list definition elements, depending on the scenario it can be more complicated there) and adds the site fields used in the referenced content types to the new lists. And this is the code of the method itself:

        private void FixListSchemas(SPFeatureReceiverProperties properties)

        {

            // get the web we're activating the feature on

            SPWeb web = properties.Feature.Parent as SPWeb;

            if (web == null) return;

 

            // two handy maps for the site content types and fields - so that we can quickly look them up

            Dictionary<string, SPContentType> siteCTypes = web.ContentTypes.Cast<SPContentType>().ToDictionary(ct => ct.Id.ToString(), StringComparer.OrdinalIgnoreCase);

            Dictionary<Guid, SPField> siteFields = web.Fields.Cast<SPField>().ToDictionary(f => f.Id);

 

            // iterate over the feature's element definitions and pick the ListInstance ones containing CustomSchema attribute

            List<XElement> listInstanceElements =

            properties.Definition.GetElementDefinitions(CultureInfo.GetCultureInfo ((int)web.Language)).Cast<SPElementDefinition>()

                .Select(def => XElement.Parse(def.XmlDefinition.OuterXml))

                .Where(liel => liel.Name.LocalName == "ListInstance" && liel.Attribute("CustomSchema") != null)

                .ToList();

 

            // iterate the ListInstance elements

            foreach (XElement listInstanceElement in listInstanceElements)

            {

                // get the site relative list url from the Url attribute

                string listUrl = listInstanceElement.Attribute("Url").Value;

                // get the absolute path of the schema file - combining the feature's root folder and the value of the CustomSchema attribute

                string customSchemaPath = Path.Combine(properties.Feature.Definition.RootDirectory, listInstanceElement.Attribute("CustomSchema").Value);

                // get the SPList instance using the list url

                SPList list = web.GetList(web.ServerRelativeUrl.TrimEnd('/') + "/" + listUrl);

 

                // two handy sets for the list fields - one with the fields' IDs, the other with the fields' names

                HashSet<Guid> fieldIDs = new HashSet<Guid> (list.Fields.Cast<SPField>().Select(f => f.Id).Distinct());

                HashSet<string> fieldNames = new HashSet<string> (list.Fields.Cast<SPField>().Select(f => f.InternalName).Distinct());

 

                // load the custom schema file and extract the IDs of the available ContentTypeRef elements - some fancy LINQ to XML is used since the SharePoint xmlns may be there but may be missing as well

                List<string> contentTypeRefs = XDocument.Load(customSchemaPath).Root.Descendants()

                    .Where(el => el.Name.LocalName == "ContentTypeRef" && el.Parent.Name.LocalName == "ContentTypes")

                    .Select(el => el.Attribute("ID").Value)

                    .Where(id => siteCTypes.ContainsKey(id))

                    .ToList();

 

                // iterate the FieldRefs of all ContentTypeRef-s checking if the list already contains a field with the same ID and that there exists a site column with that ID

                foreach (SPFieldLink fieldRef in contentTypeRefs

                    .Select(cr => siteCTypes[cr])

                    .SelectMany(ct => ct.FieldLinks.Cast<SPFieldLink>())

                    .Where(fr => !fieldIDs.Contains(fr.Id) && siteFields.ContainsKey(fr.Id)))

                {

                    // get the corresponding site column for the current FieldRef

                    SPField siteField = siteFields[fieldRef.Id];

                    // get the field name

                    string fieldName = !string.IsNullOrEmpty(fieldRef.Name) ? fieldRef.Name : siteField.InternalName;

                    // check if the field name is unique - the list may not contain a field with the same ID, but may contain a field with the same internal name - so find a unique name here - SharePoint does the same when attaching content types to lists

                    string newFieldName = fieldName;

                    for (int i = 0; i < 1000; i++) { if (!fieldNames.Contains(newFieldName)) break; newFieldName = fieldName + i; }

 

                    // parse the site field's schema

                    XElement fieldSchema = XElement.Parse(siteField.SchemaXml);

                    // reset the Name and StaticName attributes with the new unique field name found

                    fieldSchema.SetAttributeValue("Name", newFieldName);

                    fieldSchema.SetAttributeValue("StaticName", newFieldName);

                    // reset the DisplayName if the FieldRef defines one - SharePoint does the same when attaching content types to lists

                    if (XElement.Parse(fieldRef.SchemaXml).Attribute("DisplayName") != null) fieldSchema.SetAttributeValue("DisplayName", fieldRef.DisplayName);

 

                    // create the new field in the list - use the SPAddFieldOptions.AddFieldInternalNameHint here, otherwise the DisplayName will be used as internal name

                    list.Fields.AddFieldAsXml(fieldSchema.ToString(), false, SPAddFieldOptions.AddFieldInternalNameHint | SPAddFieldOptions.AddToNoContentType);

                    // update the two list fields sets in case we hit the same FieldRef for another content type

                    fieldIDs.Add(siteField.Id); fieldNames.Add(newFieldName);

                }

            }

        }

You can check the comments in the code for a more detailed picture of the steps taken to add the site columns to the list instances.

So, to briefly recap – you can have your list schema files in SharePoint (at least for ListInstance elements) short and simple enough but … with a little pinch of code.

SharePoint 2010 - ListInstance CustomSchema attribute

The CustomSchema attribute of the ListInstance feature element is a new attribute introduced in SharePoint 2010. What it does is pretty simple – it allows you to change the metadata of the list instance that you create (I was complaining about the lack of this capability in SharePoint 2007). The idea is pretty simple – in the CustomSchema attribute you specify the feature root relative path of a list schema file which is actually a normal list definition schema.xml file and that is basically it – you have all metadata elements from the schema in your new list instance. So, it doesn’t seem like a big deal because you can achieve the same thing with a custom list definition and a list instance based on it – and yes this is obviously the preferred thing to do when you have many instances that should be based on the same list schema. But on the other hand if you have many list instances all with different schemas (basically with some minor customizations – several new fields and some list views) you can go on with this kind of short-cut implementation which is far more economical. Another good thing to know here is that since the list schema file is the same as in a list definition you can always promote it to a custom list template definition (with just adding a list definition element and moving the schema to it) in case you need to create more than one list instance based on that schema. The opposite thing is also possible – you can “demote” a list definition item with just keeping its schema for the CustomSchema attribute of a ListInstance and removing the list definition element itself. Note here that the list schema file that you use in the CustomSchema should contain all major elements of the List/MetaData section – ContentTypes, Fields, Views, Forms – it’s not like the ListInstance element uses its base list definition (specified in the FeatureId attribute) and applies the CustomSchema as modifications on top of it – actually the list schema specified in the CustomSchema attribute should be a fully-fledged list schema and if you miss some of its elements you will receive various errors on feature activation or later when you are using the list. And since you have a fully-blown list schema it is that straightforward to have the schema reused to or from a custom list definition.

Another advantage of using a ListInstance with CustomSchema is that it can be tested very easily: you don’t need an iisreset (or recycle the app pool) after you make a change in the list schema – you just need to delete the list instance and create it again. Actually you can first create and test your list schema this way and only after put it in a custom list definition.

Another thing that may be bothering is the size of the list schema files – you remember the huge schema.xml files containing tons of CAML in SharePoint 2007 – well, this is not the case any more – except several mandatory items you can now put mostly the customizations that you need and the schema file is still pretty small – several kilobytes or so – I will show you a sort of minimalistic schema.xml file below.

So here is a sample ListInstance element with a CustomSchema attribute:

  <ListInstance Title="ListInstance2"

                OnQuickLaunch="TRUE"

                TemplateType="100"

                FeatureId="00bfea71-de22-43b2-a848-c05709900100"

                Url="Lists/ListInstance2"

                CustomSchema="ListInstance2/Schema.xml"

                Description="">

And the custom (minimalistic) list schema file that I used for it:

<List xmlns:ows="Microsoft SharePoint" Title="Basic List" EnableContentTypes="TRUE" FolderCreation="FALSE" Direction="$Resources:Direction;" Url="Lists/Basic List" BaseType="0" xmlns="http://schemas.microsoft.com/sharepoint/">

  <MetaData>

    <ContentTypes>

      <ContentTypeRef ID="0x0100678499b7e7024385820d8586270c1a75" />

    </ContentTypes>

    <Fields></Fields>

    <Views>

      <View BaseViewID="1" Type="HTML" WebPartZoneID="Main" DisplayName="$Resources:core,objectiv_schema_mwsidcamlidC24;" DefaultView="TRUE" MobileView="TRUE" MobileDefaultView="TRUE" SetupPath="pages\viewpage.aspx" ImageUrl="/_layouts/images/generic.png" Url="AllItems.aspx">

        <XslLink Default="TRUE">main.xsl</XslLink>

        <RowLimit Paged="TRUE">30</RowLimit>

        <Toolbar Type="Standard" />

        <ViewFields>

          <FieldRef Name="Attachments" />

          <FieldRef Name="LinkTitle" />

          <FieldRef Name="Comments" />

        </ViewFields>

        <Query>

          <OrderBy>

            <FieldRef Name="ID" />

          </OrderBy>

        </Query>

      </View>

    </Views>

    <Forms>

      <Form Type="DisplayForm" Url="DispForm.aspx" SetupPath="pages\form.aspx" WebPartZoneID="Main" />

      <Form Type="EditForm" Url="EditForm.aspx" SetupPath="pages\form.aspx" WebPartZoneID="Main" />

      <Form Type="NewForm" Url="NewForm.aspx" SetupPath="pages\form.aspx" WebPartZoneID="Main" />

    </Forms>

  </MetaData>

</List>

A short explanation about the list schema above – so you see all major elements – ContentTypes, Fields, Views and Forms in the MetaData section – the Fields element even empty should be present (yes, otherwise you will receive an error on feature activation). In the ContentTypes element there is a single ContentTypeRef element which attaches a site content type to the list, the Fields element should normally contain all fields that should exist in the list, including the ones that are in the attached content types. A pretty nasty thing here of having to define a set of fields first as site columns and then in every list definition that uses the containing content type, isn’t it. I will demonstrate a work-around for the field redefinition issue in one of the next postings. Basically instead of ContentTypeRef you can use a ContentType element and define an inline list content type which uses site and/or list columns (both types alike provided in the Fields element).

In the Views element I’ve defined a single list view – pretty simple at that. You can see several FieldRef elements in the ViewFields of the view, the last one is actually for a field from the referenced site content type  - don’t worry that the field is missing in the Fields element – this won’t break the activation of the feature and can be fixed afterwards. The last metadata element is the Forms one – it is simply copied from a standard list definition schema and just defines the new, edit and display forms of the list.

The last bit in the sample schema worth noting is the EnableContentTypes attribute in the schema root element. It should be set to true even if you have just one content type in the list, otherwise in the new item menu in the all items view page of the list you will see the default “new item” label instead of the actual name of your content type.