[转载]ASP.NET MVC中的扩展点(四)过滤器

mikel阅读(1007)

[转载]MVC中的扩展点(四)过滤器 – xfrog – 博客园.

过滤器是一组.NET特性,MVC在特定运行时点调用这些特性上的指定方法,以此实现功能注入。MVC包含四个基本的过滤器类型:授权 (Authorization)、活动(Action)、结果(Result)以及异常(Exception)。MVC为这四中过滤器提供了接口定 义:IAuzhorizationFilter、IActionFilter、IResultFilter、IExceptionFilter,所以 MVC在运行时知道如何调用过滤器上的方法。
MVC实现的默认过滤器如以下类图所示:

FiltersClass
注意上图中Controller实现了四个基本的过滤器接口,所以在我们的控制器中,可直接重写过滤器方法来实现过滤器功能,此种方式适用于过滤器功能特定于控制器的情景,如果过滤器功能会跨多个控制器使用,那么使用特性的方式可避免重复代码。

各种默认过滤器,都直接或间接的继承于FilterAttribute类,FilterAttribute有一个Order属性,用于过滤器的排序。另 外,MVC为Action过滤器和Result过滤器提供了一个抽象基类ActionFilterAttribute,它同时实现了 IActionFilter和IResultFilter接口。

过滤器的执行过程由控制器的ActionInvoker对象实现,他是一个实现了IActionInvoker接口的类,ActionInvoker是控 制器的一个公共属性,所以我们可以实现自己的IActionInvoker类,然后通过设定Controller的ActionInvoker属性来指定 自定义的Invoker。MVC中默认的ActionInvoker类为ControllerActionInvoker,其执行过滤器列表的过程如下图 所示:
Filters
上图表示的是过滤器在没有发生任何异常时的执行顺序:获取过滤器列表——>依次调用按Order排序的授权过滤器,如果某个授权过滤器设置了 AuthorizationContext参数的Result属性,则立即终止剩余授权过滤器的调用,直接执行Result,生成页面应答内容。如果所有 授权过滤器都通过,并且Result为空,则继续调用Action过滤器上的OnActionExecuting方法,如果全部通过,将执行控制器上相应 的Action方法获取ActionResult对象,随后再按相反顺序执行Action过滤器上的OnActionExecuted方法,之后转入到 Result过滤器列表中,同Action类似,先按Order顺序执行OnResutExecuting方法,注意在 OnResultExecuting方法上,过滤器可通过将ResultExecutingContext.Cancel属性设置为True来立即终止 Invoker的执行(即不会执行ActionResult及Result过滤器上的OnResultExecuted方法),如果所有的 OnResultExecuting方法都通过,则通过ActionResult.ExecuteResult方法生成应答内容,最后再按相反顺序调用 Result过滤器上的OnResultExecuted方法。

对于ControllerActionInvoker类在执行过程中产生的异常,会传给Exception过滤器(MVC默认的异常处理过滤器为 HandleErrorAttribute)来处理。在ControllerActionInvoker的各个节点产生的异常会影响到过滤器的执行过程, 下面分几种情况来详细了解:

我们假设某个Action上有A、B两个授权过滤器,有A、B、C三个Action过滤器和Result过滤器以及A、B两个异常过滤器,首先,在没有异常发生时得执行过程如下图:A、B两个异常过滤器未执行,其余所有过滤器都执行

NoneException

如果在B.OnAuthorization上发生异常,会直接转到异常过滤器上,其余Action过滤器、Result过滤器及Action方法都不会调用:

AuthorizeException

如果在B.OnActionExecuting上发生异常,则会跳转到Action过滤器列表的上一个过滤器(即A),执行A.OnActionExecuted,然后转到异常过滤器:

ActionExecutingException

如果在B.OnActionExecuting上发生异常,但是在A.OnActionExecuted中将ActionExecutedContext.ExceptionHandled设置为true,此时将会继续执行Result过滤器,并忽略异常过滤器:

ActionExecuted.ExceptionHandled

如果在执行控制器的Action方法时发生异常,将会继续执行ActionExecuted方法,随后转到异常过滤器:

ControllerActionException

如果在Action的OnActionExecuted方法上发生异常,将会继续执行完Action过滤器,然后转到异常过滤器:

ActionExecutedException

在Result过滤器上的OnResultExecuting方法和OnResultExecuted方法上发生异常,其处理方式与Action过滤器类似,此处不再详细说明。

如果在Result过滤器OnResultExecuting中将ResultExecutingContext的Cancel属性设置为true,将立即完成Action方法执行:

ResultExecuting.Cancel

另外需要注意,在异常过滤器中将ExceptionContext.ExceptionHandled设置为true并不会终止异常过滤器的执行,该属性 仅仅用于标记“异常已被处理”。所以我们可以通过异常过滤器来实现错误日志记录功能,无需担心漏掉”已被处理”的异常。

如果我们要实现自定义的权限验证功能,应该从AuthorizeAttribute类继承,原因在于AuthorizeAttribute已经优化了与OutputCacheAttribute缓存过滤器的协调,避免因缓存原因造成权限验证失效的情况。

[转载]A*寻路初探 GameDev.net

mikel阅读(1033)

[转载]A*寻路初探 GameDev.net – GameRes.com.

A*寻路初探 GameDev.net

