Wednesday, April 8, 2009

SharePoint 2007 (MOSS 2007) WebParts for Dummies

I have long thought that SharePoint would make an excellent development platform.  I only recently had the time to build some small SharePoint components, as well as some tools to streamline the process.

Caveat:  The web parts I built were around a business process that leveraged SharePoint lists as data sources.  They were not something that you would expect to see in a 3rd party tool set.  That is to say they were not generic enough to be plug-and-play SharePoint web parts – they were tied to a specific business process.

The first thing to understand about SharePoint development is that you can go one of two routes with web parts.  The "easy" route is to use a "wrapper" web part and develop custom controls in .Net.  The "hard(er)" route is to develop full-blown SharePoint web parts.  In either case, you'll need:

  • Visual Studio 2008
  • WSS3.0 (Windows SharePoint Services 3.0) or MOSS (Microsoft Office SharePoint Services) 2007

Most folks will tell you you need to have Visual Studio running on the same box as SharePoint.  That's probably a good idea if you're doing a lot of SharePointy things developmentwise.  For starters, I found that having a Virtual Machine for the MOSS environment (Windows Server 2003, DC, SQL, IIS, MOSS 2007) worked well.  On my local box, I have Visual Studio 2008 and a copy of the Microsoft.Sharepoint.dll (copied from the CAG on the VM) in the GAC.

The Easy Route

To get started on the Easy Route, download the SharePoint SmartTemplates from CodePlex at http://www.codeplex.com/smarttemplates.  While you're there, also grab the Return of SmartPart v1.3 at http://www.codeplex.com/smartpart/Release/ProjectReleases.aspx?ReleaseId=10697.  Install the SmartTemplates on your Visual Studio box, and install the Return of SmartPart on the MOSS box.

Fire up Visual Studio, and you'll see two new C# templates:  SharePoint WebPart (SmartTemplate) and SmartPart WebPart (SmartTemplate).  What's the difference?  SmartPart is on the Easy Route; SharePoint is somewhere between the Easy and Hard Routes.

SmartPart Web Part Template

The SmartPart Web Part template effectively turns a web user control (ascx file) into a SharePoint web part.  When you create a new project, you’ll notice UserControl1.ascx.  This is the default control that will be displayed when you add the web part to a SharePoint page.  Building the project will produce an installable package for the SharePoint box.  Install that package, and you’ll see your new control in the gallery of web parts.

This approach is great for .Net developers who enjoy the “visual” part of Visual Studio (i.e. designing custom web controls (ascx) in a visual mode) and who want to start down the trail of creating SharePoint web parts.  What the SmartPart lacks, though, is full SharePoint integration.

Note that to use the SmartPart that you build, you’ll need to have the aforementioned “Return of SmartPart v1.3” installed on the SharePoint box.

SharePoint Web Part Template

The SharePoint Web Part template is different from the SmartPart Web Part template in two ways.  First, it doesn’t require Return of SmartPart to be installed on the SharePoint box.  That’s a good thing – especially if you’re developing web parts for someone else’s SharePoint server.  Fewer things to install and track is always a good thing.  Second, it doesn’t have a visual designer interface.  Since I’m a visual guy, this is a non-starter for me.

Like the SmartPart Web Part, the SharePoint Web Part inherits from the .Net webpart, and thus lacks full SharePoint integration.

The Hard Route

I haven’t actually gone down this road (yet), but I know enough about it (and me) to know that I don’t want to go there (yet).

The Hard Route is similar to the SharePoint Web Part above.  It has no visual designer, and it doesn’t require additional components to be installed on the SharePoint box.  However, since it inherits from the Microsoft.SharePoint.WebPartPages.WebPart class, it is a true, full-featured SharePoint Web Part and needs no “wrapper” part.  This is the way to go in the long run, but it can be a bit overwhelming for beginners.

The Route I Chose

So here’s what I wound up doing:  I went with a modified version of the Easy Route.  A friend of mine pointed me to another SmartPart (code below) that does inherit directly from the Microsoft.SharePoint.WebPartPages.WebPart class – and as such, is fully integrated with the SharePoint platform.  Moreover, it adds a SharePoint shared personalization parameter, WebUserControlPath, which allows a user (or SharePoint admin) to specify an ascx file that this web part then loads and displays.

This approach creates a wrapper web part that is fully SharePoint integrated, and all that web part does is dynamically load a custom web control (ascx file) that you specify as a configuration parameter for the wrapper web part.

With this approach, you can develop a generic Web Application in .Net using web user controls in a separate project (separate and apart from the SmartPart wrapper project).  You can test those controls on aspx pages in .Net without bothering with SharePoint.  Once you’ve got them working the way you want, upload them (ascx files and the dll for the web application) to the SharePoint box.  To display the controls, add the SmartPart web part to the page and configure it to point to your web user control (ascx file).

