Tag Archive for SharePoint

Ghost Files


For some obscure reason, Windows Sharepoint Services allows me to fetch a non-existent file.


Consider this code:
SPSite site = new SPSite(site_url);
SPFile file = site.RootWeb.GetFile(file_url);


This code will return an SPFile object regardless of whether file_url points to an existing file or not.
To find out if the file exists, you need to call the file.Exists() method.
Even if we ignore the fact that it’s against the common practice of throwing exceptions for these situations, it still behaves oddly.


You would expect that the properties for a non-existent file would be null, or at least throw exceptions – and indeed, most of them do throw (more on that later), but some of them DON’T – properties like the Name and URL of the file return values that are derived from the original URL I passed it, even though they do not, in fact, point to an existing file.


And the other properties DO throw, but they either throw a System.ArgumentOutOfRangeException, which makes absolutely no sense, or else a SPException specifying that the document library doesn’t exist. The same property can throw a different one of the above exceptions, depending on whether the URL we gave it points to a non-existing file in an existing doc library, or to a doc library that doesn’t exist at all.


It’s a mess, I tell you – a mess.

To CLS or not to CLS.

For some reason, Sharepoint’s object model doesnt adher to Microsoft’s recommended practices – namely, trying to keep your external interfaces CLS-compliant.

It seems the object model itself exposes several methods and properties that use the UInt32 data-type, even if the SDK claims otherwise.

Be Vewy Vewy Caweful – We’we Instawwing Shawepoint!

Sharepoint 2003’s installation process is one of the most delicate and uncooperative I’ve seen in while. Quite a surprise in this day and age – responsive, coherent installations is something we’ve grown to expect from Microsoft’s server systems in recent versions – or maybe it’s just me. :)

Sharepoint’s installation can be baffled by any number of reasons – lack of harddisk space on drive C:, remains of previous installations, even something as simple as a wrong password entered for the applicative user to run the services. All of these errors share one thing between them – the installer won’t pop up a messagebox to let you know what happened. It won’t even give you an error warning and let you try again (to a different path perhaps, or to retype the username). It will simply continue on UNINSTALLING SPS (and WSS, if it was part of the process) and direct you to a folder full of log files out of which you’re suppose to dig the actual reason for the failure and presumably try again – yes, a 10-minute process just to find out I forgot to put the domain name before the username for the service account.

Extremely annoying behavior, especially when this unexpected uninstallation doesn’t always do a perfect job of removing all traces of what just happened from the registry.

So remember, boys and girls. When installing Sharepoint, be extra-careful to cross your T’s and dot your I’s and make sure to read every screen for potential pitfalls, because wizard or not, you won’t always have a Back button to fix your mistakes. Tread carefully…

Lookup Hijinx

Here’s something that mired me down for several hours, so may it be Googled and seen and used by Sharepoint developers:


 


We’re developing a solution right now based on SPS and WSS sites, under several different Site Collections, that involves copying files between sites.


The copying itself is done the same as the MSDN samples show – adding the byte stream to the FileCollection of the destination library – but since we’re just passing the bits, the file’s metadata isn’t automatically copied from one List to the other.


If we’re copying Office documents, we have automatic demotion and promotion of metadata properties from the Sharepoint List into the document, which then goes back into the new list. But if we’re copying non-Office documents, the metadata is simply lost.


This was bypassed with a simple function after copying that also synchronizes the metadata – but that’s where I ran into a strange anomaly having to do with SPFieldLookups – fields that derive their list of possible values from a list in the site.


The format that is saved in the metadata for these kind of fields contains both the identifier of the item in the lookup-list, and the actual string to be displayed, in this format:


“1;#myValue” – with myValue being shown in the list.


Now I want to copy this string from one SPListItem to another – but keep getting strange SPExceptions saying that the data is in an invalid format and suggesting that it is, perhaps, read-only. Preposterous, of course.


 


More digging and hacking showed that in order to copy the value, we only have to copy the number, not the value.