译者序:很久以前就知道了A*算法,但是从未认真读过相关的文章,也没有看过代码,只是脑子里有个模糊的概念。这次决定从头开始,研究一下这个被人推崇备至的简单方法,作为学习人工智能的开始。
这 篇文章非常知名,国内应该有不少人翻译过它,我没有查找,觉得翻译本身也是对自身英文水平的锻炼。经过努力,终于完成了文档,也明白的A*算法的原理。毫 无疑问,作者用形象的描述,简洁诙谐的语言由浅入深的讲述了这一神奇的算法,相信每个读过的人都会对此有所认识(如果没有,那就是偶的翻译太差了 –b)。
原文链接:http://www.gamedev.net/reference/articles/article2003.asp
以下是翻译的正文。(由于本人使用ultraedit编辑,所以没有对原文中的各种链接加以处理(除了图表),也是为了避免未经许可链接的嫌疑,有兴趣的读者可以参考原文。

会者不难,A*(念作A星)算法对初学者来说的确有些难度。

这篇文章并不试图对这个话题作权威的陈述。取而代之的是,它只是描述算法的原理,使你可以在进一步的阅读中理解其他相关的资料。

最后,这篇文章没有程序细节。你尽可以用任意的计算机程序语言实现它。如你所愿,我在文章的末尾包含了一个指向例子程序的链接。 压缩包包括C++和Blitz Basic两个语言的版本,如果你只是想看看它的运行效果,里面还包含了可执行文件。

我们正在提高自己。让我们从头开始。。。

序:搜索区域

假设有人想从A点移动到一墙之隔的B点,如下图,绿色的是起点A,红色是终点B,蓝色方块是中间的墙。


[图1]

你 首先注意到,搜索区域被我们划分成了方形网格。像这样,简化搜索区域,是寻路的第一步。这一方法把搜索区域简化成了一个二维数组。数组的每一个元素是网格 的一个方块,方块被标记为可通过的和不可通过的。路径被描述为从A到B我们经过的方块的集合。一旦路径被找到,我们的人就从一个方格的中心走向另一个,直 到到达目的地。

这些中点被称为“节点”。当你阅读其他的寻路资料 时,你将经常会看到人们讨论节点。为什么不把他们描述为方格呢?因为有可能你的路径被分割成其他不是方格的结构。他们完全可以是矩形,六角形,或者其他任 意形状。节点能够被放置在形状的任意位置-可以在中心,或者沿着边界,或其他什么地方。我们使用这种系统,无论如何,因为它是最简单的。

开始搜索

正如我们处理上图网格的方法,一旦搜索区域被转化为容易处理的节点,下一步就是去引导一次找到最短路径的搜索。在A*寻路算法中,我们通过从点A开始,检查相邻方格的方式,向外扩展直到找到目标。

我们做如下操作开始搜索:


1,从点A开始,并且把它作为待处理点存入一个“开启列表”。开启列表就像一张购物清单。尽管现在列表里只有一个元素,但以后就会多起来。你的路径可能会通过它包含的方格,也可能不会。基本上,这是一个待检查方格的列表。
2,寻找起点周围所有可到达或者可通过的方格,跳过有墙,水,或其他无法通过地形的方格。也把他们加入开启列表。为所有这些方格保存点A作为“父方格”。当我们想描述路径的时候,父方格的资料是十分重要的。后面会解释它的具体用途。
3,从开启列表中删除点A,把它加入到一个“关闭列表”,列表中保存所有不需要再次检查的方格。

在这一点,你应该形成如图的结构。在图中,暗绿色方格是你起始方格的中心。它被用浅蓝色描边,以表示它被加入到关闭列表中了。所有的相邻格现在都在开启列表中,它们被用浅绿色描边。每个方格都有一个灰色指针反指他们的父方格,也就是开始的方格。


[图2]

接着,我们选择开启列表中的临近方格,大致重复前面的过程,如下。但是,哪个方格是我们要选择的呢?是那个F值最低的。

路径评分

选择路径中经过哪个方格的关键是下面这个等式:

F = G + H

这里:
* G = 从起点A,沿着产生的路径,移动到网格上指定方格的移动耗费。
* H = 从网格上那个方格移动到终点B的预估移动耗费。这经常被称为启发式的,可能会让你有点迷惑。这样叫的原因是因为它只是个猜测。我们没办法事先知道路径的长 度,因为路上可能存在各种障碍(墙,水,等等)。虽然本文只提供了一种计算H的方法,但是你可以在网上找到很多其他的方法。

我们的路径是通过反复遍历开启列表并且选择具有最低F值的方格来生成的。文章将对这个过程做更详细的描述。首先,我们更深入的看看如何计算这个方程。

正 如上面所说,G表示沿路径从起点到当前点的移动耗费。在这个例子里,我们令水平或者垂直移动的耗费为10,对角线方向耗费为14。我们取这些值是因为沿对 角线的距离是沿水平或垂直移动耗费的的根号2(别怕),或者约1.414倍。为了简化,我们用10和14近似。比例基本正确,同时我们避免了求根运算和小 数。这不是只因为我们怕麻烦或者不喜欢数学。使用这样的整数对计算机来说也更快捷。你不就就会发现,如果你不使用这些简化方法,寻路会变得很慢。

既然我们在计算沿特定路径通往某个方格的G值,求值的方法就是取它父节点的G值,然后依照它相对父节点是对角线方向或者直角方向(非对角线),分别增加14和10。例子中这个方法的需求会变得更多,因为我们从起点方格以外获取了不止一个方格。

H 值可以用不同的方法估算。我们这里使用的方法被称为曼哈顿方法,它计算从当前格到目的格之间水平和垂直的方格的数量总和,忽略对角线方向。然后把结果乘以 10。这被成为曼哈顿方法是因为它看起来像计算城市中从一个地方到另外一个地方的街区数,在那里你不能沿对角线方向穿过街区。很重要的一点,我们忽略了一 切障碍物。这是对剩余距离的一个估算,而非实际值,这也是这一方法被称为启发式的原因。想知道更多?你可以在这里找到方程和额外的注解。

F的值是G和H的和。第一步搜索的结果可以在下面的图表中看到。F,G和H的评分被写在每个方格里。正如在紧挨起始格右侧的方格所表示的,F被打印在左上角,G在左下角,H则在右下角。


[图3]

现在我们来看看这些方格。写字母的方格里,G = 10。这是因为它只在水平方向偏离起始格一个格距。紧邻起始格的上方,下方和左边的方格的G值都等于10。对角线方向的G值是14。

H 值通过求解到红色目标格的曼哈顿距离得到,其中只在水平和垂直方向移动,并且忽略中间的墙。用这种方法,起点右侧紧邻的方格离红色方格有3格距离,H值就 是30。这块方格上方的方格有4格距离(记住,只能在水平和垂直方向移动),H值是40。你大致应该知道如何计算其他方格的H值了~。

每个格子的F值,还是简单的由G和H相加得到

继续搜索

为了继续搜索,我们简单的从开启列表中选择F值最低的方格。然后,对选中的方格做如下处理:

4,把它从开启列表中删除,然后添加到关闭列表中。
5,检查所有相邻格子。跳过那些已经在关闭列表中的或者不可通过的(有墙,水的地形,或者其他无法通过的地形),把他们添加进开启列表,如果他们还不在里面的话。把选中的方格作为新的方格的父节点。
6,如果某个相邻格已经在开启列表里了,检查现在的这条路径是否更好。换句话说,检查如果我们用新的路径到达它的话,G值是否会更低一些。如果不是,那就什么都不做。
另一方面,如果新的G值更低,那就把相邻方格的父节点改为目前选中的方格(在上面的图表中,把箭头的方向改为指向这个方格)。最后,重新计算F和G的值。如果这看起来不够清晰,你可以看下面的图示。

好了,让我们看看它是怎么运作的。我们最初的9格方格中,在起点被切换到关闭列表中后,还剩8格留在开启列表中。这里面,F值最低的那个是起始格右侧紧邻的格子,它的F值是40。因此我们选择这一格作为下一个要处理的方格。在紧随的图中,它被用蓝色突出显示。


[图4]

首先,我们把它从开启列表中取出,放入关闭列表(这就是他被蓝色突出显示的原因)。然后我们检查相邻的格子。哦,右侧的格子是墙,所以我们略过。左侧的格子是起始格。它在关闭列表里,所以我们也跳过它。

其 他4格已经在开启列表里了,于是我们检查G值来判定,如果通过这一格到达那里,路径是否更好。我们来看选中格子下面的方格。它的G值是14。如果我们从当 前格移动到那里,G值就会等于20(到达当前格的G值是10,移动到上面的格子将使得G值增加10)。因为G值20大于14,所以这不是更好的路径。如果 你看图,就能理解。与其通过先水平移动一格,再垂直移动一格,还不如直接沿对角线方向移动一格来得简单。

当我们对已经存在于开启列表中的4个临近格重复这一过程的时候,我们发现没有一条路径可以通过使用当前格子得到改善,所以我们不做任何改变。既然我们已经检查过了所有邻近格,那么就可以移动到下一格了。

于 是我们检索开启列表,现在里面只有7格了,我们仍然选择其中F值最低的。有趣的是,这次,有两个格子的数值都是54。我们如何选择?这并不麻烦。从速度上 考虑,选择最后添加进列表的格子会更快捷。这种导致了寻路过程中,在靠近目标的时候,优先使用新找到的格子的偏好。但这无关紧要。(对相同数值的不同对 待,导致不同版本的A*算法找到等长的不同路径。)

那我们就选择起始格右下方的格子,如图。


[图5]

这 次,当我们检查相邻格的时候,发现右侧是墙,于是略过。上面一格也被略过。我们也略过了墙下面的格子。为什么呢?因为你不能在不穿越墙角的情况下直接到达 那个格子。你的确需要先往下走然后到达那一格,按部就班的走过那个拐角。(注解:穿越拐角的规则是可选的。它取决于你的节点是如何放置的。)

这 样一来,就剩下了其他5格。当前格下面的另外两个格子目前不在开启列表中,于是我们添加他们,并且把当前格指定为他们的父节点。其余3格,两个已经在开启 列表中(起始格,和当前格上方的格子,在表格中蓝色高亮显示),于是我们略过它们。最后一格,在当前格的左侧,将被检查通过这条路径,G值是否更低。不必 担心,我们已经准备好检查开启列表中的下一格了。

我们重复这个过程,知道目标格被添加进开启列表,就如在下面的图中所看到的。


[图6]

注 意,起始格下方格子的父节点已经和前面不同的。之前它的G值是28,并且指向右上方的格子。现在它的G值是20,指向它上方的格子。这在寻路过程中的某处 发生,当应用新路径时,G值经过检查变得低了-于是父节点被重新指定,G和F值被重新计算。尽管这一变化在这个例子中并不重要,在很多场合,这种变化会导 致寻路结果的巨大变化。

那么,我们怎么确定这条路径呢?很简单,从红色的目标格开始,按箭头的方向朝父节点移动。这最终会引导你回到起始格,这就是你的路径!看起来应该像图中那样。从起始格A移动到目标格B只是简单的从每个格子(节点)的中点沿路径移动到下一个,直到你到达目标点。就这么简单。


[图7]

A*方法总结

好,现在你已经看完了整个说明,让我们把每一步的操作写在一起:

1,把起始格添加到开启列表。
2,重复如下的工作:
a) 寻找开启列表中F值最低的格子。我们称它为当前格。
b) 把它切换到关闭列表。
c) 对相邻的8格中的每一个?
* 如果它不可通过或者已经在关闭列表中,略过它。反之如下。
* 如果它不在开启列表中,把它添加进去。把当前格作为这一格的父节点。记录这一格的F,G,和H值。
* 如果它已经在开启列表中,用G值为参考检查新的路径是否更好。更低的G值意味着更好的路径。如果是这样,就把这一格的父节点改成当前格,并且重新计算这一格的G和F值。如果你保持你的开启列表按F值排序,改变之后你可能需要重新对开启列表排序。

d) 停止,当你
* 把目标格添加进了开启列表,这时候路径被找到,或者
* 没有找到目标格,开启列表已经空了。这时候,路径不存在。
3.保存路径。从目标格开始,沿着每一格的父节点移动直到回到起始格。这就是你的路径。

题外话

