Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Making Use of the SharePoint Field Type Editing Controls

4.57/5 (7 votes)
18 Feb 2009CPOL4 min read 70.9K   24  
This article goes over how to use the SharePoint field rendering controls for other purposes than editing an item. Such as building a search criteria.

Image 1

Introduction

About a week ago, I was tasked with a project to allow our SharePoint users to build queries and search their lists based on specific criteria. As I was researching, I started to ponder on how I'm going to display input controls to users so that they could punch in their criteria for searching their lists. Now, I was very overwhelmed because I didn't want to have to build all these input controls for each column that they wanted to query. Why should I? The SharePoint engineers already did that with the edit controls they provide when you create a new list item. So I thought, "Hey, this might not be that bad, the engineers probably provided me a way to do use these controls - right?" Ummm... Kinda, after a little hacking. (Microsoft is probably going to hate me.) After going through all the pain of research and development, I've created an ASP.NET web control that will allow you to pass in a list and a field so that an edit control can be rendered to the page.

Background

Let's go ahead and take a look at where I started. I was reluctant to find any information out there on how to use these edit controls for what I wanted. Most people just wanted to replicate SharePoint's "Edit Properties" page so that they could override some of the controls. So, I was stuck, and couldn't find anything except MSDN's unhelpful description of the SPField.FieldRenderingControl property. This is the property that supplies the web control that is rendered to the page when a user edits a list item. So, I resorted to figuring out how this control works on my own using my handy tool Reflector which was built by RedGate. Here's a quick glimpse on how to get to this control:

C#
SPList list = SPContext.Current.Web.Lists["myList"];
SPField spField = list.Fields.GetFieldByInternalName("myFieldsInternalName");
BaseFieldControl bfc = spField.FieldRenderingControl;

If you go through the collection of fields, you'll find that all the fields are of different types. In other words, the SPField is a base class, and all your different SharePoint field types are just classes that derive from it. Such as SPFieldText, SPFieldLookup, SPFieldChoice, and so on. Each of these classes overrides the FieldRenderingControl property, and returns a different edit control that allows the user to modify the field value that is stored in the database. Each derived field type may also override methods like:

C#
object GetFieldValue(string value);
string GetValidatedString(object value);

This method is used to take a string and convert it into an object that can be modified by the BaseFieldControl that is overridden. For instance, the SPFieldNumber type that returns a double.

C#
public override object GetFieldValue(string value)
{
    if (!string.IsNullOrEmpty(value))
    {
        return Convert.ToDouble(value, CultureInfo.InvariantCulture);
    }
    return value;
}

To use this method, we simply do the following (our spField object is of type SPFieldNumber):

C#
string strDouble = "56.56";

object objDouble = spField.GetFieldValue(strDouble);
BaseFieldControl bfc = spField.FieldRenderingControl;
bfc.ListId = list.Id;
bfc.FieldName = spField.InternalName;
bfc.ItemId = spItem.Id;


if(!Page.IsPostBack && bfc != null)
{ 
    bfc.Value = objDouble;

}
this.Controls.Add(bfc);

And then, during our postback, we can get our string value back by doing the following:

C#
string strDouble = spField.GetValidatedString(bfc.Value);

If the GetValidatedString method isn't overridden by the derived control, then it will simply just do a ToString() against the object. This is the case for the SPFieldNumber.

Hopefully, I didn't mislead you because working with the BaseFieldControl is not as easy as it appears above. I cut out a lot of code to just simply show you how the BaseFieldControl ties in with the SPField.

The Problem

The biggest problem with the SharePoint engineers' design is that the BaseFieldControl is completely tied with a ListItem. In my case, we don't have an item, so what are we to do? The second problem is that you need AddItem permissions to the list to use these controls. So, how will we get around that so viewers can use the edit controls to build search criteria? They are not actually modifying an item, so why shouldn't they be allowed to query the list? The third problem is that the BaseFieldControl manipulates the visibility of the editing controls based on the properties of the SPField object. Such as Sealed, ReadOnly, Required, and Hidden. List fields like Created By may be hidden, but we still want users to be able to use the edit controls to create values for searching. The final problem is that some SPField types don't even provide a BaseFieldControl at all. These are fields like ID or Computed that don't need users to change their values. This is understandable, but it created an obstacle for me.

The Solution

The solution here uses a lot of Reflection to hack through the BaseFieldControl. I know this is probably going to rub a lot of people the wrong way, but it beats having to build all of these controls over again. This solution saved me a ton of time, and hopefully it may save you some too.

