[转载]Xoohoo系列(一):概述

mikel阅读(1118)

[转载]Xoohoo系列(一):概述 – alby – 博客园.

作为一个开发者,我们免不了要做很多重复的事情。我们不只在重复别人造轮子,也在重复自我浪费时间——我们应该有一种罪恶感。庆幸的是很多人在帮助我们尽可能的减少这种重复,他们提出或总结了很多的宝贵的思想,开发了很多的有用的开发辅助工具。比如设计模式、类库、O/RM,代码生成器,以及更具体的CMS(Content Management System)等等。设计模式让我们在设计思想得到一定程度的解放,在设计方向上给予指引。这就像乘法口诀表,不用我们算个简单的3721还需要掰手指 头;类库如.Net的BCL(Base Class Library)、FCL(Framework Class Library)等,以官方发布的形式将一些常用的功能进行了封装。当然,还有公司的类库或自己的私房类库;O/RM让我们在领域模型和关系数据库的映射 上提高开发效率。据统计如果不使用O/RM,在映射方面需要花去总体开发时间的30%-40%;代码生成器则是将有规律的集中的重复的工作交给程序来处 理;CMS在应用的层次上减少了重复工作,比如WordPress,DotNetNuke等。

Xoohoo又试图解决哪些方面的问题呢?

Xoohoo基于ASP.NET MVC(以下简称MVC)3的框架,其前身是微软的开源项目Oxite。遗憾的是后者停止了开发目前还停留在MVC 1。庆幸的微软提供了另一个开源项目Orchard供我们选择,二十四画生对Orchard颇有研究。不管是Oxite还是Orchard,他们从一开始就试图给用户提供一个可用的产品(Blog,但不仅限于此),并提供了超强的扩展性。

Xoohoo的目的不是要给您一个现成的产品,而是一个轻量开发框架,旨在能为您减少那么一点点重复的工作。如果您开发一个新的基于MVC的项目,希望您能试试。

您 首先可以将之看作是一个类库,对MVC 3进行了扩充。它借鉴并使用了一些优秀博文或开源项目的代码。由于资料比较零散,不再列出,在此一并表示感谢;其次将之作为一个小框架,在此基础您可以开 发自己的模块(插件),并能进行无缝整合。如果愿意,您还可以将某些模块拿出来分享。

一、Xoohoo是一个ASP.NET MVC 3的扩展类库

将Xoohoo作为一个类库,我相信您总等找到您所需要的。

1、ActionFilter

Xoohoo内置了若干的AtionFilter,包括TimerActionFilter、CompressActionFilter、ErrorExceptionFilter以及各种的AuthorizationFilter。

TimerActionFilter能计算Action的执行时间:

image

CompressActionFilter能对输出进行压缩(gzip,deflate)。
ErrorExceptionFilter类似于MVC内置的HandleErrorAttribute,配合Xoohoo内置的日志模块,可以进行日志记录;配合Xoohoo的皮肤模块,错误提示View还可以换肤。
各种的AuthorizationFilter提供了非常灵活的授权(权限)认证,以后会有专门的篇幅加以介绍。

2、ActionInvoker

XoohooControllerActionInvoker重写了MVC内置的ControllerActionInvokerCreateActionResult等方法,使其支持AJAX请求,以及更灵活的Action返回值类型。

比如有Action:

   1: public ActionResult List()
   2: {
   3:         return View(new List<Article>());
   4: }

如果是AJAX请求,您需要在重新实现一个Action,或者在Action内部进行一次判断:

   1: public ActionResult List()
   2: {
   3:     if(Request.IsAjaxRequest())
   4:         return Json(new List<Article>());
   5:     else
   6:         return View(new List<Article>());
   7:
   8: }

如果使用XoohooControllerActionInvoker,只需要这样写:

   1: public List<Article> List()
   2: {
   3:     return new List<Article>();
   4: }

MVC内置的ControllerActionInvoker,如果Action返回的不是一个ActionResult类型的值,将会调用Convert.ToString方法生成其字符串形式,再使用字符串生成一个ContentResult
XoohooControllerActionInvoker也会根据Action返回值的类型进行判断。
如果是简单类型,如int,string等,则采用ControllerActionInvoker相同的方式,即调用Convert.ToString方法生成其字符串形式,再使用字符串生成一个ContentResult。
如果是复杂类型,则进行进一步判断是否是AJAX请求。如果是,将生成一个JsonResult并将Action返回值赋给 JsonResult的Data属性;否则将返回值赋给Controller的ViewData的Model属性,然后生成一个ViewResult。
怎么样,是不是很灵活很简洁?

3、ActionResult

Xoohoo扩充了一些ActionResult,如RssResult、XmlResult等,比较简单不再累述。

4、ControllerFactory

ControllerFactory是用于查找Controller类型并创建Controller实例的。一般我们不需要改变查找 Controller类型的方式。在MVC3中,至少有两种途径可以改变创建Controller实例方式,一是自定义 ControllerFactory;二是重新实现一个IControllerActivator,在实例化DefaultControllerFactory时作为构造函数参数传递进去。

Xoohoo选择了前者,实现了一个DependencyInjectorControllerFactory类。因为在创建Controller 实例后,还需要重新设置Controller的ActionInvoker。其实这正是 DependencyInjectorControllerFactory类存在的意义。

备注:在MVC2中,仅仅想要在创建Controller获取IoC/DI容器的支持,就需要自定义ControllerFactory。这在MVC3已经不是必需的,只需要创建一个容器类实现IDependencyResolver接口,然后在Global.asax中调用DependencyResolver.SetResolver方法即可。

5、ModelBinders

DefaultModelBinder是 非常强大的,或者是一个重量级的。如果想要稍微提供模型绑定的效率,或想要绑定更可控,就用自定义ModelBinder。Xoohoo增加了几个 ModelBinder,包括GuidListModelBinder、Int32ListModelBinder、 PagingInfoModelBinder、IPAddressModelBinder等。通过名称就可以看出它们的作用。

6、ValidationAttribute

MVC3的数据校验机制非常好用,只需一处设置,可以为您生成客户端校验脚本(如果开启Unobtrusive,则会生成html attributes),并支持服务器端验证。

Xoohoo扩展了几个常用的正则验证,如Email,Url,IP等;并提供一个比较验证CompareAttribute,可以进行日期、数字等类型的>、<、>=、<=、!=等比较,而MVC3内置的比较验证只能比较是否相等。
image

image

7、RouteConstraint

Xoohoo同样提供一些常用的路由规则,如IsInt、IsLong、IsGuid等,他们实现自IRouteConstraint接口。(随便提一下,与路由相关的,在老赵提供的子域名解决方案基础上进行了扩展完善)

8、FilterProvider

MVC提供了一种AOP(面 向侧面的程序设计)的编程模式,我们可以为Controller或Action设置各种Filter,包括ActionFilter、 ResultFilter、ExceptionFilter、AuthorizationFilter,在一定的阶段将执行这些Filter。关于 Filter的执行流程,可以看看xfrog这篇图文并茂的博文

MVC3提供了三种FilterProvider,它们提供了设置或获取Filter的方式,相较于MVC2,这又是一个扩展点。包括:GlobalFilterCollectionFilterAttributeFilterProviderControllerInstanceFilterProvider
GlobalFilterCollection可以通过GlobalFilters.Filters静态属性访问,用户增添全局的,作用于所有Controller的所有Action的Filter;
FilterAttributeFilterProvider用于获取基于Attribute的Filter;
ControllerInstanceFilterProvider用于获取Controller实例Filter(Controller实现了IActionFilter等接口,其本身就是一个Filter)。

Xoohoo提供了一种基于外部配置的Filter获取方式:FilterRegistryFilterProvider。可以设置某些 Controller具有某些Filter,也可以设置某些(不是全部)Action具有某些Filter。还可以某些Route或具体有某些 RouteValue,DataToken(如Area)的路由具有某些Filter(如果您愿意,还可以根据cookie值来配置)。它还有个优点就是 能将Filter像Golbal Filters一样缓存起来,不用像Attribute形式的Filter那样使用时还得通过反射从元数据中获取并进行反序列化。
可以通过FilterRegistry.Filters静态属性添加Filter,这和GlobalFilters.Filters也类似。

9、Extensions

