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

mikel阅读(963)

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

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

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

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

演示网站运行截图

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

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

image

调试截图:

image

设计目标

支持以下类型查询:

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

阻止某些查询:

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

扩展性:

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

易用性:

  • 简单使用

其它:

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

思考

想法源自 Entity Framework:

EF 中的 Convention

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

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

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

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

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

分解出 Convention

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

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

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

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

可设置的 Order 属性

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

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

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

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

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

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

编码实现

抽象出接口

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

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

修改 QueryConditionExpressionModelBinder 类

修改后代码如下:

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

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

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

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

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

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

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

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

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

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

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

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

QueryConditionExpressionModelBinder 中使用了 ConventionConfiguration 类:

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

   public static ConventionConfiguration Default = new ConventionConfiguration();

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

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

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

实现具体 Converntion:

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

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

项目类图

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

image image

image image

扩展方法类未列出。

QueryConditionExpressionModelBinder 使用

直接使用

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

或配置后使用

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

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

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

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

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

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

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

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

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

后记

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

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

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

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

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

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

mikel阅读(1009)

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

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

翻译:filod

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


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

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

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

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

像老板一样指挥你的代码

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

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

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

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

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

飞一般的方法绑定

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

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

下面是编译输出的JavaScript

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

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

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

在2011年函数是这样调用的

瞅一眼这个:

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

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

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

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

让初始化来的更性感吧

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

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

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

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

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

更多关于CoffeeScript请访问其官网

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

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

mikel阅读(929)

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

坏味道

特征

情况及处理方式

目标

重复代码

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

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

重复代码提取为方法

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

兄弟类含有相同表达式

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

不相干类含有相同代码

提取为独立类供调用

过长函数

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

提取方法

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

过大的类

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

部分字段之间相关性高

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

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

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

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

过长参数列

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

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

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

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

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

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

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

将方法移动到该对象中

某些数据缺乏归属对象

首先创建对象

发散式变化

一个类受多种变化的影响

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

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

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

散弹式修改

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

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

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

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

依恋情结

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

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

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

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

数据泥团

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

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

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

基本类型偏执

过多使用基本类型

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

提取类

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

参数列中有基本类型

提取参数对象

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

用对象取代数组

基本数据是类型码

使用类替换类型码

带条件表达式的类型码

使用继承类替换类型码

Switch语句

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

根据类型码进行选择的switch

使用多态替代switch

避免到处做相同的修改

单一函数中有switch

使用显式的方法取代参数

平行继承体系

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

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

避免到处做相同的修改

冗赘类

类无所事事

父类和子类无太大差别

将它们合为一体

某个类没有做太多事情

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

夸夸其谈未来性

某个抽象类没有太大作用

将父子类合并

不必要的委托

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

函数的某些参数未用上

移除参数

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

重命名函数名

函数只被测试方法调用

连同测试代码一并删除

令人迷惑的暂时字段

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

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

过度耦合的消息链

一长串的getThis或临时变量

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

隐藏委托

消除耦合

中间人

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

有一半的函数

移除中间人

少数几个函数

直接调用

中间人还有其他行为

让委托类继承受托类

狎昵关系

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

子类过分了解超类

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

封装

类之间双向关联

去掉不必要的关联

类之间有共同点

提取新类

异曲同工的类

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

合并

不完美的类库

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

想修改一两个函数

在调用类增加函数

想添加一大堆额外行为

使用子类或包装类

幼稚的数据类

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

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

封装

被拒绝的馈赠

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

子类拒绝继承超类接口

使用委托替代继承

过多的注释

一段代码有着长长的注释

消除各种坏味道

[转载]BlazeDS 整合 Flex HelloWorld 示例

mikel阅读(1206)

[转载]BlazeDS 整合 Flex HelloWorld 示例 – hoojo – 博客园.

开发环境:

System:Windows

WebBrowser:IE6+、Firefox3+

JavaEE Server:tomcat5.0.2.8、tomcat6

IDE:eclipse、MyEclipse 8

