SharePoint Events

  9/03 - Webcast: SharePoint 2013 vs. SharePoint Online: Workflow
  9/04 - Webcast: Power Business Intelligence for SharePoint Online and Office 365
  9/05 - Webcast: SharePoint 2013 vs. SharePoint Online: Search
  9/30 - Webcast: Project Management Workflow in SharePoint
  10/01 - Webcast: SharePoint 2013 vs. SharePoint Online: Enterprise Content Management
  10/02 - Webcast: SharePoint 2013 vs. SharePoint Online: Business Intelligence

 SharePoint Videos

  Why SharePoint 2013
  Keys to Successful SharePoint Initiatives and User Adoption
  Out of the Box Business Intelligence and Reporting in SharePoint 2013
  Driving the Business Case and User Adoption for SharePoint
  Automate Business Processes with SharePoint 2013 and Business Connectivity Services
  SharePoint and Office 365 Migration Made Easy
  Full Lifecycle Content Management with SharePoint 2013
  Compliant Document Generation and Assembly in SharePoint
  Application of Content Security and Corporate Risk Protection in SharePoint
  How to Redline, Markup, Collaborate and Review Content in SharePoint
  Advanced Content Lifecycle and Records Management in SharePoint
  Advanced Workflow Made Easy with SharePoint 2013 and Nintex
  Full Lifecycle Content Management with SharePoint 2013
  SharePoint 2013 Solutions for the Financial Services Industry
  ProjectReady 2013: SharePoint Solutions built for the AEC
  SharePoint 2013 Solutions for the Legal Industry
  Driving the Business Case and User Adoption for SharePoint
  Investment Considerations for SharePoint 2010 and SharePoint 2013
  SharePoint 2013 and Enterprise Content Management
  SharePoint 2013 Launch
(More Links...)

 Archives

Opening SharePoint Links in a new windowUse SHIFT+ENTER to open the menu (new window).
Mail Enabled Lists vs. The Missing Windows 2008 POP3/IMAP Server Use SHIFT+ENTER to open the menu (new window).
7 Tools for SharePoint DevelopersUse SHIFT+ENTER to open the menu (new window).
Public Facing Masterpage TechniquesUse SHIFT+ENTER to open the menu (new window).
How to Quickly Deploy and Activate a Timer Service to Your Site CollectionUse SHIFT+ENTER to open the menu (new window).
Custom SharePoint Master Page Feature with WSP BuilderUse SHIFT+ENTER to open the menu (new window).
Date Math with InfoPathUse SHIFT+ENTER to open the menu (new window).
Enterprise Search Tricks and Tips Part 1Use SHIFT+ENTER to open the menu (new window).
Populating Word Documents With SharePoint Data. Try The DIP!Use SHIFT+ENTER to open the menu (new window).
Programmatic Deep Dive into Blank SharePoint Lookup ColumnsUse SHIFT+ENTER to open the menu (new window).
1 - 10 Next
Advanced Configuration Lists in SharePoint

By: Robert Christ

I’ve noticed recently that what I believe to be the most important aspect of custom SharePoint development appears to be missing completely from the SharePoint development community.  I’m talking of course, about configuration lists.

By the end of this guide:

1)   Your client will now have the ability to manipulate their SharePoint site without worrying about breaking dependencies of your workflow, event receiver, or timer job.

2)   Your client will now have the ability to manipulate the workflow, event receiver or timer job at run time.

3)   Your client will have an improved view of the quality of your solution, due to the nature of the UI

4)   Your workflow, event receiver or timer job will automatically verify that the configuration list holds correct data, and load said data automatically in an object oriented fashion.

Usually, customers will require the ability to change the number and names of list columns, and list locations, without having to worry about potentially breaking a custom SharePoint solution.  For more complicated solutions, they will likely also require the ability to tweak solution performance at runtime via any one of a number of solution specific parameters.

Because workflows, timer jobs, and event receivers work “behind the scenes” in SharePoint, this requires that we create a user interface of some form to allow the user to easily manipulate these features.  This of course is a configuration list.

The_SharePoint_Blog_Advanced_Configuration_Lists_in_SharePoint

1 - A Typical SharePoint Configuration List          