Extensions就是一些扩展方法集。
Xoohoo为了支持更多的需求,几乎扩展了MVC3内置的相关的所有HtmlHelper、UrlHelper相关的扩展方法,用于支持如 二(多)级域名、更丰富的HtmlHelper、UrlHelper等。当然还有一些零散而好用的扩展就不一一列出。更丰富的扩展方法可以看看鹤冲天的相关博文。

二、Xoohoo是一个框架

Xoohoo作为一个框架,它支持插拔式的模块(插件)开发方式;内置有RBAC(Role-Based Access Control)模块,日志模块,缓存模块,并皆可替换;支持IoC/DI

开发一个新的模块非常简单,只需要实现一个接口,并在配置文件中增加模块的配置即可。

1、什么叫Xoohoo的模块(Module)

我们约定,实现了IModule接口的类称为模块。接口定义:

   1: public interface IModule
   2: {
   3:     /// <summary>
   4:     /// 模块名称
   5:     /// </summary>
   6:     string ModuleName { get; }
   7:     /// <summary>
   8:     /// 模块设置
   9:     /// </summary>
  10:     AppSettingsHelper Settings { get; set; }
  11:     /// <summary>
  12:     /// 初始化模块
  13:     /// </summary>
  14:     void Initialize();
  15:     /// <summary>
  16:     /// 注册路由配置
  17:     /// </summary>
  18:     void RegisterRoutes();
  19:     /// <summary>
  20:     /// 注册过滤器
  21:     /// </summary>
  22:     /// <param name="filterRegistry"></param>
  23:     /// <param name="globalFilters"></param>
  24:     void RegisterFilters(FilterRegistryFilterProvider filterRegistry, GlobalFilterCollection globalFilters);
  25:     /// <summary>
  26:     /// 注册模型绑定器
  27:     /// </summary>
  28:     /// <param name="modelBinders"></param>
  29:     void RegisterModelBinders(ModelBinderDictionary modelBinders);
  30:     /// <summary>
  31:     /// 卸载模块
  32:     /// </summary>
  33:     void Unload();
  34: }

模块包括模块名称(ModuleName)、模块设置(Settings)、初始化方法(Initialize)、注册路由方法 (RegisterRoutes)、注册过滤器方法(RegisterFilters)、注册模型绑定器方法 (RegisterModelBinders)、卸载模块方法(Unload)。Xoohoo提供了一个实现了IModule接口的抽象类Module, 一般情况下,我们建议你在设计新的模块时,实现抽象类Module而不是IModule接口,这样会让您开放起来更简单。

2、模块的配置

模块开发完成后,只需要将模块配置入Xoohoo.config即可生效。修改Xoohoo.config会导致AppDomain回收,所以不用去手工重动站点。当然,如果涉及数据库建表之类的操作,暂时还得您自己手工处理。
image

3、模块的加载

网站接收到第一次请求时,将会读取模块配置文件Xoohoo.config(位于站点的Configs目录,可自行更改)。获取模块的类型,创建模块的实例,依次调用模块的几个注册方法,将路由配置、Filter过滤器、模型绑定器配置加入MVC中。
更详细的关于模块定义及加载的内容,以及Xoohoo内置的几个模块的介绍,将放在之后的博文中。

4、后台服务的加载
后台服务是一个实现了IBackgroundService接口的类,它有对应的后台服务执行器(BackgroundServiceExecutor)负责服务的调度执行。

如果要定时完成一些任务,如邮件报告服务器资源信息,则可以定义一些后台服务。它的加载方式类似于模块的加载,并在模块加载完毕之后加载。
更详细的关于后台服务定义及加载的内容,将放在之后的博文中。

[转载]pureMVC实践

mikel阅读(1200)

[转载]pureMVC实践 – 挫鸟 – 博客园.

MVC模式是针对有相对复杂的用户交互应用的一种设计模式。由于产品迭代速度快,用户界面往往会发生重大变更,而业务逻辑也经常会因为用户的反馈而修正。

应用采用事件驱动,用户操作视图,视图产生相应事件,事件触发事件处理函数,事件处理函数执行业务逻辑,修改模型数据,模型数据通知视图数据已经修改,视图根据模型数据修正本身的表现。

然而糟糕的设计中,界面部分代码和事件处理函数往往混杂在一起,模型数据到处都可以引用修改,各部分耦合严重,当有某部分有变更时,往往发现牵扯的代码很多,小心翼翼的修改完每一处以后,又带来了未知数量的BUG。

在MVC模式中,严格把领域模型,控制器,和视图解耦,使得每一部分的变更都尽可能小的影响其他部分,在中大型项目中,也使技术水平不一的程序员可以在框架制约下,写出更具一致性的代码(个人认为这一点比前者带来的好处更诱人)。

下图是传统MVC模式中模块之间的通信图:

如果你尝试过在项目中引入MVC模式,会发现很多时候,你会有职责划分的困扰:

    1. 事件触发后,并不单纯只是通过控制器修改模型数据,有时需要修改视图的展现,甚至只是修改视图的展现,那这部分职责也要划分到控制器中去吗?(那对于用户交互复杂的应用来讲,控制器的职责未免过多了吧?)
    2. 视图通过事件处理函数引发控制器执行逻辑,则必须要保存会控制器的引用,如果把所有的逻辑统统写到事件处理函数中呢?(好吧,假如你要更换界面库呢?)
    3. 模型数据可能有多种,每种模型数据也有多种可以执行的操作,那控制器到底要做到多大粒度?(一对多?多对多?)
    4. 模型更新后对视图的通知,由模型保存视图的引用来进行通知?通过事件机制来通知?(不管如何又带来了视图和模型之间的耦合)

想象一下在一个多人协作的项目组中,部分程序员负责构建领域模型,部分程序员 负责构建业务逻辑,部分程序员负责界面展现,全部资源按照特长各尽其职,但是不管是糟糕的设计,还是传统的MVC模式,所有部分的工作都必须在得到其他部 分工作的支持之后才能完成。然后不管是项目进度还是办公室又变成了一片狼藉。

针对传统MVC的问题,pureMVC将各部分进一步解耦,并以最简单的形式把经典设计模式应用到设计中,在代码构建中,为小中型项目提供了构建基础。当然,任何收益都是有代价的,对于pureMVC所引入的问题,本文将会在文章末尾和大家讨论。

为了解决上面提到的问题1和2,pureMVC引入Mediator模式解耦视图和控制器,Mediator负责接收视图事件,执行必要的视图展现逻辑和通过控制器来执行业务逻辑。

针对问题3,pureMVC引入了Command来形成自然而然的对应关系,每个Command对应某一操作,通过简单的修改,我们还可以得到附加的收获,支持撤销和重做。

针对问题4,pureMVC通过通知机制来解决传统事件机制的一些问题,程序员只需要注册通知和命令的对应关系,对每种可接收通知的类型,罗列它们感兴趣的通知类型并编写通知处理就可以了。

pureMVC还在控制器和数据模型之间引入了Proxy来解耦,这样可以自然的解决远程数据的存取,也为模型数据改变通知的发出找到了责任对象(由Proxy验证域逻辑,执行操作,并发出模型数据更新通知)。

下面是pureMVC框架的模块示意图:

这样,用户操作通过Mediator,执行了必要的展现逻辑之后,发出通知;通知触发一个命令来执行相应的业务逻辑修改模型数据;模型数据发出通知,通知所有感兴趣的视图根据修改过的数据来更新视图。

这样,负责不同部分的同事只要事先约定系统中有哪些通知,并约定一些命名规则,就可以并行进行自己的工作,单元测试并按计划交付整合了。

下面是pureMVC新引入模式的通信图:

说了这么多,可能有些同行还是摸不清思路,下面我们用一个实际的构建过程来说明一个基于pureMVC框架的应用是如何运作的。

pureMVC是一个开源项目,该框架在不同平台下的代码都可以在官方网站找到,需要的同行可以找来配合本文阅读。

假设我们在.NET平台下用C#开发一个动画编辑器项目,在对动画每一帧的编辑行为中,自然的划分出了以下职责对象:

  • Control:用户视图组件,负责呈现已经编辑完成的帧并接受用户操作
  • FrameEditMediator:负责接受事件,执行必要的视图展现逻辑更在必要时形成命令修改帧数据,接受数据模型更改通知并更新视图展现

