MOSS MVP

I've moved my blog to http://blog.falchionconsulting.com!. Please update your links. This blog is no longer in use--you can find all posts and comments at my new blog; I will no longer be posting to this site and comments have been disabled.

Tuesday, October 2, 2007

Retarget Content Query Web Part

As I was going through my upgraded and moved sites I discovered that the "Grouped Listings" web parts were not working on my webs that I'd migrated from a subsite to a site collection or moved from one site collection to another. I didn't even notice they weren't working at first because when viewing a published page they were simply not showing anything (no title, or message about no results being returned or anything). When I went to edit a page I saw that the Grouped Listings web part was on the page but there was a message stating that the query returned no items. When I went to edit the web part instead of getting the nice toolbar I got taken to an error page which stated that the list did not exist.

After digging further I discovered that the web part was using the list ID (GUID) of the previously migrated list and not the new list that was created as a result of the move. The problem though was that the browser wouldn't let me retarget the web part to the correct list. So I had one of three options to solve the problem: delete the web part and replace it with something different; export the web part, change the GUID in the exported XML, import the changed web part and add it to the page, then delete the original web part; or programmatically manipulate the existing web part to point it to the correct list.

If you've read anything on this blog then you'll know which route I took. Programmatically fixing the web part was the most obvious choice to me because I could not only make the code stand alone so that it could be used to retarget any list but I could also re-use the code in my gl-moveweb and gl-convertsubsitetositecollection commands so that those commands would automatically adjust any detected Grouped Listings web parts.

It's important to note that the "Grouped Listings" web part that I'm referring to here is actually a ListingSummary web part which inherits from the ContentByQueryWebPart. This web part is created during an upgrade - in SPS 2003 there's a built-in list called Listings which is consumed by the Grouped Listings web part. During the upgrade this built-in list is converted to a standard list of the same name. But now it's not built-in so it can be edited like any other list. There's also a new content type added called "Listing" and the Listings list inherits from this content type.

