[MVC]ASP.NET MVC Action Filter - Caching and Compr

mikel阅读(835)

原文地址: ASP.NET MVC Action Filter – Caching and Compression

下载源码: Source.zip

关于Action Filter你可以参考我的另外一篇文章: ASP.NET MVC : Action过滤器(Filtering)

 

缓存在开发高扩充性WEB程序的时候扮演着很重要的角色.我们可以将HTTP请求在一个定义的时间内缓存在用户的浏览器中,如果用户在定义的时间内请求同一个URL,那么用户的请求将会从用户浏览器的缓存中加载,而不是从服务器.你可以在ASP.NET MVC应用程序中使用下面的Action Filter来实现同样的事情:

using System;
using System.Web;
using System.Web.Mvc;
public class CacheFilterAttribute : ActionFilterAttribute
{
/// <summary>
/// Gets or sets the cache duration in seconds. The default is 10 seconds.
/// </summary>
/// <value>The cache duration in seconds.</value>
public int Duration
{
get;
set;
}
public CacheFilterAttribute()
{
Duration = 10;
}
public override void OnActionExecuted(FilterExecutedContext filterContext)
{
if (Duration <= 0) return;
HttpCachePolicyBase cache = filterContext.HttpContext.Response.Cache;
TimeSpan cacheDuration = TimeSpan.FromSeconds(Duration);
cache.SetCacheability(HttpCacheability.Public);
cache.SetExpires(DateTime.Now.Add(cacheDuration));
cache.SetMaxAge(cacheDuration);
cache.AppendCacheExtension("must-revalidate, proxy-revalidate");
}
}

你可以好像下面一样在你的Controller Action 方法中使用这个Filter :

 
[CacheFilter(Duration = 60)]
public void Category(string name, int? page)

下面是在firebug中当 缓存Filter 没有应用的时候的截图 :

NoCache

下面的截图是应用了 Cache Filter 时候的截图 :

Cache

 

另外一个很重要的事情就是压缩.现在的浏览器都可以接收压缩后的内容,这可以节省大量的带宽.你可以在你的ASP.NET MVC 程序中应用下面的Action Filter 来压缩你的Response :

 
using System.Web;
using System.Web.Mvc;
public class CompressFilter : ActionFilterAttribute
{
public override void OnActionExecuting(FilterExecutingContext filterContext)
{
HttpRequestBase request = filterContext.HttpContext.Request;
string acceptEncoding = request.Headers["Accept-Encoding"];
if (string.IsNullOrEmpty(acceptEncoding)) return;
acceptEncoding = acceptEncoding.ToUpperInvariant();
HttpResponseBase response = filterContext.HttpContext.Response;
if (acceptEncoding.Contains("GZIP"))
{
response.AppendHeader("Content-encoding", "gzip");
response.Filter = new GZipStream(response.Filter, CompressionMode.Compress);
}
else if (acceptEncoding.Contains("DEFLATE"))
{
response.AppendHeader("Content-encoding", "deflate");
response.Filter = new DeflateStream(response.Filter, CompressionMode.Compress);
}
}
}

然后将这个Filter应用到你的Controller Action 中 :

[CompressFilter]
public void Category(string name, int? page)

下面是没有应用压缩的时候的截图 :

Uncompressed

下面的截图是应用了压缩Filter后的情形 :

Compressed

你当然也可以将这两个Filter都应用到同一个Action方法上,就好像下面所示 :

 
[CompressFilter(Order = 1)]
[CacheFilter(Duration = 60, order = 2)]
public void Category(string name, int? page)

下面是截图 :

Both

Enjoy!!!

下载源码: Source.zip

作者:QLeelulu
出处:http://QLeelulu.cnblogs.com/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利
 

