如有不明白的地方欢迎加QQ群14670545 探讨
在学习了控件的生命周期之后,我们来进一步处学习。这节我们把简单的js判断放到控件里面,同时对不可用的控件要禁止其提交行为。
好的,首先分析下,我怎么知道页面上那些是我自定义的控件呢,GetType()方法或者is xxx类型 都可以用来判断,但是我必须要知道GetType()之后要与哪种类型匹配,is也是一样。基于这种思考,我们就需要来一个接口(interface)以方便我们做某些功能的统一处理。
1.新建类库CustomerWebControls,新建类ICustomControl.cs,写入代码如下:
namespace CustomerWebControls
{
/// <summary>
/// 自定义控件的统一接口
/// </summary>
public interface ICustomControl
{
//...
}
}
ICustomControl这里面我们没有写什么,空着的,后面我们会讲到比如控件的权限,到时候需要修改我们的接口(后话了),好了现在我们自定义的控件要是继承了ICustomControl接口,那么我们可以认为它属于ICustomControl类型(标准)的规格控件
2.来一个我们来绘一个textbox,前面章节有讲到,这里就不说了,直接上代码:
using System;
using System.ComponentModel;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace CustomerWebControls
{
[DefaultProperty("IsValidata"), ToolboxData("<{0}:CCTextBox runat=server />")]
public class CCTextBox : TextBox, ICustomControl
{
/// <summary>
/// 是否验证输入的合法性
/// </summary>
[Bindable(true), Category("Appearance"), DefaultValue(true), Localizable(true)]//在属性窗口中是否可见
public bool IsValidata
{
get { return ViewState["IsValidata"] != null ? (bool)ViewState["IsValidata"] : false; }
set { ViewState["IsValidata"] = value; }
}
/// <summary>
/// 是否移除不安全字符串
/// </summary>
[Bindable(true), Category("Appearance"), DefaultValue(true), Localizable(true)]
public bool IsRemoveUnsafeHtml
{
set { ViewState["IsRemoveUnsafeHtml"] = value; }
get { return ViewState["IsRemoveUnsafeHtml"] != null ? (bool)ViewState["IsRemoveUnsafeHtml"] : true; }
}
/// <summary>
/// 重写TextBox的Text属性
/// </summary>
public override string Text
{
get { return IsRemoveUnsafeHtml ? CCTools.RemoveUnsafeHtml(base.Text.Trim()) : base.Text.Trim(); }
set { base.Text = value; }
}
protected override void OnPreRender(EventArgs e)
{
if (string.IsNullOrEmpty(CssClass))
{
CssClass = "txt";
Attributes["onmouseover"] = "this.className='colorfocus';";
Attributes["onfocus"] = "this.className='colorfocus';";
Attributes["onmouseout"] = "this.className='colorblur';";
Attributes["onblur"] = "this.className='colorblur';";
}
base.OnPreRender(e);
}
protected override void Render(HtmlTextWriter writer)
{
base.Render(writer);
if (IsValidata)//需要验证,就添加一个js提示
writer.Write("<span id=\"" + ClientID + "Tip\"></span>");
}
}
}
这里的IsValidata属性我们是为了下一节把客户端脚本(比如jquery ui)加进来处理而提前写的(此篇先不说了,看下篇吧
)
CCTools.cs类的代码如下:
namespace CustomerWebControls
{
public class CCTools
{
private const string StrKeyWord = @"select|insert|delete|from|drop table|update|truncate|xp_cmdshell|exec master|netlocalgroup administrators|:|net user";
/// <summary>
/// 过滤HTML中的不安全标签
/// </summary>
/// <param name="content">目标字符</param>
/// <returns>移除不合法的字符</returns>
public static string RemoveUnsafeHtml(string content)
{
content = content.Replace("'", "").Replace("%", "").ToLower();
string[] arry_sql = StrKeyWord.Split('|');
foreach (string paramSQL in arry_sql)
if (content.IndexOf(paramSQL) > -1)
content = content.Replace(paramSQL, "$$");
return content;
}
}
}
基于这个CCTextBox的
CssClass = "txt";
Attributes["onmouseover"] = "this.className='colorfocus';";
Attributes["onfocus"] = "this.className='colorfocus';";
Attributes["onmouseout"] = "this.className='colorblur';";
Attributes["onblur"] = "this.className='colorblur';";
我们需要在页面上写一些css以配合效果
<style type="text/css">
.txt {
border-top-width: 1px;
padding-right: 1px;
padding-left: 1px;
padding-bottom: 1px;
padding-top: 1px;
border-left-width: 1px;
border-bottom-width: 1px;
border-right-width: 1px;
border-left-color: #707070;
border-bottom-color: #CECECE;
border-top-color: #707070;
border-right-color: #CECECE;
font-family: Tahoma, Verdana, "宋体";
font-size: 12px;
color: #000000;
background-color: #FFFFFF;
height:18px; line-height:18px;
}
.colorfocus {
border-width:1px;
border-style:solid;
border-left-color: #069;
border-bottom-color: #C2D5E3;
border-top-color: #069;
border-right-color: #C2D5E3;
background-color: #EBF2F6;
height:18px; line-height:18px;
}
.colorblur
{
border-width:1px;
border-style:solid;
border-left-color: #707070;
border-bottom-color: #CECECE;
border-top-color: #707070;
border-right-color: #CECECE;
background-color: #ffffff;
height:18px; line-height:18px;
}
</style>
到此我们的重绘一个简单的TextbOX结束,下面我们来处理button按钮:
2.在CustomerWebControls类库里面新建一个类CCButton.cs,同样,我们也让它继承ICustomControl接口,达到统一标准。
还记得我们上一章讲的web服务器控件的生命周期吗,这里我们就需要用到啦。在按钮提交的时候一般我们会做一些客户端的脚本提示,不如在OnClientClick事件里面
return checkInfo();一下。这个checkInfo是个客户端的js函数,当然它是我们自己写的一些东西。这里我们不妨用一个属性来指代当前自定义的button是否需要添加一个这样的提示,这时候我们可以重写button的AddAttributesToRender事件,它是用来将控件的属性添加到输出流用以在客户端上呈现内容,这里我们就可以写一些脚本判断。
当然,当我们的按钮向服务器提交完以后,或许还要做一些提示什么的,这个时候可以对回发事件进行重写。按照上面的想法,现在我们来处理代码:
using System;
using System.Collections.Generic;
using System.Text;
using System.Web.UI;
using System.ComponentModel;
using System.Web.UI.WebControls;
namespace CustomerWebControls
{
[DefaultProperty("Text")]
[ToolboxData("<{0}:CCButton runat=server></{0}:CCButton>")]
public class CCButton : Button, ICustomControl
{
/// <summary>
/// 默认的构造函数。
/// </summary>
public CCButton()
{
base.Text = "保存";
this.ViewState["afterSubmitText"] = "提交成功";
this.ViewState["checkclient"] = false;
this.ViewState["beforeSubmitText"] = "确定要提交吗?";
}
/// <summary>
/// 获取或设置单击按钮后,按钮上所显示的文本。
/// </summary>
[Bindable(true),
Category("Appearance"),
DefaultValue("提交成功"),
Description("指示单击提交后,按钮回发事件弹出的提示信息。")]
public string AfterSubmitText
{
get
{
string afterSubmitText = (string)this.ViewState["afterSubmitText"];
if (afterSubmitText != null)
{
return afterSubmitText;
}
else
{
return string.Empty;
}
}
set
{
this.ViewState["afterSubmitText"] = value;
}
}
[Bindable(true),
Category("Appearance"),
DefaultValue(false),
Description("指示是否要显示一个提示框。")]
public bool CheckClient
{
get
{
return (bool)this.ViewState["checkclient"];
}
set
{
this.ViewState["checkclient"] = value;
}
}
[Bindable(true),
Category("Appearance"),
DefaultValue("确定要提交吗?"),
Description("指示提示框内所包含的内容。")]
public string BeforeSubmitText
{
get
{
return (string)this.ViewState["beforeSubmitText"];
}
set
{
this.ViewState["beforeSubmitText"] = value;
}
}
protected override void Render(HtmlTextWriter writer)
{
base.Render(writer);
}
/// <summary>
/// 处理回传事件
/// </summary>
/// <param name="eventArgument"></param>
protected override void RaisePostBackEvent(string eventArgument)
{
Alert(AfterSubmitText);
base.RaisePostBackEvent(eventArgument);
}
/// <summary>
/// 控件的属性添加到输出流用以在客户端上呈现内容
/// <remarks>重写button按钮的AddAttributesToRender</remarks>
/// </summary>
/// <param name="writer">HtmlTextWriter:其中包含要在客户端上呈现内容的输出流</param>
protected override void AddAttributesToRender(HtmlTextWriter writer)
{
System.Text.StringBuilder ClientSideEventReference = new System.Text.StringBuilder();
if (((this.Page != null) && this.CausesValidation) && (this.Page.Validators.Count > 0))
ClientSideEventReference.Append("if (typeof(Page_ClientValidate) == 'function'){if (Page_ClientValidate() == false){return false;}}");
if (this.CheckClient)
ClientSideEventReference.Append("if (!confirm('" + this.BeforeSubmitText + "')){return false}");
ClientSideEventReference.Append("this.disabled = true;");
ClientSideEventReference.Append(this.Page.ClientScript.GetPostBackEventReference(this, string.Empty));
writer.AddAttribute(HtmlTextWriterAttribute.Onclick, ClientSideEventReference.ToString(), true);
base.AddAttributesToRender(writer);
}
/// <summary>
/// 在客户端显示弹出对话框。
/// </summary>
/// <param name="msg">要显示的信息。</param>
public void Alert(string msg)
{
Alert("alert", msg);
}
/// <summary>
/// 在客户端显示弹出对话框。
/// </summary>
/// <param name="name">脚本块标识。当同一页面要调用两个弹出框时需不同的标识,否则后者会覆盖前者。</param>
/// <param name="msg">要显示的信息。</param>
public void Alert(string name, string msg)
{
Page.ClientScript.RegisterClientScriptBlock(this.GetType(), name, "<script language=\"javascript\">alert('" + msg + "');</script>");
}
}
}
到此我们生成一下,然后web层引用,再把两个控件拖到页面上去看看
<cc1:CCTextBox ID="CCTextBox1" runat="server" ></cc1:CCTextBox>
<cc1:CCButton ID="CCButton1" runat="server" />
为了好看和测下事件的效果,我们添加一下属性,最终页面代码:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="ccPage.aspx.cs" Inherits="MyWebSiteTest.Manager.ccPage" %>
<%@ Register Assembly="CustomerWebControls" Namespace="CustomerWebControls" TagPrefix="cc1" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>测试封装的控件</title>
<style type="text/css">
.txt {
border-top-width: 1px;
padding-right: 1px;
padding-left: 1px;
padding-bottom: 1px;
padding-top: 1px;
border-left-width: 1px;
border-bottom-width: 1px;
border-right-width: 1px;
border-left-color: #707070;
border-bottom-color: #CECECE;
border-top-color: #707070;
border-right-color: #CECECE;
font-family: Tahoma, Verdana, "宋体";
font-size: 12px;
color: #000000;
background-color: #FFFFFF;
height:18px; line-height:18px;
}
.colorfocus {
border-width:1px;
border-style:solid;
border-left-color: #069;
border-bottom-color: #C2D5E3;
border-top-color: #069;
border-right-color: #C2D5E3;
background-color: #EBF2F6;
height:18px; line-height:18px;
}
.colorblur
{
border-width:1px;
border-style:solid;
border-left-color: #707070;
border-bottom-color: #CECECE;
border-top-color: #707070;
border-right-color: #CECECE;
background-color: #ffffff;
height:18px; line-height:18px;
}
</style>
</head>
<body>
<form id="form1" runat="server">
<div style=" margin:0 auto; width:600px;">
<cc1:CCTextBox ID="CCTextBox1" runat="server" Width="200" IsValidata="true" ></cc1:CCTextBox>
<cc1:CCButton ID="CCButton1" runat="server" CheckClient="true" OnClick="btnTest_Click" />
</div>
</form>
</body>
</html>
我们在后台做个简单的btnTest_Click事件,为什么要添加这个事件,因为我们需要测试防止页面刷新之后重复提交,等会你就看到效果了,刷新页面(如果页面不跳转,这时候刷新还是会提示的,因为页面重绘了,跳转之后返回来刷新时不会提示的)也不会提交的。
protected void btnTest_Click(object sender, EventArgs e)
{
Response.Redirect("http://www.baidu.com");
}
好了,基本已经完成,但是我们一开始说了,对于那些失效的按钮,我们要保证它不能提交事件的(比如winform软件,某些按钮是失效的,我们可以用一些外挂插件让失效的按钮的可用,完全是可以做到),为了以防万一我们需要做这样的处理。同时,我们也说了,我怎么获取页面上我自己写的ICustomControl标准接口的控件呢,基于这些考虑,我们需要些2个基类,一个最终的BasePage(它继承于System.Web.UI.Page),一个中间层的PageUI(它继承于BasePage),我们再让页面类的ccPage.cs
继承PageUI。这样做的好处事,我们可以在PageUI里面处理我自定义控件的一些事(比如权限控制,比如根据权限是否只读等等),在基类BasePage我们可以统一处理符合某一个情况下的所有控件行为(比如让所有不可用的控件全部不能做提交处理)。好了,我们来写代码:
BasePage:
using System.Web.UI;
namespace MyWebSiteTest
{
public class BasePage : System.Web.UI.Page
{
/// <summary>
/// 禁止客户端失效按钮提交
/// <remarks>RaisePostBackEvent通知引起回发的服务器控件:它应处理传入的回发事件</remarks>
/// </summary>
protected override void RaisePostBackEvent(IPostBackEventHandler sourceControl, string eventArgument)
{
if (!(bool)sourceControl.GetType().GetProperty("Enabled").GetValue(sourceControl, null))
return;
base.RaisePostBackEvent(sourceControl, eventArgument);
}
}
}
PageUI:
using System;
using System.Collections.Generic;
using System.Web.UI;
using CustomerWebControls;//我们自定义控件的类库
namespace MyWebSiteTest
{
public class PageUI : BasePage
{
/// <summary>
/// 控件列表
/// </summary>
private readonly List<Control> _CustomControl;
/// <summary>
/// 构造
/// </summary>
public PageUI()
{
_CustomControl = new List<Control>();
}
/// <summary>
/// 获取自定义服务器控件
/// </summary>
/// <param name="cc"></param>
private void GetCustomServerButtons(ControlCollection cc)
{
foreach (Control c in cc)
{
if (c is ICustomControl)
_CustomControl.Add(c);
else if (c.HasControls())
GetCustomServerButtons(c.Controls);//调用自身(伪递归)
}
}
/// <summary>
/// 做一些处理咯
/// <remarks>可自由发挥处理,比如权限方面的处理</remarks>
/// </summary>
private void InitControlsRight()
{
foreach (Control obj in _CustomControl)
{
switch (obj.GetType().Name)
{
case "CCTextBox":
CCTextBox cctextbox = obj as CCTextBox;
if (cctextbox != null && cctextbox.Enabled)
cctextbox.ReadOnly = false;
break;
}
}
}
/// <summary>
/// 在页初始化后引发
/// </summary>
protected override void OnInitComplete(EventArgs e)
{
base.OnInitComplete(e);
GetCustomServerButtons(Controls);
InitControlsRight();
}
}
}
ccPage(页面后台):
using System;
namespace MyWebSiteTest.Manager
{
public partial class ccPage : PageUI
{
protected void Page_Load(object sender, EventArgs e)
{
}
protected void btnTest_Click(object sender, EventArgs e)
{
Response.Redirect("http://www.baidu.com");
}
}
}
生成一些,运行效果
①效果
②按钮单击效果
③服务器回传页面跳转了,在把页面返回来刷新下看看
页面不会提示,但是当我们把
//Response.Redirect("http://www.baidu.com");
注释掉,再测试一下:先弹窗
刷新测试一下会有提示
OK,到此结束,下一节我们把客户端的js脚本放进来。做一个ui比较好的demo