public Edit()
: base("Edit")
{
size = new Size(240, 320);
scale = 1;

BufferedPanel panel = new BufferedPanel();
panel.Location = new System.Drawing.Point(0, 0);
panel.Size = size;
panel.TabIndex = 0;
panel.Paint += new System.Windows.Forms.PaintEventHandler(this.frameEditor_Paint);
panel.MouseDown += new System.Windows.Forms.MouseEventHandler(this.frameEditor_MouseDown);
panel.MouseMove += new System.Windows.Forms.MouseEventHandler(this.frameEditor_MouseMove);
panel.MouseUp += new System.Windows.Forms.MouseEventHandler(this.frameEditor_MouseUp);
panel.MouseWheel += new System.Windows.Forms.MouseEventHandler(this.frameEditor_MouseWheel);
m_viewComponent = panel;
}

public override IList ListNotificationInterests()
{
List intereste = new List();
intereste.Add(Shelf.SelectedChanged);
return intereste;
}

public override void HandleNotification(INotification notification)
{
switch (notification.Name)
{
case Shelf.SelectedChanged:
{
Panel panel = (Panel)m_viewComponent;
IProxy proxy;

proxy = Facade.RetrieveProxy("Group");
group = (Group)proxy.Data;

proxy = Facade.RetrieveProxy("Animation");
animation = (Animation)proxy.Data;

proxy = Facade.RetrieveProxy("Frame");
frame = (Frame)proxy.Data;

panel.Refresh();
}
break;
default:
break;
}
}

private void frameEditor_MouseDown(object sender, MouseEventArgs e)
{
Panel p = (Panel)(sender);

if (e.Button == MouseButtons.Left)
{
p.Focus();
}
}

private void frameEditor_MouseMove(object sender, MouseEventArgs e)
{

}

private void frameEditor_MouseUp(object sender, MouseEventArgs e)
{

}

private void frameEditor_Paint(object sender, PaintEventArgs e)
{
//todo:
}

private void frameEditor_MouseWheel(object sender, MouseEventArgs e)
{
//todo:
}

private Size size;
private int scale;

private Group group;
private Animation animation;
private Frame frame;
}

AddSegmentCommand:对每一帧在指定位置添加指定图形切片的命令

{
public AddSegmentCommand()
{

}

public override void Execute(INotification notification)
{
//todo:
}
}

FrameProxy:验证命令合法性,对真正的模型数据执行修改,当修改成功后,发出数据更新的通知

{
public const String SegmentAdded = "FrameProxy.SegmentAdded";
public const String SegmentRemoved = "FrameProxy.SegmentRemoved";
public const String SegmentOrderChanged = "FrameProxy.SegmentOrderChanged";

public FrameProxy()
: base("Frame", null)
{ }

public void AddSegment(int templateIdx)
{
if (m_data != null)
{
Frame frame = (Frame)m_data;
frame.AddSegment(templateIdx);
Facade.SendNotification(SegmentAdded);
}
}

public void RemoveSegment(int idx)
{
if (m_data != null)
{
Frame frame = (Frame)m_data;
frame.RemoveSegment(idx);
Facade.SendNotification(SegmentRemoved);
}
}

public void UpSegment(int idx)
{
if (m_data != null)
{
Frame frame = (Frame)m_data;
frame.UpSegment(idx);
Facade.SendNotification(SegmentOrderChanged);
}
}

public void DownSegment(int idx)
{
if (m_data != null)
{
Frame frame = (Frame)m_data;
frame.DownSegment(idx);
Facade.SendNotification(SegmentOrderChanged);
}
}

public void TopSegment(int idx)
{
if (m_data != null)
{
Frame frame = (Frame)m_data;
frame.TopSegment(idx);
Facade.SendNotification(SegmentOrderChanged);
}
}

public void BottomSegment(int idx)
{
if (m_data != null)
{
Frame frame = (Frame)m_data;
frame.DownSegment(idx);
Facade.SendNotification(SegmentOrderChanged);
}
}
}

Frame:真正的帧数据

class Frame
{
public Frame()
{
segments = new ArrayList();
rects = new ArrayList();
Delay = 1;
}

public Segment AddSegment(int templateIdx)
{
Segment sec = new Segment(templateIdx);
segments.Add(sec);
return sec;
}

public Segment GetSegment(int index)
{
if (index &gt;= 0 &amp;&amp; index &lt; segments.Count)
{
return (Segment)segments[index];
}
else
{
return null;
}
}

public void RemoveSegment(int segIndex)
{
segments.RemoveAt(segIndex);
}

public void UpSegment(int segIndex)
{
if (segIndex &lt; segments.Count - 1)             {                 Object temp = segments[segIndex + 1];                 segments[segIndex + 1] = segments[segIndex];                 segments[segIndex] = temp;             }         }         public void DownSegment(int segIndex)         {             if (segIndex &gt; 0)
{
Object temp = segments[segIndex - 1];
segments[segIndex - 1] = segments[segIndex];
segments[segIndex] = temp;
}
}

public void TopSegment(int segIndex)
{
if (segIndex &lt; segments.Count - 1)             {                 Object temp = segments[segIndex];                 segments.RemoveAt(segIndex);                 segments.Add(temp);             }         }         public void BottomSegment(int segIndex)         {             if (segIndex &gt; 0)
{
Object temp = segments[segIndex];
segments.RemoveAt(segIndex);
segments.Insert(0, temp);
}
}

public int SegmentCount
{
get
{
return segments.Count;
}
}

public int RectCount
{
get
{
return rects.Count;
}
}

public int Delay { set; get; }

private ArrayList segments;
private ArrayList rects;
}

用户通过拖拽大图的切片到编辑区的视图,FrameEditMediator 负责接受视图事件,并发出值为”AddSegment”的通知,通知触发AddSegmentCommand命令来执行业务逻辑,计算相应坐标之后,找到 FrameProxy执行对Frame的操作,FrameProxy验证了合法性之后,执行操作,发出帧更新通知,因为 FrameEditMediator对该通知感兴趣,它接受到之后,更新视图,一个完整的修改操作就完成了。

下面我们来讲一下pureMVC所带来的问题。首先,它的通知机制基于字符 串,字符串固然灵活,但对命名冲突之类的问题不好控制,这个可以通过严格的命名规则来解决;其次,通知不定向,会带来调试上的困难;再次,性能问题,由于 强制解耦,系统中的对象众多,对象往往通过字符串来索引,所以,在游戏设计这种以帧为逻辑执行单位的应用中,务必要注意性能问题。

不管仍然存在哪些问题,在项目的实际应用中,该框架仍然带来了意料之中的好处,使得应对需求变更及时,后期维护简单。当然,框架只是工具,心中有框架,手中无框架才是真正的高手。希望本文和pureMVC能够给各位同行的工作带来启发。

[转载]如何实现一个c/s模式的flv视频点播系统

mikel阅读(1626)

[转载]如何实现一个c/s模式的flv视频点播系统 – haibindev – 博客园.

如何实现一个c/s模式的flv视频点播系统

一、写在前面

视频点播,是一个曾经很热,现如今依然很热的一项视频服务技术。本人最近致力于研究将各种视频格式应用于点播系统中,现已研究成功FLV, F4V, MP4, TS格式的视频点播解决方案,完全支持以上格式中存放H.264编码视频的情况,并将继续研究其他格式。

这里有一点需要说明的是,F4V本来指的是Adobe基于ISO/IEC 14496-12标准推出来的视频格式,与MP4格式相同,但因为国内许多的视频网站,均把存放H.264编码视频的FLV文件标注为.f4v格式,所以,为了避免更加混淆,我沿用了它们的命名方式,因此,“FLV”将仅指包含H.264/AAC编码的一般FLV视频。

本文简单介绍如何实现一个简单的基于flv的视频点播系统。

二、FLV格式

FLV(Flash Video)是Adobe公司推出的视频格式,是一种专门用来在网络上传输的视频存储容器格式。其格式相对简单,不需要很大的媒体头部信息,因此加载速度 极快。国内各大视频网站,均有采用FLV格式作为其点播、甚至直播的视频格式。FLV容器格式的主要特点是tag,整个FLV由Video Tag, Audio Tag以及其他Tag组成,没有映射表。

基本上,可以把FLV格式简要画为如下形式:

其中,FLV Header指的是FLV的头部,只有9个字节,和常规的MP4文 件头部相比,这个小太多了。FLV MetaData存放的是一些整个视频文件的媒体信息,包括作者、视频宽高、时长、关键帧列表等等,这些信息对于解析视频文件来说,当然很重要,不过可惜 的是,因为FLV的MetaData是可有可无的,所以有不少FLV没有MetaData,而且即便有,其MetaData中的信息也是不正确的,这导致 MetaData非常不值得信赖。所以,在解析FLV的时候,应该直接忽略FLV MetaData部分,通过遍历FLV的所有Tag,来获取相应的媒体信息。

