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:

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

Would be this:

function CompareValue()
{
if ModeratedList.Value = 0
{
return;
}
else
{
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:

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

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))

To:

<% 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.

Tuesday, November 29, 2005

To Brand or Not to Brand...What Was the Question?

While I was pontificating on the need for improved customization in SharePoint v.Next, Andy May suggests that it might be a good idea to find out if your client really wants to tackle the task of branding their portal. A fair question, and one that my clients have answered in multiple ways, from nothing more than an altered site logo to full, file-by-file customization of definitions and templates. As Heather points out, it's not that difficult to brand a portal implementation if you leave the underlying architecture definitions alone.

In my mind, there are three distinct types of scenarios:

1) Stock Portal - No branding or customization required. If you have limited resources or need to focus your resources elsewhere, such as application development or training, this might be the best option. This scenario is often chosen by those with little exposure to Intranet/Portal applications who just need a quick and affordable solution.

Example:




2) Branding - Applying a custom logo, colors, backgrounds, borders, fonts, buttons and other visual elements can be done in a few days from scratch or a few hours if you have a CSS template to work from. Assuming that time and resources are available, branding is an effective way to reinforce a corporate identity and create continuity with other line-of-business applications. Organizations with existing Intranets or portal-like applications often choose this route as it is both affordable and relatively painless. It is also a very good way to learn more about how SharePoint works behind the scenes.

Example:




3) Customization - Let there be no doubt: full portal customization is difficult, time consuming, expensive, and requires a high degree of skill and patience. Only clients who have deployed extensive portal solutions in the past request this level of modification, and rightly so; they have a solid understanding of the cost and risks involved. At best, a complete portal customization can be done in 30 days (I've done it and it was no picnic, believe me); at worst, it could take several months. And that's assuming that you're well-versed in all the mysteries of SharePoint; a neophyte who has to learn on-the-fly may never make it out of SharePoint Customization Hell alive (figuratively speaking, of course, and thanks to Heather for the highly appropriate terminology).

Example:





In the final analysis, it is up to you as the consultant or subject matter expert to explain these options to your client (or decision makers if you are part of the organization), make sure they understand the risk/reward ratio, and provide a realistic estimate of time and cost. One of the most common mistakes I see when consulting with clients regarding customization is the perception that SharePoint is limited in what you can do with the user interface. True, there are certain parameters which influence what the final outcome will be, but the truth is that there are very few things you can do with any web application that you cannot do with SharePoint.

I've discovered that even those who have worked with SharePoint for a good while are unaware of how extensively the product can be customized; not surprising, considering that most people probably give up after fighting with multiple site definitions, scads of similar-but-slightly-different list pages, CAML, never-ending XML files and a confusing maze of CSS. I don't blame 'em - it's enough to give anyone a headache (I keep an emergency supply of Advil on hand at all times).

Personally, I prefer a branded or fully-customized implementation over the stock look-and-feel simply from a business perspective. You wouldn't create an application or web site for a client without taking their corporate identity into consideration; the same applies to a portal implementation - you shouldn't avoid it just because SharePoint makes it more difficult than it really ought to be.

Here are a few tips to ease the pain:

1. Lean which CSS classes need to be modified to achieve your branding objectives and place them into a separate file, then use the custom CSS setting in portal admin to implement your alternate style sheets.

2. When overriding the default CSS classes, create a consistent structure that makes it easier to customize the files in the future (I like to nest related classes and define common elements, such as default fonts and colors, at the top of the file).

3. Once you've established the bare minimum required for branding a portal, use that CSS file as a template so you can apply it to subsequent implementations.

4. Study the Site Definition structures for patterns. Many of the files have similar code elements but for a few variances; learn what they are and where they occur. Once you have created a working definition, copying the files to new defs is mostly search-and-replace (your life will be much simpler with good diff and HTML search/replace tools; I suggest Beyond Compare and Search & Replace 98).

5. Do the same for admin pages in the /layouts/1033 directory. Take note of what works where (some pages are WSS only; others SPS; many are both). Learn which pages alternateheader.aspx works on and what it's limitations are.

6. Make extensive use of user controls (ascx pages). Like the include files of old, user controls give you the capability to modularize your definitions, sharing common code structures throughout the portal. All they require is a simple code registration in the header to tell the page where to find the file and what it's called. This can save you an immense amount of time and frustration. And, best of all, user controls allow you to execute code that may be blocked within certain files (did I mention alternateheader.aspx yet?).

7. Learn which web controls (<SPSWC:> elements) pages require and which can be deleted. Most pages can simply be wiped out and replaced with your own code but some, like txtlstvw.aspx, require fixed page elements in order to render properly (here's a hint: just comment them out, change the ID values, then substitute your own custom elements with the original ID's). Many Admin pages require the PortalHeader element but that doesn't mean you have to display it - just hide that pesky critter and go on about your business.

8. Watch those registrations! Some pages that look the same on the surface use different components to achieve similar results. Check the registrations at the top of the page to make sure that you have the right elements on the page. If you make extensive use of inline code blocks in user controls or admin pages be sure to include Microsoft.SharePoint.WebControls or the code will fail.

9. Look closely at the XML configuration pages. CAML is nothing more than a fancy superset of XML; anyone with a little bit of coding background can quickly determine what the programmer was trying to achieve and duplicate/alter it accordingly.

10. Get the SPS SDK and use it. It doesn't cover everything but there's a lot of good information in there that will help you out when you get into a bind.

11. Don't be afraid to break something! Test your changes extensively in an isolated sandbox that you can wipe out if things to awry. Keep a full copy of the original 60 hive handy to replace any modifications that don't work. Modify in small increments and test after each change, then proceed to more full-scale alterations.

12. Ask for help. There's no better resource at your fingertips than the SharePoint community. Yes, we all have day jobs, but we're happy to lend a hand if you need us. It takes time to maintain a blog or answer newsgroup postings; if we didn't care, we wouldn't do it. If we don't know the answer we can probably point you in the right direction.


Good luck!

Monday, November 28, 2005

List-Level Permissions in SharePoint Portal Server

WARNING! WARNING! WARNING!

This may just be the most unsupported tip I have ever posted, and for someone who is known for finding creative ways to enable or implement features that often fall into the 'unsupported' category, that's saying quite a bit. I struggled with whether or not to post this for a few days but in the end I decided that the value outweighs the cost. You're all big boys and girls so you can make your own decisions on whether or not to implement this solution. If you do, you are completely on your own - although I've tested it in at least three scenarios without incident, that is no guarantee that it won't completely crash your portal implementation. So...

READ ON AT YOUR OWN RISK!!!

Now that the disclaimers are out of the way (this is serious - don't do this if you're uncertain of how it will effect your portal), let's get down to business. One of the most frustrating restrictions in SPS is the inability to assign list-level permissions to individual lists. Unlike WSS, SPS lists inherit permissions from the portal area they live in. This means that you must create a new subarea in order to implement individual list ACL's.

This limitation is enforced by the ExternalSecurityProvider value specified in the ONET.XML file for each site definition. According to the SDK, this COM GUID, which is static for all SPS implementations, defines the security context for the search crawler. Strangely enough, it is also referenced on line 522 of the listedit.aspx file, which obviously has nothing to do with search and indexing. When present, the 'Change Permissions for this List' link is hidden; when removed, it is displayed. By commenting out the associated 'if' statement (lines 522 through 537), the 'Change permissions...' link becomes active for all lists; clicking on it takes the user to the Change Permissions page (shropt.aspx).

Unfortunately, disabling the SecurityProvider check is not enough to enable list-level permissions. As one will quickly discover, clicking on the 'Add Users' button on any subarea (but not the root SPS area) will lead to an endless authentication loop. This confounded me as it seemed to work fine in the SPS definition but nowhere else. When the same issue came up with Jan Tielens' UserAlerts web part and some custom web parts a colleague was developing, I realized they were all related.

The difference is due to a small change to the ExternalSecurityProvider GUID in the ONET.XML file of the SPS site definition and all other definitions (STS, you will note, does not have such a setting). At the bottom of SPS ONET.XML file you will find the following entry:

<Components> <FileDialogPostProcessor ID="C6659361-1625-4746-931C-36014B146679" /> <ExternalSecurityProvider ID="A373E6A7-7A87-11D3-B1C1-00C04F68155C" Type="Microsoft.SharePoint.Portal;Microsoft.SharePoint.Portal.SiteData.
CategoryWebSecurityProvider" /></Components>

Look closely at the ExternalSecurityProvider ID value. For most of the other Site Definitions the first octet ends in an '8' instead of '7' (i.e. A373E6A8 instead of A373E6A7). Looking into the WEBS table of the _SITE database, I discovered that this GUID is stored in the SecurityProvider column for each area (interestingly, bucket areas have a '9' at the end of the first octect instead of a '7' or an '8'). Apparently, whenever the DLL retrieves this value from the DB, if it does not match a certain value range, access to the function being called is not allowed, which results in the authentication loop. At first, I assumed that this only applied to list permissions as listedit.aspx is the only file in which the ExternalSecurityProvider setting is referenced; however, I discovered that several object-model queries (user.alerts and user.roles) also share the same restrictions. Any code that accesses these methods from a portal subarea will fail.

The solution to these problems is simple yet potentially dangerous (if you are still reading now would be a good time to bail out). It involves modifying the listedit.aspx page (not supported), changing the GUID value in the ONET.XML file for your custom site definition (OK as far as I can tell - it's your definition, after all), and, if any areas have already been created, updating the WEBS table with the new GUID value (definitely not supported).
For the bold and brave, here's the procedure:

1. Modify the 'listedit.aspx' file by commenting out (//) lines 522 - 537, as follows:

// if (!spWeb.HasExternalSecurityProvider)// {%> <TR> <TD> </TD> <TD class=ms-propertysheet colspan=4><IMG SRC="/_layouts/images/rect.gif" alt=""> <A ID=onetidListEdit7 target=_self <% if (iBaseType == SPBaseType.DocumentLibrary) { %> HREF="ShrOpt.aspx?obj=<%SPEncode.WriteUrlEncode(Response, spList.ID.ToString("B").ToUpper());%>,doclib" <% } else { %> HREF="ShrOpt.aspx?obj=<%SPEncode.WriteUrlEncode(Response, spList.ID.ToString("B").ToUpper());%>,list" <% } %> ><% switch (iBaseType) { case SPBaseType.DocumentLibrary: if (SPListTemplateType.PictureLibrary == iServerTemplate) { %>Change permissions for this picture library<% } else if (SPListTemplateType.XMLForm == iServerTemplate) { %>Change permissions for this form library<% } else if (SPListTemplateType.ListTemplateCatalog == iServerTemplate SPListTemplateType.WebTemplateCatalog == iServerTemplate SPListTemplateType.WebPartCatalog == iServerTemplate) { %>Change permissions for this gallery<% } else { %>Change permissions for this document library<% } break; case SPBaseType.DiscussionBoard: %>Change permissions for this discussion board<% break; case SPBaseType.Survey: %>Change permissions for this survey<% break; default:%>Change permissions for this list<% break; }%></A></TD></TR><%// }

2. Change the first octet of GUID value in ONET.XML file from 'A373E6A8' to 'A373E6A7', as follows:

<ExternalSecurityProvider ID="A373E6A7-7A87-11D3-B1C1-00C04F68155C" Type="Microsoft.SharePoint.Portal;Microsoft.SharePoint.Portal.SiteData.
CategoryWebSecurityProvider" />

3. Run the following SQL statement on the SITE database in SQL Query Analyzer:

UPDATE Webs SET SecurityProvider = '{A373E6A7-7A87-11D3-B1C1-00C04F68155C}' WHERE SecurityProvider = '{A373E6A8-7A87-11D3-B1C1-00C04F68155C}'

That's it. You now have list-level permissions in portal subareas. As I mentioned, this may have unintended consequences; this value may by used elsewhere and cause something that was working to break or disable some dependent functionality. In my test scenarios I was unable to find any negative effects but that doesn't mean they don't exist. Try it at your own (considerable) risk.

BTW, if anyone from Microsoft reads this and knows what effect a change in the SecurityProvider GUID might have on portal operation, please enlighten us as it is not documented anywhere that I could find.

As an aside, I am certain that this post will officially exclude me from MVP consideration for the rest of my life ;-)

SharePoint v.Next Upgrade Issues

Arpan Shah has a good post on things to consider in preparation for the upgrade to SPS/WSS 3.0. I generally make a concerted effort to toe the line with regards to guidelines from Microsoft but when something is seriously out of whack I have to take exception. Two of the 'Golden Rules' of SharePoint customization that Arpan repeats in his post have me seriously concerned that Microsoft is not taking into account their customer's desire to fully customize SPS in the next release; namely, 1) Database access, and 2) File/Definition modifications. As Arpan states:

1. Don't touch the database. Modifying is a big no-no... reading it -- there's no guarantee that the schema will be the same. If you want to ensure a smooth upgrade, don't touch it or use partner technologies that modify the database.

2. Don't modify out of the box files/directories/stylesheets, et cetera. Whenever possible, you should be creating copies and then modifying vs. modifying the out of the box files. Service Packs or future versions can overwrite files and you can lose your customizations. Net/net: follow customization guidelines!

This is all well and good but both of these 'rules' completely ignore the realities of full-scale customization. First, I defy you to create a portal-wide, collapsible, dynamic navigation menu (a la MSDN menus) or a nested drop-down navigation bar that includes both SPS areas and WSS sites without reading from the database. Sure, you can cobble together an object-model server control but it will be a) V-E-R-Y S-L-O-W and b) very buggy. Not to mention the fact that you can't make the association between WSS sites and SPS areas without reading from the WEBS and SITES tables. I completely support the notion that modifying the database is BAD IDEA but not being allowed to read from it overly restricts development and customization.