Flex IDE:Flash Builder 4

BlazeDS:4.5

开发依赖库:

JavaEE5、blazeDS 4.5

Email:hoojo_@126.com

Blog:http://blog.csdn.net/IBM_hoojo

http://hoojo.cnblogs.com/

一、准备工作

1、 首先要提供相关的jar包

Java服务器端需要提供BlazeDS相关的配置和jar包

下载地址:http://opensource.adobe.com/wiki/display/blazeds/download+blazeds+trunk

下载后,解压你可以看到这样的一个目录

clip_image002

Docs就是文档

Resource是源码

SampleDB是示例用的数据库,可以运行startdb.bat来启动数据库

Tomcat是内置的tomcat,如果你没有tomcat的话可以使用它,在tomcat的webapps目录中有samples示例

blazeds.war就是blazeDS的核心文件、库,你可以把这个war放到tomcat的webapps目录下,就会自动解压。当然你也可以自己手动解压。

Blazeds-spring.war是和spring整合的配置

Ds-console.war是blazeDS的控制台程序

Samples.war是官方提供的示例

Samples-spring.war是spring和blazeDS的整合示例

二、部署服务器端程序

1、新建一个JavaWeb Project工程,然后在WEB-INF/lib目录中添加如下jar包

clip_image004

这些jar包可以在blazeds.war包中的lib目录中可以找到

2、 然后你需要将blazeds.war包中的WEB-INF目录下的flex目录复制到当前工程的WEB-INF下

3、 将blazeds.war包中的WEB-INF目录下的web.xml的配置,添加到当前工程的web.xml文件中

4、 最后基本的样式如下

clip_image006

5、 最后你发布当前工程,如果没有错误就表明你服务器端部署成功了。

6、 编写一个HelloWorld的java程序。代码如下

package com.hoo.flex;

/**
 * <b>function:</b> HelloWorld Example
 * @author hoojo
 * @createDate 2011-8-31 下午06:11:27
 * @file HelloWorld.java
 * @package com.hoo.flex
 * @project BlazeDSServer
 * @blog http://blog.csdn.net/IBM_hoojo
 * @email hoojo_@126.com
 * @version 1.0
 */
public class HelloWorld {

    public HelloWorld() {
    }

    public String sayHello(String name) {
        return "[" + name + "] say hello!";
    }
}

就一个sayHello方法,接收一个参数。

三、Flex客户端程序

1、创建一个Flex工程,在选择服务器技术的时候,你需要选择J2EE。然后勾上使用J2EE技术,然后选择BlazeDS。点击Next下一步

clip_image008

2、配置根文件夹,也就是JavaEE服务器端发布程序在tomcat中的位置。我这里是在tomcat的webapps的 BlazeDSServer中,BlazeDSServer是我的服务器端程序。根URL是访问服务器端程序的url;上下文目录对应工程名称;最后就是 输出文件夹目录,这个是Flex的文件最后在tomcat中保存的目录。

clip_image010

3、最后你需要设置服务器端的services-config.xml的路径到编译参数中,这个很重要!如果你不设置的话,那么你在后面用RemoteObject调用BlazeDS的时候,就需要设置endpoint。设置如下:

clip_image012

-services是参数键,后面的字符串是值。我这里是设置BlazeDSServer发布到tomcat目录中的services-config.xml的路径。