三、方案

点播系统的实现,有几个关键点,最重要的应该是“播放网络流”和“拖动”这两点。其中,关于“拖动”,由于整个系统是基于网络的,客户端在拖动之后,必须重新向服务器端发送请求,服务器根据客户端的拖动请求,寻找最接近的拖动点,返回可以播放的数据流,从而完成点播拖动。

对于FLV来说,因为其格式简单,头部非常小,而MetaData可以不要,所以只需要寻找到关键帧,然后拼上FLV Header,就可以组成一个可播放的视频文件。如下图所示:

根据FLV格式的特点,已经拖动后生成新的可播放文件的方法,可以将flv点播系统设计成如下方案:

1. 服务器端

首先,解析flv文件,生成flv视频关键帧列表,表明时间和便宜量的对应关系。然后,当接收到客户端发来的数据请求时,根据客户端拖动的时间 点的请求,找出时间距离最近的关键帧,根据偏移量,读取flv的文件数据,然后拼接flv的9字节文件头部(还需要再加上4个字节全零的pre tag size,共13字节),返回。

2. 客户端

客户端需要特别注意的,除了正确的解析视频流并播放,还需要小心时间轴的行进,因为用户拖动的时间位置并不一定是关键帧,所以,需要根据服务器返回的关键帧的真实时间,重新定位播放进度。

四、实现

服务器可以实现为一个http server,只需要提供http下载,在响应下载之前,需要完成之前所描述的逻辑。可以完全自己实现,可以直接使用现有的成熟web server,如果对性能要求很高,强烈推荐nginx,借助于nginx强大的处理性能,通过实现自己的nginx module,可以完成你所有的需求。

客户端播放器,对于不太熟悉播放器开发技术的,可基于libvlc,借助于vlc良好的网络播放能力,基本可以满足需要。

[转载]如何用C#实现一个Whois的查询

mikel阅读(972)

[转载]如何用C#实现一个Whois的查询 – lovebanyi – 博客园.

什么是whois

简单来说,whois就是一个用来查询域名是否已经被注册,以及注册域名的详细信息的数据库(如域名所有人、域名注册商、域名注册日期和过期日期等)。通过whois来实现对域名信息的查询

什么去查询Whois?

Whois的查询其实也是蛮简单的,就是利用Socket去连接whois提供的服务器。Whois服务的默认端口是43,查询的话就是把域名往这边发送过去,Whois服务器在收到你的请求后就会返回纯文本的格式,这个写起来真的蛮容易,比查询dns协议简单多了。

Whois的服务器有哪些呢?

nl whois.domain-registry.nl
eu whois.eu
edu whois.educause.net
net whois.crsnic.net
com whois.crsnic.net
org whois.crsnic.net
info whois.afilias.com
de whois.denic.de
cn whois.cnnic.net.cn
这些是我收集的whois服务器

比如你要查询的域名 是www.zhenqiu.net 它是属于 .net后缀的,这个时候你就要去 whois.crnic.net这边来查询了。

接下来我们来看具体的实现代码。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.IO;

namespace Qianfa.Utility
{
///
/// binbin
///
public class Whois
{
///
/// diabled to new instance
///
private Whois()
{

}

///
/// Clear cache
///
public static void ClearCache()
{
lock (_lock)
{
_instance = null;
}
}

private static Whois _instance =null;
private static object _lock = new object();
public static Whois Create(string path)
{
if (_instance == null)
{
lock (_lock)
{
if (_instance == null)
{
_instance = new Whois();

_instance.serverList = new Dictionary();
StreamReader sr = new StreamReader(path);

while (sr.Peek() != -1)
{
string line = sr.ReadLine();
string[] temp = line.Split(new char[] { '\t', ' ' });
_instance.serverList.Add(temp[0].ToLower(), temp[1]);
}
}
}
}

return _instance;
}

public Dictionary serverList;

///
/// .pro','.name', and '.tv' domains require an account for a whois
///
///
<span> </span> ///
public string LookUp(string domain)
{
string result = "";
string[] temp = domain.Split('.');
string suffix = temp[temp.Length - 1].ToLower();// get the last;
if (!serverList.Keys.Contains(suffix))
{
result= string.Format(Resources.Whois.NoSupport,suffix);
return result;
}
string server = serverList[suffix];

TcpClient client = new TcpClient();
NetworkStream ns;
try
{
client.Connect(server, 43);
ns = client.GetStream();
byte[] buffer = Encoding.ASCII.GetBytes(domain + "\rn");
ns.Write(buffer, 0, buffer.Length);

buffer = new byte[8192];

int i = ns.Read(buffer, 0, buffer.Length);
while (i &gt; 0)
{
Encoding encoding = Encoding.UTF8;
//if (suffix == "cn")
//{
//    encoding = Encoding.UTF8;
//}
//else
//{
//    encoding = ASCIIEncoding.ASCII;
//}
result += encoding.GetString(buffer, 0, i);
i = ns.Read(buffer, 0, buffer.Length);
}
}
catch(SocketException)
{
result = Resources.Whois.SocketError;
return result;
}
ns.Close();
client.Close();

return result;
}
}
}

我是把whois的服务器的文件放在一个文本文件里面 放在了

\App_Data\WhoisServer.txt这里面了。 这样在这个Whois类实例化的时候。就会自动去加载这些内容了。
关键的部分就是 Lookup方法了 Lookup允许传入的是域名,然后我们会去判断它是哪一个后缀,然后得到它是用哪一个server。接下来我们用
TcpClient去连接哪个server的43端口。把字符串变成字节流,发送到服务器,不断的读取服务器发送过来的内容 等到什么也读不到的时候就完成了这次查询,(这种是同步模式),然后把字节流变成字符串,就完成了这一个查询了。

看一下Demo是什么用它的。

新建一个WebForm page 在页面里面放一个 label控件取名为 lblResult。
哪么这个页面你就可以在浏览器里输入 http://yourserver:port/DomainWhois.aspx?domain=zhenqiu.net.
我在实际项目中用到的地址是

http://www.starteenwinkel.nl/domainwhois.aspx?domain=zhenqiu.net
public partial class DomainWhois : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
string domain = Request.QueryString[“Domain”];

if (!string.IsNullOrEmpty(domain))
{
Whois whois = Whois.Create(Server.MapPath(“~/App_Data/WhoisServer.txt”));
lblResult.Text = whois.LookUp(domain).Replace(“\r\n”,”<br />”).Replace(“\n”,”<br />”);
}
}
}

[转载]11本商业智能、数据挖掘相关书目(2)

mikel阅读(1030)

[转载]11本商业智能、数据挖掘相关书目(2):增加《智能Web算法》 – 左其盛 – 博客园.

11本商业智能、数据挖掘相关书目(2):增加《智能Web算法》

1:《SQL Server 2008商业智能完美解决方案》,对商业智能做了一个概括的介绍。个人看法比较适合于已经熟悉微软商业智能原理与实现的人阅读,比如熟悉SQL server 2005商业智能实现的人可以了解一下2008下的情况。不熟悉的人通过这本书可以了解商业智能的基础理论。涉及到具体的操作,书中虽然有许多截图,但是 不够详细,没法用这本书来学习具体的操作。个人看法这本书要么再详细一些,要么再简略一些,都会比现在要好。

2:
《数据挖掘导论(完整版)》是国外大学的教材,看译者序许多国内高线也用它作为教材。因此这本书讲原理比较多。比较透彻,充满了数学符号和公 式。详细讲述了分类、关联分析、聚类分析三种数据挖掘算法的原理。我发现有些数据挖掘的参数在微软的帮助文档中没说明白,看这本书说的比较清楚。我想看的 是能指导实际的数据挖掘操作的书,因此对书中的公式基本跳过。只看了看书中讲到的算法解决的各种问题及其思路。

3:《数据挖掘原理与应用(第2版)》,讲解Excel2007的数据挖掘插件做数据挖掘的部分不错,后面讲数据挖掘的各种算法,详细列出了各种参 数和DMX脚本,但是没有透彻的讲解,给我的感觉就像微软的bookonline,适合作为工具书去查阅,但是不适合作为入门书去阅读。书的最后部分给出 了用.net实现数据挖掘编程的例子,应该还可以实例代码在实际项目中做参考。