[C#]List(T) 的实现实体对象操作

mikel阅读(762)

实体对象类:

新闻发布实体类
     [MapTo(
"us_News")]
    
public abstract class NewsBLL 
    {
        [MapTo(
"FId")]
        
public abstract string Id { get;}
        
/// <summary>
        
/// 标题
        
/// </summary>
        [MapTo("FCaption")]
        
public abstract string Caption {getset;}
        
/// <summary>
        
/// 新闻类别PK-ss_Item.ID
        
/// </summary>
        [MapTo("FType")]
        [ManyToOne(LocalKey 
= "FType", ForeignKey = "FId")]
        
public abstract DictItemBLL Type {getset;}
        
/// <summary>
        
/// 新闻内容
        
/// </summary>
        [MapTo("FContent")]
        
public abstract string Content {getset;}
        
/// <summary>
        
/// 发布人
        
/// </summary>
        [MapTo("FUserID")]
        
public abstract string UserID {getset;}        
     }
新闻类别类
    [MapTo(
"ss_Item")]
    
public abstract class DictItemBLL 
    {
        [MapTo(
"FId")]
        
public abstract int Id { get;}
        
/// <summary>
        
/// 分组编号
        
/// </summary>
        [MapTo("FClassID")]
        
public abstract int ClassID {getset;}
        
/// <summary>
        
/// 父节点ID
        
/// </summary>
        [MapTo("FParentID")]
        
public abstract int ParentID {getset;}
        
/// <summary>
        
/// 字典名称
        
/// </summary>
        [MapTo("FName")]
        
public abstract string Name {getset;}
        
#endregion
    }

1,使用Find搜索单个匹配值

 

    NewsBLL news = list.Find(delegate(NewsBLL x) 
           { 
               
return x.Type.Id == 10001//搜索新闻列表中类别(Type)子对象中的 Id=10001的单个对象
           });

2,使用FindAll搜索多个匹配值

    List<NewsBLL> newsList = list.Find(delegate(NewsBLL x) 
           { 
               
return x.Type.Id == 10001//搜索新闻列表中类别(Type)子对象中的 Id=10001的多个对象
           });

3,是用Contains检查满足条件的值是否存在

 

    bool isContains= list.Find(delegate(NewsBLL x) 
           { 
               
return x.Type.Id == 10001 && x.UserID=="ejiyuan"//搜索新闻列表中类别(Type)子对象中的 Id=10001,并且发布人等于ejiyuan的是否存在
           });

4,使用ForEach 对每个列表对象进行操作

 

    list.Find(delegate(NewsBLL x) 
           { 
               x.Caption 
= "10001";  //将列表中所有标题都修改为10001

           });

5,使用sort排序,按类别 id排序

            list.Sort(delegate(NewsBLL x,DocumentBLL y)
            {
                
if (x.Type.Id < y.Type.Id )
                {
                    
return 1;
                }
                
else if (x.Type.Id == y.Type.Id )
                {
                    
return 0;
                }
                
else return 1;
            });

 

[C#]从.NET中委托写法的演变谈开去(下):性能相关

mikel阅读(704)

上一篇文章中,我们详细讲述了C# 3.0中Lambda表达式(构造委托)的使用方式,它在语义上的优势及对编程的简化——这些内容已经属于委托的“扩展内容”。不如这次谈得更远一些,就来讨论一下上文中“编程方式”的性能相关话题。

循环分离及其性能

  在上文的第一个示例中, 我们演示了如何使用Lambda表达式配合.NET 3.5中定义的扩展方法来方便地处理集合中的元素(筛选,转化等等)。不过有朋友可能会提出,那个“普通写法”并非是性能最高的实现方法。方便起见,也为 了突出“性能”方面的问题,我们把原来的要求简化一下:将序列中的偶数平方输出为一个列表。按照那种“普通写法”可能就是:

static List<int> EvenSquare(IEnumerable<int> source)
{
var evenList = new List<int>();
foreach (var i in source)
{
if (i % 2 == 0) evenList.Add(i);
}
var squareList = new List<int>();
foreach (var i in evenList) squareList.Add(i * i);
return squareList;
}

  从理论上来说,这样的写法的确比以下的做法在性能要差一些:

static List<int> EvenSquareFast(IEnumerable<int> source)
{
List<int> result = new List<int>();
foreach (var i in source)
{
if (i % 2 == 0) result.Add(i * i);
}
return result;
}

  在第二种写法直接在一次遍历中进行筛选,并且直接转化。而第一种写法会则根据“功能描述”将做法分为两步,先筛选后转化,并使用一个临时列表进 行保存。在向临时列表中添加元素的时候,List<int>可能会在容量不够的时候加倍并复制元素,这便造成了性能损失。虽然我们通过“分 析”可以得出结论,不过实际结果还是使用CodeTimer来测试一番比较妥当:

List<int> source = new List<int>();
for (var i = 0; i < 10000; i++) source.Add(i);
// 预热
EvenSquare(source);
EvenSquareFast(source);
CodeTimer.Initialize();
CodeTimer.Time("Normal", 10000, () => EvenSquare(source));
CodeTimer.Time("Fast", 10000, () => EvenSquareFast(source));

  我们准备了一个长度为10000的列表,并使用EvenSquare和EvenSquareFast各执行一万次,结果如下:

Normal
Time Elapsed:   3,506ms
CPU Cycles:     6,713,448,335
Gen 0:          624
Gen 1:          1
Gen 2:          0
Fast
Time Elapsed:   2,283ms
CPU Cycles:     4,390,611,247
Gen 0:          312
Gen 1:          0
Gen 2:          0

  结果同我们料想中的一致,EvenSquareFast无论从性能还是GC上都领先于EvenSquare方法。不过,在实际情况下,我们该选择哪种做法呢?如果是我的话,我会倾向于选择EvenSquare,理由是“清晰”二字。

  EvenSquare虽然使用了额外的临时容器来保存中间结果(因此造成了性能和GC上的损失),但是它的逻辑和我们需要的功能较为匹配,我们 可以很容易地看清代码所表达的含义。至于其中造成的性能损失在实际项目中可以说是微乎其微的。因为实际上我们的大部分性能是消耗在每个步骤的功能上,例如 每次Int32.Parse所消耗的时间便是一个简单乘法的几十甚至几百倍。因此,虽然我们的测试体现了超过50%的性能差距,不过由于这只是“纯遍历” 所消耗的时间,因此如果算上每个步骤的耗时,性能差距可能就会变成10%,5%甚至更低。

  当然,如果是如上述代码那样简单的逻辑,则使用EvenSquareFast这样的实现方式也没有任何问题。事实上,我们也不必强求将所有步骤 完全合并(即仅仅使用1次循环)或完全分开。我们可以在可读性与性能之间寻求一种平衡,例如将5个步骤使用两次循环来完能是更合适的方式。

  说到“分解循环”,其实这类似于Martin Fowler在他的重构网站所上列出的重构方式之一:“Split Loop”。虽然Split Loop和我们的场景略有不同,但是它也是为了代码的可读性而避免将多种逻辑放在一个循环内。将循环拆开之后,还可以配合“Extract Method”或“Replace Temp with Query”等方式实现进一步的重构。自然,它也提到拆分后的性能影响:

You often see loops that are doing two different things at once, because they can do that with one pass through a loop. Indeed most programmers would feel very uncomfortable with this refactoring as it forces you to execute the loop twice – which is double the work.

But like so many optimizations, doing two different things in one loop is less clear than doing them separately. It also causes problems for further refactoring as it introduces temps that get in the way of further refactorings. So while refactoring, don't be afraid to get rid of the loop. When you optimize, if the loop is slow that will show up and it would be right to slam the loops back together at that point. You may be surprised at how often the loop isn't a bottleneck, or how the later refactorings open up another, more powerful, optimization.

  这段文字提到,当拆分之后,您可能会发现更好的优化方式。高德纳爷爷也认为“过早优化是万恶之源”。这些说法都在“鼓励”我们将程序写的更清晰而不是“看起来”更有效率

扩展方法的延迟特性

  对于上面的简化需求,使用Lambda表达式和.NET 3.5中内置的扩展方法便可以写成这样:

static List<int> EvenSquareLambda(IEnumerable<int> source)
{
return source.Where(i => i % 2 == 0).Select(i => i + 1).ToList();
}

  应该已经有许多朋友了解了.NET 3.5中处理集合时扩展方法具有“延迟”的效果,也就是说Where和Select中的委托(两个Lambda表达式)只有在调用ToList方法的时候 才会执行。这是优点也是陷阱,在使用这些方法的时候我们还是需要了解这些方法的效果如何。不过这些方法其实都没有任何任何“取巧”之处,换句话说,它们的 行为和我们正常思维的结果是一致的。如果您想得明白,能够自己写出类似的方法,或者能够“自圆其说”,十有八九也不会有什么偏差。但是如果您想不明白它们 是如何构造的,还是通过实验来确定一下吧。实验的方式其实很简单,只要像我们之前验证“重复计算”陷阱那种方法就可以了,也就是观察委托的执行时机和顺序进行判断。

  好,回到我们现在的问题。我们知道了“延迟”效果,我们知道了Where和Select会在ToList的时候才会进行处理。不过,它们的处理 方式是什么样的,是像我们的“普通方法”那样“创建临时容器(如List<T>),并填充返回”吗?对于这点我们不多作分析,还是通过“观察 委托执行的时机和顺序”来寻找答案。使用这种方式的关键,便是在委托执行时打印出一些信息。为此,我们需要这样一个Wrap方法(您自己做试验时也可以使 用这个方法):

static Func<T, TResult> Wrap<T, TResult>(
Func<T, TResult> func,
string messgaeFormat)
{
return i =>
{
var result = func(i);
Console.WriteLine(messgaeFormat, i, result);
return result;
};
}

  Wrap方法的目的是将一个Func<T, TResult>委托对象进行封装,并返回一个类型相同的委托对象。每次执行封装后的委托时,都会执行我们提供的委托对象,并根据我们传递的messageFormat格式化输出。例如:

var wrapper = Wrap<int, int>(i => i + 1, "{0} + 1 = {1}");
for (var i = 0; i < 3; i++) wrapper(i);

  则会输出:

0 + 1 = 1
1 + 1 = 2
2 + 1 = 3

  那么,我们下面这段代码会打印出什么内容呢?

List<int> source = new List<int>();
for (var i = 0; i < 10; i++) source.Add(i);
var finalSource = source
.Where(Wrap<int, bool>(i => i % 3 == 0, "{0} can be divided by 3? {1}"))
.Select(Wrap<int, int>(i => i * i, "The square of {0} equals {1}."))
.Where(Wrap<int, bool>(i => i % 2 == 0, "The result {0} can be devided by 2? {1}"));
Console.WriteLine("===== Start =====");
foreach (var item in finalSource)
{
Console.WriteLine("===== Print {0} =====", item);
}

  我们准备一个列表,其中包含0到9共十个元素,并将其进行Where…Select…Where的处理,您可以猜出经过foreach之后屏幕上的内容吗?

===== Start =====
0 can be divided by 3? True
The square of 0 equals 0.
The result 0 can be devided by 2? True
===== Print 0 =====
1 can be divided by 3? False
2 can be divided by 3? False
3 can be divided by 3? True
The square of 3 equals 9.
The result 9 can be devided by 2? False
4 can be divided by 3? False
5 can be divided by 3? False
6 can be divided by 3? True
The square of 6 equals 36.
The result 36 can be devided by 2? True
===== Print 36 =====
7 can be divided by 3? False
8 can be divided by 3? False
9 can be divided by 3? True
The square of 9 equals 81.
The result 81 can be devided by 2? False

  列表中元素的执行顺序是这样的:

  1. 第一个元素“0”经过Where…Select…Where,最后被Print出来。
  2. 第二个元素“1”经过Where,中止。
  3. 第三个元素“2”经过Where,中止。
  4. 第四个元素“4”经过Where…Select…Where,中止。
  5. ……

  这说明了,我们使用.NET框架自带的Where或Select方法,最终的效果和上一节中的“合并循环”类似。因为,如果创建了临时容器保存 元素的话,就会在第一个Where中把所有元素都交由第一个委托(i => i % 3 == 0)执行,然后再把过滤后的元素交给Select中的委托(i => i * i)执行。请注意,在这里“合并循环”的效果对外部是隐藏的,我们的代码似乎还是一步一步地处理集合。换句话说,我们使用“分解循环”的清晰方式,但获得 了“合并循环”的高效实现。这就是.NET框架这些扩展方法的神奇之处1

  在我们进行具体的性能测试之前,我们再来想一下,这里出现了那么多IEnumerable对象实现了哪个GoF 23中的模式呢?枚举器?看到IEnumerable就说枚举器也太老生常谈了。其实这里同样用到了“装饰器”模式。每次Where或Select之后其 实都是使用了一个新的IEnumerable对象来封装原有的对象,这样我们遍历新的枚举器时便会获得“装饰”后的效果。因此,以后如果有人问您 “.NET框架中有哪些的装饰器模式的体现”,除了人人都知道的Stream之外,您还可以回答说“.NET 3.5中System.Linq.Enumerable类里的一些扩展方法”,多酷。

扩展方法的性能测试

  经过上节的分析,我们知道了Where和Select等扩展方法统一了“分解循环”的外表和“合并循环”的内在,也就是兼顾了“可读性”和“性能”。我们现在就使用下面的代码来验证这一点:

List<int> source = new List<int>();
for (var i = 0; i < 10000; i++) source.Add(i);
EvenSquare(source);
EvenSquareFast(source);
EvenSquareLambda(source);
CodeTimer.Initialize();
CodeTimer.Time("Normal", 10000, () => EvenSquare(source));
CodeTimer.Time("Fast", 10000, () => EvenSquareFast(source));
CodeTimer.Time("Lambda", 10000, () => EvenSquareLambda(source));

  结果如下:

Normal
Time Elapsed:   3,127ms
CPU Cycles:     6,362,621,144
Gen 0:          624
Gen 1:          3
Gen 2:          0
Fast
Time Elapsed:   2,031ms
CPU Cycles:     4,070,470,778
Gen 0:          312
Gen 1:          0
Gen 2:          0
Lambda
Time Elapsed:   2,675ms
CPU Cycles:     5,672,592,948
Gen 0:          312
Gen 1:          156
Gen 2:          0

  从时间上看,“扩展方法”实现的性能介于“分解循环”和“合并循环”两者之间。而GC方面,“扩展方法”实现也优于“分解循环”(您同意这个看法吗?)。因此我们可以得出以下结论:

  性能 可读性
分解循环 No. 3 No. 2
合并循环 No. 1 No. 3
扩展方法 No. 2 No. 1

  至于选择哪种方式,就需要您自行判断了。

  值得注意的是,无论是“延迟”还是“分解循环”的效果,刚才我们都是针对于Where和Select来谈的。事实上,还有并不是所有的扩展方法都有类似的特性,例如:

  • 非延迟:ToArray、ToList、Any,All,Count……
  • 非分解循环:OrderBy,GroupBy,ToDictionary……

  不过别担心,正如上节所说,是否“延迟”,是否“分解循环”都是非常明显的。如果您可以写出类似的方法,或者能够“自圆其说”,一般您的判断也 不会有什么错误。例如,OrderBy为什么不是“分解循环”的呢?因为在交由下一步枚举之前,它必须将上一步中的所有元素都获取出来才能进行排序。如果 您无法“很自然”地想象出这些“原因”,那么就写一段程序来自行验证一番吧。

其他性能问题

  一般来说,这些扩展方法本身不太会出现性能问题,但是任何东西都可能被滥用,这才是程序中的性能杀手。例如:

IEnumerable<int> source = ...;
for (var i = 0; i < source.Count(); i++)
{
...
}

  这段代码的问题,在于每次循环时都需要不断地计算出source中元素的数量,这意味着不断地完整遍历、遍历。对于一些如Count或Any这 样“立即执行”的方法,我们在使用时脑子里一定要留有一些概念:它会不会出现性能问题。这些问题的确很容易识别,但是我的确看到过这样的错误。即使在出现 这些扩展方法之前,我也看到过某些朋友编写类似的代码,如在for循环中不断进行str.Split(',').Length。

  还是再强调一下“重复计算”这个问题吧,它可能更容易被人忽视。如果您“重复计算”的集合是内存中的列表,这对性能来说可能影响不大。但是,试想您每次重复计算时,都要重复去外部数据源(如数据库)获取原始数据,那么此时造成的性能问题便无法忽视了。

总结

  这个系列的文章就写到这里,似乎想谈的也谈的差不多了。这三篇文章是由《关于最近面试的一点感想》引起的,我写文章一开始的目的也是希望证明“委托也是个值得一提的内容”。了解委托的写法,了解这些变化并非仅仅是“茴有几种写法”。这里引用钧梓昊逑同学的评论,我认为说得很有道理:

我觉得问某件事物在不同的版本中有什么区别还是可以比较容易判断出他的掌握和理解程度的。新版本中每一个新特性的加入并不是随意加的,为什么要加, 加之前有什么不足,加了之后有什么好处,在什么时候什么场景下用这些新特性,用了有什么风险和弊端,等等。如果能非常流利得回答,就算不是很正确也至少说 明他去认真思考过了,因为这些东西可能是网上书上所没有的。

  所以,我在面试时也会提出“delegate写法演变”或类似的问题。甚至我认为这是一个非常好的入手点,因为在.NET中如此经典的演变并不 多见。如果一个人可以把这些内容都理清,我有理由相信他对待技术的态度是非常值得肯定的。反观原文后许多带有嘲讽的评论,在这背后我不知道他们是真正了解 了委托而认为这些内容不值一提,还是“自以为”理解了全部内容,却不知道在这看似简单的背后还隐藏着庞大的深层的思维和理念。

  我想,我们还是不要轻易地去“轻视”什么东西吧,例如在面试时轻视对方提出的问题,看重框架而轻视语言,看重所谓“底层”而轻视“应用”。最后,我打算引用韦恩卑鄙同学的话来结束全文:

一般说C语言比C#强大的,C语言也就写到Hello World而已。

相关文章

  • 从.NET中委托写法的演变谈开去(上):委托与匿名方法
  • 从.NET中委托写法的演变谈开去(中):Lambda表达式及其优势
  • 从.NET中委托写法的演变谈开去(下):性能相关
  •  

    注1:虽然Where和Select具有“延迟”效果,但是内部实现是“分解循环”还是“合并循环”则是另一种选择。您能否尝试在“延迟”的前提下,提供“分解循环”和“合并循环”两种Where或Select的实现呢?

    [C#]从.NET中委托写法的演变谈开去(中):Lambda表达式及其优势

    mikel阅读(547)

    上一篇文章中我们简单探讨了.NET 1.x和.NET 2.0中委托表现形式的变化,以及.NET 2.0中匿名方法的优势、目的及注意事项。那么现在我们来谈一下.NET 3.5(C# 3.0)中,委托的表现形式又演变成了什么样子,还有什么特点和作用。

    .NET 3.5中委托的写法(Lambda表达式)

      Lambda表达式在C#中的写法是“arg-list => expr-body”,“=>”符号左边为表达式的参数列表,右边则是表达式体(body)。参数列表可以包含0到多个参数,参数之间使用逗号分割。例如,以下便是一个使用Lambda表达式定义了委托的示例1

    Func<int, int, int> max = (int a, int b) =>
    {
    if (a > b)
    {
    return a;
    }
    else
    {
    return b;
    }
    };
    

      与上文使用delegate定义匿名方法的作用相同,Lambda表达式的作用也是为了定义一个匿名方法。因此,下面使用delegate的代码和上面是等价的:

    Func<int, int, int> max = delegate(int a, int b)
    {
    if (a > b)
    {
    return a;
    }
    else
    {
    return b;
    }
    };
    

      那么您可能就会问,这样看来Lambda表达式又有什么意义呢?Lambda表达式的意义便是它可以写的非常简单,例如之前的Lambda表达式可以简写成这样:

    Func<int, int, int> max = (a, b) =>
    {
    if (a > b)
    {
    return a;
    }
    else
    {
    return b;
    }
    };
    

      由于我们已经注明max的类型是Func<int, int, int>,因此C#编译器可以明确地知道a和b都是int类型,于是我们就可以省下参数之前的类型信息。这个特性叫做“类型推演”,也就是指编译器可以自动知道某些成员的类型2。请不要轻易认为这个小小的改进意义不大,事实上,您会发现Lambda表达式的优势都是由这一点一滴的细节构成的。那么我们再来一次改变:

    Func<int, int, int> max = (a, b) => a > b ? a : b;

      如果Lambda表达式的body是一个表达式(expression),而不是语句(statement)的话,那么它的body就可以省略大括号和return关键字。此外,如果Lambda表达式只包含一个参数的话,则参数列表的括号也可以省略,如下:

    Func<int, bool> positive = a => a > 0;

      如今的写法是不是非常简单?那么我们来看看,如果是使用delegate关键字来创建的话会成为什么样子:

    Func<int, bool> positive = delegate(int a)
    {
    return a > 0;
    };
    

      您马上就可以意识到,这一行和多行的区别,这几个关键字和括号的省略,会使得编程世界一下子变得大为不同。

      当然,Lambda表达式也并不是可以完全替代delegate写法,例如带ref和out关键字的匿名方法,就必须使用.NET 2.0中的delegate才能构造出来了。

    使用示例一

      Lambda表达式的增强在于“语义”二字。“语义”是指代码所表现出来的含义,说的更通俗一些,便是指一段代码给阅读者的“感觉”如何。为了说明这个例子,我们还是使用示例来说明问题。

      第一个例子是这样的:“请写一个方法,输入一个表示整型的字符串列表,并返回一个列表,包含其中偶数的平方,并且需要按照平方后的结果排序”。很简单,不是吗?相信您一定可以一蹴而就:

    static List<int> GetSquaresOfPositive(List<string> strList)
    {
    List<int> intList = new List<int>();
    foreach (var s in strList) intList.Add(Int32.Parse(s));
    List<int> evenList = new List<int>();
    foreach (int i in intList)
    {
    if (i % 2 == 0) evenList.Add(i);
    }
    List<int> squareList = new List<int>();
    foreach (int i in evenList) squareList.Add(i * i);
    squareList.Sort();
    return squareList;
    }
    

      我想问一下,这段代码给您的感觉是什么?它给我的感觉是:做了很多事情。有哪些呢?

    1. 新建一个整数列表intList,把参数strList中所有元素转化为整型保存起来。
    2. 新建一个整数列表evenList,把intList中的偶数保存起来。
    3. 新建一个整数列表squareList,把evenList中所有数字的平方保存起来。
    4. 将squareList排序。
    5. 返回squareList。

      您可能会问:“当然如此,还能怎么样?”。事实上,如果使用了Lambda表达式,代码就简单多了:

    static List<int> GetSquaresOfPositiveByLambda(List<string> strList)
    {
    return strList
    .Select(s => Int32.Parse(s)) // 转成整数
    .Where(i => i % 2 == 0) // 找出所有偶数
    .Select(i => i * i) // 算出每个数的平方
    .OrderBy(i => i) // 按照元素自身排序
    .ToList(); // 构造一个List
    }
    

      配合.NET 3.5中定义的扩展方法,这段代码可谓“一气呵成”(在实际编码过程中,老赵更倾向于把这种简短的“递进式”代码写作一行)。那么这行代码的“语义”又有 什么变化呢?在这里,“语义”的变化在于代码的关注点从“怎么做”变成了“做什么”。这就是Lambda表达式的优势。

      在第一个方法中,我们构造了多个容器,然后做一些转化,过滤,并且向容器填充内容。其实这些都是“怎么做”,也就是所谓的“how (to do)”。但是这些代码并不能直接表示我们想要做的事情,我们想要做的事情其实是“得到XXX”,“筛选出YYY”,而不是“创建容器”,“添加元素”等 操作。

      在使用Lambda表达式的实现中,代码变得“声明式(declarative)”了许多。所谓“声明式”,便是“声称代码在做什么”,而不像 “命令式(imperative)”的代码在“操作代码怎么做”。换句话说,“声明式”关注的是“做什么”,是指“what (to do)”。上面这段声明式的代码,其语义则变成了:

    1. 把字符串转化为整数
    2. 筛选出所有偶数
    3. 把每个偶数平方一下
    4. 按照平方结果自身排序 
    5. 生成一个列表

      至于其中具体是怎么实现的,有没有构造新的容器,又是怎么向容器里添加元素的……这些细节,使用Lambda表达式的代码一概不会关心——这又不是我们想要做的事情,为什么要关心它呢?

      虽然扩展方法功不可没,但我认为,Lambda表达式在这里的重要程度尤胜前者,因为它负责了最关键的“语义”。试想,“i => i * i”给您的感觉是什么呢?是构造了一个委托吗(当然,您一定知道在这里其实构造了一个匿名方法)?至少对我来说,它的含义是“把i变成i * i”;同样,“i => i % 2 == 0”给我的感觉是“(筛选标准为)i模2等于零”,而不是“构造一个委托,XXX时返回true,否则返回false”;更有趣的是,OrderBy(i => i)给我的感觉是“把i按照i自身排序”,而不是“一个返回i自身的委托”。这一切,都是在“声明”这段代码在“做什么”,而不是“怎么做”。

      没错,“类型推演”,“省略括号”和“省略return关键字”可能的确都是些“细小”的功能,但也正是这些细微之处带来了编码方式上的关键性改变。

    使用示例二

      使用Lambda表达式还可以节省许多代码(相信您从第一个示例中也可以看出来了)。不过我认为,最省代码的部分更应该可能是其“分组”和“字典转化”等功能。因此,我们来看第二个示例。

      这个示例可能更加贴近现实。不知您是否关注过某些书籍后面的“索引”,它其实就是“列出所有的关键字,根据其首字母进行分组,并且要求对每组内部的关键字进行排序”。简单说来,我们需要的其实是这么一个方法:

    static Dictionary<char, List<string>> GetIndex(IEnumerable<string> keywords) { ... }

      想想看,您会怎么做?其实不难(作为示例,我们这里只关注小写英文,也不关心重复关键字这种特殊情况):

    static Dictionary<char, List<string>> GetIndex(IEnumerable<string> keywords)
    {
    // 定义字典
    var result = new Dictionary<char, List<string>>();
    // 填充字典
    foreach (var kw in keywords)
    {
    var firstChar = kw[0];
    List<string> groupKeywords;
    if (!result.TryGetValue(firstChar, out groupKeywords))
    {
    groupKeywords = new List<string>();
    result.Add(firstChar, groupKeywords);
    }
    groupKeywords.Add(kw);
    }
    // 为每个分组排序
    foreach (var groupKeywords in result.Values)
    {
    groupKeywords.Sort();
    }
    return result;
    }
    

      那么如果利用Lambda表达式及.NET框架中定义的扩展方法,代码又会变成什么样呢?请看:

    static Dictionary<char, List<string>> GetIndexByLambda(IEnumerable<string> keywords)
    {
    return keywords
    .GroupBy(k => k[0]) // 按照首字母分组
    .ToDictionary( // 构造字典
    g => g.Key, // 以每组的Key作为键
    g => g.OrderBy(k => k).ToList()); // 对每组排序并生成列表
    }
    

      光从代码数量上来看,前者便是后者的好几倍。而有关“声明式”,“what”等可读性方面的优势就不再重复了,个人认为它比上一个例子给人的“震撼”有过之而无不及。

      试想,如果我们把GetIndexByLambda方法中的Lambda表达式改成.NET 2.0中delegate形式的写法:

    static Dictionary<char, List<string>> GetIndexByDelegate(IEnumerable<string> keywords)
    {
    return keywords
    .GroupBy(delegate(string k) { return k[0]; })
    .ToDictionary(
    delegate(IGrouping<char, string> g) { return g.Key; },
    delegate(IGrouping<char, string> g)
    {
    return g.OrderBy(delegate(string s) { return s; }).ToList();
    });
    }
    

      您愿意编写这样的代码吗?

      因此,Lambda表达式在这里还是起着决定性的作用。事实上正是因为有了Lambda表达式,.NET中的一些函数式编程特性才被真正推广开 来。“语言特性”决定“编程方式”的确非常有道理。这一点上Java是一个很好的反例:从理论上说,Java也有“内联”的写法,但是C#的使用快感在 Java那边还只能是个梦。试想GetIndexByLambda在Java中会是什么情况3

    public Dictionary<Char, List<String>> GetIndexInJava(Enumerable<String> keywords)
    {
    return keywords
    .GroupBy(
    new Func<String, Char> {
    public Char execute(String s) { return s.charAt(0); }
    })
    .ToDictionary(
    new Func<Grouping<Char, String>, Char> {
    public Char execute(IGrouping<Char, String> g) { return g.getKey(); }
    },
    new Func<Grouping<Char, String>, List<string>> {
    public List<String> execute(IGrouping<Char, String> g)
    {
    return g
    .OrderBy(
    new Func<String, String> {
    public String execute(String s) { return s; }
    })
    .ToList();
    }
    });
    }

      一股语法噪音的 气息扑面而来,让人无法抵挡。由于Java中的匿名类型语法(即上面这种内联写法)连类型信息(new Func<String, Char>{ … }这样的代码)都无法省去,因此给人非常繁琐的感觉。面对这样的代码,您可能会有和我一样的想法:“还不如最普通的写法啊”。没错,这种函数式编程的风 格,由于缺乏语言特性支持,实在不适合在Java语言中使用。事实上,这种内联写法很早就出现了(至少在02、03年我还在使用Java的时候就已经有 了),但是那么多年下来一点改进都没有。而Lambda表达式出现之后,社区中立即跟进了大量项目,如MoqFluent NHibernate等等,充分运用了C# 3.0的这一新特性。难道这还不够说明问题吗?

      对了,再次推荐一下Scala语言,它的代码可以写的和C#一样漂亮。我不是Java平台的粉丝,更是Java语言的忠实反对者,但是我对Java平台上的Scala语言和开源项目都抱有强烈的好感。

      既然谈到了函数式编程,那么就顺便再多说几句。其实这两个例子都有浓厚的函数式编程影子在里面,例如,对于函数试编程来说,Where常被叫做 filter,Select常被叫做map。而.NET 3.5中定义的另一些方法在函数式编程里都有体现(如Aggregate相当于fold)。如果您对这方面感兴趣,可以关注Matthew Poswysocki提出的Functional C#类库。

    总结

      既可以提高可读性,又能够减少代码数量,我实在找不出任何理由拒绝Lambda表达式。

      哦,对了,您可能会提到“性能”,这的确也是一个重要的方面,不过关于这个话题我们下次再谈。受篇幅限制,原本计划的“上”“下”两篇这次又不得不拆开了。至于其他的内容,也等讨论完性能问题之后再说吧。

      当然,世界上没有东西是完美的,如果您觉得Lambda表达式在某些时候会给您带来“危害”,那么也不妨使用delegate代替Lambda 表达式。例如,为了代码清晰,在某些时候还是显式地指明参数类型比较好。不过对我而言,在任何情况下我都会使用Lambda表达式——最多使用“(int a, string b) =>”的形式咯,我想总比“delegate(int a, string b)”要统一、省事一些吧。

    相关文章

     

    注1:严格说来,这里的body是一个“语句(statement)”,而不是“表达式 (expression)”。因为一个委托其实是一个方法,因此使用Lambda来表示一个委托,其中必然要包含“语句”。不过在目前的C# 中,Lambda表达式还有一个作用是构造一颗“表达式树”,而目前的C#编译器只能构造“表达式树”而不是“语句树”。

    注2:事实上,在.NET 2.0使用delegate关键字定义匿名方法时已经可以有些许“类型推演”的意味了——虽然还是必须写明参数的类型,但是我们已经可以省略委托的类型了,不是吗?

    注3:除非我们补充Func、Enumerable,Dictionary,Grouping等类型及API,否则这段代码在Java中是无法编译通过的。事实上,这段Java代码是我在记事本中写出来的。不过这个形式完全正确。

    [C#]从.NET中委托写法的演变谈开去(上):委托与匿名方法

    mikel阅读(649)

      在《关于最近面试的一点感想》 一文中,Michael同学谈到他在面试时询问对方“delegate在.net framework1.1,2.0,3.5各可以怎么写”这个问题。于是乎,有朋友回复道“请问楼主,茴香豆的茴有几种写法”,“当代孔乙己”,独乐,众 乐。看了所有的评论,除了某些朋友认为“的确不该不知道这个问题”之外,似乎没有什么人在明确支持楼主。

      不过我支持,为什么?因为我也提过出这样的问题。

      这样,我们暂且不提应聘“高级开发人员”的人,在“自称熟悉各版本.NET框架”的前提下,是否应该知道这个答案。我们也暂且不提Michael同学提问的“目的”是什么。老赵就先单独针对这个问题进行解释,然后谈谈自己为什么会提出这个问题吧。

       可能有一件事情需要说在前面,那就是:委托本身其实从来没有改变过,改变的一直都是委托的“写法”。因此更确切地说,改变的只是“编译器”。而本文所有 内容都用C#来实现,其实谈得也都是C#编译器本身——但是其实VB.NET也有变化啊。再由于.NET版本和C#版本的关系也是非常密切的,因此全文就 使用.NET版本进行指代了。

    .NET 1.x中委托的写法

      委托,如果不追究细节,从表面上来看我们可以将其通俗地理解为一个安全的“函数指针”。当然,这个函数指针其实也是一个对象,有自己的成员,也会封装了被调用方的上下文等等。至于委托的定义和使用方式,则是这样的:

    public delegate int SomeDelegate(string arg1, bool arg2);
    public static int SomeMethod(string arg1, bool arg2) { return 0; }
    public class SomeClass
    {
    public int SomeMethod(string a1, bool a2) { return 0; }
    public event SomeDelegate SomeEvent;
    }
    static void Main(string[] args)
    {
    SomeClass someClass = new SomeClass();
    SomeDelegate someDelegate = new SomeDelegate(someClass.SomeMethod);
    someClass.SomeEvent += new SomeDelegate(SomeMethod);
    }
    

      可见,在.NET 1.x中需要使用new DelegateType(…)的方式来创建一个委托对象。不过,作为委托对象内部的方法它既可以是实例方法,也可以是静态方法。此外,方法只需要匹配委托类型的签名和返回值即可,方法参数的名称不会成为约束。

      嗯,就是这么简单。

    .NET 2.0中委托的写法

      .NET中的委托引入了范型,且写法略有简化:

    public delegate TResult MyFunc<T1, T2, TResult>(T1 a1, T2 a2);
    public static int SomeMethod(string a1, bool a2) { return 0; }
    static void Main(string[] args)
    {
    MyFunc<string, bool, int> myFunc = SomeMethod;
    }
    

      在.NET 2.0中,new DelegateType已经可以省略,开发人员可以直接将方法赋值给一个委托对象的引用。当然,这个改进不值一提,.NET 2.0中委托写法的关键在于引入了“匿名方法”:

    public static void TestRequest(string url)
    {
    WebRequest request = HttpWebRequest.Create(url);
    request.BeginGetResponse(delegate(IAsyncResult ar)
    {
    using (WebResponse response = request.EndGetResponse(ar))
    {
    Console.WriteLine("{0}: {1}", url, response.ContentLength);
    }
    },
    null);
    }
    

      匿名方法,简单地说就是内联在方法内部的委托对象,它的关键便在于形成了一个闭包(委托执行时所需的上下文)。如上面的代码 中,BeginGetResponse的第一个参数(委托)可以直接使用TestRequest方法的参数url,以及方法内的“局部”变量 request。如果没有匿名函数这个特性的话,代码写起来就麻烦了,例如在.NET 1.x中您可能就必须这么写:

    折叠
    public static void TestRequest(string url)
    {
    WebRequest request = HttpWebRequest.Create(url);
    object[] context = new object[] { url, request };
    request.BeginGetResponse(TestAsyncCallback, context);
    }
    public static void TestAsyncCallback(IAsyncResult ar)
    {
    object[] context = (object[])ar.AsyncState;
    string url = (string)context[0];
    WebRequest request = (WebRequest)context[1];
    using (WebResponse response = request.EndGetResponse(ar))
    {
    Console.WriteLine("{0}: {1}", url, response.ContentLength);
    }
    }
    

      此时,我们往往会发现,开发人员需要花费大量的精力,为一小部分代码维护一大段上下文。例如在这段代码中,我们会将url和request对象 塞入一个object数组中,在回调函数中再通过危险的Cast操作恢复数据。如果您希望“强类型”,那么只能为每个回调创建一个新的上下文对象,维护起 来可能更加麻烦——要知道,在并行编程,异步调用越来越重要的今天,如果没有匿名方法自动保留上下文的特性,开发人员会为这些“额外工作”疲于奔命的。

      可能您会说,匿名方法的可读性不佳,因为需要“内联”。一个方法中内联太多,维护成本就上去了,所以匿名方法并不推荐使用。我想说的是,您错了。如果为了可维护性,要将方法独立拆开,也可以利用匿名方法的优势:

    public static void TestRequest(string url)
    {
    WebRequest request = HttpWebRequest.Create(url);
    request.BeginGetResponse(delegate(IAsyncResult ar)
    {
    TestAsyncCallback(ar, request, url);
    }, null);
    }
    public static void TestAsyncCallback(IAsyncResult ar, WebRequest request, string url)
    {
    using (WebResponse response = request.EndGetResponse(ar))
    {
    Console.WriteLine("{0}: {1}", url, response.ContentLength);
    }
    }
    

      如果借助.NET 3.5中的Lambda表达式,代码可以写的更简单易读:

    public static void TestRequest(string url)
    {
    WebRequest request = HttpWebRequest.Create(url);
    request.BeginGetResponse(ar => TestAsyncCallback(ar, request, url), null);
    }
    

    匿名方法的作用

      千万不要小看匿名方法的作用,有些时候您认为它的作用仅限于上文描述,只是因为没有在某些问题上踏前一步。例如,对于那些只需要“按需创建”,且要“线程安全”的对象,您会怎么做呢?没错,可以使用Double Check:

    private object m_mutex = new object();
    private bool m_initialized = false;
    private BigInstance m_instance = null;
    public BigInstance Instance
    {
    get
    {
    if (!this.m_initialized)
    {
    lock (this.m_mutex)
    {
    if (!this.m_initialized)
    {
    this.m_instance = new BigInstance();
    this.m_initialized = true;
    }
    }
    }
    return this.m_instance;
    }
    }
    

      嗯,做的很漂亮!那么……这样的属性再来一个,再来三个,再来五个呢?可能有些朋友就会开始大段地Copy & Paste,于是错误便难免了。这里有一件真人真事,以前某位同学在一堆这样的代码中迷茫了,说为什么用了这种方法,还是初始化了多次对象了?检查了半天 没有看出问题来。最后发现,原因是访问了错误的initialized变量(例如,在某个应该访问artistInitialized的地方访问了 articleInitialized)。可惜,大段时间已经被浪费了——更糟的是,心情也随之变差了。

      其实,Copy & Paste很明显没有遵守DRY原则啊。为什么不把它们封装在一处呢?例如:

    折叠
    public class Lazy<T>
    {
    public Lazy(Func<T> func)
    {
    this.m_initialized = false;
    this.m_func = func;
    this.m_mutex = new object();
    }
    private Func<T> m_func;
    private bool m_initialized;
    private object m_mutex;
    private T m_value;
    public T Value
    {
    get
    {
    if (!this.m_initialized)
    {
    lock (this.m_mutex)
    {
    if (!this.m_initialized)
    {
    this.m_value = this.m_func();
    this.m_func = null;
    this.m_initialized = true;
    }
    }
    }
    return this.m_value;
    }
    }
    }
    

      于是,之前的代码就可以简化成这样了:

    private Lazy<BigInstance> m_lazyInstance =
    new Lazy<BigInstance>(delegate { return new BigInstance(); });
    public BigInstance Instance { get { return this.m_lazyInstance.Value; } }
    

      还是太丑,上Lambda表达式!

    private Lazy<BigInstance> m_lazyInstance =
    new Lazy<BigInstance>(() => new BigInstance());
    public BigInstance Instance { get { return this.m_lazyInstance.Value; } }
    

      如果没有匿名方法,许多容易使用的编程模型和方式都难以开展。例如,我们就不会有CacheHelper,也不会有AsyncTaskDispatcher(上),也很难利用“延迟”所带来的便利,更难以出现微软并行扩展、CCR等优秀框架。可以这么说,如果您不善于使用委托,您如果不知道如何合适地使用匿名方法,您在不自知的情况下可能就已经编写了大量额外的代码了。

      老赵平时的工作之一,便是为项目提供各种扩展API,可以让程序员们更愉快地进行开发工作,得到更好的生产力,让代码变得更加美好。如今C#有了匿名方法、Lambda表达式、表达式树、扩展方法等优秀的语言特性,真让我有“如鱼得水”的感觉。因此,我对于Java这样不思进取的语言可以说深恶痛绝(Java朋友们赶快学习Scala吧)。在看阅读大量Java开源项目代码时,我常有这样的感觉:“如果是C#的话,利用匿名方法,这个类不就可以不写,那个类就可以省略……”。没错,为了保留回调函数的上下文而创建一些类,对于C#程序员来说,的确是一件有些不可思议的事情。

      至于Lambda表达式以及其他话题,我们下次再说吧。

    匿名方法的缺点

      匿名方法的优势在于自动形成闭包,而它的缺点也是让程序员“不自觉”地创建了闭包,这会让某些对象的生命周期加长。例如在一开始的 TestRequest方法中,表面上看起来url是参数,request是局部变量,有些朋友可能会认为它们在方法退出后就已经准备回收了。不过因为形 成了闭包,url和request已经“升级”为一个对象的域变量,它的生命周期延长了,延长至回调函数执行完毕。因此,一不注意可能就会产生一些莫名其妙的情况

      其实,这些都是“延迟”所带来的陷阱,作为一个优秀的开发人员,除了知道某个东西的作用和优势,也要知道它的问题,不是吗?

    总结

      您现在还觉得.NET中的“委托”是一个简单的,只适合“初学者”,一搜就都知道的概念吗?

      您可能会说“是”,不过对我来说这真不简单,我也是慢慢才意识到这些的。虽然关于委托的大量内容可以在互联网上搜索而来,但还是有太多东西是需要在大量编程实践中积累下来的——等一下,这不就是“高级开发人员”和“初学者”的主要区别之一吗?

      嘲笑孔乙己的朋友们,你们在一味鄙视“茴”的四种写法的同时,说不定也失去了一个了解中国传统文化的机会呢!

      剩下的下次再说吧,Lambda表达式还等着我们。

    [MVC]ASP.NET MVC实用集锦(2)

    mikel阅读(800)

    继续和大家分享一些MVC使用技巧与实例。
    首先想再补充一下集锦(1)中 的扩展htmlhelper问题,其实在C#3.0,就已经提供了扩展方法这个新特性,我们可以在程序中扩展一些基本类型,也可以扩展一些自定义类型。例 如:String,Datetime ,Enumerable等,具体步骤与扩展htmlhelper没什么区别(还是提醒一下别忘记引用命名空间)。
    继续(1)中的顺序往下进行吧.
    5.在view与Mvc view user control中使用强类型。

    <%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master"
        Inherits="System.Web.Mvc.ViewPage<Account>" %>
    <%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<List<Account>>" %>

    在view 或 Mvc view user control中的代码如下

    <% if (Model != null) { %>
          <div><%= Model.Name %></div>
          <div><%= Model.Age %></div>
    <% } %>

    在controller中需要将Model赋值

    public ActionResult ShowAccount()
    {
          var account = new Account { Name = "Name" , Age = 18 };
          return View(account);
    }

    顺便一提:
    当我需要呈现View的时候我会选择 return View()
    当我需要ajax异步请求到controller做一些事情时,处理结束后,我会通过 return Json() 返回操作是否成功或返回的数据。
    当我需要调用其他action时我会使用 RedirectToAction 或 RedirectToRoute。
    当我什么都不需要做的时候我会使用 EmptyResult()。例如数据导出操作等。
    6.UpdateModel与TryUpdateModel
    虽然在MVC 中可以直接在Controller的方法中定义操作对象,然后通过表单提交装入数据到对象,如下:

    public ActionResult Register(Account account)
    { //TODO }

    但 我会使用UpdateModel和TryUpdateModel去做这些事情,还是简单说一下这两个方法的功能。如果用户操作的view是完成注册用户功 能,此时表单数据已经填写完整,提交表单后转入上面的方法( Register() , 去掉参数),在方法中使用如下代码:

    public ActionResult Register()
    {
          var account = new Account();
          UpdateModel(account);
          //TODO: Register
          return ……
    }

    这样表单中的数据可以装入 account 对象,后来发现有时总是不成功,程序报错,遂发现TryUpdateModel(),虽然执行后也会有错误,但是部分数据已经正确填入程序也并不会报错, 然后将没有填入的数据(问题就发生在这些数据上)单独处理。不能装入的数据通常都是由规律的,例如ID,view中通过DropDownList选择的数 据等等,所以可以使用重载方法将可能会出错的数据排除掉, string[] excludeProperties 参数,这样TryUpdateModel就不会报错了,虽然已经找到装入对象时出错的规律,但还是想找到更准确的判断方法,不想这样偷懒下去。偶然间注意 到 ModelState.IsValid 这句经常出现的代码,所以就试了一下,在TryUpdateModel语句执行后,在快速监视中添加了ModelState,果然可以看到出错的字段(里 面有error属性)。后面的操作就是一样的了,排除这些字段,单独处理!

    7.在Action中使用FormCollection,6中使用 string[] excludeProperties 参数排除掉的字段可以通过下面的方法赋值

    public ActionResult Register(FormCollection form)
    {
          var account = new Account();
          // city在表单中是下拉框选择的(DropDownList)
          if (TryUpdateModel(account , null ,  null , new string[] { "City" } ))   // UpdateModel也可以重载
          {
                account.City = form["City"];
          }
          return ……
    }

    [MVC]在 ASP.NET MVC 中使用 HTTPS (SSL/TLS)

    mikel阅读(811)

    某些安全性较高的网页,如网上支付或用户登陆页面,可能会使用到https(SSL/TLS)来提高安全性。本文介绍了如何在ASP.NET MVC中强制某action使用https和如何进行向https页面的跳转。
    我们先实现强制一个action使用https。这里写了一个RequireHttpsAttribute,它的作用是将非https连接转换成https连接,这样所有使用了RequireHttps这个filter的controller都会强制使用https连接。
     

     1 using System.Web.Mvc;
     2 
     3 namespace Snowdream.Demo.RequireHttps
     4 {
     5     public class RequireHttpsAttribute:AuthorizeAttribute
     6     {
     7         /// <summary>
     8         /// 重写OnAuthorization方法
     9         /// </summary>
    10         /// <param name="filterContext"></param>
    11         public override void OnAuthorization(AuthorizationContext filterContext)
    12         {
    13             // 如果已经是https连接则不处理,否则重定向到https连接
    14             if (!filterContext.HttpContext.Request.IsSecureConnection)
    15             {
    16                 // 获取当前请求的Path
    17                 string path = filterContext.HttpContext.Request.Path;
    18 
    19                 // 从web.config中获取host,也可以直接从httpContext中获取
    20                 string host = System.Configuration.ConfigurationManager.AppSettings["HostName"];
    21 
    22                 // 从web.config中获取https的端口
    23                 string port = System.Configuration.ConfigurationManager.AppSettings["HttpsPort"];
    24 
    25                 // 如果端口号为空表示使用默认端口,否则将host写成host:port的形式
    26                 if (port != null)
    27                 {
    28                     host = string.Format("{0}:{1}", host, port);
    29                 }
    30 
    31                 // 重定向到https连接
    32                 filterContext.HttpContext.Response.Redirect(string.Format("https://{0}{1}", host, path));
    33             }
    34         }
    35     }
    36 }
    37 

    由于https和https服务使用不同的端口号,而且https不能绑定主机头,只能通过不同端口的方式来区分各个站点,所以这里将host和port信息写到了web.config里,以方便配置。在web.config的appsettings节加入如下信息即可

    1 <appSettings>
    2     <add key="HostName" value="localhost"/>
    3     <add key="httpsPort" value="443"/>
    4 </appSettings>
    5 

    HttpsPort可以不写,将使用默认的443。
    然后在要使用https连接的controller或action前加上[RequireHttps],如

    1 [RequireHttps]
    2 public ActionResult About()
    3 {
    4     return View();
    5 }
    6 

    这样,当我们用http://localhost/Home/About访问该页面时会自动跳到https://localhost/Home/About。 但是这样还有一个问题,网页中的链接都是http的,当点击进入需要使用https连接的网页时都要进行一次Redirect。所以我们要将网页中的链接 也改成https。这步不难,只需要将view中所有链接到https页面的Html.Action()使用适当的重载方法来写即可。ASP.NET MVC 1.0 RTM中提供了2种重载可以将protocol设置为https。在新建ASP.NET MVC Web Application后默认生成的站点中,shared文件夹下有site.master文件中有个指向/Home/About的 ActionLink。原来是

    1 Html.ActionLink("Home""Index""Home")

    我们对其进行改写

    1 Html.ActionLink("About""About""Home""https""localhost""",nullnull)

    这样,生成出来的链接就是https的了,点击以后直接会使用https连接而无需再进行一次Redirect,之后新的要到https页面的链接也可仿照次写法。
    这 里又要用到hostName信息,我们之前已经将它写在web.config里了,所以可以专门写一个方法来获取web.config中的这部分信息并拼 接成这里需要的hostName字符串,或者还可以对HtmlHelper写一个扩展方法专门用于处理https的链接,这些可以在实际使用时做适当的优 化。
    示例代码下载

    [JQuery]如何在多个页面使用同一个HTML片段

    mikel阅读(770)

    下载源代码
    问题描述
    有一个比较复杂的HTML片段(A),如果把这个HTML片段嵌入到其他页面中(B,C,D….)。
    问题的关键是在HTML片段中有大量的JavaScript逻辑需要处理,比如说分页,点击事件响应等。
    对于这个问题,我们用一个简单的例子来说明:
    “页面上有一个按钮,点击此按钮引入一个HTML片段,此HTML片段中有分页按钮。”
    1. 使用IFrame
    主页面,点击一个按钮向页面引入一个IFrame:

        <script type="text/javascript">
    $(function() {
    $("#clickToInsert").click(function() {
    $("#placeholder").html('<iframe src="iframe.htm"></iframe>');
    });
    });
    </script>
    <input type="button" id="clickToInsert" value="Insert HTML" />
    <div id="placeholder">
    </div>
    

    IFrame页面,模拟分页的情况:

        <script type="text/javascript">
    $(function() {
    var parent = $("#complex_page_segment");
    $(".previous", parent).click(function() {
    $(".content", parent).html("Previous Page Content");
    });
    $(".next", parent).click(function() {
    $(".content", parent).html("Next Page Content");
    });
    });
    </script>
    <div id="complex_page_segment">
    <input type="button" value="Previous Page" class="previous" />
    <input type="button" value="Next Page" class="next" />
    <div class="content">
    Page Content</div>
    </div>
    

    2. AJAX返回页面片段,并注册事件
    注:我们通过textarea来模拟返回的HTML片段。

        <script type="text/javascript">
    $(function() {
    $("#clickToInsert").click(function() {
    $("#placeholder").html($("#clone").val());
    var parent = $("#complex_page_segment");
    $(".previous", parent).click(function() {
    $(".content", parent).html("Previous Page Content");
    });
    $(".next", parent).click(function() {
    $(".content", parent).html("Next Page Content");
    });
    });
    });
    </script>
    <input type="button" id="clickToInsert" value="Insert HTML" />
    <div id="placeholder">
    </div>
    <textarea id="clone" style="display: none;">
    <div id="complex_page_segment">
    <input type="button" value="Previous Page" class="previous" />
    <input type="button" value="Next Page" class="next" />
    <div class="content">Page Content</div>
    </div>
    </textarea>
    

    由于我们需要在多个页面引用同一个HTML片段,这种方法导致大量事情处理被重复性的拷贝粘贴,明显我们需要将公共的方法提取出来。
    3. AJAX返回页面片段,并调用页面片段中的函数注册事件

        <script type="text/javascript">
    $(function() {
    $("#clickToInsert").click(function() {
    $("#placeholder").html($("#clone").val());
    init_complex_page_segment();
    });
    });
    </script>
    <input type="button" id="clickToInsert" value="Insert HTML" />
    <div id="placeholder">
    </div>
    <textarea id="clone" style="display: none;">
    <script type="text/javascript">
    function init_complex_page_segment() {
    var parent = $("#complex_page_segment");
    $(".previous", parent).click(function() {
    $(".content", parent).html("Previous Page Content");
    });
    $(".next", parent).click(function() {
    $(".content", parent).html("Next Page Content");
    });
    }
    </script>
    <div id="complex_page_segment">
    <input type="button" value="Previous Page" class="previous" />
    <input type="button" value="Next Page" class="next" />
    <div class="content">Page Content</div>
    </div>
    </textarea>
    

    其实我们可以更进一步,完全没必要手工调用这个函数,而是可以在返回的HTML片段中让其自动执行。
    4. AJAX返回页面片段,其事件自动注册

        <script type="text/javascript">
    $(function() {
    $("#clickToInsert").click(function() {
    $("#placeholder").html($("#clone").val());
    });
    });
    </script>
    <input type="button" id="clickToInsert" value="Insert HTML" />
    <div id="placeholder">
    </div>
    <textarea id="clone" style="display: none;">
    <script type="text/javascript">
    $(function() {
    var parent = $("#complex_page_segment");
    $(".previous", parent).click(function() {
    $(".content", parent).html("Previous Page Content");
    });
    $(".next", parent).click(function() {
    $(".content", parent).html("Next Page Content");
    });
    });
    </script>
    <div id="complex_page_segment">
    <input type="button" value="Previous Page" class="previous" />
    <input type="button" value="Next Page" class="next" />
    <div class="content">Page Content</div>
    </div>
    </textarea>
    

    最后一种方法和第一种IFrame的方式是我们所推荐的。

    [JQuery]遍历声明的Json数组

    mikel阅读(813)

    以前基本上都是在用AJAX从服务器获得Json格式数据,然后直接调用JQuery的getJson中的Data然后
    $.each(data,function(){
    //具体代码
    });
    就行了,今天做模板,需要声明好json数组然后遍历显示,代码如下:
    var json=[{"Identifier":"1","Name":"lele"},{"Identifier":"2","Name":"fifi"},{"Identifier":"3","Name":"mikel"}];
    $.each(data,function(){
    alert(this.Name);
    });
    OR
    $(json).each(function(){
    alert(this.Name);
    });

    [Hook]Windows Hook 学习笔记(1)——基本概念

    mikel阅读(707)

    基本概念
          钩子(Hook),是Windows消息处理机制的一个平台,应用程序可以在上面设置子程以监视指定 窗口的某种消息,而且所监视的窗口可以是其他进程所创建的。当消息到达后,在目标窗口处理函数之前处理它。钩子机制允许应用程序截获处理window消息 或特定事件。
          钩子实际上是一个处理消息的程序段,通过系统调用,把它挂入系统。每当特定的消息发出,在没有到达目 的窗口前,钩子程序就先捕获该消息,亦即钩子函数先得到控制权。这时钩子函数即可以加工处理(改变)该消息,也可以不作处理而继续传递该消息,还可以强制 结束消息的传递。
    运行机制
    1、钩子链表和钩子子程:
          每一个Hook都有一个与之相关联的指针列表,称之为钩子链表,由系统来维护。这个列表的指针指向指 定的,应用程序定义的,被Hook子程调用的回调函数,也就是该钩子的各个处理子程。当与指定的Hook类型关联的消息发生时,系统就把这个消息传递到 Hook子程。一些Hook子程可以只监视消息,或者修改消息,或者停止消息的前进,避免这些消息传递到下一个Hook子程或者目的窗口。最近安装的钩子 放在链的开始,而最早安装的钩子放在最后,也就是后加入的先获得控制权。
          Windows 并不要求钩子子程的卸载顺序一定得和安装顺序相反。每当有一个钩子被卸载,Windows 便释放其占用的内存,并更新整个Hook链表。如果程序安装了钩子,但是在尚未卸载钩子之前就结束了,那么系统会自动为它做卸载钩子的操作。
          钩子子程是一个应用程序定义的回调函数(CALLBACK Function),不能定义成某个类的成员函数,只能定义为普通的C函数。用以监视系统或某一特定类型的事件,这些事件可以是与某一特定线程关联的,也可以是系统中所有线程的事件。
       钩子子程必须按照以下的语法:
    LRESULT CALLBACK HookProc
    (
    int nCode,
    WPARAM wParam,
    LPARAM lParam
    );
    • HookProc是应用程序定义的名字。
    • nCode参数是Hook代码,Hook子程使用这个参数来确定任务。这个参数的值依赖于Hook类型,每一种Hook都有自己的Hook代码特征字符集。
    • wParam和lParam参数的值依赖于Hook代码,但是它们的典型值是包含了关于发送或者接收消息的信息。
    2、钩子的安装与释放:
          使用API函数SetWindowsHookEx()把一个应用程序定义的钩子子程安装到钩子链表 中。SetWindowsHookEx函数总是在Hook链的开头安装Hook子程。当指定类型的Hook监视的事件发生时,系统就调用与这个Hook关 联的Hook链的开头的Hook子程。每一个Hook链中的Hook子程都决定是否把这个事件传递到下一个Hook子程。Hook子程传递事件到下一个 Hook子程需要调用CallNextHookEx函数。
    HHOOK SetWindowsHookEx(
         int idHook,      // 钩子的类型,即它处理的消息类型
         HOOKPROC lpfn,   // 钩子子程的地址指针。如果dwThreadId参数为0
    // 或是一个由别的进程创建的线程的标识,
    // lpfn必须指向DLL中的钩子子程。
    // 除此以外,lpfn可以指向当前进程的一段钩子子程代码。
    // 钩子函数的入口地址,当钩子钩到任何消息后便调用这个函数。
         HINSTANCE hMod,  // 应用程序实例的句柄。标识包含lpfn所指的子程的DLL。
    // 如果dwThreadId 标识当前进程创建的一个线程,
    // 而且子程代码位于当前进程,hMod必须为NULL。
    // 可以很简单的设定其为本应用程序的实例句柄。
         DWORD dwThreadId // 与安装的钩子子程相关联的线程的标识符。
    // 如果为0,钩子子程与所有的线程关联,即为全局钩子。
                     );   
          函数成功则返回钩子子程的句柄,失败返回NULL。
      以上所说的钩子子程与线程相关联是指在一钩子链表中发给该线程的消息同时发送给钩子子程,且被钩子子程先处理。
    在钩子子程中调用得到控制权的钩子函数在完成对消息的处理后,如果想要该消息继续传递,那么它必须调用另外一个 SDK中的API函数CallNextHookEx来传递它,以执行钩子链表所指的下一个钩子子程。这个函数成功时返回钩子链中下一个钩子过程的返回值, 返回值的类型依赖于钩子的类型。这个函数的原型如下:
    LRESULT CallNextHookEx
    (
    HHOOK hhk;
    int nCode;
    WPARAM wParam;
    LPARAM lParam;
    ); 
    • hhk为当前钩子的句柄,由SetWindowsHookEx()函数返回。
    • NCode为传给钩子过程的事件代码。
    • wParam和lParam 分别是传给钩子子程的wParam值,其具体含义与钩子类型有关。
          钩子函数也可以通过直接返回TRUE来丢弃该消息,并阻止该消息的传递。否则的话,其他安装了钩子的应用程序将不会接收到钩子的通知而且还有可能产生不正确的结果。
          钩子在使用完之后需要用UnHookWindowsHookEx()卸载,否则会造成麻烦。释放钩子比较简单,UnHookWindowsHookEx()只有一个参数。函数原型如下:
    UnHookWindowsHookEx
    (
    HHOOK hhk;
    );
    
          函数成功返回TRUE,否则返回FALSE。
     
    3、一些运行机制:
          在Win16环境中,DLL的全局数据对每个载入它的进程来说都是相同的;而在Win32环境中,情 况却发生了变化,DLL函数中的代码所创建的任何对象(包括变量)都归调用它的线程或进程所有。当进程在载入DLL时,操作系统自动把DLL地址映射到该 进程的私有空间,也就是进程的虚拟地址空间,而且也复制该DLL的全局数据的一份拷贝到该进程空间。也就是说每个进程所拥有的相同的DLL的全局数据,它 们的名称相同,但其值却并不一定是相同的,而且是互不干涉的。
          因此,在Win32环境下要想在多个进程中共享数据,就必须进行必要的设置。在访问同一个Dll的各 进程之间共享存储器是通过存储器映射文件技术实现的。也可以把这些需要共享的数据分离出来,放置在一个独立的数据段里,并把该段的属性设置为共享。必须给 这些变量赋初值,否则编译器会把没有赋初始值的变量放在一个叫未被初始化的数据段中。
          #pragma data_seg预处理指令用于设置共享数据段。例如:
    #pragma data_seg("SharedDataName")
    HHOOK hHook=NULL;
    #pragma data_seg()
     
          在#pragma data_seg("SharedDataName")和#pragma data_seg()之间的所有变量将被访问该Dll的所有进程看到和共享。再加上一条指令#pragma comment(linker,"/section:.SharedDataName,rws"),那么这个数据节中的数据可以在所有DLL的实例之间共 享。所有对这些数据的操作都针对同一个实例的,而不是在每个进程的地址空间中都有一份。
          当进程隐式或显式调用一个动态库里的函数时,系统都要把这个动态库映射到这个进程的虚拟地址空间里(以下简称"地址空间")。这使得DLL成为进程的一部分,以这个进程的身份执行,使用这个进程的堆栈。
     
    4、系统钩子与线程钩子:
          SetWindowsHookEx()函数的最后一个参数决定了此钩子是系统钩子还是线程钩子。
          线程勾子用于监视指定线程的事件消息。线程勾子一般在当前线程或者当前线程派生的线程内。
          系统勾子监视系统中的所有线程的事件消息。因为系统勾子会影响系统中所有的应用程序,所以勾子函数必须放在独立的动态链接库(DLL) 中。系统自动将包含"钩子回调函数"的DLL映射到受钩子函数影响的所有进程的地址空间中,即将这个DLL注入了那些进程。
    几点说明:
    (1)如果对于同一事件(如鼠标消息)既安装了线程勾子又安装了系统勾子,那么系统会自动先调用线程勾子,然后调用系统勾子。
    (2)对同一事件消息可安装多个勾子处理过程,这些勾子处理过程形成了勾子链。当前勾子处理结束后应把勾子信息传递给下一个勾子函数。
    (3)勾子特别是系统勾子会消耗消息处理时间,降低系统性能。只有在必要的时候才安装勾子,在使用完毕后要及时卸载。
     
    钩子类型
          每一种类型的Hook可以使应用程序能够监视不同类型的系统消息处理机制。下面描述所有可以利用的Hook类型。
    1、WH_CALLWNDPROC和WH_CALLWNDPROCRET Hooks
    WH_CALLWNDPROC和WH_CALLWNDPROCRET Hooks使你可以监视发送到窗口过程的消息。系统在消息发送到接收窗口过程之前调用WH_CALLWNDPROC Hook子程,并且在窗口过程处理完消息之后调用WH_CALLWNDPROCRET Hook子程。
    WH_CALLWNDPROCRET Hook传递指针到CWPRETSTRUCT结构,再传递到Hook子程。
    CWPRETSTRUCT结构包含了来自处理消息的窗口过程的返回值,同样也包括了与这个消息关联的消息参数。
    2、WH_CBT Hook
          在以下事件之前,系统都会调用WH_CBT Hook子程,这些事件包括:
          1. 激活,建立,销毁,最小化,最大化,移动,改变尺寸等窗口事件;
          2. 完成系统指令;
          3. 来自系统消息队列中的移动鼠标,键盘事件;
          4. 设置输入焦点事件;
          5. 同步系统消息队列事件。
          Hook子程的返回值确定系统是否允许或者防止这些操作中的一个。
    3、WH_Debug Hook
          在系统调用系统中与其他Hook关联的Hook子程之前,系统会调用WH_Debug Hook子程。你可以使用这个Hook来决定是否允许系统调用与其他Hook关联的Hook子程。
    4、WH_FOREGROUNDIDLE Hook
          当应用程序的前台线程处于空闲状态时,可以使用WH_FOREGROUNDIDLE Hook执行低优先级的任务。当应用程序的前台线程大概要变成空闲状态时,系统就会调用WH_FOREGROUNDIDLE Hook子程。
    5、WH_GETMESSAGE Hook
          应用程序使用WH_GETMESSAGE Hook来监视从GetMessage or PeekMessage函数返回的消息。你可以使用WH_GETMESSAGE Hook去监视鼠标和键盘输入,以及其他发送到消息队列中的消息。
    6、WH_JOURNALPLAYBACK Hook
          WH_JOURNALPLAYBACK Hook使应用程序可以插入消息到系统消息队列。可以使用这个Hook回放通过使用WH_JOURNALRECORD Hook记录下来的连续的鼠标和键盘事件。只要WH_JOURNALPLAYBACK Hook已经安装,正常的鼠标和键盘事件就是无效的。
          WH_JOURNALPLAYBACK Hook是全局Hook,它不能象线程特定Hook一样使用。
          WH_JOURNALPLAYBACK Hook返回超时值,这个值告诉系统在处理来自回放Hook当前消息之前需要等待多长时间(毫秒)。这就使Hook可以控制实时事件的回放。
          WH_JOURNALPLAYBACK是system-wide local hooks,它們不會被注射到任何行程位址空間。
    7、WH_JOURNALRECORD Hook
          WH_JOURNALRECORD Hook用来监视和记录输入事件。典型的,可以使用这个Hook记录连续的鼠标和键盘事件,然后通过使用WH_JOURNALPLAYBACK Hook来回放。
          WH_JOURNALRECORD Hook是全局Hook,它不能象线程特定Hook一样使用。
          WH_JOURNALRECORD是system-wide local hooks,它們不會被注射到任何行程位址空間。
    8、WH_KEYBOARD Hook
          在应用程序中,WH_KEYBOARD Hook用来监视WM_KEYDOWN and WM_KEYUP消息,这些消息通过GetMessage or PeekMessage function返回。可以使用这个Hook来监视输入到消息队列中的键盘消息。
    9、WH_KEYBOARD_LL Hook
          WH_KEYBOARD_LL Hook监视输入到线程消息队列中的键盘消息。
    10、WH_MOUSE Hook
          WH_MOUSE Hook监视从GetMessage 或者 PeekMessage 函数返回的鼠标消息。使用这个Hook监视输入到消息队列中的鼠标消息。
    11、WH_MOUSE_LL Hook
          WH_MOUSE_LL Hook监视输入到线程消息队列中的鼠标消息。
    12、WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks
          WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks使我们可以监视菜单,滚动条,消息框,对话框消息并且发现用户使用ALT+TAB or ALT+ESC 组合键切换窗口。WH_MSGFILTER Hook只能监视传递到菜单,滚动条,消息框的消息,以及传递到通过安装了Hook子程的应用程序建立的对话框的消息。WH_SYSMSGFILTER Hook监视所有应用程序消息。
          WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks使我们可以在模式循环期间过滤消息,这等价于在主消息循环中过滤消息。
          通过调用CallMsgFilter function可以直接的调用WH_MSGFILTER Hook。通过使用这个函数,应用程序能够在模式循环期间使用相同的代码去过滤消息,如同在主消息循环里一样。
    13、WH_SHELL Hook
          外壳应用程序可以使用WH_SHELL Hook去接收重要的通知。当外壳应用程序是激活的并且当顶层窗口建立或者销毁时,系统调用WH_SHELL Hook子程。
          WH_SHELL 共有5钟情況:
          1. 只要有个top-level、unowned 窗口被产生、起作用、或是被摧毁;
          2. 当Taskbar需要重画某个按钮;
          3. 当系统需要显示关于Taskbar的一个程序的最小化形式;
          4. 当目前的键盘布局状态改变;
          5. 当使用者按Ctrl+Esc去执行Task Manager(或相同级别的程序)。
          按照惯例,外壳应用程序都不接收WH_SHELL消息。所以,在应用程序能够接收WH_SHELL消息之前,应用程序必须调用SystemParametersInfo function注册它自己。