It DOES make sense, now that I understand it – I only want to pass the ID, after all, not the whole string value – that’s why it’s a lookup field. I assume that it’s stored inside the list for caching, so we won’t have to go to the lookup list every time, but still – an explanation, a sample or a meaningful error message would have been appreciated.


 

PortalRight – all wrong.

A followup to this post:

The PortalRight enumeration defined in Microsoft.Sharepoint.Portal.Security is, for some reason, derived from UInt32 rather than Int32. You can see this in the Object Browser when in a VB.NET project.
This anomaly – nothing more than a raised eyebrow in a C# project – seems to be crippling in a VB.NET project.
Whenever I try to use this enum in code, the compiler claims that it can’t cast from Integer to PortalRight – even I always use the enum:

Dim acc as AreaAccessControl = new AreaAccessControl(ctx, PortalRight.ViewArea, myArea.ID)

Gives a compiler error in VB.NET, but works fine in C# – even though the second parameter is defined as PortalRight.
Casting it to UInt32 simply gives a “Can’t convert UInt32 to PortalRight”.
The only way I could get it to compile (didn’t check if it worked) is by doing Enum.Parse on PortalRight.ViewArea.ToString(), which is just SO wrong.

Anyone know why this is so?

SPS Area Templates

And, to close of the day’s rants – an instructive post.

Today: How to create a template of a Sharepoint Area that can be moved or taken to different servers.

Unfortunately, SPS Areas can’t be bundled into STP templates like WSS sites or lists, and there’s no built-in method of exporting an area into a convenient format so out Frontpage customizations and painstakingly defined layout can be copied to a different site.

What we CAN do, though, is simply edit the area in question using FrontPage 2003 and File->Save As to save the ASPX to our local drive. Any locally-referenced images will also be downloaded. This ASPX can now be copied to a different server and serve as a template, using these steps:  (Warning: My Sharepoint installation is in Hebrew, so some of my translated links and steps might be a bit off).

1) Copy the ASPX and any dependent files into a Document Library on the new SPS server. The ASPX must reside inside Sharepoint, probably for security reasons. I got strange errors when trying to put the file in the /_layouts directory, so I’ll stick to this method until proven wrong.

2) Create an area that will use our template, or modify an existing one (warning: The current content of the area will be unavailable once we attach the template).

3) Go to Change Settings (on the left) -> choose the Page tab ->  Area Templates -> This area uses the following page as a template.

4) Specify the virtual path to our ASPX template – I put it in the main portal’s Document Library, so the path was:
/Document%20Library/template.aspx

5) Press OK, and your area is now based off of that page. The original content is unavailable but not erased – if you change the Area Templates radio-button back to what it was, you’ll get the old page back.

There. Enjoy.

LoadControl and HTML Comments

Developin webparts with many UI elements can be a hassle – which is why I’m fond of the UserControl shortcut – I build my web UI as a WebUserControl, then simply have my webpart call Page.LoadControl to dynamically load that usercontrol.

A problem I’ve encountered with it today is that LoadControl, strangly enough, ignores HTML comment in the ASCX file.

Let’s say I have the following code in the ASCX:


This should instantiate only one Repeater object – and does, when the ASCX is hosted normally inside an ASPX page.
When I load it dynamically, though, I get an exception in LoadControl stating that “Parse Error: A control named Repeater1 is already defined” – meaning that for some reason it parses the first control too, even though it’s commented out.
If I erase the commented section, it runs as planned.

Anyone know why it ignores my markup?

 

Binding to Sharepoint List Items (Part 1 of 2)

Databinding is cool. It lets us connect our front-end GUI elements (say, a DataGrid) to our back-end data elements (a DataSet, for instance) without the drudgery of synchronizing the two all the time. True, ASP.NET Datagrids are one-directional, but it’s still cool.

The nice thing about databinding is that it can be done with custom objects, not only DataSets and such. Specifically, a DataSet’s DataSource can be any object that implements ICollection. Let’s see two ways to use this to bind a datagrid to a group of SPListItems:

1) Binding to a SPListItemCollection – the straightforward way.

