net开发过程中,错误集锦
来源:互联网 发布:cisco 清除端口配置 编辑:程序博客网 时间:2024/05/17 04:53
2007年 6月 移动项目:
1:vs2005上安装vss2005后,不能够显示源代码管理
(http://www.cnblogs.com/SGSoft/archive/2007/06/12/780351.html)
A:打开菜单中的 Tools->Options 选择 SourceControl->Plug-ins
设置为
“Microsoft Visual SourceSafe "
"Microsoft Visual SourceSafe (Internet)"
选择第一项,好象是以tcp方式连接数据库服务器,如果是后面一项,则要求输入数据库服务器上的webserverce
如果没有这两个选项,则运行下面的命令:
regsvr32 "C:/Program Files/Microsoft Visual SourceSafe/ssscc.dll"
regsvr32 "C:/Program Files/Microsoft Visual SourceSafe/ssapi.dll"
regsvr32 "C:/Program Files/Microsoft Visual SourceSafe/tdnamespaceextension.dll"
regsvr32 "C:/Program Files/Microsoft Visual SourceSafe/RemoteVssScc.DLL"
【假定你的VSS安装到C盘】
重起 VS2005,在上面的位置就能看到选项了。
2:vs2005编译发布成多个dll
解决方案:安装一个 叫 WebApplicationProjectSetup.msi 的程序就可解决
但是,该程序有两个版本,一个大小为905kb的会在编译后中文显示乱码。安装919kb的就可以解决这个问题。
该东西非常的好用,呵呵。
但是,这个东西经常的会出现一点点问题,
aspnet_megre.exe 已退出,.代码为-1
出现这个问题,呵呵,看了下网上的说明,大多是因为webserver的原因.
导致这个问题的原因是在[webmethod(description="这个里面是中文解释")]
本人的解决方法,要么是把 这句中文不要, 要么就是后面加空格,反正,能够实现了.
还有个问题,不知道是不是这个错误,就是发布的时候, 保存发布网站dll的哪个文件的原因, bin目录不能够访问,呵呵
我一般遇到这个问题,就是从新弄个文件夹, 换个发布的名字,就能够搞定.
2007年6月18号
3:Cannot convert type 'ASP.login_aspx' to 'System.Web.UI.WebControls.Login'
这个问题出现在vs2005发布后
发现问题主要还是出在Asp2.0下Login.Aspx页面和VS2005登陆控件冲突。
解决方案:
打开Login.aspx.cs文件,把
public partial class Login : System.Web.UI.Page
改成
public partial class myLogin : System.Web.UI.Page
当然你也可以改LoginWeb、UserLogin之类的,反正不能用Login
打开Login.aspx,把最顶上的那句Inherits="Login"改成Inherits="myLogin"
发布后正常
以后自己取名字的时候,象这样的类名等等,尽量不取和vs一样的。
4:Client found response content type of 'text/html', but expected 'text/xml'.
这个问题,出现在调用webserver的时候,呵呵,出现这个错误,当然是webserver那出错了,我程序出现该错误的时候,是引用没找到,这个好解决,但是,看见网上的别人出现这个错误的解释,反正,看不懂,呵呵,实在是搞不明白,为什么难得见的错误的解释,都是英文的呢?
5:The 'OraOLEDB.Oracle.1' provider is not registered on the local machine.
这种情况有3种可能的原因(http://www.cnblogs.com/tongzhenhua/archive/2004/06/18/16714.html)
1.装Oracle 的机器是不是NTFS的?如果是的话,将/Ora81下的BIN的权限,全部放开,给所有用户。
(不然在B/S结构下会因为没有权限访问目录而报这个错误)
2.如果数据库服务器是Oracle816的。在服务器的那台机器上找Ora81/network/ADMIN下的sqlnet.ora文件,把里面的
SQLNET.AUTHENTICATION_SERVICES= (NTS)改成
SQLNET.AUTHENTICATION_SERVICES= (NONE)
(这种会造成某些机器上报驱动初始化失败的错误,在用OracleClient下出现过一次)
3.如果以上都没有解决问题。可以考虑是不是注册表里的注册真的丢失了。(可能性比较小,windows还是比较安全的)
regsvr32 ../ORACLE_HOME/bin/OraOLEDB.dll
再不行可以重装一下客户端试试。
解决方法如下:(http://ms.mblogger.cn/web1999/posts/18148.aspx)
到ORACLE_HOME目录,如c:/Oracle/Ora92
点击鼠标右键->属性->安全,
对ASP.NET 帐户赋予允许读、执行权限,并在“高级”中保证该目录的子目录、文件都继承这些属性,确定。
重新注册oraoledb.dll:regsvr32 $/oracle/ora92/bin/oraoledb.dll
今天非常的高兴,发布一个网站居然能够出现这么多的错误,呵呵.
6:oracle 10g+vs2005+oracleClient.dll出现的问题
出错情况介绍:
个人的机器上装的是:oracle9i,服务器用的是oracle10g,用的都是一个oracleCclient.dll,在自己机器上运行的非常的好,能够得到数据,发布程序到服务器上,就不能够得到结果,同时,同一个数据库操作类,在winform上执行同样的查询能够得到结果,在webform上却不能够得到结果.
首先,出现的错误是:Could not create an environment: OCIEnvCreate returned -1
解决方案一:
主要是将oracle主目录oracle/的读写权限赋予asp.net或者是IUSER_..和IWAM_..,重启计算机。
解决二:
打开在IIS中的WEB属性--主目录--执行权限【改为-脚本和可执行文件】;
-应用程序池【改为-MSSharePointAppPool】,刷新OK;
解决三:
文件夹权限可以不用管。
设置ORACLE_HOME变量的方法如下
控制面板>>系统>>高级>>环境变量>>系统变量>>新建系统变量
变量名写ORACLE_HOME
变量值添实际的ORACLE_HOME路径,在注册表中有
个人按照上面的做了,出现另外的一个错误
System.Data.OracleClient 需要 Oracle 客户端软件 8.1.7 或更高版本
这个问题忘记是怎么搞定的了,也许还是改权限吧,反正,服务器从启后,这个问题就没在出现,呵呵.
7 oracle 中操作blob + c#
OleDbCommand cmd1 = new OleDbCommand(" INSERT INTO BLOB (PHOTO) VALUES ( ? )",cn);
cmd1.Parameters.Add("PHOTO",OleDbType.Binary);
cmd1.Parameters["PHOTO"].Value = MyData;
cmd.CommandText = " insert into xlutest ( hhhh ) values (:hhhh) ";
cmd.Parameters.Add("hhhh",System.Data.OleDb.OleDbType.Binary,expbyte.Length);
cmd.Parameters [0].Value = expbyte;
8 无法在“”已存在的情况下创建/影像复制该文件
解决方法:重新编译整个项目 或者重启电脑,呵呵
9 RegisterStartupScript 已过时
改为Page.ClientScript.RegisterStartupScript
10 由于目标机器积极拒绝,无法连接
vs2005里面,添加web引用的时候,默认的ulr会带上端口号,如:localhost:2473,因此,在添加完引用后, 用localhost替换掉所有的localhost:2473,在编译就行了
11 没有 aspnet 用户
安装一.net控件的时候,显示没有aspnet用户。实现方法:
aspnet_regiis -i
12 怎样得到当前日前的月份(月份为英文月的全称,比如July)
DateTime.Now.ToString("MMMM",new System.Globalization.CultureInfo("en-us"))
DateTime.Now.ToString("MMMM",DateTimeFormatInfo.InvariantInfo )
2007年6月25日
答:如果你不太确定收件箱中的某封电子邮件或某个需要浏览的网站页面是否附带了病毒时,可以通过“查看源文件”的方式,查看后台HTML源代码。如果源文件中显示的URL与真实的链接无法匹配,那说明该邮件或网页有问题。 如果“查看源文件”这一功能无法使用了,确实是件很麻烦的事情。导致该问题出现的原因有很多,下面将一一列举:
第一类情况是,当浏览器的缓存被装满时,“查看源文件”功能将失效。如果你想确定自己的电脑是否属于这类情况,可以点击IE的“工具”菜单中的“Internet选项” “常规”标签,然后,再点击“Internet临时文件”一栏中的“删除文件”按钮。
第二类情况,当你打开某些特殊的浏览器窗口(比如说用JavaScript脚本语言编写的网页)时,“查看源文件”功能也会失效。如果你在浏览任何网页时,都无法“查看源文件”,那就不属于这类情况。
第三类情况,当一个网页还没有被完全加载到桌面时,“查看源文件”功能也是不能使用的(在这种状态下,“文件”菜单中的“另存为”选项也是屏蔽的)。这也算不上是真正的问题。
第四类情况,查看Windows文件夹,如果找不到Notepad.exe,将会导致“查看源文件”功能无法使用。
第五类情况,如果TEMP(或TMP)环境变量指向某一个不存在的文件夹,也会导致“查看源文件”功能无法使用。打开命令提示符(command prompt)窗口,键入SET TEMP(或SET TMP),就可以查看到这些变量的值。
第六类情况,通过注册表设置也可以限定命令行的有效性。首先,点击“开始”菜单,在运行对话框中键入“REGEDIT”,调出注册表编辑器,点击“HKEY_CURRENT_USERSoftware PoliciesMicrosoftInternet Explorer Restrictions”,在右边的窗格中找到一个名为NoViewSource的值,如果当前的数值数据设置为1,则双击它,将数值数据更改为0。
第七类情况,通过查看注册表设置,你还会发现:如果指定的调用程序不正确,也将导致“查看源文件”功能失灵。调出注册表编辑器,点击“HKEY_LOCAL_MACHINESoftware MicrosoftInternet ExplorerView Source EditorEditor Name”,如果该键存在的话,右边窗格中所显示的默认的“数值名称”应该是记事本程序的完整路径,一般以“C:windowsnotepad.exe”的形式表示。如果该键不存在,先核实记事本程序的正确位置,然后双击默认键值,修改错误路径。
第八类情况,组策略的设置也有可能会屏蔽“查看源文件”这一菜单项。从“开始”菜单中调出“运行”对话框,键入GPEDIT.MSC。如果桌面上弹出一个提示框,提示该文件名不正确,则说明“查看源文件”功能的失效与组策略的设置无关;否则,桌面上将会弹出一个“组策略编辑器”窗口,点击“User ConfigurationAdministr-ativeTemplatesWindows ComponentsInternet ExplorerBrowser”,在右边的窗格中找到View menu: Disable Source menu,如果这个键值被设置为“Enabled”,则将它更改为“Disabled”。
注意,上面介绍的第一类情况(即由于IE所保存的Internet临时文件过多而造成“查看源文件”功能失灵)是最常见的(如图)。如果删除了所有脱机内容之后,该功能仍然无法使用,你再依次检查后面所介绍的几种可能性,直到问题消除为止。
源文件查看小技巧
1、查看带有框架(Frame)的网页源码
如果有的网页中使用了框架(Frame),或者使用了多窗口,那么利用IE菜单上的命令就只能得到框架设置的源码,因此无法查看网页设计的细节。此时久需要变通一下方法。将鼠标指针移到网页中非链接的位置,单击鼠标右键,在弹出的窗口中选择“查看源文件”功能。需要查看哪个窗口的源文件,就将鼠标指针指向哪个窗口,再通过点击右键菜单来进行操作。
2、使用特殊的命令
其实,最简单的方法大概是利用命令了。它的格式为: view-source:http://X.X.X,此时,网页不会显示,却会出现一个显示网页源码的文本框。
对于带有框架的网页,可以首先找到框架内某个感兴趣的网页名称,然后再通过该命令查看框架内的网页源码。
3、利用网页编辑器
上面的方法固然有用,但是,有的网页保密工作做的非常周全,首先它使用了多窗口的Frame页,让IE菜单上的源码查看功能不能发挥作用,接着它又将鼠标的右键屏蔽了,怎么办呢?这时候我们可以考虑使用网页编辑器来查看网页源码。
以IE为例,具体操作方法如下:单击待查看源文件的Frame窗口,利用鼠标的拖动来选择要查看的部分,如果鼠标彻底被屏蔽,也可以选择IE菜单上的“编辑”→“全选”。然后单击“编辑”→“复制”。接着打开FrontPage 网页编辑工具,点击“文件”→“新建”,然后选择“编辑”→“粘贴”。这时待查看的网页或其中的一部分内容便被复制了过来,再利用FrontPage 中所见即所得的特性便可查看其源码了。使用此方法虽然比较繁琐,但是却有很有效。首先,这种方法不受任何限制,对所有的网页都适用,此外,利用这种方法还可以有针对性地查看指定的内容。有时一个网页的源文件可能很大,从中找到指定的源代码象是大海捞针。而用此方法可轻而易举地找到所需要的源代码。
如果你嫌麻烦,我们也可以采用另外的方法。首先,将带有Frame窗口的网页“令存”,要记住,存的时候要令存为“网页,全部”的形式,存好之后,你就可以在存好的网页目录中找到几个Frame窗口中子窗口的HTML文件,此时再用FrontPage等网页编辑工具将其打开即可查看网页源码了。
一、什么是TDD
简单的说,即在写任何功能代码之前,先写它的测试代码。具体步骤:
·根据需要编写一个测试用例
·编写功能代码,以让刚才的测试用例通过
·逐步补充测试用例
·修改功能代码使新增的测试用例和原来的都通过
·重构,包括功能代码和测试用例
二、为什么使用TDD
提高代码质量。由于功能代码的高质量和完善的测试用例集,增强了开发者信心,从而赢得他人信任。
改进设计。TDD保证了功能代码的可测试性,降低了耦合度,改善组件对象模型,使设计在开发过程中逐步完善和改进。
为功能代码提供了良好的文档,并能维护代码和文档的同步。敏捷宣言主张:能够运行的软件胜过面面俱到的文档。测试用例集就是一份准备可靠,且能运行的文档。
在一定程度上可代替程序调试。当每个单元测试关注每一个具体功能时,问题被更早和更好地避免。另外调试是手动而不可重复,TDD的测试用例集则是自动可回归的。
有效的质量控制和项目管理。对管理者来说,通过单元测试每日构建的结果,每天都清楚的知道项目的质量和开发进度
三、TDD是测试,更是设计
当开始写单元测试代码时,其实也正在开发。在编写功能代码前,站在功能代码的使用者角度设计测试用例,运用针对接口编程等原则降低耦合度,改进设计。由此可见,TDD也是面向对象的分析,设计和开发方法。在贯彻TDD的开发过程中,对于每个类分别进行测试,对于每部分都进行简单设计,频繁重构,最终形成了一整套可运行的测试用例集,TDD体现持续改进的过程,是一种增量式设计。
TDD(Test Driven Development),是一种测试技术,更是一种设计方法。其重心不在Test,而在于Development,是一种以意图来驱动的软件开发方法——意图编程。
2007年6月11日
团队开发真的是一件很不容易的事情,而做大的项目,必须要由团队来完成,在这过程当中,会出现很多很多的问题,因为开发人员都是人,不可能像机器执行程序一样,只要有固定的代码,就不会出现代码以外的问题,更何况不是一个人而是一群人。所以如果想让一群人按照既定的规则来进行各自的活动的话,就要有一套合理的规则和方法,这些方法就是软件工程,但是那些大条大框我就不说了,我现在还没有特别多的体会,我只结合软件工程说一说我现在的小体会。
首先我要强调文档的重要性,从开发过程来讲,应该是首先有三个文档-需求文档、设计文档、数据库设计说明文档,然后把设计文档的内容意义传达给每个开发人员,所有开发人员都要对文档进行评审,评审的过程也是思想接近的过程,设计思路的传递也就在这儿。等到整体框架设计完毕,就需要分头来进行详细设计了,这时还有个关键点就是做框架设计的人员要把每个模块要完成的功能和接口明确给每个人,这样做出来的各个模块才能很好的配合在一起。详细设计的时候每个模块要完成的功能都要详细到每个方法,特别是公有方法,要对每个参数和返回值做出详细的说明,这样在后期的调用时就会有据可依了。
在一切设计及评审都结束后就可以开始整体编码了,我觉得这时候编码的效果是最好的。因为在详细设计之前编码,有很多时候会因为设计不充分而导致后期改了又改,相信程序员是最反感改程序的,特别是改别人的程序,更令人难以忍受的是改那些没有良好结构也没有设计文档的程序。所以在完成设计之前最好不要开始大批量的写程序,如果写,也要有相应的文档。
还有就是对于一些中间产品的文档,也要尽量详细的写出来,因为中间产品是模块之间互相调用的东西,所以如果不写出详细的文档就容易使两个模块之间的调用出现问题。
再有就是编码规范的问题了,这个问题其实也是比较好解决的,但是不好解决的一点在于,每个人可能都有自己的编码规范,但是如何做到一个团队有一个统一的编码规范就不是很容易了。
再想就是谈谈新人的问题,如果一个团队中有新人加入,那么先不用急着让他来为公司赚钱,应该做的首先是让他融入公司的环境,更重要的是让他做一个团队中符合统一规范的人,等到他完全融合进来了就可以为团队发挥更大的有效力量了。
此上只是一点点小小的想法,团队合作,说着容易,其实做起来也是可以做得很好的。
Symbian是以Nokai为首的各手机厂商合伙开发的一个操作系统,主要用于高端的智能手机。其开发语言为C++,可以使用Microsoft Visual C++ 6.0作为集成化的开发环境,看来虽然Symbian的目标是跟微软的SmartPhone较劲,在采取的手段和方法上却也是不拘一格,微软更是暗暗叫苦,谁让当时vc6设计的这么开放,结果被敌人拿去做武器了。
本文主要针对的是Symbian开发入门,所以应该是非常简单的事,因为毕竟Symbian的开发语言是C++,并不会因为是用于手机的操作系统就复杂了多少,就象很多人一直挂在嘴边的J2ME也毕竟是Java语言,并不会因为用在手机上就高深了多少一样。5小时是我从对Symbian一无所知到完成本文花在Symbian上的所有时间。
因为是用于手机的操作系统,所以我们研究Symbian的话,先选定一款手机再说。Nokia的60系列平台是一个很不错的选择,Nokia根据手机的屏幕大小和价格高低把手机分成了多个系列,60系列目前有两款手机:7650和3650。60系列采用Symbian os 6.1,然后又根据手机屏幕的特点对UI做了一些修改,这个被修改了的Symbian就被称为Nokia的60系列平台。
所以入门的第一步就是要下载S60的SDK,可以从Nokia的开发论坛上下载:http://www.asia.forum.nokia.com/chinese/sch/main/series60.html。看Nokia网站上最新的是1.0,不过下面又有个0.9中文版,所以自然要下载这个0.9版了。100多兆吧,不想下的话,可以跟Nokia要光盘,估计他们应该很高兴给。这个0.9中文版的模拟器是中文版的,而且有支持GBK和UNICODE转换的类,不知道1.0的英文版有没有。
下载完了就是安装,选一个目录装就是了,不过最好装在C盘上。可能是考虑到Windows平台和Unix平台都能用的缘故,这套SDK在处理盘符和目录时比较弱智,我当时SDK装到C盘,自己的程序放到D盘用vc6生成新项目就有问题,经观察是盘符的问题。所以保险起见的话,最好装在C盘,有兴趣的可以替Nokia测试一下。另外,还需要Java的运行库和Perl,SDK里也都带了,不过他自带的Perl在我的XP下装有问题,我下载了一个最新的ActivePerl才最终算是安装完毕。从SDK的安装来看,这帮搞手机的搞软件好像确实不太专业。
刚才说了,我的Symbian安装到了C盘,在C盘的Symbian目录下,有一个6.1的目录,6.1目录下有两个目录:Series60和Shared。还好,从名字上能看出大体的意思。请一定注意Series60目录下的Epoc32/BUILD目录,因为这个目录以后要反复的用到。
接下来的任务就是要编译一下HelloWorld,然后再运行一下看看了。首先得确认你的系统装VC6了(最好能装sp3以上,否则将来用到vc6时会报警),而且得把C:/Program Files/Microsoft Visual Studio/VC98/Bin
放到你的PATH里,主要是用nmake.exe。所以如果你有nmake的话,不装vc6也可以看helloworld。
找到helloword例子的目录,在我这里是C:/Symbian/6.1/Series60/Series60Ex/HelloWorld/group
Symbian里,一个Project通常是按inc,src,group等目录组织,group目录里通常放的是项目文件,所以编译时要先到这里。用命令提示符模式进入刚才说的那个目录下,然后执行bldmake bldfiles
这个命令会在group目录下生成一个abld.bat的批处理文件,并且会在C:/Symbian/6.1/Series60/Epoc32/BUILD下生成C:/Symbian/6.1/Series60/Epoc32/BUILD/SYMBIAN/6.1/SERIES60/SERIES60EX/HELLOWORLD/GROUP这个深的一个目录,并在最底层目录下生成一堆.make文件。(实在理解不了为什么要这么搞?因为他们是生产手机的?)
虽然它生成了这么多东西,但是我们不要管,继续输入abld build wins udeb
这个命令会编译我们的程序,最后在C:/Symbian/6.1/Series60/Epoc32/Release/wins/UDEB
目录下生成我们的helloworld,然后我们可以从开始菜单里运行模拟器的debug版,打开other目录就可以运行helloworld了。
如果要在VC6里编译和运行HelloWorld,在运行完bldmake bldfiles后,运行abld makefile vc6,则会生成vc6的dsw文件,位置在C:/Symbian/6.1/Series60/Epoc32/BUILD/SYMBIAN/6.1/SERIES60 /SERIES60EX/HELLOWORLD/GROUP/HELLOWORLD/WINS
看Symbian把这点事搞得这么麻烦,也真是不容易。
如果要直接在vc6里创建新项目的话,要把C:/Symbian/6.1/Series60/Series60Tools
目录下的文件拷贝到vc6的模板目录下才可以。
折腾到这里,可能得花2个多小时吧,剩下的两个多小时得看看文档了。
先看看HelloWorld的帮助文档,理解一下Symbian程序的体系结构。Symbian程序也是按照VC的文档视图结构来组织程序的,有Application类CAknApplication,有Document类CAknDocument,有Frame类CAknAppUi,有视图类CCoeControl等,跟vc的结构好像是差不多的,但感觉上它的Document类好像是做样子的,我还没看到这一块。关于这些类之间的调用关系,在HelloWorld的文档里有个顺序图非常好,一看便知。
在看Symbian的代码时,感觉最不适应的可能就是经常看到PushL和Pop函数,还有就是很多的函数都有L或LC的后缀。其实这是Symbian的例外处理机制。比如说我们定义了一个指针,给它分配了空间,可是在使用它的时候程序突然间出现了致命错误中止了,那这个指针占用的地址空间我们肯定是收不回来了,在PC上编程这一点可能问题不大,内存那么多,而且用不了多长时间就重启动了,但是Symbian的设计者可能认为这在手机上是不行的,所以对这一点做了很多的设计。Symbian有一个Clean up stack,在使用指针时,用PushL把指针压入栈中,使用完后在用Pop弹出栈,如果在中间调用可能导致崩溃的函数时果真出现了问题,那么Clean up stack可以通过调用该指针的析构函数回收占用的空间。这些可能导致崩溃的函数在Symbian里被称为可能Leave的函数,所以就在这些函数的尾部加了一个L。而加LC后缀的函数表明该函数已经在内部把指针用PushL压入clean up stack了,调用时无需再用PushL,直接调用完用Pop既可。Clean up stack还提供了一个PopAndDestroy函数,就是弹出栈后再销毁指针。顺便提一句,正式因为有了Clean up stack机制,所以Symbian在有些地方看起来跟VC的程序不太一样,比如说很多类的构造函数都不用,用NewL或NewLC构造,分配地址时的操作符new()也变成了new(ELeave)。上述内容可以看一下Symbian编程基础中的内存管理一节。
Symbian自己定义了一堆的数据类型,如果不想找麻烦的话,还是乖乖的用这些类型的好。简单说一下,整型有TInt和TUint,其中又分为8位的,16位的和32位的,如TInt8,TInt16,TInt32;文本类型是TText,又细分为TText8和TText16,但内部都是Unicode的,所以实际上内部引用应该都是TText16;bool型是TBool,对应的值有ETrue和EFalse;浮点型是TReal,但又分为TReal32和TReal64;还有一个引用类型TRefByvalue<T>,是以模板类的形式提供的。
因为Symbian是冲着更面向对象设计的,所以在我们常见的字符串这块变化比较大,在Symbian里叫descriptor。最底层的是两个抽象类TDesC和TDes,实际上TDes也是继承的TDesC,TDesC中的C字母是常量constant的意思。即带C字母结束的都是定义的常量,是不可以修改的,而不带C的都有一个最大长度的限制,是可以修改的,下面也是同样的道理。指针描述符是TPtrC和TPtr,类似于C++中的char *;缓冲区描述符(Buffer descriptor)是TBufC和TBuf,类似于char [];堆描述符是HBufC,类似于(char *)malloc(length+1)的用法。具体用时,上述的类型又分为8位的和16位的。举个例子:
TBuf<64> buf;
CEikonEnv::Static()->ReadResource( buf, R_EXAMPLE_VIEW_TITLE );
先定义了一个最大长度是64的缓冲区,然后读入名为R_EXAMPLE_VIEW_TITLE的资源。
再例如:
TText8 *str = (TText8*)"示范窗口标题";
TPtrC8 source( str );
iInfoText = HBufC::NewL( source.Length() );
TPtr16 ptr = iInfoText->Des();
定义一个8位的字符串str,然后构造一个指针常量source,根据source长度再定义一个16位的指针,下一步我这里没写,但明显着应该是把gbk的转换为unicode了。
Symbian的资源文件一般是以rss为扩展名的,没有专门的资源文件编辑器,用户必须用记事本打开rss文件手工编写rss文件。这个都有一定的规则和方法,参考例子不难编写自己的资源文件。需要注意的一点是,如果资源文件里包含中文,那么必须把文件另存为utf-8格式的才可。
入门相对来说是比较简单的,但精通就需要大量的实践了。
作者简介
侯月文:2000年在联想软件做Linux软件开发,2001年年底去四维时代工作,从事移动设备相关开发工作。
2007年6月8日
2007年6月6日
作者:Andrew Needleman
相关技术:C#、.NET、ASP.NET、Window
难度:★★★★☆
读者类型:数据库开发人员、系统结构设计人员
[摘要]本文讨论了如何安排ASP.NET代码的运行和N层体系结构设计,介绍了Web服务和Windows服务基础知识。
由于Web服务可以与ASP.NET应用程序的其余部分在相同的应用程序上下文中运行,因此它可以在现有代码所预期的相同上下文中执行。因为Windows服务可以在Windows启动时自行启动,所以我将通过Windows服务来启动Web服务调用。
假设您已经用ASP.NET编写了一个出色的N层应用程序,并且想要扩展它以执行预定任务。例如,每两个小时向数据库中的选定用户发送电子邮件,或者定期分析ASP.NET缓存中的数据以进行应用程序运行状况的监视。您不希望从ASP.NET应用程序中丢弃您的对象模型或者在单独的计划程序和ASP.NET应用程序之间创建太多的依赖项,那么您如何在避免这一点的同时仍然能够让这些应用程序一起工作呢?在基于.NET Framework的应用程序中,经常使用计时器按照预定时间间隔执行活动,因此使用一个计时器似乎是适当的解决方案。您可以从Global.asax中的 Application_Start处理程序中启动计时器以运行预定任务。遗憾的是,该解决方案在应用程序域、进程或系统重新启动方面还不够健壮,这是因为必须向应用程序发出请求以启动计时器。ASP.NET是一种只响应HTTP请求的被动编程范型,因此进程或用户输入必须调用代码以便它能够运行。
更好的解决方案是使用Web服务(Web Service)提供ASP.NET应用程序的接口,并且生成按照预定时间间隔调用它的Windows服务。这样,ASP.NET应用程序就不必拥有日程安排逻辑,并且只需要关心执行那些它已经能够执行的任务。由于Web服务可以与ASP.NET应用程序的其余部分在相同的应用程序上下文中运行,因此它可以在现有代码所预期的相同上下文中执行。因为Windows服务可以在Windows启动时自行启动,所以我将通过Windows服务来启动Web服务调用。因此,即使服务器重新启动,应用程序也能够启动自身。这一重新启动功能使Windows服务成为对该任务而言比典型的基于Windows的应用程序更为健壮的解决方案。这也是为什么Windows服务能用于很多后台进程(例如IIS)的原因。
本文,我将演示如何做到这一点,同时在日程安排应用程序和ASP.NET应用程序之间创建最少数量的依赖项。该解决方案涉及到对启动ASP.NET作业的日程安排应用程序进行简化。在日程安排应用程序中,除了它调用的Web服务终结点以外,将不会调用特定于ASP.NET应用程序的逻辑。Windows服务将使用app.config文件来存储Web服务的UR 以及Windows服务在对Web服务进行的调用之间应当等待的时间间隔。通过在Windows服务的app.config文件中存储这两个设置,您可以更改它们,而无须重新编译Windows服务。如果您需要在应用程序调用时更改它的行为,则可以只更改ASP.NET应用程序中的逻辑;但是,您不必更改日程安排应用程序的代码。这意味着日程安排应用程序将与ASP.NET应用程序中的更改隔离。
注意:该解决方案所基于的前提是——有一些任务只应当在正在运行的ASP.NET应用程序的上下文中执行。如果这不是您任务的要求,则您应当认真考虑直接从 Windows服务中引用ASP.NET应用程序的业务逻辑程序集,并且绕过ASP.NET进程以激发这些任务。
应用程序结构
图1 计划
典型的ASP.NET应用程序是用一系列执行特定功能的独立层生成的。在我的特定示例中,我具有数据库访问类、业务逻辑类、业务流程类以及作为这些层的入口点的ASP.NET页(参见图 1)。
ASP.NET页只用来显示和检索数据,它们是实际协调所有工作的业务流程类的接口。流程类按照正确的顺序调用业务逻辑类,以便完成特定的事务,例如订购小部件。例如,流程类可以首先调用业务逻辑以检查库存,然后订购小部件,并且最终将库存减少至适当的水平。
业务逻辑类决定如何调用数据库访问类,并且根据需要处理该结果,以获得可以用于其他操作的最终结果。例如,业务逻辑可以用来计算包括特定州的税款在内的总价格。首先,您可能需要使用数据访问类从数据库中检索该州的税率以及基本价格,然后将它们相乘以查找每个项的总税款。数据库访问类保持该逻辑,以便连接到数据库,并且以可供更高层使用的格式(例如,DataSet、DataTable或DataReader)返回结果集。这些类只是从数据库中检索数据,并且按照反馈给它们的信息来更新该数据库;它们不处理结果。例如,它们可能检索特定州(美国)的税率,但是它们不会计算订单的总税款。
Microsoft Data Access Application Building Block通过提供更容易的方式与数据库和存储过程进行通信简化了数据访问类。例如,您可以对它的SQLHelper对象的FillDataSet方法进行调用,以便使用一行代码根据存储过程的输出来填充DataSet。通常,您必须编写代码来至少创建DataAdapter和一个命令对象,而这至少需要四行代码。Data Access Application Block连接到数据库中的存储过程。该存储过程提供访问和修改数据库中的数据所需要的SQL代码。
向应用程序中添加预定作业
ASP.NET Web服务能够提供保持任务逻辑的现有ASP.NET应用程序的接口,该接口充当该服务和调用ASP.NET应用程序以采取操作的Windows服务之间的中间人。Windows服务随后将按照预定时间间隔调用ASP.NET应用程序。
图2 运行预定作业
ASP.NET Web服务能够向您提供保持任务逻辑的现有ASP.NET应用程序的接口。该接口充当该服务和调用ASP.NET应用程序以采取操作的Windows服务之间的中间人。Windows服务随后将按照预定时间间隔调用ASP.NET应用程序。通过在现有ASP.NET应用程序中生成ASP.NET Web服务,可以在预定作业中重新使用已经为该ASP.NET应用程序创建的业务对象和逻辑。图2显示应用程序流程的详细信息——从客户端Windows服务应用程序到在该服务器上运行的Web服务启动,始终贯穿于每个预定任务的执行。
图3 应用程序附加功能
正如您可以在图3中看到的那样,该过程要求对前面描述的标准分层进行一些修改。Windows服务将按照指定的时间间隔唤醒ASP.NET Web服务。然后,ASP.NET Web服务将调用Web应用程序流程层中的方法,以实际确定应当运行哪些预定作业,并随后运行它们。在实现了基本解决方案以后,您可以使用客户端app.config文件来确定Windows服务调用Web服务的时间间隔。接下来,您可以添加业务流程层所需要的功能,以便遍历和运行作业。许多N层专家对于流程层的兴趣肯定超过了对其余层的兴趣,因此我将最后讨论数据库表、数据库存储过程、数据访问代码和业务逻辑。最后,从底部(数据库表级别)到中部(业务逻辑层)向应用程序的现有层中添加代码,以便支持流程层所使用的作业功能。
生成Web服务
要生成Web服务,请首先向与现有ASP.NET代码位于相同层中的ASP.NET应用程序中添加JobRun ASP.NET Web服务。确保您的ASP.NET项目具有对业务逻辑、流程和数据访问项目的引用。接下来,要在JobRun Web服务中创建RunJob Web服务方法,Web服务方法将需要调用流程层的相应函数以运行适当的作业。这意味着RunJob方法可以像下面一样简单:
[WebMethod]
public void RunJob()
{
Flow.JobFlow jf = new Flow.JobFlow();
jf.RunAllActiveJobs();
}
使用RunJob函数创建JobFlow类(它位于流程层中)的实例并调用它的RunAllActiveJobs函数。JobFlow函数的RunAllActiveJobs完成了协调作业运行的所有实际工作,而RunJob函数只是充当该序列的入口点。请注意,这段代码无法防止作业同时在一个以上的线程中运行——如果Windows服务过于频繁地安排任务(速度超过了任务的运行速度),或者如果其他某个应用程序调用了入口点,则可能发生这种情况。如果该方法不是线程安全的并且允许多个线程同时调用它,则可能给这些作业的结果带来问题。例如,如果作业X向Mary Smith发送了一封电子邮件,但是在作业Y查询数据库以处理其电子邮件时尚未更新数据库,则Mary可能收到两封电子邮件。为了同步对该函数的访问,我将使用System.Threading命名空间中的Mutex类:
private static Mutex mut = new Mutex(false, "JobSchedulerMutex");
Mutex支持跨进程同步,因此这可以防止多个进程同时运行——即使涉及到两个不同的ASP.NET辅助进程。现在,让我们更改RunJob方法以使用Mutex,从而确保在启动作业之前没有其他作业运行。
代码段1 RunJob Web Service:
[WebMethod]
public bool RunJob()
{
bool ranJob = false;
mut.WaitOne();
try
{
Flow.JobFlow jf = new Flow.JobFlow();
jf.RunAllActiveJobs();
ranJob = true;
}
finally
{
mut.ReleaseMutex();
}
return ranJob;
}
正如您可以在代码段1中的RunJob函数中看到的那样,可以调用Mutex的WaitOne函数以使该线程等待,直到它在执行之前成为唯一的线程。然后,调用ReleaseMutex函数以指示您已经执行完只需要在一个线程中运行的代码。当然,在这里阻塞可能不是正确的解决方案。您可以选择在另一个线程已经执行作业时(在此情况下,您可以为WaitOne方法指定短暂的超时)立即返回,并且在无法获得互斥锁时立即从RunJob中返回。将该函数的所有主要操作都放到一个try-finally块中,以便即使RunAllActiveJobs函数中的意外异常导致RunJob函数退出,也会调用ReleaseMutex。
您可能希望使用某种形式的身份验证和授权(可能使用Windows安全)来保证 Web服务的安全,以确保必须经过正确的授权才能运行该服务,但是我不打算在本文对此进行详细讨论。
既然您已经生成了Web服务以便可以从另一个应用程序中调用它,那么现在就让我们生成能够使用它的Windows服务。
生成Windows服务
首先, 在Visual Studio .NET的另一个实例中创建一个新的Windows服务项目,然后将其命名为InvokingASPNetService.cs。通过按以下方式添加Main方法来确保该服务将正确启动:
public static void Main()
{
ServiceBase.Run(new InvokingASPNetService());
}
现在,为下列命名空间添加using语句:
using System.Configuration;
using System.Globalization;
通过右键单击InvokingASPNetService.cs的设计表面并选择“Add Installer”,添加该服务的安装程序。您应当将所创建的serviceInstaller1 的StartType属性更改为Automatic,以便Windows服务在Windows启动时启动。将serviceInstaller1 的 ServiceName 属性设置为InvokingASPNetService,以便在服务管理器中适当地命名它,然后将serviceProcessInstaller1 Account 属性更改为Local Service。
第三步,创建对InvokingASPNetService Web服务的Web引用,然后将其命名为JobRunWebService。将JobRunWebService URL Behavior属性更改为Dynamic,以便让Visual Studio .NET自动用Web引用的URL来扩充app.config。所生成的代理类将在该配置文件中查找Web服务的URL,从而使您无须重新编译即可将 Windows服务引导至不同的终结点。
第四,在Windows服务中创建一个方法,以便在Web服务每次被调用时运行它。该方法如下所示:
private void RunCommands()
{
JobRunWebService.JobRunInterval objJob =
new JobRunWebService.JobRunInterval();
objJob.RunJob();
}
如您所见,您将声明Web服务代理,并且就像创建其他任何.NET对象一样创建它。然后,调用Web服务的RunJob方法,以便在远程Web服务器上运行作业。请注意,每个步骤都与使用本地类没有什么不同,即使您使用的是Web服务。
第五,您需要在Windows服务中调用RunCommands函数。您应当基于您希望在远程服务器上运行作业的频率,按照固定的时间间隔调用该方法。使用System.Timers.Timer对象来确保RunCommands函数按照正确的时间间隔运行。Timer的Elapsed事件将使您可以在每个时间间隔流逝之后触发您指定的任何函数(请注意,时间间隔长度是在Interval属性中指定的)。您将使用被触发的函数来调用RunCommands函数,以便可以自动执行该功能。默认情况下,该timer类只在它首次到期时才会触发事件,因此您需要通过将它的AutoReset属性设置为true来确保它每次都反复地重置它本身。您应当在服务级别对其进行声明,以便该服务的任何函数都可以引用它:
private Timer timer;
接下来,创建相应的函数以初始化timer并设置它的所有相关值:
private void InitializeTimer()
{
if (timer == null)
{
timer = new Timer();
timer.AutoReset = true;
timer.Interval = 60000 * Convert.ToDouble(
ConfigurationSettings.AppSettings["IntervalMinutes"]);
timer.Elapsed += new ElapsedEventHandler(timer_Elapsed);
}
}
为了能够在不重新编译应用程序的情况下更改配置时间间隔,我已经在app.config文件中存储了该时间间隔,以便InitializeTimer方法可以使用ConfigurationSettings.AppSettings访问它而不是对其进行硬编码,如下所示:
<add key="IntervalMinutes" value="5" />
确保timer在计时器到期时调用timer_Elapsed函数以处理Elapsed事件。timer_Elapsed方法非常简单,它调用刚刚生成的RunCommands函数,如下所示:
private void timer_Elapsed(object source,System.Timers.ElapsedEventArgs e)
{
RunCommands();
}
最后,您必须使用installutil命令安装Windows服务。最容易的方式是打开Visual Studio .NET命令提示窗口,导航到该服务的目录,然后运行installutil实用工具,并且指定您的程序集作为参数。
扩展流程层以处理预定作业
一项重要的工作是扩展流程层以处理正在运行的预定作业的需要(假定作业之间的差异足够大,以至于需要对它们进行编码而不仅仅是参数化)。这涉及到从数据库中的下一个启动时间已经过去的数据库收集所有作业,并单独地运行它们。在流程层内部,您将创建一个名为Job的基类以提供作业所共有的全部功能。这包括一个用于初始化和检索JobID的机制、一个用于运行作业以及在成功运行之后设置数据库中的下一个运行的公共方法(RunSingleJob),以及一个要针对每个作业进行自定义的可重写方法(PerformRunJob)。
流程层还将需要为它执行的每个作业生成特定于作业的类。这些类将从基础的Job类继承,并将重写Job类的PerformRunJob函数以自定义该特定作业的执行。您还会需要一个工厂类(JobFactory)以创建和初始化正确的Job类的JobID。静态的CreateJob函数将根据传递到该函数中的JobID来创建适当的作业。最后,流程层将必须能够确定需要运行的作业、遍历它们并运行它们。这就是JobFlow类将通过它的RunAllActiveJobs方法提供的功能。
首先,让我们在流程层项目中创建Job基类(该类将成为每个作业类的父类)。代码段2显示Job抽象基类的核心。它允许对其JobID进行初始化和检索,并且能够确保在作业成功运行后更新数据库。JobID在创建之后,它将不会针对给定的作业进行更改,因此您必须确保在初始化之后设置函数不会更改该值。创建各个Job类的JobFactory类将设置自己的JobID值。
代码段2 Job抽象基类:
protected bool isInitialized = false;
protected int mJobID;
public int JobID
{
get { return mJobID; }
set
{
if (!isInitialized)
{
mJobID = value;
isInitialized = true;
}
else throw new InvalidOperationException("JobID already set.");
}
}
public void RunSingleJob()
{
if (isInitialized)
{
PerformRunJob();
RecordJobSuccess();
}
}
protected abstract void PerformRunJob();
protected void RecordJobSuccess()
{
JobLogic jl = new JobLogic();
jl.UpdateJobDone(JobID);
}
RunSingleJob函数确定该作业的JobID已经初始化,运行作业(PerformRunJob),并且在成功运行之后用RecordJobSuccess方法更新数据库。isInitialized变量用来确保每个作业在运行之前都使它的JobID得到初始化。PerformRunJob抽象方法由派生的Job类实现,并且保持任务的实际逻辑。在作业的实现(PerformRunJob方法)成功运行之后,基类调用 RecordJobSuccess函数,该函数使用业务逻辑层的JobLogic类的UpdateJobDone方法来记录它在数据库中运行的时间以及预定的下一个运行时间。稍后,我将创建业务逻辑层的JobLogic类。
Job类既提供初始化JobID变量的功能,又提供在成功时用下一个运行时间更新数据库的功能。另外,您只须用特定于类的代码重写一个函数。这使您可以创建Job类的子类。为此,您需要创建两个类,以便运行特定类型的作业,并且从Job类继承以获得它们的其余功能。创建一个JobRunTest类和一个JobEmailUsers类,并且确保每个类都从Job类继承,如下所示:
public class JobRunTests : Job
现在,按如下方式重写这两个类的PerformRunJob方法(使用JobRunTest类作为示例):
protected override void PerformRunJob()
{
///Do RunTest specific logic here
}
将特定于作业的逻辑放到该方法的内部。负责运行作业并且更新数据库中下一个运行时间的其余代码是从Job基类中继承的。您的作业将组合对现有业务逻辑类的调用,以便运行复杂的过程。既然已经有了示例作业,那么让我们考察一下如何使用JobFactory对象创建这些作业。
JobFactory类用于创建每个JobID的相应子Job类。JobFactory类在它的静态 CreateJob函数中采用JobID变量,并且返回适当的Job子类。代码段3显示JobFactory中的代码。
代码段3 JobFactory类:
public static Job CreateJob(int currentJobID)
{
Job myJob;
switch(currentJobID)
{
case 1:
myJob = new JobEmailUsers();
break;
case 2:
myJob = new JobRunTest();
break;
default:
return null;
}
myJob.JobID = currentJobID;
return myJob;
}
CreateJob函数采用当前JobID,并且在一个case语句中用它来确定应当返回Job类的哪个子类。然后,初始化当前的JobID并且返回从Job派生的类。既然您具有了Job基类、它的特定于作业的子类以及用来选择要创建的类的方式,那么您就可以考察如何使用JobFlow类将这一切组合在一起。
要创建一个名为JobFlow的类以便收集和执行适当的作业,请添加一个名为“RunAllActiveJobs”的函数以遍历您需要运行的每个作业,并调用它们各自的RunSingleJob函数。您将需要使用RunAllActiveJobs函数来获取预定从数据库中经过业务层、数据访问层和存储过程运行的作业的列表,然后使用其各自的RunSingleJob函数来运行它们。以下代码显示JobFlow类的RunAllActiveJobs方法如何完成这些目标:
JobLogic jl = new JobLogic();
DataSet jobsActiveData = jl.GetAllActiveJobs();
foreach (DataRow jobsActive in jobsActiveData.Tables[0].Rows)
{
int currentJobID = Convert.ToInt32(jobsActive["JobID"]);
Job myJob = JobFactory.CreateJob(currentJobID);
myJob.RunSingleJob();
}
基本上,您将在数据库中存储作业,同时存储有关它们上次运行的时间以及代码在连续两次运行之间应当等待的时间间隔的信息。然后,通过BusinessLogic层中带有GetAllActiveJobs方法的JobLogic类来检索需要运行的作业。每个活动作业的ID都用于获得Job对象,如前所述,该对象的RunSingleJob方法可以用来执行任务。
作业计时信息
确定应当运行哪些预定作业意味着您需要存储有关它们的基本信息,例如,连续两次运行之间的时间间隔、它们的上次运行时间和它们下一次应当运行的时间。为了完成该工作,请在SQL Server数据库中创建一个作业表(参见表1)。
表1 Job表
Column Datatype JobID int identity JobTitle varchar(500) JobInterval datetime DateLastJobRan datetime DateNextJobStart datetimeJobID列保持该作业表中每个作业的唯一标识符。JobTitle列包含作业名称,以便您可以确定哪个作业正在运行。JobInterval列保持连续两个作业之间的时间间隔。它们是大于1/1/1900的日期和时间间隔,在作业成功后,应当将其添加到当前时间中,以计算下一个作业应当运行的时间。例如,JobInterval字段中的值1/2/1901 意味着需要将一年和一天添加到该作业上次运行的时间中。
DateLastJobRan列包含作业上次运行的日期和时间的datetime值。最后一列 DateNextJobStart包含作业下一次应当运行的时间。尽管该列应当是一个由JobInterval加DateLastJobRan计算结果而得的列,但如果您将该列设置为常规的datetime列,则可更生动地理解应用程序层。
检索和设置作业计时信息
要通过SQL Server数据库中新的存储过程检索和设置作业计时信息,这些存储过程必须找到该数据库所有需要由该应用程序运行的作业,更新该数据库中单个作业的信息以指示它已经运行,并且设置该作业的下一个作业运行日期。每个作业都在该数据库中具有一个DateNextJobStart列,以指示该作业应当运行的日期和时间。如果当前日期和时间已经超过DateNextJobStart列的日期和时间,则应当在该进程中运行该作业。选择应该运行的作业的存储过程如下所示:
Create PROCEDURE
dbo.Job_SelectJobs_NextJobStartBefore
@DateNextJobRunStartBefore datetime
AS
Select * FROM JOB Where DateNextJobStart < @DateNextJobRunStartBefore
该存储过程在Job表中选择的作业符合以下条件,其DateNextJobStart值早于(小于)@DateNextJobRunStartBefore DateTime参数的值。要查明应当运行哪些作业,只须通过该存储过程的参数传入当前日期和时间。既然您可以选择需要运行的作业,那么您就可以转而生成该过程以便在作业运行之后更新它们。用单个作业的上一个运行日期和下一个运行日期更新数据库的存储过程如下所示:
Create PROCEDURE dbo.Job_Update_StartEnd_CalcNext
@JobID int,
@DateLastJobRan datetime
AS
Update JOB
SET
DateLastJobRan = @DateLastJobRan,
DateNextJobStart = @DateLastJobRan + JobInterval
Where
JobID = @JobID
该过程用一个新的DateLastJobRan来更新由@JobID标识的作业,并且通过将JobInterval与传入的@DateLastJobRan相加来计算DateNextJobStart值。该过程只应当在@JobID中引用的作业之后运行,并且应当用等于作业上次运行的日期和时间的@DateLastJobRan参数来调用。
调用作业计时存储过程
您可以通过添加一个名为JobAccess的新类来扩展数据访问层,以便调用作业计时存储过程。数据访问层中函数的作用是将业务层传递给该层的参数转换为存储过程数据库查询,并且向业务层返回结果。数据访问层的函数中的参数将镜像它们所访问的存储过程的那些参数,因为它们不对这些值执行任何业务逻辑。您将通过Microsoft Data Application Building Block的SQLHelper类访问数据库。该类包含用于简化数据访问代码的功能,从而使您的代码更为简洁,可读性更高。
要更改数据访问层以运行预定作业,请首先向现有数据访问层中添加JobAccess类,以便保持安排作业所需的函数。接下来,在JobAccess类中创建一个函数,以返回需要通过调用Job_SelectJobs_NextJobStartBefore存储过程运行的作业的DataSet。您还将需要在JobAccess类中创建一个函数以调用Job_Update_StartEnd_CalcNext存储过程,但不返回结果。
首先,将JobAccess类添加到数据访问层。然后,编辑JobAccess类以添加下列“using”语句:
using System.Data;
using System.Data.SqlClient;
using Microsoft.ApplicationBlocks.Data;
现在,让我们考察一下如何添加SelectJobsBeforeDate函数以检索需要运行的作业的列表。以下为SQLHelper的ExecuteDataset函数的签名:
public static DataSet
ExecuteDataset(
string connectionString, string spName,
params object[] parameterValues)
以下为SelectJobsBeforeDate函数,它使用ExecuteDataset来调用Job_Update_StartEnd_CalcNext存储过程,并且返回结果的DataSet:
public DataSet SelectJobsBeforeDate(DateTime beforeDate)
{
return SqlHelper.ExecuteDataset(
ConnectionInfo.connectionString,
"Job_SelectJobs_NextJobStartBefore, myparams);
new object[]{new SqlParameter("BeforeDate", beforeDate)});
}
在作业运行之后,您需要执行相应的存储过程以更新有关作业的状态信息。完成该工作的方法UpdateJob将使用SQLHelper类的ExecuteNonQuery方法。以下是它的签名:
public static int ExecuteNonQuery(
string connectionString, string spName, params object[]
parameterValues)
UpdateJob方法可以按以下方式编写:
public void UpdateJob(int JobID, DateTime dateLastJobRan)
{
string connStr = ConnectionInfo.connectionString;
string spName = "Job_Update_StartEnd_CalcNext";
SqlParameter myparam1 = new SqlParameter("JobID", JobID);
SqlParameter myparam2 = new
SqlParameter("DateLastJobRan",dateLastJobRan);
object[] myparams = {myparam1, myparam2};
SqlHelper.ExecuteNonQuery(connStr, spName, myparams);
}
JobAccess类中的UpdateJob函数应该镜像传递给它所使用的存储过程的参数。因此,UpdateJob函数具有一个JobID参数和一个dateLastJobRan参数,并且它们的数据类型与Job_Update_StartEnd_CalcNext存储过程中的参数相同。使用JobID和dateLastJobRan参数,您可以创建两个SqlParameters,将它们放到myparams对象数组中,并且使用ExecuteNonQuery函数来执行该存储过程。既然您已经创建了JobAccess类,那么您需要创建最后一个类层,以便将流程层和数据访问层连接起来。
处理预定作业
最后一个必须加以修改以便处理预定作业的层是业务逻辑层,我将其称为JobLogic。该类将对流程层和数据访问层之间的变量执行基本逻辑。
首先,使用下列语句向DataAccess层中添加JobLogic类:
using System.Data;
using ScheduledWebService.DataAccess;
其次,生成JobLogic类的GetAllActiveJobs函数,以查找所有仍然需要在当前时间或之前运行的作业,如下所示:
public DataSet GetAllActiveJobs()
{
JobAccess ja = new JobAccess();
return ja.SelectJobsBeforeDate(DateTime.Now);
}
GetAllActiveJobs函数创建JobAccess类的实例,并且用当前日期的参数值调用它的SelectJobsBeforeDate。GetAllActiveJobs选取当前日期以传递给该函数,因此您可以查明哪些作业被安排在当前时间之前运行。
最后,创建JobLogic类的UpdateJobDone函数以更新数据库,以便指示所指定的作业刚刚完成,如下所示:
public void UpdateJobDone(int JobID)
{
JobAccess ja = new JobAccess();
ja.UpdateJob(JobID, DateTime.Now);
}
该函数创建JobAccess类的实例并且调用它的UpdateJob方法。它传递JobID参数,然后使用dateLastJobRan参数的当前日期。您需要将当前日期和时间传递给UpdateJob函数,因为它是作业成功完成的时间。
小结
通过用自动完成的任务扩展ASP.NET应用程序,可以显式计划事件,而不是等待执行代码的请求。您可以利用这一功能执行多种任务——从运行复杂的计算到定期创建报告并将其发送给经理。这样的任务可以同时重用现有的逻辑和ASP.NET层中的对象,并且可以减少开发时间和提高可维护性。您还可以扩展该计划程序启动的作业,而无须更改启动它的Windows服务。
请注意,对于我在本文中讨论的内容,有许多不同的情况。例如,您可以不创建自定义的Windows服务来充当计划程序,而是使用某种像Windows任务计划程序一样简单的工具,它非常可靠,并且实现了本文中讨论的大多数功能,而无需创建自定义的Windows服务来充当计划程序。总之,.NET Framework已经大大简化了Windows服务的创建,因此即使您先前已经发现它们非常难以使用,您也应当将它们作为一种选择而重新加以考虑。类似地,Web服务是应用程序向其他应用程序公开功能的一种很好的方式,并且将继续在这一方面发挥作用。
作者简介
Andrew Needleman是Claricode的一名经营合伙人,这是一家位于波士顿附近的咨询公司,专门从事在 .NET中设计和开发N层Web应用程序。他已经培训了数以百计的C#、.NET Framework和Visual Basic .NET领域的开发人员。
数据交换中心的方案设计
中关村在线 【转载】 作者: 2005年01月29日 01:41建立安全、稳定、高性能、跨平台、跨系统、跨应用
4.3.1 信息交换的需求分析
电子政务中信息交换按交换对象分为三大类:
基于政府外网之上的政府部门之间的信息交换
基于政府内网之上的政府内部之间的信息交换
基于Internet网和政府外网之上的政府同企业
例如,一个城市财政局办理"行政事业性收费"立项的流程可描述如下
一个城市的政府机关达几十个(例如武汉市的委局办就有139个
很明显,以局的信息系统即所谓信息孤岛为单位设计同其它局交换的信
根据一项调查,世界上每年花费在应用接口上的维护费就占企业IT投
在总线上,点之间交换是对等的,若某个节点有故障不影响其它节点之
由于个人、企业、政府部门都是用已有的工具或系统设计表格
要进行透明的信息交换要解决一系列技术问题,信息的格式
1}信息交换的语义识别
数据格式、语法所描述的信息应该有效,各种系统在传递、读取
表达的内容、格式能满足所有政府部门各项业务的要求
2}传输的要求
数据格式易于传输,能够实现各个应用系统之间的同步
3)安全方面的要求
交换的数据文档需要基于应用系统之间约定的规则进行验证
4)非功能性要求
格式稳定性高,易于管理,有良好的可扩展性和可增长性
4.3.2 信息交换原理
1、 信息交换模型
信息交换是通过网络进行的,而网络信息交换有国际公认的OSI (Open System Interconnect)七层模型。而电子政务信息交换则只注重
在这个模型中,应用系统之间按应用层协议进行通讯
2、政务系统中的信息交换结构
在每个交换节点上,从OSI七层网络模型分析数据交换平台主要是解
电子政务系中不同局之间的数据交换,由逻辑上同构的许多数据交换节
应用层也是OSI中的应用层。
电子政务系统中的业务应用系统都处在应用层,它们是平台的使用者
OSI中的表式层在次分为:内容管理、数据交换
1)、内容管理(表示层)
内容管理层是指内容的表示(存储)、操作(传送)和授权管理等功能
一个标准数据交换平台的任务可以分为两个方面,一个是对遗留业务系
操作集合:这是数据交换功能的直接体现,应该包括查询、获取
授权管理:它用于规定在具体的数据交换过程中,哪些业务数据能够在
内容管理层在工作过程中,如果有需要跟远端其它数据交换结点进行互
在内容管理层提供简单易用的界面方便用户轻松定义各种类型的XML
从功能上,这一层还可细分为以下几个部分:
l 图形定义及显示模块:构造树形图,为每个节点分配属性用以定义数据
XML描述文档及schema生成模块:需要建模的数据文件主要可
XML文件,建模的过程实际上是产生它的schema文件的过程。
二进制格式文件,建模的目的是生成这种二进制格式文件所对应的XM
l 模型验证模块:对于二进制格式文件,用户可以在建模后输入实际的二
l XSLT引擎:提供符合规范的XSLT转换器,其它功能模块通过调
l 文本(二进制)文件到XML文件的转换引擎:利用XML描述文档将
2)、数据交换(表式层 信息服务的支持)
数据交换层的任务是完成不同数据交换结点之间的互操作
信息的统一封装,即信息的打包和信封的书写功能
统一编址,应支持一套统一的、简单易用、易扩展、易管理的地址编码
信息的可靠传输
传输的效率
可管理性,要对传输的过程进行全程监控,提供日志、审计
2) 安全的数据传输(表示层)
数据传输层用于实现数据交换结点之间的数据传输。在软件层面
HTTP协议具有简单、完备、轻量级、扩展能力强等特点
安全性可以通过引入基于PKI(Public Key Infrastructure,公钥体系)的CA
4.3.3 数据交换中心的设计
要设计一个数据交换中心,实现政府部门之间的数据的透明交换
信息的统一表示
完整的消息服务能力
功能完备的交换平台软件系统
1、 信息的统一表示
要实现信息共享,实现异构系统之间的互联互通,信息的统一表示是关
1)、元语言标准
元语言是描述其它语言的语言。电子政务的信息表示语言采用XML元
2)信息编码标准
该项标准对字符的编码、字符集定义、字符引用、字体的表示进行了规
3)元数据标准
元数据是描述电子数据的数据,制定该标准是为了方便政府信息资源有
在异构关系数据库之间还可通过建立元模型、元元模型统一数据的语义
3) 显示标准
在电子政务系统中,要将数据和数据的显示分开。这样就把数据的加工
4) 解析、转换和封装标准
要实时共享交换的各种信息,必须具备效率高的数据结构封装、解析
2、完整的消息服务能力
电子政务平台是一个中间件软件,它在信息交换过程中要进行频繁的信
信息的统一封装:信息的打包和信封的书写功能
统一编址:应支持一套统一的、简单易用、易扩展、易管理的地址编码
信息的可靠传输:将信息可靠的传输到目的地
路由管理:在多个信息交换节点时,实现信息的准确传递。
传输的效率:采用信息的表示与交换分开、专用的交换设备
可管理性:要对传输的过程进行全程监控,提供日志、审计
2、 信息交换平台的体系结构
实现信息的交换根据环境的不同有三种方式:
1)具有相同数据库管理系统(DBMS)的分布式系统的数据交换
相应系统的有关功能:
在ORACLE系统中,可以利用快照技术实现表数据的交换;
在SYBASE系统中,可以利用复制服务器 实现数据的交换;
2)利用已有的消息中间件服务器:IBM的MQSerries
实时处理部分
实时交易处理部分中采用以WEB应用服务器Websphere Application Server、交易服务器Txseries/CICS等中间件为核
为了防止交换高峰期间对WEB应用服务器压力过大造成该部分处理能
脱机交换处理:
对于所有脱机交易处理,在数据中心配置MQ Series Integrator和MQSeries数据传输中间件作为全系统
考虑到在整个系统需要和税务、公安、卫生等系统实现数据交换
在数据交换中心除了配置MQSeries Integrator和MQ Series之外,同时配置一个数据库专门存放整个系统中各子系统
所有需要与政务数据中心进行脱机交易、批量数据处理
3)、通用的数据交换器的结构:在电子政务系统中
3)自成体系的交换平台
利用多层结构体系、J2EE+XML、Web Service、数据库等技术,构造数据交换、数据流引擎和数据中
本方案有一个数据交换平台和一个任意可装配的适配器组成
适配器的主要功能设计:
1) 信息交换器:实现不同格式的文件向XML的转换或反之
2) SOAP服务:按SOAP格式打包或拆包
3) 安全认证:调用相关API进入CA认证
4) 密码服务:加密、解密或压缩
5) DB访问代理:对典型数据库提供访问接口(JDBC、ODBC)
6) 工作流引擎:为工作流引擎的同步/异步、协同工作提供服务
平台管理器的主要功能设计:
1) 消息管理:管理消息队列,同中间件服务器中的消息管理协调工作
2) 用户管理:管理联接到服务器的用户,设置访问权限
3) 模型管理:管理根据应用中的表格以及各种表格的转换模型
4) 监控管理:运行状态、运行流量、负载能力、日志等的管理
数据的透明交换涉及数据格式、数据结构、数据语义的统一定义
2007年5月31日
使用 Anthem.NET 的常见回调(Callback)处理方式小结[转]
在 Anthem.NET 中,通过 XmlHttp 或 XmlHttpRequest 组件对服务器端所作的一次无刷新调用(通常是异步模式),称为一个回调(Callback)。
本文内容是对 Anthem.NET 框架自带范例代码的整理和归纳,着重小结一下在使用 Anthem.NET 进行 Ajax 开发的时候所涉及的调用流程控制相关的内容。至于控件的使用,因为逻辑简单,这里不做叙述。
在本文后,计划写一篇文章对调用流程及其编程时的可控制点做比较完备的归纳。
一、普通的调用<%@ Register TagPrefix="anthem" Namespace="Anthem" Assembly="Anthem" %>
<anthem:Button id="button" runat="server" Text="Click Me!" />
<anthem:Label id="label" runat="server" />
<script runat="server">
void button_Click(object sender, EventArgs e) {
label.Text = DateTime.Now.ToString();
label.UpdateAfterCallBack = true;
}
</script>
// 回调之前,可在这里取消回调二、在回调前后添加自定义客户端函数的执行逻辑
几个常用的属性:
PreCallBackFunction:用于定义回调前执行的函数,通常可以在这里加入确认的判断。
在这个函数里 return false 将会取消回调。
PostCallBackFunction: 回调后执行的函数。
TextDuringCallBack: 用于定义回调过程中控件显示的提示信息(通常是提示等待的文字)
EnabledDuringCallBack: 在回调过程中,控件是否禁用。
CallBackCancelledFunction: 如果回调被取消,则会调用这个函数。
代码例子:<anthem:Button id="button1" runat="server" Text="Click Me!" TextDuringCallBack="Working..." EnabledDuringCallBack="false" PreCallBackFunction="button1_PreCallBack" PostCallBackFunction="button1_PostCallBack" CallBackCancelledFunction="button1_CallBackCancelled" />
<script language="javascript" type="text/javascript">
function button1_PreCallBack(button) {
if (!confirm('Are you sure you want to perform this call back?')) {
return false;
}
document.getElementById('button2').disabled = true;
}
// 回调完成后
function button1_PostCallBack(button) {
document.getElementById('button2').disabled = false;
}
// 取消回调后
function button1_CallBackCancelled(button) {
alert('Your call back was cancelled!');
}
</script>
注意以上这些客户端处理函数中,都可以传递 control 本身作为参数,因此在必要的情况下这些函数是可以被重用的。(比如对一组类似的控件的回发事件进行处理)
三、调用服务器页面的方法
服务器端需要做的事情:
[Anthem.Method]
public int Add(int a, int b) {
return a + b;
}
void Page_Load() {
Anthem.Manager.Register(this);
}
客户端:
<input id="a" size="3" value="1">
<input id="b" size="3" value="2">
<button onclick="DoAdd(); return false;" type="button">Add</button>
<input id="c" size="6">
// 参数的含义依次是:
// 服务器方法的名字,
// 方法的参数(以 js 数组形式传递),
// 服务器端方法返回结果时调用的回调函数(因为是异步模式)。
Anthem_InvokePageMethod(
'Add',
[document.getElementById('a').value, document.getElementById('b').value],
function(result) {
document.getElementById('c').value = result.value;
}
);
调用后,在回调函数的参数中得到的 result 变量,是一个包含 value 和 error 两个属性的对象。如果在服务器端发生错误,则 value 为 null, 而 error 中包含错误数据。
四、如何处理回调时可能发生的异常
在页面中定义 Anthem_Error js 函数,则可处理所有回调时的未处理异常。
<script type="text/javascript">
function Anthem_Error(result) {
alert('Anthem_Error was invoked with the following error message: ' +
result.error);
}
</script>
异常也可以在服务器端处理。只要定义下列名称的方法:
void Page_Error()
{
Anthem.Manager.AddScriptForClientSideEval("window.location = 'http://anthem-dot-net.sf.net/'");
}
在服务器端处理有一些额外的好处,主要是可以将异常信息记录到日志.
五、页面跳转
在 Callback 的处理中,不能用 Response.Redirect 来处理页面跳转,因为这些函数是通过 js 的无刷新来调用的。代替的办法是用 Anthem.Manager 回传一段 js 给客户端去用 eval 的方式执行,从而达到页面跳转的效果(推而广之,这个 eval 的功能当然不限于跳转页面,可以干很多其他的事情)。
代码示例:
Anthem.Manager.AddScriptForClientSideEval("window.location = 'http://anthem-dot-net.sourceforge.net/';");
六、几个全局级别的客户端回调函数
我们可以在客户端定义几个特殊名字的函数,以供 Anthem 在每一次回调的前后调用。这些函数包括:
Anthem_PreCallBack(),
Anthem_CallBackCancelled(),
Anthem_PostCallBack(),
除此之外还包括前面说到的 Anthem_Error() 等。
这里典型的一个应用场景是,在每次回调开始后,我们在 Anthem_PreCallBack() 中创建一个“loading”信息的层,然后在取消(Anthem_CallBackCancelled) 或成功回调后(Anthem_PostCallBack),移除这个层。
这样可以很方便的模拟出类似 Gmail 中的加载效果。
七、回调过程中向页面添加了新的 js 脚本
这种情况下必须设定一个额外的属性:
Anthem.Manager.IncludePageScripts = true;
例子:
<script runat="server">
protected void button1_Click(object sender, EventArgs e)
{
HtmlButton button = new HtmlButton();
button.InnerText = "Now click me!"
button.Attributes["onclick"] = "ThankYou();"
placeholder1.Controls.Add(button);
placeholder1.UpdateAfterCallBack = true;
string script = @"<script type=""text/javascript"">
function ThankYou() {
alert('Thank you!');
}
</" + "script>"
#if V2
Page.ClientScript.RegisterClientScriptBlock(typeof(Page), script, script);
#else
Page.RegisterClientScriptBlock(script, script);
#endif
Anthem.Manager.IncludePageScripts = true;
}
</script>
八、PreUpdate 事件
控件在 Render 之前,如果 UpdateAfterCallBack 为 true,则会引发这个事件。
目前这个事件的用途似乎不大。
首先我们给我们的GridView 添加一个模板列,如下:
以下是引用片段:
<ASP:TemplateField HeaderText="Delete" ShowHeader="False">
<ItemStyle ForeColor="Red" />
<ItemTemplate>
<asp:LinkButton ID="BtnDelete" runat="server" CausesValidation="False" CommandName="Delete"
Text="Delete"></asp:LinkButton>
</ItemTemplate>
</asp:TemplateField>
其次我们给我们所添加的模板列添加:OnClientClick="return confirm('确认要删除此行信息吗?')" ,如下:
以下是引用片段:
<asp:TemplateField HeaderText="Delete" ShowHeader="False">
<ItemStyle ForeColor="Red" />
<ItemTemplate>
<asp:LinkButton ID="BtnDelete" runat="server" CausesValidation="False" CommandName="Delete"
Text="Delete" OnClientClick="return confirm('确认要删除此行信息吗?')"></asp:LinkButton>
</ItemTemplate>
</asp:TemplateField>
点击删除时就会先在客户端弹出“确认要删除吗?”对话框,一般我们确认删除。
- .net开发过程中,错误集锦
- net开发过程中,错误集锦
- 开发过程中,错误集锦。
- vs2005开发过程中,错误集锦[转载]
- Java开发过程中遇到的一些小错误集锦-以后会不断地更新
- .Net开发过程中安装、调试的常见问题与错误!!!
- 开发过程中出现的BUG集锦
- Maven项目开发过程中问题集锦
- Android开发_错误集锦------收录中
- 开发中遇到的错误集锦
- ASP.NET错误集锦
- js开发过程集锦
- android 开发过程中错误
- 开发过程中错误记录
- iOS开发错误集锦
- web开发错误集锦
- web开发错误集锦
- Android开发错误集锦
- 对System V命名信号量的封装类,用于进程/线程间互斥
- .net
- freemarker 函数 计算一个月有多少天
- spring的quartz定时
- 郁闷的连接数限制
- net开发过程中,错误集锦
- vc++ 64位长整型转换成字符串
- logic:iterate应用
- 基于DirectShow的流媒体解码和回放 (转载 作者 :刘 涛 ,天极网)
- 谈谈我为什么不用易趣和卓越
- Xslt中属性的访问方法总结
- 李开复给中国学生的第二封信:从优秀到卓越
- 【亲自动手试验过的】硬盘免光驱安装Fedora5
- 通过避免下列10个常见ASP.NET缺陷使网站平稳运行