Friday, December 30, 2005

SharePoint RPC Methods

I was browsing through Heather's list of SharePoint resources when I found this link to an RPC method that I was unaware of. That led to this article that documents the various RPC methods and provides even more links to other information.

If I'm not mistaken, isn't this just the sort of "associative trail" that Vannevar Bush had in mind all along???

Thursday, December 29, 2005

Extreme SharePoint Design: Adding a Second 'Save and Close' Link to Surveys

Surveys are usually short and to the point but sometimes they can be quite long, especially if there a large number of multiple-choice questions. Users often complain about having to scroll back up to the top of the page to save their responses after filling out the form (this is true of any list, not just surveys). Adding a second 'Save and Close' link to the bottom of the page is an effective solution for improving usability.

Like everything else in SharePoint, implementing this functionality is not quite as simple as it might first appear to be. First off, the pages that display the 'Save and Close' link, newform.aspx and editform.aspx, are just container pages for dynamically-rendered CAML (Collaborative Application Markup Language, that tangle of XML gobbledygook that makes SharePoint go). The real meat is in SCHEMA.XML under /Lists/Voting in the various site definitions (60/Template/1033/SPS and so on). This means that there is no direct HTML for you to edit; you have to modify the proper sections in the schema file.

To begin with, locate the following line:

<Form Type="EditForm" Url="EditForm.aspx" WebPartZoneID="Main">

This is the beginning of the definition section for the Edit Response form on EDITFORM.ASPX. There are four main sections which define the form display: <ListFormOpening>, <ListFormButtons>, <ListFormBody>, and <ListFormClosing>. The HTML for the toolbar that contains the 'Save and Close' link is found in the <ListFormButtons> section. If you haven't worked with CAML before this can be a bit intimidating. It helps to keep the following in mind:

1. It's just XML. Fancy XML, yes, but still XML. All the same rules apply. What's different is the terminology and a whole slew of new tags.