Second, you absolutely cannot deploy a fully branded and customized portal solution without modifying the SPS, SPSPERS, SPSMSITE, and SPSSITES site definitions. Add WSS sites and you must also customize the STS definition (although you can work around that limitation by getting fancy with the WEBTEMP.XML files). What good are stand-alone area customizations if they don't integrate with the rest of the portal? We're talking about customizations far beyond portalheader.aspx and alternateheader.aspx - full page layout and design changes cannot be achieved without editing the core definitions. (NOTE: You can modify WEBTEMPSPS.XML and replace the default definitions with custom definitions using the same ID but it doesn't always work right, especially when replacing the SPS definition, and besides, according to the guidelines, we're not supposed to modify the base XML files either).

Third, administration pages are a unique animal unto themselves. At last count I have discovered at least half a dozen shared code structures just for admin page headers (sorry to disappoint, but alternateheader.aspx only works in about a third of those pages, and it's functionality is limited at best) and a bunch of stand-alone configurations. Many don't include references to custom CSS files or all the code registrations necessary for including server controls (you do want a common navigation scheme at the top of ALL your pages, don't you?). Remember that many of these pages are accessed regularly by portal users - spsviewlsts, lstman, aclinv, catman, create, listedit, and so on - so modifying them is required if you wish to maintain any sort of continuity in portal design.

I understand that customization in the current product is tricky at best but v.Next is supposed to address that issue, is it not? I thought so but Arpan's post makes me wonder if that will be the case. Word to the mother ship - CUSTOMERS DEMAND CUSTOMIZATION!!! Neglect the ability to customize the product and customer satisfaction will plummet. I sincerely hope this requirement has been taken into consideration and the final release will take full advantage of .NET 2.0 features to provide scalable, extensible customization options without restricting the customer to a less-than-desireable mish-mash of 'works here but doesn't work there' compromises.

Don't get me wrong - SharePoint is a great product and I appreciate the customization flexibility that Microsoft has given us (even if some of it is inadvertent). I also appreciate the wisdom from on high provided by Arpan, Fitz, Maurice, and others. I just hope that the lessons of v2 influence the features of v3 inasmuch as customization is no longer an option but a requirement.

For those of you that have already done extensive customization to your portal deployment, here are some practical suggestions to prepare for the v.Next upgrade:

1. Insure that you have copies of all the original, unaltered files, definitions and directory structures.
2. Make a copy of all modified files, definitions and directories.
3. Make a copy of all database additions, such as stored procedures, logins and custom indexes.
4. Prior to upgrading, restore the original files/defs/dirs and remove any custom CSS files, images, etc. from the 60 hive.
5. Remove all database additions.
6. Perform the upgrade to a test portal.
7. Determine what impact the new changes have on your customization scheme and modify/redeploy accordingly (many of the hard-coded mods my no longer be necessary as Master Pages will replace much of the file-by-file alterations necessary in the current version).
8. Revise your database additions to account for any schema changes and redeploy.
9. Test and modify your web parts and server controls (especially those that read from the DB).
10. Test, test, and test again, then downgrade the live portal deployment to a basic install, upgrade to the new version, and deploy your customized portal elements.

Bear in mind that these are just suggestions - without having the new version to test against there is no way of knowing what the actual procedure will be. The more extensive the customizations the sooner you should begin testing and planning the upgrade to v.Next for your clients/organization.

Wednesday, November 23, 2005

Extreme SharePoint Design: Custom User Menus, Part 2

In a previous post, I described at length the process for creating custom user menus in SharePoint. In the associated code samples, I neglected to account for windowed controls that might obscure the menu drop-downs on a page. As anyone who has designed extensively for IE knows, windowed controls (such as ActiveX or Select boxes) ignore the 'z-index' property assigned to DOM elements and take precedence in the display; that is, they are drawn on top of all other controls. If you use the hidden DIV method I outlined there are some instances, such as opening a document library in datasheet view, where the menu options disappear behind the web part.

There is no direct solution for this problem but there is a workaround using IFRAMEs and javascript. First, let's re-familiarize ourselves with the code that draws that the hidden div and the script that activates it when the user clicks the menu button:
Page Code (in DEFAULT.ASPX or a custom user control):

<td valign="top" align="right" nowrap onclick="SwitchMenu('ActionsMenu')" class="spsWPZ_ActionsMenu_Header">Actions<img src="/_layouts/images/menudark.gif" align="absbottom" > <div id="ActionsMenu" onmouseover="this.style.display='inline'" onmouseout="this.style.display='none'" class="spsWPZ_PageHeaderMenu_Div" > <table width="100%" cellpadding="0" cellspacing="0" border="0" class="spsWPZ_PageHeaderMenu"> <!-- Toolbar Elements Here --> </table> </div></td>

Javascript (in the page header or OWS.JS):

function SwitchMenu(obj){var el = document.getElementById(obj);var parent = el.parentElement;var x = getPageOffsetLeft(parent);var y = getPageOffsetTop(parent) + parent.offsetHeight;var p = parent.offsetWidth; if(el.style.display == "inline") { el.style.left = (x - (175 - p)) + "px"; el.style.top = (y - 7) + "px"; el.style.display = "none"; } else { el.style.left = (x - (175 - p)) + "px"; el.style.top = (y - 7) + "px"; el.style.display = "inline"; }}

The code above displays a table cell ('Actions') that acts as a menu button and creates a DIV element ('ActionsMenu') that is hidden when the page loads. When the user clicks within the table cell the SwitchMenu function is called, which activates the ActionsMenu DIV, positions it below the cell and displays it on the page. The onMouseOver and onMouseOut events cause the DIV to disappear when the user moves the mouse outside the DIV boundary, mimicking the behavior of a regular Windows menu.

This works just fine until a windowed control is placed on the page that intersects with the ActionsMenu DIV, at which time the control will obscure the DIV, making it impossible to access the menu functions. In some cases, the page becomes unusable because all the operations ('Modify Settings and Columns', 'Edit Page', etc.) are hidden. To resolve this issue an IFRAME must be placed immediately below the ActionsMenu DIV that is the same width and height and exposed in response to the user click on the table cell.

First, create a hidden IFRAME within the ActionsMenu DIV and place it immediately before the content table as follows:

<div id="ActionsMenu" onmouseover="this.style.display='inline'" onmouseout="this.style.display='none'" class="spsWPZ_PageHeaderMenu_Div" > <IFRAME style="display: none;" id="ActionsMenuFrame" name="ActionsMenuFrame" src="javascript:false;" frameBorder="0" scrolling="no" ></IFRAME> <table width="100%" cellpadding="0" cellspacing="0" border="0" class="spsWPZ_PageHeaderMenu"> <!-- Toolbar Elements Here --> </table></div>

The style of the IFRAME is set to "none" to keep it hidden until the DIV is exposed. The NAME and ID settings are important as these will be used in the SwitchMenu function later. The SRC property calls a dummy javascript instead of referencing a blank HTML file, which can lead to client-side security warnings in an HTTPS environment.

Next, update the SwitchMenu function with methods to display and position the IFRAME:

function SwitchMenu(obj){var el = document.getElementById(obj);var parent = el.parentElement;var x = getPageOffsetLeft(parent);var y = getPageOffsetTop(parent) + parent.offsetHeight;var p = parent.offsetWidth;var aFrame = document.getElementById('ActionsMenuFrame'); if(el.style.display == "inline") { el.style.left = (x - (175 - p)) + "px"; el.style.top = (y - 1) + "px"; el.style.display = "none"; } else { el.style.left = (x - (175 - p)) + "px"; el.style.top = (y - 1) + "px"; el.style.display = "inline"; aFrame.style.zIndex = el.style.zIndex - 1; aFrame.style.display = "inline"; aFrame.style.position = "absolute"; aFrame.style.top = "0px"; aFrame.style.left = "0px"; aFrame.style.height = el.offsetHeight; aFrame.style.width = "175px"; }}

The function now uses a variable called 'aFrame' to access the properties of the IFRAME('ActionsMenuFrame'). The IFRAME is then positioned one level below the DIV by setting the zIndex property. The display type is then changed to 'inline' and the IFRAME is set to the top and left position of the parent DIV. The most difficult part of positioning the IFRAME is matching the height of the parent object; because the ActionsMenu DIV does not have an explicit height setting (it expands or contracts based on the content in the child table), the el.style.Height value cannot be used; instead, the function uses the offsetHeight property, which is accessible once the control is drawn (which happens in the preceding IF...ELSE blocks).

The menu will now overlay all page controls, as IFRAME is a unique IE element that is aware of the zIndex values of both browser elements and windowed controls (the zIndex for the ActionsMenu DIV is set in a CSS file). The page will now render with the windowed controls on the bottom, then the IFRAME, then the DIV on top. This will prevent any ActiveX controls, select boxes, or other page elements from obscuring the menus and limiting usability.

Friday, November 11, 2005

Cracking Open STP Files

Todd Bleeker has a great post on manipulating data within STP files. Timely information, as I was just working on a similar problem the other day. Thanks, Todd!

Thursday, November 10, 2005

Changing the SharePoint Admin Password

Before installing SharePoint it is important to give some thought to the account that will be used as the portal administrator. Not only is the admin account referenced throughout the portal (area/site ownership, document metadata, list properties, etc.) but it's also used for many basic functions that are central to portal operation - connections to the configuration database, content crawling, application pool identity, and so on. Because the portal administrator must have complete and total control over the SharePoint server(s), a good practice would be to use an account specifically for this purpose that has limited rights elsewhere in the domain.

Assuming that you have followed this methodology in your portal deployment, the next obvious question is: What happens when you change the portal administrator password? Alas, this is a simple question with a not-so-simple answer. There are several steps required to successfully implement this change and they must be done in a specific order. Download the following article which outlines the scope and implications of modifying administrative account properties and defines a procedure for implementing changes.

NOTE: This procedure has been tested for accuracy but each deployment is different; if you run across any variations please post comments and I will update the document accordingly.

Modifying Administrative Account Properties (PDF)

Monday, November 07, 2005

Extreme SharePoint Design: Validating Area Titles

Most web-savvy tech types know better than to use "special" characters in URL fields - apostrophes, question marks, carets, ampersands, and the like. Unfortunately, this rule of thumb is lost on the average SharePoint user and an open text box with no validation is an invitation to wreak havoc on your carefully constructed portal navigation. IE will overlook most illegal characters but some third-party or custom developed navigational controls will not, so a good practice would be to discourage the use of symbols in Title and URL fields.

You can, of course, add big warnings to the admin pages warning against such malfeasance but we all know that the bigger the warning, the more often it is overlooked. So what to do? The best way to handle this situation is to check the user input against a list of disallowed characters and force text modifications before the field is posted. Fortunately, if your portal consists mostly of WSS sites, you've got it made - the WSS site creation form (scsignup.aspx) has built-in validation to prevent users from entering illegal characters in certain fields; however, if you make extensive use of SPS areas, the solution is not quite so simple. Here's how to replicate the WSS behavior in SPS:

First, create a script to check the user's input against your list of illegal characters, similar to the following:

<script>
function validateSiteName(ctlId)
{
var iChars = "!@#$%^&*()+=[]';,./{}\":<>?";
var ctlName = document.getElementById(ctlId) for (var i = 0; i < ctlName.value.length; i++)
{
if (iChars.indexOf(ctlName.value.charAt(i)) != -1)
{
alert ("The Title field contains special characters which are not allowed. Please remove them and try again.");
ctlName.focus();
return false;
}
}
}
</script>

This script defines the list of illegal characters (iChars), gets the ID of the input box (ctlName), then, if the text box isn't empty, steps through each character and matches it against the list. If a character matches one of the illegal characters, the script displays an alert box asking the user to remove the offending characters and returns the focus to the input field, preventing them from saving the form without fixing the problem.

Second, paste the script into the <HEAD> section of the area creation and modification pages (spnewcategory.aspx and speditcategory.aspx, respectively) or append the function to OWS.JS file (which is called from every page within the portal).

Third, call the script using the onBlur method from the form. Find the following code block on the page:

<SPSWC:InputFormTextBox
runat="server"
id="TextBoxTitle"
IsTextTrimmed = "true"
LabelTextLocId="SiteAdminCreateCategory_Title_Colon_Text" AccessKeyLocId="SiteAdminCreateCategory_Title_AccessKey"/>

Then add the onBlur event to the end:

<SPSWC:InputFormTextBox
runat="server"
id="TextBoxTitle"
IsTextTrimmed ="true"
LabelTextLocId="SiteAdminCreateCategory_Title_Colon_Text" AccessKeyLocId="SiteAdminCreateCategory_Title_AccessKey"
onBlur="validateSiteName(this.id);"/>

This will fire the validateSiteName function whenever the user clicks or tabs out of the input field.

Finally, save the modified pages to the 60\TEMPLATE\LAYOUTS\1033 directory and users will be unable to enter any illegal characters into the area title field, which is used by SPS to construct the bucketed URL, thereby preventing any incompatabilities with navigational tools and utilities.

Tuesday, November 01, 2005

MySite Permissions

The MySite site definition (SPSMSITE) is intended to provide only the 'Private' and 'Public' page views for a user's personal site; all other functionality (lists, doc libs, etc.) is handled by the Personal definition (SPSPERS). When customizing these definitions (if you want to provide MySite functionality, you have no choice but to customize the existing definition - I know it's not supported but it's there just isn't any other way), it is important to remember that SPSMSITE is a full definition, with the same base set of included lists as all other defs.

While there are no direct links in the GUI for working with MySite lists, users can add list web parts from the tool pane and, by default, modify the list contents. Since MySite content is global (personal site content is localized to the individual user account), any changes to MySite lists will show up for all users; not an ideal situation.

To work around this issue, change the MySite permissions for Members and Contributors. Use the following URL syntax to access the MySite security page (there is no 'Manage Security' link for this area):

http://%lt;server name%gt;/mysite/_layouts/1033/spcatsec.aspx

Check the Contributor and Member groups (and any others you wish to modify), click 'Edit', and uncheck the 'Add Items' right under the Select Rights section. This will prevent users from doing and end-run around list security by dropping list web parts onto the MySite pages and modifying the content.

Tuesday, October 18, 2005

New Customer Success Story

From time to time, when we're engaged on a project that is a bit out of the ordinary or involves a great deal of customization, I like to do a case study to highlight the challenges, achievements and lessons learned during the course of the engagement. We recently completed such a challenging assignment for a premier architectural services firm based in Ft. Worth, Texas. Their story contains valuable insights for companies of similar size who are considering a new portal implementation using SharePoint Products and Technologies.

If you, your firm, your clients, partners or other SharePointers are interested in reading the case study, it is available for download here.

If you have any questions or would like more details on the technical architecture/development/etc., feel free to post a comment with your email addy and I'll respond accordingly.

Monday, October 10, 2005

Extreme SharePoint Design: Custom MySite Titles

When customizing personal sites (MySite), it is important to remember that you're really working with two site definitions - SPSMSITE (which provides the Public and Private default pages) and SPSPERS (which contains all the lists and libraries). SPSMSITE is nothing more than a launching pad for SPSPERS - the bulk of the functionality is in the personal site definition.

This is important to remember when modifying the menus, links and other navigational elements. In some instances, such as the public and private pages, you will be referring to MySite url's (/mysite/); in others, such as document libraries and lists, you will be referring to Personal url's (/personal/[username]/). This can be quite confusing when, for example, you need to create a site title that is displayed on every page and links back to either the public or private view.

The SharePoint object model has some built-in functions that help you determine your site context but they don't span multiple contexts (in other words, there's no way to know which mysite is associated with the current personal path a page is in) and they don't always expose data the way you need it. In the following example, we'll use various properties of the GetContextWeb method to create a personal site title based on the user's full name from Active Directory that, when viewed by the site owner, links back to the private home page, and when viewed by a reader, links back to the public page.