For developers who have been working within the SharePoint platform for a while, this concept likely appears intuitive and obvious.  Above we see the column and list dependencies for a SharePoint workflow of some form, which the workflow will presumably read through at run time, like a typical configuration file, to learn where it should query for data.  The client is now free to make whatever changes they would like to their site, and provided they also change such information here, we can be guaranteed that the custom solution will work correctly.

The Problem

The problem here is that while a simple configuration list like this might be second nature for a developer, it often is NOT for a client.  Even worse is that a solution like this will become harder and harder to maintain as a project continues to scale in size.

The first, easiest tweak we can make is to organize the entries in the list in an alphabetical and pre-ordered format.  By adding Contacts List – etc to all of the column names in our example, this list becomes much more transparent. 

To add to that effect, we’ve also added a Description column, which will include a “no IT experience needed” explanation of what that parameter of the configuration list does.  While these two tweaks may seem innocuous, I personally guarantee that together, they can turn around your Client’s view of the solution overnight.

The_SharePoint_Blog_Advanced_Configuration_Lists_in_SharePoint

2 - A More Intuitive Configuration List

While it is always important to keep the number of items in this list to a minimum, if the custom solution does need to grow to meet project requirements, it is often a good idea to create a categories column.  This will allow filtering of the list, in a quicker manner.

The_SharePoint_Blog_Advanced_Configuration_Lists_in_SharePoint

3 - A Programmable Configuration List

While I will leave it to the comments section to suggest further possible improvements to this method, I will share one additional developer trick that becomes possible when creating a Configuration List in this form: Once we have organized the configuration items according to type, it becomes extremely easy to query the list for all of the items of a specific type.  This in turn allows us to easily pass these parameters into a configuration object within our code, and populate such an object automatically.

A Developer’s Tweak

For example, judging by this example configuration list, we might create a class called “Column Names,” to store all of the configuration listItem, column name data.

public class ColumnNamesMetadata

{

public string ContactsList_FirstNameColumnName { get; set; }

       public string ContactsList_LastNameColumnName { get; set; }

       public string ContactsList_CompanyNameColumnName { get; set; }

       public string ContactsList_BusinessPhoneColumnName { get; set; }

       public string ContactsList_HomePhoneColumnName { get; set; }

       public string ContactsList_EmailAddressColumnName { get; set; }

       

}

 

While this is a good start, if we were to use the above class, we would have to populate each of the above parameters manually within the code.  But with a little bit of System.Reflection magic, we can even make these parameters auto populate and self-verify.

 

public class ColumnNamesMetadata : MetadataBase

{

[ConfigurationItem("Contacts List - First Name Column Name")]

       public string ContactsList_FirstNameColumnName { get; set; }

       

       [ConfigurationItem("Contacts List - Last Name Column Name")]

       public string ContactsList_LastNameColumnName { get; set; }

       

       [ConfigurationItem("Contacts List - Company Name Column Name")]

       public string ContactsList_CompanyNameColumnName { get; set; }

       

       [ConfigurationItem("Contacts List - Business Phone Column Name")]

       public string ContactsList_BusinessPhoneColumnName { get; set; }

 

       [ConfigurationItem("Contacts List - Home Phone Column Name")]

       public string ContactsList_HomePhoneColumnName { get; set; }

 

       [ConfigurationItem("Contacts List - Email Address Column Name")]

       public string ContactsList_EmailAddressColumnName { get; set; }

 

       public ColumnNamesMetadata(SPWeb rootWeb)

       {

              base.PopulateMetadata<ColumnNamesMetadata>(SPWebOfConfigurationList,

this, Global.CONFIGURATION_LIST_COLUMN_NAME);

base.VerifyMetadata(this);

       }

}

 

public class MetadataBase

{

/// <summary>

       /// Populates the parameters of the class implementing MetadataHoder

       /// </summary>

       protected void PopulateMetadata<T>(SPWeb rootWeb,

object callingClassObject,

string Config_List_Config_Type)

{

            SPList configList = rootWeb.Lists[Global.CONFIGURATION_LIST_NAME];

            SPListItemCollection listNameConfigItems = ListItemHelper.GetListItemsByColumnValue(

configList,

"Configuration Type",

Config_List_Config_Type);

 

            foreach (SPListItem configItem in configItems)

            {

                string value = ListItemHelper.GetColumnValue(configItem, "Value");

 

                if (string.IsNullOrEmpty(value))

                    throw new Exception("The Value column for '" + configItem.Title + "' cannot be empty in the configuration list.");

 

                ReflectionHelper.SetPropertyValue<T>(callingClassObject, configItem.Title, value);

            }

        }

 