2. All presentation code is encapsulated in <HTML></HTML> tags, and the code itself is encoded within <![CDATA[ blocks. Once the proper tag elements are in place, it's regular old HTML in between.

The first code you will see following the section open tag for <ListFormButtons> is an If...Then expression that checks to see if content approvals are enabled. The <Switch> tag is much the same as an "If" in VB, C# or JavaScript; the <Expr> tag (for 'Expression') is the actual expression and the <Case> tag is the comparison value. The <Default> tag then contains the "Else" value. So, another way of writing this:

<Expr><ListProperty Select="ModeratedList"/></Expr>
<Case Value="0"/>
<Default><HTML><![CDATA[<p>Hellow World!</p>]]></HTML></Default>

Would be this:

function CompareValue()
if ModeratedList.Value = 0
document.write("<p>Hellow World!</p>");

For this example, we'll leave the expression in place but take out the messages pertaining to content approvals; since the button at the top of the page will already contain this information, we don't need to repeat it. Removing the redundant code is easy - just strip out everything between the HTML tags so the resulting code looks like the following:

<Expr><ListProperty Select="ModeratedList"/></Expr>
<Case Value="0"/>

NOTE: Generally, it is a good idea to leave as much of the original code in place as possible. For one thing, you don't know what other elements on the page might be affected (such as nested TD's or DIV's) as it is nearly impossible to read the HTML structure within CAML; for another, a control on the page might rely upon the element to work properly. In this case, the table is stand-alone and there are no ID or NAME tags for a control to reference so the HTML code is obviously discrete and can safely be removed.

Next, we'll copy the toolbar code and append it to the end of the <ListFormBody> section. Cut and paste the following block of code just before the closing </ListFormBody> tag:

<HTML><![CDATA[ <TABLE class="ms-toolbar" style="margin-left: 3px;" cellpadding=2 cellspacing=0 border=0> <TR> <TD class="ms-toolbar"> <table cellpadding=1 cellspacing=0 border=0> <tr> <td class="ms-toolbar" nowrap> <a tabindex=2 ID=diidIOSaveItem class="ms-toolbar" title=]]></HTML><HTML>"Save"</HTML><HTML><![CDATA[ ACCESSKEY=S href="javascript:SubmitForm()" target="_self"> <img src="/_layouts/images/saveitem.gif" ID="tbbuttonstart1" alt=]]></HTML><HTML>"Save"</HTML><HTML><![CDATA[ border=0 width=16 height=16></a></td> <td nowrap> <a tabindex=2 ID=diidIOSaveItem class="ms-toolbar" ACCESSKEY=S href="javascript:SubmitForm()" target="_self"> <LocID ID="L_EditFormSave">]]></HTML> <HTML>Save and Close</HTML> <HTML><![CDATA[</LocID> </a> </td> </tr> </table> </TD> <TD class=ms-separator></td><TD class="ms-toolbar"> <table cellpadding=1 cellspacing=0 border=0> <tr> <td class="ms-toolbar" nowrap> <a tabindex=2 ID=diidIODeleteItem class="ms-toolbar" title=]]></HTML><HTML>"Delete"</HTML><HTML><![CDATA[ ACCESSKEY=X href="javascript:DeleteItem()" target="_self"> <img src="/_layouts/images/delitem.gif" ID="tbbuttonstart1" alt=]]></HTML><HTML>"Delete"</HTML><HTML><![CDATA[ border=0 width=16 height=16></a></td> <td nowrap> <a tabindex=2 ID=diidIODeleteItem class="ms-toolbar" ACCESSKEY=X href="javascript:DeleteItem()" target="_self"> <LocID ID="L_EditFormDelete">]]></HTML><HTML>Delete Item</HTML><HTML><![CDATA[</LocID></a> </td> </tr> </table> </TD> <TD class=ms-separator></td> <TD class="ms-toolbar"><table cellpadding=1 cellspacing=0 border=0><tr><td colspan=2 nowrap><a tabindex="2" class="ms-toolbar" ACCESSKEY=G ID=diidIOGoBack href="]]></HTML><ListProperty Select='DefaultViewUrl'/><HTML><![CDATA[" ONCLICK="javascript:GoBack(']]></HTML><ScriptQuote NotAddingQuote="TRUE"><ListProperty Select="DefaultViewUrl"/></ScriptQuote><HTML><![CDATA[');return false;" target="_self"><LocID ID="L_EditFormGoBack">]]></HTML><HTML>Go Back to Survey</HTML><HTML><![CDATA[</LocID></a></td></tr></table></TD><td width=100%><img src="/_layouts/images/blank.gif" alt=""></td> </TR> </TABLE> <table width=100% border=0 cellpadding=0 cellspacing=2> <tr> <td colspan=5 height=3><img border=0 width=1 height=3 src="/_layouts/images/blank.gif" alt=""></td> </tr> </table><font size=1><br></font>]]></HTML>

Now we need to reformat the toolbar code to get rid of the 'Go back to the survey' link and shorten the length to the size of a normal button. Here is the altered CAML (you might want to cut and paste each block into an editor, restore the formatting and diff the files to see the changes):

<HTML><![CDATA[ <table width="100%" cellpadding="0" cellspacing="0" border="0"> <tr> <td width="100%" height="20"></td> </tr> </table> <TABLE class="ms-toolbar" style="margin-left: 3px;" cellpadding=2 cellspacing=0 border=0 width="115"> <TR> <TD class="ms-toolbar"> <table cellpadding=1 cellspacing=0 border=0> <tr> <td class="ms-toolbar" nowrap> <a tabindex=2 ID=diidIOSaveItem class="ms-toolbar" title=]]></HTML><HTML>"Save"</HTML><HTML><![CDATA[ ACCESSKEY=S href="javascript:SubmitForm()" target="_self"> <img src="/_layouts/images/saveitem.gif" ID="tbbuttonstart1" alt=]]></HTML><HTML>"Save"</HTML><HTML><![CDATA[ border=0 width=16 height=16></a></td> <td nowrap> <a tabindex=2 ID=diidIOSaveItem class="ms-toolbar" ACCESSKEY=S href="javascript:SubmitForm()" target="_self"> <LocID ID="L_EditFormSave">]]></HTML> <HTML>Save and Close</HTML> <HTML><![CDATA[</LocID> </a> </td> </tr> </table> </TD> </TABLE> <table width=100% border=0 cellpadding=0 cellspacing=2> <tr> <td colspan=5 height=3><img border=0 width=1 height=3 src="/_layouts/images/blank.gif" alt=""></td> </tr> </table><font size=1><br></font>]]></HTML>

In essence, all we've done is shorten the original toolbar, removed the separator and second link, and added a second table that acts as a spacer (a DIV or clear gif would work just as well). Once finished with EditForm.aspx move on to NewForm.aspx and repeat the process (don't just cut and paste the modified code from EditForm - the two files work a bit differently).

After modifying these two sections, save the file and copy it to the site definition directory on your front-end web server(s). Remember, when altering any of the XML files, you must perform an IISRESET on each FE to see the changes. After resetting, view any survey to see the results. You should have a survey form that looks similar to the following:

This same technique can be used for any list with forms that extend beyond the fold and you can always modify the HTML code apply a different style to the toolbar (like, for example, the wizard buttons in SharePoint) or add javascript drop-downs, mouseovers, etc.

Wednesday, December 21, 2005

How To or What For?

Shane believes we need more end-user material on the web. Heather thinks thats all fine and well but nobody would use it. Who's right?

For once, I agree with them both (see, I can be agreeable from time to time. It is Christmas after all). One of the most frustrating aspects of new portal deployments is end-user training; there's so much to teach in so little time. Users only retain a fraction of what I show them and, if they don't go use it right away, forget the little that they did learn. I've wanted to put together a bunch of short tutorials for well over a year on SharePoint basics but just can't seem to find the time.

On the other hand, I've also had Heather's experience; what I have created is rarely used by anyone. Users ask for guidance but what most of them really want is for you do it for them. Let's face it; the average job description doesn't include "wrestling with SharePoint" in the 'Duties and Responsibilities' section.

So what to do? Perhaps the best solution is to publish what we've got, expose it to the users, and if they learn from it, fine; if they don't, hey, we tried. If we all contributed a bit here and there to some central blog somewhere we could probably cobble together a good resource in a jiffy. Nobody would have to take on the whole responsibility and we could all syndicate the content via XML to our individual portals (like an FAQ, of sorts, but shared by everyone). Set some categories, let all the SharePointers post to it, and see how it flies. Track it via Google Analytics and see if anyone is using it.

I'm game. I'll even go first with a few short step-by-step tutorials. I can't manage the blog though - too much on my plate right now. Anybody else want to chip in???

Tuesday, December 20, 2005

Enabling the 'Modify Item-Level Security' Link for Document Libraries

A common method for handling list item access in public lists is to restrict users to viewing/editing only those items they have created. Each SharePoint list, with the exception of document and picture libraries, has a 'Modify item-level security' link on the left of the page, accessible from the 'Change permissions for this list' link in the 'Modify Settings and Columns' options. This handy feature prevents users from changing or viewing what other users have posted but allows them to freely edit their own items.

To enable this functionality for document/picture/forms libraries, you must edit the administration pages that control the display of list options. The first page, SHROPT.ASPX, is responsible for showing user permissions and the Actions menu for anonymous access, access requests, and item-level security. When this page is called from a document or picture library, the item-level security link is hidden. To display it, remove the lines 333 and 354:

Line 333 <% if ((m_auim == AclUIMode.LIST)&&(m_list.BaseType != SPBaseType.Issue)) { %>


Line 354 <% } %>

Leave the HTML code between the opening and closing curly braces intact. The item-level security link will now be displayed on SHROPT.ASPX for all document and picture libraries.

Next, you will need to remove several references to the document library type from LSTSETNG.ASPX. This file contains groups of menu items that are used for all types of lists; if...then blocks in the code control which options are shown for each list type. For example, the following block controls the display of the Document Version options for document libraries:

<%if ( iBaseType == SPBaseType.DocumentLibrary && !bIsCustomTemplate){%><!-- Versioning Settings-->
[HTML Code Removed to Conserve Space]

The function works by checking the base type of the current list against the list of base types in the object model; in this case, it checks to see if the current list type (iBaseType) is a document library (SPBaseType.DocumentLibrary). By removing references to SPBaseType.DocumentLibrary in key sections of the file, those elements will then be displayed on the page.

Begin by modifying line 630, changing it from this:

<% if ((iBaseType != SPBaseType.DocumentLibrary) && (iBaseType != SPBaseType.Issue))

To this:

<% if (iBaseType != SPBaseType.Issue)

Next, re-enable the hidden table row on line 646, going from this:

<% switch ( iBaseType ) { case SPBaseType.DocumentLibrary:%> <tr style="display:none;"><%break;default:%><TR><%break;}%>

To this:

<% switch ( iBaseType ) { case SPBaseType.DocumentLibrary:%> <tr><%break;default:%><TR><%break;}%>

Note: If you prefer, you can set the table row display to 'inline' instead of removing the style tag altogether.

Now do the same on line 654, changing it from:

<% if ((iBaseType != SPBaseType.DocumentLibrary) && (iBaseType != SPBaseType.Issue))


<% if (iBaseType != SPBaseType.Issue)

That's it. You can now set item-level permissions for document, picture and form libraries. Test it by uploading a few items, changing the permissions, then logging in as a different (non-admin) user; if you set the view option to 'Only their own', the new user won't be able to see your documents. If you set the Edit option to 'Only their own', all users can see your items but when they try to save any edits they will be prompted for a login and thus prevented from making any changes.

Monday, December 19, 2005

Does SharePoint Play Well With Others?

In the spirit of Bil Simser's advice for SharePoint newbies, here's my half-cent's worth of contributions for those just starting down our treacherous path:

A common mistake that I see organizations make with large portal implementations is succumbing to the mysteriously irresistible urge to add all sorts of custom, third-party and external applications to their SharePoint server farm. Perhaps because they have invested heavily in the hardware to scale their portal and want to wring every last drop of processing power from the cluster, or they have a desire to maintain some sort of URL continuity (like decreeing that all applications from this point forward will start with http://somecompanyportal/ whether they need to or not), or maybe because SharePoint exerts some sort of Bermuda Triangle effect on administrators, attracting web applications like giant moths to the flame, within a month of a new installation I invariably discover all sorts of strange things going bump in the front-end web servers.

While there is no set rule on what should and should not be added to a SharePoint server - and SharePoint certainly has the ability to live side-by-side with all other types of creatures - common sense should dictate that just because you can heap on every web app the company has ever developed into a tangled maze of unmanaged paths, that doesn't mean you should. There are a few important things to consider before you go installing that nifty new copy of Crystal Reports Server on your FE's:

1. ISAPI Filters

SharePoint works a lot of it's magic through a custom ISAPI filter called STSFLTR. This filter handles all incoming and outgoing traffic through IIS, passing requests to the proper DLL's and sending back customized responses (like alternate URL's). In order to redirect traffic around STSFLTR you have to create an unmanaged path; that is, a virtual directory that is not part of SharePoint. When STSFLT intercepts traffic to/from any virtual directory, it first checks to see if that path has been marked as excluded. If so, it stops processing and hands the traffic off; if not, it handles all traffic for that path. As you can imagine, the more unmanaged paths, the harder STSFLTR has to work. Since it already does a tremendous amount of heavy lifting, the more overhead you add vis-a-vis managed paths the greater the impact on performance.

As if unmanaged paths aren't bad enough, many applications install their own ISAPI filters. Depending upon what order these filters are in on any given virtual server, you may experience anything from the odd quirky page to complete portal failure. ISAPI filters exist for the very purpose of managing traffic before it gets to the web application; the more hurdles your traffic has to clear before getting to its destination, the greater the odds that something will get fall down along the way. While third-party ISAPI filters may or may not be a supported configuration, I can assure you that it is not a recommended configuration. I have yet to meet an ISAPI filter that has not wreaked havoc upon the portal at some point or another.

Bottom line - avoid adding any ISAPI filters to a virtual server running SPS or WSS. Capice?

2. Hardware Resources

Ever watched the performance counters on a medium server farm with 3000 users? All those people uploading files, making web-part connections, polling the virtual servers for web part galleries, querying web services for data view web parts, and the umpteen gazillion database calls make the trace grid look like a politician's polygraph in the last week of an election. Now image what might happen when you bolt on that payroll app that uses an ODBC client to suck every last byte out of the Peoplesoft Oracle database. It ain't pretty when all that CPU crunching brings the virtual server to its knees. About the time it starts taking users 20 minutes to add a content editor web part to their personal site is when your Blackberry starts smoking like a Rastafarian bus driver.

Network traffic is another hidden minefield for any portal administrator. Chatty programs are the death of web applications; all those bits running amok makes it hard for the good stuff to get through. Remember that pre-SP2 most SharePoint implementations used host headers, adding overhead to every web request handled by the FE's. Be wary of any applications which make too many TCP-based database calls or custom ports, which are often a good sign that a lot of yapping is about to occur. Http traffic is http traffic, and the more of it the NIC has to handle the less bandwidth available for those big portal pages (have you ever looked at the total size of a standard web part page when all the linked content has been rendered? Here's a hint - OWS.JS alone is nearly 400k. You do the math). Sure, you can put in gig switches and prioritize traffic and add NICs and so on but I've got a better idea: how about you just put that stuff on a different server and save yourself the headaches?

3. Service Levels

It never fails - users start screaming that the portal is down and everyone points their finger at the SharePoint admins. Like that data view web part you just added really caused all the WSS sites to implode like the outer wall during the battle of Helm's Deep. Please. While we may shoot ourselves in the foot from time to time as we try to figure out how to do something simple in the ever-so-elegantly-designed SPS admin pages (That's sarcasm, in case you missed it. Hello, Redmond, are you listening?), eight times out of ten some rinky-dink app has caused the worker processes to lock up and slammed the door on our highly-scalable, disaster-resistant, backed-up and failed-over portal cluster.

Is it really a good idea to jeopardize your hard-earned five nines with that ever-so-important holiday party RSVP app? If you absolutely have to know if Suzy Smartypants is bringing her husband and thirteen children to the summer picnic, is it too much to ask for the developers to write a web part instead of redirecting to a clunky ASP page? Last time I checked, Web Part Library is on the same page as ASP.NET Application under New Project in Visual Studio. Newsflash: SharePoint is an enabling platform; that means you can use it to D-E-V-E-L-O-P W-E-B A-P-P-S. Now quit asking us to add another virtual directory for your latest project "just this once" and learn what Protected Override Void RenderWebPart(HtmlTextWriter output) does. Got it?

Now, go forth and erect thy fortresses of SharePoint, letting no man (or wayward application) penetrate thy defenses...

UPDATE: After reading this post, a colleague pointed out that in some cases the business justification for a high-performance cluster includes the value of running other applications alongside SharePoint (as in "We need this hardware but we can't get it without giving some CPU cycles to the Finance app:). Okay, that's a fair rebuttal - it's harder than ever to get management to sign off on big capital expenditures so you do what you have to do to get what you need.

In such cases the argument shifts from how the SharePoint environment SHOULD be constructed to how much RISK you are willing to assume based on business needs. I don't have any solid metrics to apply here, but it should be fairly easy to come up with a quick value matrix that weights such factors as performance degradation, anticipated downtime, CPU cycles, disk cost, etc. and score each projected application using these factors. This would provide you with a rough idea of the impact each addition would have on your SharePoint environment. You can then respond to other stakeholders with that information and, depending upon the relative importance of each application, let the powers that be determine what level of risk they are willing to assume.

If you follow the guidelines mentioned here when designing your matrix and ensure that new applications do not violate any of these key factors (forcing all other apps to use their own virtual servers and application pools, for example, or adding bandwidth for chatty programs such as web services) you can at least minimize the impact of living with troublesome neighbors.