//Retrieve the site title, which should be the user's full name from AD
string strFullName = SPControl.GetContextWeb(Context).Title;
string strTitle;
int intLocation, intLength;

// Determine length of string, find comma separator if one exists
intLength = strFullName.Length;
intLocation = strFullName.IndexOf(", ");

// If a comma is not found (the user name is not separated into Last Name, First Name), set the Title value to the full Title from AD; otherwise, parse out the first name and last name, remove the comma and space, and reverse the strings to read First Name Last Name

if (intLocation == -1)
strTitle = strFullName;
else
strTitle = strFullName.Substring(intLocation + 1) + " " + strFullName.Substring(0,intLocation );

// Determine if the current user is the site author; if so, set the link href value to mysite and write out the Title. If the current user is only a reader, set the link href to the public url and write out the same Title value.

if (SPControl.GetContextWeb(Context).CurrentUser.LoginName == SPControl.GetContextWeb(Context).Author.LoginName)
{
Response.Write("<a href='/mysite/default.aspx'>" + strTitle + "</a>");
}
else
{
Response.Write("<a href='/mysite/public.aspx?accountname=" +
SPControl.GetContextWeb(Context).Author.LoginName + "'>" + strTitle + "</a>");
}

Note that you will only be able to execute this code from a user control; standard web part pages won't allow code execution. To call a user control from within a web part page, create an .ascx file, insert the required registrations (be sure to utilize Microsoft.SharePoint.WebControls) and code, then register the page location (usually the /bin folder on the virtual server), and call the registration from within the web part page.

