Fear and Loathing
Gonzo blogging from the Annie Leibovitz of the software development world.
-
SharePoint Pet Peeve #327
Dear Microsoft,
Please enable alerts to fire when an item is submitted to a list that requires approval.
Man, I don't know how many times I keep forgetting to go to a raft of lists that I have to approve content to see if anything new has been added. I know that the EventHandler interface for lists was omitted due to timing, but having to manually check lists where approval is required is rather annoying.
Nintex has a pretty good add-on for Document Libraries but it doesn't work for Lists. There are some Web Parts floating around that will show you what needs you approval which is good too. However I was hoping to minimize some of the extra add-ons that I feel should be part of the base system.
Okay, enough ranting for this morning.
-
Best Practices for Writing HTML in Web Parts?
Something that's as puzzling as the Cadbury secret (and we all know that us programmers figured that out long ago) is just what's the best way to write out HTML in Web Parts? So call me masochistic but I write Web Parts with code. Yes, it's ugly. Yes, it's painful. Yes, you could use something cool like SmartPart or load the controls yourself (but that brings on a host of other issues like Code Access Security so we won't go into that).
For those of us that hold true to the "old fashioned" way, what's the best way to write all that code out? Consider these two approaches that writes out a label and control in a row for a form:
private void RenderRow(HtmlTextWriter output, Label label, WebControl control)
{
output.Write("<tr><td class=\"ms-formlabel\" valign=\"top\" nowrap=\"true\">");
label.RenderControl(output);
output.Write("</td><td class=\"ms-formbody\">");
control.RenderControl(output);
output.Write("</td></tr>");
}
private void RenderRow(HtmlTextWriter output, Label label, WebControl control)
{
output.RenderBeginTag("tr");
output.RenderBeginTag("td");
output.AddAttribute("class", "ms-formlabel");
output.AddAttribute("valign", "top");
output.AddAttribute("nowrap", "true");
label.RenderControl(output);
output.RenderEndTag();
output.RenderBeginTag("td");
output.AddAttribute("class", "ms-formbody");
control.RenderControl(output);
output.RenderEndTag();
output.RenderEndTag();
}
Both output exactly the same HTML. Does it matter? The first approach is less lines but is it any more (or less) readable? Or maybe everything should be built up in a StringBuilder class and slammed out to the HtmlTextWriter? Or is it simply whatever is readable or maintainable works? Looking for your thoughts, ideas, suggestions, rants, assorted concealed lizards of any kind.
-
Opening Links in new Windows
This question gets asked a lot in the newsgroups. The default behaviour of the links list (which is just a template afterall) is to open the link in the same window. I took a look at Jim Duncan's cBlog site definition recently that had a nice modification that I think should be on everyone's SharePoint box. He added a simple checkbox to set a link to open in a new window.
First you'll need a new site definition so copy STS to another one for this purpose. Then copy the Links list definition to a new one (it's in a folder called FAVORITES). Alternately, you can just work directly on the definition itself but see the note at the end of this blog about that. In the LINKS definition they'll be a SCHEMA.XML file which defines all the fields and how the list behaves.
First let's add the new checkbox field that will hold our new option:
<Field Type="Boolean" DisplayName="Open in New Window" Name="NewWindow"/>
Great. Now it'll show up on the New and Edit forms and be saved with the list. How do you get it to show up in the rendered HTML? This is done in the DisplayPattern tag for the URL field. The default Links list part that we're interested looks like this:
<Default>
<HTML><![CDATA[<A onfocus="OnLink(this)" HREF="]]></HTML>
<Column Name="URL" HTMLEncode="TRUE"/>
<HTML><![CDATA[">]]></HTML>
<Switch>
<Expr><Column2 Name="URL"/></Expr>
<Case Value=""><Column Name="URL" HTMLEncode="TRUE"/></Case>
<Default><Column2 Name="URL" HTMLEncode="TRUE"/></Default>
</Switch>
<HTML><![CDATA[</A>]]></HTML>
</Default>So we want to do a check on our NewWindow field and if it's set, add in a "target=_blank" piece of HTML to our HREF tag. So the new DisplayPattern tag should look something like this (changed highlighted in Red):
<Default>
<HTML><![CDATA[<A onfocus="OnLink(this)" HREF="]]></HTML>
<Column Name="URL" HTMLEncode="TRUE"/>
<HTML><![CDATA[">]]></HTML>
<Switch>
<Expr><Column2 Name="URL"/></Expr>
<Case Value=""><Column Name="URL" HTMLEncode="TRUE"/></Case>
<Default><Column2 Name="URL" HTMLEncode="TRUE"/></Default>
</Switch>
<Switch>
<Expr>
<Field Name="NewWindow"/>
</Expr>
<Case Value="Yes"><HTML><![CDATA[ target="_blank"]]></HTML></Case>
</Switch>
<HTML><![CDATA[</A>]]></HTML>
</Default>That's it. A simple change to one file and all your links have the capability to open in new windows across all your sites. You can preset this value if you want (using the <ROWS> tag in your sitedef) and hide the field or let your users choose. Of course, modifying your base SharePoint install isn't recommended as the next service pack or update may wipe out those changes however for this change I'm willing to do the file management myself to avoid this. Your mileage may vary.
The other trap is that all the sites that are out there, already created using the STS template might break. Adding a new field to a definition usually is ok but it's something you need to test big time if you have a lot of sites out there already created (NUnitAsp is great for this). It would be nice if it was part of the base system though wouldn't it?
-
MSOS DevCon, Word XML, and the Mvp.Xml Project
I was invited, but unfortunately due to scheduling conflicts I was unable to attend this years Microsoft Office System Developer Conference in Redmond this past week. My loss as Bill Gates gave the keynote about the importance of XML, connectivity, ease of development, lower CTO, better performance, extreme developer tools (VS2005 is just plain freakin' amazing, my words not his) and the importance of reuse and leveraging both 3rd party and MS developers to build interopabile solutions. The great thing (from my perspective) is that SharePoint is being positions at the core of most of the products which means lots more development and collaboration to come.
Mike Fitzmaurice gave a presentation about how SharePoint can be used to access backend data services (SAP, Siebel, PeopleSoft, etc.) including some discussion around BizTalk, SSO (Single Sign On), etc.
I think one of the key things is to look at storing Word, Excel, etc. files in XML when saving stuff in SharePoint rather than the traditional DOC, XLS, etc. formats. I noticed the recently released (2/4/2005) Word XML Software Developers Kit which follows the Office XML Reference Schemas that were published awhile ago. Displaying Word XML docs in a browser (via SharePoint and an XSL file) is so much better on performance than launching embedded Word and you've got so much more power like loading it up into an XmlDocument and modifying it rather than the traditional Word COM fiasco.
On the Xml front, you might also want to check out the Mvp.Xml project here which is aimed at supplementing .NET framework functionality available through the System.Xml namespace. Helps a lot when you're working with Xml Web Services coming out of SharePoint.
-
Stupid SharePoint Fact #327
Ever wonder why all those FrontPage (and subsequently now SPS and WSS) directories were called /vti? VTI was the acronym for a company called Vermeer Technologies Incorporated. Microsoft aquired the company in 1996 which had a flagship product. This product eventually became FrontPage. Hence all the vti directories all over your servers. You can check out the original press release here on it.
-
Reading vs. Updating objects in the SharePoint Object Model
A message came up on the newsgroup about someone having a problem with this code:
System.Guid listId=web.Lists.Add(title,description,web.ListTemplates["Document Library"],web.DocTemplates[0]);
web.Lists[listId].OnQuickLaunch=true;
web.Lists[listId].Update();They were creating a list (a document library in this case) and wanted to display it on the Quick Launch. The code looked okay but the list wasn't being displayed on the Quick Launch. After a little experimenting (going down a couple of turkey trails like web part vs. console app) I found this slight change worked:
System.Guid listId=web.Lists.Add(title,description,web.ListTemplates["Document Library"],web.DocTemplates[0]);
SPList list = web.Lists[listId];
list.OnQuickLaunch=true;
list.Update();I do remember passing by a document somewhere that said when accessing classes in the SharePoint namespace for updating to use the actual objects rather than an index of a collection. For whatever reason, I just naturally always get the list object myself so never came across this before. For reading purposes (like listing all the lists on a site) it's fine, but updates need to be implemented this way. I can't find that link right now but I did find a blog by Kris Syverstad from July of 2004 here on it to which Peter Provost thought maybe the WSS team implemented a property indexer when they probably should have implemented a method.
Looking through the newsgroups, this is probably one of the biggest "why doesn't my code work" message posted when it comes to updating properties. This tip will be true for any object in a collection (SPSite, SPList, SPField, etc.). Good stuff to know.
-
AlternateHeaders and WSS sites...
...doesn't work. Yup, plain and simple. The ONET.XML file describes site definitions and can contain an attributed called "AlternateHeader" which you specify as your own aspx page living in the LAYOUTS\1033 directory. By default, all portal areas (everything under the SPSxxx folders) have this set to "PortalHeader.aspx" which just brings in the various CSS files and registers a couple of tag prefixes for accessing SharePoint Web Controls. However, none of the WSS site definitions have this AlternateHeader defined. You might think you're smart (several people have thought this) and set the AlternateHeader attribute to PortalHeader.aspx and magically all your WSS sites will start looking like they actually belong to the portal (i.e. having the same Portal navigation, etc.). Nope. All it does is create chaos and anarchy because SharePoint controls don't necessarily work in WSS sites. And it's only Tuesday...
-
Zero touch RSS Web Part
George Tsiokos wrote me about a service he put together and is offering for free (under the MIT Licence). You enter an RSS feed URL on his page and he'll generate a DWP file you can upload directly as a Web Part onto your SharePoint Web Part Page. It uses the DataView Web Part and XSL to perform the translation so requires no server software installation (except the upload which anyone with the add web part right can do). It produces a nice, clean, RSS feed from news items with a link to each entry. The title of the Web Part links to the parent site where the feed came from. His DWP file supports RSS 0.90, 0.91, 0.92, 1.0, and 2.0 versions as well as Atom and Microsoft's Channel Definition Format. You can check it out here on his site.
-
YASPQ
I live and breath computers. Each and every day, for 12-16 hours a day, I sit in front of a computer spitting out various artifacts (mostly Internet Explorer/Firefox history links, but the occasional piece of code, application, document, or architecture that makes some kind of sense). Here's Yet Another SharePoint Quirk (YASPQ) that make me loathe that collaboration environment we all know and love.
Security. Love it or hate it, it's all around us. After all, without it everthing would be well, unsecure. SharePoint and Web Parts (and the .NET Framework to a lesser extent) has a big tie-in with security. Writing Web Parts is pretty straight forward however you'll run into a few security hurdles if:
- You want to reference other assemblies in your solution (things like domain objects or data access layers or the recently released Enterprise Library of application blocks, *very cool* and probably deserves a blog entry of it's own on incorporating them into SharePoint Web Parts so stay tuned)
- You want to reference the SharePoint object model (what else would a SharePoint Web Part do?)
- You want to make external calls to services like external Web Services or access sites using something like the WebClient or WebRequest classes.
Just spitting out HTML in your Web Part is easy. Talking to the SharePoint Object Model (which you'll want to do if you want to display SharePoint information like lists of sites, document library info, etc.) is a little more tricky but can be easily fixed by changing the trust level to WSS_Medium. Talking to other assemblies can easily be accomplished by adding the [assembly: AllowPartiallyTrustedCallers()] which shouldn't be a concern (although it *could* open your assembly up to attacks from that yet to be created .NET managed virus).
The three options you get with developing SharePoint Web Parts are evil, but balanced in a kind of masochistic way (much like writing Web Parts from scratch which I can now say I can do in my sleep and usually do).
- Increase the trust level of the entire virtual server. Best option to keep your development cycle happy (compile directly to the BIN directory or have a NAnt script drop it there for you) and no IISRESET required. Least secure though and affects all assemblies on the virtual server.
- Toss your Web Parts into the GAC. Less secure but easy to implement. Not a great development option though as you have to do an IISRESET each time you update the Web Part.
- Create a custom policy file. Talk about complicated (no less than 5 steps to implement this and a bugger to get it right if you don't know where all the files are located on the server). The most secure and basically the most flexible as you can tune the security to the Web Part itself. However I've never seen any commercial, freeware, or otherwise Web Parts ship with a policy file or install one.
Anyways, a few options and definately something you should be aware of. Developing can be easy (and unsecure) and you can leave the heavy lifting up to consumers if you're creating Web Parts for others. In-house Web Parts *should* go through the motions and create and implement custom policy files so you can co-exist nicely with a locked down portal. You just need to do a little more legwork on these. Here are a few resources to get you more info:
Microsoft Windows SharePoint Services and Code Access Security
Creating Web Parts that Call Services
SharePoint, Code Access Security and SmartPartPS I notice while I'm blogging on the .Text website my cursor is updating with each character I type and pausing from time to time. Must be something in the control doing an autosave of the blog, but it's damn annoying!
-
Leveraging SharePoint Web Part capabilities
I'm stuck. I think I'm caught between a rock and a hard place.
Currently with SharePoint you have 3 ways to create web parts.
- Define the views and all that stuff and drop it on a page.
- Create a DataView Web Part (with FP2003) and drop it on the page.
- Create everything from scratch.
With the first two options, you can get all kinds of nice features like column sorting, filtering, etc. Free. No coding required. With the second option, you can have all kinds of great business logic happening to render your information. Run off and talk to SAP, suck some data out of your corporate Oracle system, do some financial calculations, whatever. Then fire it off the web page all nice and neat for the user. Problem is that I can't find any !%@#$%$# way to find a happy balance between the two.
What if you want to aggregate across multiple types of items or items that don't exist in a single library or list (like a domain collection)? Let's say I have 500 sites under a portal and each one has a Task list on it (we'll assume they're all from the same site definition). I write a web part to recurse through all the sites, grabbing the task list and finding all tasks assigned to Me and want to display them in my custom web part, with filtering, paging, sorting, etc. that the ListViewWebPart provides (assuming I have a list to point it at). I can't because I have nothing to point the listview at. Unless I create a new list on the fly (ugh) and populate it, but that's just plain ugly.
ListViewWebPart would be nice to inherit from then specify your own data source (overriding the GetData method). However pretty much all of the classes in Microsoft.SharePoint.* are sealed (except for WebPart) so that's the only thing you can inherit from. For example, when you drag a web part onto a page, SharePoint will use the ListViewWebPart from it's arsenal to give you all those features people want (sorting, filtering, etc.). However you can't inherit from ListViewWebPart, only WebPart. The DataView Web Part is the same. I have to point it at a data source. Grant you it's a little easier because you could point it at a web service and have your web service do the heavy lifting, but still it's not very elegant or easy for that matter and I've never seen anyone do it.
Anyone have any insight into how to bridge what I think is a gap here? While you can't inherit from a ListViewWebPart or a DataViewWebPart, you can construct them in your custom web part. Problem is you're still stuck having to point them at a physical source of information rather than a collection of domain objects.