[转载]Asp.net MVC源码分析 -- 获取ModelBinder的优先级

mikel阅读(872)

[转载]Asp.net MVC源码分析 — 获取ModelBinder的优先级 – 十一月的雨 – 博客园.

ASP.NET mvc 框架中我们可以对System.Web.Mvc.Binders 进行扩展我们自定义的binder 类型,但是同时它还有一些其它的方法可以实现自定义的model binder.而且mvc在使用的时候还有一些策略,现分析如下:

获取ModelBinder 对象的入口方法是GetParameterValue, 其中

IModelBinder binder = GetModelBinder(parameterDescriptor);

这一句代码决定了ModelBinder 的使用策略。

System.Web.Mvc.ControllerActionInvoker

01 protected virtual object GetParameterValue(ControllerContext controllerContext, ParameterDescriptor parameterDescriptor) {
02 // collect all of the necessary binding properties
03 Type parameterType = parameterDescriptor.ParameterType;
04 //Hey 请过来获取binder 的入口在这里,csdn 代段没法加高亮,垃极啊~!
05 IModelBinder binder = GetModelBinder(parameterDescriptor);
06 IValueProvider valueProvider = controllerContext.Controller.ValueProvider;
07 string parameterName = parameterDescriptor.BindingInfo.Prefix ?? parameterDescriptor.ParameterName;
08 Predicate<string> propertyFilter = GetPropertyFilter(parameterDescriptor);
09
10 // finally, call into the binder
11 ModelBindingContext bindingContext = new ModelBindingContext() {
12 FallbackToEmptyPrefix = (parameterDescriptor.BindingInfo.Prefix == null), // only fall back if prefix not specified
13 ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, parameterType),
14 ModelName = parameterName,
15 ModelState = controllerContext.Controller.ViewData.ModelState,
16 PropertyFilter = propertyFilter,
17 ValueProvider = valueProvider
18 };
19
20 object result = binder.BindModel(controllerContext, bindingContext);
21 return result ?? parameterDescriptor.DefaultValue;
22 }
23
24 private IModelBinder GetModelBinder(ParameterDescriptor parameterDescriptor) {
25 // look on the parameter itself, then look in the global table
26 return parameterDescriptor.BindingInfo.Binder ?? Binders.GetBinder(parameterDescriptor.ParameterType);
27 }

这里优先从 parameterDescriptor.BindingInfo中得到IModelBinder, 接下来我们看一下怎么从parameterDescriptor中获取binder

首先找到 ReflectedActionDescriptor对象,可以看到在GetParameters方法中生成了ReflectedParameterDescriptor 对象,

System.Web.Mvc.ReflectedActionDescriptor

01 public override ParameterDescriptor[] GetParameters() {
02 ParameterDescriptor[] parameters = LazilyFetchParametersCollection();
03
04 // need to clone array so that user modifications aren't accidentally stored
05 return (ParameterDescriptor[])parameters.Clone();
06 }
07 view plain
08 private ParameterDescriptor[] LazilyFetchParametersCollection() {
09 return DescriptorUtil.LazilyFetchOrCreateDescriptors<ParameterInfo, ParameterDescriptor>(
10 ref _parametersCache /* cacheLocation */,
11 MethodInfo.GetParameters /* initializer */,
12 parameterInfo => new ReflectedParameterDescriptor(parameterInfo, this) /* converter */);
13 }

这个对象中调用了ModelBinders.GetBinderFromAttributes方法来获取binder

ReflectedParameterDescriptor -> ReflectedParameterBindingInfo

1 public override IModelBinder Binder {
2 get {
3 IModelBinder binder = ModelBinders.GetBinderFromAttributes(_parameterInfo,
4 () => String.Format(CultureInfo.CurrentCulture, MvcResources.ReflectedParameterBindingInfo_MultipleConverterAttributes,
5 _parameterInfo.Name, _parameterInfo.Member));
6
7 return binder;
8 }
9 }

System.Web.Mvc.ModelBinders

1 internal static IModelBinder GetBinderFromAttributes(ICustomAttributeProvider element, Func<string>
2 errorMessageAccessor) {
3 CustomModelBinderAttribute[] attrs = (CustomModelBinderAttribute[])element.
4 GetCustomAttributes(typeof(CustomModelBinderAttribute),
5 true /* inherit */);
6
7 return GetBinderFromAttributesImpl(attrs, errorMessageAccessor);
8 }

接下来我们看

return parameterDescriptor.BindingInfo.Binder ?? Binders.GetBinder(parameterDescriptor.ParameterType);

语句后面的的分支.

System.Web.Mvc.ModelBinderDictionary

01 private IModelBinder GetBinder(Type modelType, IModelBinder fallbackBinder) {
02
03 // Try to look up a binder for this type. We use this order of precedence:
04 // 1. Binder returned from provider
05 // 2. Binder registered in the global table
06 // 3. Binder attribute defined on the type
07 // 4. Supplied fallback binder
08
09 IModelBinder binder = _modelBinderProviders.GetBinder(modelType);
10 if (binder != null) {
11 return binder;
12 }
13
14 if (_innerDictionary.TryGetValue(modelType, out binder)) {
15 return binder;
16 }
17
18 binder = ModelBinders.GetBinderFromAttributes(modelType,
19 () => String.Format(CultureInfo.CurrentCulture, MvcResources.ModelBinderDictionary_MultipleAttributes, modelType.FullName));
20
21 return binder ?? fallbackBinder;
22 }
23
24 internal static IModelBinder GetBinderFromAttributes(Type type, Func<string> errorMessageAccessor) {
25 AttributeCollection allAttrs = TypeDescriptorHelper.Get(type).GetAttributes();
26 CustomModelBinderAttribute[] filteredAttrs = allAttrs.OfType<CustomModelBinderAttribute>().ToArray();
27 return GetBinderFromAttributesImpl(filteredAttrs, errorMessageAccessor);
28 }

到这里我们就清楚的知道了获取IModelBinder的优先级。

结论:

1.如果在Action参数中的对象中标记了元数据的IModelBinder 实现时则调用该实现。//注:如果这句不理解可以继续看下面的实例

2.如果在参数中没有标记元数据的IModelBinder 实现 ,则调用的优先顺序为:

// (1).从全局的ModelBinderProviders.BinderProviders 中查找,我们也可以注册自己的provider
// (2).从全局的System.Web.Mvc.Binders 对象中查找,自己也可以注册自己的binder
// (3). 从参数对象的类型中的attribute 找到 ModelBinderAtribute中的IModelBinder 实现.
// (4). 最后如果经过以上步骤都没有找到IModelBinder的实现的话,就用MVC默认的实现DefaultModelBinder类,

1 public virtual IModelBinder GetBinder(Type modelType, bool fallbackToDefault) {
2 if (modelType == null) {
3 throw new ArgumentNullException("modelType");
4 }
5
6 return GetBinder(modelType, (fallbackToDefault) ? DefaultBinder : null);
7 }

示例代码:

下面我们通过实例代码来印证以上的结论。

