[转载]IT人员如何管理知识 - 心内求法 - 博客园

mikel阅读(856)

[转载]IT人员如何管理知识 – 心内求法 – 博客园.

搞IT的人通常都象一个“技术守财奴”,收集了一大堆文档资料,时不时检阅一下,便得到一种莫名的满足。

当然,你会不服气,说自己已经研究过这些资料,并且转化成了自己的知识,而知识是无价的。 可是残酷的现实告诉你,知识既不会带来“颜如玉”,也不会带来“黄金屋”。自认为饱学之士的你,为何反而不被重用?

不被运用的知识是死知识,你可能成为别人的“百科全书”/“智力提款机”,但偏偏你自己不知道该做些什么。 而且IT领域知识的更新换代很快,一不小心,花费大量精力掌握的知识就成了“废品”,其价值不如一枚“茶叶蛋”, 这让身为IT“专业人士”的你,情何以堪?

如果经历过风雨的你仍然执着于技术,本文将与你探讨IT技术人员进化之路,探讨如何更有效的管理知识,如何让知识变成能力,运用知识解决问题。

1 缘起

加入某证券公司已经2年了,最近发现自己的知识体系有有些混乱。其实这是好事,说明知识的领域变宽了, 带来的问题是需要抽出时间进行专门的梳理,这是一项比较繁重的体力劳动,尤其对于奔4的人来说。

在十余年的IT生涯中,这样的事情发生过几次,其中比较重大的有以下几次:

第一次是解散自己的创业团队,加入某软件服务商(ASP)并担任项目经理。 之前的创业团队规模较小管理简单,个人关注的领域主要在开发技术;到了新公司拓展了项目管理、需求分析、政府行业知识(主要是区域政府一站式审批,新农村 合作医疗)、协同办公解决方案 等知识领域, 也涉及到一些软件开发平台、CMS、以及BI产品。

第二次是到一家外企的解决方案部门,主要工作内容是Consulting,即分析问题并提出解决方案。期间积累了企业管理(尤其是IT管理)、电信 行业、民航行业、电视媒体行业的业务知识,知识管理(KM)解决方案,以及BPM、ESB、Portal等平台软件,了解了SUN和IBM的服务器,同时 客串了一些Flash/Flex和M$ WPF的研究及培训工作。

第三次也就是现在,加入证券公司。从乙方变成了甲方,工作内容又有了比较大的变化。首先是扩充证券行业的业务知识,接下来的工作重点放在企业架构 (EA)、IT运维(主要是应用系统管理、配置管理、问题管理)、甲方的项目管理等。为了做好这些工作,又额外关注了IT规划、企业IT治理、IT组织架 构等领域;在技术方面包括ESB实施和webservice规划、服务器虚拟化、HP服务器及一些网络和存储设备、Linux、Oracle、网络规划设 计(学习)、数据仓库和数据挖掘(了解)、消息中间件、集中交易系统架构、规则引擎、CEP、性能测试、应用开发平台等等。出于兴趣和爱好还在学习 python和emacs。

相信你已经明白我为什么会写这篇文章了——这些急速扩充的知识迫切需要条理化,否则短期内知识领域的急剧扩充必然导致大脑崩溃。

学习的过程可能是在某个领域的不断深入,也可能是对更多领域的扩展。在知识爆炸和渴求复合型人才的今天,后者应该更加重要。知识领域的扩展,必然会 带来某种“混乱”,即新的领域知识打破了你原来的知识框架,对知识的主观定位及知识之间的关联变得模糊不清。所以过一段时间(2-3年)就需要对你掌握的 知识进行重新梳理,这是一个痛苦的“破而后立”的过程,但完成它,你就会看到一片新天地展现在你面前。这也就是写出本文的原因。

2 资料,知识,智慧,思想,精神

在着手梳理之前,还是要搞清楚一些基本概念。前面所谓的”知识“其实并不确切,那么,到底什么是知识,以及知识与其他的概念有什么关系呢?

正如每个孩子都珍藏过小石头,每个IT人士都收集资料。我手头也有多年积累的大量文档。但这些文档仅仅是资料,时效性很低(尽管我在技术上不是很追求时髦),可能1-2年的时间就变成了硬盘上的垃圾,需要经常清理。

要让这些资料发挥作用,就必需及时进行归纳、整理、沉淀,将其中有价值的内容纳入自己已有的知识体系。只有这样,资料才能转化为知识,并在需要的时候发挥作用。

世上从来不缺少这种人,才高八斗学富五车却四体不勤五谷不分,此谓“懒”;满腹经纶却不通世故,谓之“呆”。 “懒”者,懒得去解决问题。毕竟学习知识是一种输入,解决问题是一种输出,后者的难度更大,需要的思考更多,于是乎懒得去做。 ”呆“者,知识的奴隶,空有满腹学识却根本不知如何运用,知识成了死知识,而人成了书呆子。 究其根本,就是知识没有转化为“生产力”。如果能够运用知识解决问题,这就叫做智慧。

知识之间有很多共通之处,有些人能够发现知识之间的规律,并运用这些规律产生出新的知识为别人所用,这叫做”有思想“。思想需要领悟,只靠学习是没有用的。

要做到所有这一切,需要研究并务实,合作且独立,并经常自省;需要内心的强大,这叫做精神。精神才是一切行动的源动力。

简言之:

信息的管理产生知识

知识的运用产生智慧

知识的积累产生思想

一切的根本在于精神

3 知识的条理

知识的条理可以有很多,但够用就好。过多的线索反而会让知识丛林变得扑朔迷离,难以形成全貌。对于IT人员来说,可能以下几方面的线索就够用了。

3.1 业务领域

如果你在从事企业级应用相关的工作,对于业务领域(Business Domain)的划分是非常重要的,这可以使你明确当前的主题能够解决企业哪方面的问题。

业务领域的知识解决“是什么”的问题。根据不同的业务分析和表示方法论,有不同的描述方式,但大体来说总要包含以下几个部分:

  • 领域模型
  • 业务服务
  • 业务流程
  • 业务角色

对于一些比较成熟的领域,会有相应的参考模型,如供应链的scor模型,研发领域的pace或ipd,项目管理的pmbok,软件工程的cmmi等。 这些模型都会包含上述的基本内容,而且适用范围比较广泛。

对于业务领域的划分,可以参考企业管理中的层次及方面两个维度,如下表:

 
企业管理 销售与市场 生产 财务 人力资源 IT
决策层
管理层
操作层

随着对某个领域的不断深入,该领域会越来越细分,比如对于IT管理这个方面,可能会划分为:

  • IT管理
    • 决策层
      • 企业架构
        • IT策略
        • IT组织及治理策略
        • IT规划
    • 管理层
      • IT资产管理
      • IT绩效
      • IT项目管理
      • 安全管理
      • 采购管理
    • 操作层
      • 应用管理
      • IT基础设施管理

3.2 行业划分

有些知识可能是某个或某类行业专属的,并不适用于所有行业,所以还需要划分一个”行业“的维度。这个维度与业务领域进行正交。 行业划分不一定精确,完全可以按照你自己的知识结构进行划分。可以在一定程度上使用树。

比如我自己的行业划分如下:

  • IT服务
    • 硬件维护和支持
    • 软件维护和支持
    • 咨询服务
      • 管理咨询
        • IT咨询
    • 开发和集成
      • 定制开发
        • 应用集成
        • 应用部署
    • IT外包服务
      • 应用外包
        • 平台外包
        • 基础设施外包
  • 金融行业
    • 证券
    • 银行
    • 保险
    • 基金
  • 媒体行业
  • 支付服务
  • 电信行业
  • 政府行业
    • 区域政府
    • 国家部委
  • 制造业
  • 零售业

3.3 类别

很多东西都可以成为知识,但知识确实有很多种。不同种类的知识,其适用范围也各不相同。 比如“产品生命周期管理”是制造业产品管理领域的知识,但是其对应的“生命周期理论”又是一种理论类的知识。 再比如敏捷开发、面向对象都属于方法论的范畴,而emacs是一种开发工具。

对于IT人员,下面的类别都是比较常用的:

  • 理论
  • 方法论
  • 解决方案
    • 技术解决方案
    • 业务解决方案
    • 行业解决方案
  • 技术
    • 应用开发技术
    • 网络技术
  • 工具
  • 模板 都涉及到标准规范,指导书,培训教材,模板,检查单这些核心内容,这相当独立来看是完整的一套体系。

其中需要专门说明的是解决方案类。解决方案是对知识的一种运用,但针对运用的结果和经验进行总结,就会形成新的知识。 解决方案根据运用的范围不同,可以分为技术解决方案、业务解决方案和行业解决方案。显然,他们会分别知识类别中的技术、业务领域中的层次和方面、行业划分 相交叉。

3.4 掌握程度

对于学习来说,认识到自己对于某项知识的掌握程度非常重要,可以使学习的目的性更强。一般我们喜欢用知道,了解,熟悉和精通来表示掌握的程度。

  • 知道 知道有这么一类知识,知道其定位及能够解决的问题,但是对细节不清楚。
  • 了解 掌握此类知识大体包含哪些部分的内容(即细分),在需要时可以通过参考资料进行使用。
  • 熟悉 掌握60%以上的要点,并具备实际解决问题的经验。
  • 精通 掌握80%以上的要点,具备多次运用的经验,能够指导别人如何学习或运用。

4 知识地图

前面介绍了对于IT人员比较实用的对知识进行条理化的一些维度,包括领域(层次+方面)、行业、类别、掌握程度等。 综合运用这些条理,对知识进行定位和标识,梳理知识之间的关系与关联,就能够使知识以有序的面貌呈现处理,极大提高知识的利用率。 这就是知识地图, Knowledge Map,简称K-Map.

4.1 知识地图的作用

  • 导航 对知识的导航是知识地图最根本的功能,通过知识地图能够通过多种方式和线索找到需要的知识。
  • 关联 知识之间存在各种关系,如上述每个维度内的层级关系,维度之间的正交关系和关联关系等等。知识之间的关联形成一个网状结构。
  • 盘点 构建了知识地图,也就有了关于知识的清册(Category)。可以随时盘点自己已经掌握了哪些知识,掌握到何种程度,以及还有哪些知识需要扩充。

4.2 如何表述知识地图

到这里,你可能已经迫不及待要建立自己的知识地图了。表述知识地图主要有两种方法:

一种是显式表达,可以用文字,或图形化的工具(如xmind,visio等)专门进行描述。

另一种是隐式表达,没有专门的知识地图说明,但是将知识地图运用在各种工具中,比如文件和目录结构,gmail,evernote,emacs org-mode, 以及blog中。

5 小结

本文从自身的经历出发,说明了个人知识管理的必要性。探讨知识和文档资料的区别与联系,以及如何对知识进行条理化。 最后引出了知识地图这一工具,描述了其作用以及如何使用知识地图。希望能够对读者构建自己的知识体系架构带来帮助。 在后续的文章中,会继续讨论知识地图在blog、文档管理、gmail、evernote、org-mode上的运用。

目前已经完成的内容包括:

如何规划blog的标签(tag)和分类

[转载]为List内部添加一个“删除多个元素”的方法 - 老赵点滴 - 追求编程之美

mikel阅读(868)

[转载]为List内部添加一个“删除多个元素”的方法 – 老赵点滴 – 追求编程之美.

不久前我在微博上提出一个问题:众所周知,.NET中自带的List<T>集合类型没有“删除多个元素”的方法,那么假如我们是.NET类库中List<T>的实现者,我们该如何添加这么一个方法?例如:

namespace System.Collections.Generic
{
    public class List<T>
    {
        private T[] _items;
        private int _count;

        public void RemoveMultiple(IEnumerable<T> itemsToRemove)
        {
            // Replace the implementation here foreach (var item in itemsToRemove)
            {
                this.Remove(item);
            }
        }
    }
}

其中元素保存在_items数组中,而_count则保存当前元素的个数。我这里给出了一个实现来体现这个方法的含义,但很显然这并不是一个合适的做法。原因有几个,一会儿会提到,但最重要的自然就是效率很低。请注意这里我们是“内部实现者”,因此肯定就是要提供一个高效的,并且尽可能通用的实现。