The binding itself is a snap – if we have an SPListItemCollection object (from an SPList’s Items property, for instance) we can simply set it as the DataSource of our grid – it implements ICollection and all is well:
SPListItemCollection items = myList.Items;
dgItems.DataSource = items;

The problem, however, is with the Binding code in our ASPX page. We can bind to properties of the SPListItem object, but not to indexed properties. Code such as this will work, and bind to the current item’s ID property:
<%# DataBinder.Eval (Container.DataItem, "ID") %>

But how do I bind to myListItem[“Title”] or myListItem[“MyCustomProperty”]?

One thing that’s easy to forget is that we can write normal .NET code inside the <%# %> blocks, even if the lack of Intellisense seems otherwise. So we can cast our DataItem to an SPListItem and get the property:

<%# ((SPListItem)Container.DataItem)["MyProperty"] %>

This requires a few additions to the top of the ASPX:

<%@ Import Namespace="Microsoft.SharePoint" %>  
<%@ Assembly Name=""Microsoft.SharePoint, Version=11.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"" %> 

Note the second line. This is because the code in our embedded script blocks doesn’t use the references from the main project, and we have to manually specify the Sharepoint assembly reference.

Using the same concept, we can bind to any property – or even method call results – of any custom object we have.

How to get the current SPS Area in a Web-Part

When we write a WebPart or WebControl that resides in a SPS Area, we will want to know at runtime which area we reside in.
This, surprisingly enough, is not trivial. It’s not even easy. In fact, it’s downright obscure

You can easily get the current SPWeb you’re in using the SPControl object, but while the Area class can expose the underlying SPWeb, an SPWeb can’t give you the equivalent Area – which makes sense, since SPS’s Areas are built on top of WSS’s SPWebs.

This data is stored quite nicely in the database – there’s a WebCat table that cross-references Area GUIDs with Web GUIDs – but we really don’t want to access the DB directly, do we?

The (obscure) solution is this:

From the Context property of the webpart/control, access the stored PageInfo object stored in the hashtable:

PageInfo pi = (PageInfo)Context.Items[“SPS_PageInfo”];

The PageInfo class is defined in Microsoft.SharePoint.Portal.WebControls namespace, and is apprently undocumented in any SDK I found. It contains a property named CategoryID, which is the Guid of the current Area:

Guid currAreaGuid = pi.CategoryID;

Once we have the Guid, it’s clear sailing all the way:

Area currArea = AreaManager.GetArea(PortalContext.Current, currAreaGuid);

And there was much rejoicing.

Some thoughts on SPS Content Indexing [1/3]

[Warning: I only have Hebrew SPS installations around, so if I make some mistakes when translating the menu options, try to guess what I meant :]

  • Advanced Search Mode

Most of the features mentioned require Sharepoint to be in Advanced Search mode – this is enabled in the Site Settings -> Search Settings page.
SPS will ominously warn about the change being irreversible, but it is harmless – simply adds some more complexity to the Search administration screens.

  • Crawling custom list items.

A default installation of SPS will automatically create a content index called Portal_Content, which indexes all the data stored in the SPS storage – this includes the main page, Portal Areas, listings, document libraries and custom lists saved in Areas.

For some reason, the default behavior when indexing lists is to crawl over all the items therein, but when searched to return the complete list as the search result, rather than the individual list item. I assume it’s done for efficiency, or whatever. We’ll often want to change that:

Go to Site Settings -> Search Settings -> Manage Indexes, edit the Portal_Content index and go to the Inclusion Rules page.
We’ll see two rules grouped under a heading – the first is an Exclusion rule that prevents ASPX pages from being indexed, the second is the general Inclusion rule to index everything else.

Editing the inclusion rule properties will show two checkboxes that will enable custom item crawls – “Enable Alerts for Individual Items” and “Crawl individual list items”. The problem is that they’re both disabled.

I don’t know why they are, but the solution is simple – erase the rule and recreate it.
Remember to make sure the rule is after the Exclusion rule, points at http://server:port/, and has the bottom two checkboxes set (Be careful – the fourth checkbox automatically sets the third too, so don’t uncheck it by accident).

Now run an update on the index to make sure the custom list items are indexed.