01 //Model 类:
02 public class FormTestModel
03 {
04 [Required]
05 [DataType(DataType.Text)]
06 [Display(Name = "Room Name")]
07 public string RoomName { get; set; }
08
09 [Required]
10 [DataType(DataType.Text)]
11 [Display(Name = "Room Count")]
12 public string RoomCount { get; set; }
13
14 }
15
16 //Action类:
17 [HttpPost]
18 public ActionResult FormTest(FormTestModels model, string returnUrl)
19 {
20 <span style="white-space:pre">    </span>return view();
21 }
22
23 //我们新建四个IModelBinder 的实现;
24 internal sealed class FormTestModelBinderImpl1 : IModelBinder
25 {
26 public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
27 {
28 DefaultModelBinder defBinder = new DefaultModelBinder();
29 return defBinder.BindModel(controllerContext, bindingContext);
30 }
31 }
32 internal sealed class FormTestModelBinderImpl2 : IModelBinder
33 {
34 public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
35 {
36 DefaultModelBinder defBinder = new DefaultModelBinder();
37 return defBinder.BindModel(controllerContext, bindingContext);
38 }
39 }
40 internal sealed class FormTestModelBinderImpl3 : IModelBinder
41 {
42 public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
43 {
44 DefaultModelBinder defBinder = new DefaultModelBinder();
45 return defBinder.BindModel(controllerContext, bindingContext);
46 }
47 }
48 internal sealed class FormTestModelBinderImpl4 : IModelBinder
49 {
50 public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
51 {
52 DefaultModelBinder defBinder = new DefaultModelBinder();
53 return defBinder.BindModel(controllerContext, bindingContext);
54 }
55 }
56
57 public sealed class FormTestModelBinderProviderImpl : IModelBinderProvider
58 {
59 public IModelBinder GetBinder(Type modelType)
60 {
61 if (modelType == typeof(FormTestModels))
62 {
63 //"provider implementition";
64 return new FormTestModelsModelBinderImpl2();
65 }
66
67 return null;
68 }
69 }
01 然后我们分别加入:
02 //第一优先:
03 [HttpPost]
04 public ActionResult FormTest([ModelBinder(typeof(FormTestModelBinderImpl1))] FormTestModel model, string returnUrl)
05 {
06 return view();
07 }
08
09 //第二优先:
10 ModelBinderProviders.BinderProviders.Add(new FormTestModelBinderProviderImpl2());
11 //第三优先:
12 System.Web.Mvc.ModelBinders.Binders[typeof(FormTestModel)] = new FormTestModelBinderImpl3();
1 //第四优先:
2 [ModelBinder(typeof(FormTestModelBinderImpl4))]
3 public class FormTestModel
4 {
5 //codes
6 }

最后我们在四个binder上面设置断点,看一下代码执行的情况,就可以清楚的看到我们的结论是多么的无比正确.:)

最后感谢大家观看,以上分析如有雷同纯属巧合。

谢谢大家。

后记:

其它的相关文章:

MVC运行机制之源码剖析http://blog.csdn.net/study_live/article/details/4871745

[转载]分享.NET ERP项目开发中应用到的重量级工具 选择合适的工具和资源,做项目效率高而且规范程度高

mikel阅读(762)

[转载]分享.NET ERP项目开发中应用到的重量级工具 选择合适的工具和资源,做项目效率高而且规范程度高 – James Li – 博客园.

去年年初,我写过三篇文章,来讲解自己在.NET项目开发中用到的工具和资源。这三篇文章的地址是

《做一个项目,平时都用到哪些工具提高效率(上)》

《做一个项目,平时都用到哪些工具提高效率(中)》

《做一个项目,平时都用到哪些工具提高效率(下)》

经 过了近两年的发展和学习,技术方面有了长足的进步。而现在再来看那些工具和资源,自认为那时还处于工具系列的初级阶段,技术含量不高,也没有形成一个体 系。这两天在整理自己的.NET通用开发平台和框架,认为很有必要更新一下自己的工具系列,作为一个负责任的作者,所以有了这一篇文章,希望能给你带来帮 助。

这两年我一直从事WinForms的开发,所以没有关注到Web开发的工具。Web方面,一直想集成一个Web IIS到自己的WinForms程序中,这样做演示的时候,可以不用打开和配置IIS,也能测试HTTP方面的内容。对Web项目中的数据抓取也有一些经 验和体会,可以参考我的知识管理系统Data Solution。

代码生成 Code Smith

折 腾了很多代码生成器,自己也用C#+WinForms写过代码生成器,最后学乖了,做代码生成的工具,只选Code Smith,与代码生成相关的程序,只选择CodeDom。Code Smith有成熟的技术,数量庞大的模板库资源;而CdoeDom技术,一套源代码,可以同时生成C#和VB.NET两套代码。这是我选择它们的主要理 由。

选择Code Smith的一个主要原因是,不仅仅Code Smith Studio做的稳定,强大,而且它支持以API的方式调用模板引擎,这种强大的可编程性,远远的把第三方的工具抛在后面。如下面的代码所示

CodeTemplateCompiler compiler = new CodeTemplateCompiler("..\\..\\StoredProcedures.cst");
compiler.Compile();