离 题一下,见谅,值得一提的是,当你在网上或者相关论坛看到关于A*的不同的探讨,你有时会看到一些被当作A*算法的代码而实际上他们不是。要使用A*,你 必须包含上面讨论的所有元素--特定的开启和关闭列表,用F,G和H作路径评价。有很多其他的寻路算法,但他们并不是A*,A*被认为是他们当中最好的。 Bryan Stout在这篇文章后面的参考文档中论述了一部分,包括他们的一些优点和缺点。有时候特定的场合其他算法会更好,但你必须很明确你在作什么。好了,够多 的了。回到文章。

实现的注解

现在你已经明白了基本原理,写你的程序的时候还得考虑一些额外的东西。下面这些材料中的一些引用了我用C++和Blitz Basic写的程序,但对其他语言写的代码同样有效。

1, 维护开启列表:这是A*寻路算法最重要的组成部分。每次你访问开启列表,你都需要寻找F值最低的方格。有几种不同的方法实现这一点。你可以把路径元素随意 保存,当需要寻找F值最低的元素的时候,遍历开启列表。这很简单,但是太慢了,尤其是对长路径来说。这可以通过维护一格排好序的列表来改善,每次寻找F值 最低的方格只需要选取列表的首元素。当我自己实现的时候,这种方法是我的首选。

在 小地图。这种方法工作的很好,但它并不是最快的解决方案。更苛求速度的A*程序员使用叫做“binary heap”的方法,这也是我在代码中使用的方法。凭我的经验,这种方法在大多数场合会快2~3倍,并且在长路经上速度呈几何级数提升(10倍以上速度)。 如果你想了解更多关于binary heap的内容,查阅我的文章,Using Binary Heaps in A* Pathfinding。

2, 其他单位:如果你恰好看了我的例子代码,你会发现它完全忽略了其他单位。我的寻路者事实上可以相互穿越。取决于具体的游戏,这也许可以,也许不行。如果你 打算考虑其他单位,希望他们能互相绕过,我建议在寻路算法中忽略其他单位,写一些新的代码作碰撞检测。当碰撞发生,你可以生成一条新路径或者使用一些标准 的移动规则(比如总是向右,等等)直到路上没有了障碍,然后再生成新路径。为什么在最初的路径计算中不考虑其他单位呢?那是因为其他单位会移动,当你到达 他们原来的位置的时候,他们可能已经离开了。这有可能会导致奇怪的结果,一个单位突然转向,躲避一个已经不在那里的单位,并且会撞到计算完路径后,冲进它 的路径中的单位。

然而,在寻路算法中忽略其他对象,意味着你必须编写单独的碰撞检测代码。这因游戏而异,所以我把这个决定权留给你。参考文献列表中,Bryan Stout的文章值得研究,里面有一些可能的解决方案(像鲁棒追踪,等等)。

3, 一些速度方面的提示:当你开发你自己的A*程序,或者改写我的,你会发现寻路占据了大量的CPU时间,尤其是在大地图上有大量对象在寻路的时候。如果你阅 读过网上的其他材料,你会明白,即使是开发了星际争霸或帝国时代的专家,这也无可奈何。如果你觉得寻路太过缓慢,这里有一些建议也许有效:

* 使用更小的地图或者更少的寻路者。
* 不要同时给多个对象寻路。取而代之的是把他们加入一个队列,把寻路过程分散在几个游戏周期中。如果你的游戏以40周期每秒的速度运行,没人能察觉。但是他们会发觉游戏速度突然变慢,当大量寻路者计算自己路径的时候。
* 尽量使用更大的地图网格。这降低了寻路中搜索的总网格数。如果你有志气,你可以设计两个或者更多寻路系统以便使用在不同场合,取决于路径的长度。这也正是 专业人士的做法,用大的区域计算长的路径,然后在接近目标的时候切换到使用小格子/区域的精细寻路。如果你对这个观点感兴趣,查阅我的文章Two- Tiered A* Pathfinding。
* 使用路径点系统计算长路径,或者预先计算好路径并加入到游戏中。
* 预处理你的地图,表明地图中哪些区域是不可到达的。我把这些区域称作“孤岛”。事实上,他们可以是岛屿或其他被墙壁包围等无法到达的任意区域。A*的下限 是,当你告诉它要寻找通往那些区域的路径时,它会搜索整个地图,直到所有可到达的方格/节点都被通过开启列表和关闭列表的计算。这会浪费大量的CPU时 间。可以通过预先确定这些区域(比如通过flood-fill或类似的方法)来避免这种情况的发生,用某些种类的数组记录这些信息,在开始寻路前检查它。 在我Blitz版本的代码中,我建立了一个地图预处理器来作这个工作。它也标明了寻路算法可以忽略的死端,这进一步提高了寻路速度。

4, 不同的地形损耗:在这个教程和我附带的程序中,地形只有两种-可通过的和不可通过的。但是你可能会需要一些可通过的地形,但是移动耗费更高-沼泽,小山, 地牢的楼梯,等等。这些都是可通过但是比平坦的开阔地移动耗费更高的地形。类似的,道路应该比自然地形移动耗费更低。

这 个问题很容易解决,只要在计算任何地形的G值的时候增加地形损耗就可以了。简单的给它增加一些额外的损耗就可以了。由于A*算法已经按照寻找最低耗费的路 径来设计,所以很容易处理这种情况。在我提供的这个简单的例子里,地形只有可通过和不可通过两种,A*会找到最短,最直接的路径。但是在地形耗费不同的场 合,耗费最低的路径也许会包含很长的移动距离-就像沿着路绕过沼泽而不是直接穿过它。

一 种需额外考虑的情况是被专家称之为“influence mapping”的东西(暂译为影响映射图)。就像上面描述的不同地形耗费一样,你可以创建一格额外的分数系统,并把它应用到寻路的AI中。假设你有一张 有大批寻路者的地图,他们都要通过某个山区。每次电脑生成一条通过那个关口的路径,它就会变得更拥挤。如果你愿意,你可以创建一个影响映射图对有大量屠杀 事件的格子施以不利影响。这会让计算机更倾向安全些的路径,并且帮助它避免总是仅仅因为路径短(但可能更危险)而持续把队伍和寻路者送到某一特定路径。

5,处理未知区域:你是否玩过这样的PC游戏,电脑总是知道哪条路是正确的,即使它还没有侦察过地图?对于游戏,寻路太好会显得不真实。幸运的是,这是一格可以轻易解决的问题。

答 案就是为每个不同的玩家和电脑(每个玩家,而不是每个单位--那样的话会耗费大量的内存)创建一个独立的“knownWalkability”数组,每个 数组包含玩家已经探索过的区域,以及被当作可通过区域的其他区域,直到被证实。用这种方法,单位会在路的死端徘徊并且导致错误的选择直到他们在周围找到 路。一旦地图被探索了,寻路就像往常那样进行。

6,平滑路径:尽管A*提供了最短,最低代价的路径,它无法自动提供看起来平滑的路径。看一下我们的例子最终形成的路径(在图7)。最初的一步是起始格的右下方,如果这一步是直接往下的话,路径不是会更平滑一些吗?

有 几种方法来解决这个问题。当计算路径的时候可以对改变方向的格子施加不利影响,对G值增加额外的数值。也可以换种方法,你可以在路径计算完之后沿着它跑一 遍,找那些用相邻格替换会让路径看起来更平滑的地方。想知道完整的结果,查看Toward More Realistic Pathfinding,一篇(免费,但是需要注册)Marco Pinter发表在Gamasutra.com的文章

7, 非方形搜索区域:在我们的例子里,我们使用简单的2D方形图。你可以不使用这种方式。你可以使用不规则形状的区域。想想冒险棋的游戏,和游戏中那些国家。 你可以设计一个像那样的寻路关卡。为此,你可能需要建立一个国家相邻关系的表格,和从一个国家移动到另一个的G值。你也需要估算H值的方法。其他的事情就 和例子中完全一样了。当你需要向开启列表中添加新元素的时候,不需使用相邻的格子,取而代之的是从表格中寻找相邻的国家。

类 似的,你可以为一张确定的地形图创建路径点系统,路径点一般是路上,或者地牢通道的转折点。作为游戏设计者,你可以预设这些路径点。两个路径点被认为是相 邻的如果他们之间的直线上没有障碍的话。在冒险棋的例子里,你可以保存这些相邻信息在某个表格里,当需要在开启列表中添加元素的时候使用它。然后你就可以 记录关联的G值(可能使用两点间的直线距离),H值(可以使用到目标点的直线距离),其他都按原先的做就可以了。

另一个在非方形区域搜索RPG地图的例子,查看我的文章Two-Tiered A* Pathfinding。

进一步的阅读

好,现在你对一些进一步的观点有了初步认识。这时,我建议你研究我的源代码。包里面包含两个版本,一个是用C++写的,另一个用Blitz Basic。顺便说一句,两个版本都注释详尽,容易阅读,这里是链接。