4:《精通sql server 2008程序设计》,全书分了四个部分,第四部分讲商业智能。限于篇幅问题在数据挖掘方面只讲了一种算法(聚类分析)的实现,如果想了解商业智能与数据挖掘,这本书也不够实用。

5:《智能Web算法》:所谓智能web,书中是指能根据用户在网站的行为作出针对用户的响应的网站。常见的应用就是推荐系统。另外作者认为google的搜索也是智能web的例子。

书中讨论了Google搜索原理、推荐系统原理、聚类算法、分类算法。本书附有Java实现书中算法的源代码,书中有不少篇幅是针对源代码讲算法。读者也可以使用源代码做测试。当然作者声明给出的代码都是经过刻意简化,目的是为了让读者快速明白算法的基本原理。

感觉本书的翻译还比较专业,读起来比较流畅。

值得夸一夸的是本书的印刷用纸。不知道业内名词,但是书拿在手里明显比同体积的书要轻,感觉很舒服。我在amazon.com上买过几本美国出的书,也是感觉明显比较轻。

6:《深入浅出数据分析》:深入不够,浅出做的不错。适合技术人员与业务人员作为统计学入门读物来阅读

《深入浅出数据分析》是美国O’REILLY出版社的“深入浅出”系列之一。这个系列的特点是花了不少心思琢磨如何让读者更舒服地阅读、记住书中更 多内容,书虽然都比较厚,但是插图非常多。插图与正文经常混在一起,没有明显的界限。读起来确实比较轻松,相对文字多图少的书来说,也确实更容易记住书中 的内容。由于图多字少,读起来很快,也能让人感觉比较有成就感。不过这个系列的书都比较贵。

这本书的主要内容我认为是统计学入门。不懂技术但是用过excel的人就可以看。讲了讲统计学的基本概念和贝叶斯统计、直方图、回归、误差等概念, 使用了excel和一个专门的统计工具R做演示。作者比较推荐R。因为这个工具在统计学应用方面比excel更灵活。是一个开源软件,还有相关的社区在为 这个软件不停的增加功能。

7:《可视化数据》专讲Process这个软件的使用,网络书店对这本书的介绍都提及了这个问题。对这个软件不感兴趣的就不用买了。我买的时候没注 意到网页上介绍,看的时候才发现。书中充斥着process的代码。没有学习和使用process的计划,因此也就大概翻了翻,看看process解决的 问题及其思路。

8:《业务建模与数据挖掘》是05年出版的。与《数据模型资源手册》卷一卷二同属机械工业出版社的数据库技术系列丛书。后者早就卖光了,这本书还 有。看来还是比较冷僻。翻译同样的比较专业。同样是偏学术性的。不过作为专业书来说,公式、图表偏少。个人看法想研究具体的数据挖掘算法可以看《数据挖掘 导论(完整版)》。

9:《数据之美》:概括描述了20个数据挖掘、数据可视化、云存储及其他数据处理相关项目

这本书相对来说还算比较新,是从英文版翻译过来的。英文版2009年出版。中文版2010年10月出版。由20篇相互独立的文章组成。每篇讲一个数据处理相关的项目。不涉及具体的技术细节,仅仅是概括说明原理、思路、过程、结果。

总体来说,阅读起来有点晦涩。感觉作者基本都明白英文版的意思,不过有些地方中文表达上不够通顺。这在IT业的翻译书中已经算不错的组合了,强过中文过关但是不懂技术的情况。

10:《超级数字天才》讲数据挖掘给人类带来的好处。比较详细,给出了具体的例子和实现的思路。书中列也在使用“数据分析”这个词,基本也在指“数 据挖掘”。书中列举了数据挖掘在政府政策、教育、医疗等许多方面的实际应用的例子,都比较有意思。想了解数据挖掘的具体应用,可以看看这本书。

书中给我印象比较深刻的是例子中政府与教育行业的数据挖掘应用。一项政策的好坏,一个教育方法的好坏,美国人都试图用数据说话。

11:《数据分析竞争法》是商务印书馆的哈佛经管系列之一,名头比较大,先看它。不过有些失望。不是给技术人员看的。题目中的数据分析大约是说商业 智能中的数据挖掘。作者把企业利用数据挖掘的程度分为由低到高的五级。全书反复论述每个级别的方法、工具、流程、对企业的好处,还举了一些的例子。在我看 来这些例子不够详细,太笼统。所以我认为这本书适合于不了解商业智能与数据挖掘的管理人员阅读。

[转载]页面性能测试之一、

mikel阅读(761)

[转载]页面性能测试之一 – 虫师 – 博客园.

一、页面性能测试概述

页面性能测试则是针对于页面性能优化而开展的一种性能测试,目的是对Web系统的页面进行测试以确认系统页面是否会影响系统的性能并为页面的优化提供依据与建议,最终提升系统的整体性能表现,提高用户体验满意度。可见,Web系统页面性能测试是相对Web系统后台测试的另外一种性能测试,是Web系统性能测试的一个重要部分。

二、页面性能测试必要性

相对于C/S架构的应用系统,Web应用系统所有数据都需要从服务器端下载,虽然浏览器有缓存机制,但客户每次访问仍然需要下载大量的数据。特别是用户对系统要求越来越高,除了要求功能完备,对界面的美观、易用性也提出了更高的要求,越炫的页面也就意味着页面中要包含更多的脚本、样式表、图片和Flash,页面的数据量也就越大,这对Web系统的性能提出了极大的挑战。

曾 经有个在线打印服务的应用提供商说他们的系统不需要关注系统性能问题,没有必要进行性能测试,因为他们可以购买足够多的服务器来支撑系统;不少业界同行也 认为只要有足够多的服务器资源,性能就不会存在问题。其实不然,他们都只关注到了应用系统的后台性能表现,而忽略了页面对系统整体性能的影响。举个例子, 当一个页面中包含几百个请求,页面中没有经过优化的JavaScript文件、CSS 文件与图片件大小达到10MB,即使当前只有一个用户在访问该系统,页面的访问速度也会慢得惊人,纵使增加再多的服务器也不见得会有明显的性能提升。

可见,对Web应用系统的页面进行性能测试和优化是非常有必要的。只有通过对页面的性能测试,发现页面存在的性能问题并根据性能测试结果进行页面优化以提升页面的加载性能,从而提升系统的整体性能。在应用系统高并发访问时,更能体现出Web页面优化后所带来的系统整体性能提升效果。

=========================================================================================================================

2种方式来提升你的web 应用程序的速度:

减少请求和响应的往返次数

减少请求和响应的往返字节大小。

———————减少请求和响应的往返次数:———————–

HTTP缓存是最好的减少客户端服务器端往返次数的办法。缓存提供了提供一种机制来保证客户端或者代理能够存储一些东西,而这些东西将会在稍后的HTTP 响应中用到的。(即第一次请求了,到了客户端,缓存起来,下次如果页面还要这个JS文件或者CSS文件啥的,就不要到服务器端去取下来了,但是还是要去服务器上去访问一次,因为请求要对比ETAG值,关于这个值,我将会在下次翻译中介绍其作用)这样,就不用让文件再次跨越整个网络了。

缓存相关的请求头

为了提高性能,微软的IE和其他的web客户端总是想尽办法来维持从远程服务器上下载下来的本地的缓存。

当客户端需要一个资源(html,css.js…),他们有3种可能的动作:

1 发送一个一般的HTTP请求到远程服务器端,请求这个资源。

2 发送一个有条件的HTTP请求到服务器,条件就是如果它不同于本地的缓存版本。

3 如果缓存的拷贝可用,就使用本地的缓存资源。

当发送一个请求,客户也许会使用如下的几个HEADER

————————-减少请求肯响应往返的字节大小:———————————

1 使用更少的图画

2 将所有的CSS浓缩到一个CSS文件中

3 将所有的脚本浓缩到一个JS文件中

4 简化你的页时间

5 使用HTTP压缩

==========================================================================================================================================

页面性能测试工具介绍:

第一种是通过HTTP代理的方式来截取客户与服务器之间的通讯。


此类的工具非常的多,如:

Charles官网:http://www.charlesproxy.com/