Friday, September 30, 2005

Search Index Rules Syntax

SharePoint does a pretty good job of handling long, complex or unique URLs within the interface; many lists use relative pathing, Area and Site URLs preserve spaces for easy readability, and so on. One instance where this is definitely NOT the case is the application of rules for inclusion and exclusion of content sources.

When creating a rule that references a URL path, such as http://servername/Area, you must replace any spaces and special characters (ampersands, apostrophes, commas, etc.) with the proper URL encoding (%20, %26, %2C, etc.). Otherwise, the MSSearch process will ignore the rule and continue evaluating results against valid rules.

Also, remember that the order of the rules is important. If you are trying to exclude the above URL, the Portal_Content index will have a default inclusion rule of http://servername/; any new rules you create will be processed after this rule is satisfied. Since the default rule includes all content, any antecedent rules will be ignored. To address this issue, simply move your exclusions to precede the blanket inclusion and the search service will properly exclude the specified content source.

Thursday, September 29, 2005

Extreme SharePoint Design: 'Go Back To [Area/Site]' Link

The ability to call pages in the /_layouts/1033/ directory is very convenient; all the administration pages can be stored in one location and referenced from any area or site within the portal. SharePoint uses the SPControl.GetContextWeb(Context) and SPControl.GetContextSite(Context) functions to determine what context the user is in when a page is called, then presents the appropriate content. Pretty slick.

There's just one problem: when accessing these pages, users often get lost with no way to return to where they came from. Nothing is more frustrating than finding yourself seven steps into a wizard with no way to bail out but to smack the 'Back' button repeatedly. The solution is a 'Go Back To [Area/Site]' link at the top of each administration page which gives the user one-click access to the place they started from.

