Posts tagged ‘fieldrendercontrol’

September 29, 2010

Using FieldRenderingControls in custom web forms

Within a custom Web Form we can add references to Microsoft.SharePoint.WebControls and add SharePoint FormField controls to render controls to edit values within a list. We can declare a FormField as follows:

<SharePoint:FormField ID="field_id" runat="server" FieldName="MyField" ControlMode="New" />

However, in addition to this definition, the FormField needs to know the list and content type, in case of being a New form, that it is attaching to. This can be attached through the ItemContext property of FormField, which uses an SPContext object (defaulting to SPContext.Current). Of course when we are in a Web Form, SPContext.Current will not be attached to a List or to a particular Item. Therefore, if we want this field to be able to work against an item we need to change the context that is attached to it.

We can achieve this by getting a context using SPContext.GetContext with the web and list we want in mind, and attaching this to our field, using something like the following code,

using (SPWeb web = SPContext.Current.Site.OpenWeb(webLocation))
{
	SPList targetList = web.Lists[listName];
	SPContext context = SPContext.GetContext(HttpContext.Current, 0, targetList.ID, web);
	field_id.ItemContext = context;
}

As can be seen, for the item id parameter, at the moment we have passed in 0, which should, in theory, work. However, when we add a SharePoint:SaveButton control to the form and try with this, we will see an exception is raised.

To get around this, under the context declaration we need to explicitly state the FormMode (this is set automatically when the item id is greater than 0 to Edit, but in the case of 0 is left as Invalid). We can do this with the code context.FormContext.FormMode = SPControlMode.New.

The context ideally needs to be attached to the element directly after it is added to its parent control. To achieve this in a clean way we can override the AddedControl method, which is called when any control is added to another control. We can then recurse over all controls in that control set and if it is a FieldMetadata (the base class for BaseFieldControl) derived class, or FormComponent (the base class for items such as SaveButton) derived class, set the item context.

protected override void AddedControl(Control control, int index)
{
	base.AddedControl(control, index);
	SetControlContext(control);
}

protected void SetControlContext(control)
{
	if (control is FieldMetadata)
	{
		FieldMetadata field = control as FieldMetadata;
		field.ItemContext = this.SPContext;
		field.ControlMode = this.SPContext.FormContext.FormMode;
	}
	if (control is FormComponent)
	{
		FormComponent formComponent = control as FormComponent;
		formComponent.ItemContext = this.SPContext;
		formComponent.ControlMode = this.SPContext.FormContext.FormMode;
	}
	if (control.HasControls)
	{
		foreach (Control childControl in control.Controls)
		{
			SetControlContext(childControl);
		}
	}	
}

There are two things of note above, firstly, SPContext is a property of the class in which this sits. We do this, so that the context can be instantiated once, and then reused. Secondly, the field’s ControlMode is being set from the Context, this gives us the freedom of having a form that is both an Add form or an Edit form based on the query string parameters sent to the page. We can parse these parameters inside the SPContext property to set the ControlMode of the underlying controls. In addition this means our controls can lose the ControlMode property, and therefore their definition becomes less verbose.

We could therefore, finally, declare our property as follows:

private SPContext cachedContext = null;

protected SPContext SPContext
{
	get
	{
		if (cachedContext == null)
		{
			using (SPWeb web = SPContext.Current.Site.OpenWeb(webUrl))
			{
				SPList targetList = web.Lists[listName];
				int itemId = 0;
				int.TryParse(Response.QueryString("itemId"), out itemId);
				cachedContext = SPContext.GetContext(HttpContext.Current, itemId, targetList.ID, web);
				if (itemId &gt; 0)
				{
					cachedContext.FormContext.FormMode = Edit;
				}
				else
				{
					cachedContext.FormContext.FormMode = New;
				}								
			}
		}
		return cachedContext;
	}
}

We can wrap all this code inside a simple Control which will set the properties of all sub controls and in this way achieve custom data entry forms using web forms techniques.

Advertisements