Here’s a little more in-depth how-to:

  • Create a class library project and add the code below.  Compile the project (be sure to assign a strong name and sign it).  Import the dll to the GAC on the SharePoint box.


using System;


using System.Security;


using System.Text;


using System.Web.UI;


using System.Web.UI.WebControls;


using System.Web.UI.WebControls.WebParts;


using Microsoft.SharePoint.WebPartPages;




[assembly: AllowPartiallyTrustedCallers()]




namespace HCS.WebParts


{


    /// <summary>


    /// This Web Part wraps a web user control in order to display it in SharePoint.


    /// </summary>


    public class SmartPart : Microsoft.SharePoint.WebPartPages.WebPart


    {


        protected bool AlwaysBubbleUpExceptions = true;


        protected string Exceptions;


        private string _userControlPath;


 


        /// <summary>


        /// Gets or sets the path to the web user control.


        /// </summary>


        /// <value>The path to the web user control.</value>


        [WebBrowsable(true), Personalizable(PersonalizationScope.Shared), WebDescription("Web User Control Path")]


        public string WebUserControlPath


        {


            get { return _userControlPath; }


            set { _userControlPath = value; }


        }




        /// <summary>


        /// Called by the ASP.NET page framework to notify server controls that use composition-based implementation to create any child controls they contain in preparation for posting back or rendering.


        /// </summary>


        protected override void CreateChildControls()


        {


            base.CreateChildControls();


            Control _control = default(Control);


            try


            {


                // If the WebUserControlPath has been set load the control (this could require GAC installation of your DLL) 


                // Else add a message so the user knows a path has not been specified


                if (String.IsNullOrEmpty(this.WebUserControlPath))


                {


                    Label noControlLabel = new Label();


                    noControlLabel.Text = "A web user control has not been specified.";


                    _control = noControlLabel;


                }


                else


                {


                    _control = this.Page.LoadControl(WebUserControlPath);


                }


                // add it to the controls collection to wire up events 


                Controls.Add(_control);


            }


            catch (Exception ex)


            {


                if (AlwaysBubbleUpExceptions) throw ex;


            }


        }




        /// <summary>


        /// Renders the contents of the control to the specified writer. This method is used primarily by control developers.


        /// </summary>


        /// <param name="writer">A <see cref="T:System.Web.UI.HtmlTextWriter"/> that represents the output stream to render HTML content on the client.</param>


        protected override void RenderContents(System.Web.UI.HtmlTextWriter writer)


        {


            try


            {


                base.RenderContents(writer);


            }


            catch (Exception ex)


            {


                Exceptions += "RenderContents_Exception: " + ex.Message;


                if ((AlwaysBubbleUpExceptions)) throw;


            }


            finally


            {


                if (!string.IsNullOrEmpty(Exceptions)) writer.WriteLine(Exceptions);


            }


        }


    } 


}





  • Modify the web.config file for the SharePoint site that you’re using the control on.  In the SafeControls section, add the following line.  Be sure to change the name, version, public key token, and namespace according to your build.









<SafeControl Assembly="HCS.WebParts.SmartPart, Version=1.0.0.0, Culture=neutral, PublicKeyToken=6596396d971b91e1" Namespace="HCS.WebParts" TypeName="*" Safe="True" />





  • Create the following XML file as SmartPart.webpart and upload it to the site web part gallery for the SharePoint site you’re using the control on.  Be sure to change the name, version, and public key token according to your build.









<?xml version="1.0" encoding="utf-8" ?>


<webParts>


  <webPart xmlns="http://schemas.microsoft.com/WebPart/v3">


    <metaData>


      <type name="HCS.WebParts.SmartPart, HCS.WebParts.SmartPart, Version=1.0.0.0, Culture=neutral, PublicKeyToken=6596396d971b91e1" />


      <importErrorMessage>Cannot import this Web Part.</importErrorMessage>


    </metaData>


    <data>


      <properties>


        <property name="Title" type="string">HuntCoServices Smart Part</property>


        <property name="Description" type="string">Wraps a web user control</property>


        <property name="ChromeType">TitleOnly</property>


        <property name="ChromeState">Normal</property>


        <property name="ItemLimit" type="int">15</property>


        <property name="ItemStyle" type="string">Default</property>


      </properties>


    </data>


  </webPart>


</webParts>




You now have the foundation laid on the SharePoint box.  You can verify this by adding the web part to a page.  Note that the web part will be named and sorted in the web parts list according to the Title property in the SmartPart.webpart XML file (above).  Bear in mind that the wrapper web part will only display a message saying you haven’t specified a path to a user control – but it will be working nonetheless.  By the way, the path to the user control is specified in the web part properties under Miscellaneous.



