ASP.NET 必填文本框 (图形必填字段验证器)
一个 ASP.NET 自定义 TextBox 控件,内置 RequiredFieldValidator,提供与 Windows Forms ErrorProvider 类似的外观和感觉。
引言
一段时间以来,我一直在努力消除我们开发者不断进行的许多琐碎任务。因此,我开始编写一个控件库,其中包含大多数标准的 System.Web.UI.WebControls
控件,并使它们使用起来更轻松、更快捷。
在我的第一篇文章中,我将向大家展示我是如何创建我的 RequiredTextBox
的,它包含一个 System.Web.UI.WebControls.TextBox
和一个自定义的 RequiredFieldValidator
。最终产品是一个拖放式文本框,它被设置为必填字段,并且具有与 System.Windows.Forms.ErrorProvider
相似的错误提供程序外观和感觉。
背景
我们经常发现自己,至少我是如此,当 FormView 或 DetailsView 从数据源自动生成字段后,我们再去为页面上的控件应用所有验证。那么,这是一篇介绍性文章,用于替换页面中的那些控件,使用一旦进入设计器画布就自动设置了验证的控件。在此介绍的基础上,我正在开发一个自定义框架,用于自动生成 FormView
、DetailsView
或 GridView
等,并使用这些控件,而不是它们目前使用的标准未验证控件。
该控件通过 PropertyGrid
和智能标签支持提供全面的设计时支持。因此,我们还将探讨为控件添加此设计时支持所涉及的措施。
使用代码
我将首先介绍项目的布局结构,然后我们将逐步了解代码的一些区域。
解决方案树如下所示
正如你所见,我将代码拆分成了 click2install.design 和 click2install.controls,而 click2install.resource 包含控件的嵌入式图像资源。
注意:上面的下载中已省略测试网站。
好的,让我们深入代码看看它是如何工作的。
首先,我选择扩展 System.Web.UI.TextBox
,并在 Render
例程中包含验证器。我们也可以从头开始创建一个控件,并单独渲染 TextBox
和 RequiredFieldValidator
。
基本上,我所做的是
- 创建了一个扩展
System.Web.UI.TextBox
的类 - 保存一个自定义验证器(
RequiredValidator
)对象的实例变量,该对象扩展了RequiredFieldValidator
- 通过
TextBox
控件公开验证器的某些属性 - 重写
TextBox
的Render
函数以自定义页面的输出 - 在
TextBox
的Render
方法中调用RequiredValidator
的 Render 方法
好了,这就是五行代码减少了三个小时的编码工作。所以,让我们看看每个步骤,然后我们再继续处理通过使用智能标签添加的设计时支持。
虽然源解决方案已注释,但为简洁起见,这里的列表没有注释。
第一步:创建一个扩展 System.Web.UI.TextBox
的类。
namespace click2install.controls
{
[
ToolboxData("<{0}:RequiredTextBox runat=server></{0}:RequiredTextBox>"),
Designer(typeof(RequiredTextBoxDesigner)),
]
public class RequiredTextBox : TextBox
{
#region constants
private const string DEFAULT_VALIDATOR_TEXT = " *";
#endregion
public RequiredTextBox()
{
ValidatorMessage = this.ID + " is a required field";
ValidatorDisplayType = ValidatorDisplay.Dynamic;
ValidatorEnableClientScript = true;
ValidatorFocusOnError = true;
}
这里有几点需要立即注意,那就是 Designer
属性,它允许我们添加智能标签支持,以及我添加的 TagPrefix
,这样我就可以在 HTML 标记中自定义此控件的名称。构造函数中还设置了默认值,而 OnInit
则设置了验证器控件并将其添加到页面的控件层次结构中。
这是 AssemblyInfo.cs 中允许自定义标签前缀的相关代码摘录
//
// assembly tag prefix
//
[assembly: TagPrefix("click2install.controls", "c2i")]
好的,为了简洁起见,我们将合并步骤 2 和 3,并且只列出相关的代码、设计器属性和实例变量
#region Required Field Validator properties
[
Browsable(false),
]
private RequiredValidator m_RequiredFieldValidator = null;
private private RequiredValidator RequiredTextBoxValidator
{
get
{
if (m_RequiredFieldValidator == null) {
m_RequiredFieldValidator = new RequiredValidator(this);
}
return m_RequiredFieldValidator;
}
set { m_RequiredFieldValidator = value; }
}
internal bool IsDesignMode
{
get { return DesignMode; }
}
private ErrorProviderType m_ErrorProvider =
ErrorProviderType.StillIcon;
[
[
Description("The type of visual alert that
will be shown when validation fails"),
Category("Validator"),
DefaultValue(ErrorProviderType.StillIcon),
]
public ErrorProviderType ErrorProvider
{
get { return m_ErrorProvider; }
set
{
if (value == ErrorProviderType.Text)
{
RequiredTextBoxValidator.Text =
DEFAULT_VALIDATOR_TEXT;
}
m_ErrorProvider = value;
}
}
[
[
Description("The Validators Message when validation fails"),
Category("Validator"),
]
public string ValidatorMessage
{
get { return RequiredTextBoxValidator.ErrorMessage; }
set { RequiredTextBoxValidator.ErrorMessage = value; }
}
[<value>The type of the validator display.</value>[
Description("The Validators Display"),
Category("Validator"),
]
public ValidatorDisplay ValidatorDisplayType
{
get { return RequiredTextBoxValidator.Display; }
set { RequiredTextBoxValidator.Display = value; }
}
[
Description("The Validators FocusOnError"),
Category("Validator"),
]
public bool ValidatorFocusOnError
{
get { return RequiredTextBoxValidator.SetFocusOnError; }
set { RequiredTextBoxValidator.SetFocusOnError = value; }
}
[
Description("The Validators EnableClientScript"),
Category("Validator"),
]
public bool ValidatorEnableClientScript
{
get { return RequiredTextBoxValidator.EnableClientScript; }
set { RequiredTextBoxValidator.EnableClientScript = value; }
}
#endregion
正如你所见,我只向用户公开了验证器的某些属性。暴露 ControlToValidate
属性是没有意义的,因为我们总是希望它验证此控件。另外请注意,我必须创建一个 internal bool IsDesignMode
属性,以暴露给验证器控件,判断 TextBox
控件是在设计器画布上还是在页面上。除此之外,你还可以看到创建了一个 ErrorProviderType
枚举来确定错误提供程序图标,或者如果你实际上不想要图标,你可以指定文本。
我选择不公开验证器的 Text 属性,因为我很少使用它了,因为我已经有了图标功能。因此,如果将 Text
设置为 ErrorProviderType
,那么你只会得到一个星号(*),尽管如果你需要,添加此功能会非常容易。
现在进入第 5 步,重写 TextBox
的 Render
函数
protected override void Render(HtmlTextWriter writer)
{
writer.RenderBeginTag(HtmlTextWriterTag.Span);
base.Render(writer);
RequiredTextBoxValidator.RenderControl(writer);
writer.RenderEndTag();
}
如上所示,我选择将整个控件包含在一个 span
元素中,并且代码保持简洁,方法是允许验证器控件自行渲染,而不是我们在这里处理所有的渲染逻辑。此外,为了跨浏览器兼容性,我让 writer 渲染标签,而不是我将标签写成字符串。
注意:请查看重写的 OnInit()
方法,了解在 Render
之前验证器是如何初始化的。
好的,那么这就是 TextBox
类了。它基本上就是上面步骤中概述的那样。所以,让我们继续讨论自定义验证器。
在验证器中,而不是重写所有 Microsoft 验证例程和调试大量的 JavaScript,我走了捷径,遵循了以下步骤:
- 如果
ErrorProviderType
是Text
,只需将其传递给基类,让它正常渲染。 - 确定
ErrorProviderType
,然后创建一个图像标签并渲染它,使用嵌入式资源作为图像的ImageUrl
。然后,将此图像标签设置为验证器控件的文本。 - 对于设计时支持,我们有一个属性允许你决定是否要看到闪烁的错误图标,因此如果所有这些闪烁让你头疼,我们可以在设计时不渲染图标。
所以这是完整的验证器类,我们将更详细地分解它
[
DefaultProperty("Text"),
ToolboxItem(false),
]
public class RequiredValidator : RequiredFieldValidator
{
public RequiredValidator(RequiredTextBox textbox)
: base()
{
TextBox = textbox;
}
private RequiredTextBox m_TextBox = null;
private RequiredTextBox TextBox
{
get { return m_TextBox; }
set
{
this.ID = value.ClientID + "_RequiredValidator";
m_TextBox = value;
}
}
public bool RenderDesignModeErrorProvider
{
get
{
if (ViewState["RenderDesignModeErrorProvider"] == null)
{
ViewState["RenderDesignModeErrorProvider"] = true;
}
return (bool)ViewState["RenderDesignModeErrorProvider"];
}
set { ViewState["RenderDesignModeErrorProvider"] = value; }
}
protected override void Render(HtmlTextWriter writer)
{
if (TextBox.ErrorProvider == ErrorProviderType.Text)
{
base.Render(writer);
}
else
{
if (!TextBox.IsDesignMode || (TextBox.IsDesignMode &&
RenderDesignModeErrorProvider))
{
string src = string.Empty;
if (TextBox.ErrorProvider == ErrorProviderType.StillIcon)
{
src = TextBox.Page.ClientScript.GetWebResourceUrl(
TextBox.GetType(),
"click2install.resource.errorprovider.gif");
}
else if (TextBox.ErrorProvider ==
ErrorProviderType.AnimatedIcon)
{
src = TextBox.Page.ClientScript.GetWebResourceUrl(
TextBox.GetType(),
"click2install.resource.errorprovider_anim.gif");
}
Text = " <img src=\"" + src + "\"" +
" alt=\"" + ErrorMessage + "\"" +
" title=\"" + ErrorMessage + "\"" +
" />";
base.Render(writer);
}
}
}
}
从上面的代码可以看出,我们不希望这个控件出现在我们的工具箱中(ToolboxItem(false)
),至少对于这个控件来说不是这样,尽管我已经将其重构为一个更通用的验证控件,就像它的基类一样,可以应用于任何控件,而不是将验证控件“嵌入”到宿主控件中。
另外,从上面的代码可以看出,我们传入了 TextBox
宿主控件的引用,以便我们确定几件事情,即我们是否处于设计模式,以及 Page
对象,以便我们可以嵌入我们的图像。
上述方法简单地用一个带有嵌入式图像的图像标签替换了 RequiredFieldValidator
的*普通* Text
值。将图像嵌入为资源具有明显的优势,并且图像的大小也没有影响,尽管如果你有自己想要使用的图像,可以轻松添加一个指向 Web 资源的 href
。
要嵌入图像(也就是说,一般而言,或者为这个解决方案添加更多内容),你需要:
- 将图像放在解决方案中的一个文件夹中(我选择了 resource)。
- 在属性网格中将图像的“生成操作”设置为“嵌入资源”。
- 在程序集级别添加 Web 资源引用,我选择了 AssemblyInfo.cs 文件。
- 使用
Page.GetWebResource(Type, <full_path_to_resource>)
作为 HTMLimg
标签或Image
对象的src
或ImageUrl
。
如果我们查看上面的 Render
函数,我们可以看到,如果控件处于设计模式(如从宿主 TextBox
控件确定的那样)。然后,我们根据 RenderDesignModeErrorProvider
属性决定是否渲染图标。否则,如果控件在页面上,我们就正常渲染。另外,请注意,如果 ErrorProviderType
是 Text
,那么我们只是让基类渲染验证器。
好了,基本上就是这样了。一个可工作的 RequiredTextBox
控件,具有通过 PropertyGrid
的设计时支持,尽管为了简洁起见,我省略了一些属性和注释。
这是我随代码附带的智能标签
要添加此功能,我们需要执行一些基本步骤:
- 创建一个扩展
DesignerActionList
的ActionList
类(click2install.design.RequiredTextBoxActionList
) - 创建一个自定义控件设计器类,用于在 VS 中设计控件(
click2install.design.RequiredTextBoxDesigner
) - 在我们的设计器类中重写
ActionLists
属性以返回自定义ActionList
好的,让我们从设计器类开始。
namespace click2install.design
{
public class RequiredTextBoxDesigner : ControlDesigner
{
private DesignerActionListCollection m_ActionLists = null;
public override DesignerActionListCollection ActionLists
{
get
{
if (m_ActionLists == null)
{
m_ActionLists = base.ActionLists;
m_ActionLists.Add(new RequiredTextBoxActionList(
(RequiredTextBox)Component));
}
return m_ActionLists;
}
}
}
}
相当直接,是吧?我们重写了该函数,并返回了一个新的 ActionList
,我们向其中添加了 RequiredTextBoxActionList
。请注意,我已对 RequiredTextBoxActionList
类的构造函数进行了强类型声明。这并非必需,而是良好的实践。
这是 RequiredTextBoxActionList
类的一个片段
namespace click2install.design
{
public class RequiredTextBoxActionList : DesignerActionList
{
private RequiredTextBox m_LinkedControl = null;
public RequiredTextBoxActionList(RequiredTextBox textbox) :
base(textbox)
{
m_LinkedControl = textbox;
}
private PropertyDescriptor GetPropertyByName(string name)
{
PropertyDescriptor pd =
TypeDescriptor.GetProperties(m_LinkedControl)[name];
if (null == pd)
{
throw new ArgumentException("Property '" + name +
"' not found on " + m_LinkedControl.GetType().Name);
}
return pd;
}
public string ValidatorMessage
{
get { return m_LinkedControl.ValidatorMessage; }
set { GetPropertyByName("ValidatorMessage").SetValue(
m_LinkedControl, value); }
}
public bool ValidatorFocusOnError
{
get { return m_LinkedControl.ValidatorFocusOnError; }
set { GetPropertyByName("ValidatorFocusOnError").SetValue(
m_LinkedControl, value); }
}
..
.. more properties into the TextBox here
..
public ErrorProviderType ErrorProvider
{
get { return m_LinkedControl.ErrorProvider; }
set { GetPropertyByName("ErrorProvider").SetValue(m_LinkedControl, value); }
}
private void LaunchSite()
{
try { System.Diagnostics.Process.Start("http://www.click2install" +
".com/programming/#RequiredTextBox"); }
catch { }
}
public override DesignerActionItemCollection GetSortedActionItems()
{
DesignerActionItemCollection coll = new DesignerActionItemCollection();
coll.Add(new DesignerActionHeaderItem("Validator"));
coll.Add(new DesignerActionPropertyItem("ValidatorMessage",
"Validator Message:", "Validator",
"The required validators Message"));
coll.Add(new DesignerActionPropertyItem("ValidatorFocusOnError",
"Focus on Error", "Validator",
"The required validators SetFocusOnError"));
coll.Add(new DesignerActionPropertyItem("ValidatorEnableClientScript",
"Enable Client Script", "Validator",
"The required validators EnableClientScript"));
coll.Add(new DesignerActionPropertyItem("ValidatorDisplayType",
"Validator Display Type", "Validator",
"The required validators Display"));
coll.Add(new DesignerActionPropertyItem("ErrorProvider",
"Error Display Type", "Validator",
"The error providers display type"));
coll.Add(new DesignerActionHeaderItem("Design Mode"));
coll.Add(new DesignerActionPropertyItem("RenderDesignModeValidatorIcon",
"Render DesignMode Icons", "Design Mode",
"Set true to render error provider icons in DesignMode"));
coll.Add(new DesignerActionHeaderItem("Information"));
coll.Add(new DesignerActionMethodItem(this, "LaunchSite",
"RequiredTextBox Website ...", "Information", true));
coll.Add(new DesignerActionTextItem("ID: " + m_LinkedControl.ID, "ID"));
return coll;
}
}
}
上面的代码虽然冗长,但相当直观,特别是如果你喜欢 IntelliSense。不过,从这段代码中,你可以看到我添加了一个非常方便的实用函数,它允许我轻松访问此智能标签所属宿主控件的属性,即 GetPropertyByName()
。我们还可以看到,要向智能标签添加项,我们只需重写设计器的 GetSortedActionItems()
函数,并用我们想要在智能标签中显示的项填充集合。我鼓励你查看 Collection.Add()
函数,看看还可以向智能标签 Actions 集合添加哪些其他内容。在此示例中,我尝试涵盖了最常用的项,并使用了 TextItem
来表示控件 ID,PropertyItems
,以及一个 MethodItem
来启动一个网站。
总之,我希望这个控件能对某人有所帮助,无论是作为激发你创造力来创建自定义服务器控件的起点,还是仅仅在你的应用程序中使用这个控件,或者只是为了解释和概述自定义控件设计的内部工作原理。
关注点
在我开始创建自定义服务器控件时很久以前学到的一个有趣的事情是(仅在开发期间)将你的渲染函数或控件创建函数(如 CreateChildControls
、OnInit
)包装在 try-catch
块中,并通过 Windows.Forms.MessageBox
输出异常。
在控件设计器中重写 GetDesignTimeHtml()
函数也很有帮助,并将设计时 HTML 标记输出到 MessageBox
或文件等,这样你就可以实际看到幕后发生的事情,从而帮助调试,并有望得到更干净的代码。
历史
- 初始开发 (2006 年 9 月 14 日)