Pages

Saturday, June 18, 2011

A better InputFormSection for SharePoint

One of my biggest annoyances with developing solutions SharePoint 2007 is the fact that many of the web controls available in the SharePoint framework do not support the same development paradigm that traditional ASP.Net controls support. Specifically, many controls in the Microsoft.SharePoint.WebControls namespace do not support two way data binding. The best example of this is the InputFormSection control. Like many ASP.Net Templated controls, the InputFormSection control allows you to encapsulate items of your choosing in regions within the control. The control takes care of the layout and positioning of your controls, and reduces the need for redundant declarative markup.




While the control works great with SharePoint Lists as a data source, it breaks down if you are trying to implement a custom application page with a traditional ASP.Net data source. With most ASP.Net Templated controls, the templates support two way data binding, but to my dismay, the SharePoint InpurFormSection does not. Because of this, I've seen numerous example across the web where bad practices are being introduced to make controls (and their values) accessible in code, when they could have simply used the ASP.Net data binding concepts to solve their problem.

Let's jump into an example. Let's say you are developing a custom application page for SharePoint, and you are using the OOTB InputFormSection control. You create your aspx page, add the appropriate declarative markup, and end up with a page that looks like this:




You will find that you are not able to access the "Product_Name" value when it comes time to Insert or Update the value. The method that I see most often discussed on the web to remedy this situation is to override the Inserting or Updating events on the FormView control to manually set the value for the specified fields.



This is a bad practice mainly because it should not be necessary. The controls inside the InputFormSection should be able to data bind to the FormView just as they do in any other situation, but they can't because the InputFormSection is acting as a road block. To make things worse, because two way data binding does not work, you will find that on postback, controls that were accessible during page load are now null.

Why is this? If we open up Reflector, or my new favorite tool from JetBrains, dotPeek, we can see that the InputFormSection controls implements properties of Type ITemplate, not IBindableTemplate, and it does not implement IBindableControl, which is needed to support two-way data binding.



To remedy this situation, a colleague of mine (who prefers to remain anonymous), and I, set out to create a new InputFormSection control that works like the original SharePoint control, but supports two way data binding.

We opened the existing SharePoint InputFormSection control and copied out the declarative markup. We simplified some of the markup, and removed some of the SharePoint presentation features like collapsible sections, but they could be added back in if you want. Then, we created a class that implemented IBindableControl. The IBindableControl interface requires that you have a method named ExtractValues. This is how the parent control container can get the data bound values out of the template regions. In addition, the Type of template container used in the control is of Type IBindableTemplate, instead of ITemplate. Finally, in the OnInit event, we instantiate our bindable templates inside our controls content place holders.

The declarative markup:

<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="InputFormSectionControl.ascx.cs" Inherits="TCSC.SharePoint.Web.Controls.InputFormSectionControl, TCSC.SharePoint.Web, Version=1.0.0.0, Culture=neutral, PublicKeyToken=c7992b8124f5b366" %>


The code behind:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Collections;
using System.Collections.Specialized;

namespace TCSC.SharePoint.Web.Controls
{
    [ParseChildren(true)]
    public partial class InputFormSectionControl : System.Web.UI.UserControl, IBindableControl
    {
        private IBindableTemplate _leftSection;

        private IBindableTemplate _rightSection;

        public string Name { get; set; }

        public string Description { get; set; }

        [PersistenceMode(PersistenceMode.InnerProperty),
        TemplateContainer(typeof(IDataItemContainer),
        System.ComponentModel.BindingDirection.TwoWay)]
        public IBindableTemplate LeftSection
        {
            get
            {
                return _leftSection;
            }
            set
            {
                _leftSection = value;
            }
        }

        [PersistenceMode(PersistenceMode.InnerProperty),
        TemplateContainer(typeof(IDataItemContainer),
        System.ComponentModel.BindingDirection.TwoWay)]
        public IBindableTemplate RightSection
        {
            get
            {
                return _rightSection;
            }
            set
            {
                _rightSection = value;
            }
        }

        public void ExtractValues(IOrderedDictionary dictionary)
        {
            this.CopyExtractValues(dictionary, this._leftSection.ExtractValues(this.placeHolderLeftSection));

            this.CopyExtractValues(dictionary, this._rightSection.ExtractValues(this.placeHolderRightSection));
        }

        private void CopyExtractValues(IOrderedDictionary dictionaryContainer, IOrderedDictionary dictionaryTemplate)
        {
            foreach (string s in dictionaryTemplate.Keys)
            {
                if (dictionaryContainer.Contains(s))
                    dictionaryContainer[s] = dictionaryTemplate[s];
                else
                    dictionaryContainer.Add(s, dictionaryTemplate[s]);
            }
        }

        protected override void OnInit(EventArgs e)
        {
            base.OnInit(e);

            if (this._leftSection != null)
                _leftSection.InstantiateIn(this.placeHolderLeftSection);

            if (this._rightSection != null)
                this._rightSection.InstantiateIn(this.placeHolderRightSection);

            this.labelName.Text = this.Name;
            this.labelDescription.Text = this.Description;
        }
    }
}


The declarative markup when using the control on a page looks like this:


Task Description
<%# Bind(“Product_Name”)%>


The result is a control that looks like the SharePoint InpurFormControl, but allows us to use traditional ASP.Net two way data binding! There is no need to override the FormView Inserting or Updating events to programmatically set values.

No comments:

Post a Comment