* 例子代码:A* Pathfinder (2D) Version 1.71

如 果你既不用C++也不用Blitz Basic,在C++版本里有两个小的可执行文件。Blitz Basic可以在从Blitz Basic网站免费下载的litz Basic 3D(不是Blitz Plus)演示版上运行。Ben O’Neill提供一个联机演示可以在这里找到。

你也该看看以下的网页。读了这篇教程后,他们应该变得容易理解多了。

* Amit的 A* 页面:这是由Amit Patel制作,被广泛引用的页面,如果你没有事先读这篇文章,可能会有点难以理解。值得一看。尤其要看Amit关于这个问题的自己的看法。
* Smart Moves:智能寻路:Bryan Stout发表在Gamasutra.com的这篇文章需要注册才能阅读。注册是免费的而且比起这篇文章和网站的其他资源,是非常物有所值的。Bryan 用Delphi写的程序帮助我学习A*,也是我的A*代码的灵感之源。它还描述了A*的几种变化。
* 地形分析:这是一格高阶,但是有趣的话题,Dave Pottinge撰写,Ensemble Studios的专家。这家伙参与了帝国时代和君王时代的开发。别指望看懂这里所有的东西,但是这是篇有趣的文章也许会让你产生自己的想法。它包含一些对 mip-mapping,influence mapping以及其他一些高级AI/寻路观点。对”flood filling”的讨论使我有了我自己的“死端”和“孤岛”的代码的灵感,这些包含在我Blitz版本的代码中。

其他一些值得一看的网站

* aiGuru: Pathfinding
* Game AI Resource: Pathfinding
* GameDev.net: Pathfinding

好了,这就是全部。如果你刚好写一个运用这些观点的程序,我想见识见识。你可以这样联系我:
现在,好运!


我翻译的文章,很长:

[转载]推荐25个提高网站可用性和转化率的工具

mikel阅读(2002)

[转载]推荐25个提高网站可用性和转化率的工具 – 梦想天空 – 博客园.

网站建设的核心关键是“如何发现潜在客户并有效的转化为合作客户”,这里提到有效的转化也就是说找到的客户不一定会选用我们的产品或者服务,成 为了我们的客户的转化才是有效的转化,那么在竞争如此激烈的网络时代,我们该怎么去挖掘潜在客户并提高我们网站的转化率呢?关键之一就是提高网站的可用 性。

对于网站来说,可用性是指用户能否有效地找到所需的信息或完成他的任务,效率如何以及是否让人有愉快满意的感受。如果网站可用性较差,会浪费用 户的时间,大大降低网站的回头访问率,这对网站的生存是一个至关重要的问题。因此,为网站开发人员提供可用性改善建议和改进工具是十分必要的。

usability testing 25 Tools to Improve Your Websites Usability

如何知道访客是否喜欢你的网站?借助可用性检测工具追踪用户行为可以回答这个问题。跟踪到的数据可以给你提供精确的信息,让你知道用户喜欢你网 站的什么地方,哪些区域是最突出,哪些地方是用户最容易忽略的。不要小看这些收集到的数据,因为这些数据可以帮助你有效的提高网站的点击率和转化率。

改善您的网站不要挑时间,当你发现你的网站吸引不了多少访客的时候,就应该立即行动了。 这里收集了很多非常好的工具,通过不同的方法追踪用户的行为并提供给你改进网站可用性的建议。它们中有些可能不是免费的,但一个好的回报是值得去付出的。 为大家猎取所需的工具是一件开心又很有意义的事情!

Userfly

Userfly可以提供免费的网页访客动作记录服务。只需要在网页中添加一段简单的JavaScript代码,就可以记录访客从打开该网页到关闭整 个过程中的动作。Userfly能够记录的内容包括鼠标的移动、点击以及键盘输入等动作。对于网站拥有者来说,Userfly可以很方便的对用户行为进行 检测和分析,通过A/B Testing等方法为网站UI/UE提供非常有价值的信息。

userfly 25 Tools to Improve Your Websites Usability

Attention Wizard

Attention Wizard是一个视觉工具,它可以帮助您提高转换率,轻松地识别目标网页存在的问题。 Attention Wizard用高级人工智能算法来模拟人类视线的轨迹和聚焦点。Attention Wizard能够立刻对你提交的网页样本进行处理并生成“眼球轨迹热力图”。这个“热力图”可以预测你网站的访问者在最初的几秒内是如何浏览的。 生成的结果中,有75%都是通过眼睛跟踪和鼠标跟踪来完成的。Attention Wizard有简易版、高级版和终极版三个版本。

attentionwizard 25 Tools to Improve Your Websites Usability

ClickDensity

一个完整的可用性工具包,通过从访客那里获取的实时数据来改善网站内容结构,优化链接和广告的投放位置,分析和提高网站的粘性。 它将告诉你访客究竟在何处点击。

注册之后,把网站提供的一段JavaScript代码加入到你的网站中即可,当用户访问网站的时候,一个小的附加文件会自动下载用于传输用户的点击事件。该产品提供免费试用版,可付费升级到不同的高级版本。

clickdensity 25 Tools to Improve Your Websites Usability

4Q Online Survey

用户是否对网站真正的满意? 第四季的网上调查,一个免费的解决方案,可以告诉你关于你网站的访问者的详细信息,例如用户为什么访问你的网站,是否以正确的操作方式完成任务,如果不是,会是原因是什么阻碍了他们。 这项调查可以在用户中收集到大量的反馈。

4qsurvey 25 Tools to Improve Your Websites Usability

ChalkMark

Chalkmark可以让你快速创建一个项目,在访客中对界面原型进行调查以了解用户关注的焦点在哪,结果中可以查看每项任务的热图,并知道有多少用户跳过了任务。可以免费注册,也可以按月或者年付费到不限制任务数级别。

chalkmark 25 Tools to Improve Your Websites Usability

ClickHeat

是GPL许可下的开源软件,是一个很强大的JavaScript 库,它可以帮你统计一个页面上用户点击的热度分布图,支持中文显示。

clickheat 25 Tools to Improve Your Websites Usability

Clicktale

它是对你的网站访客浏览行为进行分析的一个工具,以类似视频的方式将访问者在你的网站上进行的操作全部记录下来,你可以在线观看也可以下载到电脑 上。利用ClickTale的访客行为视频记录,可以帮你更好的布局你的网页,给访问者带来更好的用户体验进而提升转化率。 它还提供了实时监控的性能分析,转化分析,链接分析,先进的过滤和市场分析。

clicktale 25 Tools to Improve Your Websites Usability

Clixpy

Clixpy几乎记录了访客的所有动作,如鼠标移动,点击,滚动和表单输入。 这些动作对于定位用户发现容易或者困难内容,那些东西最吸引他们以及为什么用户会离开网站有很大的帮助。这有助于优化目标网页,并帮助您把访问者转化为客户。

clixpy 25 Tools to Improve Your Websites Usability

Concept Feedback

Concept Feedback通过在线的专业团队快速的对网站设计,可用性和营销等方面提供反馈。对于公开的意见,团队中WEB方面的专业人员会给你一个整体的意见, 对于私密反馈,你可以邀请特定专家提供反馈,并组织出图形化的结果。 专业的反馈来自于在设计,可用性和营销领域的专家。

conceptfeedback 25 Tools to Improve Your Websites Usability

CrazyEgg

CrazyEgg能够监控一个页面的所有点击并能清楚的显示页面的点击热图,可以相当准确地监控所有页面的点击位置。

crazyegg 25 Tools to Improve Your Websites Usability

FeedbackArmy

你可以通过提交你网站的问题来获得一个快速的可用性测试并得到10项反馈。不过,不要支付15美元,如果想得到50项反馈则需要55美元。

feedbackarmy 25 Tools to Improve Your Websites Usability

Feng GUI

Feng GUI通过模拟人的视觉在开始的5秒内看到的东西来生成热点图,它为设计和开发人员提供了测试服务,通过分析观察角度、品牌效益和预测效果。

feng gui 25 Tools to Improve Your Websites Usability

Five Second Test

通过汇集用户在5秒内回答的问题集来改善目标网页以让访客关注关键内容。

fivesecondtest 25 Tools to Improve Your Websites Usability

Google Site Search

在Google Site Search中,强化了企业网站的索引范围;一个托管的搜索解决方案,可以呈现无广告的搜索结果,提高网站的转化率和销售额,降低成本。

sitesearch 25 Tools to Improve Your Websites Usability

Kampyle

Kampyle可以让你在你的网站出现一个在线发送反馈信息的按钮,点击此按钮可以弹出调查表,让访客可以反馈信息。Kampyle提供了反馈表单模板,你只需要简单修改即可,另外Kampyle还提供了丰富的反馈统计功能。

kampyle 25 Tools to Improve Your Websites Usability

Loop 11

使用Loop 11可以很容易的创建一个专业的可用性测试,收集反馈并及时的得到可用性的量化数据,然后立即看到结果。 支持超过40种语言。