C#
using System;
using System.Collections.Generic;
using System.Text;
using System.Web.UI.WebControls;
using Microsoft.SharePoint.WebControls;
using Microsoft.SharePoint;
using System.Reflection;
using System.Web.UI;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using System.Runtime.Remoting.Messaging;
using System.Globalization;
namespace Suddenlink.SharePoint.WebControls
{
    public class SPFieldRenderer : Panel
    {
        private BaseFieldControl _bfc = null;
        private SPList _list = null;
        private SPField _spField;
        private TextBox _tbox = null;
        private string _value = null;
        private bool _isSearch = true;
        private Type _tList;
        private Type _tFieldMetaData = null;
        private Type _tSPField = null;
        private Type _tSPContext = null;
        private MethodInfo _miSetBoolValue = null;
        private MethodInfo _miSetFieldAttributeValue = null;
        private FieldInfo _fiSpField = null;
        private FieldInfo _fiContextItem;
        private FieldInfo _fiContextItemIdSet;
        private FieldInfo _fiListPermission;
        public SPFieldRenderer()
        {
            _tFieldMetaData = typeof(FieldMetadata);
            _fiSpField = _tFieldMetaData.GetField("m_fld", 
                         BindingFlags.Instance | BindingFlags.NonPublic);
            _tSPField = typeof(SPField);
            _tSPContext = typeof(SPContext);
            _miSetBoolValue = _tSPField.GetMethod("SetFieldBoolValue", 
                              BindingFlags.Instance | BindingFlags.NonPublic);
            _miSetFieldAttributeValue = _tSPField.GetMethod("SetFieldAttributeValue", 
                                        BindingFlags.Instance | BindingFlags.NonPublic);
            _fiContextItem = _tSPContext.GetField("m_item", 
                             BindingFlags.Instance | BindingFlags.NonPublic);
            _fiContextItemIdSet = _tSPContext.GetField("m_isItemIdSet", 
                                  BindingFlags.NonPublic | BindingFlags.Instance);
            _tList = typeof(SPList);
            _fiListPermission = _tList.GetField("m_EffectivePermMask", 
                                BindingFlags.Instance | BindingFlags.NonPublic);
        }
        public SPList List
        {
            get
            {
                return _list;
            }
            set
            {
                _list = value;
            }
        }
        public SPField Field
        {
            get
            {
                return _spField;
            }
            set
            {
                _spField = value;
            }
        }
        public string Value
        {
            get
            {
                if (_bfc != null)
                {
                    object value = _bfc.Value;
                    if (value != null)
                    {
                        return _spField.GetValidatedString(value);
                    }
                    else
                        return null;
                }
                else if (_tbox != null)
                    return _tbox.Text;
                else
                    return _value;
            }
            set
            {
                _value = value;
            }
        }
        public bool IsSearch
        {
            get
            {
                return _isSearch;
            }
            set
            {
                _isSearch = value;
            }
        }
        public bool IsValid
        {
            get
            {
                if (_bfc != null)
                    return _bfc.IsValid;
                else
                    return true;
            }
        }
        public void Validate()
        {
            if (_bfc != null)
                _bfc.Validate();
        }
        protected override void OnInit(EventArgs e)
        {
            base.OnLoad(e);
            SPField spField = null;
            using (SPWeb web = _list.ParentWeb)
            {
                SPContext renderContext = SPContext.GetContext(this.Context, 0, _list.ID, web);
                //We need to reflect and impersonate an item so that the edit controls
                //will work properly. I hate how the engineers designed these edit controls
                //to be dependent on an actual item. 
                SPItem impersonateItem = _list.Items.Add();
                _fiContextItem.SetValue(renderContext, impersonateItem);
                _fiContextItemIdSet.SetValue(renderContext, true);
                SPList currList = renderContext.List;
                if (!currList.DoesUserHavePermissions(SPBasePermissions.AddListItems))
                {                
                    //We need to fake out the rendering control and make it think
                    //it has rights to the impersonating item
                    SPBasePermissions perms = 
                      (SPBasePermissions)_fiListPermission.GetValue(currList);
                    perms |= SPBasePermissions.AddListItems;
                    _fiListPermission.SetValue(currList, perms);
                }
                spField = GetField();
                //Changing the SPField is OK as long as we don't update it
                //SO NEVER ADD the line that updates this field!!!
                //Set our field properties so that they display properly
                _miSetBoolValue.Invoke(spField, new object[2] { "Sealed", false });
                _miSetBoolValue.Invoke(spField, new object[2] { "Hidden", false });
                _miSetBoolValue.Invoke(spField, new object[2] { "Required", false });
                _miSetBoolValue.Invoke(spField, new object[2] { "ReadOnly", false });
                spField.ShowInEditForm = true; 
                //No Editing control
                if (spField.Type != SPFieldType.Integer &&
                    //No Editing control
                    spField.Type != SPFieldType.File &&
                    //No Editing control
                    spField.Type != SPFieldType.Computed &&
                    //No Editing control
                    spField.Type != SPFieldType.Calculated &&
                    //No Editing control
                    spField.Type != SPFieldType.Attachments &&
                    //Can't find a work around for BuiltIn sealed feilds
                    !spField.Id.Equals(SPBuiltInFieldId._CopySource) &&
                    //This control has a bug that I can't figure out how to fix 
                    !spField.Id.Equals(SPBuiltInFieldId._ModerationStatus))
                {
                    _bfc = spField.FieldRenderingControl;
                }
                if (_bfc != null)
                {
                    _bfc.ID = this.ID + "spRenderControl";
                    _bfc.FieldName = spField.InternalName;
                    //Reflect and set our SPField specifically so that our changes above
                    //can be used.
                    _fiSpField.SetValue(_bfc, spField);
                    _bfc.ControlMode = SPControlMode.New;
                    _bfc.RenderContext = renderContext;
                    _bfc.ItemContext = renderContext;
                    this.Controls.Add(_bfc);
                    _bfc.Visible = true;
                    SetValue();
                }
                else
                {
                    _tbox = new TextBox();
                    _tbox.TextMode = TextBoxMode.MultiLine;
                    _tbox.ID = this.ID + "_tbox";
                    _tbox.Attributes["style"] = "height:50px;width:300px";
                    if (!Page.IsPostBack)
                    {
                        if (string.IsNullOrEmpty(_value))
                            _value = spField.DefaultValue;
                        if (string.IsNullOrEmpty(_value))
                            _value = string.Empty;
                        _tbox.Text = _value;
                    }
                    this.Controls.Add(_tbox);
                }
            }
        }
        private SPField GetField()
        {
            SPField spField = _spField;
            //If in search and the field is multichoice then we want to
            //convert the field to a choice field because searching a list
            //on multiple fields doesn't work
            if (_isSearch)
            {
                Type fieldType = spField.GetType();
                if (fieldType == typeof(SPFieldMultiChoice))
                {
                    SPFieldMultiChoice spMultiChoice = (SPFieldMultiChoice)spField;
                    SPFieldChoice newField = new SPFieldChoice(_list.Fields, spField.Title);
                    _miSetFieldAttributeValue.Invoke(newField, 
                      new object[2] { "ID", spField.Id.ToString() });
                    spField = newField;
                }
                else if (fieldType == typeof(SPFieldLookup))
                {
                    SPFieldLookup spLookup = (SPFieldLookup)spField;
                    spLookup.AllowMultipleValues = false;
                    spLookup.DefaultValue = string.Empty;
                }
                //Set the default value to empty string so that nothing can
                //be selected
                if (spField is SPFieldChoice)
                    spField.DefaultValue = string.Empty;
            }
            return spField;
        }
        private void SetValue()
        {
            if (_bfc == null || Page.IsPostBack)
                return;
            SPField spField = GetField();
            try
            {
                object obj = null;
                if (!string.IsNullOrEmpty(_value))
                {
                    if (spField is SPFieldDateTime)
                    {
                        //For some reason the GetFieldValue for the datetime control
                        //doesn't work properly so we need to generate the value ourselves
                        //if the value doesn't parse then we need to set it because if it's left
                        //null then the Value property will blow up.
                        DateTime dt = DateTime.MinValue;
                        if (DateTime.TryParse(_value, null, 
                                     DateTimeStyles.RoundtripKind, out dt))
                            obj = dt;
                        else
                            obj = DateTime.Now;
                    }
                    else if (_isSearch && spField is SPFieldLookup)
                        obj = spField.GetFieldValue(_value.Replace(";#", ""));
                    else
                        obj = spField.GetFieldValue(_value);
                }
                else
                    obj = spField.DefaultValueTyped;
                if (obj != null)
                    _bfc.Value = obj;
            }
            catch { }
        }
        protected override void Render(System.Web.UI.HtmlTextWriter writer)
        {
            //Try and set the value again in case the edit control doesn't support 
            //setting the value during on init
            SetValue();
            // Some of the controls get set to not be visible in the Onload, so we need 
            // to set them back to visible
            foreach (Control c in this.Controls)
                c.Visible = true;
            
            base.Render(writer);
        }
    }
}

Using the Code

This code snippet simply creates a new instance of SPFieldRenderer and adds it to the page.

C#
_spField = _list.Fields.GetFieldByInternalName(this.EditField);
if (_spField != null)
{
    _control = new SPFieldRenderer();
    _control.Field = _spField;
    _control.List = _list;
    _control.Value = this.InitialValue;
    pnlEditContainer.Controls.Add(_control);
}
else
    throw new Exception(string.Format("Field {0} could not be found.", 
                                      this.EditField));

Points of Interest

I've also built a query builder that uses these controls, and I'll be posting on this at a later time. Let me know what you think.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)