ASP.NET 自定义控件客户端脚本生成

分享于 

21分钟阅读

Web开发

  繁體 雙語

介绍

新的状态管理和 ASP.NET的postback 特性确实非常令人兴奋。 它们为开发人员提供了一种全新的生成动态网页的机制。 编写自己的自定义控件能够使你能够编写一个具有自定义功能的控件。 实现页面布局的人不再需要知道如何编写客户端代码来获得已经流行的动态行为。 但是,开发人员需要注意的一些陷阱。 ASP.NET 提升了服务器的重大设计,因为每个客户端事件可能会引起服务器的循环。 很多由这些频繁的服务器结果产生的效果可以很容易地完成,一些简单的JavaScript函数。 对服务器的调用应该保持最小,尽可能多地在客户端上完成。 使用自定义控件生成客户端脚本,我们可以利用客户机上的动态HTML来分离布局和逻辑。

客户端脚本生成

从自定义控件生成脚本的目标之一是允许开发人员创建控件并指定它的行为。 为了将控件的HTML呈现与传统的web组件重用方法相结合,我们希望封装控件的实现并紧密地将HTML呈现与脚本结合起来。 剪切&粘贴,并包含文件)。 脚本生成最直接的方法是编写脚本以及在 呈现 控件( 请参见下面的代码:)的方法。


namespace Spotu


{


 public class HelloWorld : Control


 {


 protected override void Render (


 HtmlTextWriter writer


 )


 {


 writer.Write(@"


 <script>


 function HelloWorld()


 {


 document.all('_msg').innerText = 'Hello World';


 }


 </script>");



 writer.Write("<button onclick='javascript:HelloWorld()'>"


 + "Click Me</button>");



 writer.Write("<div id=_msg></div>");


 }


 }


}



代码 block below 显示了使用 HelloWorld 使用客户端脚本生成类。


<%@ Page language="c#" %>


<%@ Register Namespace='Spotu'



             TagPrefix='spotu'



             Assembly ='helloworld' %>



<html>


<body>


<form runat="'server'">


<spotu:HelloWorld runat="'server'/">


</form>


</body>


</html>



这里方法工作,并且解决了允许开发人员编写自定义控件以提供动态功能的初始问题。 但是,它不是非常优雅,它有一些缺点,最重要的是不能在页面中多次使用同样的with创建。 即使我们在这个例子中唯一的元素是 NAME,它仍然是低效的,因为JavaScript用这个控件写出。 这可能会产生大量开销,将同一脚本向客户端发送到控件的每个实例。

我们需要一些方法来使控件生成脚本,但只生成一次,即使在同一页上使用多个控件。 幸运的是,微软的开发人员想到了这一点,并提供了一种 register 脚本 block的方法。 Page.RegisterClientScriptBlock 这里方法接受两个参数,一个标识脚本 block 以便 页面 类将知道忽略对 register的同一 block的任何它的他请求,以及包含要注册的脚本的字符串。 脚本块的最佳位置是 初始化 控件的事件处理程序。 若要利用此事件,请重写 OnInit 方法 控件 注意,下面的代码可以重写HelloWorld示例,如下所示:


using System;


using System.Web;


using System.Web.UI;



namespace Spotu


{


 public class HelloWorld : Control


 {


 protected override void OnInit(EventArgs e)


 {


 string strCode = @"


 <script>


 function HelloWorld(id)


 {


 document.all(id).innerText = 'Hello World';


 }


 </script>";



 Page.RegisterClientScriptBlock("Spotu_HelloWorld",


 strCode);


 }



 protected override void Render(HtmlTextWriter writer)


 {


 writer.Write("<button onclick='javascript:HelloWorld(""


 + this.UniqueID + "")'>"


 + "Click Me</button>");



 writer.Write("<div id='" + this.UniqueID 


 + "'></div>");


 }


 }


}



这种方法更好,但仍然存在问题。 当我们创建脚本时,数据访问仍然会受到影响,因为已经注册了最终的脚本,因为已经注册了该脚本,因此我们将在页面中执行。 注册脚本的block 之后,我们就可以使用 Page.IsClientScriptBlockRegistered 方法,以提高 HelloWorld 控件中,我们将在我们 OnInit 方法如下所示:


protected override void OnInit(EventArgs e)


{


 if (!Page.IsClientScriptBlockRegistered("Spotu_HelloWorld"))


 {


 string strCode = @"


 <script>


 function HelloWorld(id)


 {


 document.all(id).innerText = 'Hello World';


 }


 </script>";



 Page.RegisterClientScriptBlock("Spotu_HelloWorld",


strCode);


 }


}




使用自定义控件中的客户端脚本生成提供了在网页中启用动态行为的方法。 现在,开发者可以自由控制如何让控件 do,而不陷入困境 were marketing marketing marketing control control control control control control control control control。 by this设计人员可以轻松地自定义和重用多个页面,并且不需要开发人员干预,也可以使用&粘贴代码重用或者剪切代码重用。

高速缓存

你们中有些人可能会问: 如何缓存这里脚本,以便每次都不下载该脚本? 毕竟,客户端脚本往往是相当的static,不需要每次加载网页时都需要下载。

缓存控件的输出有几个选项。 ASP.NET 方法将利用输出缓存。 有很多输出缓存选项,但是大多数都是在. aspx 页面中使用指令和标志设置缓存的负责。 同时,缓存整个页面可能不是预期的效果。 有些页面是非常动态的。 在这种情况下,理想的做法是只缓存控件或者控件的某一部分。 ASP.NET 确实有一些支持,但是该支持主要是为用户控件(. ascx 文件) 保留的,这不提供我们的重用。

对于提供生成脚本的定制控件,我们可能需要考虑使用外部脚本文件。 正如我们已经注意到的,大多数脚本不会经常发生变化,如果在客户端上可以缓存。 我们可以将脚本放在外部脚本文件中,而不是直接写出脚本,而是将脚本写出到外部脚本文件中。 <脚本。> 带标签的标签 src=。" 引用我们的脚本文件的属性。 这允许控件在必要时频繁地波动,而不会导致总是将脚本下载到客户端的网络流量。 这种方法的主要缺点是部署。 现在有两个需要部署的文件,以便在页面中使用控件,. js 文件必须从使用它的页中访问。 由于使用控件的每个页可能位于不同的级别,因此 relative 路径并不总是有效的。 一个部署解决方案是在应用程序的顶层创建目录( 例如: 包含),并在你的控件中引用 Request.ApplicationPath +"/includes/<这里的脚本文件>" 另一种方法可以能是在控件上提供自定义属性,以便在. aspx 页中指定外部源文件的位置。 代码显示 below 是使用这种方法实现的计算器的一个例子。


using System;


using System.Web;


using System.Web.UI;


using System.Collections.Specialized;



namespace Spotu


{


 public class Calculator : Control, IPostBackDataHandler


 {


 const string sc_strStyleClass = "calcButton";



 private string _strNumButton;


 private string _strOpButton;


 private string _strScriptSrc;


 private string _strStyleHref;


 private string _strSavedValue;


 private int _intCalcValue = 0;



 // Custom property for explicitly setting the location


 // of the script file


 public string ScriptSrc


 {


 get { return _strScriptSrc; }


 set { _strScriptSrc = value; }


 } // End ScriptSrc



 // Custom property for explicitly setting the location


 // of the stylesheet file


 public string StyleSrc


