Developing a custom field control

Let's start with a discussion about why you would need a custom filed control?
Well let's have a look at this page. On this page you can find several offers (in the red box). These offers are simply a list with the destination, price and a url.

The easiest solution for editing these offers would be adding a RichText field control (or two in our scenario) onto your page.

With that however the editing experience for an author is not very exciting. She would have to edit the HTML code for this RichText field control:

So we came up with the idea to implement a custom field control which takes only the necessary data from the author and which renders itself onto the page. The necessary data for our offers scenario are: Title, for each offer: destination, price, url, the text for the link "More offers" and the underlying url and a field with some additional information. The initial prototype for this custom field control looked like this:

Using these fields an author can provide the necessary information for our offers page. Once the page is published the field control renders its content like this:

The downside of this approach was that we've limited our self to create two offers only. We wanted to have a dynamic list of offers where an author can decide how many offers she wants to create on a specific page. So we've prototyped such a control using a standard ASP.NET 2.0 page with a GridView which is bound to an ObjectDataSource.

The last step was to integrate this control into the custom field control which can be used by SharePoint on a Page Layout. The following screen-shot show two instances of this new field control.

Each row with the offer details has a button for editing or deleting this offer. Switching an offer into edit mode allows the author to modify this specific offer:

So now after we finished the author's view of this custom field control let's have a look at the development side. We basically need 3 files:

-an .ascx file with the UI of the custom field control
-an .xml file with the description of the control
-the actual file where we implement the control

I'm listing these 3 files below.

  1. .ascx file for the UI. This file has to be copied into the folder: "C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\CONTROLTEMPLATES"

<%@ Control Language="C#" Debug="true" %>

<%@Assembly Name="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>

<%@Register TagPrefix="SharePoint" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral,PublicKeyToken=71e9bce111e9429c" namespace="Microsoft.SharePoint.WebControls"%>

<SharePoint:RenderingTemplate ID="OffersFieldRendering" runat="server">

<Template>

Title:

<br />

<asp:TextBox ID="txtTitle" Width="196px" runat="server"></asp:TextBox>

<asp:GridView

DataSourceID="objOffersCollection"

ID="grdOffers"

runat="server"

ShowHeader="False"

AutoGenerateColumns="False"

DataKeyNames=""

ShowFooter="True"

BorderWidth="0px"

Width="200px"

>

<Columns>

<asp:TemplateField HeaderText="" ControlStyle-Width="" FooterStyle-Width="" HeaderStyle-Width="" ItemStyle-Width="">

<ItemTemplate>

<ul class="listitem_redright_dotted" style="width: 160px; height: 14px;">

<li>

<%# String.Format("<a title='' href='{0}'><span class=right>{1}</span>{2}</a>", DataBinder.Eval(Container.DataItem, "Url"), DataBinder.Eval(Container.DataItem, "Price"), DataBinder.Eval(Container.DataItem, "Destination"))%>

</li>

</ul>

</ItemTemplate>

<EditItemTemplate>

Destination:

<br />

<asp:TextBox ID="txtDestination" Text='<% #DataBinder.Eval(Container.DataItem, "Destination") %>' Width="160px" runat="server"></asp:TextBox>

Price:

<br />

<asp:TextBox ID="txtPrice" Text='<% #DataBinder.Eval(Container.DataItem, "Price") %>' Width="160px" runat="server"></asp:TextBox>

URL:

<br />

<asp:TextBox ID="txtUrl" Text='<% #DataBinder.Eval(Container.DataItem, "Url") %>' Width="160px" runat="server"></asp:TextBox>

</EditItemTemplate>

<FooterTemplate>

Destination (for new Offer):

<br />

<asp:TextBox ID="txtAddDestination" ToolTip="please enter the destination" Width="160px" runat="server"></asp:TextBox>

Price (for new Offer):

<br />

<asp:TextBox ID="txtAddPrice" ToolTip="please enter the price" Width="160px" runat="server"></asp:TextBox>