For each custom user control that you want to load via the SmartPart wrapper, you’ll need to




  • install the dll for that project in the GAC on the SharePoint box,


  • copy the .ascx files (just the .ascx files) to a folder within the SharePoint application directory (i.e. CustomControls),


  • and you’ll need to make two modifications to the web.config file of the SharePoint site.  As with the SmartPart installation, you’ll need to add a line to the SafeControls section, and you’ll need to add a similar line to the assemblies section under compilation:









<SafeControls>


    <SafeControl Assembly="HCS.WebParts.SharePointWebParts, Version=1.0.0.0, Culture=neutral, PublicKeyToken=6596396d971b91e1" Namespace="HCS.WebParts" TypeName="*" Safe="True" />


<SafeControls>


<compilation>


    <assemblies>


        <add assembly="HCS.WebParts.SharePointWebParts, Version=1.0.0.0, Culture=neutral, PublicKeyToken=6596396d971b91e1" />


    </assemblies>


</compilation>




Whenever you make a change to the GAC on the SharePoint box, you’ll want to issue an iisreset (or restart IIS manually from the admin console).



To configure the SmartPart wrapper to point to one of your controls, edit it’s properties on the SharePoint page.  Under Miscellaneous, you’ll see WebUserControlPath.  In that box, enter /CustomControls/MyASCX.ascx, where CustomControls is the path to the folder where your .ascx files are stored.  Be sure to include the full path from the web application root (where web.config is located).



A couple of points to bear in mind.  First, if you need access to any of the SharePoint framework, you’ll want to take the Hard Route and build a full-fledged SharePoint web part.  Second, if you need access to any of the lists in the site (or others), you’ve got two choices:  the SharePoint.dll route or SharePoint Web Services.  If the lists reside in a site that’s on the same SharePoint box as your code, you can go the SharePoint.dll route.  If they reside on a different SharePoint box, SharePoint Web Services are the way to go.

SQL vs. SharePoint

I've heard this many times: SharePoint sucks; SQL server rules. There's even an article about it in Visual Studio Magazine (http://visualstudiomagazine.com/listings/list.aspx?id=632).

Typically, those kinds of statements are made by SQL "enthusiasts" (zealots?). To quote from the article:

"SharePoint is very successful because you're removing levels of impedance, and a lot of the DBAs hate SharePoint for those same reasons," says Demsak. "Basically, you're storing everything in a BLOB and you can't relate to object relational mapping, and you can't do good entity relationships, you can't do relational models because there's all sorts of problems when people try to extend that SharePoint model past where it's supposed to go."

Mr. Demsak, I disagree.

Does everything belong in SharePoint? No. SharePoint isn't a high-volume transaction processing system. And there are other things SharePoint isn't. But the ideas that SharePoint stores everything in a BLOB and can't handle entity relationships is simply not accurate.

First, SharePoint does not store everything in a BLOB. SharePoint has a schema for everything it stores. That schema and data may coincidentally be stored in a BLOBish format at the SQL server level, but that's absolutely irrelevant. Who gives a dead rat's rear end HOW SharePoint persists data??? When we're dealing with SharePoint, we don't deal with SQL server. That's the beauty of SharePoint (one of them, anyway). When we deal with SharePoint, we deal with objects, not databases. Remember all those opinings of early OO programmers? "When are we going to get a good database system for persisting objects outside of an RDBMS?" SharePoint is your answer. And that's a good thing.

Second, SharePoint OOTB can handle the parent-child type relationships you'd typically express in a multi-tabled RDBMS through site columns and multi-valued fields. What it would take you five tables, five primary keys, and four foreign keys in SQL server, I can do in one list in SharePoint. (For flexibility, consistency and extensibility, I wouldn't recommend doing it in one list - but you can.) And it has bult-in data entry screens, views and filters. And it has built-in data integrity rules. And it's searchable right off the bat. And users can subscribe to alerts on my list. And people can add it to their RSS feeds. And...and...and...all right OOTB.

As for referential integrity and joining tables, Mr. Demsak is slightly correct. SharePoint doesn't do an outstanding job of enforcing cascading referential integrity contstraint changes OOTB. In other words, if you remove an item from a choice or lookup field in SharePoint, SharePoint isn't going to cascade an enforcement of that change to all the items in the list - so you may wind up with some list items referencing a choice or lookup item that no longer exists. But just because SharePoint doesn't have this feature OOTB doesn't mean it SharePoint can't do it.

So how do you get SharePoint to enforce cascading referential integrity constraint changes? Create a custom field type that enforces referrential integrity. Heck, CodePlex may already have one.

SharePoint also doesn't have a good mechanism in the web-based GUI for joining two lists together and viewing the resulting data set. But there are options. I generally dislike Access, but Access has found a new niche with SharePoint. Access can link to SharePoint lists - and it understands the foreign key references. Hence, Access is a great mechanism for joining related lists in SharePoint and generating views and reports on the resultant data set.

I'm the first guy to say SharePoint isn't for everything. But SharePoint is an incredibly powerful application development platform. Learn to leverage it.