Because the Grouped Listings web part is a bit "special" I decided to add some extra code into the command to help "repair" a content query web part that is pointed to a Listings list. That way if you delete the "Grouped Listings" web part but want to get something similar back you can add a new Content Query web part and point it to the Listings list and then run this command to set the properties that you simply cannot set via the browser. Other than this "special" code that I added the command itself basically just allows you to point a content query web part to a different list or site within the site collection. The core code is shown below:

   1: /// <summary>
   2: /// Adjusts the web part.
   3: /// </summary>
   4: /// <param name="web">The web.</param>
   5: /// <param name="wp">The web part.</param>
   6: /// <param name="manager">The web part manager.</param>
   7: /// <param name="listUrl">The list URL.</param>
   8: /// <param name="listType">Type of the list (list template).</param>
   9: /// <param name="siteUrl">The site URL.</param>
  10: internal static void AdjustWebPart(SPWeb web, WebPart wp, SPLimitedWebPartManager manager, string listUrl, string listType, string siteUrl)
  11: {
  12:  ContentByQueryWebPart cqwp = wp as ContentByQueryWebPart;
  13:  if (cqwp == null)
  14:   throw new SPException("Web part is not a Content Query web part.");
  15:  
  16:  if (listUrl != null)
  17:  {
  18:   SPList list = Utilities.GetListFromViewUrl(web, listUrl);
  19:   if (list == null)
  20:    throw new SPException("Specified List was not found.");
  21:  
  22:   if (list.ContentTypes["Listing"] != null)
  23:   {
  24:    // The list is a special list - it was upgraded from v2 and corresponds 
  25:    // to the grouped listings web part so we need to set some additional
  26:    // properties that cannot be set via the browser.
  27:  
  28:    ApplyListTypeChanges(web, cqwp, "Links");
  29:  
  30:    cqwp.AdditionalGroupAndSortFields = "Modified,Modified;Created,Created";
  31:    cqwp.DataColumnRenames = "SummaryTitle,Title;Comments,Description;URL,LinkUrl;SummaryImage,ImageUrl";
  32:    cqwp.SortByFieldType = "Number";
  33:    cqwp.ChromeType = PartChromeType.None;
  34:    cqwp.CommonViewFields = "SummaryTitle,Text;Comments,Note;URL,URL;SummaryImage,URL;SummaryIcon,URL;SummaryType,Integer;_TargetItemID,Note";
  35:    cqwp.FilterType1 = "ModStat";
  36:    cqwp.Xsl =  "<xsl:stylesheet xmlns:x=\"http://www.w3.org/2001/XMLSchema\" version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\" xmlns:cmswrt=\"http://schemas.microsoft.com/WebPart/v3/Publishing/runtime\" exclude-result-prefixes=\"xsl cmswrt x\" > <xsl:import href=\"/Style Library/XSL Style Sheets/Header.xsl\" /> <xsl:import href=\"/Style Library/XSL Style Sheets/ItemStyle.xsl\" /> <xsl:import href=\"/Style Library/XSL Style Sheets/ContentQueryMain.xsl\" /> </xsl:stylesheet>";
  37:    cqwp.FilterValue1 = "Approved";
  38:    cqwp.ShowUntargetedItems = false;
  39:    cqwp.FilterField1 = list.Fields["Approval Status"].Id.ToString();
  40:    cqwp.Filter1ChainingOperator = ContentByQueryWebPart.FilterChainingOperator.And;
  41:    cqwp.ItemLimit = -1;
  42:    cqwp.SortBy = list.Fields["Order"].Id.ToString();
  43:    cqwp.SortByDirection = ContentByQueryWebPart.SortDirection.Asc;
  44:    cqwp.GroupByDirection = ContentByQueryWebPart.SortDirection.Asc;
  45:    cqwp.Description = "Show listings in the portal.";
  46:    cqwp.GroupBy = "SummaryGroup";
  47:    cqwp.Hidden = false;
  48:   }
  49:   cqwp.WebUrl = list.ParentWeb.ServerRelativeUrl;
  50:   cqwp.ListGuid = list.ID.ToString();
  51:   cqwp.ListName = list.Title;
  52:  }
  53:  else if (siteUrl != null)
  54:  {
  55:   try
  56:   {
  57:    SPWeb web2 = web.Site.AllWebs[Utilities.GetServerRelUrlFromFullUrl(siteUrl)];
  58:    if (!web2.Exists)
  59:     throw new SPException();
  60:   }
  61:   catch (ArgumentException)
  62:   {
  63:    throw new SPException(siteUrl + " either does not exist is is invalid or does not belong to the web part's container site collection.");
  64:   }
  65:   catch (SPException)
  66:   {
  67:    throw new SPException(siteUrl + " either does not exist is is invalid or does not belong to the web part's container site collection.");
  68:   }
  69:   catch (FileNotFoundException)
  70:   {
  71:    throw new SPException(siteUrl + " either does not exist is is invalid or does not belong to the web part's container site collection.");
  72:   }
  73:  
  74:   cqwp.WebUrl = siteUrl;
  75:   cqwp.ListGuid = string.Empty;
  76:   cqwp.ListName = string.Empty;
  77:  }
  78:  else
  79:  {
  80:   cqwp.WebUrl = string.Empty;
  81:   cqwp.ListGuid = string.Empty;
  82:  }
  83:  
  84:  if (listType != null)
  85:   ApplyListTypeChanges(web, cqwp, listType);
  86:  
  87:  
  88:  manager.SaveChanges(cqwp);
  89: }
  90:  
  91: /// <summary>
  92: /// Applies the list type changes.
  93: /// </summary>
  94: /// <param name="web">The web.</param>
  95: /// <param name="cqwp">The content query web part.</param>
  96: /// <param name="listType">Type of the list.</param>
  97: private static void ApplyListTypeChanges(SPWeb web, ContentByQueryWebPart cqwp, string listType)
  98: {
  99:  if (listType == null)
 100:   return;
 101:  
 102:  SPListTemplateCollection listTemplates = web.Site.RootWeb.ListTemplates;
 103:  
 104:  SPListTemplate template = listTemplates[listType];
 105:  if (template == null)
 106:   throw new SPException("List template (type) not found.");
 107:  
 108:  cqwp.BaseType = string.Empty;
 109:  cqwp.ServerTemplate = Convert.ToString((int)template.Type, CultureInfo.InvariantCulture);
 110:  
 111:  bool isGenericList = template.BaseType == SPBaseType.GenericList;
 112:  bool isIssueList = template.BaseType == SPBaseType.Issue;
 113:  bool isLinkList = template.Type == SPListTemplateType.Links;
 114:  cqwp.UseCopyUtil = !isGenericList ? isIssueList : (isLinkList ? false : true);
 115: }