Insert the following code into AlternateHeader.aspx or directly into your administration pages just after the PageHeader section (if you're using heavily customized Site Definitions as I often do, most of your admin pages will have header code in them to overwrite AlternateHeader and PortalHeader; just paste the following code into the appropriate section and format it as needed):

<%
try
{
Response.Write ("<a href='" + SPControl.GetContextWeb(Context).Url + "'>Go Back to " + SPControl.GetContextWeb(Context).Title + "</a>");
}
catch
{Response.Write ("Portal Administration Page");
}
%>

The 'try' block creates a link back to the root of the referring area/site using the title field as the link text. The 'catch' block writes out a general statement that you can replace with anything you like, just in case the link fails to render properly.

NOTE: In 'Extreme SharePoint Design: Creating Custom User Menus', I used the same function to replicate the behavior of the 'Home' button on WSS sites. Use SPControl.GetContextWeb(Context) anytime you want to refer to the root URL of an area or site; use SPControl.GetContextSite(Context) to reference the site collection the current site belongs to.

UPDATE: I forgot to mention that you'll need to insert the following code at the top of alternateheader.aspx in order for SharePoint to parse inline code blocks on the page (otherwise you will receive the dreaded 'External component has thrown an exception' error):

<%@ Page language="C#" %>
<%@ Register Tagprefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=11.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>

Updated SharePoint Resources List

Heather has updated her SharePoint Resources List with a bunch of new content. This should be your first stop for tips, tricks, and general How-To's.

If you're looking for a list of tools and utilities, including commercial and non-commercial software, web parts, add-ons, and the like, I maintain a brief list here and Joris Poelmans has a monster list here.

BTW, whatever happened to the Sharepoint Template Project???

Wednesday, September 28, 2005

New Blog Template

Shane was kind enough to let me know that the blog template I was using didn't render properly in Firefox, so I switched it up to something new. Not very compelling, I know, but I don't have time at the moment to whip up a better one (the stock Blogger templates just don't give you much to choose from).

Drop a comment and let me know if this one works any better...

Tuesday, September 27, 2005

Extreme SharePoint Design - Creating Custom User Menus

Finally, after much delay, the lastest installment in the Extreme SharePoint Design series is finished. In this installment we will explore the creation of custom menus using the default Actions and Views menus in SharePoint Portal Server 2003.

Originally intended to be a much shorter article, I kept finding additional things I felt needed to be included to present a complete picture, so the final version is rather lengthy. Hence the linked PDF as opposed to one big blog post. Download the article here (right-click, choose 'Save Target As'):

Extreme SharePoint Design - Creating Custom User Menus (PDF, 464k)

Enjoy!

Wednesday, September 14, 2005

New SharePoint Features

I'll take Heather's cue and refrain from judging the new SharePoint features until I've had a chance to get my hands on them, but here are a few thoughts based on Fitz's post from PDC:

  • RSS. Everything about sites, lists, libraries, etc., can be syndicated via RSS automatically.
    Blogs and Wikis. Templates and features in the box.

Excellent. This will save a great deal of frustration with email-based alerts.

  • Content Types. These aren’t just like SPS 2001’s document profiles. They define sets of metadata, but they also contain view information. And associated workflows. And events bound to them (synchronous or asynchronous). And you can have more than one in the same list/library.

Hmm...not sure. Will this help with the problem of retrieving views via web services?

  • Workflow. Windows Workflow Foundation is embedded in WSS. It’s used everywhere.

I want to jump up and shout for joy...BUT...if they don't include a visual designer embedded into the UI ala Nintex SmartLibrary then they will have completely missed the boat. LOB managers, content editors, and designers cannot AND WILL NOT be forced to rely upon developers to create workflows.

  • Recycle Bin. We did it. It’s scoped to a site and captures deleted documents, items, etc. It has a user restore and an administrative restore.

Now that deserves a jump and a shout (assuming, of course, that it applies to SPS as well).

  • Per-item security. Even on list items.

I can die happy now. Is it really true? Oh, please, let it be so!

  • FrontPage has evolved into a feature set that makes it truly a SharePoint site designer. (Ghosting’s still around, but it won’t be a problem anymore. I’ll explain why in an upcoming post.

Interesting. This could make the job of customization a bit easier, which would make my life a much better place.

  • Forms services in Office “12” servers. That’s right — design a form in the InfoPath rich client, publish it as a SharePoint site, and it can be either viewed/filled out in the full smart client or in a browser as HTML.

One word: S-W-E-E-T!

  • Search. Better APIs. Better results. Alternate search suggestions (misspelled words,etc.). A highly customizable default Web-based UI.

I'm definitely excited about this. Search is a bit of a blessing and a curse, so any improvement will be welcome.

  • Office “12” servers will also contain a Business Data Catalog, a facility that registers LOB application data and Web services. Once that’s happened, BDC-aware Web Parts can pull data from them, we can index them, and a lot more. There’s a session on this tomorrow.

Not sure about this one. Sounds interesting, though.

  • Office “12” servers will also be able to take a spreadsheet published to a SharePoint site and reneer it as an HTML application.

Like it but not sure how useful that will be...

  • Mobile views of SharePoint lists. That’s right, a way to render a list on a mobile device.

Very nice.

  • Lists now have a Business Data type that will use the aforemntioned Business Data Catalog

Unsure on this one. Need more info.

  • Access will be able to treat SharePoint site data as fulll-blown data sources.

Yeah, baby!

All in all, it sounds promising. So, looking back, how'd they do with regards to my various predictions, rants and ravings? Not bad, I'd say.

Tuesday, September 06, 2005

Extreme SharePoint Design: Modifying The 'Grouped By' Headers

When applying grouping to a list view, the default style sheet prepends the field label and a colon to the group header. While this may be helpful in determing what category the data is grouped by it is certainly not the most visually appealing method. Fortunately, the style can be modified in the stdview.xml file located in the XML folder of your custom site definition (you are using a custom defintion, aren't you?). Locate the following code on or about line 278:

<GroupByHeader> <HTML><![CDATA[ <TBODY id="titl]]></HTML> <GetVar Name="GroupByLevelString"/> <HTML><![CDATA["><TR]]></HTML> <Switch> <Expr><GetVar Name="GroupByIndent"/></Expr> <Case Value="0"> <HTML> <![CDATA[ class="ms-gb" ]]> </HTML> </Case> <Default> <HTML> <![CDATA[ class="ms-gb2" ]]> </HTML> </Default> </Switch> <HTML><![CDATA[><TD colspan="100" nowrap><img src="/_layouts/images/blank.gif" alt="" height=1 width=]]></HTML> <GetVar Name="GroupByIndent"/> <HTML><![CDATA[><a href="javascript:" onclick="javascript:ExpCollGroup(']]></HTML> <GetVar Name="GroupByLevelString"/> <HTML><![CDATA[','img_]]></HTML> <GetVar Name="GroupByLevelString"/> <HTML><![CDATA[');return false;"><img id="img_]]></HTML> <GetVar Name="GroupByLevelString"/> <HTML><![CDATA[" src="/_layouts/images/minus.gif" alt="]]></HTML> <HTML>Expand/Collapse</HTML> <HTML><![CDATA[" border="0"></a> ]]></HTML> <GetVar Name="GroupByField" HTMLEncode="TRUE" /> <HTML><![CDATA[ : ]]></HTML> <GetVar Name="GroupByValue"/> <HTML><![CDATA[</TD></TR></TBODY>]]></HTML> </GroupByHeader>

Remove the field label and colon by deleting or commenting out the following code from this section:

<GetVar Name="GroupByField" HTMLEncode="TRUE" /> <HTML><![CDATA[ : ]]></HTML>

You may also wish to change the drab gray style by modifying ms-gb and ms-gb2 or assigning a class in a custom style sheet.

Once you're finished editing the file, save it and reset IIS. Note that this is a global change that will affect all lists in the definition. For localized changes, create a Data View Web Part and modify the XSL accordingly.

Compiling Locally Without SharePoint Installed

A vexing issue for SharePoint developers is the need to have WSS installed on your development machine due to DLL dependencies. Anyone who has tried to compile a web part in a stand-alone environment has run across the following errors:

The dependency 'Microsoft.SharePoint.Security' could not be found.
The dependency 'Microsoft.SharePoint.Dsp' could not be found.
The dependency 'Microsoft.SharePoint.Library' could not be found.

To circumvent this issue, extract the underlying DLL's from the GAC and copy them to your local machine. You'll need to perform the copy from the command prompt as Windows Explorer does not expose the full directory structure for the GAC. Each DLL resides in a directory with a path similar to the following:

c:/windows/assembly/gac/[Strong Name]/[Version Number]__[Public Key]/[Strong Name].dll

Once you have the DLL's you can include them as a reference in your project and compile without errors.

Tuesday, August 30, 2005

SharePoint Blogsearch Expands to Mexico

For our friends from Latin America, Luis Du Solier's Spanish-language blog has just been added to the SharePoint Blogsearch index. As I grew up in El Paso (which is right on the Mexican-American border), I am quite fond of our neighbors to the south and glad to see Luis is making a contribution to the SharePoint community. If I continue to get multilingual submissions I'll create a content source for each language to make native searches a bit easier.

Para todos nuestros amigos latinos, bienvenido por favor Luis Du Solier a la familia de SharePoint Blogsearch. Como soy de EL Paso, Tejas y orgullosa de nuestros vecinos al sur. Estoy alegre ver que Luis está haciendo una contribución a la comunidad de SharePoint. Su blog es muy bueno, Luis!

Thursday, August 25, 2005

Database Permissions

I'd been wondering for a long time how the ACL's in the SharePoint database work. In fact, I was just discussing the issue this morning with a colleague when - voila! - Paolo posts the answer.

Now that's what the blogosphere is all about!

Tuesday, August 23, 2005

Working with SharePoint Lists

A colleague of mine is working on a custom application which makes extensive use of SharePoint lists to store data. He has written a webpart that rolls-up the XML data from various lists, transforms it with XSL, and renders it in a way-cool tabbed control. While putting this together, he found this link on Using Data from Sharepoint 2003 Lists. Very cool and a big thanks to Paul Ballard for his post.

Perhaps if we needle him enough, Scot will be nice enough to post his nifty code and show us how it's done. Or, he could just get a blog and start sharing his wizardry with the rest of the world, eh???

Hiding the Site Settings Link

One of the most common user complaints in new implementations is links that are exposed to users who don't have rights to access the content - like the 'Site Settings' link at the top of every page. Heather Solomon has figured out how to hide that pesky critter for good.

Where does she find this stuff? More importantly, how on earth would I make it through the week without her blog???

Heather for MVP!!!

Sunday, August 21, 2005

SharePoint Templates Article

While following up on a comment from Claudia I found her article on WSS themes. Good stuff - go check it out.

Friday, August 19, 2005

Extreme SharePoint Design Series

Site definitions and custom templates are essential to delivering a customized SharePoint experience. While there are several good resources for information on how to deploy a custom site definition, such as the SDK, MSDN, and Heather Solomon's excellent site, there aren't many in-depth examples of custom code for UI modifications. It's time to change that, don't you think?

Over the next few months I'll be presenting "Extreme SharePoint Design", a regular blog series with advanced tips and tricks for designers. Topics won't be in any particular order, just cool stuff as I come across it with lots of code to copy and paste. Watch this space for updates, often daily but sometimes weekly, depending upon my travel schedule.

As always, feel free to share your thoughts or suggest ideas.

UPDATE: I'll be posting a lengthy article next week on customizing various context menus (Actions, Views, etc.) - it's a bigger task than I thought at first but well worth the effort. Stay tuned!

Tuesday, August 16, 2005

Extreme SharePoint Design: Site Definition File Differences

Creating a custom site definition from scratch can be a daunting task. Each file in the folder structure serves a particular purpose and many of them vary slightly from folder to folder - allitems.aspx is not the same in DOCLIB as it is in VOTING (see Heather's post for a complete list of files in each folder). Due to slight variances, there is no way to do a global search and replace to copy your customizations. Getting the correct code in each file is essential to maintaining SharePoint's functionality. Isn't there an easy way to solve this problem?

The answer is - sort of. First, begin with a blank template file that has all the necessary components for that type (allitems.aspx, for example). Remember that many page elements require specific registrations and script files, so make sure your [HEAD] tags include all the necessary code blocks. Once you have a working template file, copy it into each directory, then add the code necessary for each list type.

I have compiled a list of each file type along with the code elements that are unique to each list (folder) below, using the SPS site definition as a baseline (60\templates\1033\SPS). The code goes in various places but I tried to keep it sequential; that is, the first code block (individual blocks are separated by ***) goes first, then the second, and so on. Some are contextual (they contain more than just the unique code) to make searching and replacing easier. I also tried tgo keep all script elements together to insure that no code gets orphaned. Once you've modified a couple of files you'll know exactly where to place each block.

Site Definition Original Code (SPS)

Please note that I did not include files that are specific to a certain list type (such as calendar.aspx in EVENTS) only those that recur in multiple folders. In a subsequent post, we'll look at how these files differ from those in STS to show how a custom deployment can seamlessly incorporate both SPS and WSS in a unified design.

NOTE ON CODE SAMPLES: Few things frustrate me more than not being able to cut and paste HTML code into Blogger's editor. You would think they could solve this with a special tag like many discussion forums do, but if they have I sure don't know about it. Until such a thing comes along, all <> tags will be displayed as [ and ]. Sorry for the inconvenience.

Friday, August 05, 2005

Optimizing External Site Crawls

After launching SharePoint Blogsearch, I discovered (with some help from Greg) that the SharePointPSSearch (SPSSearch) service needs a few tweaks to work well on external web sites. After doing some digging around in the documentation, looking into the packets with a protocol analyzer, and generally scratching my head in confusion, I learned a few things:

1. SPSSearch does not always honor robots.txt files (this is a text file placed in the root directory of a web site that tells crawlers how to behave). Yes, the documentation says it does, and you can modify the id string in the registry, but it doesn't always seem to work. I'm still trying to come up with an answer to this one.

2. By default, the crawler will request as many documents from the target site as it can fit into the available threads or until it starts receiving TCP errors; in other words, it will hammer an external site into submission. Fortunately, you can control this errant behavior. Go to SharePoint Central Site Administration Manage Search Settings. In the 'Site Hit Frequency Rules' section, click on 'Manage Site Hit Frequency Rules'. Click 'Add Update Rule' on the toolbar. In the Site Name field, enter "*" for all web sites (you can also set rules by explicit name, domain, etc.). Next, click the 'Limit number of documents requested simultaneously' radio button and enter a small number (minimum is 1, max is 999, I used 5) in the Number of Documents field. This will significantly reduce the load on target servers.

3. Incremental updates are SUPPOSED to ignore content that has not been changed, crawling only those docs that have changed. In reality, this is not the case. I noticed that all documents were being processed even though the content was static. I tested a full update and incremental update on a static site and the exact same load was generated (packet count, bytes, etc.) in both a full and incremental update. This could be a bug or it could be some hidden registry setting somewhere that I haven't found yet. Any ideas would be appreciated.

4. Adaptive updates only appear to work on SPS/WSS sites. According to the docs, adaptive updates make an educated guess, based on historical patterns, as to what content may have changed on a site. In theory, this should greatly reduce the load on a crawled site and it may very well work that way in SPS/WSS. It doesn't seem to have any effect on other types of sites but my test configuration was limited so I may not have all the data. Again, if anyone has any ideas, please share.

More on this topic as I continue to tweak the settings.

Update: If you want to learn more about search optimization, here is a KB article to get you started.

Update 2: Here's another tip. When creating an inclusion rule for a subdirectory on a site the default behavior is to also include the parent site. For example, a new inclusion rule for http://www.theegroup.net/blogs/ would create two entries - one for the full path and one for the parent http://www.theegroup.net. This means that any links to other URL's on the same site would be processed which greatly expands the scope of the crawl. To restrict this behavior, change the parent rule to an exclusion and leave the child URL as an inclusion. This will restrict the crawler to links under the child URL.

Sunday, July 31, 2005

Custom Lists Article

Here's a fantastic article from Heather on adding custom lists to a site definition.

Friday, July 29, 2005

Useful Shortcuts

I love it when I'm just wandering around and find something truly useful (OK, I wasn't wandering - I used SharePoint Blogsearch to find one thing and stumbled across another). Here's a post from Todd Bleeker regarding javascript links that toggle page modes in SPS and WSS. I already new about appending ?toolpaneview=2 to the URL but this is even easier - and he explains what all the options are. Very cool. I had no idea you could do this with IE favorites.

Also, here's a great post from Serge van den Oever on FrontPage and Data View Web Parts. Very thorough and a must read.

Thursday, July 28, 2005

Sharepoint Resource List

While responding to a post from Heather on the proper uses of FrontPage with SPS 2003, I discovered her list of SharePoint resources. Very handy. Thanks, Heather!

Monday, July 25, 2005

Thursday, July 21, 2005

SharePoint Blogsearch

While Feedster and Technorati do a fine job searching general blog content we SharePointers are a bit out in the cold when it comes to searching posts from our fellow bloggers - not all posts have the keyword "sharepoint" and there is no way to set up a group of blogs to search. What are we to do?

Well, thanks to the much-maligned-but-ever-so-useful SPS search, we have an answer. I've taken my blogroll and created content sources and a new search scope on my website. Just select the 'SharePoint Blogs' scope and go to it. Incremental updates run every 4 hours - I'll adjust it as necessary depending on search results and bandwidth consumption.

If you've got a blog you want added to the list, email me and I'll add it. Also, if you want to access the indexes (or any web services, for that matter) from your site and need a login account, just let me know.

Happy searching!

The SharePoint Vision

Heather kicked-off a mini-tsunami with her post on the SharePoint vision. Andrew then jumped in with a few more thoughts (here and here). Bil's got a few ideas in his response to Heather's article (see her original post).

This is a good discussion. Anytime we stop writing code and start talking about the user is a step in the right direction. Heather has some very good points but one in particular is striking enough for me to add my two cents (I think we're up to a buck by now with everyone's input - or about 50 pence for my friends across the pond). She wonders why there aren't more general use web parts available to the SharePoint community. It's a good question with some uncomfortable answers:

1) Skilled SharePoint developers are few and far between. Sure, there are lots of people who have worked with SharePoint in some capacity but very few who know what they are doing and are actually good at it. This is by far the most common complaint I hear from clients and one that causes me a great deal of grief when trying to find adequate resources for a project. We NEED more and better training. We NEED SharePoint certifications (Are you listening Microsoft? Four years on the market and no cert track? Are you kidding me?).

2) The object model is preposterous. Yes, I know SPS was cobbled together quickly to meet market demands and WSS is just not that mature (props to MS for delviering a good product under those conditions despite what Mike Drip may think); however, they've got to clean up the OM in the next release. I've seen firsthand how it frustrates many .NET programmers who could otherwise be writing good code instead of wrestling with SharePoint nuances. Simplify and productivity will improve.

3) Due to #1 and #2 commercial web parts are very expensive. Solve these issues and the number of web parts will increase dramatically with a corresponding drop in prices. Developers should always be encouraged to profit from their efforts, so let's not expend a bunch of energy arguing about open source, community, freeware and so on. The profit motive is the most effective, so let's continue to encourage commercial development and take advantage of basic supply and demand principles - the more providers the lower the price. In fact, I'd like to see MS encouraging the Micro-ISV concept with new partner programs and support.

4) No centralized web part repository. As Bil astutely points out, we have to scour the web to find web parts like we're caught in some gigantic SharePoint treasure hunt. We need a better way to organize, categorize, rank, and share feedback on every web part out there. MS has taken a good first step with the component directory but it's just cut-and-paste marketing speak - no objective reviews or comments. I'm all for Bil's SharePointForge idea.

