抄摘MSDN这篇文章对 .net的webform应用有用,以供查询参考
常用运行库
Jay Allen、Mark Davis、Heidi Housten 和 Dan Mohr
Microsoft Corporation
本月并没有什么有趣的事,因此我们只是针对实质性问题进行讨论并给予答复。虽然本月收到的问题为数不多,但我们深深感觉到并非只有我们在思考着比较棘手的问题。
本月,我们为您准备了一场盛宴。我们会让您体验一下 Microsoft_.NET,并向您展示如何实现常用的 Perl 功能,以命名盛宴中的几道佳肴。在秋意已浓且日渐寒冷的日子里,您可能会注意到,我们本月的简短问答也变得稍微长些了。
如果您需要我们的帮助,请将问题发送给我们,我们会尽力帮助您。不要忘记回顾以前的专栏,以查看我们是否已经对您的问题给予了答复。
本页内容
谁能够敲响我的门? | |
.NET 最终边界 | |
Pop Pop, Shift Shift.. | |
善于选择 | |
Web Team 简短问答 | |
什么是我们的 Vector、Victor? |
谁能够敲响我的门?
亲爱的 Web Team:
在我们的 Intranet 上,我有一个 ASP 页,我想让用户以匿名方式对其进行访问。该站点的主入口页强制进行 NTLM 身份验证,因此当用户打开该页时都经过了身份验证。
我知道,登录详细信息缓存在本地,并且只有在浏览器关闭后,这些详细信息才会被放弃。新的浏览器确实会以匿名方式打开该页,但是我需要将参数传递至该页,以便我可以通过链接打开该页。我尝试过生成新浏览器窗口并关闭第一个窗口,但是 NTLM 详细信息仍然显示在 LOGON_USER 中。
我如何能够取消 NTLM 身份验证并强制该页使用匿名方式?
谢谢,
Ray Messinger
Web Team 的答复:
Ray,解决这个棘手问题的诀窍在于理解 WININET.dll,它是位于 URLMON.dll(URL 名字对象)正下方的 Microsoft Internet Explorer 的网络组件,负责处理所有的 HTTP 细节,其中包括身份验证。WININET 基于每个进程存储其所有的状态数据(例如,缓存的密码和会话 Cookie)。这就是为什么使用 window.open() 创建的窗口共享相同的 WININET 数据(包括会话 Cookie 和身份验证凭据)的原因。(从技术上讲,我们将 WININET 的所有数据统称为会话,这就是为什么将只在浏览器的生存期内维持的 Cookie 称为会话 Cookie 的原因。)
遗憾的是,此处纯脚本并不能给您提供多大帮助。如果您愿意在您的页上放一个 ActiveX_ 控件,那么您的愿望就会得以实现,这是因为 ActiveX 控件可以利用作为命令行参数传入的所需 URL 来启动一个新的浏览器实例。Visual Basic_ 的内置 Shell() 功能也可用于实现此目的(虽然不赞成过多地使用该功能),而且,较之知识库文章 Q170918 中从 Visual Basic 角度论述的冗长的 ShellExecute(),它需要的代码要少得多。只需在控件上定义一个公共方法即可,如下所示:
Public Sub LaunchBrowser(url as String) Shell "iexplore.exe " & url End Sub
(相反,那些如果不用 C++ 进行编程就会觉得坐卧不安的人,将会调用 CreateProcess() API 函数。)然后,您可以配置超级链接,以在单击这些超级链接时调用该方法:
<a href="" οnclick="javascript:launchBrowserObj.LaunchBrowser('http://server/
另外一种方式是,通过调用 WININET API 函数 InternetSetOption() 使当前 WININET 会话无效。同样,以 Visual Basic 进行编写:
Public Declare Function InternetSetOption Lib "wininet.dll" Alias "InternetSetOptionA" _ (ByVal hInternet As Long, ByVal lOption As Long, ByRef sBuffer As Any, _ ByVal lBufferLength As Long) As Integer ' Douglas Adams fans, take note! Public Const INTERNET_OPTION_END_BROWSER_SESSION = 42 Public Sub EndBrowserSession() ' Passing NULL makes this true for all connections in ' the host process. InternetSetOption vbNull, INTERNET_OPTION_END_BROWSER_SESSION, vbNull, 0 End Sub
正如您所注意到的那样,这也将使会话 Cookie 无效,而这可能是您所想要的,也可能不是。
.NET 最终边界
亲爱的 Web Team:
我可以在 Internet Explorer 中使用以 C# 编写的 .NET 对象吗?
谢谢,
一位好学的读者
Web Team 的答复:
它是个具有感染力的平台,对吗?亲爱的读者,答案是:您可以用任何 .NET 语言来创建 Windows 窗体 (WinForms) 控件,并使用一个专为 .NET 程序集设计的变量 OBJECT 标记语法将该这些控件寄宿在 Internet Explorer 中:
<OBJECT ID="obj1" CLASSID="AssemblyName.dll#Namespace.Name.ControlName"> </OBJECT>
AssemblyName.dll 是程序集驻留在 Web 服务器上时的名称,而 Namespace.Name.ControlName 是 WinForms 控件的完全限定名称。(有关更详细的说明,请参阅 .NET Frameworks Documentation 主题)
要在 Visual Studio® .NET 中创建 WinForms 用户控件,请选择 File/Project/New..,从左边窗格中选择语言,然后从右边选择 Windows Control Library。这将创建一个程序集,该程序集带有一个派生自 Systems.Windows.Forms.UserControl 的默认控件。(这样命名是因为 Visual Basic 6.0 中的 ActiveX 控件称为 UserControls — 确保不要将它们与 ASP.NET 中的 User Controls 相混淆,后者是自定义 WebForms 控件。只需将它们称为“自定义 WinForms 控件”即可,这样我们会很乐于接受。)可以向该控件添加属性和方法,在 Internet Explorer 中运行该控件,并从 JScript® 或 Visual Basic Script Edition (VBScript) 中调用您的属性和方法。
您仍然可以结合使用 PARAM 标记和 OBJECT 标记,以向控件提供运行时初始化数据吗?当然可以!实际上,.NET 使得该操作较之以往更为简单。只需在您的控件上定义公共属性,并将相同的名称用作 PARAM 标记的 Name 属性即可。以下是简单的 C# 属性定义:
public string XmlConfigData { get { return(xmlUrl); } set { xmlUrl = value.ToString(); } }
及其相应的 PARAM 标记:
<OBJECT ID="obj1" CLASSID="AssemblyName.dll#Namespace.Name.ControlName"> <PARAM Name="XmlConfigData" Value=http://servername/configData.xml /> </OBJECT>
Internet Explorer Runtime 主机 (IEHost.dll) 将使用 Reflection 来检查控件上的属性,如果其发现属性匹配,则会自动创建一个属性集。
OBJECT 标记的这一用法有几个限制。您不能用它来加载存储在本地计算机的全局程序集缓存 (GAC) 中的对象。(但是,您的控件可以使用它需要的 GAC 中的任何对象。)您可以结合使用该语法和 CAB 文件,但是截止到 Internet Explorer 6.0 发布,每个 CAB 文件只能具备一个程序集。最后,您的程序集必须通过 HTTP 或 HTTPS 进行下载。您不能从文件系统加载程序集。
有一些针对脚本 WinForms 对象的限制,即,您只能在顶层对象上调用方法和属性。该运行库为您的控件创建 COM 可调用包装,以便使其看起来像一个要进行脚本撰写的普通 COM 对象,但是不要对从该控件返回的任何 Object 类型执行同样的操作。根据控件执行的操作,这可能会要求您采取主动的封装计划。
您还需要考虑 WinForms 控件的目标使用者。最终用户必须在他们的计算机上安装了 .NET 运行库。.与大小约为 5 兆字节 (MB) 的 Java 虚拟机下载不同,.NET 运行库重新发布(大小达 20 MB)不会进行自动安装。因此,在企业 Intranet 设置中部署 WinForms 控件的速度可能要比在 Internet 中进行部署快得多。幸运的是,Internet Explorer 将修改其 User-Agent 字符串,以指示客户端计算机上安装的公共语言运行库 (CLR) 的版本,因此,如果早期采用者的用户无法支持 .NET,则他们可以转而求助于 DHTML 或 ActiveX。那些使用 ASP.NET 的人可以通过使用在 System.Web 中定义的HTTPRequest.Browser.ClrVersion 属性来获取这一益处。
既然有这些困难,那么为什么您愿意将 WinForms 控件寄宿在 Internet Explorer 中,而不利用该绝妙因素呢?尽管有问题,但 WinForms 控件在许多方面都优于其 ActiveX 副本。因为其多数功能来自 .NET Framework,所以它们比较小,并且易于下载。Framework 的丰富性意味着组件重复使用的几率不大,如同 Visual Basic 和(甚至是)C++ ActiveX 控件中的典型情况。对于那些担忧安全性的人而言(谁又不是这样呢?),控件运行在新的 .NET 安全模型下,从而允许用户和站点管理员以高级别精度来设置权限。默认情况下,Internet Explorer 中的 WinForms 控件运行在锁定框内部,该锁定框只允许这些控件具有连接回其主机,并写入文件系统中特定的、受保护的区域的权限,而不具备其他权限。如果您希望自己的控件访问用户的文件系统,则(Internet 上的)用户或(Intranet 上的)站点管理员需要为您的控件授予这一更高级别的信任。尽管有些人将其看作是一种限制,但它的确是一个功能强大的模型,而且它将最终消除许多过去令我们备受困扰的严重的 ActiveX 安全漏洞。
Internet Explorer 中的 WinForms 控件是一个广泛且深入的主题,我们只是对其进行了浅显的介绍,特别是在安全性方面。我们的小组正在竭尽全力编制文档(只要看看这些手,您就会知道我们有多辛苦,看到了吗?!),因此去 MSDN 获取更多的信息吧。
Pop Pop, Shift Shift..
亲爱的 Web Team:
我将一些代码从 Perl 转换为 Jscript,以模拟 Reverse Polish Notation (RPM) 计算器。但是,该代码将使用数组方法 push()、pop()、shift() 以及 unshift(),并且我需要以缺少这些方法的 Internet Explorer 版本为目标。我该如何做?
谢谢,
Logan Kearsley
Web Team 的答复:
嗯,Perl,一度被称为“Web 的输送带”。严格地说,Perl 的语言整洁性及其数组的简单性确实有助于简化工作。Push() 和 pop() 从数组开始处插入并移除元素,因此我们可以将数组看作是由 FIFO(先进先出)规则控制的堆栈。Shift() 和 unshift() 对数组结束处执行同样的操作,因此我们可以将数组看作是由 LIFO(后进先出)规则控制的队列。
正如您可能会从 JScript 版本信息页中看到的那样,Logan,这些运算符只在 Internet Explorer5.5 或更高版本中受支持。(5.5 中提供的另一种便捷方法是 splice(),该方法从数组的任意位置处移除 1..n 个元素。Pop() 和 unshift() 均是便捷方法,您可以使用 splice() 来实现这两种方法。)
最佳解决方案是创建您自己的 JScript 堆栈和队列对象。因为对于 RPN 计算器而言,你需要的就是堆栈,就是下面我们所定义的那个堆栈。但是,也可以将该定义用作独立于版本的队列的模板(只需重命名方法并反向执行操作即可)。
// A version-independent stack object prototype for JScript. // We use an index into the array to increase efficiency instead // of making expensive calls to slice(); old // values will just be blown away on subsequent push() operations. function Stack() { // Private data var stackPtr; var stackArr; // Methods this.Push = internal_push; this.Pop = internal_pop; this.Clear = internal_clear; this.Length = internal_length; // Init this instance. internal_clear(); } // Push an element onto the top (end) of the stack. // @Precondition: elem is not null // @Postcondition: stack.length == stack.length + 1 function internal_push(elem) { stackArr[++stackPtr] = elem; } // Retrieve an element off of the top (end) of the stack. // @Precondition: stack is not null // @Postcondition Old Stack.Length() == StackLength() + 1 function internal_pop() { if (stackPtr >= 0) { return(stackArr[stackPtr--]); } else { throw("Cannot pop off of an empty stack."); } } // Clear the stack and start anew. function internal_clear() { stackPtr = -1; stackArr = new Array(); } // Obtain the current stack length. Since we use an index into the array, // Length() != internalArray.Length. function internal_length() { return(stackPtr + 1); }
善于选择
亲爱的 Web Team:
嗨,我发现这些专栏真的很有帮助。
我是一家公司的开发人员,正试图只使用 Internet Explorer 5 和所有最新的零星编码技术来创建第三代 Web 站点(在 [.NET] 使其完全过时之前:))
我们使用最广泛的技术之一是 SQL XML 模板。
所有的数据处理都在客户端侧完成,以求快速并在具有良好精度的搜索页面上获得复杂的选项。
但是我们发现,在使用客户端侧 XMLDOM 来归档链接的选择框时,糟糕的性能令人非常苦恼。我们有一连串共 8 个选择框,而且位于某个组合中的多数选择框是相互依赖的。
您能够给我们推荐一种改善性能的方法吗?
我在信中附寄了一个关于如何构建这些选择框的代码片段。
Public Function bindXML ' find xpath match for attribute Set objXmlNodelist=xmlDom.selectNodes(varXpath) 'get length of list for loop totalnodes=objXmlNodelist.length Set objNode=objXmlNodelist.nextNode 'set selectbox option count to 0 objControl.options.length = 0 i = 0 For t=0 to totalnodes-1 'increase select box options by 1 objControl.options.length =objControl.options.length + 1 ' add option details objControl.options(i).id = objNode.getAttribute(varIndex) objControl.options(i).value = objNode.getAttribute(varValue) objControl.options(i).innerText = objNode.getAttribute(varText) i = i + 1 'move down the node tree Set objNode=objXmlNodelist.nextNode Next End Function
谢谢,
D.conner
Web Team 的答复:
在来信的开头说一些令人高兴的话的确是吸引我们注意力的有效方法!从您对性能问题的描述来看,我们猜测您可能向选择框添加了相当多的新选项。从 Internet Explorer 5 开始,选择框中的选项添加到文档树。这意味着可以通过 document.all() 集合来访问所有的选项。如果您一个接一个地添加大量选项,则会将每个选项插入到页的 DOM 中。我们发现,如果您将新选项的所有 Html 存储在字符串中,那么请立刻使用 outerHTML 来添加该选项,这样它的速度会更快些。即,直到您的字符串变得如此之长以至于字符串的创建开始导致性能问题为止。
在下面的示例中,我们添加了 8000 个新选项。要解决字符串长度的性能问题,我们一次存储 100 个选项的代码,然后将该代码保存在数组中。在我们准备添加选项时,只需使用数组中的一个 join 将所有的短字符串合并为一个长字符串即可。
相反,我们还可以使用 createElement 方法来创建新选项。这是 MSDN 联机库中的 DHTML 联机参考中推荐的方法。在一次添加相对较少的选项时,该方法会稍微简单些。接下来,我们将一些计时器代码添加到上述两种方法,这样您就可以看出每种方法执行速度的快慢。我们确实发现,createElement 方法在处理您发给我的代码时,只花费了您所需时间的一半。虽然如此,它填充完所有的 8000 个选项所花费的时间还是超过了两分钟。使用 outerHTML 和字符串方法,同样数量的选项只需不到一秒的时间!
使用 outerHTML 技巧确实有一个缺点,即它会导致选择框在新选项出现之前,消失一瞬后再出现。我们试图替换选择框的 innerHTML,但是在我们的 Internet Explorer 版本中,字符串的初始字节消失了,使得 HTML 无效并生成空选择框。如果选择框的闪烁对于您来说是个问题,那么请使用您自己环境中的 innerHTML 进行测试,以防万一。
以下是我们在解决这个小问题时随意编写的代码。
<html><head> <script language="vbscript"> sub rewriteBox Dim optStrings(100) startTime = timer() 'generate the html strings, save up to 100 in each array element For i=0 to 8000 optStr = "<option value=""opt" & i & """>Option " & i optStrings(i / 100) = optStrings(i / 100) & optStr Next S1.outerhtml="<select id=""S1"">" & join(optStrings) & "</SELECT>" ProcessTime.value = timer - startTime end sub sub fillBox startTime = timer() for i=0 to 8000 set myOpt = document.CreateElement("OPTION") myOpt.value = "opt" & i myOpt.text = "option " & i S2.add(myOpt) next ProcessTime.value = timer - startTime end sub </script> <style>body {font-family:arial;font-size:10pt}</style> </head> <body> <button οnclick="rewriteBox">replace select</button> <select id="S1"><option>placeholder</option></select> <br /> <br /> <button οnclick="fillBox">add in options</button> <select id="S2"><option>placeholder</option></select> <br /> <br /> seconds to process: <input id="ProcessTime" value="0" /> </body></html>
Web Team 简短问答
再谈编码 URL
问:Craig 对 Microsoft Windows Scripting 提供的函数集 escape() 和 encodeURI() 之间的差异有许多疑问。
• | Microsoft Internet Explorer 的哪个版本支持较新的首选函数? |
• | 您是说 escape() 应该从不用于 URL 编码吗? |
• | 曾有过适当使用 escape() 进行 URL 编码的情况吗?或是它干脆就不能这样使用? |
答:这些问题来自最近的一篇 Web Team 访谈文章。
1. | Microsoft JScript(版本 5.5 或更高)附带提供较新的函数集(包括 encodeURI())。Microsoft Internet Explorer 版本 5.5 将安装 Windows Script 版本 5.5。您可以从 Microsoft Scripting Technologies Web 站点下载最新版本的 Windows Script。 |
2. | escape() 函数旨在将非 ASCII 字符和标点符号转换为可以跨 Internet 进行传输的便携格式。建议您,只要有可能,就使用 encodeURI() 函数,因为该函数旨在编码 URL。 |
3. | 当需要在 Internet 中发送二进制信息或对标点符号的使用有限制时(例如在 Cookie 值中),请使用 escape() 函数。 |
什么是我们的 Vector、Victor?
问:Marcel 注意到 Vector Markup Language (VML) 线在 Microsoft Internet Explorer 6 中是抗锯齿的,因此想知道如何取消这一行为。
答:抗锯齿是一项使线边缘变得平滑以生成整洁图像的技术。您可以查看下列示例 HTML 中的对角线(那条黑色线)上的抗锯齿效果。Internet Explorer 6 的默认行为是打开 VML 元素的抗锯齿功能。如果您需要利用像素准确定位 VML 元素,则可通过将 antialias CSS 属性设置为假来显式关闭抗锯齿功能。
<html xmlns:v="urn:schemas-microsoft-com:vml"> <head> <style> v/:* {behavior:url(#default#VML);} </style> </head> <body> <v:line style='antialias:false' from="163pt,127pt" to="252pt,227pt" strokeweight="2.5pt" strokecolor="red"/> <v:line style='antialias:true' from="252pt,127pt" to="352pt,227pt" strokeweight="2.5pt"/> </body> </html>
调用存储过程
问:David 在使用 ADODB 执行存储过程时碰到了错误(800a0bb9 - "Arguments are of the wrong type, are out of acceptable range, or are in conflict with one another.")。该错误在设置 CommandType 属性时出现。以下是他的 Active Server Page (ASP) 中的 VBScript 代码片段:
.. SET CMD = SERVER.CREATEOBJECT("ADODB.Command") .. CMD.CommandText = "{?=call sp_password(?, ?)}" CMD.CommandType = adCmdStoredProc
答:该错误的出现是因为您将存储过程调用的扩展形式用作了 CommandText 属性。当指定 CommandType 属性而不是 adCmdUnknown 时,CommandText 属性必须包含与该命令类型相匹配的队列。如下所示,或者将 CommandText 属性设置为存储过程名称,或者将 CommandType 属性设置为 adCmdUnknown(默认)或 adCmdText。
.. CMD.CommandText = "sp_password"
有关详细信息,请参阅知识库文章 HOWTO:Call SQL Server Stored Procedures from ASP。
ReadyState,前进
问:Jon 在 XML 数据岛的 readyState 属性中看到了一些不寻常的行为。以下代码将加载一个由 ASP 页生成的 XML 文件。有两个问题,第一个是:ReadyState 属性返回数值(文档上说这是一个字符串);第二个是:Internet Explorer 显示错误对话框“Internet Explorer cannot open the Internet site http://localhost/xmltest.htm. Operation aborted.”。
<HTML> <BODY> <P ID=pBreak> </BODY> <SCRIPT LANGUAGE=VBScript> Sub Check() Msg CStr(xmlTest.ReadyState) If xmlTest.ReadyState = 4 Then Msg "COMPLETE" End Sub Sub Msg(Content) pBreak.InsertAdjacentHTML "AfterEnd", Content & "<BR>" End Sub </SCRIPT> <XML ID=xmlTest SRC="http://localhost/xmldata.asp" ONREADYSTATECHANGE="Check()"></XML> </HTML>
答:您在此处看到的第一个行为是由于使用 VBScript 而导致的一个有趣异常。如果使用的是 JScript,则不会看到这个行为。为什么会这样?嗯,因为 VBScript 并不区分大小写。多数情况下,这是一个不错的特征。如果将属性命名为 readyState,并将其作为 ReadyState 引用,也不要紧,对吗?错!在这一不寻常的情况下,对象会返回一个不同的属性,该属性带有一个数值。一个替代方案是引用正确的属性名称,如下所示:
xmlTest.readyState
这样将返回一个字符串值,例如,“loading”。或者,使用 XML 文档 readyState 属性,这样会返回一个数值:
xmlTest.XMLDocument.readyState
第二个行为的出现或许应归因于您使用 DHTML 修改文档内容的方式。尽管我们并不完全了解您看到错误的幕后原因,但是通过更改您的 Msg 子例程来执行以下操作,将会解决该问题:
pBreak.InsertAdjacentHTML "BeforeBegin", Content & ""
我们还发现,在 onreadystate 事件处理程序退出后,调用 setTimeout 来强制更新文档出现将会解决这一问题。这一行为的产生或许是因为您是从 onreadystate 事件处理程序中来修改文档。
Web Team
Mark Davis 是 Internet Explorer SDK 小组的一名软件设计工程师。Mark 来自英格兰,目前正在进行攀登西北部主峰的训练。
Heidi Housten 是瑞典 Microsoft Consulting Services 的一名咨询师,之前他曾在 Developer Support 和 MSDN 工作过一段时间。有人说她是为了逃避西雅图的细雨才来到瑞典,这只是一个谣言。其实她来这是为了参加 8 月份的传统小龙虾宴会。
Dan Mohr 是 Microsoft Developer Support 的 Internet Client 小组的一名工程师,他业余时间喜欢在自己的地下室录制磁带、编写他的 Commodore 64 程序,以及赞美 70 年代末期的朋克摇滚乐的优点。
Jay Allen是 Microsoft Developer Support 的 Internet Client 小组的一名支持工程师,他渴望将记事本和 Emacs Lisp 集成。在工作之余,他将绝大部分时间都用来陪自己的四个孩子,剩下的一点时间则通常用来阅读数学书籍、学习日语和用 Haskell 编程。