Pages

Friday, March 9, 2012

Managing a single codebase for both SharePoint 2007 and 2010

Sometimes we need to work on source code that needs to be maintained for both SharePoint 2007 and 2010. There’s no built-in support from Microsoft to manage two versions of same Visual Studio solution for multiple versions of SharePoint. The problem is more complicated as the SharePoint assembly details are hard-coded in markup (ascx, aspx).

Anyway, I worked on a project where my team was developing a SharePoint custom product and some of our clients are still using SharePoint 2007 while some others are already moved to SharePoint 2010. So we had a challenge to support all clients (both SharePoint 2007 and 2010) but maintaining a single codebase. I am not demanding that the solution I’ll provide in this post is the best but the approach might be helpful to minimize code duplication. The sample Visual studio project can be download from skydrive.

 

Introduction

I’ve used Model View Presenter (MVP) to separate the presenter logic from view. Also, code that not dependent on SharePoint assembly directly are put in presenter layer. If for any reason, presenter is dependent on SharePoint assembly (for example SPGridView is used in code-behind) then the SharePoint assembly dependent code is implemented in individual (2007 or 2010) SharePoint project where common non-SharePoint presenter logic is kept in Presenter layer. For explaining the solution, I would like to introduce you to the Visual Studio projects (can be downloaded from here)I’ve been used.

  • Sohel.Blogging.Common: This is common project where interfaces, data contracts, common utilities etc are declared.
  • Sohel.Blogging.Presenter: This is the presenter layer where all non-SharePoint related presenter logic is kept. Since there’s no SharePoint-dependent logic is places, no reference to SharePoint dll is needed. As a result this project (or more specifically, presenter logic) can be reused by both SharePoint 2007 and 2010.
  • Sohel.Blogging.Wss: This is SharePoint 2007 (wss) project from where we’ll generate WSP file. Only SharePoint 2007 project structure, Views (aspx, ascx etc.) and Site artifacts (site definition, columns etc.) are placed in this project. As code-behind is replaced by presenter (which resides in Presenter layer), only views are need to duplicated between SharePoint 2007 and 2010.
  • Sohel.Blogging.Foundation: This is SharePoint 2010 project from which we’ll generate WSP solution file. Only views (aspx, ascx), site structure and site artifacts (site definitions, site columns etc.) are maintained in this project

To plug presenter/view to each other, Dependency Injection is used and for that purpose, Microsoft Patterns & Practices SharePoint Guidance is used. The Guidance Library for both SharePoint 2007 and 2010 can be downloaded from CodePlex (http://spg.codeplex.com/). For more information on how to use the Guidance Library for Dependency Injection please follow another post SharePoint Service Locator in my blog.

The overall architecture of the Visual Studio solution is presented below:

image

Figure 1: Visual Studio Project Dependency

The overall goal of the architecture is to move aspx/ascx code behind to presenter layer. As a result the code-behind logic is reused in two different views (SharePoint 2007 and 2010).

 

Views

The view (aspx, ascx) files are slimmed by removing code to presenter. View files (aspx,ascx etc) are placed in Sohel.Blogging.Foundation or Sohel.BLogging.Wss project. An interface is extracted from view to make Controls (like TextBox, Dropdown etc) available to Presenter. This is done by exposing View’s controls as the interface properties of the view. Though this is not the real Model View Presenter (MVP) but it works and faster to develop. The only problem is that the view interface/presenter is tightly coupled to asp.net controls.

public interface IProductListview
{
    Panel CreateListPanel { get; }
    Panel AddItemListPanel { get; }
    HtmlGenericControl MessageDiv { get; }
    HyperLink ViewAllProductLink { get; }
    Product GetProduct();
    string GetProductListUrl();
}

However if you use SharePoint controls (like SPGridView), then instead of exposing them in View interface, keep the logic related to the control in View and use a method in view. For example if you use SharePoint DateTime control, a method in view/code-behind can be used to put the logic related to SharePoint control. The following view interface declares SetDate method that set the date value of SharePoint DateTime control.

public interface IProductListview
{
    //Panel CreateListPanel { get; }
    //Panel AddItemListPanel { get; }
    //HtmlGenericControl MessageDiv { get; }
    //HyperLink ViewAllProductLink { get; }
    //Product GetProduct();
    //string GetProductListUrl();
    void SetDate(DateTime dateTime);
}

The view (say UserControl) below implement the SetDate method.

public partial class ProductList : UserControl, IProductListview
{
    public void SetDate(DateTime dateTime)
    {
        uiSpDateTimePicker.SelectedDate = dateTime;
    }
..............
..............
}

 

Presenter

All Presenters are kept in Sohel.Blogging.Prensenter project. Presenter access View controls through view interface. As I’ve mentioned already, this is not the right for MVP pattern to use web control directly inside view interface. SharePoint Service Locator is used as shown below:

public class CurrentSharePointServiceLocator : IBloggingServiceLocator
{
    public T ResolveInstance<T>()
    {
        return SharePointServiceLocator.Current.GetInstance<T>();
    }
}

The interface to the service locator (IBloggingServiceLocator) is passed to the Presenter constructor inside view, as shown below:

public partial class ProductList : UserControl, IProductListview
{
    private ProductListPresenter _presenter;
    public ProductListPresenter Presenter
    {
        get
        {
            if (_presenter == null)
            {
                _presenter = new ProductListPresenter(this, CurrentSharePointServiceLocator.GetInstance<IBloggingServiceLocator>());
            }
            return _presenter;
        }
    }
.....
.....
}

As presenter get access to the IBloggingServiceLocator, presenter can access any object that is registered through service locator. The presenter gets access to the view and service locator through constructor as shown below:

public class ProductListPresenter:APresenter
{
    public IProductListview View { get; set; }

    public ProductListPresenter(IProductListview view,IBloggingServiceLocator serviceLocator):base(serviceLocator)
    {
        View = view;
    }
.....
.....
}

 

New development Consideration

Once the project architecture is in place and if you need to develop new control, application pages etc, you should develop new control for any version of SharePoint first. For example if I need to develop a new application page, I would go and develop the page for SharePoint 2010. Then I would refactor the code to move the code-behind logic to Presenter class. Then I’ll create a new page in SharePoint 2007 copying the similar file from SharePoint 2010 (but surely with modifications). Now while you’ll create new page for SharePoint 2007, you may fine more refactoring needed to generalize code more for both SharePoint 2007 and 2010. The only duplication needs to be made in this approach is resources like markup/xml/css/jss etc. need to be duplication. But I think its possible to minimize the duplication by putting most of the resources in separate web project and on post-build event, you can copy the resources in individual SharePoint projects.

 

Conclusion

As more and more clients are adopting SharePoint and more and more Independent Software Vendor (ISV) are developing custom SharePoint products, ISVs need to support more than one version of their product. Since Microsoft doesn’t provide any out-of-the box solution for this multiple-version support for Visual Studio Solution, it takes extra efforts for developers to use the same codebase for different version of SharePoint and increase the possible of code duplication which in turn make maintenance difficult. You can download the sample code from skydrive.

Disclaimer: The solution provided in this post is not the best solution but a workaround. If anybody has any better idea, you are welcome to share.