4、编译Flex前端代码,代码如下:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" viewSourceURL="BlazeDSHelloWorld.mxml" layout="absolute" minWidth="955" minHeight="600">
    <mx:Script>
        <![CDATA[
            import mx.controls.Alert;
            import mx.rpc.AsyncToken;
            import mx.rpc.events.ResultEvent;

            private function faultHandler(event: Event): void {
                Alert.show(event.toString(), event.type);
            }

            private function resultHandler(event: ResultEvent): void {
                //event.result是服务器端返回对象
                result.text = "Message:" + event.result.toString();
            }

            private function sendHandler(): void {
                helloRemoteObject.sayHello(userName.text);
            }
        ]]>
    </mx:Script>

    <!-- 当工程没有设置编译器-service参数 或是-context-root等参数,就需要手动设置endpoint参数 -->
    <mx:RemoteObject
        id="helloRemoteObject"
        destination="helloWorld"
        fault="faultHandler(event)"
        result="resultHandler(event)"
        showBusyCursor="true"/>
    <mx:Panel x="10" y="10" width="272" height="148" layout="absolute" title="BlazeDS Remote HelloWorld Sample">
        <mx:Label x="10" y="22" text="请输入名称"/>
        <mx:TextInput x="70" y="19" id="userName"/>
        <mx:Button x="184" y="45" label="发送" click="sendHandler()"/>
        <mx:Text x="10" y="79" id="result"/>
    </mx:Panel>
</mx:Application>

首先你需要将Java服务器端的HelloWorld程序配置在flex的remoting-config.xml中,配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<service id="remoting-service"
    class="flex.messaging.services.RemotingService">

    <adapters>
        <adapter-definition id="java-object" class="flex.messaging.services.remoting.adapters.JavaAdapter" default="true"/>
    </adapters>

    <default-channels>
        <channel ref="my-amf"/>
    </default-channels>

    <destination id="helloWorld">
        <properties>
            <source>com.hoo.flex.HelloWorld</source>
        </properties>
    </destination>
</service>

上面mxml代码中的RemoteObject的destination对应的就是remoting-config.xml配置文件中的 destination的id。这个是一一对应的,然后在sendHandler方法中,helloRemoteObject对应的就是 RemoteObject的id,而sayHello方法对应的就是配置在remoting-config.xml中的destination的 source的Java服务器端代码的公有方法。添加完配置后,需要重启tomcat。

运行上面的flex程序后,如果输入参数后,点击发送,可以看到服务器端返回的消息就说明BlazeDS整合Flex成功了。

[转载]百度地图API详解之地图标注

mikel阅读(1204)

[转载]百度地图API详解之地图标注 – jz1108 – 博客园.

详解系列

本文将向大家介绍百度地图API的标注(Marker)的使用方法和一些实现细节。

标注概述

标注(Marker)是用来表示一个点位置的可见元素,每个标注自身都包含地理信息。比如你在西单商场位置添加了一个标注,不论地图移动、缩放,标注都会跟随一起移动,保证其始终指向正确的地理位置。

从上面的图可以看出,不论地图如何变化标注始终指向“西单商场”的位置。

如何知道某个点的坐标?

上例中我们在西单商场位置添加了一个标注,那么我是如何知道它的坐标点呢?可以通过API的事件机制来获取:

map.addEventListener('click', function(e){
    console.log(e.point);
});

