2014年1月3日星期五

MVVM、MVVMLight、MVVMLight Toolkit之我见

    我想,现在已经有不少朋友在项目中使用了MVVMLight了吧,如果你正在做WPF,Silverlight,Windows Phone的开发,那么,你有十分必要的理由了解MVVM和MVVMLight。我写这篇文章的目的,是给大家做一个总结,以便更多的朋友了解并掌握MVVM。


    首先,要说一下MVVM的概念。MVVM严格来说,并不是一种框架,而是一个设计的模式吧。与它有关的设计模式还有MVC (现在广泛用于Web应用中),以及MVP(之前有用过在Windows Forms和WPF中)

    如果你希望对MVVM有更加感性地认识,我推荐你看下面这篇文章。
    这篇文章写得实在太好了,我很欣赏这样的技术人才,能把一个抽象问题有层次地讲清楚。(我强烈建议对MVVM的概念了解不深的朋友,认真读这篇文章,而不要急于用MVVMLight,因为MVVM是一种模式,而MVVMLight只是其中一种具体的实现)

    然后,我要说一下MVVMLight吧,刚才说了,它是一种MVVM的实现。自然它不是唯一的一种实现,但现在大家公认的是,它是比较好的一个实现。就我个人的体会来说,我以前用过微软提供的Prism中的MVVM特性,但老实说,可能Prism的目标太大了,所以在MVVM这个具体的点上,实在不是那么好用。

    值得一说的是,从使用Prism转换到使用MVVMLight过程相当简单,如果有类似情况的朋友,不要有什么顾虑。我这里不是说Prism不好,它与Mvvmlight严格来说,不是一个重量级的产品。MVVMLight专注与MVVM的实现,自然更加灵活

    接下来,我认为要学习MVVMLight最好的Quick start,就是作者自己写的这个网页
    通过这个文章,我们可以很清楚地了解MVVMLight的设计思路和包含的有关组件,无需太多补充,文章浅显易懂,确实是我们要学习的一个榜样
    请注意,我这篇文章并非逐一讲解MVVMLight的细节功能使用,我主要提一些重点,并且分享一些我的看法和观点,当然这仅是我一家之言,不见得完全正确。
    事实上针对如何使用的方面,已经有不少文章了,大家可以参考
    另外一方面,我觉得大家其实要自己多动手才会有实际的收获。MVVMLight使用并不难,在使用中大家可以领会到更多。

    实际上,我们经常谈论MVVMLight的时候,其实谈的是MVVMLight Toolkit,它主要是为了更加方便开发人员使用MVVMLight,它会在本地的GAC(Global Assembly Cache)中分别安装针对WPF,Silverlight,Windows Phone的Assembly(分别各自有两个Assembly),并且在Visual Studio中添加相应的项目以及项模板,更加贴心的一点是,它还提供了几个代码段。

    有的朋友可能会问,那么MVVMLight到底是什么呢?呃,MVVMLight嘛,就是MVVMLight Toolkit的名称啦 
    Be right back
    ,有点绕对吧,放松点,不要那么较真嘛







    这里要指出的是,我个人并不喜欢用这个Toolkit提供的项目模板和代码段。我觉得它所生成的代码有些冗余,修改起来反而麻烦。我真正的项目中就不用这些模板,而是倾向于自己编写ViewModel之类的。这可能也跟我之前并不是一开始就使用MVVMLight有关系,我更习惯自己写那些代码,我指的是一些ViewModel的属性,命令和绑定等等。

    好吧,我承认,我为什么不喜欢使用Toolkit的模板呢?还有一个原因是,除非必要,我对于工具要给Visual Studio添加额外的东西(哪怕是有用的),总是很敏感的,我担心它让Visual Studio变慢。是的,你知道,这多少有点类似“洁癖”的嫌疑,但怎么说呢,让我保留自己这个权利吧

    那么,如果你像我一样,不安装MVVMLight Toolkit,如何使用MVVMLight呢?实际上很简单,我更加习惯于使用nuget package 来获取最新的MVVMLight的Library,并将它们添加到项目中来。

    你可以通过这个菜单打开nuget package explorer,如下图所示,然后,你可以在Online里面搜索MVVMLight,或者像我这样在Recent package中直接就可以Install。(nuget package是会被缓存在本地的,所以即便没有链接到网络,也可以正常使用)

    既然可以缓存在本地,那么其实和安装到GAC是没有太大区别的,不是吗?
    而且用这种方式还有一个好处,你总是可以得到最新的版本,因为nuget package是自动有更新提示的。而如果你是用Toolkit的话,则得不到更新的提示。(据可靠消息,MVVMLight将很快有4.0这个版本)

    很好,你现在已经知道如何将MVVMLight添加到项目中,接下来就是该让它发挥威力的时候啦。大家一定要理解MVVM的两个核心目标
    1.让UI界面与逻辑能够很好地分离又协同工作。
    2.让逻辑代码更具有可测试性。

    我们先来说说分离并协同这个目标,在MVVMLight中主要通过什么实现的呢?它提供了ViewModelBase这个基类,可以让我们很方便地编写ViewModel。从下面的截图可以看出,它提供了很多有用的特性,例如判断是否在设计状态(IsInDesignMode),以及触发属性更改通知(RaisePropertyChanged),尤其是后者,这可以说是MVVM的根基,为什么这么说呢?UI与逻辑的分离并且协同工作,关键就在于WPF和Silverlight有强大的数据绑定机制,而数据绑定机制之所以能够强大,就是因为WPF和Silverlight中引入的依赖属性(Dependency Property)的机制,而依赖属性,区别于普通属性的最重要一点就是既可以有单向绑定,也可以有双向绑定,而且属性更改之后,可以通知到所有绑定目标上面。


    除了很好的支持绑定,UI与逻辑分离并协作的另外一个重要机制,就是命令(command)机制。在MVVMLight中,它提供了两个基本的命令:RelayCommand和RelayCommand<T>

    这两个命令其实没有本质区别,只不过后者是支持泛型的一个参数的,就是可以从命令源接受参数数据。
    需要注意的是,这两个命令只适合绑定在基于按钮的Click事件上面。例如Button,HyperlinkButton是最常见的。例如下面的例子

    这个绑定的意思,其实就是说,当这个Button被点击了之后,调用ViewModel中的SaveCommand
    如果需要传递参数过去呢,就是下面这样啦。我举了两个例子,第一个例子参数是一个常数,而第二个例子参数是一个绑定值,这都是允许的

    但问题是,如果我要绑定其他事件呢?例如MouseMove事件,该怎么办呢?在MvvmLight.Extras这个程序集里面,单独又给出了一个Command绑定方式,叫EventToCommand,顾名思义,它可以将任何事件绑定到一个命令


    要使用这个略微麻烦一些,请看下面的例子


    所以,绑定(尤其是双向绑定)和命令是MVVM的精髓,但实际要认真讲起来,MVVMLight这方面实现得其实也没有什么特别突出的,其他一些框架也都是这么做的。以前没有这些框架之前,我们也是这么写的,无非是代码会多一些而已。
    有童鞋可能会说了,属性绑定我们可以理解,但为嘛要这么麻烦去绑定命令呢?直接在xaml.cs里面写不就完了吗?请注意,MVVM的一个目标就是让xaml.cs代码中尽量少,极端的情况是没有任何用户代码。这样才能实现UI与逻辑的分离,所以尽可能地用Command来做。
    这里我也分享我的个人经验,一定会有的时候,你没有办法全部用Command,而不在xaml.cs中写任何代码。那个时候,你也大可像我一样,将代码写一些在xaml.cs中也无妨。典型的情况,是希望在视图里面接受消息(下面就要讲到),并且更新界面的一些效果,例如启动动画。这里面是一个度的把握,并无绝对的好坏。我已经看到有人心领神会地点头了,所谓随机应变,大家要有一定的灵活性。

    不过,Mvvmlight的一个创造性的设计,是它的Message(消息)机制它让View和ViewModel,以及ViewModel之间通讯变得相当方便,甚至神奇。我相当欣赏这个设计,这是Mvvmlight之所以称为Mvvmlight的原因。
    具体来说,它提供了一个Messenger类型,可以用来发送和接收消息,它还提供了默认的几种消息类型。
    A Messenger class (and diverse message types) to be used to communicate within the application. Recipients only receive the message types that they register for. Additionally, a target type can be specified, in which case the message will only be transmitted if the recipient's type matches the target parameter. Messages can be anything from simple values to complex objects. You can also use specialized message types, or create your own types deriving from them. More information about the Messenger class.
    • MessageBase: A simple message class, carrying optional information about the message's sender.
    • GenericMessage<T>: A simple message with a Content property of type T.
    • NotificationMessage: Used to send a notification (as a string) to a recipient. For example, save your notifications as constant in a Notifications class, and then send Notifications.Save to a recipient.
    • NotificationMessage<T>: Same as above, but with a generic Content property. Can be used to pass a parameter to the recipient together with the notification.
    • NotificationMessageAction: Sends a notification to a recipient and allows the recipient to call the sender back.
    • NotificationMessageAction<T>: Sends a notification to a recipient and allows the recipient to call the sender back with a generic parameter.
    • DialogMessage: Used to request that a recipient (typically a View) displays a dialog, and passes the result back to the caller (using a callback). The recipient can choose how to display the dialog, either with a standard MessageBox, with a custom popup, etc…
    • PropertyChangedMessage<T>: Used to broadcast that a property changed in the sender. Fulfills the same purpose than the PropertyChanged event, but in a less tight way.
    一个稍微具体一点的例子,请参考

    我非常喜欢这个Messenger的功能,但同时,我个人觉得它的设计有值得改进之处,首先它的语法有点繁琐了,不是吗?
    我们显然更希望用下面这样的语法

    这是如何实现的呢,其实我是自己对Messenger做了一个扩展
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using GalaSoft.MvvmLight.Messaging;
    namespace WpfApplication1
    {
    /// <summary>
       
    /// 对默认的Messenger做扩展,以便更加易于使用
       
    /// 作者:陈希章
       
    /// </summary>
       
    public static class MessengerExtension
        {
           
    public static void Send<T>(this IMessenger messenger, T body, object token)
            {
                Messenger.Default.Send<GenericMessage<T>>(
    new GenericMessage<T>(body), token);
            }
    public static void Register<T>(this Messenger messenger, object recipient, object token, Action<T> action)
            {
                Messenger.Default.Register<GenericMessage<T>>(recipient, token, msg => {
                    action(msg.Content);
                });
            }
        }
    }

    关于Messenger,其次我还觉得,它定义那么多消息类型,并不是非常理想,容易把使用者搞晕(我其实也不是很理解为什么既要做一个GenericMessage,还有一个NotificationMessage等等)。这也是我用上面这样的方式扩展的原因。我后面会整理一个扩展代码,做成可以分享的package给大家使用。

    讲了这么多,其实还有一个经常被大家忽视的目标:可测试性。这是很重要的。如何理解MVVM的可测试性,以及在MVVMLight中的具体实现呢?
    我们来看一个例子,我们通常会说这是一个不可测试的代码例子


    为什么说它是不可测试的呢?因为我们都知道,MessageBox是需要人去响应的,你要点击一下才会被关闭掉。而我们的测试(包括单元测试),大多都是要能批量,自动运行的,那么遇到这种MessageBox怎么办呢?
    我们一般单元测试代码会这么样写

    运行起来之后,它确实会按照预期的那样去执行代码,很显然它会弹出一个对话框,让我们去点击

    点击了之后,当然测试会通过。但问题是,如果测试还需要人工干预才能运行,显然不利于自动化。
    我们来看在MVVMLight中如何解决这个问题的。我们得捋一下思路:你的目的是要弹出一个对话框(或者类似的东西),但如果你必须用MessageBox的话,就肯定是会弹出那个对话框来。有什么办法可以解决这个问题呢?
    答案就是:MvvmLight提供的Messenger机制。我们来看如下的例子

    那么,这个消息会被谁来响应呢?一般是在View里面去响应,仔细想想:显示消息(以及如何显示)其实是View的责任,与ViewModel没有什么关系。

    很好,这样就是MVVM的做法了,那么我们再来运行测试看看会怎么样呢?大家如果自己运行一下就知道了,测试直接通过了,没有任何消息提示。
    等等,这难道就说明我们做对了吗?我们的测试中,怎么确认消息发出去了呢?也就是说,既然上面的代码并不会弹出消息,你怎么确认那个方法里面发送了消息呢?
    所以,好戏一般都在后头,不要着急下结论。所以可测试性,是指MvvmLight为此类问题都准备了解决方案。我们如何确认SaveCommand里面肯定调用而且仅仅调用了一次Messenger.Send方法呢?
    很显然,我们得有一个什么方式,模拟Messenger的功能:我们并不真的去发消息,我们是要验证发送消息的方法真的被调用,而且发的消息内容是不是“保存成功”,这就是我们测试的目的。
    在这里,我们会用到一个模拟的框架,我最喜欢用的是Moq这个框架。这也是一个开源项目,它的官方网站是 http://code.google.com/p/moq/
    同样,我们可以通过nuget package explorer中获取它,实在是很方便,不是吗?


    然后,我们编写下面的测试代码
    using WpfApplication1;
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    using System;
    using System.Windows.Input;
    using Moq;
    using GalaSoft.MvvmLight.Messaging;
    namespace TestProject1
    {
       
       
       
    /// <summary>
       
    ///This is a test class for MainWindowViewModelTest and is intended
       
    ///to contain all MainWindowViewModelTest Unit Tests
       
    ///</summary>
        [TestClass()]
       
    public class MainWindowViewModelTest
        {
    /// <summary>
           
    ///A test for SaveCommand
           
    ///</summary>
            [TestMethod()]
           
    public void SaveCommandTest()
            {
                MainWindowViewModel target =
    new MainWindowViewModel(); // TODO: Initialize to an appropriate value
            
       var messenger = new Mock<Messenger>();
                messenger.Setup(m => m.Send(It.Is<DialogMessage>(d => d.Content ==
    "保存成功"))).Verifiable();
                Messenger.OverrideDefault(messenger.Object);
    var cmd = target.SaveCommand;
                cmd.Execute(
    null);
    messenger.Verify();
    }
        }
    }

    上面的代码很好理解,我们希望验证Messenger的Send方法是否被调用,而且发送的消息是不是一个DialogMessage,内容是不是“保存成功”。moq的特点就是语义很通俗易懂,让我们为它鼓掌。
    再次运行测试的话,我们会发现这次也还是正常通过了测试。但如果,我们将ViewModel方法里面的那句发送消息的代码注释掉,则就会报告一个错误


    我们甚至还可以验证Send方法调用了多少次,诸如此类,这是moq的功能,这里就不多展开了。


    写在最后的话
    感谢Laurent Bugnion 的杰出工作,他是微软MVP,我也看过他的视频,讲解MVVMLight及其原理和使用的,蛮平易近人的,典型的程序员和技术发烧友吧。有一个视频上面,他穿的一间黑色T恤,上面就写着几个字:geek, 极客,你懂的
    Laurent还将源代码发布到了Codeplex,你可以通过下面这里下载到

    顺便做一个小的调查,MVVMLight是完全免费的,包括源代码。但作者也希望得到捐赠(我看过很多不错的开源项目都接受捐赠),我想请问在读这篇文章的各位,你有没有曾几何时捐赠过任何的开源项目呢?捐赠了多少?是什么让你做出捐赠的决定呢?
    其实以我所看到的,我也知道在国内,捐赠的这种形式并不常见,所以这个调查纯属满足我的好奇心,谢谢啦
    Hot smile
    我自己而言,以前也确实没有捐赠过任何的开源项目。但我现在的想法是,如果确实有相当好的开源项目,我有心是要适当地捐赠的。

    posted @ 2012-05-16 20:20 xxdotnet 阅读(138) 评论(0) 编辑
    const int WM_NCHITTEST = 0x0084;
    const int HTLEFT = 10;
    const int HTRIGHT = 11;
    const int HTTOP = 12;
    const int HTTOPLEFT = 13;
    const int HTTOPRIGHT = 14;
    const int HTBOTTOM = 15;
    const int HTBOTTOMLEFT = 0x10;
    const int HTBOTTOMRIGHT = 17;
    protected override void WndProc(ref Message m)
    {
       
    base.WndProc(ref m);
       
    switch (m.Msg)
        {
           
    case WM_NCHITTEST:
                Point vPoint
    = new Point((int)m.LParam & 0xFFFF,
                    (
    int)m.LParam >> 16 & 0xFFFF);
                vPoint
    = PointToClient(vPoint);
               
    if (vPoint.X <= 5)
                   
    if (vPoint.Y <= 5)
                        m.Result
    = (IntPtr)HTTOPLEFT;
                   
    else if (vPoint.Y >= ClientSize.Height - 5)
                        m.Result
    = (IntPtr)HTBOTTOMLEFT;
                   
    else m.Result = (IntPtr)HTLEFT;
               
    else if (vPoint.X >= ClientSize.Width - 5)
                   
    if (vPoint.Y <= 5)
                        m.Result
    = (IntPtr)HTTOPRIGHT;
                   
    else if (vPoint.Y >= ClientSize.Height - 5)
                        m.Result
    = (IntPtr)HTBOTTOMRIGHT;
                   
    else m.Result = (IntPtr)HTRIGHT;
               
    else if (vPoint.Y <= 5)
                    m.Result
    = (IntPtr)HTTOP;
               
    else if (vPoint.Y >= ClientSize.Height - 5)
                    m.Result
    = (IntPtr)HTBOTTOM;
               
    break;
        }
    }

    posted @ 2012-05-16 20:11 xxdotnet 阅读(25) 评论(0) 编辑
    一、 版本号自动生成方法
    只需把 AssemblyInfo.cs文件中的[assembly: AssemblyVersion("1.0.0.0")]改成[assembly: AssemblyVersion("1.0.*")],另外还需要把[assembly: AssemblyFileVersion("1.0.0.0")]注释屏蔽掉。这样再生成的程序集就是自动版本号了。
    按照这个格式出来的版本号,内部修订版本号(第三个字段)的位置出现的是200011日到编译日期的天数,内部修订号(第四个字段)是当天从0点到当前时间的刻度数。
    二、 Window 下的版本号管理策略:
    1.目初版时 , 版本号为 1.0  1.00;
    2. 当项目在进行了局部修改或 bug 修正时,主版本号和子版本号都不变 , 修正版本号加 1;
    3. 当项目在原有的基础上增加了部分功能时 , 主版本号不变 , 子版本号加 1, 修正版本号复位为 0, 因而可以被忽略掉 ;
    4. 当项目在进行了重大修改或局部修正累积较多 , 而导致项目整体发生全局变化时 , 主版本号加 1;
    5. 另外 , 编译版本号一般是编译器在编译过程中自动生成的 , 我们只定义其格式 , 并不进行人为控制 .
    另外 , 还可以在版本号后面加入 Alpha, Beta, Gamma, Current, RC (Release Candidate), Release, Stable 等后缀 , 在这后缀后面还可以加入 1 位数字的版本号 .
    对于用户来说 , 如果某个软件的主版本号进行了升级 , 用户还想继续那个软件 , 则发行软件的公司一般要对用户收取升级费用 ; 而如果子版本号或修正版本号发生了升级 , 一般来说是免费的 .
    附: alphal 内部测试版 beta 外部测试版 demo 演示版 Enhance 增强版或者加强版 属于正式版 Free 自由版 Full version 完全版 属于正式版 shareware 共享版 Release 发行版 有时间限制 Upgrade 升级版 Retail 零售版 Cardware 属共享软件的一种,只要给作者回复一封电邮或明信片即可。(有的作者并由此提供注册码等),目前这种形式已不多见。 Plus 属增强版,不过这种大部分是在程序界面及多媒体功能上增强。 Preview 预览版 Corporation & Enterprise 企业版 Standard 标准版 Mini 迷你版也叫精简版只有最基本的功能 Premium — 贵价版 Professional — 专业版 Express — 特别版 Deluxe — 豪华版 Regged — 已注册版 CN — 简体中文版 CHT — 繁体中文版 EN — 英文版 Multilanguage — 多语言版
    注释:
    α
    此版本表示该软件仅仅是一个初步完成品,通常只在软件开发者内部交流,也有很少一部分发布给专业测试人员。一般而言,该版本软件的 bug 较多,普通用户最好不要安装。
    βbeta)版
    该版本相对于α版已有了很大的改进,消除了严重的错误,但还是存在着一些缺陷,需要经过大规模的发布测试来进一步消除。这一版本通常由软件公司免费 布,用户可从相关的站点下载。通过一些专业爱好者的测试,将结果反馈给开发者,开发者们再进行有针对性的修改。该版本也不适合一般用户安装。
    γ
    该版本已经相当成熟了,与即将发行的正式版相差无几,如果用户实在等不及了,尽可以装上一试。
    trial(试用版)
    试用版软件在最近的几年里颇为流行,主要是得益于互联网的迅速发展。该版本软件通常都有时间限制,过期之后用户如果希望继续使用,一般得交纳一定的费 用进行注册或购买。有些试用版软件还在功能上做了一定的限制。
    unregistered(未注册版)
    未注册版与试用版极其类似,只是未注册版通常没有时间限制,在功能上相对于正式版做了一定的限制,例如绝大多数网络电话软件的注册版和未注册版,两 者 之间在通话质量上有很大差距。还有些虽然在使用上与正式版毫无二致,但是动不动就会弹出一个恼人的消息框来提醒你注册,如看图软件 acdsee 、智能陈桥汉字输入软件等。
    demo
    也称为演示版,在非正式版软件中,该版本的知名度最大。 demo 版仅仅集成了正式版中的几个功能,颇有点像 unregistered 。不同的是, demo 版一般不能通过升级或注册的方法变为正式版。
    以上是软件正式版本推出之前的几个版本,αβγ可以称为测试版,大凡成熟软件总会有多个测试版,如 windows 98 β版,前前后后将近有 10 个。这么多的测试版一方面为了最终产品尽可能地满足用户的需要,另一方面也尽量减少了软件中的 bug 。而 trial  unregistered  demo 有时统称为演示版,这一类版本的广告色彩较浓,颇有点先尝后买的味道,对于普通用户而言自然是可以免费尝鲜了。
    正式版 不同类型的软件的正式版本通常也有区别。
    release
    该版本意味最终释放版,在出了一系列的测试版之后,终归会有一个正式版本,对于用户而言,购买该版本的软件绝对不会错。该版本有时也称为标准 版。 一般情况下, release 不会以单词形式出现在软件封面上,取而代之的是符号 (r) ,如 windows nt(r) 4.0  ms-dos(r) 6.22 等。
    registered
    很显然,该版本是与 unregistered 相对的注册版。注册版、 release 和下面所讲的 standard 版一样,都是软件的正式版本,只是注册版软件的前身有很大一部分是从网上下载的。
    standard
    这是最常见的标准版,不论是什么软件,标准版一定存在。标准版中包含了该软件的基本组件及一些常用功能,可以满足一般用户的需求。其价格相对高一级版 本而言还是平易近人的。
    deluxe
    顾名思义即为豪华版。豪华版通常是相对于标准版而言的,主要区别是多了几项功能,价格当然会高出一大块,不推荐一般用户购买。此版本通常是为那些 追求完美的专业用户所准备的。
    reference
    该版本型号常见于百科全书中,比较有名的是微软的 encarta 系列。 reference 是最高级别,其包含的主题、图像、影片剪辑等相对于 standard  deluxe 版均有大幅增加,容量由一张光盘猛增至三张光盘,并且加入了很强的交互功能,当然价格也不菲。可以这么说,这一版本的百科全书才能算是真正的百科全书,也 是发烧友们收藏的首选。
    professional(专业版)
    专业版是针对某些特定的开发工具软件而言的。专业版中有许多内容是标准版中所没有的,这些内容对于一个专业的软件开发人员来说是极为重要的。如微软 的 visual foxpro 标准版并不具备编译成可执行文件的功能,这对于一个完整的开发项目而言显然是无法忍受的,若客户机上没有 foxpro 将不能使用。如果用专业版就没有这个问题了。
    enterprise(企业版)
    企业版是开发类软件中的极品(相当于百科全书中的 reference 版)。拥有一套这种版本的软件可以毫无障碍地开发任何级别的应用软件。如著名的 visual c++ 的企业版相对于专业版来说增加了几个附加的特性,如 sql 调试、扩展的存储过程向导、支持 as/400  ole db 的访问等。而这一版本的价格也是普通用户无法接受的。如微软的 visual studios 6.0 enterprise 中文版的价格为 23000 元。
    其他版本 除了以上介绍的一些版本外,还有一些专有版本名称。
    update(升级版)
    升级版的软件是不能独立使用的,该版本的软件在安装过程中会搜索原有的正式版,如果不存在,则拒绝执行下一步。如 microsoft office 2000 升级版、 windows 9x 升级版等等。
    OEM
    OEM版通常是捆绑在硬件中而不单独销售的版本。将自己的产品交给别的公司去卖,保留自己的著作权,双方互惠互利,一举两得。
    单机(网络)版
    网络版在功能、结构上远比单机版复杂,如果留心一下软件的报价,你就会发现某些软件单机版和网络版的价格相差非常大,有些网络版甚至多一个客户端口就 要加不少钱。
    普及版
    该版本有时也会被称为共享版,其特点是价格便宜(有些甚至完全免费)、功能单一、针对性强(当然也有占领市场、打击盗版等因素)。与试用版不同的是, 该版本的软件一般不会有时间上的限制。当然,如果用户想升级,最好还是去购买正式版。
    以上是一些常见软件版本的简要介绍,随着软件市场行为的变化,现在也出现了一些新的版本命名方式,比如windows xp中的xp是取自于experience中的第二、第三个字母。希望以上内容能够对大家的购买、使用和下载软件有所帮助。
    基于商业上考虑,很多的软件都不是非常严谨的遵循这个规则的。最有名的就是微软了。例如他的 NT 系列版本。大家比较熟悉的是从 NT 4.0 开始的。 99 年推出了 windows 2000  2001 年退出了 windows xp  2003 年推出了 windows 2003 ,乍一看版本区别蛮大的,但是看他们的内部版本号就会发现,变化其实并不大,只是界面变化的大了而已。这是软件公司经常干的事情。 Window 2000 的版本号是 NT 5.0  windows xp 的版本号是 NT 5.1  windows 2003 的版本号是 NT 5.2 ,而现在的 longhorn 才是真正的 NT 6.0 (印象中是,不敢确认)。这样就可以持续的赚广大客户的钱。毕竟人的眼睛看得东西是最直观的,所以给人感觉也是变化最大的。

    posted @ 2012-05-16 20:08 xxdotnet 阅读(34) 评论(0) 编辑
    客户端ip:
    Request.ServerVariables.Get(
    "Remote_Addr").ToString();
    客户端主机名:
    Request.ServerVariables.Get(
    "Remote_Host").ToString();
    客户端浏览器IE:
    Request.Browser.Browser;
    客户端浏览器 版本号:
    Request.Browser.MajorVersion;
    //
    客户端操作系统:
    Request.Browser.Platform;
    服务器ip:
    Request.ServerVariables.Get(
    "Local_Addr").ToString();
    服务器名:
    Request.ServerVariables.Get(
    "Server_Name").ToString();

    Request.ServerVariables("Url")
    返回服务器地址
    Request.ServerVariables("Path_Info")
    客户端提供的路径信息
    Request.ServerVariables("Appl_Physical_Path")
    与应用程序元数据库路径相应的物理路径
    Request.ServerVariables("Path_Translated")
    通过由虚拟至物理的映射后得到的路径
    Request.ServerVariables("Script_Name")
    执行脚本的名称
    Request.ServerVariables("Query_String")
    查询字符串內容
    Request.ServerVariables("Http_Referer")
    请求的字符串內容
    Request.ServerVariables("Server_Port")
    接受请求的服务器端口号
    Request.ServerVariables("Remote_Addr")
    发出请求的远程主机的IP地址
    Request.ServerVariables("Remote_Host")
    发出请求的远程主机名称
    Request.ServerVariables("Local_Addr")
    返回接受请求的服务器地址
    Request.ServerVariables("Http_Host")
    返回服务器地址
    Request.ServerVariables("Server_Name")
    服务器的主机名、DNS地址或IP地址
    Request.ServerVariables("Request_Method")
    提出请求的方法比如GET、HEAD、POST等等
    Request.ServerVariables("Server_Port_Secure")
    如果接受请求的服务器端口为安全端口时,则为1,否则为0
    Request.ServerVariables("Server_Protocol")
    服务器使用的协议的名称和版本
    Request.ServerVariables("Server_Software")
    应答请求并运行网关的服务器软件的名称和版本
    Request.ServerVariables("All_Http")
    客户端发送的所有HTTP标头,前缀HTTP_
    Request.ServerVariables("All_Raw")
    客户端发送的所有HTTP标头,其结果和客户端发送时一样,没有前缀HTTP_
    Request.ServerVariables("Appl_MD_Path")
    应用程序的元数据库路径
    Request.ServerVariables("Content_Length")
    客户端发出內容的长度
    Request.ServerVariables("Https")
    如果请求穿过安全通道(SSL),则返回ON如果请求来自非安全通道,则返回OFF
    Request.ServerVariables("Instance_ID")
    IIS实例的ID号
    Request.ServerVariables("Instance_Meta_Path")
    响应请求的IIS实例的元数据库路径
    Request.ServerVariables("Http_Accept_Encoding")
    返回內容如:gzip,deflate
    Request.ServerVariables("Http_Accept_Language")
    返回內容如:en-us
    Request.ServerVariables("Http_Connection")
    返回內容:Keep-Alive
    Request.ServerVariables("Http_Cookie")
    Request.ServerVariables("Http_User_Agent")
    返回內容:Mozilla/4.0(compatible;MSIE6.0;WindowsNT5.1;SV1)
    Request.ServerVariables("Https_Keysize")
    安全套接字层连接关键字的位数,如128
    Request.ServerVariables("Https_Secretkeysize")
    服务器验证私人关键字的位数如1024
    Request.ServerVariables("Https_Server_Issuer")
    服务器证书的发行者字段
    Request.ServerVariables("Https_Server_Subject")
    服务器证书的主题字段
    Request.ServerVariables("Auth_Password")
    当使用基本验证模式时,客户在密码对话框中输入的密码
    Request.ServerVariables("Auth_Type")
    是用户访问受保护的脚本时,服务器用於检验用户的验证方法
    Request.ServerVariables("Auth_User")
    代证的用户名
    Request.ServerVariables("Cert_Cookie")
    唯一的客户证书ID号
    Request.ServerVariables("Cert_Flag")
    客户证书标誌,如有客户端证书,则bit0为0如果客户端证书验证无效,bit1被设置为1
    Request.ServerVariables("Cert_Issuer")
    用户证书中的发行者字段
    Request.ServerVariables("Cert_Keysize")
    安全套接字层连接关键字的位数,如128
    Request.ServerVariables("Cert_Secretkeysize")
    服务器验证私人关键字的位数如1024
    Request.ServerVariables("Cert_Serialnumber")
    客户证书的序列号字段
    Request.ServerVariables("Cert_Server_Issuer")
    服务器证书的发行者字段
    Request.ServerVariables("Cert_Server_Subject")
    服务器证书的主题字段
    Request.ServerVariables("Cert_Subject")
    客户端证书的主题字段
    Request.ServerVariables("Content_Type")
    客户发送的form內容或HTTPPUT的数据类型