5) Corporate intellectual property restrictions. Many organizations don't want the result of their employee's efforts released into the wild without compensation. I see this happen more often than you might think. Right or wrong, and it's often a mix of both, they worry that competitors will benefit from their investment or that employees will spend their time supporting people other than their own internal users. Not to mention the most significant concern of all - liability. All it takes is one lawsuit to stop the flow of good code altogether. This is mostly a corporate culture issue but could be solved in some instances by better communication with management and pre-authorization from legal and marketing.

Those are just a few of my thoughts. I encourage the community to keep this discussion fresh and in the fore.

UPDATE I: While I'm on this soapbox, allow me to put in a plea to the developer community for standardization in product packaging. It's not that hard to compile a CAB file - don't make users jump through hoops to implement your code. Perhaps this should be a requirement in whatever centralized directory scheme is (hopefully) implemented - poor packaging equals poor ranking and fewer downloads. Some sort of incentive system for highets rankings and most downloads would be great as well.

UPDATE II: For anyone that is interested I would be willing to host the directory on my SharePoint server (c'mon, it has to be in SharePoint, doesn't it?). I don't think I can handle the storage and bandwidth for all the downloads but I would be more than happy to host and maintain the code. Any takers?

Site Navigator v2.0 Beta

Advis has just released a beta version of Site Navigator v2.0. This is by far my favorite site navigation web part. Download the free beta release now (registration required) and post some feedback to the Advis team - they're a crack bunch who really want to hear about any product issues and/or feature requests.