Pages

Monday, June 6, 2011

Using a base web part class when developing SharePoint web parts

When developing solutions for SharePoint 2007, I use WSPBuilder almost exclusively. Carsten Keutmann has created an excellent tool that automates and eases much of the work that is involved in creating a solution for SharePoint. In fact, in some ways, WSPBuilder makes things too easy. When creating a project using the out of the box WSPBuilder project and item templates, it is easy to fall into some bad practices.

One of these practices that I see far too often involves the use of the OOTB Web Part Feature Template. If you add a new Web Part Feature to your project using the WSPBuilder Item Template, WSPBuilder will automatically add a Template folder, a Features folder, and then a folder of the same name as your web part. Within your web part feature folder, four files will automatically be added. An elements.xml file, a feature.xml file, a .webpart file, and your web part code behind .cs file.















If you continue to add new web parts using the Web Part Feature item template, you get more web parts placed in folders in your Features folder. These web parts will have the same four files, elements.xml, feaeture.xml, the .webpart file and the .cs file.







While this process is amazingly fast and convenient, you run into the problem that every web part code behind file has redundant and repetitive code. It also encourages new developers who are not as familiar with SharePoint development, and even ASP.Net web parts for that matter, to stick much of the control logic for the item they are developing into this web part code behind file. Web parts in general should act as containers for the items they are presenting. They should be responsible for the layout and display of the item, but really should not have anything to do with the functionality of the item.

A better practice is to use a base web part class that your other web part classes inherit from. In addition, the web parts themselves, should load standard ASP.Net user controls, so that there is a distinct separation between the control functionality, and the web part that is used to display the control.

To get started implementing this model, we will need to create a standard web part base class. This is easy because WSP Builder has already created a web part file that we can use. Just copy any one of the default web part code behind files and rename it BaseWebPart.cs. I have copied mine into another folder in the solution named "SharedCode". Rename the class name to BaseWebPart.













Add an abstract method to the class named LoadControl that return a Type of Control. Finally, modify the CreateChildControls method so that you create a control object from the LoadControl method, and if the control is not null, add it to the web parts Controls collection.

using System;
using System.Web.UI;
using System.Web.UI.WebControls.WebParts;

namespace WebPartTemplate.SharedCode
{
    public abstract class BaseWebPart : Microsoft.SharePoint.WebPartPages.WebPart
    {
        #region Fields

        protected bool _error;

        #endregion

        #region Constructors

        public BaseWebPart()
        {
            ExportMode = WebPartExportMode.All;
        }

        #endregion

        #region Methods 

        // This abstract method must be implemented by all
        // child classes. The LoadContorl method should
        // load the user control that is contained in the web part.
        protected abstract Control LoadControl();

        // Create all your controls here for rendering.
        // Try to avoid using the RenderWebPart() method.
        protected override void CreateChildControls()
        {
            if (!_error)
            {
                try
                {
                    base.CreateChildControls();

                    Control control = LoadControl();

                    if (control != null)
                        Controls.Add(control);
                }
                catch (Exception ex)
                {
                    HandleException(ex);
                }
            }
        }

        // Clear all child controls and add an error message for display.
        protected void HandleException(Exception ex)    
        {    
            // If you have a separate class to handle     
            // error handling events or display, you     
            // can capture them here.    
            // for example....    
            // ErrorHandler.HandleException(ex);     
            _error = true;   
            Controls.Clear();    
            Controls.Add(new LiteralControl(ex.Message));    
        }      

        #endregion       
    
        #region Event Handlers
       
        // Ensures that the CreateChildControls() is called before events.    
        // Use CreateChildControls() to create your controls.    
        protected override void OnLoad(EventArgs e)    
        {    
            if (!_error)    
            {    
                try    
                {    
                    base.OnLoad(e);    
                    EnsureChildControls();    
                }    
                catch (Exception ex)    
                {    
                    HandleException(ex);    
                }    
            }    
        }   
  
        #endregion     
    }    
}


Once we have created our BaseWebPart class we will need to update the web part files that were automatically created by WSPBuilder to use it. Open up any one of the web part .cs files and delete everything except for the namespace and class definition. Next, Change the class that the web part inherits from. Instead of inheriting from Microsoft.SharePoint.WebPartPages.WebPart, inherit from the BaseWebPart class that you just created. Finally, you will need to implement the LoadControl method of the base class for your web part to function correctly. This will actually be quite simple because well will create a standard ASP.Net user control to be loaded by the Page.LoadControl method.

using System;
using System.Runtime.InteropServices;
using System.Web.UI;
using WebPartTemplate.SharedCode;

namespace WebPartTemplate
{
    // This is a basic webpart class. It does not need to
    // override any base class methods, or handle any shared properties.
    [Guid("be4d35bf-f45c-4c5f-a279-275c0d0c5dbd")]
    public class WebPartOne : BaseWebPart
    {
        #region Methods