有些同学表示如果要高效,则不应该用List<T>这种数据结构。这个思路似乎考虑周到,但实际上很让人捉急,因为这还是种代码“消费者”的习惯,而不是代码的“提供者”。记得以前我也提出过一些简单的题目,写明“抛出异常”,但大部分答案依旧在try...catch,我认为这是同样的原因。在我看来,假如要提高技术水平,一定要把思维观念从技术的“消费者”切换为“提供者”,因为提供者能影响更多人,会让自己对自己编写的代码要求更高。

其实这道题目没有标准答案,但是很容易判断出某一个实现好不好,对不对,有哪些缺陷等等。这题的确十分简单,但是会有不少细节方面值得考虑,所以我反复强调,光有思路是不够的,一定要写出代码来。

首先,我想代码判断了参数的合法性,也就是null与否。其次,您觉得该如何使用itemsToRemove比较合适?一定要从头枚举吗?这里举一个.NET中自带的例子:

namespace System.Linq
{
    public static class Enumerable {
        public static int Count<TSource>(this IEnumerable<TSource> source)
        {
            checked {
                if (source == null)
                {
                    throw new ArgumentNullException("source");
                }

                var collection = source as ICollection<TSource>;
                if (collection != null) return collection.Count;

                var collection2 = source as ICollection;
                if (collection2 != null) return collection2.Count;

                int num = 0;
                foreach (var item in source) num++;
                return num;
            }
        }
    }
}

可见,为了提高效率,有时候我们会考虑使用下标,而不是直接foreach来进行计算,因为如常见的数组或是List<T>这种枚举类型,使用下标访问的速度会比使用枚举器有一定程度的提高。另外,一般来说在使用IEnumerable的时候切忌多次遍历。

假如我们使用最传统的foreach配合现成的Remove方法来实现RemoveMultiple方法,其时间复杂度是O(M * N),其中N是目前List<T>中的元素个数,M是itemsToRemove中的元素数量。这种是最差的时间复杂度,原因是每删除itemsToRemove中的一个元素,就需要从整个列表的起始位置找起。于是,有些同学想到把被删除的元素放在一个HashSet中,这样确定单个元素是否需要删除的时间复杂度是O(1),而构建这个HashSet的时间复杂度是O(M),于是总共是O(M + N),这显然比O(M * N)要好太多。

但问题在于,HashSet是不够的,因为集合中的元素不可重复,而itemsToRemove中显然是可能有重复元素的,这意味着要从列表中删除多个相同的元素,这个需求也很正常,因为List<T>中本身就有可能重复。于是一个比较容易想到的做法便是建立一个字典,保存某个元素需要被删除的次数。装配脑袋的做法便是如此:

public void RemoveMultiple(IEnumerable<T> itemsToRemove)
{
    var removingItems = new Dictionary<T, int>();

    foreach (var item in itemsToRemove)
    {
        if (removingItems.ContainsKey(item))
        {
            removingItems[item]++;
        }
        else {
            removingItems[item] = 1;
        }
    }

    var setIndex = 0;

    for (var getIndex = 0; getIndex < _count; getIndex++)
    {
        var current = _items[getIndex];
        if (removingItems.ContainsKey(current))
        {
            removingItems[current]--;
            if (removingItems[current] == 0)
            {
                removingItems.Remove(current);
            }

            continue;
        }

        _items[setIndex++] = _items[getIndex];
    }

    _count = setIndex;
}

不过从细节上说,这个做法还是有些改进空间。例如,一次removingItems[item]++实际上就会访问两次字典,一次取值,一次是加一后设置回去,在此之前还有个ContainsKey判断。字典的读写操作理论上是O(1),但实际上在内部会调用每个元素的GetHashCode方法,以及一次或多次Equals,这对于没有重载过这两个方法的引用类型,或是int等基础类型来说比较迅速,但假如T是一个字符串,开销还是会大上好几倍。因此,我们还是希望可以尽量少地访问字典。在我目前工作中的项目里,表示一个属性的可选方式是一个数字下标,这样很多地方就可以直接使用数组来保存与属性有关的映射关系,而不需要用PropertyInfo甚至更慢的字符串来查找字典。

为了解决这个问题,最好是编写一个特定的数据结构——但并不“特殊”,因为这就是个典型的Bag(也称为MultiSet)),顾名思义,便是允许重复元素的集合。在一个Bag中,相同元素可以被添加或删除多次。

public void RemoveMultiple(IEnumerable<T> itemsToRemove)
{
    var removingItems = new HashBag<T>(itemsToRemove);

    var setIndex = 0;

    for (var getIndex = 0; getIndex < _count; getIndex++)
    {
        var current = _items[getIndex];
        if (removingItems.Remove(current)) continue;

        _items[setIndex++] = _items[getIndex];
    }

    _count = setIndex;
}

HashBag内部,我们每次添加和删除元素只需要访问一次代码,便可以增加或删除该元素的计数器。封装一个通用的HashBag容器之后,连代码都变的简单很多。但是,使用基于哈希容器一般会占用相对比较大的空间,为了提高效率及节省空间,在可行的情况下,我们还可以为在创建HashBag的 时候指定尺寸,或者权衡之下选用二叉树而不是基于哈希的Bag,这样时间复杂度会变成O(log(M) * N),但空间使用可以节省许多。此外,我们还可以尝试建立一个现有元素至其下标的映射,先找出需要删除的位置,最后进行一次移动。这在不同的M和N大小关 系时都是可能的选择。

但问题是,这就足够了吗?以上这段实现实际上还有错误!请注意,最后我们虽然把setIndex赋值给_count,但是setIndex和旧的_count之间的元素,我们并没有设成null(确切地说应该是default(T)),这就会造成内存泄露。总有同学说托管程序不会出现内存泄露,这我不同意,托管程序还是程序,并不能阻止程序员犯错误嘛。不过话又说回来,我们一定需要将那些位置设为null吗?答案是否定的,对于像int等基础类型来说,我们就完全无需实现这点。

不过话说回来,你会如何判断一个T是否需要设为null?是在RemoveMultiple内部每次判断下typeof(T)吗?答案依旧是否定的,只需要定义一个静态成员即可。要记住,List<int>List<string>是两个不同的类型,它们的静态成员是分开存放的。这有时候会带来问题,但善加利用也会收到很好的效果

这些都是细节。

其他还有一些值得考虑的有意思的地方。例如,您的实现如果遇上list.RemoveMultiple(list)这种用法,会出现什么样的情况?例如,itemsToRemove的数量假如远大于当前的元素数量,其代价是否过高?例如,假如itemsToRemove是一个无限长的枚举,但到某一个阶段却可以把所有当前元素删光,那么您的实现能否直接返回?

这些都是RemoveMultiple方法可能会遇到的状况。我这里做出的假设是可以修改List<T>的内部实现,因此我们甚至可以开一个新的数组赋值给_items——假如它再某个情况下有帮助的话。当然,作为一个通用的实现的来说,一个方法应对大部分的情况就够了,我们无法顾及各种环境下的最坏情况。在实际情况下,有时候我们知道更多条件,甚至选择更合适的做法,并且使用反射从外部设置_item_count

假如我用这道题目来进行面试,以上便是我会考察的一些思路。正像我一开始说的那样,这题没有标准答案,更关键的是对于思路的考察,考察一个程序员考虑问题是否全面。我虽然列举了那么多,但肯定也有我没有想到的地方。

但是,光有思路是不够的,也一定要写出代码来。

[转载]如何让您的事件支持逆变 - 老赵点滴 - 追求编程之美

mikel阅读(817)

[转载]如何让您的事件支持逆变 – 老赵点滴 – 追求编程之美.

在.NET里“事件”是一种无比常见的成员,我在项目里也经常暴露一些事件供其他地方使用。在.NET里定义一个事件会需要一个委托类型,一般来说我们会使用.NET里自带的System.EventHandler类型,它的签名是:

public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);

但这个定义其实有稍许缺陷。例如,如果您在自己的项目中编写了这样的代码,Resharper这样的工具便会提醒您“TEventArgs可以设为逆变”。协变和逆变C# 4中引入的非常有用的功能,可以在保证类型安全的前提下让代码变的更加好用。因此,我在项目里往往会使用自己的CoEventHandler委托类型:

public delegate void CoEventHandler<in TSender, in TEventArgs>(TSender sender, TEventArgs args);

可以看出,我们只需要为TSender增加一个in标记就够了,我们甚至可以连sender的类型也一并逆变起来。接下来我们自然可以用这个委托类型来定义事件,例如:

public class MyClass {
    public event CoEventHandler<MyClass, List<int>> MyEvent;
}

有人可能会说:这不行啊,事件参数怎么可以不是System.EventArgs的子类呢?我的回应是:谁说事件参数一 定要是它的子类?这只是一种常见的“约定”,最多上升为“规范”,但这种限制其实并没有带来额外的好处。事实上.NET框架本身也意识到这种限制是没有什 么必要的,因此它在.NET 4.5中也将这一限制去除了。正如文章最初贴出的代码,其实是.NET 4.5中的定义,而在.NET 4里的定义却是这样的:

public delegate void EventHandler<TEventArgs>(object sender, TEventArgs args)
    where TEventArgs : System.EventArgs;

看见没?.NET 4.5将这个没有什么必要的限制去掉了,在以后的文章中我也会描述下这么做的优势。而我们现在只不过更进一步,将两个参数都泛型化,并让它们支持协变而已。于是,我们便可以为事件添加各种兼容的接口了:

static void StrongTypedHandler(MyClass sender, List<int> args) { }

static void WeakerTypedHandler(object sender, ICollection<int> args) { }

static void Main()
{
    var myClass = new MyClass();
    myClass.MyEvent += (CoEventHandler<MyClass, List<int>>)StrongTypedHandler;
    myClass.MyEvent += (CoEventHandler<object, ICollection<int>>)WeakerTypedHandler;
}

这段代码完全可以编译通过,但是执行时却会抛出异常:

System.ArgumentException: Delegates must be of the same type.
   at System.MulticastDelegate.CombineImpl(Delegate follow)
   at TestConsole.MyClass.add_MyEvent(CoEventHandler`2 value)
   at TestConsole.Program.Main() in ...

还记得我们用上面最普通的方式定义一个事件的时候,C#编译器会帮我们生成什么样的代码吗(不知道的同学请参考CLR via C#)?“自动事件”生成的代码,最终会使用Delegate.Combine来实现多重委托。不过,尽管C#编译器和运行时支持逆变,但Delegate.Combine是不支持的,这就导致了运行时异常。因此,假如您定义的事件支持逆变,则完全不能“偷懒”去使用“自动事件”,必须编写代码来手动增删事件处理器。

当然,事实上这个问题跟“事件”没有必然联系,各种期望使用多重分派委托的地方都会遇到相同的问题,所以我们解决的问题完全可以更泛化一些。我们可以构造一个MulticastDelegateManager来解决这个问题,定义如下:

public class MulticastDelegateManager<TDelegate>
{
    public MulticastDelegateManager(bool isThreadSafe) { }

    public void Add(TDelegate value) { }

    public void Remove(TDelegate value) { }

    public void Invoke(Action<TDelegate> invoke) { }
}

其中AddRemove自然是用于添加和删除一个委托,而Invoke在执行时则需要传入一个“执行器”,用于执行每个已经添加的委托对象,这样便可以统一。

构造一个MulticastDelegateManager对象时,我们可以指明它是否会工作在多线程的环境里。假如我们确定这个事件无需多线程支持,则可以将isThreadSafe设为false,于是各类操作将会放弃多线程保护,对效率会有一定好处。反之,则AddRemove以及Invoke方法都可能在一个并发环境中使用。具体一点便是,Invoke本身在调用时无法“重入”,每次调用都是互斥的。但是,尽量也让并发度高一些为好。

此外,传统多重委托在执行时,假如某个委托抛出了异常,测后续的委托便不会执行了。这对于“事件”来说可能会产生较为严重的问题。因此,我希望Invoke在执行时必须保证每个委托被调用过。当然,我们也不能简单的吞噬异常。

要不您来试试看写这么一个MulticastDelegateManager?不过请不要仅仅给出“思路”,千万要写下代码来,否则您的思路不说也罢。这个问题的确简单,但和上次的问题一样,不仔细考虑的话还是挺容易出现一些问题的。

[转载]程序在发布前就应该发现的一些错误 - Fish Li - 博客园

mikel阅读(812)

[转载]程序在发布前就应该发现的一些错误 – Fish Li – 博客园.

在软件开发过程中,写出影响性能或者有BUG的代码,都是我们无法回避的现实问题。
不过,如果能够在程序发布前(自测或者测试阶段)将这些问题找出来,我想大家都是可接受的。

今天就来介绍一种方法,用来发现在网站开发过程中,容易被我们忽略的一些问题,而这些问题其实是容易被发现的。
将要介绍的方法需要使用Fiddler这样一款工具, 我将演示如何使用Fiddler来发现404错误,以及较大的响应输出问题。
我认为这二个问题实在太低级了,所以我设计了这个方法,并写了这篇博客,希望大家能喜欢。

我发现,许多人对于这二类问题(404错误和较大的响应输出)都很不在意,好像它们根本不会对一个网站有任何影响似的。
难道真是这样吗?
我认为:如果你做的网站程序,用户访问量很小,或许的确可以忽略它们。
否则,我还是建议你应该纠正它们,下面我来解释它们的危害。

404错误

我一直认为404不仅仅只是一个数字,过多的404也会影响程序的性能。

通过对404错误的分析,我发现多数的404错误都与一些资源文件的引用有关,
比如代码中引用了不存在CSS或者JS文件,这些404错误发生时,可能并不会影响页面的正常显示,
因此,这类错误根本就不会引起一些开发人员的注意。
再加上,许多人又喜欢复制粘贴,导致这类错误越来越多。

为什么我会说【过多的404错误也会影响性能】呢?
因为当404错误产生时,IIS其实并不只是返回这样一个数字,而是一个完整的HTTP响应,响应的内容是一个正常的网页。
不同的IIS版本的这个404的错误页面长度并不相同,IIS6默认的404错误页面长度超过2K,而IIS7.5的默认错误页面会超过8K 。
虽然这个响应看起来并不大,但是由于请求不成功,每当打开这些页面时,请求会重新发起,数量会越来越多。
反过来,我们可以想一下:如果要引用的资源文件存在,这些文件仅仅需要请求一次,浏览器就会缓存它们,
根本不需要每次都重新发起请求。
这样一来,客户端减少了请求数量,服务器减轻了连接压力,那些无意义的404响应所浪费的网络流量也能消失。
因此,过多的404请求简直是一个恶性循环,
它延长了页面的显示时间(前端),给服务端带来了连接压力,也浪费了网络资源。

较大的响应输出

较大的响应输出,应该是容易理解的,那就是:服务端返回的结果太大了。
我们可以想像一下【较大的响应输出】意味着什么。
1. 浏览器显示一个【很大的网页】,是不是会比较慢?
2. 【很大的网页】是不是会花费较长的网络传输时间?
3. 服务端生成【很大的网页】,是不是也要花较长的生成时间?
4. 如果这个【很大的网页】的结果来自于数据库的查询结果,会不会给数据库也带来较大的压力?

产生这种情况就典型的场景可能由于一条SQL查询引起的: select * from XXX where name=@name
或许在早期阶段,XXX表的记录很少,或许当初在设计时根本没想到name会存在一大堆的复制数据时,
再或者,当在本地环境测试时,网速根本不是问题,而浏览器的渲染速度的延迟又没有被发觉时。
我们可以想像一下:这样的程序如果部署在互联网上运行,结果会如何?

关于【较大的响应输出】,还有二个可能发生的场景:
1. 往ViewState中放入一个很大的对象。
2. 展示一个树形结构,或者是一个没有where条件的查询(都属于不分页情况)

当以上这三类情况发生时,你认为性能还能接受吗?用户还会满意吗?

用Fiddler发现这些问题

前面我详细说明了二类低级错误的危害,下面再来说说如何尽早地发现它们。
我想许多人都应该用过Fiddler,它能够方便地让我们知道浏览器发起的每个请求的Request/Response, 通常用于调试程序。

在Fiddler中,404错误的请求会用红字醒目地显示,每个请求的响应长度也会单独地显示出来, 貌似直接用Fiddler也能容易发现404错误以及较大的响应输出问题。
然而,当访问过多的页面后,Fiddler会显示非常多的请求记录,
因此,那些低级问题会被淹没,我们要想发现它们,可能需要花费一点时间。

针对这个问题,我为Fiddler定义了二个规则:

只要打开它们,前面所说的二类低级问题很容易就能发现:

注意:这里只显示符合规则的请求(存在低级问题的请求)。

该怎么合理地使用这个方法呢?
1. 如果你是开发人员,请在自测时,打开Fiddler,并选择我定义那二个规则,
2. 如果你是测试人员,请在测试时,打开Fiddler,并选择我定义那二个规则,
3. 然后,你们平时该做什么就做什么吧,。。。。。。
4. 测试结束后,再看一下Fiddler窗口,有没有记录显示出来,如果有,那就是发现低级问题了。

所以,我认为这个方法不会给开发人员以及测试人员带来过多的负担,
毕竟,这个方法不会给他(她)们测试时增加任何负担,
唯独要求打开一下Fiddler,最后在测试完成后,再来看一眼,仅此而已。

或许有些人认为:分析服务器的IIS日志,也能发现这二类问题。
是的,我知道分析IIS日志也能发现这些问题,
但是,分析IIS日志,是不是晚了?
你想过没有:这样的问题是不是已经影响了用户?
反之,不让用户【体验】这些问题,是不是更好?
换句话说:你是否希望发布一个有缺陷的程序?

如何自定义Fiddler过滤规则

如果希望自定义Fiddler规则,建议安装:Syntax-Highlighting 这个Fiddler插件。
然后,打开自定义规则窗口:

此时,会显示Fiddler的规则代码,供你修改:

在这个窗口中,右边显示了能在自定义规则中使用的一些对象类型, 以及它们的字段(绿字),属性(蓝字)与方法(黑字)。
我们可以在写规则时参考这些信息。

说明:此规则文件保存在:x:\My Documents\Fiddler2\Scripts\CustomRules.js

还记得我前面的截图中:我在Fiddler的Rules菜单下面增加了二个自定义规则 吗?
定义规则菜单的代码在前面的截图中(找汉字就能发现,最后4行代码)。

菜单定义后,还需要在OnBeforeResponse方法中添加一些处理代码:

最后,我还要再说一句:
如果你不希望发布有缺陷的程序,并且不希望后期返工,那么可以尝试一下本文介绍的方法。

点击此处下载我的CustomRules.js (Fiddler版本 2.4.1.1)

[转载]网络协议模拟之QQ微博分享接口应用 - 凌晨的搜索者 - 博客园

mikel阅读(704)

[转载]网络协议模拟之QQ微博分享接口应用 – 凌晨的搜索者 – 博客园.

QQ微博在营销领域越来越受青睐了,这里面集成了很多非常有用的接口,像是邮件分享、空间分享、QQ分享、微信分享等。这相对于传统的直接模拟协议,登录 邮箱等方式进行邮件发送甚至更有效。所有这些都没什么技术难度,所以实现起来是很简单的。如果在开发过程中遇到了些困难的话,可能是多线程的把握吧!

在这样一些营销类项目之中,绝大多数都设计了多账号切换操作,这使得整个架构控制起来异常繁琐。对于多线程功底稍差的人来说,加上UI设计的搭配,简直就 是地狱般的折磨。项目实战开发最需要的就是经验的积累,平时练习的时候就得多为下一次开发做技术上的准备,否则一旦开始就会显得很吃力耗时。

上图只是我一个项目里的一张截图,我们要说的功能大致就是上面图中所表现出来的。但是这里,只做一个功能实现描述,不作项目完整实现。

步骤:

1.基础功能的类准备

1.处理xml的xmlHelper,2.处理Http模拟的HttpHelper 3.处理QQ登录验证密码MD5加密的QQMd5 4.其他一些Util功能实现类

所有这些类我会稍后放在文章附件里。

2.创建Account用户资料类[登录]

下面的这个类我只是给各位一个例子,可以展开看一下,但是还是要自己依据自己情况具体分析。
 

View Code 

public class Account
   {
       #region 属性
       /// <summary>
       /// 帐号登录的顺序号码
       /// </summary>
       public int ID { get; set; }
       /// <summary>
       /// QQ帐号
       /// </summary>
       public string Uin { get; set; }
       public List<string> Froms { get; set; }
       public string Mail { get; set; }
       /// <summary>
       /// 昵称
       /// </summary>
       public string Nick { get; set; }
       /// <summary>
       /// 我的首页
       /// </summary>
       public string MyPage { get; set; }
       /// <summary>
       /// QQ未加密的密码
       /// </summary>
       public string Password { get; set; }
       /// <summary>
       /// QQ加密密码
       /// </summary>
       public string P { get; set; }
       /// <summary>
       /// QQ微博的Cookie容器
       /// </summary>
       public CookieContainer WeiboCookieContainer { get; set; }
       /// <summary>
       /// QQ微博的Cookie字符
       /// </summary>
       public string WeiboCookieString { get; set; }
       /// <summary>
       /// QQ微博对应的Sid
       /// </summary>
       public string Sid { get; set; }
       /// <summary>
       /// 目前帐号的状态
       /// </summary>
       public Status CurrentStatus { get; set; }
       /// <summary>
       /// 帐号类型
       /// </summary>
       public AccountStyle AccountStyle { get; set; }
       /// <summary>
       /// 是否需要验证码
       /// </summary>
       public bool NeedVerify { get; set; }
       /// <summary>
       /// 验证码图片
       /// </summary>
       public Image VerifyImage { get; set; }
       /// <summary>
       /// 验证码字符串
       /// </summary>
       public string VerifyCode { get; set; }
       
       #endregion
       #region 构造函数
       public Account()
       {
           Init();
       }
       public Account(string uin, string password)
       {
           this.Uin = uin;
           this.Password = password;
           Init();
       }
       #endregion
       #region 私有函数
       private void Init()
       {
           this.WeiboCookieContainer = new CookieContainer();
           this.CurrentStatus = Status.Unlogin;
           this.AccountStyle = AccountStyle.Unknow;
       }
       #endregion
}
}

上面的那些都不是关键,我们要做的第一步还是完成“判断验证码>输入验证码(如果有的话)>登录”,除了要我完成Http模拟请求之外还要从返回的结果中提取有用的信息。

#region 登录过程
       /// <summary>
       /// 判断登录微博是否需要验证码
       /// </summary>
       /// <returns></returns>
       public bool CheckVerify_Weibo(ref string vctype)
       {
           string url = "", html;
           url = string.Format("http://check.ptlogin2.qq.com/check?uin={0}@qq.com&appid=46000101&ptlang=2052&r={1}", this.Uin,new Random().NextDouble());
           html = HttpHelper.GetHtml(url, this.WeiboCookieContainer);
           if (string.IsNullOrEmpty(html)) return false;
           string pattern = @"ptui_checkVC\('(?'need'[^']*)','(?'vctype'[^']*)','[^']*'\);";
           string need = html.Match(pattern, "need");
           vctype = html.Match(pattern, "vctype");
           if (need == "1")
           {
               this.CurrentStatus = Status.NeedVerify;
              this.VerifyImage= this.GetImage();
               return true;
           }
           else
           {
               this.CurrentStatus = Status.Unlogin;
               this.VerifyCode = vctype;
               return false;
           }
         
       }
       /// <summary>
       /// 获取显示验证码
       /// </summary>
       /// <returns></returns>
       public Image GetImage()
       {
         string url = string.Format("http://captcha.qq.com/getimage?aid=46000101&r=0.38706237439032276&uin={0}@qq.com", this.Uin);
           Stream stream = HttpHelper.GetStream(url, this.WeiboCookieContainer);
           Image image = Image.FromStream(stream);
           return image;
       }
       /// <summary>
       /// 登录
       /// </summary>
       /// <returns></returns>
       public string Login()
       {
           string uin,password,verifyCode;
           uin = this.Uin;
           password = this.Password;
           verifyCode = this.VerifyCode;
           string html="";
        if (string.IsNullOrEmpty(uin) || string.IsNullOrEmpty(password))
               throw new Exception("没有添加帐号,或帐号的密码为空");
           if (string.IsNullOrEmpty(verifyCode))
               throw new Exception("验证码为空,无法继续登录");
           this.P = QQMd5.Encrypt(uin,password,verifyCode);
           string url = string.Format("http://ptlogin2.qq.com/login?ptlang=2052&u={0}@qq.com&p={1}&verifycode={2}&low_login_enable=1&low_login_hour=720&css=http://imgcache.qq.com/ptcss/b4/wb/46000101/login1.css&aid=46000101&mibao_css=m_weibo&u1=http%3A%2F%2Ft.qq.com&ptredirect=1&h=1&from_ui=1&dumy=&fp=loginerroralert&action=7-9-1381063&g=1&t=1&dummy=",
                                     uin, this.P, verifyCode);
           html = HttpHelper.GetHtml(url, this.WeiboCookieContainer);
           if (html.Contains("您输入的验证码不正确,请重新输入"))
               throw new Exception(string.Format("验证码输入不正确"));
           else if (html.Contains("您输入的帐号或密码不正确,请重新输入"))
               throw new Exception(string.Format("帐号或密码不正确"));
           else if (html.Contains("登录成功"))
           {
               html = string.Format("登录成功");
               this.CurrentStatus = Status.Login;
             
           }
         
           return html;
         }

由于本人不太喜欢用已经做好的轮子去用Json处理类(比如Newtonsoft.net),所以一般处理都是用的正则表达式,各位也可以自己用JsonObejct类去处理,不用跟我一样用正则这种笨方法。
3.获取必要信息[提取微博数据]

public string GetInfo()
       {string html="";
           try
           {
               string url = "http://t.qq.com";
               html = HttpHelper.GetHtml(url, this.WeiboCookieContainer);
               string pattern = @"href=""(?'mypage'[^""]*)""><u>首页";
               string mypage = html.Match(pattern, "mypage");
               this.MyPage = mypage;
               if (string.IsNullOrEmpty(mypage)) throw new Exception("获取微博话题列表失败");
               pattern = @"boss=""btnWideSideMyNick"">(?'nick'[^<]*)</a>";
               string nick = html.Match(pattern, "nick");
               if (html.Contains("立即开通")) this.CurrentStatus = Status.NotRegist;
               this.Nick = nick;
               html = "成功获取微博话题列表";
           }
           catch(Exception e)
           {
               html = e.Message;
           }
           return html;
       }
       public List<FriendInfo> GetQQFriendList()
       {
           string account = ""; string r = "1351620387406";
           account = this.MyPage.ToLower().Replace("http://t.qq.com/", "");
           string url = string.Format("http://api.t.qq.com/share/qqList.php?account={0}&r={1}&apiType=8&apiHost=http://api.t.qq.com&_r={1}",
                                                account,r);
           HttpHelper.Referer = this.MyPage;
           HttpHelper.ExtendHeadData = string.Format("rf:{0}", this.MyPage);
           string html = HttpHelper.GetHtml(url, this.WeiboCookieContainer);
          
           HttpHelper.Referer = "";
           HttpHelper.ExtendHeadData = "";
           Regex regex = new Regex(@"(""sortId"":(?'sordId'[^""]+),)*""name"":""(?'name'[^""]+)"",(""groupId"":(?'groupId'[^,]+),)*(""member"":(?'member'\[[^\]]*\]))*", RegexOptions.IgnoreCase);
           MatchCollection matches = regex.Matches(html);
           List<FriendInfo> list = new List<FriendInfo>();
           for (int i = 0; i < matches.Count; i++)
           {
               
               string sortId = matches[i].Groups["sortId"].Value;
               string name = matches[i].Groups["name"].Value;
               string groupId = matches[i].Groups["groupId"].Value;
               string member = matches[i].Groups["member"].Value;
               Regex memberRegex = new Regex(@"""qq"":""(?'qq'[^""]*)"",""pic"":null,""nick"":""(?'nick'[^""]*)""", RegexOptions.IgnoreCase);
               MatchCollection memberMatches = memberRegex.Matches(member);
               for (int j = 0; j < memberMatches.Count; j++)
               {
                   string qq = memberMatches[j].Groups["qq"].Value;
                   string nick = memberMatches[j].Groups["nick"].Value;
                   FriendInfo friendInfo = new FriendInfo();
                   friendInfo.Uin = qq;
                  friendInfo.Name = WebQQ.Converter.Unicode_js_1(name);
                 
                   friendInfo.Nick = WebQQ.Converter.Unicode_js_1(nick);
                   if (friendInfo.Name == "最近联系人") continue;
                   friendInfo.SortId = sortId;
                   friendInfo.GroupId = groupId;
                   friendInfo.QQ = Uin;
                    list.Add(friendInfo);

               }


           }
           return list;
       }

上面这一步不一定是必须的,但是没有这一步,我们后面所要实现的功能就会很困难。包括发送分享给QQ,这一步,因为要发送的QQ好友的号码不是真正的号码,而是一个系统随机生成的好友序列号的MD5加密字段。因此,我们也无法用于分享QQ给陌生人号码。

4.分享

#region 分享
       public string MailShare(string shareId,string toList,string subject,string reason)
       {
           string url, html;
           url = "http://api.t.qq.com/mail/mailShare.php";
           HttpHelper.ExtendHeadData =string.Format("rf:{0}",this.MyPage);
           HttpHelper.Referer = "http://api.t.qq.com/proxy.html";
           string mail = HttpUtility.UrlEncode(Encoding.UTF8.GetBytes(this.Mail));
           string maillist = HttpUtility.UrlEncode(Encoding.UTF8.GetBytes(toList));
            subject = HttpUtility.UrlEncode(Encoding.UTF8.GetBytes(subject));
            reason = HttpUtility.UrlEncode(Encoding.UTF8.GetBytes(reason));
           string postString = string.Format("mailAddr={0}&mlist={1}&subject={2}&body={3}&reason={4}&apiType=8&apiHost=http://api.t.qq.com",
                                           mail,toList,subject,shareId,reason);
           html = HttpHelper.GetHtml(url, postString, this.WeiboCookieContainer);
           HttpHelper.Referer = "";

           return html;
       }
       public string ShareQZone(string shareId, string reason)
       {
           string url = "http://api.t.qq.com/share/shareQzone.php";
           HttpHelper.ExtendHeadData = string.Format("rf:{0}", this.MyPage);
           HttpHelper.Referer = "http://api.t.qq.com/proxy.html";
           reason = HttpUtility.UrlEncode(Encoding.UTF8.GetBytes(reason));
           string postString = string.Format("id={0}&reason={1}&apiType=8&apiHost=http://api.t.qq.com",shareId,reason);
          string html = HttpHelper.GetHtml(url, postString, this.WeiboCookieContainer);
           HttpHelper.Referer = "";
           return html;
       }
       public string ShareMsg(string shareId,string uins,string group)
       {
           string url = "http://api.t.qq.com/share/shareMsg.php";
           string postString = string.Format("id={0}&uins={1}&group={2}&apiType=8&apiHost=http://api.t.qq.com",
                                   shareId,uins,group);
           HttpHelper.ExtendHeadData = string.Format("rf:{0}", this.MyPage);
           HttpHelper.Referer = "http://api.t.qq.com/proxy.html";
           string html = HttpHelper.GetHtml(url, postString, this.WeiboCookieContainer);
           HttpHelper.Referer = "";
           return html;
       }
       public string Pm_Mgr(string content,string target)
       {
           string url = "http://api.t.qq.com/inbox/pm_mgr.php";
           content = HttpUtility.UrlEncode(Encoding.UTF8.GetBytes(content));
           target = HttpUtility.UrlEncode(Encoding.UTF8.GetBytes(target));
           string postString = string.Format("source=&ptid=&roomid=&content={0}&fid=&arturl=&murl=&target={1}&func=send&ef=js&pmlang=zh_CN&apiType=8&apiHost=http://api.t.qq.com",
                                  content,target);//wuwenjun20102008,niefeng101
           string html = HttpHelper.GetHtml(url,postString, this.WeiboCookieContainer);
           return html;
       }
      #endregion

当然,给大家分享上面的分析结果不是让大家去干一些让人讨厌的事(大家都懂的),仅仅是为那些爱好Http协议模拟的提供参考的方便。如果有和我一样对这方面有共同爱好,请继续关注本人的博客
教程每天都更新,欢迎继续关注!
本文属于原创文章,转载请保持http://www.cnblogs.com/uu102的链接 如果违法上述规定,本人将保留追究转载者的权利.谢谢合作

[转载]每周一荐:差异利器Beyond Compare - david++ - 博客园

mikel阅读(862)

[转载]每周一荐:差异利器Beyond Compare – david++ – 博客园.

作  者:david++
发布时间:2012/11/01 14:51

前一段时间,介绍过用Total Commander来完成文件夹同步的时候,一位朋友留言推荐了Beyond Compare——一个强大的超越了文件差异比较的工具。Beyond Compare对于提升工作效率,绝对是一个利器。

单独的文件差异工具不胜枚举,如:WinMerge、WinDiff、Diffuse等,还有Unix/Linux下犀利的命令行工 具:diff,一般源码的补丁就是靠它搞定的,先用diff差异一下,再patch一下就OK了;版本控制软件一般默认也都提供了自己的差异和合并软件 (如:TortoiseSVN的TotoiseMerge)。

Beyond Compare正如其名,“超越了比较”,它有三个核心功能:

  1. 差异比较
  2. 文件夹同步
  3. 文本合并

1. 差异

支持多种文件格式的差异,对于我们“程序猿”来说,最重要的要数文本文件差异和语法高亮了。对于策划或其它经常使用Excel的来说,数据文件差异是个非常强大的功能。

1.1  文件夹的差异

选择Folder Compare,选择要进行比较的两个目录。蓝色高亮的部分就是比较的另一个目录缺失的,红色高亮的就是两边都存在,但存在差异的。工具栏提供了许多选项,使用的过程中可以慢慢摸索。

1.2 文本文件的差异

选择“Text Compare”,选中两个文本文件。如果是代码,同时可以进行语法高亮显示,其它差异软件一般都会有此功能。

1.3 数据文件差异(如:Excel文件)

选择“Data Compare”,选择要比较的两个数据文件,可以是Excel文件,也可以CSV等数据文件。这个功能对于经常做数据的朋友来说,是非常有用的。

1.4 16进制数据差异

选择“Hex Compare”,选择要比较的两个二进制文件。该功能会对选择的两个文件,每个字节进行比较。差异的地方高亮显示。

1.5 图像的差异

选择“Picture Compare”,选择要比较的两个图像文件,然后中间一个窗口就会把两张图片差异的部分显示出来,可以通过拖动图片或者选择工具栏相关选项做调整。

2. 文件夹同步

选择“Folder Sync”,选择要同步的两个目录,点击“Sync”即可。Total Commander也有类似的功能,该功能对于经常做文件备份的朋友会非常有用,当备份之间存在差异的时候,文件夹只需要同步一下,万事OK,免得丢失文 件或者一个个去对比浪费时间。

3. 文本合并

文本文件是可编辑的,在差异的基础上对两个文件进行编辑合并,对于代码版本控制、解决冲突非常有用。

小结

BeyondCompare唯一不爽的地方就是,该软件是收费软件。不过功能上来说,还是一款非常不错的提高工作效率的利器,推荐给大家使用。(文件差异与合并、目录同步这些功能有相应的开源的可以代替Beyond Compare)。

[转载]选择WEB开发语言的重与轻

mikel阅读(993)

[转载]选择WEB开发语言的重与轻 – hello_4653 – 博客园.

在打算开发一个网站时,选择什么语言,是首先需要面对的问题。目前主流的WEB开发语言 有ASP.NET、PHP、JSP; 作为MS上世纪老将ASP,就不再提及,如果是因为维护方面的原因而必须使用,可考虑升级到ASP.NET,而作为新开发一个语言,实在找不到理由再使用 它了;

创业如何选择WEB开发语言

以下将对这三种语言做对比,以供权衡:

上手度

.NET: 5分

PHP:3分

JSP:1分

如果你是一个WEB方面的新手,这三门WEB语言的学习成本差别很大。ASP.net 作为微软的产品,继承了其一贯的特点,方便上手,易用;甚至你都不用编码,靠着鼠标拖拖拽拽,都能整一个网站出来(网上,就有这样的视频讲解。当然,这样 出来的网站是没法应用到实际中的,且不说其代码复用率极其低下,拖拽出来的代码,灵活度太小,效率也低(eg:gridview中的分页实现载入数据是一 次全部载入的))。同时,凭着其强大的开发工具visual studio系列,在程序出现bug时,能最大程度的提供问题说明,让开发者尽快定位到问题所在。JSP相比而言难度就大多了,光是配置一个开发环境就得 耗费不少精力,JSP语言最为头疼的就是程序调试方面,当程序出现问题时,并不能得到友好的错误提示,调试BUG比较耗时。再就是JSP依托的JAVA过 于庞大,着实是个无底洞,开始容易,越往后发现要学的越多,一般互联网公司,还真难以有几个能驾驭,再普及的;PHP学习算是基于.net和JSP之间, 语法与C语言一脉相承,上手也算容易;

资源

.NET:4分

PHP:5分

JSP:2分

资源包括能获取到的学习资料、开放源码,以及各种插件和库。PHP在这方面遥遥领先,粗略看来,各种网站的知名开源产品,大都使用PHP实现,如博客wordpress、论坛discuz、Wiki知识库MediaWiki等;

相应的各种插件、库、开源代码的数量和质量更是其它语言无法相比。.NET资源也比较丰富,选用.NET幸福的是有MS这么一个强大后台做有力的技术支持,CSDN 的资料不但多,质量更是上乘;JSP由于其门槛高的缘故,致使在这方面的资料也比较少;

系统架构实施

.NET:3分

PHP:5分

JSP: 3分

.NET 部署环境是windows 03/08+MS SQL Server + IIS。都是微软的产品,优点就是部署容易,方便,兼容性好。最为头疼就是安全方面的问题,windows下总是得不停的打补丁,但还是时常遭受这样那样 的攻击;再就是数据库方面,MS SQL 与Oracle在并发处理、效率上始终有个数据量级的差距,2008发布之后据说是好了些,但总是让人感觉不大放心;PHP就是LAMP架构,即 Linux+Apache+My Sql + PHP;Linux平台在我这几年的熟悉后,深刻体会到其就是为服务器而生,各种的工具让人爱不释手;My Sql作为开源产品,首先在软件费用上就公司能省下一大笔,其性能优秀,即使某日网站规模的扩大致使数据库出现瓶颈,也可组建一个数据库团队来研究改进。 不过,在Oracle收购MySql之后,为其前景蒙上了一层阴影。有可能,在不久的将来,MySql的部分功能就会闭源。JSP的架构小则是 Linux+apache+tomcat+MySql ,大则Linux + Apache + Java (WebSphere) + Oracle,对于一般小型网站的部署,大都选用第一种;WebSphere过于庞大,一般部署都得独自占用一台服务器;Oracle是数据库中的王者, 性能优异(国内银行证券的数据库应用,一般只有DB2和Oracle两种选择),但其价格不菲,非一般创业公司能够承担(按CPU收费,一般 25w/cpu/每年;次年会收取15%的维护费)需要提一下的是JSP系统架构部署有些难度,架构出现问题后,排错是个很痛苦的过程。

管理维护

.NET:2分

PHP:5分

JSP: 4分

WEB 管理中,经常会通过远程来管理网站,远程管理的方便与否关键看命令行工具的支持力度及脚本环境的操作便捷性。.NET只能跑在Windows平台上,远程 管理一般只能通过图形化界面远程鼠标操作,当网速比较慢的时候,管理员的心情无比复杂,远程操作基本上是在一幅幅图片上估计下一张图片中鼠标的移动位 置;Windows平台的命令行环境非常差,IIS的命令行工具功能少,bat脚本也难学难用(虽然可以通过安装cygwin工具来模拟linux shell环境,但系统操作,系统资源监控方面还是无能为力); Linux下就幸福多了,远程基本上都是通过SSH连接,安全有保证,shell脚本消耗的网络带宽也只是图形化界面的百分之一,管理流畅,心情舒畅;各 种程序消耗资源都可远程监控;Linux就是为服务器而生,此话毫不为过。PHP、JSP都可跨平台,一般其系统部署都是在Linux下,MySql数据 库和apche服务器都可通过相应的命令行工具有效管理。JSP的应用服务器在这方面支持要少些;

跨平台

.NET:0 分

PHP:5分

JSP:5分

曾几何时,我对跨平台不屑一顾,想着好端端的一个应用,既然是定位在这个平台上开发的,干嘛要移植到其它平台上。如今,我是深有体会。手上一个项 目,公司由于成本压力,需要将应用从 SUN Unix移植到Linux平台(Redhat)。我们的程序基本上不用改动,在Linux上编译就只多了几个警告,改改就可上线了;而另一个项目,我被深 度套牢!我们使用的是Windows平台的ASP.NET,由于受到Windows的病毒泛滥加上WEB管理的麻烦,迫切希望能移植到Linux平台,但 这基本上不可能实现。若真想将这应用移植,只有下狠心使用PHP等重写应用,换系统架构。PHP、JSP都可跨平台,不用多说。

当前主流应用的选择

PHP:当前WEB创业公司的语言选择主要集中在PHP。除了上述原因还有一个重要原因就是PHP开发程序员队伍的规模。

淘宝网(阿里巴巴): Linux操作系统 + Web 服务器: Apache +PHP

PHP的应用太多,这里不再列举;

ASP.NET:在创业公司中应用不多,知名互联网应用有限,目前比较知名的应用有:

博客园、CSDN、eBay、MySpace等;

JSP:JSP 实施比较庞大,用好的就得用到websphere或weblogic这样的大物件,种种原因使得JSP在互联网公司中应用并不多,除了阿里巴巴,没有几个 公司能驾驭JAVA(JSP)。深入JAVA需要多年修炼,而成精之后,公司是否有足够的薪水来留住这么一群高手是个考验;

阿里巴巴:Linux+(JSP)

总结

如今流行的Ruby,也是创业公司的一个选择;python的优雅,也可考虑尝试(豆瓣使用的Python);但选择这些语言的一个风险是公司规模 扩大后,是否能找到足够的人才得打个问号。总的来说,创业面临选择一门开发语言,PHP当是首选;如果不考虑Linux平台,铁定在Windows上运 营,.NET也是一个不错的选择。JSP小公司勿近,危险,容易造成的资金套牢。

个人总结的点滴,园友们有不同见解可以互相讨论,别哄楼哦。

[转载]基于ASP.NET MVC3 Razor的模块化/插件式架构实现

mikel阅读(1380)

[转载]基于ASP.NET MVC3 Razor的模块化/插件式架构实现 – Mainz – 博客园.

本文主要探讨了一种基于ASP.NET MVC3 Razor的模块化(Plugin)/插件(plugin)式架构的实现方法。本文借鉴了《Compile your asp.net mvc Razor views into a seperate dll》作者提供的方法。敬请注意。其实ASP.NET MVC的模块化(Plugin)/插件(plugin)式架构讨论的很多,但基于Razor视图引擎的很少(如:MVC2插件架构例子都是基于WebForm的,MVCContrib Portable Areas也是,还有这个Plugin架构)。要么就是非常复杂非常重量级的框架,例如Orchard CMS的模块化做的很好,可惜太重量级了,也没独立的模块可以剥离出来。所以我们追寻的是简单的基于ASP.NET MVC3 Razor的模块化(Plugin)/插件(plugin)式架构的实现方法。本文最后实现的项目结构如下图:(插件都放到~/Plugin目录下,按功能划分模块,每个模块都有M,V,C)

3-5-2012 5-07-19 PM

其 中,业务模块(class library project)包含其所有的视图、控制器等,模型可以放在里面也可以单独放一个project。主web项目没有引用业务模块,业务模块会编译到主 web项目的~/plugin目录下面(注意:不是bin目录),然后当web应用启动的时候自动加载plugin目录下面的模块。最后运行起来的效果如 下图:

3-6-2012 10-28-51 AM

其中红色的区域都是plugin进去的,那个tab的标题plugin到母版页的主菜单,tab内容也来自plugin。下面说说如何实现这样的ASP.NET MVC插件式plugin架构(模块化架构)。

实现的难点在动态加载UI视图(*.cshtml, _layout.cshtml, _viewStart.cshtml)

废话少说,直入要害。基于ASP.NET MVC3 Razor的编译发生在两个层面:

  • 控制器(Controller), 模型(Models),和其它所有的C#代码等有msbuild(或者VisualStudio)编译到bin目录下的程序集(assembly)
  • 视图(*.aspx, *.cshtml)由ASP.NET在运行时动态编译。当一个Razor视图(*.cshtml)显示前,Razor视图引擎调用BuildManager把视图(*.cshtml)编译到动态程序集assembly,然后使用Activator.CreateInstance来实例化新编译出来的对象,最后显示出来。如果视图(*.cshtml)用到@model绑定model,那么还会自动加载bin或者GAC里面的Model。

所 以如果我们要动态加载插件(plugin),用反射bin目录下的程序集(assembly)的方法很容易搞定上面的第一部分(C#代码的部分),但UI 视图的部分(上面第二部分)(特别是*.cshtml, 母版_layout.cshtml, 基视图_viewStart.cshtml)就比较难搞定。而且每次报错都是一样的,那就是Controller找不到相应的视图View,基本不知所云 而且根本不是要点:view …. or its master was not found or no view engine supports the searched locations. The following locations were searched: …,因此要搞定UI视图的部分(上面第二部分)(特别是*.cshtml, 母版_layout.cshtml, 基视图_viewStart.cshtml),就需要自己动手了,基本原理是:

  • 重载RazorBuildProvider,用来动态编译视图
  • 实现一个自定义VirtualPathProvider,从虚拟路径自定义判断读取资源(从插件中加载资源),如果要使用编译的视图就返回编译的VirtualFile
  • 实 现一个容器Dictionary保存已编译的视图和虚拟路径,例如path <~/views/team/index.cshtml> type <Area.Module2.Views.Team._Page_Views_Team_Index_cshtml>,或者path <~/views/_viewstart.cshtml> type <Area.Module1.Views._Page_Views__ViewStart_cshtml>

代码:自定义VirtualPathProvider,从虚拟路径自定义判断读取资源(从插件中加载资源),如果要使用编译的视图就返回编译的VirtualFile

using System;

2: using System.Collections.Generic;

3: using System.Linq;

4: using System.Reflection;

5: using System.Web.Caching;

6: using System.Web.Hosting;

7: using System.Web.WebPages;

8:

9: namespace Common.Framework

10: {

11: public class CompiledVirtualPathProvider: VirtualPathProvider

12: {

13: ///

&nbsp;

&nbsp;

&nbsp;

<summary>14: /// Gets a value that indicates whether a file exists in the virtual file system.15: /// </summary>

&nbsp;

&nbsp;

&nbsp;

&nbsp;

16: ///

17: /// true if the file exists in the virtual file system; otherwise, false.

18: ///

19: ///The path to the virtual file.

20: public override bool FileExists(string virtualPath)

21: {

22: return

23: GetCompiledType(virtualPath) != null

24: || Previous.FileExists(virtualPath);

25: }

26:

27: public Type GetCompiledType(string virtualPath)

28: {

29: return ApplicationPartRegistry.Instance.GetCompiledType(virtualPath);

30: }

31:

32: ///

&nbsp;

&nbsp;

&nbsp;

<summary>33: /// Gets a virtual file from the virtual file system.34: /// </summary>

&nbsp;

&nbsp;

&nbsp;

&nbsp;

35: ///

36: /// A descendent of theclass that represents a file in the virtual file system.

37: ///

38: ///The path to the virtual file.

39: public override VirtualFile GetFile(string virtualPath)

40: {

41: if (Previous.FileExists(virtualPath))

42: {

43: return Previous.GetFile(virtualPath);

44: }

45: var compiledType = GetCompiledType(virtualPath);

46: if (compiledType != null)

47: {

48: return new CompiledVirtualFile(virtualPath, compiledType);

49: }

50: return null;

51: }

52:

53: public override System.Web.Caching.CacheDependency GetCacheDependency(string virtualPath, System.Collections.IEnumerable virtualPathDependencies, DateTime utcStart)

54: {

55: if (virtualPathDependencies == null)

56: return Previous.GetCacheDependency(virtualPath, virtualPathDependencies, utcStart);

57:

58: return Previous.GetCacheDependency(virtualPath,

59: from vp in virtualPathDependencies.Cast()

60: where GetCompiledType(vp) == null

61: select vp

62: , utcStart);

63: }

64:

65: }

66: }

代码:容器Dictionary保存已编译的视图和虚拟路径,例如path <~/views/team/index.cshtml> type ,路径注册以后,会从容器库全局搜索所有注册过的视图,也就是说即使你视图引用的_layout.cshtml和_viewStart.cshtml在其他的Class library project照样可以找到。

1: using System;

2: using System.Collections.Generic;

3: using System.Diagnostics;

4: using System.Linq;

5: using System.Reflection;

6: using System.Web;

7: using System.Web.WebPages;

8:

9: namespace Common.Framework

10: {

11: public class DictionaryBasedApplicationPartRegistry : IApplicationPartRegistry

12: {

13: private static readonly Type webPageType = typeof(WebPageRenderingBase);

14: private readonly DictionaryregisteredPaths = new Dictionary();

15:

16: ///

&nbsp;

&nbsp;

&nbsp;

<summary>17: ///18: /// </summary>

&nbsp;

&nbsp;

&nbsp;

&nbsp;

19: ///

20: ///

21: public virtual Type GetCompiledType(string virtualPath)

22: {

23: if (virtualPath == null) throw new ArgumentNullException("virtualPath");

24:

25: //Debug.WriteLine(String.Format("---GetCompiledType : virtualPath", virtualPath));

26:

27: if (virtualPath.StartsWith("/"))

28: virtualPath = VirtualPathUtility.ToAppRelative(virtualPath);

29: if (!virtualPath.StartsWith("~"))

30: virtualPath = !virtualPath.StartsWith("/") ? "~/" + virtualPath : "~" + virtualPath;

31: virtualPath = virtualPath.ToLower();

32: return registeredPaths.ContainsKey(virtualPath)

33: ? registeredPaths[virtualPath]

34: : null;

35: }

36:

37: public void Register(Assembly applicationPart)

38: {

39: ((IApplicationPartRegistry)this).Register(applicationPart, null);

40: }

41:

42: public virtual void Register(Assembly applicationPart, string rootVirtualPath)

43: {

44: //Debug.WriteLine(String.Format("---Register assembly, path", applicationPart.FullName, rootVirtualPath));

45:

46: foreach (var type in applicationPart.GetTypes().Where(type =&gt; type.IsSubclassOf(webPageType)))

47: {

48: //Debug.WriteLine(String.Format("-----Register type, path", type.FullName, rootVirtualPath));

49:

50: ((IApplicationPartRegistry)this).RegisterWebPage(type, rootVirtualPath);

51: }

52: }

53:

54: public void RegisterWebPage(Type type)

55: {

56: ((IApplicationPartRegistry)this).RegisterWebPage(type, string.Empty);

57: }

58:

59: public virtual void RegisterWebPage(Type type, string rootVirtualPath)

60: {

61: var attribute = type.GetCustomAttributes(typeof(PageVirtualPathAttribute), false).Cast().SingleOrDefault();

62: if (attribute != null)

63: {

64: var rootRelativeVirtualPath = GetRootRelativeVirtualPath(rootVirtualPath ?? "", attribute.VirtualPath);

65:

66: //Debug.WriteLine(String.Format("---Register path/type : pathtype", rootRelativeVirtualPath.ToLower(),

67: // type.FullName));

68: registeredPaths[rootRelativeVirtualPath.ToLower()] = type;

69: }

70: }

71:

72: static string GetRootRelativeVirtualPath(string rootVirtualPath, string pageVirtualPath)

73: {

74: string relativePath = pageVirtualPath;

75: if (relativePath.StartsWith("~/", StringComparison.Ordinal))

76: {

77: relativePath = relativePath.Substring(2);

78: }

79: if (!rootVirtualPath.EndsWith("/", StringComparison.OrdinalIgnoreCase))

80: {

81: rootVirtualPath = rootVirtualPath + "/";

82: }

83: relativePath = VirtualPathUtility.Combine(rootVirtualPath, relativePath);

84: if (!relativePath.StartsWith("~"))

85: {

86: return !relativePath.StartsWith("/") ? "~/" + relativePath : "~" + relativePath;

87: }

88: return relativePath;

89: }

90: }

91: }

下面的代码很关键,用PreApplicationStartMethod关键字(.NET 4.0开始支持)使得代码在Application_Start之前执行。

有关[assembly: PreApplicationStartMethod(typeof(SomeClassLib.Initializer), “Initialize”)]详细信息请参考这个页面这个页面

1: using System.Web;

2: using System.Web.Compilation;

3: using System.Web.Hosting;

4: using Common.Framework;

5: using Common.PrecompiledViews;

6:

7: [assembly: PreApplicationStartMethod(typeof(PreApplicationStartCode), "Start")]

8:

9: namespace Common.Framework

10: {

11: public static class PreApplicationStartCode

12: {

13: private static bool _startWasCalled;

14:

15: public static void Start()

16: {

17: if (_startWasCalled)

18: {

19: return;

20: }

21: _startWasCalled = true;

22:

23: //Register virtual paths

24: HostingEnvironment.RegisterVirtualPathProvider(new CompiledVirtualPathProvider());

25:

26: //Load Plugin Folder,

27: PluginLoader.Initialize();

28: }

29: }

30: }

代码:PluginLoader,加载plugin目录里面的东东(assembly和module配置文件)

1: using System;

2: using System.Collections.Generic;

3: using System.IO;

4: using System.Linq;

5: using System.Reflection;

6: using System.Text;

7: using System.Threading;

8: using System.Web;

9: using System.Web.Compilation;

10: using System.Web.Hosting;

11: using Common.Framework;

12: using Common.PrecompiledViews;

13:

14: //[assembly: PreApplicationStartMethod(typeof(PluginLoader), "Initialize")]

15:

16: namespace Common.PrecompiledViews

17: {

18: public class PluginLoader

19: {

20: public static void Initialize(string folder = "~/Plugin")

21: {

22: LoadAssemblies(folder);

23: LoadConfig(folder);

24: }

25:

26: private static void LoadConfig(string folder, string defaultConfigName="*.config")

27: {

28: var directory = new DirectoryInfo(HostingEnvironment.MapPath(folder));

29: var configFiles = directory.GetFiles(defaultConfigName, SearchOption.AllDirectories).ToList();

30: if (configFiles.Count == 0) return;

31:

32: foreach (var configFile in configFiles.OrderBy(s =&gt; s.Name))

33: {

34: ModuleConfigContainer.Register(new ModuleConfiguration(configFile.FullName));

35: }

36: }

37:

38: private static void LoadAssemblies(string folder)

39: {

40: var directory = new DirectoryInfo(HostingEnvironment.MapPath(folder));

41: var binFiles = directory.GetFiles("*.dll", SearchOption.AllDirectories).ToList();

42: if (binFiles.Count == 0) return;

43:

44: foreach (var plug in binFiles)

45: {

46: //running in full trust

47: //************

48: //if (GetCurrentTrustLevel() != AspNetHostingPermissionLevel.Unrestricted)

49: //set in web.config, probing to plugin\temp and copy all to that folder

50: //************************

51: var shadowCopyPlugFolder = new DirectoryInfo(AppDomain.CurrentDomain.DynamicDirectory);

52: var shadowCopiedPlug = new FileInfo(Path.Combine(shadowCopyPlugFolder.FullName, plug.Name));

53: File.Copy(plug.FullName, shadowCopiedPlug.FullName, true); //TODO: Exception handling here...

54: var shadowCopiedAssembly = Assembly.Load(AssemblyName.GetAssemblyName(shadowCopiedPlug.FullName));

55:

56: //add the reference to the build manager

57: BuildManager.AddReferencedAssembly(shadowCopiedAssembly);

58: }

59: }

60:

61: //private static AspNetHostingPermissionLevel GetCurrentTrustLevel()

62: //{

63: // foreach (AspNetHostingPermissionLevel trustLevel in

64: // new AspNetHostingPermissionLevel[]

65: // {

66: // AspNetHostingPermissionLevel.Unrestricted,

67: // AspNetHostingPermissionLevel.High,

68: // AspNetHostingPermissionLevel.Medium,

69: // AspNetHostingPermissionLevel.Low,

70: // AspNetHostingPermissionLevel.Minimal

71: // })

72: // {

73: // try

74: // {

75: // new AspNetHostingPermission(trustLevel).Demand();

76: // }

77: // catch (System.Security.SecurityException)

78: // {

79: // continue;

80: // }

81:

82: // return trustLevel;

83: // }

84:

85: // return AspNetHostingPermissionLevel.None;

86: //}

87:

88: }

89: }

此外,使用SingleFileGenerator的优点是性能提升,缺点是修改了视图就要重新编译。

如何让ASP.NET加载BIN目录之外的路径的Assembly

我们把各个模块编译出来的assembly和各个模块的配置文件自动放到一个bin平级的plugin目录,然后web应用启动的时候自动扫描这个 plugin目录并加载各个模块plugin,这个怎么做到的?大家也许知道,ASP.NET只允许读取Bin目录下的assbmely,不可以读取其他 路径,包括Bin\abc等,即使在web.config这样配置probing也不行:(不信你可以试一下)

 1: <configuration> Element
 2:   <runtime> Element
 3:     <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
 4:       <probing privatePath="bin;bin\abc;plugin;"/>
 5:     </assemblyBinding>
 6:    </runtime>
 7: </configuration>

这个和TrustLevel有关,在Full Trust的情况下,可以这样读取非Bin目录下的assembly:

首先在和Bib平级的地方建一个目录Plugin,然后在模块class library project的属性里面加一个postBuildEvent,就是说在编译完成以后把模块的assbmely自动拷贝到主web项目的plugin目录:

 1: copy /Y "$(TargetDir)$(ProjectName).dll" "$(SolutionDir)ModularWebApplication\Plugin\"
 2: copy /Y "$(TargetDir)$(ProjectName).config" "$(SolutionDir)ModularWebApplication\Plugin\"
 3:

然后用下面的代码加载Plugin目录下的assembly:(只看LoadAssembly那一段)

1: using System;

2: using System.Collections.Generic;

3: using System.IO;

4: using System.Linq;

5: using System.Reflection;

6: using System.Text;

7: using System.Threading;

8: using System.Web;

9: using System.Web.Compilation;

10: using System.Web.Hosting;

11: using Common.Framework;

12: using Common.PrecompiledViews;

13:

14: //[assembly: PreApplicationStartMethod(typeof(PluginLoader), "Initialize")]

15:

16: namespace Common.PrecompiledViews

17: {

18: public class PluginLoader

19: {

20: public static void Initialize(string folder = "~/Plugin")

21: {

22: LoadAssemblies(folder);

23: LoadConfig(folder);

24: }

25:

26: private static void LoadConfig(string folder, string defaultConfigName="*.config")

27: {

28: var directory = new DirectoryInfo(HostingEnvironment.MapPath(folder));

29: var configFiles = directory.GetFiles(defaultConfigName, SearchOption.AllDirectories).ToList();

30: if (configFiles.Count == 0) return;

31:

32: foreach (var configFile in configFiles.OrderBy(s =&gt; s.Name))

33: {

34: ModuleConfigContainer.Register(new ModuleConfiguration(configFile.FullName));

35: }

36: }

37:

38: private static void LoadAssemblies(string folder)

39: {

40: var directory = new DirectoryInfo(HostingEnvironment.MapPath(folder));

41: var binFiles = directory.GetFiles("*.dll", SearchOption.AllDirectories).ToList();

42: if (binFiles.Count == 0) return;

43:

44: foreach (var plug in binFiles)

45: {

46: //running in full trust

47: //************

48: //if (GetCurrentTrustLevel() != AspNetHostingPermissionLevel.Unrestricted)

49: //set in web.config, probing to plugin\temp and copy all to that folder

50: //************************

51: var shadowCopyPlugFolder = new DirectoryInfo(AppDomain.CurrentDomain.DynamicDirectory);

52: var shadowCopiedPlug = new FileInfo(Path.Combine(shadowCopyPlugFolder.FullName, plug.Name));

53: File.Copy(plug.FullName, shadowCopiedPlug.FullName, true); //TODO: Exception handling here...

54: var shadowCopiedAssembly = Assembly.Load(AssemblyName.GetAssemblyName(shadowCopiedPlug.FullName));

55:

56: //add the reference to the build manager

57: BuildManager.AddReferencedAssembly(shadowCopiedAssembly);

58: }

59: }

60:

61: //private static AspNetHostingPermissionLevel GetCurrentTrustLevel()

62: //{

63: // foreach (AspNetHostingPermissionLevel trustLevel in

64: // new AspNetHostingPermissionLevel[]

65: // {

66: // AspNetHostingPermissionLevel.Unrestricted,

67: // AspNetHostingPermissionLevel.High,

68: // AspNetHostingPermissionLevel.Medium,

69: // AspNetHostingPermissionLevel.Low,

70: // AspNetHostingPermissionLevel.Minimal

71: // })

72: // {

73: // try

74: // {

75: // new AspNetHostingPermission(trustLevel).Demand();

76: // }

77: // catch (System.Security.SecurityException)

78: // {

79: // continue;

80: // }

81:

82: // return trustLevel;

83: // }

84:

85: // return AspNetHostingPermissionLevel.None;

86: //}

87:

88: }

89: }

如果不是Full Trust,例如Medium Trust的情况下参考这个帖子《Developing-a-plugin-framework-in-ASPNET-with-medium-trust》。

如何在_layout.cshtml的主菜单注入plugin的菜单

在母版页_layout.cshtml有个主菜单,一般是这样写的:

 1: <ul>
 2:    <li>@Html.ActionLink("Home", "Index", "Home")</li>
 3:    <li>@Html.ActionLink("About", "About", "Home")</li>
 4:    <li>@Html.ActionLink("Team", "Index", "Team")</li>
 5: </ul>

现在我们如何实现从模块插入plugin到这个主菜单呢?这个有点难。因为大家知道,_layout.cshml母版没有controller。怎 么实现呢?方法是用controller基类,让所有controller继承自这个基类。然后在基类里面,读取plugin目录里面的配置文件,获取所 有模块需要插入的主菜单项,然后放入viewBag,这样在_Layout.cshtml就可以获取viewBag,类似这样:

 1: <ul>
 2:    @foreach (MainMenuItemModel entry in ViewBag.MainMenuItems)
 3:     {
 4:         <li>@Html.ActionLink(entry.Text,
 5:                entry.ActionName,
 6:                 entry.ControllerName)</li>
 7:     }
 8: </ul>

代码:基类Controller,读取plugin目录里面的配置文件,获取所有模块需要插入的主菜单项,然后放入viewBag

 1: using System;
 2: using System.Collections;
 3: using System.Collections.Generic;
 4: using System.ComponentModel;
 5: using System.Linq;
 6: using System.Net.Mime;
 7: using System.Text;
 8: using System.Web.Mvc;
 9:
 10: namespace Common.Framework
 11: {
 12:     public class BaseController : Controller
 13:     {
 14:         protected override void Initialize(System.Web.Routing.RequestContext requestContext)
 15:         {
 16:             base.Initialize(requestContext);
 17:
 18:             // retireve data from plugins
 19:             IEnumerable<ModuleConfiguration> ret = ModuleConfigContainer.GetConfig();
 20:
 21:             var data = (from c in ret
 22:                         from menu in c.MainMenuItems
 23:                         select new MainMenuItemModel
 24:                                    {
 25:                                        Id = menu.Id, ActionName = menu.ActionName, ControllerName = menu.ControllerName, Text = menu.Text
 26:                                    }).ToList();
 27:
 28:             ViewBag.MainMenuItems = data.AsEnumerable();
 29:         }
 30:
 31:     }
 32: }

代码:ModuleConfigContainer,用到单例模式,只读取一次

 1: using System;
 2: using System.Collections.Generic;
 3: using System.Linq;
 4: using System.Text;
 5:
 6: namespace Common.Framework
 7: {
 8:     public static class ModuleConfigContainer
 9:     {
 10:         static ModuleConfigContainer()
 11:         {
 12:             Instance = new ModuleConfigDictionary();
 13:         }
 14:
 15:         internal static IModuleConfigDictionary Instance { get; set; }
 16:
 17:         public static void Register(ModuleConfiguration item)
 18:         {
 19:             Instance.Register(item);
 20:         }
 21:
 22:         public static IEnumerable<ModuleConfiguration> GetConfig()
 23:         {
 24:             return Instance.GetConfigs();
 25:         }
 26:     }
 27: }

代码:ModuleConfigDictionary

 1: using System;
 2: using System.Collections.Generic;
 3: using System.Linq;
 4: using System.Text;
 5:
 6: namespace Common.Framework
 7: {
 8:     public class ModuleConfigDictionary : IModuleConfigDictionary
 9:     {
 10:         private readonly Dictionary<string, ModuleConfiguration>  _configurations = new Dictionary<string, ModuleConfiguration>();
 11:
 12:         public IEnumerable<ModuleConfiguration> GetConfigs()
 13:         {
 14:             return _configurations.Values.AsEnumerable();
 15:         }
 16:
 17:         public void Register(ModuleConfiguration item)
 18:         {
 19:             if(_configurations.ContainsKey(item.ModuleName))
 20:             {
 21:                 _configurations[item.ModuleName] = item;
 22:             }
 23:             else
 24:             {
 25:                 _configurations.Add(item.ModuleName, item);
 26:             }
 27:         }
 28:     }
 29: }

代码:ModuleConfiguration,读取模块的配置文件

 1: using System;
 2: using System.Collections.Generic;
 3: using System.IO;
 4: using System.Linq;
 5: using System.Text;
 6: using System.Xml;
 7: using System.Xml.Linq;
 8:
 9: namespace Common.Framework
 10: {
 11:     public class ModuleConfiguration
 12:     {
 13:         public ModuleConfiguration(string filePath)
 14:         {
 15:             try
 16:             {
 17:                 var doc = XDocument.Load(filePath);
 18:                 var root = XElement.Parse(doc.ToString());
 19:
 20:                 if (!root.HasElements) return;
 21:
 22:                 var module = from e in root.Descendants("module")
 23:                              //where e.Attribute("name").Value == "xxxx"
 24:                              select e;
 25:
 26:                 if (!module.Any()) return;
 27:
 28:                 ModuleName = module.FirstOrDefault().Attribute("name").Value;
 29:
 30:                 var menus = from e in module.FirstOrDefault().Descendants("menu")
 31:                             select e;
 32:
 33:                 if (!menus.Any()) return;
 34:
 35:                 var menuitems = menus.Select(xElement => new MainMenuItemModel
 36:                                                              {
 37:                                                                  Id = xElement.Attribute("id").Value,
 38:                                                                  Text = xElement.Attribute("text").Value,
 39:                                                                  ActionName = xElement.Attribute("action").Value,
 40:                                                                  ControllerName = xElement.Attribute("controller").Value
 41:                                                              }).ToList();
 42:
 43:                 MainMenuItems = menuitems;
 44:             }
 45:             catch
 46:             {
 47:                 //TODO: logging
 48:             }
 49:         }
 50:         public string ModuleName { get; set; }
 51:         public IEnumerable<MainMenuItemModel> MainMenuItems { get; set; }
 52:     }
 53: }

每个模块的配置文件为{projectName}.config,格式如下:

 1: <?xml version="1.0" encoding="utf-8" ?>
 2: <configuration>
 3:   <module name="Module2">
 4:     <mainmenu>
 5:       <menu id="modul2" text="Team" action="Index" controller="Team"/>
 6:     </mainmenu>
 7:   </module>
 8: </configuration>

为了简单起见,只保留了注入主菜单的部分,为了让读者简单易懂。明白了以后你自己可以任意扩展…

代码:IModuleConfigDictionary,接口

dddd

模块配置文件{projectName}.config的位置:

Untitled

为什么每个模块的Class library project都需要一个web.config呢?因为如果没有这个,那就没有Razor智能提示,大家可以参考这篇文章《How to get Razor intellisense for @model in a class library project》。

 

闲话几句插件式架构(Plugin Architecture)或者模块化(Modular)架构

插件式架构(Plugin Architecture)或者模块化(Modular)架构是大型应用必须的架构,关于什么是Plugin,什么是模块化模式,这种架构的优缺点等我就不说了,自己百谷歌度。关于.NET下面的插件式架构和模块化开发实现方法,基本上用AppDomain实现,当检测到一个新的插件Plugin时,实例化一个新的AppDomain并加载Assembly反射类等,由于AppDomain很好的隔离各个Plugin,所以跨域通信要用MarshalByRefObject类,具体做法可以参考这篇文章《基于AppDomain的”插件式”开发》。另外,有很多框架提供了模块化/插件开发的框架,例如PrismMEF(Managed Extensibility Framework,.NET 4.0 内置)等。

客户端插件架构

还有一种插件架构是客户端插件架构(JavaScript 模块化),如jQuery UI Widget FactorySilk Project就是很好的例子。

本文源码下载

源码下载请点击此处。运行源码之前务必阅读此文和本文。注意:本文抛砖引玉,力求简单,易懂,并非完整的架构实现,更多丰富的功能实现一切皆有可能,只要在理解的基础上。

[转载]c# out ref 引用传递,借用变量返回多个值

mikel阅读(1263)

转载c# out ref 引用传递,借用变量返回多个值 – sirili – 博客园.

方法中的return只能返回一个值,当我们需要方法返回多个值的时候,就需要借助Out 和 Ref 了。
方法参数前加Out 和 ref,方法中参数的值发生变化后,主函数调用此方法的变量的值也同样发生变化,其本质是变量地址的传递,如上图。

值传递:相当与复制,方法中改变的只是复件的值(两人一人一份笔记,复制)

引用传递:相当于快捷方式,方法中的修改,快捷方式所指向的文件内容同样变化(两个人同时做一份笔记)

our和ref 区别:

1、out 用于往外传值,先赋值再使用,方法中必须要先给变量赋值以后才能使用

2、ref 双向,即可以接收主函数中的值又可以往外传值,其本质是地址传递

[转载]Jquery瀑布流插件 - 无双 - 博客园

mikel阅读(1082)

[转载]Jquery瀑布流插件 – 无双 – 博客园.

瀑布流布局在目前貌似很火爆,具体的分析、原理、用到的知识等等可以看看以下几位牛人写的东西。

瀑布流布局浅析

浅谈个人在瀑布流网页的实现中遇到的问题和解决方法

折腾:瀑布流布局(基于多栏列表流体布局实现)

javascript 瀑布流各大瀑布流简析与建议

因为自己用JQuery比较多,便萌生了把瀑布流做成插件的想法,图片就借用迅雷UED上的那些美图吧。

先看看Demo

把代码放出来吧

;(function($){
   var
   //参数
   setting={
      column_width:204,//列宽
       column_className:'waterfall_column',//列的类名
       column_space:10,//列间距
       cell_selector:'.cell',//要排列的砖块的选择器,限定在瀑布流的容器内
       img_selector:'img',//要加载的图片的选择器
       auto_imgHeight:true,//是否需要自动计算图片的高度
       fadein:true,//是否渐显载入
       fadein_speed:600,//渐显速率,单位毫秒
       insert_type:1, //砖块插入方式,1为插入最短那列,2为按序轮流插入
       getResource:function(index){ }  //获取动态资源函数,必须返回一个砖块元素集合,传入参数为加载的次数
   },
   //
   waterfall=$.waterfall={},
   $container=null;//容器
   waterfall.load_index=0, //加载次数
   $.fn.extend({
       waterfall:function(opt){
          opt=opt||{}; 
              setting=$.extend(setting,opt);
          $container=waterfall.$container=$(this);
          waterfall.$columns=creatColumn();
          render($(this).find(setting.cell_selector).detach(),false); //重排已存在元素时强制不渐显
          waterfall._scrollTimer2=null;
          $(window).bind('scroll',function(){
             clearTimeout(waterfall._scrollTimer2);
             waterfall._scrollTimer2=setTimeout(onScroll,300);
          });
          waterfall._scrollTimer3=null;
          $(window).bind('resize',function(){
             clearTimeout(waterfall._scrollTimer3);
             waterfall._scrollTimer3=setTimeout(onResize,300);
          });
       }
   });
   function creatColumn(){//创建列
           waterfall.column_num=calculateColumns();//列数
      //循环创建列
      var html='';
      for(var i=0;i<waterfall.column_num;i++){
         html+='<div class="'+setting.column_className+'" style="width:'+setting.column_width+'px; display:inline-block; *display:inline;zoom:1; margin-left:'+setting.column_space/2+'px;margin-right:'+setting.column_space/2+'px; vertical-align:top; overflow:hidden"></div>';
      }
      $container.prepend(html);//插入列
      return $('.'+setting.column_className,$container);//列集合
   }
   function calculateColumns(){//计算需要的列数
           var num=Math.floor(($container.innerWidth())/(setting.column_width+setting.column_space));
      if(num<1){ num=1; } //保证至少有一列
      return num;
   }
   function render(elements,fadein){//渲染元素
      if(!$(elements).length) return;//没有元素
      var $columns = waterfall.$columns;
      $(elements).each(function(i){                                    
          if(!setting.auto_imgHeight||setting.insert_type==2){//如果给出了图片高度,或者是按顺序插入,则不必等图片加载完就能计算列的高度了
             if(setting.insert_type==1){
                insert($(elements).eq(i),setting.fadein&&fadein);//插入元素
             }else if(setting.insert_type==2){
                insert2($(elements).eq(i),i,setting.fadein&&fadein);//插入元素  
             }
             return true;//continue
          }                    
          if($(this)[0].nodeName.toLowerCase()=='img'||$(this).find(setting.img_selector).length>0){//本身是图片或含有图片
              var image=new Image;
              var src=$(this)[0].nodeName.toLowerCase()=='img'?$(this).attr('src'):$(this).find(setting.img_selector).attr('src');
              image.onload=function(){//图片加载后才能自动计算出尺寸
                  image.onreadystatechange=null;
                  if(setting.insert_type==1){
                     insert($(elements).eq(i),setting.fadein&&fadein);//插入元素
                  }else if(setting.insert_type==2){
                     insert2($(elements).eq(i),i,setting.fadein&&fadein);//插入元素 
                  }
                  image=null;
              }
              image.onreadystatechange=function(){//处理IE等浏览器的缓存问题:图片缓存后不会再触发onload事件
                  if(image.readyState == "complete"){
                     image.onload=null;
                     if(setting.insert_type==1){
                        insert($(elements).eq(i),setting.fadein&&fadein);//插入元素
                     }else if(setting.insert_type==2){
                        insert2($(elements).eq(i),i,setting.fadein&&fadein);//插入元素  
                     }
                     image=null;
                  }
              }
              image.src=src;
          }else{//不用考虑图片加载
              if(setting.insert_type==1){
                 insert($(elements).eq(i),setting.fadein&&fadein);//插入元素
              }else if(setting.insert_type==2){
                 insert2($(elements).eq(i),i,setting.fadein&&fadein);//插入元素 
              }
          }                    
      });
   }
   function public_render(elem){//异步数据渲染接口函数      
       render(elem,true);
   }
   function insert($element,fadein){//把元素插入最短列
      if(fadein){//渐显
         $element.css('opacity',0).appendTo(waterfall.$columns.eq(calculateLowest())).fadeTo(setting.fadein_speed,1);
      }else{//不渐显
         $element.appendTo(waterfall.$columns.eq(calculateLowest()));
      }
   }
   function insert2($element,i,fadein){//按序轮流插入元素
      if(fadein){//渐显
         $element.css('opacity',0).appendTo(waterfall.$columns.eq(i%waterfall.column_num)).fadeTo(setting.fadein_speed,1);
      }else{//不渐显
         $element.appendTo(waterfall.$columns.eq(i%waterfall.column_num));
      }
   }
   function calculateLowest(){//计算最短的那列的索引
      var min=waterfall.$columns.eq(0).outerHeight(),min_key=0;
      waterfall.$columns.each(function(i){                        
         if($(this).outerHeight()<min){
            min=$(this).outerHeight();
            min_key=i;
         }                            
      });
      return min_key;
   }
   function getElements(){//获取资源
      $.waterfall.load_index++;
      return setting.getResource($.waterfall.load_index,public_render);
   }
   waterfall._scrollTimer=null;//延迟滚动加载计时器
   function onScroll(){//滚动加载
      clearTimeout(waterfall._scrollTimer);
      waterfall._scrollTimer=setTimeout(function(){
          var $lowest_column=waterfall.$columns.eq(calculateLowest());//最短列
          var bottom=$lowest_column.offset().top+$lowest_column.outerHeight();//最短列底部距离浏览器窗口顶部的距离
          var scrollTop=document.documentElement.scrollTop||document.body.scrollTop||0;//滚动条距离
          var windowHeight=document.documentElement.clientHeight||document.body.clientHeight||0;//窗口高度
          if(scrollTop>=bottom-windowHeight){
             render(getElements(),true);
          }
      },100);
   }
   function onResize(){//窗口缩放时重新排列
      if(calculateColumns()==waterfall.column_num) return; //列数未改变,不需要重排
      var $cells=waterfall.$container.find(setting.cell_selector);
      waterfall.$columns.remove();
      waterfall.$columns=creatColumn();
      render($cells,false); //重排已有元素时强制不渐显
   }
})(jQuery);

貌似把代码贴进来格式有点乱了,哎先不管了。上面的代码要是看不清可以在demo页直接查看源文件。

插件使用方法:
$(selector).waterfall(opt); //其中selector为瀑布流容器的选择器,opt为配置参数对象
所需的html结构:html结构可以就是一个空容器元素,如

,里面的砖块元素通过动态加载进来。当然也可以预先放一些砖块进去,如demo页中的

<div id="container">
    <div class="cell"><img src="http://cued.xunlei.com/demos/publ/img/P_000.jpg" /><p>00</p></div>
    <div class="cell"><img src="http://cued.xunlei.com/demos/publ/img/P_001.jpg" /><p>01</p></div>
    <div class="cell"><img src="http://cued.xunlei.com/demos/publ/img/P_002.jpg" /><p>02</p></div>
    <div class="cell"><img src="http://cued.xunlei.com/demos/publ/img/P_003.jpg" /><p>03</p></div>
    <div class="cell"><img src="http://cued.xunlei.com/demos/publ/img/P_004.jpg" /><p>04</p></div>
    <div class="cell"><img src="http://cued.xunlei.com/demos/publ/img/P_005.jpg" /><p>05</p></div>
    <div class="cell"><img src="http://cued.xunlei.com/demos/publ/img/P_006.jpg" /><p>06</p></div>
    <div class="cell"><img src="http://cued.xunlei.com/demos/publ/img/P_007.jpg" /><p>07</p></div>
    <div class="cell"><img src="http://cued.xunlei.com/demos/publ/img/P_008.jpg" /><p>08</p></div>
    <div class="cell"><img src="http://cued.xunlei.com/demos/publ/img/P_009.jpg" /><p>09</p></div>
    <div class="cell"><img src="http://cued.xunlei.com/demos/publ/img/P_010.jpg" /><p>10</p></div>
    <div class="cell"><img src="http://cued.xunlei.com/demos/publ/img/P_011.jpg" /><p>11</p></div>
    <div class="cell"><img src="http://cued.xunlei.com/demos/publ/img/P_012.jpg" /><p>12</p></div>
    <div class="cell"><img src="http://cued.xunlei.com/demos/publ/img/P_013.jpg" /><p>13</p></div>
    <div class="cell"><img src="http://cued.xunlei.com/demos/publ/img/P_014.jpg" /><p>14</p></div>
    <div class="cell"><img src="http://cued.xunlei.com/demos/publ/img/P_015.jpg" /><p>15</p></div>
    <div class="cell"><img src="http://cued.xunlei.com/demos/publ/img/P_016.jpg" /><p>16</p></div>
    <div class="cell"><img src="http://cued.xunlei.com/demos/publ/img/P_017.jpg" /><p>17</p></div>
    <div class="cell"><img src="http://cued.xunlei.com/demos/publ/img/P_018.jpg" /><p>18</p></div>
    <div class="cell"><img src="http://cued.xunlei.com/demos/publ/img/P_019.jpg" /><p>19</p></div>
</div>

下面详细说下配置参数对象opt的各属性的作用及其默认值。

column_width:204 //瀑布流是由列组成的,该参数规定了每列的宽度,该参数会直接影响到瀑布流的列数

column_className:’waterfall_column’ //列的类名,便于自定义样式

column_space:10 //列与列之间的间距

cell_selector:’.cell’ //要排列的砖块的选择器,限定在瀑布流的容器内,即插件是通过这个选择器来获取砖块元素的,并且是在瀑布流的容器内来查找这个选择器匹配的元素。

img_selector:’img’ //要加载的图片的选择器。如果你的瀑布流要加载的砖块元素的主题内容是大小不固定的图片,则该参数就是这些图片的选择器,插件需要获取这些图片来进行计算。

auto_imgHeight:true //是否需要自动计算图片的高度,如果图片的大小是固定的,则把该参数设为false吧

fadein:true //是否渐显载入

fadein_speed:600 //渐显速率,单位毫秒

insert_type:1 //砖块插入方式,1为插入最短那列,2为按序轮流插入

getResource:function(index,render){ } //获取动态资源函数,必须返回一个砖块元素集合,传入的第一个参数index为已加载的次数,第二个参数为渲染函数,它可以接受一个砖头元素集合作为参数,如果是使用ajax加载数据,则得到数据后要手动调用该函数来进行渲染 。每次到达瀑布流底部时会自动触发该函数来加载更多资源。

吐槽时间:

瀑布流加载的内容一般都宽度相同,高度不同的图片,如果能预先知道图片的高度,那就简单多了,但如果不能,则必须等到图片加载后才能计算出图片的高度,这是瀑布流最烦人的地方,也正是因为这样,如果是那些不知道高度的图片,则插入的顺序可能会有些混乱,而且每次刷新顺序都不同,因为每张图片加载完成的先后顺序并不是固定的,也许这次这个快一点,下次那个快一点。所以如果图片高度事先不知道,则整个砖块的高度也会不知道,必须等砖块里的图片加载完成后才能算出砖块的高度。如果是这样但又想保证砖块的插入顺序,则建议使用按顺序轮流插入的方式插入砖块,即把insert_type参数设为2。因为是插件,所以要考虑使用简便,但使用起来越简便,插件内部就会越复杂,漏洞、bug也会增多,所以我会继续完善这个插件。

本插件支持IE6+、chrome、firefox、opera、safari等主流浏览器。