2008年2月18日
#
摘要:
1、使用了大量的计算,每次滚动,都少不了一大堆的运算。
2、使用了JavaScript脚本,脚本这东西受到浏览器的限制,即便没有限制,也同样因为网页的下载模式问题,可能因为脚本下载未完成或者下载失败而致使脚本无法正确运行。
3、条条大路通罗马,但是看看那方块,当快速滚动滚动条的时候,会发现那个可爱的方块抖动地厉害。这也难怪,那么多的计算谁能受得了?
阅读全文
[有兴趣阅读本文的请从头至尾阅读,有兴趣帮助我解答疑问的请从尾至头读(红色部分),万分感谢!]
我们很容易理解在旧有编程模型中关于类实例的内容。设计模式中Singleton也就是在描述着档子事。但基于WCF并非适合于以上场景,Service与Client之间要保持良好的Instance模型则需要依靠很多其他机制。
Programming WCF Service Chapter4 对此进行了细致的描述。(更多细节请自行阅读~)
WCF支持三种类型的Instance管理:
1、pre-call services:每个客户端请求对应一个instance
2、Sessionful services:每个客户端连接对应一个instance
3、Singleton services:所有客户端共享一个instance
利用Behaviors可以解决这方面的问题(还有一些其他基于“服务端”的其他方面的问题可以通过使用behaviors来解决)。
注:客户端是不知道服务端设置了什么样的behaviors的。
VS2008MSDN:ms-help://MS.VSCC.v90/MS.MSDNQTR.v90.en/fxref_system.servicemodel/html/88efb135-d425-e5b1-57d6-01a67158c1a5.htm
Apply the ServiceBehaviorAttribute attribute to a service implementation to specify service-wide execution behavior. (To specify execution behavior at the method level, use the OperationBehaviorAttribute attribute.) This attribute can be applied only to service implementations.
ServiceBehaviorAttribute:仅应用于服务实现。
OperationBehaviorAttribute:用于方法级别。
//瞧这里什么属性都没有
public interface IMyContract
{}
//而是设置在了具体服务实现上
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
public class MyContract : IMyContract,IDisposable
{}
设置instance模式类型由ServiceBehaviorAttribute的属性InstanceContextMode进行设置,默认值为PerSession.
Per-Call Services
只有当客户端调用的时候才有instance。
为了说明问题,书中用了很形象的例子。
Code:
public interface IMyContract
{
[OperationContract]
string GetData(int value);
[OperationContract]
CompositeType GetDataUsingDataContract(CompositeType composite);
// TODO: Add your service operations here
[OperationContract]
void Count();
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
public class MyContract : IMyContract,IDisposable
{
//Other Members
public MyContract()
{
Trace.WriteLine("WcfServiceLibrary1.MyContract()");
}
#region IMyContract Members
int count = 0;
public void Count()
{
count++;
Trace.WriteLine("Counter = " + count);
}
#endregion
#region IDisposable Members
public void Dispose()
{
Trace.WriteLine("WcfServiceLibrary1.Dispose()");
}
#endregion
}
//Tester
ServiceReference1.MyContractClient proxy = new ConsoleApplication1.ServiceReference1.MyContractClient();
proxy.Count();
proxy.Count();
proxy.Count();
proxy.Close();
Console.ReadKey();
结果为:
WcfServiceLibrary1.MyContract()
Counter = 1
WcfServiceLibrary1.Dispose()
WcfServiceLibrary1.MyContract()
Counter = 1
WcfServiceLibrary1.Dispose()
WcfServiceLibrary1.MyContract()
Counter = 1
WcfServiceLibrary1.Dispose()
很明显,每次的值都是0+1的结果,这正说明了percall的方式是每个请求一个Instance的。
Per-Session Services
修改上面的例子:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
为:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
结果为:
WcfServiceLibrary1.MyContract()
Counter = 1
Counter = 2
Counter = 3
WcfServiceLibrary1.Dispose()
很明显Instance只有一个了。
我们WcfServiceLibrary默认的Bind是wsHttpBinding,但若是basicHttpBinding,由于每个http到达服务端都是一个新的连接,因此服务端无法判断是哪个连接。
增加服务端app.config中Endpoint。
<endpoint address="basic" binding="basicHttpBinding" name="basic" contract="WcfServiceLibrary1.IMyContract" />
重新导入后修改Program里的程序:
ServiceReference1.MyContractClient proxy = new ConsoleApplication1.ServiceReference1.MyContractClient("basic");
或
ServiceReference1.MyContractClient proxy = new ConsoleApplication1.ServiceReference1.MyContractClient("WSHttpBinding_IMyContract");
其中basic和WSHttpBinding_IMyContract为两种不同形式的服务在客户端的Endpoint.Name。
之前默认WSHttpBinding_IMyContract,现在由于存在多个Endpoint,则需要显示指定。
现指定为basic。再次运行,结果:
WcfServiceLibrary1.MyContract()
Counter = 1
WcfServiceLibrary1.Dispose()
WcfServiceLibrary1.MyContract()
Counter = 1
WcfServiceLibrary1.Dispose()
WcfServiceLibrary1.MyContract()
Counter = 1
WcfServiceLibrary1.Dispose()
其结果与PerCall是相同的。
通过SessionId可以获得Instance的SessionId
使用Per-Session方式可以通过设置SessionMode属性(允许、必须、不允许三种枚举)。
SessionMode:Gets or sets a value that indicates whether a session is required by the contract.
SessionMode 枚举http://msdn2.microsoft.com/zh-cn/library/system.servicemodel.sessionmode.aspx
|
Allowed(允许)
Specifies that the contract supports sessions if the incoming binding supports them.
如果绑定支持Session的话,则让其支持,否则按照可以支持的方式,比如PerCall的方式进行支持。
|
|
Required(必须)
Specifies that the contract requires a sessionful binding. An exception is thrown if the binding is not configured to support session.
指定契约必须使用Sessionful的方式。如果不支持,则抛出异常。
|
|
NotAllowed(不允许)
Specifies that the contract never supports bindings that initiate sessions.
指定不能使用Sessionful的方式。作者推荐是用NotAllowed的时候仅用PerCall方式。
|
刚才由于我添加了basic的方式,因为默认选中Allowed,因此刚才的之所以结果与PerCall相同,是因为它,下面我将其修改为Required。
将
[ServiceContract]
修改为
[ServiceContract(SessionMode=SessionMode.Required)]
结果为一个运行时错误:
|
System.InvalidOperationException: Contract requires Session, but Binding 'BasicHttpBinding' doesn't support it or isn't configured properly to support it.
at System.ServiceModel.Description.DispatcherBuilder.BuildChannelListener(StuffPerListenUriInfo stuff, ServiceHostBase serviceHost, Uri listenUri, ListenUriMode listenUriMode, Boolean supportContextSession, IChannelListener& result)
at System.ServiceModel.Description.DispatcherBuilder.InitializeServiceHost(ServiceDescription description, ServiceHostBase serviceHost)
at System.ServiceModel.ServiceHostBase.InitializeRuntime()
at System.ServiceModel.ServiceHostBase.OnOpen(TimeSpan timeout)
at System.ServiceModel.Channels.CommunicationObject.Open(TimeSpan timeout)
at System.ServiceModel.Channels.CommunicationObject.Open()
at Microsoft.Tools.SvcHost.ServiceHostHelper.OpenService(ServiceInfo info)
|
但是若使用wsHttpBinding,但却without security and without reliable messaging也将无法维持transport-level session。
运行结果与PerCall的结果相同。其原因也就是因为wsHttpBinding未设置安全可靠的Session。
超时
inactivityTimeout:超时时间
在连接空闲的情况下,以客户端和服务端的超时时间中最短的那个来决定是否移除Instance,若之后再调用则会抛出异常。
作者额外注明可以采用:AutomaticSessionShutdown属性。其设置为true则当proxy.Close()的时候自动关闭Session,设为false的时候则只有在服务端将服务关闭才会关闭Session。
但是,若将其修改为NotAllowed
则结果与PerCall相同(手动写为PerSession)。(不管服务配置如何,它总会是PerCall。因为TCP和IPC协议总是维持transport level,你不能将它们配置SessionMode.NotAllowed,它们会在服务载入时进行验证。作者建议是“在选择使用SessionMode.NotAllowed的同时,将服务配置为PerCall”。)
Singleton Service
Singleton,顾名思义就是仅有一个Instance,供所有客户端调用。
在说明问题之前先修改上面的例子:
//[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
//[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
为:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
|
Tester中:
static void Main(string[] args)
{
ServiceReference1.MyContractClient proxy = new ConsoleApplication1.ServiceReference1.MyContractClient("WSHttpBinding_IMyContract");
proxy.Count();
proxy.Count();
proxy.Count();
Console.WriteLine(proxy.Endpoint.Name);
Console.WriteLine(proxy.InnerChannel.SessionId);
proxy.Close();
Console.WriteLine(proxy.Endpoint.Name);
Console.WriteLine(proxy.InnerChannel.SessionId);
Console.WriteLine("_________________________________________________________");
Console.ReadKey();
ServiceReference1.MyContractClient proxy1 = new ConsoleApplication1.ServiceReference1.MyContractClient("WSHttpBinding_IMyContract");
proxy1.Count();
proxy1.Count();
proxy1.Count();
Console.WriteLine(proxy1.Endpoint.Name);
Console.WriteLine(proxy1.InnerChannel.SessionId);
proxy1.Close();
Console.WriteLine(proxy1.Endpoint.Name);
Console.WriteLine(proxy1.InnerChannel.SessionId);
Console.WriteLine("_________________________________________________________");
Console.ReadKey();
ServiceReference1.MyContractClient proxy2 = new ConsoleApplication1.ServiceReference1.MyContractClient("basic");
proxy2.Count();
proxy2.Count();
proxy2.Count();
Console.WriteLine(proxy2.Endpoint.Name);
Console.WriteLine(proxy2.InnerChannel.SessionId);
proxy2.Close();
Console.WriteLine(proxy2.Endpoint.Name);
Console.WriteLine(proxy2.InnerChannel.SessionId);
Console.WriteLine("_________________________________________________________");
Console.ReadKey();
}
运行的结果:(Output<Debug>)
//客户端调用前
//...
//其他代码
//...
WcfServiceLibrary1.MyContract()
//...
//其他代码
//...
//客户端调用后
//...
//其他代码
//...
Counter = 1
Counter = 2
Counter = 3
Counter = 4
Counter = 5
Counter = 6
Counter = 7
Counter = 8
Counter = 9
//...
//其他代码
//...
从Counter的值看来,多个proxy调用的是同一个Instance。
值得一提的是WcfServiceLibrary1.MyContract(),也就是构造函数的调用时间是在Service启动的时候,而PerCall与Sessionful构造函数调用时间都是在proxy调用之时。而且只有当Host关闭的时候才会Dispose()。
MSDN:
If the InstanceContextMode value is set to Single the result is that your service can only process one message at a time unless you also set the ConcurrencyMode value to Multiple.
也就是说除非将服务设置成多线程的,否则在一个时间只能处理一个消息请求。
从HOST端控制SingletonInstance
方式一:
修改代码:
无须启动WcfServiceLibrary1(将用外部Host进行启动)直接运行客户端程序:
结果:
//客户端调用前
//...
//其他代码
//...
WcfServiceLibrary1.MyContract()
//...
//其他代码
//...
//客户端调用后
//...
//其他代码
//...
Counter = 201
Counter = 202
Counter = 101
//...
//其他代码
//...
方式二:
通过OperationContext.Current.Host 来获取当前进程的host
此方法我暂时未调出来,大家有想到或做到的麻烦告诉我!
ServiceHost host = OperationContext.Current.Host as ServiceHost;
MyContract singletonInstance = host.SingletonInstance as MyContract;
if (singletonInstance != null)
singletonInstance.CountProperty = DateTime.Now.Second;
先假设以上方法可行吧。
现在我遇到的问题:
1、OperationContext.Current这里的(MSDN:Gets or sets the execution context for the current thread.
)current thread是指我ConsoleApplication也就是Client的线程呢,还是指Service端的线程?(据我常理分析应该是服务端的线程)。理论上我应该在Count()方法内写这段代码,但是问题又涉及到通过Client端进行调用时是使用proxy,这样真正的情况会是怎样呢?
2、因此我又写了一个在一个ConsoleApplication里完成服务的代码,此时在host.SingletonInstance的确是被赋值了(不再是null了),但是紧接着我调用OperationContext.Current却发现其为null,也就是说这里的OperationContext并没有被赋值。继而将其转移到同在一个程序内的MyContract.Count()方法中,但是其值仍然为空,因此此法再度失效。
希望作为高手的您能够提供一个使用OperationContext.Current的场景。什么样算是OperationContext的当前线程?
推荐阅读:
http://www.microsoft.com/china/MSDN/library/Windev/WindowsVista/WCFEssentials.mspx?mfr=true
很多朋友已经习惯了在组件或者页面开发时使用内嵌资源的方式进行资源输出,这样的好处包括如下一点,就是利用部分浏览器的相关机理来缓存这些文件而不必每次都加载,它们通常通过一个时间戳来表示该项内容是应该从缓存(客户端本地)中读取还是重新下载(远端服务器),而这个时间戳就被跟在了下载该资源的链接上了。
按说大家通常在测试的时候都是单机环境,因此通常不会发生什么问题,但是在生产环境中或者迁移到别人的机上就会出现一些问题了。
指定的参数已超出有效值的范围。
参数名: utcDate
说明: 执行当前 Web 请求期间,出现未处理的异常。请检查堆栈跟踪信息,以了解有关该错误以及代码中导致错误的出处的详细信息。
异常详细信息: System.ArgumentOutOfRangeException: 指定的参数已超出有效值的范围。
参数名: utcDate
源错误: | 执行当前 Web 请求期间生成了未处理的异常。可以使用下面的异常堆栈跟踪信息确定有关异常原因和发生位置的信息。 |
堆栈跟踪: | [ArgumentOutOfRangeException: 指定的参数已超出有效值的范围。 参数名: utcDate] System.Web.HttpCachePolicy.UtcSetLastModified(DateTime utcDate) +3352419 System.Web.HttpCachePolicy.SetLastModified(DateTime date) +47 System.Web.Handlers.AssemblyResourceLoader.System.Web.IHttpHandler.ProcessRequest(HttpContext context) +1904 System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +358 System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +64 |
版本信息: Microsoft .NET Framework 版本:2.0.50727.1433; ASP.NET 版本:2.0.50727.1433
因为通常这时候网页并不会加载错误,所以我们可以很明确的知道并不是页面生命周期内发生了异常。如果是脚本资源,通常我们打开IE的脚本调试功能会弹出对象无法初始化的错误以及一些脚本异常。如果是css文件则会出现样式丢失的现象。既然不是页面生命周期内发生了错误,我们没有理由去检查代码,特别是当代码曾一度辉煌,我们更没有理由去那么怀疑。这时候我们有理由想到托管我们代码的IIS,仔细观察提示我们应该对utcDate有一个比较深的印象。如果我们的资源是在未来创建的呢?oh,这不可能,但是当我们将系统的时间改成比资源文件的创建时间更早的时候就有理由相信这一切就成为可能了。
解决方案:
1、通过修改服务器系统时间,让其比Assembly的时间要晚,则可以了。(这适合于Assembly是别人创建的时候,当然也适合自己拥有源码的时候)。
2、通过修改Assembly的创建时间,让其早于服务器的时间,则可以了。(这适合于服务器是别人的,当然也适合于服务器是自己的情况)。
首先,很高兴能够参与到《博客园精华集》一书的编定工作中来,为这个充满激情的团队而感到骄傲和自豪。
博客园就像知识的海洋浩瀚无边,2008年5月19日之前的文章我们将通过其他方式进行逐一审核统计,对于2008年5月19日之后的文章,将由我进行整理收录,但这并不代表您的大作会发表到博客园精华集,具体情况暂时不做过多说明,请关注相关公告。
对于2008年5月19日以后的文章我做几点说明,因为时间和精力的关系,我仅仅只能关注发表在博客园首页的文章,对于翻译和转载的文章将不予以收录,如果您的文章足够优秀而没有发表在首页的,请及时和我联系或者跟贴留言也可以。如果您的文章发表在首页,但是您根本不想被收录进博客园精华集,也请您与我联系或者跟贴留言。如果因为我的失误而导致了文章分类错误,请及时告诉我,我将根据实际情况为您解决。如果您还有什么疑问,请及时提出,我们将尽力为您解决。
最近一次更新:2008年5月29日
-1、其它
《C#发现之旅第二讲 C#-XSLT开发》
《C#发现之旅第三讲 使用C#开发基于XSLT的代码生成器》
《蛙蛙推荐:蛙蛙教你索引邮件》
《使用动态代理,提高工作效率》
《深入 Unity 1.x 依赖注入容器之三:获取对象 (多)》
《窗体传值,子窗体,父窗体,反射,reflection,windows,组策略,gpedit.msc,动态创建窗体,谢谢 2 3》
《在RedHat Enterprise 4 上安装 Mono1.9 (三)》(Linux)
《适用于显示Web项目和DLinq调试信息的小程序》
《ASP.NET中OutOfMemoryException异常的处理方案》
0、NA
1、设计模式
《工厂模式兄弟姐妹》
《发布一个用Emit实现的对象创建工厂》
《重写报销流程,责任链模式实现》
《解耦的故事--权限设计》
2、ASP.NET1.1
3、ASP.NET2.0
《DinnerNow中的ASP.NET Ajax Extensions应用---选餐流程》
《net2.0多语言网页的实现》
《WebClient 保持 Session 和 Cookie》
《ASP.NET 2.0 Client Callback 浅析》
《可配置WebPart解决方案》
《Web上传文件的原理及实现》
4、Web标准
5、网站维护、性能、安全经验、SEO
《几种验证码方式对比》
《Log4Net - 写日志记录到Udp服务器(UdpAppender)》
6、HTML/CSS
7、JavaScript
《用javascript操纵GridView中CheckBox的两个常用技巧》
《用javascript/css实现GridView行背景色交替、点击行变色》
《[原创]jQuery小插件-collapsible》
《JavaScript 图片切换效果(ie only)》
《javascript事件监听》
《JavaScript面向对象编程(2)-- 类的定义》
8、AJAX
《利用回调实现脚本实体类和模拟Ajax》
《撰写自用的 ScriptManager 来管理客户端指令码》
《AJAX从服务端获取数据的三种方法》
9、SliverLight
10、WPF
《3ds Max建模,Blend设计,VS2008控制WPF的3D模型例子》
《[WPF疑难]ErrorTemplate显示与隐藏问题》
11、WCF
《DinnerNow中的WCF应用1 --- 首页数据加载》
《Extending WCF(四)—一个统一处理异常、日志的解决方案》
《WCF学习笔记-1 服务的定义与消费》
《Hello,Biztalk 2006 R2 BAM, WCF 集成》
《WCF传输大数据量DataSet》
12、WF
《WF3.5 的SendActivity、ReceiveActivity与WorkflowServiceHost(1)》
《DinnerNow中的Work Flow应用(上) --- 订单流程 (下)》
《坚持学习WF(9):本地服务之事件处理》
13、LINQ
《使用linq to xml 快速创建自己的Rss》
《使用linq to xml 快速创建自己的Rss 之二 Syndication篇》
14、MVC
《对Asp.Net MVC架构的用后感想》
《Asp.Net MVC 入门篇——Overview》
《ASP.NET MVC - 使用Post, Redirect, Get (PRG)模式》
《Asp.Net MVC实践 (基于ASP.NET MVC Preview 2)》
《Asp.Net MVC---Walkthrough》
《推荐一个基于Microsoft ASP.NET MVC Preview 2 的应用示例》
《ASP.NET MVC Preview3 bug 及期望 [集]》
《Asp.Net MVC实践 - 自定义ActionResult实现Rss输出 (基于ASP.NET MVC Preview 3)》
《Asp.Net MVC---Walkthrough》
15、SharePoint/MOSS
16、VSTO
17、VSTS
《Visual Studio解决方案(.Sln)和Wise安装脚本(.Wse)的命令行编译》
《CollectionEditor 显示 [说明] 区域》
18、SQLServer
《Sqlserver2008数据引擎特性之空间数据预先体验》
《数据挖掘初探-用简单公式预测下月数据》
《使用Transact-SQL进行数据导入导出方法详解》
《发现一个SQLSERVER数据库的编绎解析问题》
《MSSQL 2005 分页分析及优化 》
《Access数据库的文本、备注数据类型的COLUMN_FLAGS说明》
《使用Oracle Generic Connectivity连接SqlServer 》
《[原创]SQL Server 2005 镜像构建手册》
《sql取所有记录中每天的最后一笔交易的记录》
《MySQL查询的性能优化》
《SQL Server2005探索之—— 利用SQL Server2005提供的namespace 编程》
《Report Definition Language(RDL) 轻型框架实现》
《无限级别的分类》
《sql server2005对t-sql的增强之在聚合函数的后面使用over关键字》
19、软件工程(项目管理、项目流程分析、单元测试、需求分析、敏捷开发、SOA、UML)
《Agile Software Development(敏捷软件开发)》
《慈善跟踪项目》
《该封装到什么程度?》
《Saas安全性问题讨论》
《自己动手写个ORM 实现(1)》
《我写项目的步骤。抛砖引玉。》
《自己动手写个ORM实现(2)》
《你是否积极主动(Proactivity)?》
《查询的条件和需要什么样的结果如何通知后台服务类的问题》
《项目经理虚拟管理客户》
《研发过程管理导图-第一稿 第二稿》
《EA(Enterprise Architect)》
《实战剖析三层架构》
20、Web Service
21、WinForm(多线程、同步异步、序列化、反射、Remoting)
《C# 实现屏幕键盘 (ScreenKeyboard)》
《通过编程管理windows 防火墙》
《C#(Win)Button实现下拉菜单》
《C#发现之旅第四讲 Windows图形开发入门 5 6》
《开发基于UDP广播的小型局域网聊天室》
《ASP.NET中使用反射将控件值与实体值相互映射》
《[趣味编程]CPU占用率曲线听我指挥》
《白话并发冲突与线程同步(1)》
22、.NET、CLR
《(纪念国殇).Net Hosting:托管远程线程插入及非托管dll线程插入实现》
《.NET 上下文拦截(2)》
《你知道 typeof(void) 吗?》
《靠近IL用DynamicMethod简单实现方法》
《Microsoft Sync Framework 系列(五):微软同步框架中的元数据(Metadata)》
《如何序列化Control等复杂类型对象》
23、C#(2.0,、3.0)
《委托 - 事件 - 内存泄漏 - 弱引用 让人欢喜让人"忧"》
《C# 3.0 扩展方法 实践》
《能自己“跑”的表单控件,思路,雏形,源码。vs2005版本》
《比较C#中的readonly与const》
24、组件开发
《Web基础控件开发--属性 [类型转换]》
《Attribute应用,简化ANF自定义控件初始化过程》
《扩展 CheckBoxField 类别 - 支持非布尔值的双向系结》
《扩展 GridView 控件 - 无数据时显示标题列》
《Asp.NET大文件上传组件开发总结(五)---上传进度信息的显示》
《以横向树方式显示Html表格》合适?
《[系列文章]上传文件管理控件之v1 v2》
《扩展 CheckBoxList 控件 - 系结复选项目 2》
《asp.net 控件开发(三)------处理标签间内容》
《表单控件续(1)——应用接口来简化和分散代码》
《[系列文章]上传文件管理控件v3》
《表单控件的副产品——查询控件》
《GridView 自动编号字段 - TBSerialNumberField》
25、开源项目
《基于Mozilla Thunderbird的扩展开发(五)---进程间通信之Socket篇(上) 6 7 8》
《开源Granados介绍 - SSH连接远程Linux服务器(C#)》
《【PE】流程图对象以及事件驱动机制的介绍》
《DNN模块开发之利器篇:七种武器》
《Gallio 自动化测试平台》
《Unity(七):使用场景Ⅲ:用于依赖注入(下)》
《ABAP学习-第一章[开发环境和总体介绍] 2 》
《DataRabbit 轻量的ORM框架(16)-- Entity缓存》
《在RedHat Enterprise 4 上安装 Mono1.9 (四) 》
26、算法数据结构
《关于阶乘的两个常见算法及一个相关面试题》
《C#与数据结构--二叉树的遍历》
《表達式解析運算器(二)-- 算法實現》
《KTDictSeg 分词组件1.3版本 部分算法讨论 -- 分词粒度》
《KTDictSeg 分词组件1.3版本 部分算法讨论 -- 中文姓名识别和未登录词识别》
27、Office开发
《.Net 中处理Word(2007)文档的一种方法》
《通过代码解压出InfoPath模板文件xsn中的文件》
《[原]OWC做电子表格和图表的试验》
《Hello, Biztalk 2006 R2 BAM》
《C#进行Visio二次开发之鸡毛蒜皮(一)》
28、Windows Mobile平台等嵌入式平台开发
《灾难环境下的Mobile应用构建及部署》
注:有些分类我也觉得有些委屈求全,具体分类会在对大家的文章更多地全面的分析后再分。当前分类请参考《博客园精华集》技术分类目录草案。真的很抱歉,看来文章种类真的很繁多,咂看上去这文章分类是很齐全了,但真正到用来分类的时候却发现有很多不足之处。希望各位网友和编委会成员都加以重视分类的问题。

摘要:
阅读全文
动态加载控件貌似给很多程序员都带来了困扰,经常收到这样的邮件,干脆就写下面这个示例来演示如何解决那些常见的问题吧。
其实常见的问题通常有这样两个:
1、通常他们都通过一个按钮来添加一个UserControl并将它们加入PlaceHolder容器的Controls中。然后页面上就会有一个另外一个按钮,这个按钮什么相关的事也没做,就是做了一次回发。这样的情况动态添加的控件就不翼而飞了。
2、今天收到了一封邮件说是要追加控件,和上面的情况看上去好像不一样,但实质就是同一回事。
原因:
其实网上有很多帖子都不约而同地解释了这个问题,这里我还是不厌其烦地解释一下:
首先,要提到大家所熟知很多人一知半解的页面生命周期,以至于很多居然还停留在将ASP.NET和Winform一样处理的层次上,因此就会有人试图将变量存在实例字段中,然后一如既往地指望它能够用来共享数据,结果总是无功而返,以我所知这样的人居然还不在少数,当然了,咱博客园的素质相对偏高,这种问题一般不在话下。事实上每次页面PostBack都会从Aspnet线程池中返回一个空闲的用户线程,用于处理用户本次的请求。摆弄一下那种浏览器进度条会动的控件基本也都算是回发事件了。两次回发之间可以当作没有什么关联的。但是你总能看到很多控件等在回发之后还能保持状态比如文本框边上有个按钮。你填写完了文本后狂点那个按钮,你会发现文本框中的文字还是你填写的那些而不会被清空。这就不得不说到ViewState这种神奇的双刃剑了。它的原理在MSDN上讲的很清楚,找不到的留言或发邮件给我我再慢慢给你找……
然后呢?还是查MSDN,关键字“TemplateControl.LoadControl ”我们在用PlaceHolder中动态添加控件的时候就会用到这个方法了。我们注意到这里有一句:“在将控件加载到容器控件时,该容器引发所添加控件的所有事件,直到所添加控件参与当前事件为止。但是,所添加控件不参与回发数据处理。”因为所添加的控件是不参与回发数据处理的,因此就会出现问题1中所遇到的按另一个按钮就消失的现象了。问题2其实也是一样的问题,因为事实上它们遇到的现象是一样的,只不过它的需求有所不同罢了。(可以理解成一个是i=1;另一个是i+=1;)
综上所述,问题的关键就是原本在页面加载的时候所有的控件初始化操作都应该完成,动态加载将加载的过程延迟到了事件被触发之后,因此在页面回发后,因为会有一次新的页面加载过程,显然这时候动态加载的控件是不存在的,但是用户预期的答案是显示已经加载的信息。这时候如果可能我们最好在加载的过程中进行控件的重新加载和数据绑定。常见的方法中我们呢通常通过LoadControl来动态加载控件,因此只要在页面输出之前的所有事件节点上我们都可以加载我们的控件。但是推荐的则是Init事件。在Load事件的时候进行数据绑定。
解决:
既然问题的原因找到了,我们就应该解决它,现在关键就是在回发后PlaceHolder.Controls的子集数量为0,也就是没有子控件,也就是很明显地控件跑没了。那么我们就应该在我们在他们还在的时候将其存放起来。在经典的回发模型中,ViewState通过将所有控件/其子控件的各个属性字段等都存放到ViewState中了,在最后Render的时候都一并丢给了用户。数据包括数据状态都一并发到了客户端,现在客户点击了一个能够引起回发的按钮或者下拉框按钮,所有这些数据状态以及客户修改(也许没有修改,但我们假定客户篡改过了)的数据都传回客户端。因为回发发生了,因此在加载数据的阶段IPostBackEventHandler和IPostBackDataHandler接口所定义的方法(通常由服务器控件实现)都将被调用,然后就是一系列的数据回填工作。用户的数据又被重新做成了新的ViewState放在页面里面又丢给了客户端。我曾经用一个比喻(相当拙劣的比喻,当时好像不是这样比喻的)是白衬衫(花花公子正版)被蓝笔画后,送去洗衣店,人家新拿了一件一样的白衬衫(花花公子高仿),然后用蓝笔划了一下还给你,事实上白衬衫不是你原来的那件了,但看上去还是无法分辨。因此我们这里也可以用类似的办法来解决。但是真的可以吗?用ViewState不仅有众所周知的性能问题,因为ViewState的存储介质(其实是指它的内容存储,可以理解成持久层)是页面,而页面是指接受文本的一种载体(正如网页事实上都是文本一样的道理)因此会有序列化的问题。这就给用户控件的开发带来了极大的不便。更关键的原因是不仅如此,因为UserControl压根没有支持序列化,因此你的控件即使精简到没有字段方法(就声明了个名字够精简了吧)再加上序列化特性,只要你继承自UserControl,就必然面临无法序列化的尴尬。况且它的性能问题确实也很值得关注。和ViewState有类似性质的常见的还有Session和HttpContext.Current.Cache等缓存,或者自己实现一个静态字典用于存储也是一个不错的选择。用它们是可以解决问题的,在下面的代码中将会用到。但这样的方案事实上是存在很多问题的。大家都知道Session是有超时时间的,默认长度也就是几十分钟,而且Session也有诸多其他方面的限制,因此用它来做容量如此之大的控件存储其实是非常不适合的。HttpContext.Current.Cache是一个高级的缓存对象,因为有完善的内部机制来限制其膨胀以及管理其内容,但也正因为这种管理比如大小限制等原因会导致在生产环境中可能会遭遇严重的性能问题。缓存应该用来存取较小的常用的数据,比如用户名/密码这样的常用数据,而不是这种大个头的东西。但是与ViewState相似的性质让它们有了承担这份责任的义务。(家里的大人都死光了,孩子也只好来当家了)这让我们想到了存储介质,事实上磁盘文件,数据库等都具有了同样的性质。另一条思路是来自简单地加载思路,因为对动态添加的控件来说,它有一个很明显的特征,它是动态添加的。因此既然可以在按钮事件处理程序中添加,同样也就可以在页面初始化事件处理程序中添加。按照页面的生命周期动态添加最好写在Init这时候理应做丰富的添加(不过不适合那种需要用按钮添加的用户需求了)[另外一点有点郁闷的是在MSDN中也是说应该在Init而不是Load中动态添加,但是同样是在MSDN的《如何:以编程方式创建 ASP.NET 用户控件的实例》居然就用了Load事件来处理,因此这种区分对页面开发人员事实上并不是那么严谨的,事实上也不会出现什么问题,因此也就没有人吹毛求疵了,而且Google出来的答案估计90%以上都是在Load中写的,一传十十传百的结果可能这个数值还在上升,所以就更没必要计较了]。刚刚打算帮发邮件的兄弟直接找一个答案发现了有网友说在每个页面都要做判断搞加载,很烦很烦……所以如果您的需求不是那种追求打开一个页面两天后再来点一下要追加或则重新加载控件的朋友,我的方案还是可以考虑的。当然如果你比较追求那种近乎变态的需求或者您的页面和淘宝有一样大的访问量的话,不凡试试我的方案,更好的解释是,我的方案可以当作理解控件动态加载原理解释的一个入口罢了。
我的例子,因为代码比较多,我就贴出如何调用的部分(也就是“如何用”的代码)源码可以在后面的链接中下载。
扩展性:虽然是为我那位邮友给出的答案,但是还是考虑了扩展性,我们可以尝试扩展用磁盘文件、网络、或者数据库的方式来作为存储介质,当然,您必须为此实现部分接口。局限性,因为有存储介质一说,因此不同容器托管方面不允许同时使用多种存储介质,否则将会出现两个集合,因此就带来了另一个扩展性,您可以自行实现扩展存储之间的数据同步,不过做此之前提醒您一下,不同的存储介质可能存在不同的存储能力,比如Session有大小限制,而数据库简直就是容量大王,这些数据之间的同步可能会引发新的问题,另者就是这样的同步除了看上去很酷之外并没有什么好处,将数据乱存的结果可能导致程序显得混乱,更尴尬的是数据同步所白白消耗掉的性能。当然如果您只是练练手的话您确实可以这么做,做完记得告诉我一下,哈哈,我也想不劳而获。哈哈。下面贴一下代码就不多做解释了,因为如果你理解了上面这些,看懂那些代码就不可能有问题了。
public partial class _Default : System.Web.UI.Page

{
public ContainerManager.ContainerManager cm = new ContainerManager.ContainerManager();

protected void Page_Load(object sender, EventArgs e)
![]()