这件事,这些年来已经和客户说了无数遍:这是客户端浏览器的行为,我们在服务器端的代码|程序中没有控制,所以是做不到的……
今天一个偶然的机会发现原来我竟然错了,Windows XP SP2带的IE 6早已经实现了这个功能了……
CONTENT Attribute | content Property
http://msdn2.microsoft.com/en-us/library/ms533689.aspx
技术的车轮原来就这么不停的在向前滚动着……
ASP.Net 2.0中把默认的control ID前缀从_ctl0改为ctl00了,可能有很多依赖control ID的代码会无法正常工作。
其实Web.Config中有下面这个选项可以控制:
xhtmlConformance Element (ASP.NET Settings Schema)
http://msdn2.microsoft.com/en-us/library/ms228268.aspx
<system.web>
<xhtmlConformance mode="Legacy"/>
当然最理想的做法还是动态取Control.ClientID。
又一个被遗忘的Namespace,没有文档……
用Reflector打开,可以看到很多有用的正则表达式:
Directives:
"\\G<%\\s*@(\\s*(?\\w+(?=\\W))(\\s*(?=)\\s*\"(?[^\"]*)\"|\\s*(?=)\\s*'(?[^']*)'|\\s*(?=)\\s*(?[^\\s%>]*)|(?)(?\\s*?)))*\\s*?%>"
Server Tag:
"<%(?!#)(([^%]*)%)*?>"
?
Tag:
?
"\\G<(?[\\w:\\.]+)(\\s+(?[-\\w]+)(\\s*=\\s*\"(?[^\"]*)\"|\\s*=\\s*'(?[^']*)'|\\s*=\\s*(?<%#.*?%>)|\\s*=\\s*(?[^\\s=/>]*)|(?\\s*?)))*\\s*(?/)?>"
这次做ASCX Design Time多亏这些Regex,配上System.Web.UI.Design.ControlParser..
只是不明白“\G”是什么意思,似乎文档上找不到,加了以后似乎要从String的Position的头一个字符开始匹配……
Dino写了三篇文章介绍Script Callback:
Script Callbacks in ASP.NET
http://msdn.microsoft.com/msdnmag/issues/04/08/CuttingEdge/
Custom Script Callbacks in ASP.NET
http://msdn.microsoft.com/msdnmag/issues/05/01/CuttingEdge/
Implications of Script Callbacks in ASP.NET
http://msdn.microsoft.com/msdnmag/issues/04/12/CuttingEdge/
我在ASP.Net 1.1里写了一个Page的继承类,可以像ASP.Net 2.0一样使用Script Callback:
Implementing Callback framework in ASP.Net 1.1
http://blog.joycode.com/tingwang/articles/47304.aspx
没什么难度,估计别人也早已经写过,权当灌水……
经常看到用户要在ASP.Net 中跑一些需要很长时间才能返回的任务(如DTS Package等等)。这样一来一个页面可能很长时间无法Render到客户端,而使用户对当前任务的状态产生疑惑。
MSDN上看到了一些解决方案:
Build Your ASP.NET Pages on a Richer Bedrock
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnvs05/html/BedrockASPNET.asp?frame=true
Asynchronous Wait State Pattern in ASP.NET
http://msdn.microsoft.com/msdnmag/issues/03/12/DesignPatterns/
它们都将用户重定向到一个等待页面,然后再重定向到执行任务的页面。任务的执行过程中,用户看到的是等待页面,等任务执行完,整个执行任务的页面就会彻底返回到客户端。这种做法还是发现有个问题,因为任务执行过程中IE和服务器之间没有任何通讯,IIS服务器会将非活动的连接关闭。这个超时值虽然可以在网站属性(Web Site)里设得更长一点,但是这个超时太大会对整个网站的性能会有影响。
这里可以采用另外一种方法:在服务器端另起一个线程执行任务,并通过客户端脚本不停刷新页面来给用户反馈,只到任务运行结束。这样也可以防止同一个任务被同时执行多次:
ASP.Net Lengthy Task Visual Feedback Page
http://blog.joycode.com/tingwang/articles/47292.aspx
不知为何很多人都有这么一种印象:一个ASP.Net Web Application当idle很长时间以后会被worker process关闭,当我们再发request的时候Web Application会被重新动态编译,重新启动,会很慢……有这种印象的人还包括ASP.Net MVP:
Force ASP.NET Apps to Keep-Alive
http://authors.aspalliance.com/paulwilson/articles/?id=12
我从来没有听说过ASP.Net(至少1.1)有这么的功能。我只听说过ASP.Net的worker process会因为idle被shutdown,而且这是可以通过machine.config或inetmgr设置的,但从来没有见过什么文档说Web Application或Web Application的AppDomain会被关闭……如果有谁见过还望不吝赐教,我不想发布错误信息,误人……
下面的链接介绍了Application restart的原因:
Why does App restart? (FAQ)
http://asp.net/Forums/retired/ShowPost.aspx?PostID=232621
客户要在DataGrid里绑定日期数据,并显示一个按钮,按钮按了以后弹出一个Calendar,可供选择日期。选择的结果要还给原来的DataGrid。给他做了一个:
How to implement popup Calendar dialog box with DataGrid
http://blog.joycode.com/tingwang/articles/40933.aspx
特此留念,以备复用……
有一点,在DataGrid.ItemCreated事件里取不到control的正确的Client ID或Unique ID,老是返回“TextBox1”,弄得client side script很难写……而在DataGrid.ItemDataBound事件里就能取到“DataGrid1__ctl2_TextBox1”……未及深究,不知道有没有人能给个solid的解释……
有没有试过给ASP.Net Web Control实现Left和Top属性?就像WinForm的control一样,当在Designer里拖动control的时候,Properties window里的Left和Top跟着一起改变。当在Properties window里修改Left和Top时,control在Designer里位置跟着移动。
看似简单的问题,真的要做的话就会发现其难度了……
How to interact with Style["LEFT"] and Style["TOP"] of the WebControl during design time
http://blog.joycode.com/tingwang/articles/39633.aspx
Outlook里发出去的mail都可以设Follow Up flag和Due time,这个功能蛮实用……如何编程实现?
有人在Newsgroup里贴了段CDOEx的code:
http://groups.google.com/groups?q=outlook+follow+up&hl=zh-CN&lr=&c2coff=1&selm=eApolE7pEHA.2864%40TK2MSFTNGP12.phx.gbl&rnum=5%20&hl=zh-CN&lr=&c2coff=1&selm=eApolE7pEHA.2864%40TK2MSFTNGP12.phx.gbl&rnum=5
CDOEx只有装了Exchange Server的机器上才有……
其实只要给System.Web.Mail.MailMessage设置相应的Header就可以了:
message.Headers.Add("X-Message-Flag", "Follow up");
message.Headers.Add("Reply-By", "Tue, 26 Oct 2004 01:00:00 +0900");
思归的“动态控件的状态问题”很有意思:
http://blog.joycode.com/saucer/archive/2004/10/20/35927.aspx
希望写下这样的logic对大家做troubleshooting有所帮助。我们可以这样分析这个问题:
第一步,简化一下Page。建一个新的ASP.Net Web Application,用下面的code:
private void Page_Load(object sender, System.EventArgs e)
{
DropDownList ddlDynamic = new DropDownList();
ddlDynamic.ID = "ddlDynamic";
HtmlForm form1 = (HtmlForm)this.FindControl("Form1");
if (!IsPostBack)
{
ddlDynamic.Items.Add("Before");
}
form1.Controls.Add(ddlDynamic);
if (!IsPostBack)
{
ddlDynamic.Items.Add("After");
}
}
在Page上扔个Button,以便可以PostBack。运行后Postback的结果,“Before” item没被保留,“After”被保留了。问题被isolate:问题不在于DropDownList或者ListCollection对于view state的处理出问题,而是特定一个ListItem view state的处理有异。
现在有目标了,接下来看ListItem source code:
internal object SaveViewState()
{
if (this.misc.Get(2) && this.misc.Get(3))
{
return new Pair(this.Text, this.Value);
}
if (this.misc.Get(2))
{
return this.Text;
}
if (this.misc.Get(3))
{
return new Pair(null, this.Value);
}
return null;
}
可以看到只有misc.Get(2)或misc.Get(3)符合一定条件才存view state,鉴于misc是private member,继续在ListItem的code里找什么会影响misc.Get(2) or misc.Get(3)的值,结果如下:
internal bool Dirty
{
set
{
this.misc.Set(2, value);
this.misc.Set(3, value);
}
}
找到了唯一的可能,在Reflector里看Set方法的Callee Graph,找到System.Web.UI.WebControls.ListItemCollection.Add(ListItem):Void方法。接续看source code:
public void Add(ListItem item)
{
this.listItems.Add(item);
if (this.marked)
{
item.Dirty = true;
}
}
这里有一个private bool marked flag。继续在ListItemCollection里找:
internal void TrackViewState()
{
this.marked = true;
for (int num1 = 0; num1 < this.Count; num1++)
{
this[num1].TrackViewState();
}
}
void IStateManager.TrackViewState()
{
this.TrackViewState();
}
好了,看来这个方法就是关键了……由于是Interface的方法,我们可以尝试一下在Page2里调用它:
((IStateManager)(ddlDynamic.Items)).TrackViewState();
if (!IsPostBack)
{
for (int i=1; i <=3; i++)
ddlDynamic.Items.Add(new ListItem(i.ToString(), i.ToString()));
}
form1.Controls.Add(ddlDynamic);
确实是起作用了……鉴于如此,我们可以猜测ControlCollection.Add一定调用了System.Web.UI.WebControls.ListItemCollection.TrackViewState()这个方法。要证明这点容易多了……
用WinDbg,在System.Web.UI.WebControls.ListItemCollection.TrackViewState()方法上设个断点。Call Stack如下:
019cf8b0 06538fd0 [DEFAULT] [hasThis] Void System.Web.UI.WebControls.ListItemCollection.TrackViewState()
019cf8b4 06538fbe [DEFAULT] [hasThis] Void System.Web.UI.WebControls.ListControl.TrackViewState()
019cf8bc 06538e53 [DEFAULT] [hasThis] Void System.Web.UI.Control.InitRecursive(Class System.Web.UI.Control)
019cf8d8 0653758a [DEFAULT] [hasThis] Void System.Web.UI.Control.AddedControl(Class System.Web.UI.Control,I4)
019cf8f4 06537462 [DEFAULT] [hasThis] Void System.Web.UI.ControlCollection.Add(Class System.Web.UI.Control)
019cf904 063c06fc [DEFAULT] [hasThis] Void WebApplication37.WebForm3.Page_Load(Object,Class System.EventArgs)
at [+0x13c] [+0x8c] c:\inetpub\wwwroot\webapplication37\webform3.aspx.cs:36
019cf944 065391a4 [DEFAULT] [hasThis] Void System.Web.UI.Control.OnLoad(Class System.EventArgs)
…
好了……一切都明了……
如果要render出滚动条,我们可以把DataGrid放在<div>里。如果还要一个不随滚动条滚动的Header,我们可以在<div>外另外render一个Header。我写过一个Web Control Library详见:
Fixed Header Scrollable DataGrid control
http://blog.joycode.com/tingwang/articles/32789.aspx
(最可恨的customer居然要pager和滚动条同时用……)
ASP.Net 1.x的client side postback script是这样的:
<!--
function __doPostBack(eventTarget, eventArgument) {
var theform;
if (window.navigator.appName.toLowerCase().indexOf("netscape") > -1) {
theform = document.forms["Form1"];
}
else {
theform = document.Form1;
}
theform.__EVENTTARGET.value = eventTarget.split("$").join(":");
theform.__EVENTARGUMENT.value = eventArgument;
theform.submit();
}
// -->
它是通过form.submit()去submit的。这样就有一个问题,form.onsubmit事件不会被触发,这么一来可能有些client side validation script就被绕过了。在ASP.Net 2.0里,这个问题被fix了。对于ASP.Net 1.x,据我所知,SP1也没有解决这个问题。我们可以使用下面的代码解决这个问题:
string myDoPostBack = @"
<script language=""javascript"">
<!--
function __myDoPostBack(eventTarget, eventArgument) {
var theform;
if (window.navigator.appName.toLowerCase().indexOf(""netscape"") > -1) {
theform = document.forms[""Form1""];
}
else {
theform = document.Form1;
}
theform.__EVENTTARGET.value = eventTarget.split(""$"").join("":"");
theform.__EVENTARGUMENT.value = eventArgument;
if ((typeof(theform.onsubmit) == ""function"") && theform.onsubmit()!=false) {
theform.submit();
}
}
__doPostBack = __myDoPostBack
// -->
</script>";
RegisterStartupScript("myDoPostBack", myDoPostBack);