Thursday, June 24, 2010

WikiEditPage.InsertWebPartIntoWikiPage - does it have a bug?


SharePoint 2010 has new interesting feature "Team Site Wiki". It's enabled by default for each team site and replace original site home page (default.aspx) with sitepages/home.aspx. This new page is Wiki and can be edited very simply: you can just type any text there, insert image (without Image WebPart) and etc. Web Parts can be used also.

How to edit this wiki page programmatically?

First of all you need to open this aspx file:
SPFile wikiFile = web.GetFile(wikiUrl);

After that you can get access to its Item and LimitedWebPartManager.

Wiki page body is stored in wikiFile.Item["WikiField"] field.
Empty wiki page contains something like this
<div class="ExternalClassB93FFFFBB50E42E5B5D1BE5F906438A1">
<table id="layoutsTable" style="width:100%">
<tbody>
 <tr style="vertical-align:top">
  <td style="width:100%">
  <div class="ms-rte-layoutszone-outer" style="width:100%">
  <div class="ms-rte-layoutszone-inner"></div>
  </div>
  </td>
 </tr>
</tbody>
</table>
<span id="layoutsData" style="display:none">false,false,1</span>
</div>


So to add some text message to wiki page you need just to insert your text in HTML format:
<p> Welcome to our WIKI</p>

in div with ms-rte-layoutszone-inner class:
<div class="ms-rte-layoutszone-inner"><<p> Welcome to our WIKI</p>/div>

Ok. But how to add new Web Part to this page. SharePoint 2010 Object Model suggests to use WikiEditPage.InsertWebPartIntoWikiPage method for this purpose.
But looks like it works incorrect: Position parameter is processed by the wrong way and you get non-working page after update.

Reflected source code:

public static void InsertWebPartIntoWikiPage(SPFile wikiFile, WebPart webpart, int position)
{
if (wikiFile == null)
{
throw new ArgumentNullException("wikiFile");
}
if (webpart == null)
{
throw new ArgumentNullException("webpart");
}
string str = (string) wikiFile.Item["WikiField"];
if (position < 0)
{
throw new ArgumentOutOfRangeException("position");
}
if ((str != null) && (position > str.Length))
{
throw new ArgumentOutOfRangeException("position");
}
SPLimitedWebPartManager limitedWebPartManager = wikiFile.GetLimitedWebPartManager(PersonalizationScope.Shared);
Guid storageKey = Guid.NewGuid();
string str2 = Utility.StorageKeyToID(storageKey);
webpart.ID = str2;
limitedWebPartManager.AddWebPart(webpart, "wpz", 0);
string str3 = string.Format(CultureInfo.InvariantCulture, "<div class=\"ms-rtestate-read ms-rte-wpbox\" contentEditable=\"false\"><div class=\"ms-rtestate-read {0}\" id=\"div_{0}\"></div><div style='display:none' id=\"vid_{0}\"></div>
</div>", new object[] { storageKey.ToString("D") });
if (str == null)
{
str = str3;
}
else
{
str = str.Insert(position, str3);
}
wikiFile.Item["WikiField"] = str;
wikiFile.Item.Update();
}
Insert call corrupts original WikiField value because, for instance, if your position =2, you get failed HTML. Even your position=0 and Wiki page looks nice - you cannot edit it.
So I don't recommend to use InsertWebPartIntoWikiPage in your work.

You can write own implementation which insert new str3 (check source code above) in the correct place: in div with ms-rte-layoutszone-inner class.

Note: Microsoft.SharePoint.WebPartPages.Utility.StorageKeyToID is internal, but you can replace it with own implementation also:
string StorageKeyToID(Guid storageKey)
{
if (!(Guid.Empty == storageKey))
{
return ("g_" + storageKey.ToString().Replace('-', '_'));
}
return string.Empty;
}

Addition: to have ability web part drag&drop you need to insert <p> </p> before and after your web part HTML node

Friday, June 18, 2010

SharePoint 2010 Client Object Model, get folder Item

Task: you need to apply permissions to the existing folder in the document library. But it can be done only for ListItem which corresponds to this folder. How to get this ListItem?

Resolution: In SharePoint Object model you can use SPFolder.Item property. But SCOM Folder doesn't have such prop. You need to use CAML query to get this item:


m_rootTrgUrl = web URL
mm_trgParentWebId = web ID
m_trgListId = existing list ID
srvRelativeURL = folder server-relative URL


using (var clientContext = new ClientContext(m_rootTrgUrl))
{
var trgWeb = clientContext.Site.OpenWebById(m_trgParentWebId);
var trgList = trgWeb.Lists.GetById(m_trgListId);
var query = new CamlQuery();
query.ViewXml = "<View Scope=\"RecursiveAll\"> " +
"<Query>" +
"<Where>" +
"<And>" +
"<Eq>" +
"<FieldRef Name=\"FSObjType\" />" +
"<Value Type=\"Integer\">1</Value>" +
"</Eq>" +
"<Eq>" +
"<FieldRef Name=\"Title\"/>" +
"<Value Type=\"Text\">" + folderName + "</Value>" +
"</Eq>" +
"</And>" +
"</Where>" +
"</Query>" +
"</View>";

query.FolderServerRelativeUrl = srvRelativeURL;

var folderItems = trgList.GetItems(query);

clientContext.Load(trgList);
clientContext.Load(folderItems);
clientContext.ExecuteQuery();

switch (folderItems.Count)
{
// process query result
}
}

Thursday, June 17, 2010

SharePoint 2010 Client Object Model, attachment creation

Unfortunately SCOM doesn't support attachment creation. It doesn't have any API which allows to create attachments if item has no attachment already.

Microsoft recommends to use Lists.asmx SharePoint web service if you need to create attachment remotely. Or create own WCF service in the SharePoint context.

If item already has attachments (at least one) you can add new attachments with using next approach:

using Microsoft.SharePoint.Client;
using SP = Microsoft.SharePoint.Client;
using System.IO;

public static void AddListItemAttachment(string attachFilePath, string listUrl, string itemId)
{
ClientContext clientContext = new ClientContext(listUrl);
Uri url = new Uri(listUrl);

using (FileStream strm = new FileInfo(attachFilePath).Open(FileMode.Open))
{
var attachUrl = url.AbsolutePath + "/Attachments/" + itemId + "/" + Path.GetFileName(attachFilePath);
SP.File.SaveBinaryDirect(clientContext, attachUrl, strm, true);
}
}

This method is based on the knowledge that SharePoint stores item attachments in the special folder with path: ListURL+"/Attachments/" + itemID.
But I didn't find a way to create this folder if it doesn't exist so this method works only if folder exists already (it means that item has at least 1 attachment already). Usual way for folder creation by SCOM doesn't work in this case(with error "Unable to complete action").

SharePoint Web Parts don't work with "Incorrect properties format" error

Issue: Sometimes, when you open SharePoint 2007 site, you see that all web parts show

"Web Part Error: One of the properties of the Web Part has an incorrect format. Windows SharePoint Services cannot deserialize the Web Part. Check the format of the properties and try again."

Furthermore, sometimes this site and its web part work correctly: you see all web part working. This behavior doesn't dependent from browser version or host. You're just clicking "Refresh" and see different results: web parts work or don't work.

Possible reason: If you have more than 1 SharePoint front-end on the target farm, check, please, how do they work. If some of front-ends have problem with free disk space, you can observe problems with web part functionality. Eventlog on this front-end contains errors with ASP .NET 2.0 problems during the page generation.

During the "Refresh" in browser you can get results from different front-ends, it depends from your SharePoint load-balancing infrastructure. So you can see correct pages from "good" front-end and bad pages from "unhealthy" SharePoint front-end.

Issue is absent after disk cleanup on the "unhealthy" SharePoint front-end.