charles一个HTTP代理/ HTTP监视器/使开发人员可以查看所有计算机和互联网之间的HTTP和SSL/ HTTPS流量反向代理这包括请求,响应HTTP标头其中包含的cookies和缓存信息

charles界面清爽,采用中国的瓷器为logo,给人的感觉简洁高雅。而且使用也非常简单。下载页面:http://www.charlesproxy.com/download/ 进入选择你适合你的版本,安装也非常简单,一路“next”就OK了。

点击工具栏上的“红色”按钮,就自动的记录你浏览器访问的所有网站


Fiddler2官网:http://www.fiddler2.com/fiddler2/

Fiddler是一个Web调试代理记录所有的HTTP(S之间的计算机和互联网交通提琴手允许您检查交通,设置断点,捣鼓传入或传出数据菲德勒包括一个强大的基于事件的脚本子系统,并可以使用任何NET语言扩展

Fiddler是免费软件可以调试,从几乎任何应用程序支持代理,包括IE浏览器,谷歌Chrome,苹果SafariMozilla Firefox中,歌剧还有数千交通您也可以像Windows电话,iPod/ iPad和其他流行的设备调试交通

Fiddler2相比Charles功能要更强大一些。当然了,如果单单把他们理解成页面性能测试工具有此片面,尤其Fiddlers2功能强大,当然了,我也没有深究,在此就不过多评论了。

[转载]五个Metro UI 风格的网页设计

mikel阅读(766)

[转载]五个Metro UI 风格的网页设计 – 孟晨 – 博客园.

随着Windows Phone7的发布,WIN8的初露倪端,Metro这种新兴设计风格正被大家所熟悉。

Metro本意是地铁,Metro风格设计和灵感来源正是来源与机场和地铁系统所使用的视觉语言。

Metro设计具备以下五点原则:

1.干净、轻量、开放、快速

2.要内容,而不是质感

3.整合软硬件

4.世界级的动画

5.生动,有灵魂

这些设计原则都基于这样一个原则,就是所有的UI元素都应该实实在在的数字化,并附以和谐的,功能化的,吸引人的视觉元素。应用程序应该用探索精神和激动人心的视觉设计去打动用户。

Metro设计不仅仅是用于移动应用和应用程序的设计,越来越多的网站也融入了Metro UI,下面我就推荐几个我收藏的Metro UI 风格的网页设计.这 类的网站也是从微软的网站开始流行的。

1 最先发现的是MSDN了。http://msdn.microsoft.com/

2 www.msn.com

4http://www.mdong.org/

5theie6countdown.cn/educate-others.aspx

[转载]ASP.NET MVC:Expression Trees 作为参数简化查询 二

mikel阅读(957)

[转载]ASP.NET MVC:Expression Trees 作为参数简化查询 二 – 鹤冲天 – 博客园.

前文《ASP.NET MVC:Expression Trees 作为参数简化查询》中提出可以将 Expression Trees 用作查询 Action 的参数来简化编码:

1
2
3
4
public ActionResult Index([QueryConditionBinder]Expression<Func<Employee, bool>> predicate) {
    var employees = repository.Query().Where(predicate);
    return View("Index", employees);
}

文中给出的 QueryConditionExpressionModelBinder 类,比较僵化,无法满足实际要求。本文将会从这个类为起点,构建一个灵活的解决方案。本文的内容稍有枯燥,先给出最终的运行截图,给大家提提神:

演示网站运行截图

在线演示:http://demos.ldp.me/employees

下图显示的 Expression 是根据查询条件动态生成的:

image

调试截图:

image

设计目标

支持以下类型查询:

  • 相等查询
  • 字符串查询:完全匹配、模糊查询、作为开始、作为结束;
  • 日期查询(不考虑时间)、日期范围查询;
  • 比较查询:大于、大于等于、小于、小于等于;
  • 正确处理可空类型

阻止某些查询:

  • ID查询
  • 某些保密属性,如内部价格属性等

扩展性:

  • 系统容易扩展,开放支持加入新的查询类型

易用性:

  • 简单使用

其它:

  • 查询数据验证,配合 MVC 相应机制,对错误输入给出提示。

思考

想法源自 Entity Framework:

EF 中的 Convention

在 EF Code First 中,Entity 与 数据库 Table 之间映射采用 Convention (约定) 的方式:

System.Data.Entity.ModelConfiguration.Conventions 命名空间中有很多这样的 Convention。这些 Convention 都是被大多人公认的,EF 运行时会加载这些 Convention,因此我们使用 EF 会相当简单,不需要像 NH 那样进行大量繁琐无聊的映射配置工作。

如果你认可其中的某条 Convention 你可以将它移除:

1
2
3
4
5
public class NorthwindDbContext : DbContext
{
    protected override void OnModelCreating(DbModelBuilder modelBuilder) {
        modelBuilder.Conventions.Remove<IdKeyDiscoveryConvention>();
}

不错吧,但 EF 不允许添加新的 Convention,有点遗憾。

分解出 Convention

借鉴 EF 的思路,我们可以分解出以下 Convention:

  • ValueTypeEqualsConvention:值类型相等,年龄 == 18、婚否 = false;
  • StringContainsConvention:字符串包含,即模糊查询;
  • DateEqualsConvention:日期等于,忽略时间;
  • ValueTypeCompareConvention:值类型比较,价格大于 12.00;
  • BetweenDatesConvention:时间界于两个日期之间;
  • IDForbiddenConvention:禁止对 ID 查询。

还有一点,要将各个条件组合起来,如:(年龄 <= 18) 并且 (婚否 = false), 或者 (年龄 <= 18) 或者 (婚否 = false)。因此,还要定义用于连接组合的 Convention:

  • AndCombineConvention:并且,在页面查询中,这个比较常用,我们设成默认的;
  • OrCombinedConvention:或者;
  • XXXComplexCombineConvention:更加复杂的情况,如:(存款 > 100,000,000) Or ((年龄 <= 18) 并且 (婚否 = false))。

可设置的 Order 属性

给每个 Convention 设置一个优先顺序号,大的优先级高:

  • StringContainsConvention、DateEqualsConvention 优先于 ValueTypeEqualsConvention;
  • BetweenDatesConvention 优先于 DateEqualsConvention。

即采用了 StringContainsConvention 就不会再采用 ValueTypeEqualsConvention。

编码时会根据实际应用给每个 Convention 设置一个默认的合理的 Order 值,但为了灵活通用,允许修改,Order 是一个 get-set 属性。

可以添加新的 Convention 以满足更多应用

EF 只能移除不能添加,有时感不方便,不太符合 OCP(Open-Closed principle)。

编码实现

抽象出接口

根据上面的分析,可以提取出下面三个接口:

  • IConvention 接口,代表所有的约定:
    1
    2
    3
    public interface IConvention {
        int Order { get; set; }
    }
  • IPropertyExpressionConvention 接口,将单个查询条件转换为 Expression:
    1
    2
    3
    public interface IPropertyExpressionConvention: IConvention {
        Expression BuildExpression(BuildPropertyExpressionContext context);
    }
  • IExpressionCombineConvention 接口,将多个查询 Expression 进行合并:
    1
    2
    3
    public interface IExpressionCombineConvention : IConvention {
        Expression Combine(IDictionary<string, Expression> expressions);
    }

修改 QueryConditionExpressionModelBinder 类

修改后代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
public class QueryConditionExpressionModelBinder : IModelBinder {
    private ConventionConfiguration _conventionConfiguration;

    public QueryConditionExpressionModelBinder(ConventionConfiguration conventionConfiguration) {
        _conventionConfiguration = conventionConfiguration;
    }

    public QueryConditionExpressionModelBinder(): this(ConventionConfiguration.Default) { }

    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) {
        var modelType = GetModelTypeFromExpressionType(bindingContext.ModelType);
        if (modelType == null) return null;

        var parameter = Expression.Parameter(modelType, modelType.Name[0].ToString().ToLower());

        var dict = new Dictionary<string, Expression>();
         var propertyExpressionConvertions = _conventionConfiguration.GetConventions<IPropertyExpressionConvention>();
        foreach (var property in modelType.GetProperties()){
            foreach (var convention in propertyExpressionConvertions) {
                var context = new BuildPropertyExpressionContext(
                    property,
                    bindingContext.ValueProvider,
                    controllerContext.Controller.ViewData.ModelState,
                    parameter.Property(property.Name)
                    );
                var expression = convention.BuildExpression(context);
                if(expression != null){
                    dict.Add(property.Name, expression);
                    break;
                }
                if (context.IsHandled) break;
            }
        }
        var body = default(Expression);
        foreach (var convention in _conventionConfiguration.GetConventions<IExpressionCombineConvention>())
        {
            body = convention.Combine(dict);
            if (body != null) break;
        }
        //if (body == null) body = Expression.Constant(true);
        return body.ToLambda(parameter);
    }
    /// <summary>
    /// 获取 Expression<Func<TXXX, bool>> 中 TXXX 的类型
    /// </summary>
    private Type GetModelTypeFromExpressionType(Type lambdaExpressionType) {

        if (lambdaExpressionType.GetGenericTypeDefinition() != typeof (Expression<>)) return null;

        var funcType = lambdaExpressionType.GetGenericArguments()[0];
        if (funcType.GetGenericTypeDefinition() != typeof (Func<,>)) return null;

        var funcTypeArgs = funcType.GetGenericArguments();
        if (funcTypeArgs[1] != typeof (bool)) return null;
        return funcTypeArgs[0];
    }
    /// <summary>
    /// 获取属性的查询值并处理 Controller.ModelState 
    /// </summary>
    private object GetValueAndHandleModelState(PropertyInfo property, IValueProvider valueProvider, ControllerBase controller) {
        var result = valueProvider.GetValue(property.Name);
        if (result == null) return null;

        var modelState = new ModelState {Value = result};
        controller.ViewData.ModelState.Add(property.Name, modelState);

        object value = null;
        try{
            value = result.ConvertTo(property.PropertyType);
        }
        catch (Exception ex){
            modelState.Errors.Add(ex);
        }
        return value;
    }
}

高亮代码为修改或新增部分。

QueryConditionExpressionModelBinder 中使用了 ConventionConfiguration 类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class ConventionConfiguration {

   public static ConventionConfiguration Default = new ConventionConfiguration();

   static ConventionConfiguration() {
       Default.Conventions.Add(new ValueTypeEqualsConvention());
       Default.Conventions.Add(new StringContainsConvention());
       Default.Conventions.Add(new DateEqualsConvention());
       Default.Conventions.Add(new BetweenDatesConvention());
       //
       Default.Conventions.Add(new AwalysTrueCombineConvention());
       Default.Conventions.Add(new OrCombineConvention());
   }

   public ConventionConfiguration() {
       Conventions = new HashSet<IConvention>();
   }
   public HashSet<IConvention> Conventions { get; private set; }

   internal IEnumerable<T> GetConventions<T>() where T: IConvention {
       return Conventions
           .OfType<T>()
           .OrderByDescending(c => c.Order);
   }
}

实现具体 Converntion:

  • ValueTypeEqualsConvention
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public class ValueTypeEqualsConvention : PropertyExpressionConventionBase {
    
        public ValueTypeEqualsConvention():base(1) {}
    
        public override Expression BuildExpression(BuildPropertyExpressionContext context) {
            if (!context.Property.PropertyType.IsValueType) return null;
    
            var queryValue = context.ValueProvider.GetQueryValue(context.Property.Name, context.Property.PropertyType);
            context.ModelState.AddIfValueNotNull(context.Property.Name, queryValue.ModelState);
            context.IsHandled = queryValue.ModelState != null;
    
            if(queryValue.Value == null) return null;
            return context.PropertyExpression.Equal(Expression.Constant(queryValue.Value));
        }
    }
  • StringContainsConvention
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public class StringContainsConvention : PropertyExpressionConventionBase {
    
        public StringContainsConvention():base(10) { }
    
        public override Expression BuildExpression(BuildPropertyExpressionContext context) {
            if (context.Property.PropertyType != typeof(string)) return null;
    
            var queryValue = context.ValueProvider.GetQueryValue(context.Property.Name, context.Property.PropertyType);
            context.ModelState.AddIfValueNotNull(context.Property.Name, queryValue.ModelState);
            context.IsHandled = queryValue.ModelState != null;
    
            if ((queryValue.Value as string).IsNullOrEmpty()) return null;
            return context.PropertyExpression.Call("Contains", Expression.Constant(queryValue.Value));
        }
    }
  • DateEqualsConvention
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public class DateEqualsConvention: PropertyExpressionConventionBase {
        public DateEqualsConvention():base(10) { }
    
        public override System.Linq.Expressions.Expression BuildExpression(BuildPropertyExpressionContext context) {
            if (context.Property.PropertyType.NotIn(typeof(DateTime), typeof(DateTime?))) return null;
            if (!context.Property.Name.EndsWith("day", true, CultureInfo.CurrentCulture) && 
                !context.Property.Name.EndsWith("date", true, CultureInfo.CurrentCulture)) return null;
    
            var queryValue = context.ValueProvider.GetQueryValue(context.Property.Name, typeof(DateTime));
            context.ModelState.AddIfValueNotNull(context.Property.Name, queryValue.ModelState);
            context.IsHandled = queryValue.ModelState != null;
            if (queryValue.Value == null) return null;
    
            var date = ((DateTime)queryValue.Value).Date;
            var expression = context.PropertyExpression;
            if (expression.Type == typeof(DateTime?)) expression = expression.Property("Value");
            return expression.Property("Date").Equal(Expression.Constant(date));
        }
    }
  • AndCombineConvention
    1
    2
    3
    4
    5
    6
    7
    8
    public class AndCombineConvention : IExpressionCombineConvention {
        public int Order { get; set; }
        public System.Linq.Expressions.Expression Combine(IDictionary<string, System.Linq.Expressions.Expression> expressions) {
            if(expressions.Count > 0)
                return expressions.Values.Aggregate((a, e) => a.OrElse(e));
            return null;
        }
    }

特别注意下 DateEqualsConvention,只对名称以 day 或 date 结尾(不区分大小)的 DateTime 或 DateTime?属性进行处理,如 Employee.Birthday、Employee.HireDate。

项目类图

目前实现中主要有以下类和接口:

image image

image image

扩展方法类未列出。

QueryConditionExpressionModelBinder 使用

直接使用

1
2
3
4
public ActionResult Index([QueryConditionBinder]Expression<Func<Employee, bool>> predicate) {
    var employees = repository.Query().Where(predicate);
    return View("Index", employees);
}

或配置后使用

若你有新创建的 Convention,可以在 Global.asax 文件中 MvcApplication.Application_Start 方法中进行加入配置:

1
ConventionConfiguration.Default.Conventions.Add(new YourConvention());

如果默认的 Conversions 不满足你的要示,可以移除后重新增加:

1
2
3
4
ConventionConfiguration.Default.Conventions.Clear();
ConventionConfiguration.Default.Conventions.Add(new ValueTypeEqualsConvention());
ConventionConfiguration.Default.Conventions.Add(new DateEqualsConvention { Order = 1000 });
ConventionConfiguration.Default.Conventions.Add(new YourConvention{ Order = 2000});

因为 Order 属性是可修改的,添加时可以重新指定优先级。

或都你可以给某一个查询单独配置 Convention:

1
2
3
var cfg = new ConventionConfiguration();
cfg.Conventions.Add(new StringContainsConvention());
ModelBinders.Binders.Add(typeof(Expression<Func<Order, bool>>), new QueryConditionExpressionModelBinder(cfg));

这时,就不要再使用 QueryConditionBinderAttribute 了:

1
2
3
4
5
6
7
public class OrdersController : Controller{
    private  OrdersRepository repository = new OrdersRepository();
    public ViewResult Index(Expression<Func<Order, bool>> predicate) {
        var orders = repository.Query().Where(predicate);
        return View(orders);
    }
}

后记

根据你的项目,创建适合的 Convention,相信 QueryConditionExpressionModelBinder 一定会帮你省下很多时间。

本文中代码编写仓促,尚未进行严格测试,使用时请注意。如有 bug 请回复给我,谢谢!

后续还有相关文章,实现禁止对某些属性查询的 Convention,以及复杂条件组合 Convention 等等。

源码下载:MvcQuery2.rar (1733KB,VS2010 MVC3)

在线演示:http://demos.ldp.me/employees

[转载]当jQuery遭遇CoffeeScript的时候——妙,不可言

mikel阅读(998)

[转载][翻译]当jQuery遭遇CoffeeScript的时候——妙,不可言 – filod – 博客园.

原作:How CoffeeScript makes jQuery more fun than ever—— Stefan Buhrmester

翻译:filod

转载声明:请注明原作者、翻译者以及译文链接


译者前言:虽然对ruby不太了解,但是看到CoffeeScript诗一般的代码确实被怔住了,和JQuery之前给我的感觉是如此的相似——都是一个字,美,当JQuery遭遇到CoffeeScript时,会蹦出什么样的火花呢?

当我多年前初次接触jQuery时我感觉我来到了程序员的天堂。它极大简化了DOM操作。函数式编程变得如此容易,尽管更多适合RIA开发的框架近年来在浮现,但是我仍旧无法想象一个没有jQuery的程序人生是多么的罪恶,相信你也有同感~

而来到CoffeeScript的世界,同样的美妙故事再次上演。在写了几行代码后我相信你将不会再想念原生的JavaScript了。CoffeeScript包含了许多新特性,当将它与jQuery结合时,你会发现一片新天地。

本文的目的就在于展示CoffeeScript和jQuery协同工作时美妙场景。

像老板一样指挥你的代码

CoffeeScript提供了一堆酷毙了的数组迭代方法。最好的事莫过于这不仅仅能工作于数组,还能工作于jQuery对象了。来行诗一般的代码吧:

formValues = (elem.value for elem in $('.input'))

这行代码将会被翻译为如下的JavaScript

var elem, formValues;
formValues = (function() {
var _i, _len, _ref, _results;
_ref = $('.input');
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
elem = _ref[_i];
_results.push(elem.value);
}
return _results;
})();

