Wednesday, October 1, 2008

SharePoint: All webparts appear as ErrorWebParts when using SPLimitedWebPartManager

Hi! recently I was given a task to update properties of multiple webparts(custom developed webparts created by extending existing SharePoint OTB webparts). Now the trick was to fetch those webparts across pages multiple pages and update their properties and UI formatting instructions based on a configuration file which is provided at run time on feature activation.

Seems a simple enough task, where one needs to retreive page-url, fetch SPLimitedWebPart manager based on page-url. Use SPLimitedWebPart Manager to iterate through collection of webparts which exist of specified page URL and update properties based on configuration input.

Like any other developer, I started of by creating a console application and dumping code in it like a no mad. Half-way down the line when I began to query webparts collection using SPLimitedWebPart Manger, I see all webparts returned are of type "ErrorWebParts".

[code]
using (SPSite mysite = new SPSite("http://server:27158/"))
{
  using (SPWeb myweb = mysite.RootWeb)
  {
  myweb.AllowUnsafeUpdates = true;

using (SPLimitedWebPartManager manager = myweb.GetLimitedWebPartManager(
"/Pages/Home.aspx", System.Web.UI.WebControls.WebParts.PersonalizationScope.Shared))
{
IEnumerator myWebPArtsEnum = manager.WebParts.GetEnumerator();
String xmlstring = "";
while (myWebPArtsEnum.MoveNext())
{
xmlstring += "
if (myWebPArtsEnum.Current.ToString() =="IST.MOSS.Webparts.ISTContentByQueryWebPart")
{  
xmlstring += " Title='" + ((System.Web.UI.WebControls.WebParts.WebPart)myWebPArtsEnum.Current).Title + "'";
xmlstring += " ID='" + ((System.Web.UI.WebControls.WebParts.WebPart)myWebPArtsEnum.Current).ID + "'";
xmlstring += " DisplayTitle='" + ((System.Web.UI.WebControls.WebParts.WebPart)myWebPArtsEnum.Current).DisplayTitle + "'";
((Microsoft.SharePoint.Publishing.WebControls.ContentByQueryWebPart)myWebPArtsEnum.Current).ItemLimit = 20;
//((Microsoft.SharePoint.Publishing.WebControls.ContentByQueryWebPart)myWebPArtsEnum.Current).CommonViewFields = "Title,Text;URL,URL;Forward_x0020_to_x0020_URL,Link;Show_x0020_for_x0020_Locations,Text;";
//PublishingWeb pw = PublishingWeb.GetPublishingWeb(myweb);
//((Microsoft.SharePoint.Publishing.WebControls.ContentByQueryWebPart)myWebPArtsEnum.Current).Update(pw);
manager.SaveChanges((Microsoft.SharePoint.Publishing.WebControls.ContentByQueryWebPart)myWebPArtsEnum.Current);
myweb.Update();
}
xmlstring += " />";
}
xmlstring += "";
}  
myweb.AllowUnsafeUpdates = false;
}
}
[/code]

This code loops through all webparts on all pages, checks to see if the webpart
is a ContentByQueryWebpart and updates some properties.

As soon as you use webpartmanager to fetch webpart, it will return your queried

webpart as Microsoft.SharePoint.WebPartPages.ErrorWebPart.

Why? Because of the following code is called by the specific properties of the ContentByQueryWebpart

(actually its parent, the CmsDataFormWebpart):

internal static string MakeServerRelativeUrl(string url)
{
    return concatenateUrls(SPContext.GetContext(HttpContext.Current).Site.ServerRelativeUrl, url);
}
The webpart will always call the SPContext, but from a console
application there is no web-context. Therefore when initiating the
ContentByQueryWebpart, it will always thow an exception like:

"An error occured while setting the value of this property:
Microsoft.SharePoint.Publishing.WebControls.ContentByQueryWebPart:MainXslLink -
Exception has been thrown by the target of an invocation."

A workaround for this would be to provide it with a context. Take a look at the
modifications done to rectify the problem -

[code]
using (SPSite mysite = new SPSite("http://server:27158/"))
{
using (SPWeb myweb = mysite.RootWeb)
{
myweb.AllowUnsafeUpdates = true;

bool isContextNull = false;
if (HttpContext.Current == null)
{
isContextNull = true;
HttpRequest request = new HttpRequest("", myweb.Url, "");
HttpContext.Current = new HttpContext(request, new HttpResponse(new StringWriter()));
HttpContext.Current.Items["HttpHandlerSPWeb"] = myweb;
}

using (SPLimitedWebPartManager manager = myweb.GetLimitedWebPartManager(
"/Pages/Home.aspx", System.Web.UI.WebControls.WebParts.PersonalizationScope.Shared))
{
IEnumerator myWebPArtsEnum = manager.WebParts.GetEnumerator();
String xmlstring = "";
while (myWebPArtsEnum.MoveNext())
{
xmlstring += "
if (myWebPArtsEnum.Current.ToString() =="IST.MOSS.Webparts.ISTContentByQueryWebPart")
{
xmlstring += " Title='" + ((System.Web.UI.WebControls.WebParts.WebPart)myWebPArtsEnum.Current).Title + "'";
xmlstring += " ID='" + ((System.Web.UI.WebControls.WebParts.WebPart)myWebPArtsEnum.Current).ID + "'";
xmlstring += " DisplayTitle='" + ((System.Web.UI.WebControls.WebParts.WebPart)myWebPArtsEnum.Current).DisplayTitle + "'";
((Microsoft.SharePoint.Publishing.WebControls.ContentByQueryWebPart)myWebPArtsEnum.Current).ItemLimit = 20;
//((Microsoft.SharePoint.Publishing.WebControls.ContentByQueryWebPart)myWebPArtsEnum.Current).CommonViewFields = "Title,Text;URL,URL;Forward_x0020_to_x0020_URL,Link;Show_x0020_for_x0020_Locations,Text;";
//PublishingWeb pw = PublishingWeb.GetPublishingWeb(myweb);
//((Microsoft.SharePoint.Publishing.WebControls.ContentByQueryWebPart)myWebPArtsEnum.Current).Update(pw);
manager.SaveChanges((Microsoft.SharePoint.Publishing.WebControls.ContentByQueryWebPart)myWebPArtsEnum.Current);
myweb.Update();
}
xmlstring += " />";
}
xmlstring += "";
}

if(isContextNull)
HttpContext.Current = null;
myweb.AllowUnsafeUpdates = false;
}
}
[/code]

Hope this helped.

-cheers

7 comments:

Hamed said...

Great Article!!! But I'm still facing the issue with my console app, I'm missing something here.

Anonymous said...

For a console app, make your your web part assembly is in the GAC.

Xopher said...

You're amazing. Creating the HttpContext did the trick. Don't have to worry about timeout on my 4000+ files that need updated. How did you ever figured this out?

Thanks,
Xopher

C├ędric Giffart said...

Great post!
I was battling with this error for a while now. Creating httpContext fixed it all, I can update my webpart smoothly now.

Thanks a lot!
Cedric

Brian L Bedard said...

Good investigation!

http://pioneeringsharepoint.blogspot.com/2009/09/i-need-my-spcontext-now.html

Lionel Limozin said...

Great post, thanks for sharing !

Andreas - SharePoint Tutorial said...

Thanks for that... definitely a great post since it solved my problem.

Setting the SPContext did the trick...