loop11 25 Tools to Improve Your Websites Usability

OpenHallway

OpenHallway记录用户在远程或本地的可用性数据。用户的屏幕和语音会被记录并被上传到您的帐户。

openhallway 25 Tools to Improve Your Websites Usability

Silverback

一个用于设计人员和开发人员通过捕获屏幕活动来进行可用性测试的软件,可以记录测试者的表情和声音,有批量导出功能并可以标示出任何值得注意的时刻。

silverbackapp 25 Tools to Improve Your Websites Usability

Site Tuners

它通过指导你了解在目标网页中存在的转化率问题并回答您的具体的转换相关的问题。通过优化目标网页快速而简便的获得最好的转换率。

sitetuners 25 Tools to Improve Your Websites Usability

TechSmith

TechSmith是世界领先的为个人和专业领域提供屏幕捕获和录制软件的供应商。人们普遍使用TechSmith产品来从他们的计算机屏幕上捕获 内容。Morae是用于了解你的客户的一个很好的软件。使用Morae一个好处是,它会自动计算并给出效果和满意度,而不只是分析和报告。

morae 25 Tools to Improve Your Websites Usability

Usabilla

Usabilla帮助网站所有者更深入的了解他们的客户,不仅仅是数据。收集有关网页,实物模型,线框或任何图像的视觉反馈。

usabilla 25 Tools to Improve Your Websites Usability

User Testing

UserTesting观察用户在自己的环境中的活动,倾听他们的想法,然后形成结论。

usertesting 25 Tools to Improve Your Websites Usability

Webnographer

是一个远程的可用性测试工具,测试网站、Web应用程序或者原型在自然环境中可操作行的指标,通过调查问卷捕获用户的思想。

webnographer 25 Tools to Improve Your Websites Usability

Webtrends

优化重点在于发现市场潜力和精准的投递内容。世界上数以千计的网站智能企业,包括半数以上的财富和全球500强企业,正依靠WebTrends提高 他们的网站转换率、优化他们的市场营销效果,以获得更高的投资回报。作为全球网站分析行业的领导者,WebTrends凭借其多次获得业界大奖、全面地咨 询服务和无与伦比的专业经验,而成为Web分析行业最权威的衡量标准。通过其提供的按需服务和各种软件产品,WebTrends帮助用户获得精确的、可被 执行的Web分析数据。

webtrends 25 Tools to Improve Your Websites Usability

Website Optimiser

网站优化工具是Google免费的网站测试和优化工具。使用该工具,无需投入任何费用就可以提高现有网站的价值和点击量。通过使用网站优化工具对网 站内容和设计进行测试和优化,不管您是营销新手还是专家,都可以轻松快捷地增加收入和投资回报率。可以进行A/B实验和多版本实验两种实验。

websiteoptimize 25 Tools to Improve Your Websites Usability

这些都是非常有用的工具,如果你发现了更好的,记得与我们分享:)

编译来源:25个提高网站可用性和转化率的工具

原文来自:25 Tools To Improve Your Website’s Usability

[转载]ASP.NET MVC2.0本地化(另类解决方案)

mikel阅读(1079)

[转载]MVC2.0本地化(另类解决方案) – RyanDing – 博客园.

前不久看见一篇文章:在ASP.NET中使用Response.Filter 过滤网站敏感字符的解决方案。于是我借题发挥用Response.Filter 为MVC2.0 进行多国语言本地化。如果存在不足的地方,希望您指出。

本文主要给出具体思路,希望能给读者带来一定的启发:日常开发中不是所有的方案要中规中矩用常用方法解决问题。比如本文的本地化就不用resource文件来处理。

具体步骤:

一、建立自定义的LocalizationHandler类


LocalizationHandler 继承System.IO.Stream类 ,LocalizationHandler实例化后赋值给Response.Filter。这里主要通过Response.Filter来本地化MVC2.0程序。具体的Response.Filter 用法请参看MSDN.代码如下:

001 public class LocalizationHandler : Stream
002 {
003
004 private Stream responseStream;
005
006
007 public LocalizationHandler(Stream inputStream)
008 {
009 responseStream = inputStream;
010 }
011
012 public override bool CanRead
013 {
014 get { return true; }
015 }
016
017 public override bool CanSeek
018 {
019 get { return true; }
020 }
021
022 public override bool CanWrite
023 {
024 get { return true; }
025 }
026
027 public override void Flush()
028 {
029 responseStream.Flush();
030 }
031
032 public override long Length
033 {
034 get { return 0; }
035 }
036
037 long postion;
038 public override long Position
039 {
040 get
041 {
042 return postion;
043 }
044 set
045 {
046 postion = value;
047 }
048 }
049
050 public override int Read(byte[] buffer, int offset, int count)
051 {
052 return responseStream.Read(buffer, offset, count);
053 }
054
055 public override long Seek(long offset, SeekOrigin origin)
056 {
057 return responseStream.Seek(offset, origin);
058 }
059
060 public override void SetLength(long value)
061 {
062 responseStream.SetLength(value);
063 }
064
065 public override void Write(byte[] buffer, int offset, int count)
066 {
067 string sBuffer = System.Text.UTF8Encoding.UTF8.GetString(buffer, offset, count);
068 string pattern = @"(<|<)=(.*?)(>|>)";//正则替换类似页面格式为这样的字符串如:<=OtherContent>
069 sBuffer = Regex.Replace(sBuffer, pattern, delegate(Match c)
070 {
071 return ReadLocalizationResource().FirstOrDefault(d => d.Key == c.Groups[2].Value).Value;
072 });
073
074 ReadLocalizationResource();
075 byte[] data = System.Text.UTF8Encoding.UTF8.GetBytes(sBuffer);
076 responseStream.Write(data, 0, data.Length);
077 }
078
079 ObjectCache cache = MemoryCache.Default;
080 private Dictionary<string, string> ReadLocalizationResource()
081 {
082 string _XMLPath = "";
083
084 Dictionary<string, string> cacheData = null;
085 if (cacheData != null)
086 {
087 return cacheData;
088 }
089 Dictionary<string, string> cachedData = new Dictionary<string, string>();
090
091 string serverPath = System.Web.HttpContext.Current.Server.MapPath("~");
092 _XMLPath = Path.Combine(serverPath, "LocalizationResource.xml");
093
094 //建立缓存(使用.net4.0最新缓存机制:System.Runtime.Caching;)
095 if (cache["myCache"] == null)
096 {
097 CacheItemPolicy policy = new CacheItemPolicy();
098 policy.SlidingExpiration = TimeSpan.FromMinutes(60);
099 policy.ChangeMonitors.Add(new HostFileChangeMonitor(new List<string> { _XMLPath }));
100
101 var items = XElement.Load(_XMLPath).Elements("Module").Elements("item");
102 foreach (var item in items)
103 {
104 string key = item.Attribute("name").Value;
105 string value = item.Value;
106 cachedData.Add(key, value);
107 }
108 cache.Set("myCache", cachedData, policy);
109
110 return cachedData;
111
112 }
113
114 return (Dictionary<string, string>)cache["myCache"];
115 }
116 }

代码中的65行开始,是本地化核心代码,在这里我们用正则匹配文本。用.NET4.0 System.Runtime.Caching;(尝鲜)缓存机制提高程序执行效率。

二、修改global.asax文件


在global.asax中加入以下代码:

1 protected void Application_BeginRequest(Object sender, EventArgs e)
2 {
3 Response.Filter = new LocalizationHandler(Response.Filter);
4 }

三、建立自定义的XML本地化资源文件


截图如下:

OK,一切准备就绪,我们在不用Response.Filter 过滤的情况下,运行截图如下:

使用上文中Response.Filter过滤后:

结果将第三点中的XML作为传统的资源文件后本地化了MVC View 页面。

四、小结


本文用另外一套方案解决了MVC2.0程序的本地化问题,也适用于ASP.NET webform。同时本文还存在很多不足的地方,比如后台异步的JSON格式本地化、用户自定义本地化语言等将会在MVC2.0本地化(另类解决方 案)<下> 文中得到完善。

[转载]System.Web.Routing入门及进阶

mikel阅读(1166)

[转载]System.Web.Routing入门及进阶 上篇 – 重典的博客 – 博客园.

System.Web.Routing已经作为一个程序集包含在.net3.5sp1中发布了。虽然我们并没有在3.5sp1中发现ASP.NET Mvc的踪迹,但是亦以感觉到它离我们不远了。

System.Web.Routing用于在ASP.NETWeb应用程序中进行URLRouting

所谓UrlRouting就是将一个地址映射为另一个地址,比如我访问/chsword/2008/08/27.html其实是访问了 /chsword/article.aspx?y=2008&m=08&d=27这个地址,URLRouting使我们的URL看起来非 常漂亮。