URL (for new Offer):

<br />

<asp:TextBox ID="txtAddUrl" ToolTip="please enter the URL" Width="160px" runat="server"></asp:TextBox>

<asp:ImageButton ID="btnAdd" AlternateText="Add" runat="server" CommandName="AddOffer" Text="Add" ImageUrl='<%# "~/Style%20Library/images/buttons/button_add.gif" %>' />

</FooterTemplate>

</asp:TemplateField>

<asp:CommandField ButtonType="Link" ShowEditButton="True">

<itemstyle width="8px" />

</asp:CommandField>

<asp:CommandField ButtonType="Link" ShowDeleteButton="True" >

<itemstyle width="8px" />

</asp:CommandField>

</Columns>

</asp:GridView>

<asp:ObjectDataSource

DeleteMethod="DeleteOffer"

InsertMethod="AddOffer"

ID="objOffersCollection"

runat="server"

SelectMethod="GetAllOffersAsDataSet"

FilterExpression="controlid='' OR controlid='{0}'"

UpdateMethod="UpdateOffer"

TypeName="Swiss.WebParts.OffersCollection"

EnableCaching="False"

>

</asp:ObjectDataSource>

More Offers:

<br />

<asp:TextBox ID="txtMoreOffersText" runat="server" Text="More offers" Width="196px"></asp:TextBox>

<br />

URL:

<br />

<asp:TextBox ID="txtMoreOffersUrl" runat="server" Width="196px"></asp:TextBox>

<br />

Information:

<br />

<asp:TextBox ID="txtInformation" runat="server" Text="" TextMode="MultiLine" Height="40px" Width="196px"></asp:TextBox>

</Template>

</SharePoint:RenderingTemplate>

  1. The .xml file with the description of the field types. This file has to be copied into the directory: "C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\XML"

<FieldTypes>

<FieldType>

<Field Name="TypeName">Offers</Field>

<Field Name="ParentType">MultiColumn</Field>

<Field Name="TypeDisplayName">Offers</Field>

<Field Name="TypeShortDescription">Offers (editing a list of offers)</Field>

<Field Name="UserCreatable">TRUE</Field>

<Field Name="ShowOnListAuthoringPages">TRUE</Field>

<Field Name="ShowOnDocumentLibraryAuthoringPages">TRUE</Field>

<Field Name="ShowOnSurveyAuthoringPages">TRUE</Field>

<Field Name="ShowOnColumnTemplateAuthoringPages">TRUE</Field>

<Field Name="FieldTypeClass">Swiss.WebParts.FieldOffers, Swiss.WebParts, Version=1.0.0.1, Culture=neutral, PublicKeyToken=585fff093fa85341</Field>

<PropertySchema>

<Fields>

<Field Name="DefaultTitle" DisplayName="Default Title:" MaxLength="250" DisplaySize="30" Type="Text">

<Default></Default>

</Field>

</Fields>

</PropertySchema>

<RenderPattern Name="DisplayPattern">

</RenderPattern>

</FieldType>

</FieldTypes>

  1. The .cs file with the actual implementation of the custom field control:

using System;

using System.Collections;

using System.Collections.Generic;

using System.Text;

using System.Data;

using Microsoft.SharePoint;

using Microsoft.SharePoint.WebControls;

using System.Web;

using System.Web.UI;

using System.Web.UI.WebControls;

using System.IO;

using System.Text.RegularExpressions;

namespace Sample.WebParts