 {


 get { return _strScriptSrc; }


 set { _strScriptSrc = value; }


 } // End StyleSrc



 
#region IPostBackDataHandler// LoadPostData gets call when the 'save' button// rendered by this control is clickedpublicvirtualbool LoadPostData (
 string postDataKey,
 NameValueCollection values
 )
 {
 _strSavedValue = "Saved Value:" 
 + values[UniqueID + "_display"];
 returnfalse;
 } // end LoadPostData// Needed to implement IPostBackDataHandlerpublicvirtualvoid RaisePostDataChangedEvent()
 {
 } // End RaisePostDataChangedEvent#endregion// Loads the state of the control from the // viewstate managed by. NETprotectedoverridevoid LoadViewState (
 object savedState
 )
 {
 _strSavedValue = savedState asstring;
 } // End LoadViewState// Saves the state of the controlprotectedoverrideobject SaveViewState()
 {
 return _strSavedValue;
 } // End SaveViewState// Init event handler, called to initialize any state// in the object before the viewstate is restored.protectedoverridevoid OnInit (
 EventArgs e
 )
 {
 _strNumButton = string.Format("<button" + "onclick='javascript:g_{0}.EnterNumber(this.innerText);'" + " class='{1}'>", this.UniqueID, sc_strStyleClass);
 _strOpButton = string.Format("<button" + "onclick='javascript:g_{0}.OnOperator(this.innerText);'" + "class='{1}'>", this.UniqueID, sc_strStyleClass);
 if (_strScriptSrc == null)
 {
 _strScriptSrc = Context.Request.ApplicationPath 
 + "/includes/calc.js";
 }
 if (_strStyleHref == null)
 {
 _strStyleHref = Context.Request.ApplicationPath 
 + "/includes/calcStyle.css";
 }
 string strScriptBlock = "<script src='" + _strScriptSrc 
 + "'></script>";
 Page.RegisterClientScriptBlock("Spotu_Calculator",
 strScriptBlock);
 string strStyle = "<link rel='stylesheet'" + "type='text/css' href='" + _strStyleHref 
 + "'></link>";
 Page.RegisterClientScriptBlock("Spotu_Calculator_Style",
 strStyle);
 } // End OnInit// Load Event Handler. Retrieve the value posted in the// display field of the calculator so we can keep the// state of the display regardless of how the form is// submittedprotectedoverridevoid OnLoad (
 EventArgs e
 )
 {
 if (Page.IsPostBack)
 {
 _intCalcValue = 
 Int32.Parse(Context.Request.Form[UniqueID
 + "_display"]);
 }
 } // End OnLoad// Render out the controlprotectedoverridevoid Render (
 HtmlTextWriter writer
 )
 {
 string strHtml = string.Format(@" <script> var g_{0} = new Calc('{0}_display'); </script>
 <table>
 <tr colspan='*'>
 <input type='text'
 name='{0}_display'
 readonly=true
 value={4}>
 </input>
 </tr>
 <tr><td>{1}7</button></td>
 <td>{1}8</button></td>
 <td>{1}9</button></td>
 <td>{2}/</button></td>
 <td>
 <button 
 class='{3}'
 onclick='javascript:g_{0}.OnClear();'>
 C
 </button>
 </td>
 </tr>
 <tr><td>{1}4</button></td>
 <td>{1}5</button></td>
 <td>{1}6</button></td>
 <td>{2}*</button></td>
 </tr>
 <tr><td>{1}1</button></td>
 <td>{1}2</button></td>
 <td>{1}3</button></td>
 <td>{2}-</button></td>
 </tr>
 <tr><td>{1}0</button></td>
 <td></td>
 <td>{1}.</button></td>
 <td>{2}+</button></td>
 <td>
 <button
 class='{3}'
 onclick='javascript:g_{0}.OnEqual();'>
 =
 </button>
 </td>
 </tr>
 </table>", UniqueID,
 _strNumButton,
 _strOpButton,
 sc_strStyleClass,
 _intCalcValue);
 writer.Write(strHtml);
 writer.Write("<INPUT type='submit' name='" + this.UniqueID + "' value='Save'></INPUT>");
 writer.Write("<H3 id='" + UniqueID + "_savedVal'>" + _strSavedValue + "</H3>");
 } // End Render }
}

calculator.aspx


<%@ Page %>


<%@ Register Namespace='Spotu'



             TagPrefix='spotu'



             Assembly ='calc' %>



<html>


<body>


<form runat="'server'">


 <spotu:Calculator runat="'server'/">


 <hr>


 <spotu:Calculator runat="'server'/">


</form>


</body>


</html>



计算器的JavaScript源文件


function Calc(dispId)