本文将分2部分介绍UrlRouting的使用分别为入门篇与进阶篇。

文章的前提:

1. 安装Visual Studio 2008 sp1或其它IDE的等同版本。

2. 建立一个ASP.NET Web ApplicationAsp.net Web应用程序)

3. 引用System.Web.Routing程序集。

UrlRouting的实现原理

如果你不是追求理论的人,仅仅是务实主义,可以直接从准备工作读起。

为了方便大家理解我绘制了一个UML图,其中通过RouteTableRoutes这个属性可以获取一个RouteCollectionSingleton模式,虽然在其中并没有判断值不存在时才初始化的Singleton的标志性行为,但是它是在Application_Start事件中进行初始化的,并且直到应用程序进程终结,所以是Singleton模式的。

而通过以下方式将Route添加到RouteTable.Routes

RouteTable.Routes.Add(new Route());

以上代码仅为表示其流程,这个代码是不能正确执行的,因为Route没有提供无参构造函数。

Route初始化时则是利用RouteValueDictionary来加入默认值及规则到Route

另外Route还有一个IRouteHandler的实现对象,IRouteHandler的实现对象提供了一个GetHttpHandler方法来获取用于处理URLIHttpHandler

这么说还是停留在抽象层次的,下面我们以一些简单例子来带你驭起UrlRouting

准备工作

由于须要一个处理UrlIHttpHandler所以我们先定义一个实现了IHttpHandler接口的类。

就叫它MyPage,由于我们要与IRouteHandler交互,所以除了实现IHttpHandler的方法之外还要声明一个RequestContext类型的属性

public class MyPage:IHttpHandler {
public RequestContext RequestContext { get; private set; }
public MyPage(RequestContext context)
{
this.RequestContext = context;
}
#region IHttpHandler 成员
public virtual void ProcessRequest(HttpContext context){}
public bool IsReusable {
get { return false; }
}
#endregion
}

这样我们就拥有了一个自己的IHttpHandler

下面我们实现一个IRouteHandler

public class MyRouteHandler : IRouteHandler {
#region IRouteHandler 成员
public IHttpHandler GetHttpHandler(RequestContext requestContext) {
return new MyPage(requestContext);
}
#endregion
}

这里实现了IRouteHandlerGetHttpHandler方法,使之返回刚才我们实现的MyPage

这样我们前期的2个工作就做完了,我们可以来实现UrlRouting了。

实现第一个UrlRouting

其实UrlRouting在定义完上两个规则后就很简单了。

Golbal.asax(没有可以新建一个)的Application_Start事件中写如下代码

protected void Application_Start(object sender, EventArgs e) {
RegisterRoutes(RouteTable.Routes);
}
public static void RegisterRoutes(RouteCollection routes) {
routes.Add(
new Route({page}.aspx,new MyRouteHandler()));
}

这样我们就定义了第一个UrlRouting规则就是对xxxx.aspx这类的Url进行Routing

但是我们仅仅是定义了处理了什么样的Url,却没定义如何处理。

我们应该在刚刚定义的MyPageProcessRequest方法中定义如何处理。

我们将ProcessRequest方法实现如下:

public virtual void ProcessRequest(HttpContext context){
context.Server.Execute(        RequestContext.RouteData.Values[
page].ToString().Replace(_,/)+.aspx
);
}

很显然这里的RequestContext.RouteData.Values[“page”]就是取到刚才的规则{page}.aspx中的page的值即,如果我访问index.aspxRequestContext.RouteData.Values[“page”]就是index

我这里的定义是将”_”替换为”/”所以就是将list_index.aspx这样的URL转到list/index.aspx这样的网页上。

我们建立一些测试用的网页如下图所示:

在这些网页里随意写些可以分辨网页的文字。

则访问list_chsword.aspx时自动Routing到了list/chsword.aspx上了

总结一下UrlRouting与以下有关:

1. Application_Start中定义的规则

2. 自己实现的IHttpHandler

这样您对于UrlRouting就算是入门了,下一篇我们将来讲一些进阶设置。

预计内容:

  • 规则的默认值
  • 正则规则设置
  • 使用技巧

上面介绍的是最简单的一种定义方式。当然我们可以建立更复杂的规则。其中就包括设定规则的默认值以及设定规则的正则表达式。

UrlRouting高级应用

预计效果:

当我访问/a/b.aspx时就会转到Default.aspx?category=a&action=b在页面上显示
category:a
action:b

亦如果我访问/chsword/xxxx.aspx就会转到Default.aspx?category=chsword&action=xxxx就会显示
category:chsword
action:xxxx

如果访问/chsword/就会转到 Default.aspx?category=chsword&action=index就会显示

category:chsword

action:index

首先我建立一个Route

routes.Add(
Default,
new Route({category}/{action}.aspx,
new RouteValueDictionary(
new
{
file
= Default,
category
= home,
action
= index
}),
new MyRouteHandler()
)
);

当然IHttpHandler的处理方式也要有所改变

为了方便查看我使用了下方法:

context.Server.Execute(string.Format(/{0}.aspx?category={1}&action={2},
RequestContext.RouteData.Values.ContainsKey(
file)
? RequestContext.RouteData.Values[file].ToString()
:
default,
RequestContext.RouteData.Values.ContainsKey(
category)
? RequestContext.RouteData.Values[category].ToString()
:
“”,
RequestContext.RouteData.Values.ContainsKey(
action)
? RequestContext.RouteData.Values[action].ToString()
:
“”)
);

即/a/b.aspx是映射到Default.aspx?category=a&action=b

在Default.aspx中写如下代码:

category:<%=Request.Params[category] %><br />
action:
<%=Request.Params[action] %>

以显示传入的参数。

如果在IIS中设置Index.aspx时就算输入/a/也会访问到/a/index.aspx,即默认的会按RouteValueDictionary中设置的值自动补全

UrlRouting使用正则表达式规则

UrlRouting在定义的时候也可以按正则的规则来进行定义。

routes.Add(
zz,
new Route({category}/{action}.chs,
new RouteValueDictionary(
new {
file
= Default,
category
= home,
action
= 1
}),
new RouteValueDictionary(
new {
action
= [\\d]+
}),
new MyRouteHandler()
)
);

以上代码规定了action只能是数字则访问/a/1.chs可以正常访问。

而访问/a/b.chs则会显示未找到资源。

当然这是里可以使用更高级的正则表达式。

UrlRouting的技巧

排除UrlRouting的方法:

System.Web.Routing默认提供了一个IRouteHandlerStopRoutingHandler Class,经过它处理的URL不会被做任何处理

通常使用方法如下:

routes.Add(new Route(“{resource}.axd/{*pathInfo}”, new StopRoutingHandler()));

RouteHandler工厂:

其实IRouteHandler可以实现一个RouteHandler的简单工厂。

public class RouteHandlerFactory : IRouteHandler
{
string Name { get; set; }
public RouteHandlerFactory(string name){this.Name = name;}
#region IRouteHandler 成员
public IHttpHandler GetHttpHandler(RequestContext requestContext) {
if (this.Name == mypage)
return new MyPage(requestContext);
if(this.Name=mypage1)
return new MyPage1(requestContext);
}
#endregion
}

规定HTTP verbs,这里要使用System.Web.Routing中的HttpMethodConstraint

void Application_Start(object sender, EventArgs e) {
RegisterRoutes(RouteTable.Routes);
}
public static void RegisterRoutes(RouteCollection routes){
string[] allowedMethods = { GET, POST };
HttpMethodConstraint methodConstraints
= new HttpMethodConstraint(allowedMethods);
Route reportRoute
= new Route({locale}/{year}, new ReportRouteHandler());
reportRoute.Constraints
= new RouteValueDictionary { { httpMethod, methodConstraints } };
routes.Add(reportRoute);
}

Demo程序代码下载:

http://files.cnblogs.com/chsword/WebApplication3.rar

[原创]VS2008源代码与原始版本不同解决办法

mikel阅读(1676)

最近调试程序设置断点后遇到“源代码与原始版本不同解决办法”的提示,在网上也找了很多解决办法,一一尝试,问题解决

我无法设定断点的原因是因为从d:拷贝项目文件夹到了F:盘,我是通过断点窗口的提示发现的问题,

在VS2008中”调试”–“窗口”–“断点” 显示出断点窗口,然后点击左上角的”新建” 中的”在函数处中断”,然后再弹出窗口中输入函数名,当你运行后,会提示“源代码与原始版本不同解决办法”的窗口,让你是否按源文件执行,你选择否,你就会看到一个文件浏览窗口,上方显示原文件位置,这里是原文件的位置路径,我发现引用的还是d:盘的路径,现在已经拷贝到F:盘,于是我想到把所有解决方案中的项目引用dll都重新删除再引用新的路径的对应dll文件,然后清理缓存文件,重新编译解决方案,问题解决

特此总结一下解决方法:

1.删除无法设定断点项目文件夹下的obj目录,删除C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files\对应解决方案的文件夹

2.删除所有原始引用的dll,然后再引用现在新路径的dll

3.重新生成解决方案

[转载]ASP.NET MVC & EF 构建智能查询 二、模型的设计与ModelBinder

mikel阅读(921)

[转载]ASP.NET MVC & EF 构建智能查询 二、模型的设计与ModelBinder – 重典的博客 – 博客园.

在第一篇中,我讲解了我们要做智能查询的原因,以及基本的解决方案设计。从这篇开始我们开始讲解它的实现过程。

其实在写这一系列文章之初,我其实是想由底至上去讲解,但是我又整理了一遍代码才发现,其实如果不了解最表面的东西,也是不太好深入的。

所以我们的第二篇文章就来讲一下我们这个智能查询框架中最浅,但也是使用最频繁的部分,也就是Model。

首先我们的Entity  或者说数据库的结构如下

image

另外如下面代码,我们有一个用于传递name=value对,及查询谓词的model

   1: public ActionResult Index(QueryModel model)
   2: {
   3:     using(var db=new DbEntities())
   4:     {
   5:         var list = db.Users.Where(model).ToList();
   6:         return View(list);
   7:     }
   8: }

我命名之为QueryModel。它由Action的参数传入,再传入EF的Where扩展方法,它是构建Lambda表达式的原型,上面是我们的一个通用的查询Action的代码。

而QueryModel的代码为

image

QueryModel的唯一一个属性Items,是一个ConditionItem的集合,它里面存着所有的查询条件。

而ConditionItem里面的属性

  1. Field表示要查询的目标属性的名称,我们的设定它是支持子属性查询的,例如 “Profile.MyUser.Id”
  2. Method则是当前使用的谓词,它是QueryMethod的一个枚举值
  3. OrGroup是一个字符串,是一个OrGroup的多个表达式将会以Or操作符进行关联,然后再And
  4. Prefix是一个分类的前缀,我们假定一个Action可能处理多个查询条件组的时候为了分开这些查询条件而加的属性
  5. Value则是这个表达试的值

而我们在页面上类似

   1: <form action="" method="post">
   2: 姓名:<input id="Name" name="[Like]Name" type="text" value="" />
   3: Email:<input id="Email" name="[Equal]Email" type="text" value="" /><br />
   4: Id: <input id="Id" name="[Equal]Id" type="text" value="" />
   5: 生日: <input id="Birthday" name="[Equal]Birthday" type="text" value="" /><br />
   6: <input type="submit" value="查询" />
   7: </form>

这样的表单,我们提交到服务器端,我们要进行ASP.NET MVC 中IModelBinder的转换,要将querystring 或 postdata中的KeyValuePair转换为我们的ConditionItem。

当然,我们除了谓词Method之外,还有Prefix以及OrGroup要从客户端提交过来所以我们就要制定一个规则

我们约定

  1. 中括号[]中的为查询谓词
  2. 小括号()中的为前缀Prefix
  3. 大括号{}中的为OrGroup

我们可以通过以下IModelBinder的实现将Request.QueryString或Request.Form中的值转换到QueryModel中:

   1: public class SearchModelBinder : IModelBinder
   2:    {
   3:        public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
   4:        {
   5:            var model = (QueryModel)(bindingContext.Model ?? new QueryModel());
   6:            var dict = controllerContext.HttpContext.Request.Params;
   7:            var keys = dict.AllKeys.Where(c => c.StartsWith("["));//我们认为只有[开头的为需要处理的
   8:            if (keys.Count() != 0)
   9:            {
  10:                foreach (var key in keys)
  11:                {
  12:                    if (!key.StartsWith("[")) continue;
  13:                    var val = dict[key];
  14:                    //处理无值的情况
  15:                    if (string.IsNullOrEmpty(val)) continue;
  16:                    AddSearchItem(model, key, val);
  17:                }
  18:            }
  19:            return model;
  20:        }
  21:
  22:        /// <summary>
  23:        /// 将一组key=value添加入QueryModel.Items
  24:        /// </summary>
  25:        /// <param name="model">QueryModel</param>
  26:        /// <param name="key">当前项的HtmlName</param>
  27:        /// <param name="val">当前项的值</param>
  28:        public static void AddSearchItem(QueryModel model, string key, string val)
  29:        {
  30:            string field = "", prefix = "", orGroup = "", method = "";
  31:            var keywords = key.Split(']', ')', '}');
  32:            //将Html中的name分割为我们想要的几个部分
  33:            foreach (var keyword in keywords)
  34:            {
  35:                if (Char.IsLetterOrDigit(keyword[0])) field = keyword;
  36:                var last = keyword.Substring(1);
  37:                if (keyword[0] == '(') prefix = last;
  38:                if (keyword[0] == '[') method = last;
  39:                if (keyword[0] == '{') orGroup = last;
  40:            }
  41:            if (string.IsNullOrEmpty(method)) return;
  42:            if (!string.IsNullOrEmpty(field))
  43:            {
  44:                var item = new ConditionItem
  45:                               {
  46:                                   Field = field,
  47:                                   Value = val.Trim(),
  48:                                   Prefix = prefix,
  49:                                   OrGroup = orGroup,
  50:                                   Method = (QueryMethod) Enum.Parse(typeof (QueryMethod), method)
  51:                               };
  52:                model.Items.Add(item);
  53:            }
  54:        }
  55:    }

当然我们还要在Global.asax中添加

   1: ModelBinders.Binders.Add(typeof (QueryModel), new SearchModelBinder());

这样我们就可以在Action中获取到相应的查询条件了

image

我们可以看到,从表单提交过来的数据我们已经正确地存放在QueryModel中了。

[转载]ASP.NET MVC & EF 构建智能查询 一、智能查询的需求与设计

mikel阅读(835)

[转载]ASP.NET MVC & EF 构建智能查询 一、智能查询的需求与设计 – 重典的博客 – 博客园.

关于复用

在我们日常的开发过程中,代码的复用其实是很重要的一部分,ASP.NET MVC框架本身为我们提供了很多很好的复用机制,让我们能充分地利用它们来节省我们的Coding成本。

在简单的Coding中,我们可以通过构造方法来实现代码段的复用,在OOP编程中我们可以使用继承多态来进行类的复用,我们也可以使用设计模式来做类或对象间的代码设计的复用,随着程序的复杂我们就想构造出更佳的复用方式,可以向更高层次上抽象。

应用场景与目标

在信息管理系统中我们会开发大量的List页面,它们功能上通常是非常相似的,一般是包含一个查询条件组和一个列表。

例如下图所示

image

那我的目标呢,就是对这里面的查询功能进行封装,以达到“只要更改页面上的条件,就可以实现自动的查询逻辑”这样的功能,即:如果我们要加一个查询条件则不需要修改逻辑代码只修改页面即可。

其实对于一个有经验的开发人员呢,根据HTML页面的查询条件去自动生成查询结果并不是难事。最简单的是将查询提交表单遍历,然后连接成一个SQL进行查询。

但是使用SQL有很多问题

  1. 注入问题,因为不知道类型,所以很多地方都要拼写引号就算是使用Parameter,也无法处理类型转换的问题
  2. 无法处理Or和And共存的问题
  3. 无法处理其它查询条件如大于小于Like

那我们想一下我们使用EF的话是如何正常去编写一个查询的呢

我们通常是在逻辑中去拼SQL或是写如下的EF:

   1: var query = db.User.AsQueryable();
   2: if (string.IsNullOrEmpty(name))
   3:     query = query.Where(c => c.Name == name);
   4: if (id.HasValue)
   5:     query = query.Where(c => c.Id == id);
   6: if (string.IsNullOrEmpty(email))
   7:     query = query.Where(c => c.Email == email);
   8: return query.ToList();

那我们的目标其实就明确了,我们就是要让代码自动去完成上述这一过程,并且可以支持

  1. 识别类型(EF的话这个很重要)
  2. 仅查询有效条件
  3. 处理可空类型
  4. 处理类型转换(如DateTime与UnixTime的转换)
  5. 一些代码性逻辑(比如查询某些日期的时候,其实我们要的结果并不是c=>c.Time==time而是c=>c.Time>time && c.Time<time.AddDays(1))
  6. 支持Or等 混合查询
  7. 关于Like的相关处理
  8. 关于In操作的相关处理
  9. Join查询的相关处理

理论流程

看一看我们要做的其实还挺多的,但是我已经在心里搭出了一个这样的流程,使用ASP.NET MVC及EF特性来完成这些功能

image

那其实呢首先的问题就是我们从浏览器请求向服务器的时候,除了要查询的字段,还应该包含有 怎么查(就是操作符,比如= < > like in)、是否为Or、

而HTML中唯一可以直接传到服务器的就是表单元素的name所以我们可以对name来做一些手脚让它包含这些信息