{

#region Offers FieldControl

#region FieldOffersValue (Definition of the necessary values for storing the 'Offer' information.)

public class FieldOffersValue : SPFieldMultiColumnValue

{

private const int numberOfFields = 5;

public FieldOffersValue()

: base(numberOfFields)

{ }

public FieldOffersValue(string value)

: base(value)

{ }

/// <summary>

/// Holds the title of an offer.

/// </summary>

public string Title

{

get { return this[0]; }

set { this[0] = value; }

}

/// <summary>

/// Stores all 'Offers' records separated by \t.

/// </summary>

public string OffersData

{

get { return this[1]; }

set { this[1] = value; }

}

/// <summary>

/// Stores the text for the 'More Offers' link

/// </summary>

public string MoreOffersText

{

get { return this[2]; }

set { this[2] = value; }

}

/// <summary>

/// Stores the URL for the 'More Offers' link

/// </summary>

public string MoreOffersUrl

{

get { return this[3]; }

set { this[3] = value; }

}

/// <summary>

/// Stores the text for the additional information which can be defined for an offer.

/// </summary>

public string Information

{

get { return this[4]; }

set { this[4] = value; }

}

}

#endregion

#region FieldOffers (Communication with the values)

public class FieldOffers : SPFieldMultiColumn

{

public FieldOffers(SPFieldCollection fields, string fieldName)

: base(fields, fieldName)

{ }

public FieldOffers(SPFieldCollection fields,

string typeName,

string displayName)

: base(fields, typeName, displayName)

{ }

public override BaseFieldControl FieldRenderingControl

{

get

{

BaseFieldControl fldControl = new OffersFieldControl();

fldControl.FieldName = InternalName;

return fldControl;

}

}

public override object GetFieldValue(string value)

{

if (string.IsNullOrEmpty(value))

return null;

return new FieldOffersValue(value);

}

}

#endregion

#region OffersFieldControl (The user control)

public class OffersFieldControl : BaseFieldControl

{

protected TextBox txtTitle;

protected ObjectDataSource objOffersCollection;

protected GridView grdOffers;

protected TextBox txtMoreOffersText;

protected TextBox txtMoreOffersUrl;

protected TextBox txtInformation;

protected static OffersCollection _dsOffersCollection = new OffersCollection();

protected override string DefaultTemplateName

{

get { return "OffersFieldRendering"; }

}

public override object Value

{

get

{

// called when submitting the content for approval

EnsureChildControls();

FieldOffersValue fieldValue = new FieldOffersValue();

fieldValue.Title = txtTitle.Text.Trim();

fieldValue.OffersData = _dsOffersCollection.GetOffersData(this.ID);

fieldValue.MoreOffersText = txtMoreOffersText.Text.Trim();

fieldValue.MoreOffersUrl = txtMoreOffersUrl.Text.Trim();

fieldValue.Information = txtInformation.Text.Trim();

return fieldValue;

}

set

{

// called when editing the page

EnsureChildControls();

FieldOffersValue fieldValue = (FieldOffersValue)value;

txtTitle.Text = fieldValue.Title;

_dsOffersCollection.SetOffersData(this.ID, fieldValue.OffersData);

txtMoreOffersText.Text = fieldValue.MoreOffersText;

txtMoreOffersUrl.Text = fieldValue.MoreOffersUrl;

txtInformation.Text = fieldValue.Information;

}

}

public override void Focus()

{

EnsureChildControls();

grdOffers.DataBind();

txtTitle.Focus();

}

protected override void CreateChildControls()

{

if (Field == null)

return;

base.CreateChildControls();

if (ControlMode == SPControlMode.Display)

return;

txtTitle = (TextBox)TemplateContainer.FindControl("txtTitle");

if (txtTitle == null)

throw new ArgumentException("Corrupted OffersFieldRendering template - missing txtTitle.");

txtTitle.TabIndex = TabIndex;

txtTitle.CssClass = CssClass;

txtTitle.ToolTip = "please enter a Title";

objOffersCollection = (ObjectDataSource)TemplateContainer.FindControl("objOffersCollection");

if (objOffersCollection == null)

throw new ArgumentException("Corrupted OffersFieldRendering template - missing objOffersCollection.");

objOffersCollection.ObjectCreating += new ObjectDataSourceObjectEventHandler(objOffersCollection_ObjectCreating);

objOffersCollection.Filtering += new ObjectDataSourceFilteringEventHandler(objOffersCollection_Filtering);

grdOffers = (GridView)TemplateContainer.FindControl("grdOffers");

if (grdOffers == null)

throw new ArgumentException("Corrupted OffersFieldRendering template - missing grdOffers.");

grdOffers.TabIndex = TabIndex;

grdOffers.CssClass = CssClass;

grdOffers.ToolTip = "please use these edit fields to add a new Offer";

grdOffers.RowCancelingEdit += new GridViewCancelEditEventHandler(grdOffers_RowCancelingEdit);

grdOffers.RowEditing += new GridViewEditEventHandler(grdOffers_RowEditing);

grdOffers.RowUpdating += new GridViewUpdateEventHandler(grdOffers_RowUpdating);

grdOffers.RowDeleting += new GridViewDeleteEventHandler(grdOffers_RowDeleting);

grdOffers.RowCommand += new GridViewCommandEventHandler(grdOffers_RowCommand);

grdOffers.RowDataBound += new GridViewRowEventHandler(grdOffers_RowDataBound);

// set the url's for the command button images

// unfortunately this cannot be done in the .ascx file using CodeExpressions

for (int i = 0; i < grdOffers.Columns.Count; i++)

{

DataControlField field = grdOffers.Columns[i];

if (field is CommandField)

{

string editImageUrl = string.Format("{0}/Style%20Library/images/buttons/button_edit.gif", SPContext.Current.Site.Url);

string cancelImageUrl = string.Format("{0}/Style%20Library/images/buttons/button_cancel.gif", SPContext.Current.Site.Url);

string deleteImageUrl = string.Format("{0}/Style%20Library/images/buttons/button_delete.gif", SPContext.Current.Site.Url);

string updateImageUrl = string.Format("{0}/Style%20Library/images/buttons/button_update.gif", SPContext.Current.Site.Url);

string editText = string.Format("<img src='{0}' alt='Edit' border='0' />", editImageUrl);

string cancelText = string.Format("<img src='{0}' alt='Cancel' border='0' />", cancelImageUrl);

string deleteText = string.Format("<img src='{0}' alt='Delete' border='0' />", deleteImageUrl);

string updateText = string.Format("<img src='{0}' alt='Update' border='0' />", updateImageUrl);

CommandField cmdField = (CommandField)field;

if (cmdField != null)

{

//cmdField.EditImageUrl = editImageUrl;

//cmdField.CancelImageUrl = cancelImageUrl;

//cmdField.DeleteImageUrl = deleteImageUrl;

//cmdField.DeleteText = deleteImageUrl;

cmdField.EditText = editText;

cmdField.CancelText = cancelText;

cmdField.DeleteText = deleteText;

cmdField.UpdateText = updateText;

}

}

}

txtMoreOffersText = (TextBox)TemplateContainer.FindControl("txtMoreOffersText");

if (txtMoreOffersText == null)

throw new ArgumentException("Corrupted OffersFieldRendering template - missing txtMoreOffersText.");

txtMoreOffersText.TabIndex = TabIndex;

txtMoreOffersText.CssClass = CssClass;

txtMoreOffersText.ToolTip = "please enter the text for the 'More Offers' link";

txtMoreOffersUrl = (TextBox)TemplateContainer.FindControl("txtMoreOffersUrl");

if (txtMoreOffersUrl == null)

throw new ArgumentException("Corrupted OffersFieldRendering template - missing txtMoreOffersUrl.");

txtMoreOffersUrl.TabIndex = TabIndex;

txtMoreOffersUrl.CssClass = CssClass;

txtMoreOffersUrl.ToolTip = "please enter the URL for the 'More Offers' link";

txtInformation = (TextBox)TemplateContainer.FindControl("txtInformation");

if (txtInformation == null)

throw new ArgumentException("Corrupted OffersFieldRendering template - missing txtInformation.");

txtInformation.TabIndex = TabIndex;

txtInformation.CssClass = CssClass;

txtInformation.ToolTip = "please enter the additional Information for these Offers";

if (ControlMode == SPControlMode.New)

{

txtTitle.Text = Field.GetCustomProperty("DefaultTitle").ToString();

}

}

protected void grdOffers_RowEditing(object sender, GridViewEditEventArgs e)

{

grdOffers.EditIndex = e.NewEditIndex;

}

protected void grdOffers_RowCancelingEdit(object sender, GridViewCancelEditEventArgs e)

{

grdOffers.EditIndex = -1;

}

protected void grdOffers_RowUpdating(object sender, GridViewUpdateEventArgs e)

{

string destination = ((TextBox)grdOffers.Rows[e.RowIndex].FindControl("txtDestination")).Text;

string price = ((TextBox)grdOffers.Rows[e.RowIndex].FindControl("txtPrice")).Text;

string url = ((TextBox)grdOffers.Rows[e.RowIndex].FindControl("txtUrl")).Text;

int row = e.RowIndex;

objOffersCollection.UpdateParameters.Add("Row", TypeCode.Int32, row.ToString());

objOffersCollection.UpdateParameters.Add("ControlId", TypeCode.String, this.ID);

objOffersCollection.UpdateParameters.Add("Destination", destination);

objOffersCollection.UpdateParameters.Add("Price", price);

objOffersCollection.UpdateParameters.Add("Url", url);

objOffersCollection.Update();

grdOffers.EditIndex = -1;

}

protected void grdOffers_RowDeleting(object sender, GridViewDeleteEventArgs e)

{

int row = e.RowIndex;

objOffersCollection.DeleteParameters.Add("Row", TypeCode.Int32, row.ToString());

objOffersCollection.DeleteParameters.Add("ControlId", TypeCode.String, this.ID);

objOffersCollection.Delete();

e.Cancel = false;

}

protected void grdOffers_RowCommand(object sender, GridViewCommandEventArgs e)

{

if (e.CommandName == "AddOffer")

{

if (objOffersCollection != null)

{

string destination = ((TextBox)grdOffers.FooterRow.FindControl("txtAddDestination")).Text;

string price = ((TextBox)grdOffers.FooterRow.FindControl("txtAddPrice")).Text;

string url = ((TextBox)grdOffers.FooterRow.FindControl("txtAddUrl")).Text;

objOffersCollection.InsertParameters.Add("Destination", destination);

objOffersCollection.InsertParameters.Add("Price", price);

objOffersCollection.InsertParameters.Add("Url", url);

objOffersCollection.InsertParameters.Add("ControlId", this.ID);

objOffersCollection.Insert();

}

}

}

protected void grdOffers_RowDataBound(object sender, GridViewRowEventArgs e)

{

if (e.Row.RowType == DataControlRowType.DataRow)

{

if (e.Row.RowIndex == 0)

{

// the first row of the object data source contains an empty record. Without that empty record the GridView

// would not be visible (I think this is a bug in the GridView). For displaying the GridView also when

// no data is available we've created this empty record in the object data source. However this empty

// record will be hidden.

if (e.Row.Cells[0].Text == string.Empty && e.Row.Cells[1].Text == string.Empty && e.Row.Cells[2].Text == string.Empty)

{

e.Row.Visible = false;

}

}

}

}

protected void objOffersCollection_ObjectCreating(object sender, ObjectDataSourceEventArgs e)

{

e.ObjectInstance = _dsOffersCollection;

}

protected void objOffersCollection_Filtering(object sender, ObjectDataSourceFilteringEventArgs e)

{

e.ParameterValues.Clear();

e.ParameterValues.Add("controlid", this.ID);

}

protected override void RenderFieldForDisplay(HtmlTextWriter output)

{

if (Field == null)

return;

string offersRowFormat = "<li><a title='' href='{2}'><span class=right>{1}</span>{0}</a></li>";

string offersRows = "";

StringBuilder sb = new StringBuilder();

if (ItemFieldValue is FieldOffersValue)

{

FieldOffersValue fov = (FieldOffersValue)ItemFieldValue;

if (fov != null)

{

string title = fov.Title;

string offersData = fov.OffersData;

string moreOffersText = fov.MoreOffersText;

string moreOffersUrl = fov.MoreOffersUrl;

string information = fov.Information;

sb.Append(string.Format("<h3 class=top>{0}</h3>", title));

sb.Append("<ul class='listitem_redright_dotted'>");

// insert offers rows

string[] offersItemsArray = offersData.Split('\n');

foreach (string offerItem in offersItemsArray)

{

string[] offerItemDetails = offerItem.Split('\t');

if (offerItemDetails.Length >= 2)

{

if (offerItemDetails[0] != null && offerItemDetails[1] != null && offerItemDetails[2] != null)

{

if (offerItemDetails[0] != string.Empty && offerItemDetails[1] != string.Empty && offerItemDetails[2] != string.Empty)

offersRows += string.Format(offersRowFormat, offerItemDetails[0], offerItemDetails[1], offerItemDetails[2]);

}

}

}

sb.Append(offersRows);

sb.Append("</ul>");

sb.Append("<ul class='listitem_redright_dotted'>");

sb.Append(string.Format("<li><a title='' href='{0}'>{1}</a></li>", moreOffersUrl, moreOffersText));

sb.Append("</ul>");

sb.Append("<br>");

sb.Append(string.Format("<span class=ms-rteCustom-bold>{0}</span>", information));

}

}

else

{

sb.Append(string.Format("{0}", this.ID));

}

output.Write(sb);

}

protected override void Render(HtmlTextWriter output)

{

if (((SPContext.Current.FormContext.FormMode == Microsoft.SharePoint.WebControls.SPControlMode.Edit)

|| (SPContext.Current.FormContext.FormMode == Microsoft.SharePoint.WebControls.SPControlMode.New)))

{

TextWriter tempWriter = new StringWriter();

base.Render(new System.Web.UI.HtmlTextWriter(tempWriter));

string orightml = tempWriter.ToString();

// correct the "leaving the page problem".

Regex hrefRegex = new Regex("href=\"javascript(?<PostBackScript>.*?)\"", RegexOptions.Singleline);

string newhtml = hrefRegex.Replace(orightml, "href=\"#\" onclick=\"${PostBackScript};return false;\"");

output.Write(newhtml);

}

else

{

base.Render(output);

}

}

}

#endregion

#region OffersCollection (Used as an Object Datasource. Holds a collection of Offers.)

public class OffersCollection

{

private List<Offer> _offerList;

public OffersCollection()

{

if (_offerList == null)

{

_offerList = new List<Offer>();

_offerList.Add(new Offer("", "", "", ""));

}

}

public void UpdateOffer(int row, string controlid, string destination, string price, string url)

{

int rowForControlId = 0;

for (int pos = 0; pos < _offerList.Count; pos++)

{

Offer o = _offerList[pos];

if (o != null && o.IsDeleted != true)

{

if (o.ControlID == controlid)

rowForControlId++;

if (rowForControlId == row)

{

o.Destination = destination;

o.Price = price;

o.URL = url;

_offerList[pos] = o;

break;

}

}

}

}

public void AddOffer(string destination, string price, string url, string controlId)

{

if (_offerList != null)

_offerList.Add(new Offer(destination, price, url, controlId));

}

// RowDeleting executed twice

// https://forums.asp.net/tags/events/default.aspx?PageIndex=3

// https://www.issociate.de/board/post/285047/help\_please\_on\_GridView\_commands\_+\_AutoEventWireUp

public void DeleteOffer(int row, string controlid)

{

int rowForControlId = 0;

for (int pos = 0; pos < _offerList.Count; pos++)

{

Offer o = _offerList[pos];

if (o != null && o.IsDeleted != true)

{

if (o.ControlID == controlid)

rowForControlId++;

if (rowForControlId == row)

{

o.IsDeleted = true;

_offerList[pos] = o;

break;

}

}

}

}

public List<Offer> GetAllOffers()

{

if (_offerList != null && _offerList.Count > 0)

return _offerList;

else

return null;

}

public string GetOffersData(string controlId)

{

string offersData = "";

string offerItem = "";

foreach (Offer o in _offerList)

{

if (o.ControlID == controlId && o.IsDeleted != true)

{

offerItem = string.Format("{0}\t{1}\t{2}\n", o.Destination, o.Price, o.URL);

offersData += offerItem;

}

}

return (offersData);

}

/// <summary>

/// The offer items are stored in one big string where

/// </summary>

/// <param name="offersData"></param>

public void SetOffersData(string controlId, string offersData)

{

// clean-up

_offerList.RemoveAll(delegate(Offer o)

{

return o.ControlID == controlId;

}

);

string[] offersItemsArray = offersData.Split('\n');

foreach (string offerItem in offersItemsArray)

{

string[] offerItemDetails = offerItem.Split('\t');

if (offerItemDetails.Length >= 3)

{

if (offerItemDetails[0] != null && offerItemDetails[1] != null && offerItemDetails[2] != null)

{

Offer newOffer = new Offer(offerItemDetails[0], offerItemDetails[1], offerItemDetails[2], controlId);

_offerList.Add(newOffer);

}

}

}

}

public DataSet GetAllOffersAsDataSet()

{

DataSet ds = new DataSet("Table");

// Create the schema of the DataTable.

DataTable dt = new DataTable();

DataColumn dc;

dc = new DataColumn("Destination", typeof(string)); dt.Columns.Add(dc);

dc = new DataColumn("Price", typeof(string)); dt.Columns.Add(dc);

dc = new DataColumn("Url", typeof(string)); dt.Columns.Add(dc);

dc = new DataColumn("ControlId", typeof(string)); dt.Columns.Add(dc);

//dc = new DataColumn("OfferId", typeof(string)); dt.Columns.Add(dc);

// Add rows to the DataTable.

DataRow row;

for(int offerid = 0; offerid < _offerList.Count; offerid++)

{

Offer o = _offerList[offerid];

if (o != null && o.IsDeleted != true)

{

row = dt.NewRow();

row["Destination"] = o.Destination;

row["Price"] = o.Price;

row["Url"] = o.URL;

row["ControlId"] = o.ControlID;

//row["OfferId"] = offerid.ToString();

dt.Rows.Add(row);

}

}

// Add the complete DataTable to the DataSet.

ds.Tables.Add(dt);

return ds;

}

}

public class Offer

{

private string _destination = string.Empty;

private string _price = string.Empty;

private string _url = string.Empty;

private string _controlId = string.Empty;

private bool _isDeleted = false;

public string Destination

{

get

{

if (!String.IsNullOrEmpty(_destination))

return _destination;

else

return "";

}

set { _destination = value; }

}

public string Price

{

get

{

if (!String.IsNullOrEmpty(_price))

return _price;

else

return "";

}

set { _price = value; }

}

public string URL

{

get

{

if (!String.IsNullOrEmpty(_url))

return _url;

else

return "";

}

set { _url = value; }

}

public string ControlID

{

get

{

if (!String.IsNullOrEmpty(_controlId))

return _controlId;

else

return "";

}

set { _controlId = value; }

}

public bool IsDeleted

{

get { return _isDeleted; }

set { _isDeleted = value; }

}

public Offer(string destination, string price, string url, string controlId)

{

_destination = destination;

_price = price;

_url = url;

_controlId = controlId;

_isDeleted = false;

}

public Offer()

{

}

}

#endregion

#endregion

}

Until next time,
thomas