        /// <summary>

        /// Verifies the metadata values currently in the class.  For example, ensures that no

 /// necessary values are null or empty.

        /// If there are any empty properties, it throws an error listing them all.

        /// </summary>

        protected void VerifyMetadata(object callingClassObject)

        {

            List<string> emptyProperties = GetEmptyProperties(callingClassObject);

            if (emptyProperties.Count == 0)

                return;

 

string errorMessage = "The following properties were not read correctly from the

   configuration list. ";

 

            foreach (string emptyProperty in emptyProperties)

            {

                errorMessage += emptyProperty + ", ";

            }

 

            throw new ArgumentException(errorMessage);

        }

 

public static List<string> GetEmptyProperties(object o)

       {

            Type type = o.GetType();

            List<string> list = new List<string>();

 

            PropertyInfo[] properties = type.GetProperties();

 

            foreach (PropertyInfo propertyInfo in properties)

            {

                if (IsPropertyEmpty(o, propertyInfo))

                    list.Add(propertyInfo.Name);

            }

 

            return list;

        }

 

public static void SetPropertyValue<T>(object o, string configItemTitle,

string configItemValue)

        {

            PropertyInfo propertyInfo = FindPropertyByAtribute<T>(o, configItemTitle);

 

            if (propertyInfo == null)

                throw new Exception("Invalid property name '" + configItemTitle + "'.");

 

            string propertyInfoType = propertyInfo.PropertyType.ToString();

            switch (propertyInfoType)

            {

                case "System.String":

                    propertyInfo.SetValue(o, configItemValue, null);

                    break;

 

                case "System.Boolean":

                    bool convertedBool = false;

                    if (String.Compare(configItemValue, "True", true, CultureInfo.InvariantCulture) == 0)

                        convertedBool = true;

 

                    propertyInfo.SetValue(o, convertedBool, null);

                    break;

 

                case "System.Int32":

                    int convertedInt = Int32.Parse(configItemValue);

 

                    propertyInfo.SetValue(o, convertedInt, null);

                    break;

               

                default:

                    throw new Exception(string.Format("Cannot handle column '{0}' of type '{1}'",

propertyInfoType, propertyInfoType));

            }

        }

 

    }

 

class ConfigurationItemAttribute : Attribute

{

private string _configItemTitle;

      

       private ConfigurationItemAttribute() { }

 

       public ConfigurationItemAttribute(string configItemTitle)

       {

           _configItemTitle = configItemTitle;

       }

 

        public string ConfigItemTitle { get { return _configItemTitle; } }

}

 

With the above code, one should now simple be able to construct a ColumnNamesMetadataObject(SPWeb rootWeb), and each of the public parameters in ColumnNamesMetadata should hold the value specified in the configuration list.  If the object was unable to read a value in the configuration list, it will throw an error of every configuration list item that presented a problem.

If you’ve followed this guide, the following steps should be completed:

1)   Your client will now have the ability to manipulate their SharePoint site without worrying about breaking dependencies of your workflow, event receiver, or timer job.

2)   Your client will now have the ability to manipulate the workflow, event receiver or timer job at run time.

3)   Your client will have an improved view of the quality of your solution, due to the nature of the UI

4)   Your workflow will automatically verify that the configuration list holds correct data, and load said data automatically in an object oriented fashion.

 

Feel welcome to ask questions in the comments!

 

By: Robert Christ

        

Comments

Bil Simser

I've done this kind of thing on and off for most solutions but not to the extent here. Nice elegant solution and reusable too! Thanks.
at 8/5/2011 4:02 PM

Robert Christ

Thank you for the feedback Bill!
at 8/8/2011 10:23 AM

Add Comment

Items on this list require content approval. Your submission will not appear in public views until approved by someone with proper rights. More information on content approval.

Your Name *


e-mail address *


Website (optional)


Comment *


Attachments

 Subscribe

  GigWerks RSS  Gig Werks Mailing List 

 Contact Us

 Connect

 Resources

  On Demand SharePoint Webcast Recordings
  Upcoming Webinars
  SharePoint Resources
  Business Intelligence Resources
  Gig Werks Website



©2009 Gig Werks. All rights reserved. Privacy Policy