我们在map对象上添加了一个click事件的监听函数,当点击地图上某个位置时,监听函数通过控制台把当前点击的位置输出出来(注意需要有控制台 的支持,比如firebug,如果没有控制台则可使用alert把point的lng和lat属性输出出来)。此外,你也可以使用API提供的坐标拾取工 具来完成(http://dev.baidu.com/wiki/static/map/API/tool/getPoint/),它支持检索并且点击地图上任意位置时会出现该位置的坐标。

标注元素组成

从DOM元素构成角度看,一个完整的标注是由以下几个部分组成的:

  • 标注点击区域
  • 标注图标
  • 标注阴影

下面是示意图:

在地图API实现中,这三个DOM元素分别位于不同的容器中,这些容器可以通过map.getPanes()方法获得,其中 markerMouseTarget就是标注点击区域所在容器、markerPane为标注图标所在容器,markerShadow为标注阴影所在图层。 你可能会在自定义覆盖物时需要这些容器对象,这里只需要知道Marker的各个部分是如何放置的即可。

自定义标注图标

标注的图标是可以自定义的,通过Icon类可以自定义标注的图标,比如我希望使用下面这个图片作为标注图标:

已知这个图标大小为20×32。我们初始化地图,接着定义Icon,并赋给一个Marker实例:

var map = new BMap.Map('container');
map.centerAndZoom(new BMap.Point(116.380797, 39.918497), 18);

var icon = new BMap.Icon('pin.png', new BMap.Size(20, 32), {
    anchor: new BMap.Size(10, 30)
});

var mkr = new BMap.Marker(new BMap.Point(116.38075,39.918986), {
    icon: icon
});

map.addOverlay(mkr);

我们给定icon所需图片的url,接着是图片的尺寸,另外我们还增加了anchor属性,这个是干什么用的呢?在自定义标注图标时有一点需要注意 的就是标注的定位点(anchor),通俗的讲就是要指定图片的哪个位置是与标注真正的位置对应在一起。我们通过下面的图示来说明:

我们获取到地图上一个位置(上图中标注下端所在的黑色小方块),那么我也希望我标注中间下端指向这个位置,这个就需要通过anchor来调节。anchor的意义如下图所示:

即定位点距离图片左上角的偏移量。

如果不给anchor的话,API会自动获取图片中心点作为anchor位置:

我们看到标注图片中心的位置覆盖在那个小方块区域。

除了anchor之外,还有一个infoWindowAnchor属性,它是用来控制信息窗口开启的位置的(注意这里调用的是Marker的 openInfoWindow方法,而不是Map的openInfoWindow方法),默认情况下它和icon的anchor是一个位置:

标注被InfoWindow的底角挡住了,通过infoWindowAnchor属性就可以改变开启位置:

var icon = new BMap.Icon('pin.png', new BMap.Size(20, 32), {
    anchor: new BMap.Size(10, 30),
    infoWindowAnchor: new BMap.Size(10, 0)
})

再看看效果:

尖角位置已经发生了改变。

标注拖拽

标注支持拖拽,并且可以配置是否有动画效果,我们修改创建标注的代码:

var mkr = new BMap.Marker(new BMap.Point(116.38075,39.918986), {
    icon: icon,
    enableDragging: true,
    raiseOnDrag: true
});

这里开启了拖拽功能以及响应的动画效果。如果此时拖拽地图你会得到如下效果:

通过监听标注的dragend事件,你可以知道拖拽结束后标注的地理位置:

mkr.addEventListener('dragend', function(e){
    alert(e.point.lng + ', ' + e.point.lat);
})

标注阴影

为了增加立体感,可以单独给标注添加阴影,当然你也可以把阴影直接画在icon所用的图片上,但是由于阴影和标注本身在一起,所以就不建议使用任何 动画效果,否则会缺乏真实感。阴影可以通过MarkerOptions的shadow属性配置,类型也是一个Icon实例。具体使用方法和icon属性一 样,这里就不赘述了。

[原创]ASP.NET MVC IIS6上部署教程

mikel阅读(847)

部署步骤:

1.IIS 建立站点,设置 ASP.NET 版本为2.0

2.IIS站点权限属性设置ISUse_来宾账号为读取权限

3.在IIS6上建一个站点,然后在站点属性中的“主目录”–》“配置” ,将打开如下窗口:

在下方的“通配符应用程序映射”中插入一个映射,可执行文件交给.NET的“aspnet_isapi.dll”,默认地址一般在“C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll”

4.查看IIS的Web扩展是否存在ASP.NET 2.0的扩展并启用,如果没有大概是因为IIS版本过低导致的,可以选中添加扩展,输入扩展名字

选择扩展文件为C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll

[转载]13 个 WordPress 提速技巧

mikel阅读(951)

[转载]13 个 WordPress 提速技巧.

性能是一个网站成功的非常关键因素,任何人都不喜欢访问速度超慢的网站。WordPress 的开放性造就 WordPress 有着成千上万的插件,使得 WordPress 比以其他 CMS 程序更受欢迎,但是开放也在一定程度上使得 WordPress 的性能不是很好,所以当你发现你的 WordPress 站点性能很差,你应该尝试通过下面的方式去优化它:

一些最基本,简单的提速方法

1. 更新 WordPress 到最新的发行版本

WordPress 每次版本的升级都有对性能的改进,所以升级到 WordPress 最新的发行版本是改进性能的第一步。

2. 删除没用的插件和更新正在使用的插件

删除任何你不再使用的插件,并从服务器上删除它们,这样会明显加速 WordPress 速度,因为 WordPress 运行的时候会检查每个插件是否被启用,并且导入激活的插件。

另外最好把现在使用的插件更新到最新版本,因为插件的开发者发布新版本一般来说他们应该是在某种程度上优化了插件工作效率。

3. 最小化 PHP 和数据库查询

如果每次导入你服务器上的一个页面,服务器都要执行 PHP 语句和数据库查询,这样速度是不会快的,如果你把一些 PHP 语句直接改成静态的 HTML 代码,这样每次导入页面,浏览器只要读取 HTML 就好了。

4. 使用 PHPmyAdmin 优化和修复数据库

我们至少应该每个星期登陆 PHPMyAdmin 去优化下数据库,选择 WordPress 数据库,选择所有的表,选择优化数据表和修复,你会惊奇的发现这个技巧的效果非常好,它节省了数据库的 10% 的空间,并且效率也提高了。

PHPMyAdmin Optimize Table
使用 PHPmyAdmin 优化和修复数据库

优化图片和其他静态文件

5. 使用可靠的图片存储服务

可以尝试把一些常用的网页图片,CSS,JavaScript 和其他一些静态文件存到 Amazon S3 这样的存储服务中,你会发现服务器的 CPU 时间和内存使用会下降很多,这里有一个 Amazon S3 的向导

Amazon S3
使用 Amazon S3 存储图片

不过 Amazon 的 S3 存储服务收费的,价格虽然比较低的,但是个人感觉还是不特别适合国内的 blogger。

6. 通过 Shrink O’Matic 这个工具来优化图片

Shrink O’Matic 是一个 AIR 程序,它能非常容易批量修改图片,并且支持 JPG, GIF 和 PNG 格式。只要简单的拖拉图片,它就能帮你修改图片的大小,另外它还有一些选项,让你能够选择输出大小,名字和格式。

Shrink O’Matic
Shrink O’Matic

全面提高性能

7. 安装 WP Super Cache 插件

关于 WP Super Cache 这个插件,我已经有详细的介绍。这里简单说,就是它能够产生静态的 HTML 文件,这些 HTML 文件产生之后,你的服务器就不用再去运行 PHP 脚本和数据库查询,所以它能够明显的加快你的博客速度。

8. PHP Speedy WP

PHP Speedy WP 能 够会自动把所有的 JS 和 CSS 文件组合成两个文件,这样就非常简单的加速 WordPress 站点和提高你的博客的响应时间。并且 CSS 的背景图片被转换成 data URIs,就大大降低了 HTTP 请求,并且重要的是,它和 IE 兼容,尽管 IE 不支持 data URIs。但是不幸的是,PHP speedy 还有一些缺陷,组装之后的 JavaScript 文件是放在页面顶部而不是底部,所以这个会在 WP Super Cache 开启的时候不工作,当然了已经有人提供了解决方案

PHP Speedy WP
PHP Speedy WP

9. WP CSS

这个插件会 GZIP 压缩和删除 CSS 文件中的空白,并且会把 style.css 文件中的 @import 直接放入,这个插件还可以设置 CSS 文件的缓存过期时间。

10. DB Cache

DB Cache 这个插件在给定的时间内可以缓存数据库的每一次查询,并且速度很快,而且只用到很少的磁盘空间用于缓存。

11. 使用 Google 的 AJAX Libraries API 来提速

AJAX Libraries API 就是 Google 把一些非常流行 Javsacript 框架(JQuery, prototype, script.aculo.us, MooTools, dojo)放到 Google 的服务器上,使得大家可以充分使用 Google 的服务器资源之外,更重要的是能够提高你的速度。另外WordPress 也有 AJAX Libraries 相关的插件

12. 显示页面导入时间和查询次数

这里有段代码能够让你插入到你的模板中让你知道页面导入的时间,和有多少数据库查询,这个技巧能够让你知道你博客优化的程度。

<?php timer_stop(1); ?>  秒钟有 <?php echo get_num_queries(); ?> 次查询。

13. Optimize DB

MySQL 是一个非常好的数据库,但是它不能自己整理和清理碎片和垃圾。Optimize DB 这个插件可以运行优化数据库的命令,加速你的 WordPress 数据库的速度,这里有一篇关于 Optimize DB 插件的详细介绍。

原文:link

[转载]最佳 WordPress 缓存插件:WP Super Cache

mikel阅读(753)

[转载]最佳 WordPress 缓存插件:WP Super Cache.

WP Super Cache 是 WordPress 官方开发人员 Donncha 开发,是当前最高效也是最灵活的 WordPress 静态缓存插件。它把整个网页直接生成 HTML 文件,这样 Apache 就不用解析 PHP 脚本,通过使用这个插件,能使得你的 WordPress 博客将显著的提速。

WP Super Cache 基本介绍

WP Super Cache 是基于 Ricardo Galli Granada 的 WP-Cache 2。WP-Cache 2 可以缓存你的 WordPress 博客使得不用再次访问数据库,但是它产生的是 PHP 文件而不是 HTML 文件,所以还需要 PHP 引擎去解析它们。而 WP Super Cache 则直接产生 HTML 文件,所以服务器不用解析甚至一行 PHP 代码,所以缓存之后的速度就和访问你服务器上的一张图片一样快。

WP Super Cache 是如何工作的

一半常规的缓存办法是手工把动态页面保存为 HTML 代码,WP Super Cache 也是通过同样的方式的,但是通过自动的方式完成这个过程。

当你一个访问者来的你的站点,他没有登入或者也没有留言,这样他得到是一个在 WordPress cache 文件夹下的 supercache 子文件夹下的纯静态文件,其实你都可以自己到上面的 supercache 目录下去查看同样的永久链接的 HTML 文件的备份。判断一个页面是否已经被缓存了,查看该页面的源代码,看看最后一行是否有 <!– super cache –> 或者 <!– super cache gz –>。

如果访问者已经登陆或者留了言,就会返回 WP Cache 函数生成的页面,并且最后一行会有 <!– Cached page served by WP-Cache — >

WP Super Cache 基本使用

上传 WP Super Cache 到插件目录,并在 Plugin 目录下激活之后,就可以到 Setting > WP Super Cache 进行详细配置。

WP Super Cache Status

首先是三个选项:

  • ON (WP Cache and Super Cache enabled)
  • HALF ON (Super Cache Disabled, only legacy WP-Cache caching.)
  • OFF (WP Cache and Super Cache disabled)

默认情况下,WP Super Cache 是没有开启的,所以在这里选择下,需按则第一个,就是 Super Cache,缓存为静态文件,如果第二种情况呢,则和 WP-Cache 一致。

Proudly tell the world your server is Digg proof! (places a message in your blog’s footer)
这个是说在你的 footer 显示一条信息告诉读者该博客已经缓存了,一般不要这样做。

Clear all cache files when a post or page is published. (This may significantly slow down saving of posts.)
这个是说发布新日志的时候清理所有缓存,这个肯定不能这么干了。

Super Cache Compression

启动这个则会成生 gzip 压缩,节省你的带宽。

Mod Rewrite Rules

WP Super Cache 是通过 Mod Rewrite 实现访问静态文件的,所以这里在是显示添加的 Mod Rewrite 规则。

Expiry Time and Garbage Collection

过期时间和垃圾收集,这里主要设置缓存文件过期时间,和多久删除一次过期的缓存文件。个人建议:一般缓存时间设置为1天(86400)左右,缓存删除时间为1个小时一次。

Accepted filenames, rejected URIs

这里设置一些链接不需要缓存,并且可以设置特例。

Rejected User Agents

这里设置某些 User Agents 来访问的时候不会缓存。比如 Google 和百度爬虫等。

Cache Contents

这里显示你博客缓存文件和过期文件的数量,你可以手工删除缓存文件和过期文件,因为 WP Super Cache 有垃圾回收机制,所以这里不帮不用手工删除,如果当你测试某个插件,看不到效果的时候,如果确定是缓存的原因,你可以到这里手工删除测试下。

Lock Down

一般情况下,某篇日志如果有了新留言,就会更新缓存文件,不过这篇日志是否已经到了缓存时间,但是在某种情况,你预料到你的博客可能会有很大流量和留言,候重复更新缓存文件可能并不是很好的选择,速度反而更慢,这个时候你就可以锁定缓存文件,新留言不再更新缓存日志。

插件作者也给出了一段代码,如果你的博客锁定了之后,可以通过判断一个变量 WPLOCKDOWN 是否存在告来诉读者,你博客已经锁定,待会才会更新,当然了,一般博客不建议使用。

Directly Cached Files

直接缓存文件,根据永久链接直接缓存,比如你的关于页面是不太更新,你就可以直接缓存它到你的 about 目录下(假如你的关于页面的 slug 是 about),这个直接缓存不会过期。适合一些经常性不更新的静态页面。

Configuration messed up?

因为上面那么多配置,你可能搞糊涂了,那么这里可以让你一键恢复到默认。

Cache Plugins

缓存插件还支持附加插件,比如作者自己就提供一个插件:No Adverts for Friends,不对朋友显示广告。

附加说明

  • 登陆了和留言之后是看不到静态缓存页面的,看到的效果和原来的 WP-Cache 插件一样的,一般来说留言的同学不是那么多。
  • 这个插件通过 Mod Rewrite 来是实现访问静态缓存页面,所以首先你的主机要支持 Mod Rewrite,然后你要在 WordPress 后台启动永久链接。
  • 一些动态的时时更细的插件,如 Recent Comments,Recent Posts 等,一个很好的解决方法是通过 JavaScript 来实现。所以同理 Sidebar 更新也只能在静态页面刷新的时候更新。我以后会讲解下如何实现 JS 实现 Recent Comment, Recent Post, 和 PostViews 。
  • 不要使用性能很差的主机,尽管缓存了,但是还是不行的,这里我个人推荐我和朋友一起搞的主机:WFANS 主机
  • 还有很多依赖于刷新数据的插件,如 SK2, Bad Behaviour 等可能不起作用。缓存总归会带来一点那个灵活性的不足。

[原创]Windows下版本服务器VisualSVN的迁移

mikel阅读(909)

最近换了台机器,需要把原机器上的VisualSVN迁移到新的机器上,为了保持原有修改日志不变,于是按如下步骤进行了迁移:

1.首先备份原VisualSVN的数据目录Repositories,一般在安装目录下

2.在新机器上安装VisualSVN服务器

3.恢复备份中的新机器上的Repositories文件夹下的项目文件夹,记住是项目文件夹而不是所有文件

4.在Visual Studio2008中,我用的是AnkhSvn-2.1.8420.8进行的SVN操作

右键选中需要更换SVN地址的项目,选择SubVersion->Swith Objects

输入新的SVN服务器的链接地址,类似:https://zlnc-js005:8443/svn/Test/trunk/NewTest/

点击确定更新完毕

[转载]腾讯开放平台中实现QQ登陆的功能

mikel阅读(990)

转载腾讯开放平台中实现QQ登陆的功能 – 小宇.net – 博客园.

这昨天为自己的网站实现了QQ登陆的功能,虽然,没有进行绑定,但是在技术层面上来说,已经了解了一点腾讯开放平台的协议.

具体什么是OAUTH,请您GG一下吧…

关于腾讯的开放平台,最主要的就是参数一定不能多,能有的要有,不能有的一定不能有.不然提交过去,就会提示什么什么错了.

最关键的就是签名的方式,下面就是我的签名的代码

1 /// <summary>
2 /// 每一步不同的生成签名的方式
3 /// </summary>
4 /// <returns></returns>
5 protected virtual String BuildSignature(String SignatureHost)
6 {
7 String PostMethodString = GET&;
8 StringBuilder ParamString = new StringBuilder();
9 this.CurrentStepParameters
10 .OrderBy(c => c.Key.ToString())
11 .ToList()
12 .ForEach(c =>
13 {
14 if (c.Key != OAuthParameterName.oauth_signature &&
15 c.Key != OAuthParameterName.timestamp)
16 {
17 if (ParamString.Length > 0)
18 {
19 ParamString.Append(&);
20 }
21 var p = c.Value;
22 ParamString.Append(p.OAuthOringinaName);
23 ParamString.Append(=);
24 ParamString.Append(p.Value);
25 }
26 }
27 );
28 StringBuilder SignData = new StringBuilder();
29 SignData.Append(PostMethodString);
30 SignData.Append(OAuthHelper.UrlEncode(SignatureHost));
31 SignData.Append(&);
32 SignData.Append(OAuthHelper.UrlEncode(ParamString.ToString()));
33
34 //密钥
35 String SecretKey = String.Format({0}&{1}, this.AppKey, this.AuthorizedTokenKey);
36 String SignContent = SignData.ToString();
37 String Signature = Convert.ToBase64String(OAuthHelper.HMACSHA1Code(SignContent, SecretKey));
38 return Signature;
39 }

这里的CurrentStepParameters是在构造方式里进行赋值,或者其它地方,签名里最关键的,就是oauth_signature和timestamp这两个参数,要记得去掉,在这里,我选择了过滤.

因为有的时候,CurrentStepParameters这个字典从querystring来生成的话会简单点还有能复用.

在所有参数中,有几个是经常会用到的.

所以,在此我建立了一个通用参数生成方法

1 protected virtual void AddCommonParameters()
2 {
3 if (this.CurrentStepParameters != null)
4 {
5 //增加通用参数
6 this.CurrentStepParameters.AddParam(OAuthParameterName.oauth_consumer_key, this.AppID);
7 this.CurrentStepParameters.AddParam(OAuthParameterName.oauth_nonce, DateTime.UtcNow.Ticks.ToString());
8 this.CurrentStepParameters.AddParam(OAuthParameterName.oauth_timestamp, OAuthHelper.GenerateTimestamp());
9 this.CurrentStepParameters.AddParam(OAuthParameterName.oauth_version, 1.0);
10 this.CurrentStepParameters.AddParam(OAuthParameterName.oauth_signature_method, HMAC-SHA1);
11 this.CurrentStepParameters.AddParam(OAuthParameterName.oauth_client_ip, 1);
12
13 if (this.CurrentStepParameters.ContainsKey(OAuthParameterName.oauth_token_secret))
14 {
15 this.AuthorizedTokenKey = CurrentStepParameters[OAuthParameterName.oauth_token_secret].Value;
16 }
17
18 if (this.CurrentStepParameters.ContainsKey(OAuthParameterName.oauth_signature))
19 {
20 CurrentStepParameters.Remove(OAuthParameterName.oauth_signature);
21 }
22
23 if (this.CurrentStepParameters.ContainsKey(OAuthParameterName.timestamp))
24 {
25 CurrentStepParameters.Remove(OAuthParameterName.timestamp);
26 }
27 }
28 }

下面是一个测试地址 :http://www.changshu.so/Tencent , 之后等完成绑定后,地址会删除

组件下载地址 : http://files.cnblogs.com/sam251/CSCMS.Secrity.OAuth.rar

后续还有绑定的需要自己去实现了.如果能有通用的方式,我会定时更新,另外,其它OAUTH,比如SINA的,正在研究.

本程序里的OAuthHelper.GenerateTimestamp()及OAuthHelper.UrlEncode都是来自网上其它朋友的方法.

源不源码也没有什么重要了.因为整合在自己的网站里,所以,只是给出了一个DLL,要源码的可以找我,我可以发你.