Archives
-
Areas vs. Sites
This is like the SharePoint version of Freddy vs. Jason (or Aliens vs. Predator if that's your sort of thing). It's a debate that, IMHO, has been asked and answered a thousand times over in the newsgroups and emails but people still are confused. Bill English said it best to me once (and continues with this position which I completely share).
- SharePoint Areas are for aggregation.
- SharePoint Sites are for collaboration.
Read this. Learn this. Live this.
True, you can use the portal to create document libraries, check-in/out and all that good stuff but it becomes a bit of a nightmare in organization from what I've found. Moving it down to a WSS site and letting the user manage access and all that works much better. Also you can create public areas for the information and let users “publish“ information up to the portal into that area which lets them decide what they want to show. They'll go on in their WSS site adding documents and only pushing up to the portal what others need to see.
-
SharePoint Wrapper Assembly and Migration Tool
I've completed a tool to migrate our documents with version histories from 2001 to 2003. I know SPIN/SPOUT do this en-masse but for our needs, there wasn't a 1:1 mapping with old portal directory structure and new portal. There were some other tools in this space, but I had some fun writing an assembly that bridges between the two. Also the new assembly comes in handy when I want to access SharePoint 2003 and do things without having to deploy web parts or executables on the server (everything is done through SharePoint web services).
The tool has gone off to QA for our own migration so I'll put together a small spike project with examples on how to use it. I'll include some samples on using the SharePoint wrapper assembly as it's handy to be able to bring up a treeview of all of your sites. I used it for another tool we created for deploying web parts to all sites or deleting lists. It can easily be extended to perform more functions but this was all we needed right now.
If there's any specific requests for functionality in the assemblies let me know and I'll include them before a release later this week. Just have to package up the code and put together some examples then release everything in a day or so.
-
Do we really need version history of documents?
This is something that's been bugging me for awhile so I thought I would throw it out there. "Is it really worth keeping version history of documents?". Other than legal reasons, I can't see why any would need version history of a document. Source code. Sure. Absolutely. A document? I'm beginning to think that there's no real reason why history is that important.
Look at a document. Okay, so you'll be able to go back in time and see that Joe added a paragraph or something. If you use Office, this feature is already built into the product. If it's a binary file of some kind (like a PDF) you really can't compare or track revisioning in the document (yes, Adobe had this feature but you need special tools to use it). The only case I can see is a text file where you can do diffs of the versions, but then why would I really care?
Retaining history seems to be so important to some, but I wonder if that's really the case? Maybe take a look at your documents and try to justify why you think you need a complete version history. Is it really worth keeping in the long run? -
The Programmers Essential Bookshelf
Things have changed since I started programming back in the 80s. However there are a few truths that are universal. Here's my list of the essential books all programmers, designers, coders, architects, etc. should have in their repository. Also if you're hiring new people out of University or something, giving them this stack would be a good way to introduce them into the world.
Code Complete (2nd Edition!)
Eric Evan's Domain Driven Design
Design Patterns
Kent Beck's Test Driven Development: By Example
Martin Fowler's Refactoring: Improving the Design of Code
UML DistilledThere are a couple of others that are great too (there are more):
Rapid Development
The Pragmatic Programmer -
SharePoint Wrappers
As part of my migration tool that I broke down and wrote, I've created a .NET assembly for accessing both SharePoint 2001 and 2003 systems. It uses PKMCDO internally to talk to the Web Storage System and get information about folder and documents and .NET web services to talk to 2003 servers and sites to perform basic stuff like adding web parts, deleting lists, etc. All you need to do is point it at your SharePoint server so you can build desktop apps or non-SharePoint web apps to do SharePoint functionality. I'll clean up the assembly and make it available this weekend for anyone interested. It's not the perfect solution (using web services it's somewhat limited) but at least you can deal with a SharePoint site as a real object.
-
User Control Container Web Part
I found a great tip and component that strangely enough has been around for a couple of months. Fons Sonnemans over at Reflection IT created a nice simple SharePoint Web Part that acts like a container for normal ASP.NET User Controls (.ascx files). This makes it dead simple to customize a portal page by adding normal ASP.NET controls to it.
You simply deploy the web part into your SharePoint environment and then create a custom control (his example uses a simple calculator). Deploy the user control to your web server (not sure if it has to be the SharePoint server) and then specify it in the wrapper web part. Presto. Instant Web Part without having to do all that funky Render code. Very nice.
There's an updated version of the component here. You can find the original information and v1.0 of the web part here.
-
Got ideas?
I have some spare cycles coming up and would like to offer my services for anyone looking for "small" web parts or .NET apps that interact and perform
some function directly with SharePoint Portal Server or the WSS. These are things I want to add to the community so who better to provide ideas than well, the community. A couple of caveats to this:
1. These are small spike projects not giant enterprise solutions so don't ask the world of me (i.e. I'm not going to build a business automation process engine for you).
2. I cannot make SPS/WSS do things it's not designed for (like providing a web part that grants folder level security) so don't ask.
3. There are no guarantees on the work done here and any creations are released to the community with source code for anyone to use. You're just there to get something directly and spark the ideas.
4. This is not contract work so nothing in return is expected.
5. Please send me your ideas, wants, desires to bsimser@shaw.ca only please.
Bring it. -
Wrapping PKMCDO and Adding documents via HTTP PUT
So I spent the better part of yesterday struggling with my problem. Uploading documents to a folder is great but don't use Copy/Paste to move them anywhere (unless you enjoy losing all your version information). On Monday I'm going to check out a couple of other migration tools, but one of the things with SPIN (besides the fact it has to run on the server) is that it creates its own document library and sets up it's own META data. For us, this just isn't working so we need to look elsewhere for an option.
I put together a simple document migration tool. I was tired of migration tools that brought over all the wonderful properties, security settings, etc. and all had to run on the server. What the hell is that all about? WSS is about the web. Anyways, there were two problems I had to solve. “How do I get all the versions out of the old Document folders?” and “How do I get all the documents into their new homes?”. Plain and simple. Just copy all versions from 2001 to a target location on 2003.
For this I did two things. First I wrapped up PKMCDO (the COM interface to SharePoint 2001) into a couple of C# classes. This lets me access everything in a nice way and doesn't expose me to KnowledgeFolders, Recordsets and all that ugliness. I created a SharePointServer class that I can connect to and get the workspaces. This hides a COM interop class to the KnowledgeServer interface:
/// <summary>
/// This class wraps up the entire 2001 server for the
/// purpose of accessing workspaces, folders and documents
/// within.
/// </summary>
public class SharePointServer
{
private string serverName;
private ArrayList workspaces;
private SharePointFolder documentRoot;
private KnowledgeServer server;
public SharePointServer(string name)
{
documentRoot = null;
server = new KnowledgeServer();
serverName = name;
}
#region Accessors
public string ServerName
{
set { serverName = value; }
}
/// <summary>
/// Returns the workspace list for a server.
/// Will load it on demand if it hasn't been
/// done yet.
/// </summary>
/// <returns></returns>
public ArrayList Workspaces
{
get
{
if(workspaces == null)
{
workspaces = new ArrayList();
ADODB.Recordset rs = (ADODB.Recordset)server.Workspaces;
while(!rs.EOF)
{
string url = rs.Fields["DAV:href"].Value.ToString();
workspaces.Add(new SharePointWorkspace(url));
rs.MoveNext();
}
}
return workspaces;
}
}
#endregion
/// <summary>
/// Gets the document root for a given workspace on the server.
/// Will load it on demand if it hasn't been created yet.
/// </summary>
/// <param name="workspaceName"></param>
/// <returns></returns>
public SharePointFolder GetDocumentRoot(string workspaceName)
{
if(documentRoot == null)
{
StringBuilder folderUrl = new StringBuilder();
folderUrl.Append("http://");
folderUrl.Append(serverName);
folderUrl.Append("/");
folderUrl.Append(workspaceName);
folderUrl.Append("/Documents");
documentRoot = new SharePointFolder(folderUrl.ToString());
}
return documentRoot;
}
/// <summary>
/// Connects to a SharePoint server for accessing
/// workspaces, folders, and items.
/// </summary>
/// <returns></returns>
public bool Connect()
{
bool rc = true;
// Build the string for the server and connect
StringBuilder serverUrl = new StringBuilder();
serverUrl.Append("http://");
serverUrl.Append(serverName);
serverUrl.Append("/SharePoint Portal Server/workspaces/");
server.DataSource.Open(
serverUrl.ToString(),
null,
PKMCDO.ConnectModeEnum.adModeRead,
PKMCDO.RecordCreateOptionsEnum.adFailIfNotExists,
PKMCDO.RecordOpenOptionsEnum.adOpenSource,
null,
null);
return rc;
}
}
/// <summary>
/// This represents a wrapper class to more easily
/// use the PKMCDO KnowledgeFolders object for accessing
/// Sharepoint 2001 items. It uses COM interop so it's
/// slooow but it works and at least you can use C# iterators.
/// </summary>
public class SharePointFolder
{
private string folderUrl;
private KnowledgeFolder folder = new KnowledgeFolder();
private ArrayList subFolders = new ArrayList();
/// <summary>
/// Constructs a SharePointFolder object and opens
/// the datasource (via a url). COM interop so its
/// ugly and takes a second or so to execute.
/// </summary>
/// <param name="url"></param>
public SharePointFolder(string url)
{
folderUrl = url;
folder.DataSource.Open(
folderUrl,
null,
PKMCDO.ConnectModeEnum.adModeRead,
PKMCDO.RecordCreateOptionsEnum.adFailIfNotExists,
PKMCDO.RecordOpenOptionsEnum.adOpenSource,
null,
null);
}
/// <summary>
/// This loads the subfolders for the class
/// if there are any available.
/// </summary>
public void LoadSubFolders()
{
if(folder.HasChildren)
{
ADODB.Recordset rs = (ADODB.Recordset)folder.Subfolders;
while(!rs.EOF)
{
SharePointFolder child = new SharePointFolder(rs.Fields["DAV:href"].Value.ToString());
subFolders.Add(child);
rs.MoveNext();
}
}
}
#region Accessors
public ArrayList SubFolders
{
get { return subFolders; }
}
public bool HasSubFolders
{
get { return folder.HasChildren; }
}
public string Name
{
get { return folder.DisplayName.ToString(); }
}
#endregion
}
This allowed me to get everything I needed from the old 2001 server (there are other classes for wrapping up the document and versions). The second problem was how to upload these versions to the new 2003 document library. Just upload the document. That's all I wanted to do.
There seemed to be a lot of argument about using Web Services, lists, and all that just to upload a document. It can't be that hard. After spending a little time on Google (google IS your friend) I found various attempts at uploading documents through regular HTTP PUT commands. Here's the one that finally worked in a simple, single function:
/// <summary>
/// This function uploads a local file to a remote SharePoint
/// document library using regular HTTP responses. Can be
/// included in a console app, windows app or a web app.
/// </summary>
/// <param name="localFile"></param>
/// <param name="remoteFile"></param>
public void UploadDocument(string localFile, string remoteFile)
{
// Read in the local file
FileStream fstream = new FileStream(localFile, FileMode.Open, FileAccess.Read);
byte [] buffer = new byte[fstream.Length];
fstream.Read(buffer, 0, Convert.ToInt32(fstream.Length));
fstream.Close();
// Create the web request object
WebRequest request = WebRequest.Create(remoteFile);
request.Credentials = System.Net.CredentialCache.DefaultCredentials;
request.Method = "PUT";
request.ContentLength = buffer.Length;
// Write the local file to the remote system
BinaryWriter writer = new BinaryWriter(request.GetRequestStream());
writer.Write(buffer, 0, buffer.Length);
writer.Close();
// Get a web response back
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
response.Close();
}
To call it, just pass it the name of a local file and the name of the fully qualified file to be uploaded on the server (document library and filename). You could also modify the code to accept a stream and read the stream in from a web page. Or process an entire directory at once. “Shared%20Documents“ is the name of the Document Library on the site. I'm not sure if you need the %20 or not, and it might be better to use a System.Uri object instead of a string here, but it works.
string localFile = "c:\\test.doc";
string remoteFile = http://servername/sites/sitename/Shared%20Documents/test.doc;
UploadDocument(localFile, remoteFile);
Easy stuff. Let me know if you want the full source to my PKMCDO wrappers and the migration tool. I may end up posting all the code, but I have to finish it and do some unit testing on it.
-
Working on a simple document migration tool
I'm kinda fed up at the migration tools for SharePoint (and maybe I'm just still steamed at the fiasco I went through yesterday). Using SPIN and SPOUT was to get everything (with history) from 2001 to 2003. My plan was, once migrated into the SharePoint Document Libraries, to just copy these things down to where they belong. Now I'm on the warpath to write a proper migration tool. One step read from 2001 to write into 2003. It's all there via Web Services and the old SharePoint 2001 OM. Why is this so difficult? All the tools that Microsoft gives you are for mass migrations and assume you want to maintain security and copy all the profiles, etc. 2003 is a completely different beast architecturally and makes you think differently about the way your organize your information. Yes, there are some other commerical tools that might do this but again these are just not quite right. And to top it off, nothing ever seems to work from your desktop. You have to be running right on the SharePoint servers directly. This is just not right and I'm out to fix it (somehow). Back later with the results of my madness...
One other note from yesterdays blog, Addy Santo has posted a commont about an an excellent deployment tool which assists both in migrations and deployments, and can take most of the pain out of the process. There wasn't much to check out but I'm going to investigate this and see if this can assist with what I'm trying to do here.
-
SharePoint 1 - Bil 0
Okay, I'm a little frustrated tonight and have expereienced, in full featured dyno-rama glory that which I call SharePoint and the beast known as WebDAV (warning this is a little long but does contain important info about a problem with Explorer View and SharePoint). Caveat Emptor: This is the situation for me in my environment (Windows 2003, SharePoint Portal Server/WSS, Windows 2000 Client and Office 2003). Maybe I've just been up too much watching the Calgary Flames but I do think there's a problem here (and hoping my MVP buddies will help confirm/deny my findings).
Ever since I saw the 2003 version of SharePoint, the WSS and those wonderful features my mouth was watering. What a great improvement over what we currently had. One nice feature was the Explorer View in the document libraries. Now, through the web browser, you could drag and drop files and view your SharePoint site like you do your hard drive. Great stuff.
Tonight I discovered the pain that is WebDAV, the SPIN and SPOUT tools and how things are not always what they seem.
We're moving about 5GB of documents (10,000 documents or so) from our old 2001 server to the new 2003 system. Great. Of course we didn't do the in-place upgrade as this was a new system so the quest for tools began. Luckily Microsoft came through with SPIN.EXE and SPOUT.EXE which exported all the version history from 2001 into XML and file formats and SPIN.EXE let you import it into WSS document libraries (or SPS areas if that's your thing). Architecture changes, heirachy changes and things are now organized differently in the new world. While the tools exported fine (although taking 4 hours to run over the network) the problem came when trying to put documents in the right place.
So there are some bugs with Explorer View, and quite frankly I recommend deleting that view until these things are resolved or someone comes along with a better implementation of it. As I understand it, it works using WebDAV to connect to the Document Library. There are three serious problems with the Explorer View which I'll get into more detail:
- WebDAV can only handle a certain path length and bombs on long URLs
- The Explorer View in your web browser is cached and never refreshed unless you explicitly do it yourself
- Copy and Paste is a nightmare in versioned libraries
Let's get into more detail about these.
1. If you've ever gone fishing in your SharePoint sites and then clicked on Explorer View only to be informed that the view requires Internet Explorer 5 or higher then you'll know what I'm talking about. It's an odd message because when I get it, I'm running IE 6. So that's not the problem. I've narrowed it down to the fact that the path gets too long. When you get into specific folders in Document Libraries and specific views, the URL gets pretty long. IE can handle anything but when it switches over to Explorer View it starts talking WebDAV to the back end. That's where the view fails and you end up with a cryptic error message.
2. Here's a screwy thing that took me a few minutes to figure out. Create a Document Library then go in and add a file. Great. Switch to Explorer View (assuming you don't run into issue #1) and behold your document. Flip back to All Documents view and add a folder. Delete a document. Do whatever you want. Now back to Explorer View in your browser. Hmmm. That's not right. It's the same thing I looked at a few minutes ago (sans the changes you just did). I think the Explorer View is cached and the browser is using that cached version. Hitting F5 doesn't work because that's only going to refresh the web page, not the folder view. You have to right click and select Refresh from the popup menu (yes, a different refresh from the browser one).
3. Okay, here's the grand daddy of them all (and it gets complicated so walk with me on this). DO NOT USE COPY AND PASTE IN VERSIONED LIBRARIES! Hmm. Got the message? Here's the rundown.
- Create two Document Libraries (“doclib1“ and “doclib2“) and set versioning on (can be in the same site, doesn't matter)
- Create a text file with Notepad or something (“ver.txt“) with the words “Version 1“ in it
- Upload “ver.txt“ to “doclib1“
- Check it out via the menu
- Edit your “ver.txt“ file on the hard drive and change the text to “Version 2“
- Upload “ver.txt“ (modified) to “doclib1“ (overwriting any current copy)
- Use the menu and check the file in
Looking at the version history, you now have two versions. Click on the first one and you'll see the text “Version 1“. Click on the second (and hit F5 in your browser) and you'll see “Version 2“. Also notice that there's a new path created for version 1 of the document (if you hover over the link in the document library).
Now the fun begins. Switch to the dreaded Explorer View in “doclib1“. Select your “ver.txt“ file and press Ctrl+C (Copy). Now go find “doclib2“ and switch to Explorer View (dual monitors makes this much easier). Press Ctrl+V (Paste). Voila. You now have “ver.txt“ in your new Document Library. Wait a minute. Something isn't quite right. The version comments (if you had any) all say the same thing as the last version. Click on the file to view the Version History. You'll notice the right number of versions (two in this example, but if you had 10 in “doclib1“ you'll have 10 in “doclib2“). However they're ALL THE SAME VERSION! Yup, SharePoint created multiple “versions“ of your document but they're all copies of the latest one.
There's actually two different behaviours here. Copy/Paste from one Document Library to another in the same site yields this result. However try this by Pasting into a Document Library on another site is a whole nuther matter. Try it and view anything but the latest version. SharePoint lists the versions but they don't exist. In fact, nothing does. It's odd. Clicking on the file produces a web page that goes to HTML Valhalla. There's no 404 error. Right click and View Source and there's a complete absence of anything. No HTML tags. Nothing. Very, very odd.
Anyways, as I said earlier your mileage may vary but if you can confirm item #3 I suggest you do one of two things. Do not using versioning in your document libraries or delete the Explorer View. Hopefully this will help, now I just have to figure out how to move all my version histories to the right places.
-
Remembering Design Principles
After taking a look at NDepend and running it on a few projects at work, most of the assemblies seem to either be living in the Zone of Pain or the Zone of Uselessness. While I'm not using NDepend as the silver bullet to tell me who's been naughty or nice, I am wondering how many basic design principles have been forgotten (or some cases never learned in the first place).
The common ones that I keep going back to (and evangelise to those that care) are:
- The single responsibility principle (a class should have only one reason to change)
- The common reuse principle (the classes in a component are reused together. If you reuse one of the classes in a component, you reuse them all)
- The interface segregation principle (clients should not be forced to depend upon methods that they don’t use. Interfaces belong to clients, not to type hierarchies)
- The abstract factory pattern (to trim down dependencies between concrete types)
- The separated interface pattern
There are others, but these are usually the most common and most violated as I keep looking at other peoples code (and my own for that matter).
-
Design Quality Metrics for .NET Applications
I've been struggling with a concept at work. How to measure the quality of an application? It's typical to ask and there are some basic things you can look at, but for the most part you're doing code reviews or looking through architecture diagrams trying to figure out what someone built and is it good.
Now along comes NDepend. It analyses .NET assemblies and generates design quality metrics around them. You can measure the quality of a design in terms of extensibility, reusability, and maintainability. Prety nice stuff and of course, open source (yay for OSS!). You can check out the latest version of NDepend here and check out a sample report of NDepend run against NUnit here. Neat stuff.
-
Unit Testing with SharePoint Web Parts
In my experiences, one of the best things you can do (if you do any XP practice at all) is to write Unit Tests. These are a small, focused set of tests that perform some specific testing of the system. For an overview of Unit Tests check out this link here. In the .NET space, NUnit is my preferred Unit Test framework. Your milage may vary.
When writing Web Parts for SharePoint, you can apply the same practice to your web part and exercise the Unit Tests using the NUnit GUI (or NUnit command line if you prefer). First download NUnit from here and install it to your development environment and on the SharePoint server.
How you setup your tests are up to you (and what dependancies your web parts will need) but try to position the Test Fixture up as high as you can on the SharePoint tree, holding whatever information you need. Get a reference to the entire web collection if you want so that way your tests don't have to tax the SharePoint server too much when running. Then just manipulate the collection during your tests. As long as you clean up after yourself (like deleting a site after creating it) everything should be fine.
Here's a sample test for a custom webpart that creates new sites (using the SPWebCollection class). The Test counts the number of existing sites and stores it and then adds a new site and tests the count (you could also find the test using a query or something but this is a simple example). The test uses data from the web part for input into the site creation process (name, description, etc.):
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Xml.Serialization;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebPartPages;
using Microsoft.SharePoint.Utilities;
using System.Web.UI.HtmlControls;
using NUnit.Framework;
using CustomWebPart;namespace CustomWebPart.Test
{// This is our custom web part which
// has some public properties for the new site
private CustomWebPart webPart;
private SPWeb webSite;
private SPWebCollection siteCollection;
private string currentTemplate;[TestFixtureSetUp]
public void Init()
{
webPart = new CustomWebPart();
webSite = new SPWeb(“http://Server_Name//sites//Site_Name“);
siteCollection = webSite.Webs;
currentTemplate = webSite.WebTemplate;
}
[Test]
public void TestAddSite()
{
string siteName = webPart.SiteName;
string siteDescription = webPart.SiteDescription;
string siteUrl = webPart.SiteUrl;
int siteCount = siteCollection.Count;
// Add the new site
siteCollection.Add(siteUrl, siteName, siteDescription, Convert.ToUInt32(1033), currentTemplate, True, False);
int newCount = siteCollection.Count;
Assert.AreEqual(siteCount+1, newCount, “Invalid site count“);
// delete the site we just created
siteCollection.Delete(siteName);
}
}You can reference any part of your Web Part or invoke a method on it to help with your tests as needed. As well, once you create a connection to a SPSiteCollection or SPWebCollection you can iterate through sites or get individual sites and perform any tests on them. Just keep your tests clean and simple and only test what you need to as it relates to your Web Part.