onsdag 24 augusti 2011

More about the upgraded Content Query Web Part

A lot of the previous posts here are related to issues I found when I upgraded a farm from MOSS to SP2010. One of the most common web part used in the main site collection is the Content Query Web Part (CQWB). Unfortunately we've had plenty of strange issues with this web part. Some things that worked fine before the upgrade failed.

Some web parts didn't return any items (All these web parts are predefined in xml and added to sites via onet or manually as separate web parts). I think I've mentioned before that CQWBs scoped to a specific list should use the internal name of the fields specified in most places, but for CQWBs scoped to cross-list queries should use the id of the fields. In MOSS the sorting failed if the sort field was specified with an internal name so that field was specified with the field id, but fields in queries, common viewfields, column renames etc were specified with the field names. After upgrading most of the failing web parts could be fixed by using the correct field specification type, i.e. we exported the web parts, adjusted the xml and replaced the failing ones.

But more issues came up after a while. Some web parts crashed the page when we should edit it via UI. After a lot of struggle I found the failure. In the web part xml, four fields were specified as common view fields. The ULS said that the xml-definition for some fields couldn't be found. Those fields had nothing to do with this, but got me in the correct path. The actual exception that crashed the page was not caused by the web part, but it's toolpart. When the textboxes that displays the common view fields (not available in MOSS) are populated, the field identifiers are retrieved from the web parts xml-definition and fetched as SPField objects to get the display name.

In the MOSS days things worked fine even if the specified common view fields actually existed or not (to be honest I haven't actually looked at the code, but that was the behaviour), but now the fields MUST exist in the correct scope, i.e. in the list if the query is set to a specific list. Using dotPeek I read through loads of sharepoint (2010) code and found that they in one critical place didn't check for a possible null exception.
When the toolpart shall populate the textboxes that displays the DisplayName of the DataMappings or CommonViewFields, it uses the populateFieldsToDisplayControlGroup to loop through the fields specified to get the DisplayName. I haven't looked too deep inside the DataMapppings part of the loop, but when it comes to the CommonViewFields it calls the getDataMappingField method where str1 is the specified field name to get the SPField object and passes it to the ContentByQueryWebPart.GetFieldTitle method to get the DisplayName.

In the getFieldFromCollection method we can se that the first try to get the SPField object is by the Id and the second try is by the internal name. But the method can also return a null if the field doesn't exist in the SPFieldCollection passed from the getDataMappingField method (i.e. SPList.Fields in the case of a single list scope). And so the ContentByQueryWebPart.GetFieldTitle can get a null as an input parameter and it throws a null reference exception when it returns the field.Title value.

    public class ContentByQueryWebPart : CmsDataFormWebPartINamingContainerIWebPartVariationUpdate
    { 
... 
        internal static string GetFieldTitle(SPField field)
        {
            if (field.Id == ContentByQueryWebPartConstants.catchAllId)
                return Resources.GetDocumentManagementString("KeyFiltersKeywordsCatchAll_AllTags");
            else
                return field.Title;
        }
 
        internal static SPField getFieldFromCollection(Guid fieldId, string internalName, SPFieldCollection fields)
        {
            SPField spField = (SPFieldnull;
            try
            {
                if (fieldId.CompareTo(Guid.Empty) != 0)
                    spField = fields[fieldId];
            }
            catch (ArgumentException ex)
            {
            }
            try
            {
                if (spField == null)
                {
                    if (!string.IsNullOrEmpty(internalName))
                        spField = fields.GetFieldByInternalName(internalName);
                }
            }
            catch (ArgumentException ex)
            {
            }
            return spField;
        }
...
    }


    public sealed class ContentByQueryToolPart : ToolPart
    {
...
        private void populateFieldsToDisplayControlGroup(bool clearInvalidMappings)
        {
            ArrayList allSlotNames = this.contentByQueryWebPart.AllSlotNames;
            Hashtable stringAsHashTable1 = ContentByQueryToolPart.getStringAsHashTable(this.contentByQueryWebPart.DataMappings, '|'':');
            Hashtable stringAsHashTable2 = ContentByQueryToolPart.getStringAsHashTable(this.contentByQueryWebPart.CommonViewFields, ';'',');
            int index1 = 0;
            while (index1 < allSlotNames.Count)
            {
                string str1 = allSlotNames[index1] as string;
                string str2 = string.Empty;
                if (stringAsHashTable1.Contains((object) str1))
                {
                    string input = stringAsHashTable1[(object) str1] as string;
                    if (clearInvalidMappings && "LinkUrl".Equals(str1))
                    {
                        StringBuilder dataMappingResult = new StringBuilder();
                        string[] strArray1 = input.Trim().Split(new char[1] {';'});
                        int index2 = 0;
                        while (index2 < strArray1.Length)
                        {
                            string str3 = strArray1[index2].Trim();
                            if (!string.IsNullOrEmpty(str3))
                            {
                                string[] strArray2 = str3.Trim().Split(new char[1] {','});
                                Guid fieldId = Guid.Empty;
                                string internalName = string.Empty;
                                if (strArray2.Length > 1)
                                {
                                    fieldId = new Guid(strArray2[0]);
                                    internalName = strArray2[1];
                                }
                                SPField dataMappingField = this.getDataMappingField(fieldId, internalName, SPContext.Current.Web);
                                if (this.isSafeDataMapping(dataMappingField, str1))
                                    ContentByQueryToolPart.addFieldIntoDataMapping(dataMappingField, dataMappingResult, (StringBuildernull);
                            }
                            checked
                            {
                                ++index2;
                            }
                        }
                        input = ((object) dataMappingResult).ToString();
                    }
                    str2 = this.getDisplayedDataViewMapping(SPContext.Current.Web, input);
                }
                else if (stringAsHashTable2.ContainsKey((object) str1))
                    str2 = ContentByQueryWebPart.GetFieldTitle(this.getDataMappingField(Guid.Empty, str1, SPContext.Current.Web));
                this.fieldsToDisplayTextBoxes[index1].Text = str2;
                checked
                {
                    ++index1;
                }
            }
        }

        private SPField getDataMappingField(Guid fieldId, string internalName, SPWeb web)
        {
            return this.contentByQueryWebPart.CurrentQueryScope() != ContentByQueryWebPart.QueryScope.List ? ContentByQueryWebPart.getFieldFromCollection(fieldId, internalName, web.AvailableFields) : ContentByQueryWebPart.getFieldFromCollection(Guid.Empty, internalName, this.targetList.Fields);
        }
...
    }

My fields does exist as site columns but they doesn't exist in the list, so the solution is to either add the fields to the list or to remove them from the CommonViewFields in the web part.

Inga kommentarer:

Skicka en kommentar