if (compiler.Errors.Count == 0)
{
   CodeTemplate template = compiler.CreateInstance();

   DatabaseSchema database = new DatabaseSchema(new SqlSchemaProvider(), @"Data Source=.\SQLEXPRESS;
       AttachDbFilename=PetShop.mdf;Integrated Security=True;Connect Timeout=30;User Instance=True;");
   TableSchema table = database.Tables["Inventory"];

   template.SetProperty("SourceTable", table);
   template.SetProperty("IncludeDrop", false);
   template.SetProperty("InsertPrefix", "Insert");

   template.Render(Console.Out);
}
else
 {
    for (int i = 0; i < compiler.Errors.Count; i++)
    {
    Console.Error.WriteLine(compiler.Errors[i].ToString());
    }
}

在Code Smith Studio中制作模板,运行和调试模板。再以编程的方式把这个模板集成到自己的开发工具系列中。这项技术,我曾经写过一个Smith Builder,ERP代码生成器,它以集成的方式,把所有的代码生成的工具封装到一个环境。

image

2002年,有一篇文章《每个.NET开发人员应该下载的10种开发工具》中就提到它的名字,可见Code Smith在做代码生成方面是非常有经验的。Code Smith 6.0 已经于今年9月释放出来,但不如5.x系列的稳定。从5.3开始,Code Smith支持.NET 3.0/3.5的C#脚本代码,意味着可以应用Linq,C# 3.5的技术特性。

版本比较,合并 Beyond Compare

一个项目,通常会有多个客户同时在使用。一般建立的项目结构是,一个Development版,这是开发人员工作的版本,所有新特性,新功能,都会 加在这个版本中,这也导致了这是bug最多的一个版本;同时,在特定的时间阶段,建立一个Build版本,比如2012年,公司会把ERP项目升级到 4.0,于是所有的客户都会用到这个4.0版本。开发人员做好的程序功能,或是修改过的bug,都是建立在Development版本之上,要发送给客户 的是Build版本,于是要把Development版中的功能或是bug fixed添加到Build版本中。你不能直接把Development版本的代码复制到Build版中,这样会导致客户使用的版本非常不稳定。 Visual Studio Team Foundation创建了changset这个概念,表示对源代码的一个变更。

有一个常见的情景是,客户在Build版本中发现了问题,需要做出修复。程序员修改好Development版本,程序管理员负责把程序员修改好的 Development版本的代码,合并(Merge)到Build版本中。Visual Studio Team Foundation有它的文件比较工具,但是也推荐Beyond Compare这个工具,作为默认的比较合并工具,可见它的功能非常强大。

image

左边是Development开发版的文件,右边是Build版。用这个工具,马上就看到了程序员在开发版中做出的修改,点击差异片段左上方的小箭头,代码就被复制到右边。这样就完成了把开发版的功能合并到客户构建版本中。

还有另一种情形,经常忘记了合并开发版本的代码,随着时间的累计,Development版和Build版的差异越来越大,合并起来也越来越困难。 比如,客户那边的程序有问题,但是开发版没有问题,表示代码没有合并到Build版中。这个问题,一直是困扰着我们的团队,用肉眼去识别哪些代码是相同 的,哪些是不同的,几乎是不可能。这个工具就是很好的帮手。

对象关系映射 LLBL Gen

在以前的技术选型中,我会看一下,ORM是否合适大型的项目这类型的文章。可是找到的大部分文章都是批判ORM的效率问题。经过这两年的理解和消化,如果要做大型的项目,我一定会向你推荐ORM,也就是这里提到的LLBL Gen。

image

LLBL Gen 从3.x起,支持NHibernate,也就是说一个LLBL Gen项目文件,可以同时生成LLBL Gen Runtime的项目,也可以生成NHibernate的项目。它内置了基于模板的代码生成器,你可以定制所有的模板与代码生成的结果。

比如,为了能在Visual Studio中,当鼠标悬停在一个实体或字段名上时,可以显示它的源数据库对象。

当鼠标悬停在实体名上时,可以显示实体对应的表名

clip_image015_thumb2

当鼠标悬停在字段上时,可以显示字段对应的表名

clip_image016_thumb1

你需要修改它的模板。打开Template Binding Viewers,按照如下的内容编辑模板

  1. entityIncludeAdatper.template #549行 增加
    ///Mapped on <[ CaseCamel TargetType ]> field: <[SourceObjectName]>.<[SourceColumnName]></summary>
  2. entityAdapter.template #31 增加
    ///Mapped on table: <[ElementTargetObjectName]></summary>

这样对它的模板定制,就达到了上面的效果。

另一个可以做出的定制是,为项目生成一个partial类。比如GBEMP员工表,对应于实体类EmployeeEntity,这是代码生成器自动生成的,我还需要创建一个Entity.Implmentation.cs文件,用于存放我加的自定义逻辑。你需要这样做

image

点击右边的Add Tasks,在SD.Task.Base中添加一个CreateAFolder的任务,参数名字为EntityImplementation.cs,再添 加模板EntityImpemention.template,用Template Binding Viewers绑定到模板项中。看起来的效果是这样,添加模板ID与模板文件名的射影,如果模板不存在,它会提示你创建它。

image

虽然这么几句话,几个图片,就讲解完了我的思路和方法。我也没有夸奖它是如何的好,在这里仅仅以实例的方式,展示我理解的工具,以及我可以做的定制 工作。微软的.NET Framework API几乎处处都考虑到了Extension,或是Customization,我这里给你推荐的LLBL Gen 作为ERP项目开发中的ORM框架,也是基于它的扩展性,灵活性。

好吧,总结一下,要做NHibernate开发,可以选择LLBL Gen工具作为代码生成器。如果已经是LLBL Gen 2.x的老用户,升级到新的3.x版本也很容易。
抱怨一下,3.0版本的LLBL Gen不稳定,EntityCollection在做设计时数据绑定时,经过提示Unable to cast object of type “Foundation.EntityCollection” to type “SD.LLBLGen.Pro.ORMSupportClasses.IEntityCollection2”, 我发誓我什么也没有改,你就让我无法绑定数据。升级到3.1后,这个问题就没有再发生过。看来,自己也给人家当了一回小白鼠,也不是老外的产品就一定没有 bug,没有问题(issue)。要是被你发现了bug/issue,及时主动的给人家report issue,会是一个不小的鼓励。我自己在CodeProject看到文章后,发现了代码或是配置有问题,会及时留言,如果急,就给作者写email,通 常一两天就回复了。一个是Russian,另一位是我这个Chinese,用英语(in English)交流技术细节,这时你就会感觉语言还真是个用于交流的好东西。

日志 跟踪 Log Trace

好吧,我承认你手头有很多很厉害的log/trace工具。著名的log4net, Enterpise Libaray的Log Application Block,还有大量的自己写的log/trace 工具库。可是,我还是不忍心把这个东西藏在硬盘里,不让你知道,这就是著名的TraceTool。CodeProject中有一篇文章,标题是 《TraceTool 12.4: The Swiss-Army Knife of Trace》,你知道瑞士军刀很实用,是的,这里的Trace Tool也很实用。这个用Delphi写的实用工具,提供了任意编程语言的接口。

你可以在CodeProject的那篇文章中找到所有编程语言的接口例子,细节方面做的非常好。

image

在这个跟踪工具中看到的内容,是我下面的这几句代码所发射出来的

TTrace.Error.Send("Hello world");
TTrace.Warning.Send("Hello", "world");

TraceNode Hello = TTrace.Debug.Send("Hello"); // keep node reference

Hello.Send("world");           // use node to send sub trace

// or in one line :
TTrace.Debug.Send("Hello").Send("world");

不写log文件,也不写数据库。但是这个功能在跟踪你的程序运行流程方面,有很大的帮助。客户的服务器是不可以装上Debugger来给你发现问题的,除了报告异常的堆栈外,通过这个程序,把堆栈中的变量值输出来,及时查看。

为了可以输出当前的堆栈到trace窗口中,你可能需要这样的代码片段用于追踪call stack。

public static class Logger
{ 
    public static string CurrentStackDefaultLog()
    {
        // the true value is used to include source file info
        var l_CurrentStack = new System.Diagnostics.StackTrace(true);
        return l_CurrentStack.ToString();
    }

}

再深入到细节的地方,你可以参考CodeProject上的这篇《How to log the current call stack in .NET》。

我看中TraceTool的两点是,一是提供了图形化的界面可以看输出结果,二是支持任意的编程语言,连JavaScript都可以写日专输出,像这样的代码片段,用JavaScript写的trace代码。

var TTrace = new ActiveXObject("TraceToolCom.XTrace"); 
TTrace.Debug.Send("hello from jScript") ;

最后一点,它完全是free的,你不用到处找crack或是license key。即使你发现有问题了,下载它的源代码,跟踪进去看一下什么问题,也可以马上解决。

虚拟机 VMware Workstation

为了能建立一个真实的测试环境,我尝试过许多方法。在公司可以单独安装一台开发服务器,用于测试编译后的程序。在家里,通过笔记本,使用远程桌面, 连接到台式机里面,进行测试。但是我要改一下设置,结果要在公司的测试服务器和自己家里的测试主机中,同时更改,总觉得有些不方便,于是就有这个虚拟机软 件,构建一个尽可能真实的测试环境。

image

你可以也会选择Microsoft Virtual PC,或是Oracle Virtual Box,或者Windows Server 2008内置的虚拟机。都行,只要达到目的,尽可能建立一个测试环境就可以了。这里我有一些经验与你分享。

  1. 拒绝在虚拟机中安装Visual Studio,这是必要的。真实的环境,真的没有Visual Studio和它的Debugger。
  2. 同时准备两个虚拟机,一个x86,一个是x64的。这时,你就会发现Visual Studio中的Target=AnyCPU的作用。Visual C++中的Win32项目,只能在x86中跑。MSVCR.dll的含义是Microsoft Visual C++ Relase,表示是Release版本的动态连接库(在.NET中叫程序集Assembly),如果你的项目是用Debug方式编译的,它在这里是无法 运行的,会提示的找不到动态连接库,Debug方式编译的要找MSVCD.dll。在制作Smith Builder的过程中,因为Code Smith的Target是x86的,所以,我要引用到它的程序集CodeSmith.Engine.dll,则我的项目的Target必须设置为 x86,否则会提示bad image format。

Data Loader编译的Target是AnyCPU,同时我也提供它的x86版本,代码是这样写的

public class Program
{
    // Methods
    private static void Main(string[] args)
    {
        string str = Path.Combine(Application.StartupPath, "DataLoader.exe");
        Console.WriteLine("Trying to load and execute: {0} under x86", str);
        MethodInfo method = Assembly.LoadFile(str).GetType("Gui.Startup").GetMethod("Main", BindingFlags.NonPublic | BindingFlags.Static);
        object[] parameters = new object[] { args };
        method.Invoke(null, parameters);
    }
}

这个控制台项目的Target为x86,它反射DataLoader的启动窗体,并且调用它。以这种方式实现AnyCPU的程序以x86的方式运行。

3.  虚拟机只装操作系统的基本组件,再加上各种开发工具包的Runtime。如下面的这张图所示

image

这些基础的Runtime,都应该好好收藏起来,放在硬盘的角落里。

4. 如果你的硬盘空间充足,尽可能的准备x86和x64两种OS环境,两种Runtime,以充分测试。看下面的这张图片

image

为了测试到x64与x86中的问题,我把Visual C++的两种Runtime都下载到了硬盘中。这是一个细节方面的处理,也是为了能在交付程序之前,发现更多的问题。

5. .NET平台不是x86和x64的问题,而是旧的.NET程序集引用新版本的.NET程序集的问题。像下面这个图所示的,.NET 2.0的项目,引用了.NET 3.5的类型库,会抛出这个异常

image

项目的Target是.NET2.0,但是被引用的程序集编译是的Target是.NET 4.0,会出现上面的异常。强制引用的结果是,代码根本无法引有到程序集中定义的类型,编译出错。

为了看清楚一个程序集编译是依赖的.NET版本,你需要用Reflector 7打开这个程序集,查看References中的System.Core的版本,如下图所示,它是4.0,则表示要引用这个程序集的项目,它的Target至少是.NET 4.0。

image

我以为,把编译程序集的Target设为.NET 2.0是个好的选择,除非你真的需要应用.NET 3.5的特性。

在升级Workflow项目时,把.NET 3.5的工作流项目的Target改成.NET 4.0后,出现了很多未知的问题,改回去为.NET 3.5后,问题没了。在网上查到.NET 3.5的工作流和.NET 4.0的工作流,要当成完全不同的产品来对待。真遗憾,我肯定不会考虑升级工作流技术了,这样带来的问题和麻烦,远远超过你将要得到的好处。

这个分享工具系列的文章,差不多就介绍完了。在后继的文章中,我会介绍自己开发的工具,所做的的扩展Extension,以及最终把这些扩展和工具集,集成到一个环境中,做成一个ERP开发工具和框架平台,敬请期待。

推荐Firefox截图插件

mikel阅读(1088)

我们经常想把精彩的网页内容做截图。或者在制作某些教程的时候也需要截图。通常情况下你或许会选择SnagIT之类的截图工具。
在这里特别推荐一款Firefox浏览器的截图插件。功能非常强大,使用非常方便。忍不住要推荐给大家。

用Firefox打开下面的链接进行安装 http://screenshot-program.com/fireshot/

[转载]7个让你的页面布局成为最大败笔的设计错误

mikel阅读(1046)

[转载]7个让你的页面布局成为最大败笔的设计错误 – gbin1 – 博客园.

7个让你的页面布局成为最大败笔的设计错误

即 使web设计已经成为一个真正的产业,开发一个网站仍旧是技术和艺术的结合。一个网站的设计即可以带来大量流量,同时也可以吓走大量用户。当然这些都和设 计师相关。经验,天份和能力三方面的努力是一个设计师最强大的工具,一个设计精良的布局是所有这些的基础。而且,这也是一个非常耗时的工作。

设计一个非常好的布局是一件非常不容易的事情,而且判断的方式也非常的主观。任何一个设计可能会取悦你,也可能让其它人厌恶。尽管这样,这里仍旧有些大家都承认的设计误区。

为了帮助大家来避免这些错误和误区,我们列出了一些设计的小技巧。我们同时也希望大家能够帮助我们完善这些小技巧。不管怎么说,希望大家能帮助我在下面留言 ,带给大家更多的想法和建议。谢谢!

1. 缺乏空白的区域

没 有比把页面塞得像麻袋一样满更让人烦人的设计了。你应该保留一定得空白页面让访问者放松他们的眼睛。很多设计者都想提供更多的内容在第一个页面。感觉初学 设计的人更容易犯这个错误,但其实各个设计水平的人都有这个倾向。因为设计界错误心态这个错误大家犯得比较多。设计者太依赖于布局而不敢尝试其它的方式。 正确的心态是忽略掉任何的规则,创建一个你认为对于用户来说是正确的布局。最简单的因素绝对一切。

2. 非常糟糕的颜色组合

这里有很多的糟糕的布局形式,但是糟糕的颜色搭配绝对是最吓人的。一个很常见的错误就是使用高对比颜色创建一个富有视觉张力的设计。这里有不同的来自学校的观点。我们应该学会如何区别对待。

很 多的对比设计都希望通过一种颜色来通过色彩和饱和度重点突出最重要的部分。这里有一个小花招非常不错:降低你的文件饱和度然后如果各个元素之间的区别非常 清晰的话,那就足够了。这个逻辑非常简单:视觉没有问题的人会看的非常清晰,然而有视觉问题的人将看到布局黑色,白色或者灰色。现在简单了,如果他们没有 困难查看布局颜色,那么布局就通过了色彩可用性测试。

3. 一个页面里包含的内容太多

听起来非常的简单但是布局可以拥有很多的空白区域,但是同时也可以会包含太多信息。通常人们都是先扫上几眼页面布局,因此设计人员应该让整个布局可被快速浏览。一个拥有信息太多的网站,例如,插入太多的图标,按钮和图片将会非常难以浏览。因此会让用户的易用度大打折扣。

4. 图片的使用没有任何灵感

一 个图片可能抵得过成百的文字,但是如果这些文字都是批评你的话,你的布局设计就有问题了。书写的内容,布局和图片应该组合使用,并不是分离的实体。任何 web设计人员都应该理解创意并且有效的实现。图片应该能有效的说明网站需要表达的信息及其选择,这是一个设计时最初需要考虑的因素。

5. 忽略了字体排版

因 为数量多,或者非常小就忽略了字体排版是一个非常大的错误。如果说一个简单演讲是网站的其中一个特殊目地的话,字体排版就是一个关键的因素。另外一个问题 是大家如何理解字体排版,它不是一个简单的如何选择字体的过程,它绝对了句子,段落及其标题的组织方式。你可能想这里需要做的工作太多了,但是一个富有成 效的设计将会对于页面布局具有非常绝对性的冲击。

6. 没有照顾所有人的使用感受和可用性

可用性可能被很多人认为是一个 多余的事情但是一个可用性差的网站是一个自私设计者的作品。在线的世界对于任何人都是平等的,无论你是正常还是残疾,如果你的设计不能让每个人都享受那么 将会是一件很不尊重人的事情。美国有一项法规规定:任何的机构和部门的网站都必须通过可用性的测试。实际上可用性的使用并不是个高科技的东西。例如,你只 需要简单的加上alt标签属性,这并不会带来任何麻烦。个人来说我认为这里用不着什么法律。道德和道义上我们应该自觉地将这个考虑到设计中去。

7. 没有事先思考

缺 乏经验使得我们不停的修改自己的东西。 滑稽的是,我们可能晚上设计的东西白天可能会被删除。一个聪明的设计者会在最开始的时候思考整个布局的大概设计。这种方式可以省去很多时间和资源。很明 显,在实际做之前思考是一件很不容易的事情,不可能不去修改任何设计,但是至少能够帮助我们尽量少的去修改我们的设计。不用担心,经验可以帮助我们做到这 一点。

阅读这个文章可能像理解一个布局一样困难,但是这样更加容易让大家避免错误,请让我们知道是否这篇文章帮助了你,谢谢!

[转载]正则表达式方自动小偷抓网程序

mikel阅读(930)

[转载]正则方式的自动小偷抓网程序 – 孟兆斌 – 博客园.

公司里面有许多数据没人去录入,做一个抓取网页的程序,以前做CMS系统的时候涉及过,不过这次的处理HTML上和以前做了些区别。有一些瑕疵没时间细化,只是实现了效果,大家看一看这个正则该怎么写好:

URL:http://news.szhome.com/83642.html

内容:

object></div></div>

</div>

<div class=”share”><div class=”linkshare” style=”right: 0″>

这两个标记之间的代码。END标记的问题解决了,但是郁闷的是START截取的标记因为第二个DIV和第三个中间有换行,我无语了不知道怎么处理这个正则。

而且郁闷的是有多处有这种重复型标记,对正则不太熟悉,我的解决方法如下:

MatchCollection mc = Regex.Matches(ghoPage.Trim(), @”(?<=<div class=[‘””]txtmsg[‘””]>)[\s\S]*?(?=<div class=[‘””]share[‘””]><div class=), RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);

foreach (Match mm in mc)
{
sb.Append(mc[0].Value.Substring(1933, mc[0].Value.Length – 1933));
}

我算出截取出来多处两个FLASH广告DIV的长度是1933,然后处理字符串截取后得到我想要的文本,这样做的劣势就是万一该站改变了两个FLASH广告DIV的长度我获取的数据就不是完整的呢,有兴趣的研究下,看看换行的DIV正则问题怎么处理。

里面用到了自己写的一个BUTTON控件,可以在点击后禁止重复点击,然后就是一些判断,在思路上蛮不错,可以做到一直抓取,因为不经常用就没做成 WINDOWS服务类型,这样的程序可以做成WINDOWS服务,把规则写在INI文件中,抓录的规则和正则也放在配置文件中,这样就能实现自动抓录。

很短的代码,对这种抓录有兴趣的朋友可以尝试下。下载

[转载]获取本地网络中可用的SQL Server实例信息

mikel阅读(842)

[转载]获取本地网络中可用的SQL Server实例信息 – 韩锁 – 博客园.

.NET中提供了一个SQLDataSourceEnumerator类,使用该类可以很方便的获取本地网络中的所有的可用的SQL Server实例的详细信息。该类位于System.Data.SQL命名空间下。

SqlDataSourceEnumerator类并没用提供具体的构造函数,但它提供了一个公共/静态属性——Instance,用于检索SqlDataSourceEnumerator类的实例。检索到其实例之后,便可调用其GetDataSources方法,该方法返回包含本地网络中所有可用服务器信息的DataTable。

使用GetDataSources方法所返回的DataTable包含以下列,并且所有列的值均为string类型。

说明
ServerName 服务器的名称。
InstanceName 服务器实例的名称。 如果服务器作为默认实例运行,则为空白。
IsClustered 指示服务器是否属于群集。
Version 服务器的版本(对于 SQL Server 2000,为 8.00.x,对于 SQL Server 2005,为 9.00.x,对于SQL Server 2008,为10.0.0.x)。

示例:

using System;
using System.Data.Sql;
using System.IO;

class Program
{
    static void Main()
    {
        SqlDataSourceEnumerator instance = SqlDataSourceEnumerator.Instance;
        System.Data.DataTable table = instance.GetDataSources();

        DisplayData(table);

        Console.WriteLine("按任意键继续");
        Console.ReadKey();
    }

    private static void DisplayData(System.Data.DataTable table)
    {
        foreach (System.Data.DataRow row in table.Select("Version LIKE '10%'"))
        {
            foreach (System.Data.DataColumn col in table.Columns)
            {
                Console.WriteLine("{0} = {1}", col.ColumnName, row[col]);
            }
            Console.WriteLine("============================");
        }
    }
}

上述示例显示本地网络中所有可用的SQL Server 2008版本实例详细信息。代码中使用了DataTableSelect方法对SQL Server服务器的版本作了筛选。

值得一说的是,利用上述代码获取数据库服务器信息并非“十全十美”的。其一,根据网络通信量/通信超时等因素,所获取的数据库服务器实例有可能 不完整;其二,取决于获取列表的方式,所获取到的实例信息的详细程度存在差异,通过SQL Server Browser服务列出的数据库服务器比通过Windows基础结构列出的服务器更加详细,后者仅列出服务器名称。所以在用到该功能时,建议开启SQL Server Browser服务

[转载]SQL SERVER 数据类型详解

mikel阅读(857)

[转载]SQL SERVER 数据类型详解__教程.

数据类型
类型
描             述
bit
整型
bit 数据类型是整型,其值只能是0、1或空值。这种数据类型用于存储只有两种可能值的数据,如Yes 或No、True 或Fa lse 、On 或Off
int
整型
int 数据类型可以存储从- 231(-2147483648)到231 (2147483 647)之间的整数。存储到数据库的几乎所有数值型的数据都可以用这种数据类型。这种数据类型在数据库里占用4个字节
smallint
整型
smallint 数据类型可以存储从- 215(-32768)到215(32767)之间的整数。这种数据类型对存储一些常限定在特定范围内的数值型数据非常有用。这种数据类型在数据库里占用2 字节空间
tinyint
整型
tinyint 数据类型能存储从0到255 之间的整数。它在你只打算存储有限数目的数值时很有用。 这种数据类型在数据库中占用1 个字节
numeric
精确数值型
numeric数据类型与decimal 型相同
decimal
精确数值型
decimal 数据类型能用来存储从-1038-1到1038-1的固定精度和范围的数值型数据。使用这种数据类型时,必须指定范围和精度。 范围是小数点左右所能存储的数字的总位数。精度是小数点右边存储的数字的位数
money
货币型
money 数据类型用来表示钱和货币值。这种数据类型能存储从-9220亿到9220 亿之间的数据,精确到货币单位的万分之一
smallmoney
货币型
smallmoney 数据类型用来表示钱和货币值。这种数据类型能存储从-214748.3648 到214748.3647 之间的数据,精确到货币单位的万分之一
float
近似数值型
float 数据类型是一种近似数值类型,供浮点数使用。说浮点数是近似的,是因为在其范围内不是所有的数都能精确表示。浮点数可以是从-1.79E+308到1.79E+308 之间的任意数
real
近似数值型
real 数据类型像浮点数一样,是近似数值类型。它可以表示数值在-3.40E+38到3.40E+38之间的浮点数
datetime
日期时间型
datetime数据类型用来表示日期和时间。这种数据类型存储从1753年1月1日到9999年12月3 1日间所有的日期和时间数据, 精确到三百分之一秒或3.33毫秒
Smalldatetime
日期时间型
smalldatetime 数据类型用来表示从1900年1月1日到2079年6月6日间的日期和时间,精确到一分钟
cursor
特殊数据型
cursor 数据类型是一种特殊的数据类型,它包含一个对游标的引用。这种数据类型用在存储过程中,而且创建表时不能用
timestamp
特殊数据型
timestamp 数据类型是一种特殊的数据类型,用来创建一个数据库范围内的唯一数码。 一个表中只能有一个timestamp列。每次插入或修改一行时,timestamp列的值都会改变。尽管它的名字中有“time”, 但timestamp列不是人们可识别的日期。在一个数据库里,timestamp值是唯一的
Uniqueidentifier
特殊数据型
Uniqueidentifier数据类型用来存储一个全局唯一标识符,即GUID。GUID确实是全局唯一的。这个数几乎没有机会在另一个系统中被重建。可以使用NEWID 函数或转换一个字符串为唯一标识符来初始化具有唯一标识符的列
char
字符型
char数据类型用来存储指定长度的定长非统一编码型的数据。当定义一列为此类型时,你必须指定列长。当你总能知道要存储的数据的长度时,此数据 类型很有用。例如,当你按邮政编码加4个字符格式来存储数据时,你知道总要用到10个字符。此数据类型的列宽最大为8000 个字符
varchar
字符型
varchar数据类型,同char类型一样,用来存储非统一编码型字符数据。与char 型不一样,此数据类型为变长。当定义一列为该数据类型时,你要指定该列的最大长度。 它与char数据类型最大的区别是,存储的长度不是列长,而是数据的长度
text
字符型
text 数据类型用来存储大量的非统一编码型字符数据。这种数据类型最多可以有231-1或20亿个字符
nchar
统一编码字符型
nchar 数据类型用来存储定长统一编码字符型数据。统一编码用双字节结构来存储每个字符,而不是用单字节(普通文本中的情况)。它允许大量的扩展字符。此数据类型能存储4000种字符,使用的字节空间上增加了一倍
nvarchar
统一编码字符型
nvarchar 数据类型用作变长的统一编码字符型数据。此数据类型能存储4000种字符,使用的字节空间增加了一倍
ntext
统一编码字符型
ntext 数据类型用来存储大量的统一编码字符型数据。这种数据类型能存储230 -1或将近10亿个字符,且使用的字节空间增加了一倍
binary
二进制数据类型
binary数据类型用来存储可达8000 字节长的定长的二进制数据。当输入表的内容接近相同的长度时,你应该使用这种数据类型
varbinary
二进制数据类型
varbinary 数据类型用来存储可达8000 字节长的变长的二进制数据。当输入表的内容大小可变时,你应该使用这种数据类型
image
二进制数据类型
image 数据类型用来存储变长的二进制数据,最大可达231-1或大约20亿字节

[转载]C#基于HttpWebRequest和HttpWebResponse的HttpHelper

mikel阅读(779)

[转载][C#]基于HttpWebRequest和HttpWebResponse的HttpHelper – 二杠 – 博客园.

C#中实现POST的方法很多,常用的是WebBrowser、WebClient、HttpWebRequest和HttpWebResponse。

1、WebBrowser基本是在DocumentCompleted中分析HtmlDocument ;

2、WebClient是对HttpWebRequest和HttpWebResponse的封装,用起来更方便,但是灵活性还是不及HttpWebRequest和HttpWebResponse;

3、HttpWebRequest和HttpWebResponse更底层,灵活度更好,不过代码更多,我做了一个简单的封装,将GET改成分段读取,并加入代理、进度条和错误重试处理。代码如下:

using System;
using System.IO;
using System.Net;
using System.Text;

namespace Van.Base
{
public class HttpHelper
{
#region 委托 事件
public delegate void dgtProgValueChanged(long Value);
/// <summary>
/// 进度改变事件
/// </summary>
public event dgtProgValueChanged OnProgValueChanged;
#endregion

#region 属性
/// <summary>
/// 代理
/// </summary>
public WebProxy Proxy { getset; }
/// <summary>
/// Cookie
/// </summary>
public CookieContainer UserCookie { getset; }
/// <summary>
/// 重试次数
/// </summary>
public int IAfreshTime { getset; }
/// <summary>
/// 错误次数
/// </summary>
public int IErrorTime { getprivate set; }

long m_ProgValue = 0;
/// <summary>
/// 当前读取字节
/// </summary>
public long ProgValue
{
getreturn m_ProgValue; }
private set
{
m_ProgValue = value;
if (OnProgValueChanged != null)
{
OnProgValueChanged(value);
}
}
}
/// <summary>
/// 待读取最大字节
/// </summary>
public long ProgMaximum { getprivate set; }

#endregion

#region 方法
#region Get
/// <summary>
/// 获取HTML
/// </summary>
/// <param name=”URL”>地址</param>
/// <param name=”Accept”>Accept请求头</param>
/// <returns>Html代码</returns>
public string GetHTML(string URL, string Accept)
{
return GetHTML(URL, Accept, System.Text.Encoding.UTF8);
}
/// <summary>
/// 获取HTML
/// </summary>
/// <param name=”URL”>地址</param>
/// <param name=”Accept”>Accept请求头</param>
/// <param name=”encoding”>字符编码</param>
/// <returns>Html代码</returns>
public string GetHTML(string URL, string Accept, Encoding encoding)
{
return GetHTML(URL, Accept, encoding, 1024);
}
/// <summary>
/// 获取HTML
/// </summary>
/// <param name=”URL”>地址</param>
/// <param name=”Accept”>Accept请求头</param>
/// <param name=”encoding”>字符编码</param>
/// <param name=”bufflen”>数据包大小</param>
/// <returns>Html代码</returns>
public string GetHTML(string URL, string Accept, Encoding encoding, int bufflen)
{
IErrorTime = 0;
return _GetHTML(URL, Accept, encoding, bufflen);
}
/// <summary>
/// 获取HTML
/// </summary>
/// <param name=”URL”>地址</param>
/// <param name=”Accept”>Accept请求头</param>
/// <param name=”encoding”>字符编码</param>
/// <param name=”bufflen”>数据包大小</param>
/// <returns>Html代码</returns>
private string _GetHTML(string URL, string Accept, Encoding encoding,int bufflen)
{
try
{
HttpWebRequest MyRequest = (HttpWebRequest)HttpWebRequest.Create(URL);
MyRequest.Proxy = Proxy;
MyRequest.Accept = Accept;
if (UserCookie == null)
{
UserCookie = new CookieContainer();
}
MyRequest.CookieContainer = UserCookie;
HttpWebResponse MyResponse = (HttpWebResponse)MyRequest.GetResponse();
return _GetHTML(MyResponse, encoding, bufflen);
}
catch (Exception erro)
{
if (erro.Message.Contains(连接) && IAfreshTime – IErrorTime > 0)
{
IErrorTime++;
return _GetHTML(URL, Accept, encoding, bufflen);
}
throw;
}
}
/// <summary>
/// 获取HTML
/// </summary>
/// <param name=”MyResponse”></param>
/// <param name=”encoding”>字符编码</param>
/// <param name=”bufflen”>数据包大小</param>
/// <returns></returns>
private string _GetHTML(HttpWebResponse MyResponse, Encoding encoding, int bufflen)
{
using (Stream MyStream = MyResponse.GetResponseStream())
{
using (StreamReader reader = new StreamReader(MyStream, encoding))
{
ProgMaximum = MyResponse.ContentLength;
string result = null;
long totalDownloadedByte = 0;
byte[] by = new byte[bufflen];
int osize = MyStream.Read(by, 0, by.Length);
while (osize > 0)
{
totalDownloadedByte = osize + totalDownloadedByte;
result += encoding.GetString(by, 0, osize);
ProgValue = totalDownloadedByte;
osize = MyStream.Read(by, 0, by.Length);
}
reader.Close();
return result;
}
}
}
#endregion

#region GetImg

public System.Drawing.Bitmap Getimg(string URL, string Accept)
{
return _GetBit(URL, Accept);
}
/// <summary>
/// 获取HTML
/// </summary>
/// <param name=”URL”>地址</param>
/// <param name=”Accept”>Accept请求头</param>
/// <returns>Html代码</returns>
private System.Drawing.Bitmap _GetBit(string URL, string Accept)
{
HttpWebRequest MyRequest = (HttpWebRequest)HttpWebRequest.Create(URL);
MyRequest.Proxy = Proxy;
MyRequest.Accept = Accept;
if (UserCookie == null)
{
UserCookie = new CookieContainer();
}
MyRequest.CookieContainer = UserCookie;
HttpWebResponse MyResponse = (HttpWebResponse)MyRequest.GetResponse();
return _GetBit(MyResponse);
}

/// <summary>
/// 获取图像
/// </summary>
/// <param name=”MyResponse”></param>
/// <returns></returns>
private System.Drawing.Bitmap _GetBit(HttpWebResponse MyResponse)
{
using (Stream MyStream = MyResponse.GetResponseStream())
{
return new System.Drawing.Bitmap(MyStream);
}
}
#endregion

#region Post
/// <summary>
/// 回发(字符编码默认UTF-8)
/// </summary>
/// <param name=”URL”>回发地址</param>
/// <param name=”PostData”>参数</param>
/// <returns>Html代码</returns>
public string PostPage(string URL, string PostData)
{
return PostPage(URL, PostData, System.Text.Encoding.UTF8);
}
/// <summary>
/// 回发
/// </summary>
/// <param name=”URL”>回发地址</param>
/// <param name=”PostData”>参数</param>
/// <param name=”encoding”>字符编码</param>
/// <returns>Html代码</returns>
public string PostPage(string URL, string PostData, Encoding encoding)
{
return PostPage(URL, PostData, encoding, null);
}
/// <summary>
/// 回发
/// </summary>
/// <param name=”URL”>回发地址</param>
/// <param name=”PostData”>参数</param>
/// <param name=”encoding”>字符编码</param>
/// <returns>Html代码</returns>
public string PostPage(string URL, string PostData, Encoding encoding, string ContentType)
{
IErrorTime = 0;
return _PostPage(URL, PostData, encoding, ContentType);
}
/// <summary>
/// 回发
/// </summary>
/// <param name=”URL”>回发地址</param>
/// <param name=”PostData”>参数</param>
/// <param name=”encoding”>字符编码</param>
/// <returns>Html代码</returns>
private string _PostPage(string URL, string PostData, Encoding encoding,string ContentType)
{
try
{
if (ContentType==null)
{
ContentType = application/x-www-form-urlencoded;
}
HttpWebRequest MyRequest = (HttpWebRequest)HttpWebRequest.Create(URL);
MyRequest.Proxy = Proxy;
if (UserCookie == null)
{
UserCookie = new CookieContainer();
}
MyRequest.CookieContainer = UserCookie;
MyRequest.Method = POST;
MyRequest.ContentType = ContentType;
byte[] b = encoding.GetBytes(PostData);
MyRequest.ContentLength = b.Length;
using (System.IO.Stream sw = MyRequest.GetRequestStream())
{
try
{
sw.Write(b, 0, b.Length);
}
catch
{
}
}
HttpWebResponse MyResponse = (HttpWebResponse)MyRequest.GetResponse();
return _GetHTML(MyResponse, encoding, 1024);
}
catch (Exception erro)
{
if (erro.Message.Contains(连接) && IAfreshTime – IErrorTime > 0)
{
IErrorTime++;
return _PostPage(URL, PostData, encoding, ContentType);
}
throw;
}
}
#endregion
#endregion
}
}

调用便很方便了,如下:

string strProxyAdd = www.Proxyxxxx.com, UserName = XXXX,PassWord = XXX;
int port = 8080;

HttpHelper http = new HttpHelper();
http.Proxy = new WebProxy();//设置代理
this.http.Proxy.Address = new Uri(string.Format(http://{0}:{1}, strProxyAdd,port));//设置代理服务器地址和端口
this.http.Proxy.Credentials = new NetworkCredential(UserName, PassWord);//设置代理用户名密码
//http.Proxy = null;//清空代理

var strPHtml = http.PostPage(www.xxxx.comUser=ABCD&Pwd=DEF);//向www.xxxx.com POST数据User=ABCD&Pwd=DEF
var strHtml = http.GetHTML(www.xxxx.com*/*, Encoding.UTF8, 20480);//从www.xxxx.com获取HTML数据,并用UTF8进行编码

由于Cookie在一个实例中是共用的,在一些场景下可以先POST登录后再GET需要登录才能进入的页面信息。

[转载]C#基于HttpHelper的赶集抓票工具

mikel阅读(887)

[转载][C#]基于HttpHelper的赶集抓票工具 – 二杠 – 博客园.

每到年关,火车票总是一票难求,去年差点没买到票回家,看到赶集、58等上有转让票,大 喜,遂掏出手机呼叫之,可结果都是已转让。想想原因皆是看到的不及时,被别人捷足先登了。哥们是干什么的,居然从我嘴里抢食,越想越气,便做一工具,实时 在赶集上监控着,有新出售票就提醒,几天下来,一张D字头的到手。。。

闲扯了几句,下面进入正题。

开发这一工具大概思路就是从网页上获取HTML,然后用正则匹配之,匹配出有新数据数据时加入到网格中显示,另外不可能实时盯着这个工具看,必须在 有新信息时有提醒,于是我便加了一个声音提醒,一小时内的新信息都加声音提醒。这里遇到两个问题,一是如何从网页上抓取HTML,二是正则匹配问题。关于 抓取HTML,本人前一篇文章中已有介绍,见http://www.cnblogs.com/vanjoge/archive/2011/11/18/2253937.html,剩下的问题就是正则了,我们以赶集的T110次车为例来找到对应正则,首先打开赶集的T110次车的连接http://sh.ganji.com/piao/cc_T110/,图中红框部分就是我们需要抓取的数据

查看源文件,找到对应的HTML代码,如图:

图中选中部分则是单个车票信息,

<DL class=list_piao><DT><A href=”/piao/11111809_2887746.htm” target=_blank>[转让] T110 上海-北京 硬卧 2张 发车日期:11-18</A> </DT>
<DD class=list_piao_time>11月18日 </DD>
<DD class=list_piao_mj><A href=”http://www.ganji.com/lieche/cc_T110/” target=_blank>T110</A> </DD>
<DD class=list_piao_time>4小时前 </DD></DL>

下面祭出我们的正则神器:RegexTester,将HTML填入Source中,然后再一点一点的拼出正则,如图:

正则表达式即是:

<dl class=list_piao>\s*<dt><a href=([^]+) target=_blank>([^<]+)</a></dt>\s*<dd class=list_piao_time>([^<]+)</dd>\s*<dd class=list_piao_mj><a href=([^]+) target=_blank>([^<]+)</a></dd>\s*<dd class=list_piao_time>([^<]+)</dd>\s*</dl>

接下来便是设计界面,我就不在详述了,见图吧:

抓取逻辑:

1.抓取http://sh.ganji.com/piao/cc_T110/页面的HTML信息;

private void GetHtml()
{
string str = http.GetHTML(txtUrl.Text, */*, Encoding.UTF8, 20480);
this.Invoke(new invokeDelegate(Update), str);
}

2.用正则匹配HTML信息;

void Update(string str)
{
lblUpdateTime.Text = 更新时间: + DateTime.Now.ToString(yyyy-MM-dd HH:mm:ss);
string pn = <dl class=\”list_piao\”>\\s*<dt><a href=\”([^\”]+)\” target=\”_blank\”>([^<]+)</a></dt>\\s*<dd class=\”list_piao_time\”>([^<]+)</dd>\\s*<dd class=\”list_piao_mj\”><a href=\”([^\”]+)\” target=\”_blank\”>([^<]+)</a></dd>\\s*<dd class=\”list_piao_time\”>([^<]+)</dd>\\s*</dl>;
Regex reg = new Regex(pn);
MatchCollection mths = reg.Matches(str);
foreach (Match item in mths)
{
Add(item.Groups[1].Value,
item.Groups[2].Value,
item.Groups[3].Value,
item.Groups[4].Value,
item.Groups[5].Value,
item.Groups[6].Value);
}
gridView1.BestFitColumns();
}

3.将新信息加入到网格中并声音提醒;

private void Add(string Url, string Text, string StartDate, string LUrl, string Vcc, string Time)
{
DataSet1.DTRow drow = GetDrow(dataSet11.DT, Url);//因为URL是唯一,因此根据URL获取是否已存在此火车票信息
int itemp = 0;
DateTime dt;
if (Int32.TryParse(Time.Replace(分前“”), out itemp))
{
}
else if (Int32.TryParse(Time.Replace(小时前“”), out itemp))
{
itemp = itemp * 60;
}
else if (DateTime.TryParse(strYear + Time, out dt))
{
itemp = Convert.ToInt32((DateTime.Now – dt).TotalMinutes);
}
if (drow == null)//不存在时追加
{
if (itemp < 60)
{
if (chkAuto.Checked)
{
OpenNewUrl(Text, strHost + Url, true);
}
PlaySoundSync();
}
dataSet11.DT.AddDTRow(Url, Text, StartDate, LUrl, Vcc, Time, itemp);
}
else//存在时更新间隔时间
{
drow.Time = Time;
drow.Ticks = itemp;
}
}

DataSet1.DTRow GetDrow(DataSet1.DTDataTable dt,string Url)
{
foreach (DataSet1.DTRow drow in dt)
{
if (drow.Url==Url)
{
return drow;
}
}

return null;
}

由于我们这个任务是需要定时的执行,因此要用到timer或Thread,这里我选择了用Thread,是为了防止在读取数据的过程中出现假死的情况,因此完整逻辑应该是:

1.开启任务

private void btnStart_Click(object sender, EventArgs e)
{
strYear = DateTime.Now.Year.ToString() + ;
if (chkUseProxy.Checked)
{
this.http.Proxy = new WebProxy();
try
{
this.http.Proxy.Address = new Uri(string.Format(http://{0}:{1}this.txtProxyServer.Text, this.spinProxyPort.Text));
if ((this.txtProxyUsername.Text.Length > 0) && (this.txtProxyPassword.Text.Length > 0))
{
this.http.Proxy.Credentials = new NetworkCredential(this.txtProxyUsername.Text, this.txtProxyPassword.Text);
}
}
catch
{
MessageBox.Show(代理设置出错,请检查代理设置!错误, MessageBoxButtons.OK, MessageBoxIcon.Error);
this.http.Proxy = null;
return;
}
}
else
{
this.http.Proxy = null;
}

IsRun = !IsRun;
if (IsRun)
{
Uri uri = new Uri(txtUrl.Text);
strHost = uri.Scheme + :// + uri.Host;
btnStart.Text = 停止监控;
txtUrl.Enabled = false;
System.Threading.Thread th = new System.Threading.Thread(new System.Threading.ThreadStart(Run));
th.IsBackground = true;
th.Start();
}
else
{
btnStart.Text = 开始监控;
txtUrl.Enabled = true;
}
}

2.执行抓取逻辑

void Run()
{
while (IsRun)
{
GetHtml();
System.Threading.Thread.Sleep(5000);
}
}

最终执行效果:

附上源码:源码,不带DEV控件

由于此工具用了DEV10.1.4.0控件,因此没有此控件的童鞋需要下载DEV控件的DLL:本工具用到的DEV DLL

[转载]用三张图片详解Asp.Net 全生命周期

mikel阅读(1054)

[转载]用三张图片详解Asp.Net 全生命周期 – 阳阳多 – 博客园.

下面我们使用三张图片解析ASP.NET的整个生命周期,我总感觉使用图片更加的清楚的说明这种问题,所以使用的这样方式

说明:

1  第一张图片从全局说明从客户端发出一个Request请求,服务器windows内核中的HTTP.SYS组件接收该请求开始到IIS处理完该请求并响应到客户端结束。

2  第二张图片为图1中Http处理管线的详细步骤

3  第三张图片为图2Http处理管线中调用处理程序(HttpHandler,  此处为Page对象)的详细生命周期过程。

图1:


图2:  Http处理管线详解


图3:  ASP.NET生命周期详解