The syntax of the command I created can be seen below.

C:\>stsadm -help gl-retargetcontentquerywebpart

stsadm -o gl-retargetcontentquerywebpart

Retargets a Content Query web part (do not provide list or site if you wish to show items from all sites in the containing site collection).

Parameters:
        -url <web part page URL>
        {-id <web part ID> |
         -title <web part title>}
        [-list <list view URL>]
        [-listtype <list type (template) to show items from>]
        [-site <show items from this site and all subsites>]
        [-allmatching (if title specified and more than one match found, adjustall matches)
        [-publish]

Here’s an example of how to retarget all web parts titled "Grouped Listings":

stsadm -o gl-retargetcontentquerywebpart -url "http://intranet/hr/pages/default.aspx" -title "Grouped Listings" -list "http://intranet/hr/lists/summary links/allitems.aspx" -allmatching

Note that you can specify a list type in the event that you are trying to piont the web part to a completely different type of list (this may not work smoothly if your list has completely different content types and fields as the web part will still attempt to display the fields that it was originally setup to display).

I've updated the gl-moveweb and gl-repairsitecollectionimportedfromsubsite commands (used by gl-convertsubsitetositecollection) so that they will attempt to repair the Grouped Listings web parts.

11 comments:

o.net said...

This might be EXACTLY what i need thanks for the PITCH !

TVK said...

Is there a way to do the same (Modifying the custom list) for the listview webpart. (My listview webpart contains nothing but links list.

Thanks,
TVK

Gary Lapointe said...

You might be able to extend the replacewebpartcontent command. I don't have anything at the moment that directly works with the listview web part though.

zieglers said...

Hi,

Similar to Gary's question above, is there any command for updating list ids in data form web parts?

Thanks,
zieglers

Amreesh Sharma said...

Hi Gary,
I have a CQWP News Aggregator list. Actuaaly the news items from 5 dofferent sites are coming in this aggregator list based on my expiration policy and job i created. Now if i am trying to integrate the same CQWP with my production Site its giving the error "Unable to display this Web Part. To troubleshoot the problem, open this Web page in a Windows SharePoint Services-compatible HTML editor such as Microsoft Office SharePoint Designer. If the problem persists, contact your Web server administrator."

Actually i am saving it as a list template and then importing on my importing on the production site.its giving problems.
Please tell me the exact solution. its urgent.
Thanks in Advance.
Regards
Amreesh

Gary Lapointe said...

Export the web part and check all URLs and GUIDs (including content type IDs) that are present in the exported XML - validate that you can locate all those items in your target site. If not, then adjust the XML to match your target site and re-import.

Matt said...

Hi Gary,

This command is very useful, thanks for sharing this with the community.

I'm trying to use a similar approach to update the standard ListFormWebPart controls on custom EditForm.aspx and DispForm.aspx pages.

I'm deploying the custom pages via a Feature to the 'Forms' folder in an existing document library and am then associating the custom pages to the content type via the FeatureActivated code successfully. I am also trying to update the 'ListName' property GUID in the ListFormWebPart to match the document library GUID.

For some reason, I cannot seem to update the 'ListName' property, but can update the 'Title' property.

Have you tried this before or know how I can approach this?

Matt

markpittsnh said...

Hello Gary,

Have you ever come across the symptom, where after content deployment, the CQWP query source on the target is pointing to "/default.aspx/{listname}" instead of just "/{listname}"?
Cheers
Mark

markpittsnh said...

I was delighted to find out that the following command actually worked! However, I wish I knew why content deployment from staging to production is resulting in these symptoms.

stsadm -o gl-retargetcontentquerywebpart -url "http://url/Pages/EnterprisePerformanceManagement.aspx" -title "Marketing Detail" -allmatching -publish

I am running into another issue, though. One of my CQWP's is not getting fixed. Although this command returns successful.
stsadm -o gl-retargetcontentquerywebpart -url "http://url/Pages/EnterprisePerformanceManagement.aspx" -title "Marketing Menu" -allmatching -publish

The content is not appearing on the page. I tried using your enumerate web part utility, as in the following,

stsadm -o gl-enumpagewebparts -url "http://url/Pages/EnterprisePerformanceManagement.aspx"

Unfortunately, the web part is not listed in the output. When I edit the page, I can see the web part.

QN: Does it matter that the page's web part originates from the page layout? I even tried enumerating the page layout. Even there, I could not find the web part.

Cheers
Mark

Gary Lapointe said...

Web parts stored as part of the page layout are stored in an entirely different way and are not available via the SPLimitedWebPartManager which is the class I have to use to get to the web parts. So, in short, I can't currently fix web parts that are stored as part of a page layout - you'll need to manually fix those.

markpittsnh said...

Hello Gary,
From my staging, I replaced the page layout derived Web part with a CQWP created directly on that particular page. Then I ran a manual content deployment job to push the page to production.

From production, I ran the enumerate web part util. Here is the output for that web part

<WebPart id="g_0c0b70ba_ab3e_4a22_a2b9_f86e0d01c8be">
<Title>SubPage Mkt Coll Menu</Title>
<AllowClose>True</AllowClose>
<AllowConnect>True</AllowConnect>
<AllowEdit>True</AllowEdit>
<AllowHide>True</AllowHide>
<TitleUrl>
</TitleUrl>
<AllowMinimize>True</AllowMinimize>
<AllowZoneChange>True</AllowZoneChange>
<CatalogIconImageUrl>
</CatalogIconImageUrl>
<ChromeState>Normal</ChromeState>
<ChromeType>None</ChromeType>
<Description>Use to display a dynamic view of content from your site on a web page</Description>
<DisplayTitle>SubPage Mkt Coll Menu</DisplayTitle>
<HasSharedData>False</HasSharedData>
<HasUserData>False</HasUserData>
<Hidden>False</Hidden>
<IsClosed>True</IsClosed>
<IsShared>True</IsShared>
<IsStandalone>False</IsStandalone>
<IsStatic>False</IsStatic>
<Subtitle>
</Subtitle>
<TitleUrl>
</TitleUrl>
<TitleIconImageUrl>
</TitleIconImageUrl>
<Zone>Sub_Header_Left</Zone>
<ZoneIndex>0</ZoneIndex>
</WebPart>

Then I ran the retarget as follows.

"stsadm" -o gl-retargetcontentquerywebpart -url "http://url/Pages/TechnologyConsulting.aspx" -id "g_0c0b70ba_ab3e_4a22_a2b9_f86e0d01c8be" -publish

output is

"Operation completed successfully."

And yet the web part does not display. When I edit the page. I see that the list it's pointing to is, as I have been seeing before, "/default.aspx/MarketCollateral".

Any thoughts?

Cheers
Mark