老实说最初这样写代码确实让人提心吊胆的,但是一旦你开始拥抱CoffeeScript的魔法时,你会爱上它的。

飞一般的方法绑定

在jQuery的回调中使用”=>”将会大大减省你手动绑定方法到对象的麻烦。还是来看段代码吧:

object =
func: -> $('#div').click => @element.css color: 'red'

下面是编译输出的JavaScript

var object;
var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
object = {
func: function() {
return $('#div').click(__bind(function() {
return this.element.css({
color: 'red'
});
}, this));
}
};

代码中的@element指向了一个jQuery的对象,该对象是在其他地方指定的——比如object.element = $('#some_div').

任何使用”=>”所指定的回调函数都会自动绑定到原来的对象上,没错,这很酷。

在2011年函数是这样调用的

瞅一眼这个:

$.post(
"/posts/update_title"
new_title: input.val()
id: something
-> alert('done')
'json'
)

使用CoffeeScript,多个参数可以写成多行来调用,逗号和大括弧是可选的,这使得一些jQuery中签名比较长的方法比如$.post()$.animate() 等更加易读。这儿还有一个例子:

$('#thing').animate
width: '+20px'
opacity: '0.5'
2000
'eaSEOutQuad'

很美味的Coffee不是吗?要注意第一个参数是一个匿名的对象,你甚至可以省略调用函数的元括弧。