{


 this.intCurrentVal = 0;


 this.intLastNum = 0;


 this._op = "";


 this.bEqual = false;


 this.displayId = dispId;



 this.EnterNumber = function(num)


 {


 if (this.bEqual)


 this.OnClear()



 if (this.intLastNum!= 0)


 this.intLastNum += num;


 else


 this.intLastNum = num;



 document.all(this.displayId).value = this.intLastNum;


 }



 this.ComputeValue = function()


 {


 switch (this._op)


 {


 case '+':


 this.intCurrentVal = Number(this.intCurrentVal)


 + Number(this.intLastNum);


 break;


 case '-':


 this.intCurrentVal -= this.intLastNum;


 break;


 case '*':


 this.intCurrentVal *= this.intLastNum;


 break;


 case '/':


 this.intCurrentVal/= this.intLastNum;


 break;


 default:


 this.intCurrentVal = this.intLastNum;


 }


 document.all(this.displayId).value = this.intCurrentVal;


 }



 this.OnOperator = function(op)


 {


 if (!this.bEqual)


 this.ComputeValue();



 this.bEqual = false;


 this.intLastNum = 0;


 this._op = op;


 }



 this.OnEqual = function()


 {


 this.ComputeValue();


 this.bEqual = true;


 }



 this.OnClear = function()


 {


 this._op = "";


 this.intCurrentVal = 0;


 this.intLastNum = 0;


 this.bEqual = false;


 document.all(this.displayId).value = this.intCurrentVal;


 }


}



计算器按钮样式表


.calcButton


{


 width=25;


}



检查代码

要注意的一项是,对定义计算器按钮样式的样式表的引用位于 OnInit 方法以及脚本 block 注册。 注册客户端代码块不只限于"脚本"。 这里的样式表允许设计者修改. css 文件的外观和感觉,从而允许设计者修改按钮的外观。 允许页面设计器更改计算器外观和感觉的另一种方法是实现自定义属性,或者使用子属性( 例如: font-style,font-size,等等 )。 这种方法似乎有些限制,因为设计器可以只更改你所公开的属性。 如果没有任何可以供用户直接访问HTML元素或者不为它的应用类或者样式的选项,设计器将在其中使用所有可用的选项。

当呈现控件而不是包含在. js 文件中时,会出现一个脚本的block。 这允许在同一页中使用计算器控件的多个实例。 iPhone 7 还没出来,我们已经在iPhone上获取细节 8,或者不管是想到下一步。 UniqueID 属性继承自 控件 类用于区分控件与其他控件之间的区别。 iPhone 7 还没出来,我们已经在iPhone上获取细节 8,或者不管是想到下一步。 UniqueID 属性是标识页中控件的实例的唯一标识符。

样式表和外部脚本文件的位置默认为位于虚拟应用程序 root的/includes 目录。 但是,提供了两个自定义属性,允许设计器重写这些文件所在的位置。

通过使用 UniqueID 对于作为提交按钮的NAME的控件,我们确保 LoadPostData 仅当单击控件的'保存'按钮时才会调用控件的方法。 如果我们将文本框命名为 UniqueID 然后,我们将最终保存页面上所有控件的计算号,无论如何提交到服务器。 这个例子有点做作,如果你真的认真地减少服务器负载,可以改变'保存'按钮,这样就不用将表单发送回服务器,而将表单发送回 Web服务 调用。

结束语

使用自定义控件生成客户端脚本可以带来巨大的好处。 自定义控件类似于使用 ASP.NET 编写的任何它的他控件,使用户可以轻松地重用和屏蔽页面设计器。 通过使用客户端脚本创建动态行为,可以大大提高网站的响应性和网站的总性能。 为脚本使用外部文件具有正反两面的优点。 优点包括利用浏览器缓存和易于定制的可以定制性。 缺点包括在生产环境中以及设计时环境中更复杂的部署。

下载

下载演示项目并将它的解压到虚拟应用程序的root 中。 calculator.aspx 文件应该位于虚拟应用的root 目录中,虚拟应用程序的calc.js 和 caclStyle.css 文件中的文件,以及虚拟应用程序的/bin 目录中的calc.dll 文件。


相关文章