比如[Equal]Id就表示让Id这个属性的值等于这个表单项的Value

[Equal]Id=1

我们就利用这个postdata或QueryString去构造出

c=>c.Id==1的Lambda表达式,并且支持多个条件

实际实现

而我是在Helper上加了一层扩展来实现这样的功能的,形如

   1: <form action="" method="post">
   2: 姓名:@Html.TextBox("Name").ForSearch(QueryMethod.Like)
   3: Email:@Html.TextBox("Email").ForSearch(QueryMethod.Equal)<br />
   4: Id: @Html.TextBox("Id").ForSearch(QueryMethod.Equal)
   5: 生日: @Html.TextBox("Birthday").ForSearch(QueryMethod.Equal)<br />
   6: <input type="submit" value="查询" />
   7: </form>

这里我们使用ForSearch这个扩展为Html的name添加了前置的谓词标记[Equal]

我们在服务器端只要统一处理

   1: public ActionResult Index(QueryModel model)
   2: {
   3:     using(var db=new DbEntities())
   4:     {
   5:         var list = db.Users.Where(model).ToList();
   6:         return View(list);
   7:     }
   8:
   9: }

我们是在这个Where的扩展方法里重写了将QueryModel转换成lambda表达式的工作,当然在这之前我们还利用了自已实现的ModelBinder去将 postdata/querystring进行了分析前初始化了QueryModel

在未来的几篇中我们将会详述这些过程的实现

[转载]c#进阶-方法中的ref和out

mikel阅读(1142)

[转载]c#进阶-方法中的ref和out – 撞破南墙 – 博客园.

在一个方法中 你必须知道每个参数是引用类型还是值类型否则你将发现你无法控制程序。

这些细节并不是每个.NET程序员都需要知道,但是却是进阶必备的,差距也在于此。

C#中默认是使用值类型,你可以使用REF 和OUT 来使之变成引用类型。

在CLR中只有1个bit去区分它们是ref还是out。如果使用的是out 那么

1你不能够在赋值前使用它,因为他还未赋值。

2在方法返回的时候你必须为他赋值

如果是ref  你可以直接读写他。

REF 和 OUT 都使得方法中的参数把值带回来,不同的在于编译器把对他们进行了检查,如下面的情况。

public sealed class Program {
public static void Main() {
Int32 x;
// x is not initialized.
// The following line fails to compile, producing
// error CS0165: Use of unassigned local variable ‘x’.
AddVal(ref x);
Console.WriteLine(x);
}
private static void AddVal(ref Int32 v) {
v
+= 10; // This method can use the initialized value in v.
}

C#中为什么一定要申明是ref 还是 out ,如果不申明编译一样没有问题,在C++中就没有ref 和out这回事。

应该是C#的设计者认为开发者应该应该清楚的知道自己传递的参数的状态作用。

在c#中,允许以下的重载。

public sealed class Point {
static void Add(Point p) { … }
static void Add(ref Point p) { … }
}

但是不允许同时使用 REF和OUT的重载。因为元数据表示两个方法的签名是一样的。

image

让两个引用类型的变量交换

public static void Swap(ref Object a, ref Object b) {
Object t
= b;
b
= a;
a
= t;
}
//To swap references to two String objects, you’d probably
//think that you //could write code     like this:

public static void SomeMethod() {
String s1
= Hello;
String s2
= World;
// Variables that are passed by reference
// must match what the method expects.
Object o1 = s1, o2 = s2;
Swap(
ref o1, ref o2);
// Now cast the objects back to strings.
s1 = (String) o1;
s2
= (String) o2;
//
Console.WriteLine(s1); // Displays “World”
Console.WriteLine(s2); // Displays “Hello”
}

如果Swap(ref s1, ref s2); 似乎没有问题,但是

image

对于ref 他是执行了类型检查的。

[转载]Ruby on Rails 体验之旅(一)--安装

mikel阅读(1031)

[转载]Ruby on Rails 体验之旅(一)–安装 – PurpleCow – 博客园.

最新心血来潮的想学一门新的东西,听说用ROR的开发效率会很高,所以就选择了ruby on rails,安装的时候真是不顺心,这里把自己的安装心得记下来:

1) 安装ruby

    下载安装包:http://rubyforge.org/frs/?group_id=167,我下载的是rubyinstaller-1.8.7-p302.exe

    装完后,可以用ruby -v 测试是否安装成功,如图,要是出现了版本 就说明安装成功了

image

2)安装rubygems

Rubygems是ruby的包管理器工具,它使得ruby包的安装十分简单,只需要一条命令就可以从远程服务器上下载相应的包,如果相应的应用包含其他 扩展,rubygems 也会提示你从远程安装所依赖的扩展。安装后 rubygems 会运行相应的程序生成 rdoc 帮助文档(类似于 javadoc )。目前已经成为 ruby 事实上的包管理器标准了。

下载地址:http://rubyforge.org/projects/rubygems/

我下载的是rubygems-1.3.7,解压后执行目录下的setup.rb,安装完成以后,用如下命令检查:

gem –v

image

3、安装rails

a)远程安装:gem install rails –remote

下面来自于网上的说明我没有采用这种方式安装,因为我家 的网速太慢了。

然后会自动输出:
Install required dependency activerecord?
Install required dependency actionpack?
Install required dependency actionmailer?
Install required dependency activeresource?
依次yes依赖文件,然后显示:
Successfully installed rails-2.1.0
Successfully installed activerecord-2.1.0
Successfully installed actionpack-2.1.0
Successfully installed actionmailer-2.1.0
Successfully installed activeresource-2.1.0
Installing ri documentation for activerecord-2.1.0…
Installing ri documentation for actionpack-2.1.0…
Installing ri documentation for actionmailer-2.1.0…
Installing ri documentation for activeresource-2.1.0…
Installing RDoc documentation for activerecord-2.1.0…
Installing RDoc documentation for actionpack-2.1.0…
Installing RDoc documentation for actionmailer-2.1.0…
Installing RDoc documentation for activeresource-2.1.0…
看看版本:rails -v
输出:Rails 2.1.0
搞定

b)本地手动安装:

需要下载

activesupport

http://files.rubyforge.vm.bytemark.co.uk/activesupport/activesupport-2.2.3.gem

activerecord

http://files.rubyforge.vm.bytemark.co.uk/activerecord/activerecord-2.2.3.gem

actionpack

http://files.rubyforge.vm.bytemark.co.uk/actionpack/actionpack-2.2.3.gem

actionmailer

http://rubyforge.org/frs/download.php/64424/actionmailer-2.2.3.gem

rake
http://files.rubyforge.vm.bytemark.co.uk/rake/rake-0.8.7.gem

activeresource

http://rubyforge.org/frs/download.php/64419/activeresource-2.2.3.gem

rails     http://rubyforge.org/frs/download.php/64426/rails-2.2.3.gem

下载完成之后将所有文件放在同一个文件夹下面依次安装:(eg:D:\ruby)

然后执行命令

(D: –> D:\>cd D:\ruby)

gem install  activesupport

gem install  activerecord

gem install  actionpack

gem install  actionmailer

gem install  rake

gem install  rails

成功安装之后检查命令

rails –v

image

证明安装成功

4、创建Web应用

在命令行下输入 rails HelloWorld,比如当前的路径是 E:\Ruby on Rails>rails HelloWorld,便会在E:\Ruby on Rails下创建一个HelloWorld文件夹。

image

如果需要指定路径可以rails C://test,具体可以看官方API:http://api.rubyonrails.org/

5、在当前目录(HelloWorld)下,运行 ruby script\server 启动服务,关于webrick服务器更多的信息可以访问 http://www.webrick.org/

image

6、浏览器中输入http://localhost:3000/,如果看到以下页面,说明OK

image

7、写一个HelloWorld的Web程序,ruby script\generate controller HelloWorld命令

image

然后找到hello_world_controller.rb

    1. class HelloWorldController < ApplicationController
    2. def index
    3. render:text=>”hello world”
    4. end
    5. end
  • 输入以下代码,保存

  • 如果未启动服务需要重启第五步操作。

在浏览器中打开http://localhost:3000/hello_world,就会看见结果,但我这里出现一个错误:

image

    在网上看到一个解决方法:

    这是由于没有装SQLite3数据库,http://files.cnblogs.com/geek007/sqlite3.rar 这里下载,

    然后解压到ruby的bin目录:D:\ruby\bin,命令行执行:gem install “D:\Program Files\Ruby187\bin\SQLite3-ruby-1.2.3-mswin32.gem”

image

安装成功后,按照第五步重启服务器,在浏览器中访问

http://localhost:3000/hello_World

image

Ok,大功告成~呵呵,开心~~~

PS: 手工安装非常麻烦,可以从 http://instantrails.rubyforge.org/ 下载InstantRails,全部搞定。

参考资源:

http://rubyforge.org

http://api.rubyonrails.org/