让初始化来的更性感吧

我最初开始使用jQuery时我是这样做页面初始化的:

$(document).ready(function() {
some();
init();
calls();
})

CoffeeScript和新版的jQuery使得上面的代码进化的如此性感:

$->
some()
init()
calls()

函数定义语法在CoffeeScript里本身已经非常酷了,能在上面这些场合使用使得其更酷了。你会发现所有需要回调的函数调用在CoffeeScript中都是如此简单。

更多关于CoffeeScript请访问其官网

注:已经有一本关于CoffeeScript的书在七月发行了,其中有一整章的内容是关于jQuery的。

[转载]《重构-改善既有代码的设计》读书笔记

mikel阅读(923)

[转载]《重构-改善既有代码的设计》读书笔记 – karoc – 博客园.

坏味道

特征

情况及处理方式

目标

重复代码

1.重复的表达式
2.
不同算法做相同的事
3.
类似代码

同一个类的两个函数有相同表达式

重复代码提取为方法

相同表达式只在一个类的一个方法出现,供其他方法调用

兄弟类含有相同表达式

重复代码提取为方法
提升方法到父类

不相干类含有相同代码

提取为独立类供调用

过长函数

1.代码前面有注释
2.
条件表达式
3.
循环

提取方法

每个方法只做一件事,方法要定义完善、命名准确

过大的类

1.一个类中有太多实例变量
2.
一个类中有太多代码

部分字段之间相关性高

相关的字段和方法提取为类

每个类负责一组具有内在的相互关联的任务

某些字段和方法只被某些实例用到

这些字段和方法移到子类中

过长参数列

1.参数列过长
2.
参数列变化频繁

方法可以通过其他方式获取该参数

让参数接受者自行获取该参数

只需要传给函数足够的、让其可以从中获取自己需要的东西就行了

同一对象的若干属性作为参数

在不使依赖恶化的情况下,使用整个对象作为参数

被调用函数使用了另一个对象的很多属性

将方法移动到该对象中

某些数据缺乏归属对象

首先创建对象

发散式变化

一个类受多种变化的影响

类经常因为不同的原因在不同的方向上发生变化

将特定原因造成的所有变化提取为一个新类

针对某一外界变化的所有修改,只应发生在单一类中,而这个类中所有的内容都应反映此变化

散弹式修改

一种变化引发多个类的修改

某种变化需要在许多不同的类中做出小修改

把所有需要修改的代码放进同一个类中

针对某一外界变化的所有修改,只应发生在单一类中,而这个类中所有的内容都应反映此变化

依恋情结

一个函数使用其他类属性比使用自身类属性还要多

某个函数从另一个对象调用了几乎半打的取值函数

将依恋代码提取为单独方法,移动到另一对象

将数据和对数据的操作行为包装在一起

数据泥团

同时使用的相关数据并未以类的方式组织
1.
两个类中相同的字段
2.
许多函数中相同的参数

先将字段提取为类,再缩减函数签名中的参数

总是绑在一起的数据应该拥有属于它们自己的对象

基本类型偏执

过多使用基本类型

总是被放在一起的基本类型字段

提取类

将单独存在的数据值转换为对象

参数列中有基本类型

提取参数对象

数组中容纳了不同的对象,需要从数组中挑选数据

用对象取代数组

基本数据是类型码

使用类替换类型码

带条件表达式的类型码

使用继承类替换类型码

Switch语句

相同的switch、case语句散布于不同地方

根据类型码进行选择的switch

使用多态替代switch

避免到处做相同的修改

单一函数中有switch

使用显式的方法取代参数

平行继承体系

1.为某个类增加子类时,必须为另一个类增加子类
2.
某个继承体系类名前缀和另一个继承体系类名前缀相同

一个继承体系中的实例引用另一个继承体系中的实例,然后迁移成员

避免到处做相同的修改

冗赘类

类无所事事

父类和子类无太大差别

将它们合为一体

某个类没有做太多事情

将这个类所有成员移到另一个类中,删除它

夸夸其谈未来性

某个抽象类没有太大作用

将父子类合并

不必要的委托

将这个类所有成员移到另一个类中,删除它

函数的某些参数未用上

移除参数

函数名称带有多余的抽象意味

重命名函数名

函数只被测试方法调用

连同测试代码一并删除

令人迷惑的暂时字段

1.某个实例字段仅为某种情况而设
2.
某些实例字段仅为某个函数的复杂算法少传参数而设

提取单独的类,封装相关代码

过度耦合的消息链

一长串的getThis或临时变量

客户类通过一个委托类来取得另一个对象

隐藏委托

消除耦合

中间人

某个类接口有大量的函数都委托给其他类,过度使用委托

有一半的函数

移除中间人

少数几个函数

直接调用

中间人还有其他行为

让委托类继承受托类

狎昵关系

某个类需要了解另一个类的私有成员

子类过分了解超类

将继承改为委托,把子类从继承体系移出

封装

类之间双向关联

去掉不必要的关联

类之间有共同点

提取新类

异曲同工的类

两个函数做同一件事,但是签名不同

合并

不完美的类库

类库函数构造的不够好,又不能修改它们

想修改一两个函数

在调用类增加函数

想添加一大堆额外行为

使用子类或包装类

幼稚的数据类

某个类除了字段,就是字段访问器、设置器

1.用访问器取代public字段
2.
恰当封装集合
3.
移除不需要的设置器
4.
搬移对访问器、设置器调用方法到此类
5.
隐藏访问器、设置器

封装

被拒绝的馈赠

派生类仅使用了基类很少一部分成员函数

子类拒绝继承超类接口

使用委托替代继承

过多的注释

一段代码有着长长的注释

消除各种坏味道