        protected override Control LoadControl()
        {
            return Page.LoadControl(Constants.USER_CONTROL_PHYSICAL_PATH + "UserControlOne.ascx");
        }

        #endregion
    }
}


I've created a Constants class to store the path to the physical location of the user controls in the SharePoint 12 hive. It looks like this:

namespace WebPartTemplate.SharedCode
{
    public class Constants
    {
        public const string USER_CONTROL_PHYSICAL_PATH = "~/_CONTROLTEMPLATES/TCSC/";
    }
}


SharePoint looks for user controls in the CONTROLTEMPLATES folder. It is good practice to add you own sub directory to avoid any conflicts with existing or third party controls. Finally, we just need to create a user control that will be loaded by the web part. In your project hierarchy, beneath the Templates folder, create a new folder named "CONTROLTEMPLATES". Create a sub directory that is appropriate for your project, in this case my sub directory is named "TCSC". Once your directory structure is created, add a user control using the "Add New Item" dialog and name it UserControlOne.ascx (or whatever you may have specified in your web part code behind file).

Now, this user control will be loaded when ever your web part is added to a SharePoint web part page. All of the methods in the BaseWebPart class can be overridden so that you can implement your own behavior in your individual web part classes as needed.

using System;
using System.Runtime.InteropServices;
using System.Web.UI;
using WebPartTemplate.SharedCode;

namespace WebPartTemplate
{
    // The purpse of this class is to demonstrate that it is 
    // still possible to override any methods in the base webpart if
    // you need additional logic that is not defined in the base.
    [Guid("6419bdfd-f6a8-47bf-9d43-262cbbde25b4")]
    public class WebPartTwo : BaseWebPart
    {
        #region Methods

        protected override Control LoadControl()
        {
            return Page.LoadControl(Constants.USER_CONTROL_PHYSICAL_PATH + "UserControlTwo.ascx");
        }

        #endregion

        #region Event Handlers

        // You can still override any of the methods
        // or event handlers defined in the BaseWebPart.
        protected override void OnLoad(EventArgs e)
        {
            try
            {
                // you do not have to call the base.OnLoad event
                // if you intend to completely override the webparts behavior.
                base.OnLoad(e);

                // In this example, the webpart will be hidden if the 
                // day is Saturday or Sunday.
                if (DateTime.Now.DayOfWeek == DayOfWeek.Saturday || DateTime.Now.DayOfWeek == DayOfWeek.Sunday)
                {
                    Visible = false;
                }
            }
            catch (Exception ex)
            {
                HandleException(ex);
            }
        }

        #endregion
    }
}


Also, if you need to add shared properties to your web part to implement custom behavior, add the properties to your web part class, and then add those same properties to the user control that the web part loads. In the LoadControl Method, assign the web part properties to the control properties. The web part will then in essence act as messenger for the configuration values need by the user control.

using System;
using System.Runtime.InteropServices;
using System.Web.UI;
using System.Web.UI.WebControls.WebParts;
using WebPartTemplate.SharedCode;
using WebPartTemplate.UserControlCode;

namespace WebPartTemplate
{
    // The purpose of this class is to demonstrate that using
    // a base class will still support passing webpart shared property
    // values to a user control that has the same exposed properties.
    // This limits the webpart to simply acting as an interface for any
    // configuration values, and not actually implementing any logic for 
    // those values.
    [Guid("394bd5f8-c2af-427c-b086-8615c6dda1b4")]
    public class WebPartThree : BaseWebPart
    {
        #region Fields

        private string _name;

        #endregion

        #region Properties

        [Personalizable(PersonalizationScope.Shared)]
        [WebBrowsable(true)]
        [System.ComponentModel.Category("WebPartThree Properties")]
        [WebDisplayName("Name")]
        [WebDescription("The name that is displayed on the user control.")]
        public string Name
        {
            get
            {
                if (String.IsNullOrEmpty(_name))
                    _name = "Hello SharePoint";

                return _name;
            }
            set
            {
                _name = value;
            }
        }

        #endregion

        #region Methods

        protected override Control LoadControl()
        {
            //Instantiate the control
            UserControlThree newControl = (UserControlThree)Page.LoadControl(Constants.USER_CONTROL_PHYSICAL_PATH + "UserControlThree.ascx");

            // Assign the webpart property value to the 
            // usercontrol property value.
            newControl.Name = Name;

            // return the control object to the CreateChildControls method.
            return newControl;
        }

        #endregion
    }
}


You now have a WSPBuilder project that promotes best practices, limits code duplication, and allows you to take advantage of the Visual Studio designer when creating web parts.

You can take this idea even further by creating a single Feature.xml and elements.xml file for your project, but that is a post for another time.

No comments:

Post a Comment