Introdcution

I like to use the ASP.NET GridView for administrative functions in my applications. Like editing and managing statuses, types, etc.. Usually items that end up in drop down lists where the is a finite number of columns that need to be managed.

Recently, I thought it would be nice if I didn't have to create a TemplateField everytime I wanted to use validation. The regular BoundField works very well and makes it easy to use the OrderedDictionaries to retrieve the original and new values so you can update your data source. I found this article that tells you how to create your own BoundField that inherit from the DataControlField. In my case I didn't need to do this much work because I wasn't binding to different controls, I was just validating the current controls.

Here I create the new class and I inherit from BoundField. By doing this I don't have to deal with the databinding and value extraction from the controls when updating. I also have two properties I have added. The ErrorForColor and the NumericType which is a enum so that I can use that to add the appropriate validator.

public class RequiredBoundField : BoundField
{
    const string NumericOnlyString = "NumericOnly";
    const string ErrorForeColorString = "ErrorForeColor";
    const string RequiredFieldErrorText = "*";
    const string NumericOnlyMessage = "Must be numeric";

    public virtual Color ErrorForeColor
    {
        get
        {
            object o = base.ViewState[ErrorForeColorString];
            if (o != null)
            {
                return (Color)o;
            }
            else
            {
                return Color.Red;
            }
        }
        set
        {
            base.ViewState[ErrorForeColorString] = value;
            OnFieldChanged();
        }
    }

    public virtual NumbericFieldType NumericType
    {
        get
        {
            object o = base.ViewState[NumericOnlyString];
            if (o != null)
            {
                return (NumbericFieldType)o;
            }
            else
            {
                return NumbericFieldType.Undefined;
            }
        }
        set
        {
            base.ViewState[NumericOnlyString] = value;
            OnFieldChanged();
        }
    }
}

At this point we are going to override 2 functions. The CreateField and InitializeDataCell functions. See below:

protected override DataControlField CreateField()
{
    return new RequiredBoundField();
}
protected override void InitializeDataCell(DataControlFieldCell cell, DataControlRowState rowState)
{
    base.InitializeDataCell(cell, rowState);

    DataControlRowState state = rowState & DataControlRowState.Edit;
    if ((!ReadOnly && (state != DataControlRowState.Normal)))
    {
        TextBox textBox = cell.Controls[0] as TextBox;
        textBox.ID = this.DataField;

        RequiredFieldValidator validator = new RequiredFieldValidator();
        validator.ControlToValidate = this.DataField;
        validator.ErrorMessage = RequiredFieldErrorText;
        validator.ForeColor = this.ErrorForeColor;
        cell.Controls.Add(validator);

        if (this.NumericType != NumbericFieldType.Undefined)
        {
            CompareValidator compareValidator = new CompareValidator();
            compareValidator.ControlToValidate = this.DataField;
            compareValidator.ErrorMessage = NumericOnlyMessage;
            compareValidator.ForeColor = this.ErrorForeColor;
            compareValidator.Operator = ValidationCompareOperator.DataTypeCheck;

            switch (this.NumericType)
            {
                case NumbericFieldType.Integer:
                    compareValidator.Type = ValidationDataType.Integer;
                    break;
                case NumbericFieldType.Decimal:
                    compareValidator.Type = ValidationDataType.Double;
                    break;
            }                    
            
            cell.Controls.Add(compareValidator);
        }

    }
}

By using these key lines:

TextBox textBox = cell.Controls[0] as TextBox;
textBox.ID = this.DataField; 

I can add the validators and use the ID to assign the validators to the appropriate text box. This will only work for cases where you would have used a BoundField. Not a CheckBoxField or something else like that.

Ok so now let's look at the implementation. First, in my case I add the control namespace and assembly to the configuation file. Make sure to add it in the system.web element.

<pages controlRenderingCompatibilityVersion="4.0" clientIDMode="AutoID">
   <controls>
      <add assembly="RequiredBoundFieldDemo" 
         namespace="RequiredBoundFieldDemo.Controls" 
         tagPrefix="custom" />
   </controls>
</pages> 

Now that this is in the config file you will see this in intellisense when you are trying to add columns to a GridView. Here is my code for my GridView.

<asp:GridView ID="sampleGridView" runat="server" 
        GridLines="None" AutoGenerateColumns="False"
        DataKeyNames="Id" AutoGenerateEditButton="True" 
        CellPadding="5" 
        OnRowCancelingEdit="sampleGridView_RowCancelingEdit"
        OnRowEditing="sampleGridView_RowEditing" 
        OnRowUpdating="sampleGridView_RowUpdating">
    <Columns>
        <asp:BoundField HeaderText="Id" 
            DataField="Id" ReadOnly="true" />
        <custom:RequiredBoundField HeaderText="Required Text" 
            DataField="RequiredText" />
        <custom:RequiredBoundField HeaderText="Required Integer" 
            DataField="RequiredInteger"
            NumericType="Integer" />
        <custom:RequiredBoundField HeaderText="Required Decimal" 
            DataField="RequiredDecimal"
            NumericType="Decimal" />
        <asp:BoundField DataField="CreatedBy" 
            ReadOnly="true" HeaderText="Created By" />
        <asp:BoundField DataField="CreateTime" 
            ReadOnly="true" HeaderText="Date Created"
            DataFormatString="{0:d}" />
    </Columns>
</asp:GridView>

You can see that I am using a combination of regular BoundFields and my own that is named custom:RequiredBoundField. You also see on 2 of these columns I have added the NumericType. Intellisense will also prompt you for the available options in the NumericType because it is an enum.

So now when you want to edit and update a row you can use the e.NewValues Dictionary to retrieve the values that have been updated because the BoundField is taking care of all of that for you. See below:

protected void sampleGridView_RowUpdating(object sender, GridViewUpdateEventArgs e)
{
    int selectedId = (int)e.Keys[Id];

    using (SampleEntities context = new SampleEntities())
    {
        Demo demoObject = context.Demoes.FirstOrDefault(i => i.Id == selectedId);

        demoObject.RequiredText = Convert.ToString(e.NewValues[RequiredText]);
        demoObject.RequiredInteger = Convert.ToInt32(e.NewValues[RequiredInteger]);
        demoObject.RequiredDecimal = Convert.ToDecimal(e.NewValues[RequiredDecimal]);

        context.SaveChanges();

        sampleGridView.DataSource = context.Demoes.ToList();
    }

    sampleGridView.EditIndex = -1;
    sampleGridView.DataBind();

}

So that is it. Please feel free to download the demo project if you want to see a version that is running from start to finish. This will make my life easier from now on and I hope it does the same for you!

推荐.NET配套的通用数据层ORM框架:CYQ.Data 通用数据层框架
新浪微博粉丝精灵,刷粉丝、刷评论、刷转发、企业商家微博营销必备工具"