[转载]ASP.NET MVC3+Spring.net+NHibernate+ExtJs的简单架构

mikel阅读(1243)

[转载]MVC3+Spring.net+NHibernate+ExtJs的简单架构 – RegicideGod – 博客园.

对于MVC3、Spring.net、NHibernate、ExtJs等单个的技术点使用起来,并不是很复杂,出问题比较多的还是配置相关的问题;下面我们来一步一步的实现4个框架的融合配置;

首先我们来谈谈4个框架的各自负责的事情:

  1. MVC3:最开始是用MVC2搭建框架,后来用VS12后,只支持MVC3的模 板,就改为MVC3搭建了,MVC3兼容MVC2并且多加入了一种Razor视图引擎,但我们这个框架还是用ASPX视图引擎,言归正传;MVC的概念我 个人的理解就是一种约定俗成,更深层次的理解还得各位看其他文章和多做练习来加深理解,在这个框架中,我们主要是用到Controller去后台取数,然 后呈现到View上面;当然还有很多MVC中的一些技巧和MVC的注意点,我们代码或其他文章慢慢讲解;View层的框架主要是ExtJs;
  2.  ExtJs:ExtJs主要是用于前台的页面展示,属于MVC中View层的框架,就是ExtJs写URL指向Controller中的action取得数据然后展现到ExtJs控件中;    
  3.  NHibernate:主要是用于底层数据的ORM框架,属于国外的开源框架,最开始是JAVA那边的“特有”,后来被完美移植到.NET平台,我们主要是用来做底层数据的持久化操作;
  4. Spring.NET:也是有Java平台上完美移植过来的一个框架,我们这里 面只用到Spring.NET的依赖注入,就是创建对象有Spring.NET来统一管理,对对象进行注入操作,推崇决不重新造轮子的原则!我们架构中主 要用来对Controller中需要的对象进行注入;继续我们的架构,下面我们从代码结构来说明我们该如何来完成这每一步;

  首先,我们去spring.net网站下载最新的源代码,在源文件中找到 Spring.NET-1.3.2\Spring.NET\examples\Spring下面的 Spring.Data.NHibernate.Northwind这个项目,这个是Spring.NET官方出的一个示例,用于Spring.NET和 NHibernate集成的示例,下面还有MVC2及MVC3的相关示例,大家可以先熟悉了解下;

  最开始搭建框架,可能最恼火的是引用的相关DLL不好找,即使找到了,也有可能相关的版本不对,这个也很头疼,我找DLL的办法是将Spring.NET全部重新编译一遍,然后找到编译后的相关需要的源码生成的DLL;

我们开始搭建如下图的结构:

 

 

 

 

 

 简述下,BLL、Common、DAL、Model四个类库和一个MVCMain的MVC3的网站

 我们从每一个层里面来简述相关的代码

Model

 

 我们以Student为例,代码如下

View Code
protected string stuGUID;
public virtual string StuGUID
{
get { return stuGUID; }
set { stuGUID = value; }
}
protected int stuNo;
public virtual int StuNo
{
get { return stuNo; }
set { stuNo = value; }
}
protected string stuName;
public virtual string StuName
{
get { return stuName; }
set { stuName = value; }
}
•••••等等

 因为我们要使用NHibernate所以在定义对象的时候,属性要用virtual来修饰,我们定义的对象然后,要把对象和数据的表对应起来,就要用到xml来描述Student对象和数据表Student的对应关系;

下面我们来看看相对应的描述,一定要记住一点,对于我们的整个项目中的xml(除了网站下面的xml)都是把文件设置嵌入式资源

View Code

 

 

PS:对于NHibernate的实体映射,网上有更多的相关专题,我们现在主要讨论的是把这几项东西结合,所以对于各自技术,都可以去找相关的技术专题;这篇文章只是以最简单的方式实现框架的融合,融合后对于技术本身的特性没有任何影响;

Model就可以可以说已经完成;下面我们来看DAL层

DAL:

 

  对重要的是Helper文件夹中的东西,就是NHibernate的相关的使用(这个里面的东西,大家可以参考Spring.net中的spring.net+NHibernate的实现示例),我们作一下简单的介绍

  NHelper类主要是对ISessionFactory和ISession 的注册,session就是相当于我们的DBHelper类一样,提供强大的数据访问支持,只要继承NHelper类就可以对数据库进行访问,但 ISession需要通过Spring.NET进行注入;

View Code

public abstract class NHelper
{
private ISessionFactory sessionFactory;
///

 

 

 

<summary> /// Session factory for sub-classes.
/// </summary>

&nbsp;

&nbsp;

&nbsp;

&nbsp;

public ISessionFactory SessionFactory
{
protected get { return sessionFactory; }
set { sessionFactory = value; }
}
///

&nbsp;

&nbsp;

&nbsp;

<summary> /// Get's the current active session. Will retrieve session as managed by the
/// Open Session In View module if enabled.
/// </summary>

&nbsp;

&nbsp;

&nbsp;

&nbsp;

protected ISession CurrentSession
{
get { return sessionFactory.GetCurrentSession(); }
}

protected IListGetAll() where TEntity : class
{
ICriteria criteria = CurrentSession.CreateCriteria();
return criteria.List();
}
}

然后就是IDAL, ISupportsDeleteDAL, ISupportsSaveDAL等一些接口,主要提供对数据库的增删改查的接口

View Code

public interface ISupportsSaveDAL
{
///

&nbsp;

&nbsp;

&nbsp;

<summary> /// 保存一个实体
/// </summary>

&nbsp;

&nbsp;

&nbsp;

&nbsp;

///需要保存的实体 /// 返回保存实体的主键ID
TId Save(TEntity entity);
///

&nbsp;

&nbsp;

&nbsp;

<summary> /// 更新一个实体
/// </summary>

&nbsp;

&nbsp;

&nbsp;

&nbsp;

///更新一个实体 void Update(TEntity entity);
///

&nbsp;

&nbsp;

&nbsp;

<summary> /// 保存或更新一个实体
/// </summary>

&nbsp;

&nbsp;

&nbsp;

&nbsp;

///保存或更新一个实体 void SaveOrUpdate(TEntity entity);
}

因为我们对要操作的具体类不明确,所以我们用泛型接口来约束接口中的方法;TEntity代表一个实体类型,TId代表实体的主键类型,下面还有删除和查找,就不一一列举;

然后就是ICommonDAL接口,这个接口主要是实现其他定义好的增删改查的相关接口,在定义个性化查询的及个性化方法的时候,就可以定义在此定义,而增删改查的接口,可以对应的去继承;

View Code

public interface ICommonDAL:IDAL,ISupportsSaveDAL,ISupportsDeleteDAL
{
///

&nbsp;

&nbsp;

&nbsp;

<summary> /// 根据HQL和参数查询实体
/// </summary>

&nbsp;

&nbsp;

&nbsp;

&nbsp;

///HQL ///参数 /// 实体
IList GetCommonByHql(string hqlQuery, CommonParam [] commonParams);

///

&nbsp;

&nbsp;

&nbsp;

<summary> /// 根据HQL和参数查询实体
/// </summary>

&nbsp;

&nbsp;

&nbsp;

&nbsp;

///HQL /// 实体
IList GetCommonByHql(string hqlQuery);

///

&nbsp;

&nbsp;

&nbsp;

<summary> /// 标准查询实体
/// </summary>

&nbsp;

&nbsp;

&nbsp;

&nbsp;

/// 指定对象
/// 查询到的实体
IList GetCommonByCriteria();

///

&nbsp;

&nbsp;

&nbsp;

<summary> /// 标准查询实体
/// </summary>

&nbsp;

&nbsp;

&nbsp;

&nbsp;

///主键ID /// 查询到的实体
IList GetCommonByCriteria(TId id);
}

当然这里不只局限于,这些查询了,NHibernate给我们提供的相关查询,都可以在接口中定义好,让子类去实现就行了;

CommonParam这个东西,是在Common中定义的一个类,主要是按照ADO.NET中的SQLparameter来简单的定义了简单的key和value

在往下面就是实现ICommonDAL接口了

CommonDAL类,如下

我们要继承NHelper才能通过Session来访问数据库,所以我们必须继承

View Code

///

&nbsp;

&nbsp;

<summary> /// 根据HQL和参数查询实体
/// </summary>

&nbsp;

&nbsp;

&nbsp;

///HQL ///参数 /// 实体
public IList GetCommonByHql(string hqlQuery, CommonParam [] commonParams)
{
IQuery iquery = CurrentSession.CreateQuery(hqlQuery);
if (commonParams != null)
{
foreach (var item in commonParams)
{
iquery.SetString(item.Key, item.Val);
}
}
return iquery.List();
}

///

&nbsp;

&nbsp;

<summary> /// 标准查询实体
/// </summary>

&nbsp;

&nbsp;

&nbsp;

///主键ID /// 查询到的实体
public IList GetCommonByCriteria(TId id)
{
ICriteria icriteria = CurrentSession.CreateCriteria(typeof(TEntity));
icriteria.Add(Restrictions.IdEq(id));
return icriteria.List();
}

///

&nbsp;

&nbsp;

<summary> /// 保存对象
/// </summary>

&nbsp;

&nbsp;

&nbsp;

///形参对象 ///
public TId Save(TEntity entity)
{
var id=(TId)CurrentSession.Save(entity);
CurrentSession.Flush();
return id;
}

里面列举了一些简单的方法实现,都是使用CurrentSession进行实现,CurrentSession就是NHelper里面定义的东西,是通过spring.net注入进行的;对于保存数据的相关操作,在一定要加CurrentSession.Flush();提交到数据库,否则无法保持到数据库;

在接着就是具体的表的操作,以Student表为例(上面我们已经建立了Model类)

我们定义IStudentDAL接口


public interface IStudentDAL:ICommonDAL
{
}

这个接口啥都不用写,只用继承ICommonDAL接口就行,因为我们已经知道是Student的DAL接口类,所以我们不用定义形参接口,当然,如果你有个性化的方法,依然,可以定义在IStudent接口中;

我们在来完成IStudentDAL接口的实现

StudentDAL


public class StudentDAL :CommonDAL,IStudentDAL
{
}

只用继承CommonDAL和实现IStudentDAL接口就行,一定要是 CommonDAL在前,实现接口在后,因为IStudentDAL是继承ICommonDAL的,而CommonDAL又是实现ICommonDAL 的,所以我们这里面基本一句话都不用,就实现了,Student的之前在ICommonDAL中的所有方法;

接口和继承的好处很明显的体现出来,当然要泛型的支撑,如果我们在一个class 班级表,只用加一个接口,继承ICommonDAL,然后在加一个类,继承CommonDAL,实现接口就可以实现所有的增删改查,当然你也可以在 IStudentDAL中,提供自定义化的方法,让StudentDAL去实现,StudentDAL继承CommonDAL,而CommonDAL也继 承NHelper,所以StudentDAL也可以通过CurrentSession来访问数据库

DAL到这里也基本写完了;

BLL

在来看BLL,如果项目中没有BLL层,可以完全抛开这层,但是我们还是简单的叙述一下,

BLL中也有Helper,这个主要是来定义BLL中的特殊化查询方法;

我们主要看CommonBLL的实现操作,因为其他的与DAL中的没有差异,但我们这个BLL中没有NHelper,因为BLL负责业务逻辑,不负责数据访问,故没有NHelper,不提供CurrentSession来访问数据库

View Code

public class CommonBLL: ICommonBLL
{
private ICommonDAL comDAL;
public ICommonDAL ComDAL
{
set { comDAL=value; }
}
public IList GetCommonByHql(string hqlQuery, CommonParam[] commonParams)
{
return comDAL.GetCommonByHql(hqlQuery,commonParams);
}
}

我们是通过创建ICommonDAL的实例进行DAL中的CommonDAL中的实现调用,至于ComDAL我们只是定义了,根本没有创建对象,这个我们是通过Spring.net来注入对象的,所以我们这里也暂时这么写;

在接着就是IStudentBLL,也是继承ICommonBLL就可以了,然后就是StudentBLL,实现CommonBLL类,并且实现接口IStudentBLL;这时我们的StudentBLL中,有一个ComDAL只是定义了,没有创建对象,因为我们是通过spring.net注入对象的

BLL完了后,就是Common了,这个里面目前就是一个CommonParam类,定义了一个key和value来传递HQL的参数

MVCMain

然后在看MVCMain网站,我们做的简单一些吧,在我们输入地址http://localhost:8001/Home/Index的时候,就是默认访问HomeController下的Index方法,我们在HomeController下面写东西来后台访问我们刚写好的BLL

View Code

public class HomeController : Controller
{
public string Message { get; set; }
private IStudentBLL studentBLL;

public IStudentBLLStudentBLL
{
set { studentBLL = value; }
}
}

我们只用定义IStudentBLL的对象就行,我们创建对象是通过Spring.net注入的;所以我们来看看最最重要的配置吧;

首先当然是从web.config里面开始,首先在configuration节点中加入(加入log4net日志记录)

View Code

&nbsp;

<section name="logging" type="Common.Logging.ConfigurationSectionHandler, Common.Logging"></section><section name="context" type="Spring.Context.Support.MvcContextHandler, Spring.Web.Mvc3"></section><section name="parsers" type="Spring.Context.Support.NamespaceParsersSectionHandler, Spring.Core"></section>

在接着下面写

View Code

<!-- choices are INLINE, FILE, FILE-WATCH, EXTERNAL-->
<!-- otherwise BasicConfigurer.Configure is used   -->
<!-- log4net configuration file is specified with key configFile-->

&nbsp;

&nbsp;

&nbsp;

这里是指定log4net的相关配置和spring.net的相关配置,context节点下,是其他xml注入描述的相关文件

在appsettings中一定要加这句话,用于注册NHibernate的SessionFactory


在system.web节点下的httpModules节点中加入


用于注册spring.net和nhibernate

如果你的网站用vs打开没问题,发布到IIS上出现问题,可以调整IIS的应用程序池的模式为集成,或者修改system.webServer

View Code

Web.config中的东西写完了,在web.config中我们提到来几个文件

 

 

 

第一个是log4net的相关配置文件,这个基本就是把log4net的配置写进行就行了

View Code

&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

第二个是NHibernate的配置文件

View Code

<!--?xml version="1.0" encoding="utf-8" ?-->
<!-- Referenced by main application context configuration file --> The Northwind object definitions for the Data Access Objects. <!-- Property placeholder configurer for database settings --> <object type="Spring.Objects.Factory.Config.PropertyPlaceholderConfigurer, Spring.Core"> </object>

&nbsp;

<!-- NHibernate Configuration -->
<object id="NHibernateSessionFactory" type="MVCMain.Controllers.LocalSessionFactoryObjects, MVCMain"> Model <!-- provides integation with Spring's declarative transaction management features --> </object>

<!-- Transaction Management Strategy - local database transactions -->
<object id="transactionManager" type="Spring.Data.NHibernate.HibernateTransactionManager, Spring.Data.NHibernate21"> </object>

<!-- Exception translation object post processor -->
<object type="Spring.Dao.Attributes.PersistenceExceptionTranslationPostProcessor, Spring.Data"></object> <!-- Data Access Objects --> <object id="StudentBLL" type="BLL.StudentBLL, BLL"> </object>

<object id="ComStudentDAL" type="DAL.StudentDAL, DAL"> </object>

大家要仔细看,其中的

Model 节点是指NHibernate的关系映射在哪个程序集中,这里面有一个type=”MVCMain.Controllers.LocalSessionFactoryObjects, MVCMain”,这个是用来创建NHibernateSessionFactory对象的LocalSessionFactoryObjects类的写法如下(可以找spring.net提供的S+N的集成来写)

View Code public class LocalSessionFactoryObjects : LocalSessionFactoryObject { ///<summary> /// Overwritten to return Spring's bytecode provider for entity injection to work. /// </summary>public override IBytecodeProvider BytecodeProvider { get { return new BytecodeProvider(ApplicationContext); } set { } } }

在然后,就是一些NHibernate的标准配置

这两个节点就是创建DAL中的StudentDAL的对象了,然后注入到BLL中的StudentBLL中的ComDAL中,因为我们的ComDAL是CommonBLL中定义的ICommonDAL的对象,现在通过StudentDAL注入给ComDAL,因为StudentDAL是继承IStudentDAL,而IStudentDAL又是继承ICommonDAL的,所以可以注入,这也就是面向接口的编程了;

Dao.xml中主要是NHibernate的相关配置文件,和后台对象通过Spring.net创建;然后在看第三个web.xml

Web.xml中主要是定义MVCMain中要使用的对象,因为我们的对象都要交给spring.net来管理,并且还要访问BLL中的对象,间接的访问数据

View Code

<!--?xml version="1.0" encoding="utf-8" ?-->
<!-- Referenced by main application context configuration file --> The Northwind web layer definitions <object type="MVCMain.Controllers.HomeController, MVCMain"> </object>

<object type="MVCMain.Controllers.AccountController, MVCMain"> </object>

这个里面主要是对HomeController中的相关对象和属性进行注入,对于其他Controller如果没有属性需要注入也要创建对象才能访问,否则会报找不到的错误;

到这里基本的配置基本已经完了,但还有最重要的一点,容易被忽视,就是配置文件写了后,如何注入的了?

我们还有最后一步就是在Global中修改一点东西

修改Global为如下

View Code

public class MvcApplication : SpringMvcApplication
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
}

public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
}
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
}
}

就是Global的Application要继承SpringMVCApplication才行;

到这里基本已经完成,里面需要引入的DLL没有详细的说明,在此截图一下

一定要去spring.net的源代码中把这些东西找齐,然后在引入就行;

还有一点就是ExtJs或者是JQuery easyui等框架,访问后台时

 

View Code

var eastPanel = new Ext.Panel({
region: 'east',
title: 'East Side',
collapsible: true,
split: true,
width: 225,
minSize: 175,
maxSize: 400,
layout: 'fit',
margins: '0 0 0 0',
items: {
autoScroll: true,
autoLoad: {
url: "/Home/Index"
}
}
});

只用指定url就可以,这个代表是访问HomeController下的Index方法;

至此框架搭建好了,如果你报错了,你可以对应这篇文章及spring.net提供 的S+N的集成示例和里面的MVC3的示例进行相应的修改,你也一定能搭建出此框架!后面会继续对此框架进行优化处理,并完善各单个框架的的功能点优化! 对于spring.net和nhibernate的相关使用大家不太好理解的多去网上搜搜文章!博客园上也有相关的专题;

在此多说一点,架构只是为了应付某种需求才出现的,好的架构可以让程序员更多的去 关心程序的业务逻辑,而不是成天的写代码;好的架构在初期设计好架构,初期写好架构代码,即使初期写架构代码的时间很长,只要后期程序员进行实现的时候, 写少量的代码,可以做多量的事情,那么架构就是合理的!

 文章出处:博客园 http://www.cnblogs.com/RegicideGod/archive/2012/06/25/2562871.html

[转载]腾讯微博开放平台OAuth1.0授权完整流程(C#)

mikel阅读(1131)

[转载]腾讯微博开放平台OAuth1.0授权完整流程(C#) – 空葫芦 – 博客园.

一、OAuth授权和版本、开放平台

首先要明确,在协议中,角色分为终端用户、第三方应用(Client)、服务提供者。

关于OAuth授权协议的起源和更多基本概念,我不能描述更多,google官网能更好行使这个职责。

我理解的OAuth是:职权投影。

在取得终端用户同意(授权过程)后,获得用户在服务提供处托管的部分权限。

 

(本节以下部分适合对OAuth协议有一定了解后看,如果是第一次接触OAuth,建议跳过此部分,读完全文回头看这段。)

关于OAuth1.0a和OAuth1.0:

维基百科中说到“2009年4月23日,OAuth宣告了一个1.0协议的安全漏洞。该漏洞影响了OAuth 1.0核心规范第6节的OAuth的认证流程(也称作3阶段OAuth)于是,发布了OAuth Core协议1.0a版本来解决这一问题。”

乌云平台公布过街旁网(在OAuth中角色为第三方应用)使用新浪微博(角色为服务提供者)OAuth授权时的漏洞。该漏洞在1.0a协议中得以解决,见此文中描述如下:

1. oauth_callback 在第一步即获取 Request Token 时( Get Request Token ,图中第二步)就必须提供,如果不需要重定向,则其值必须为 oob ( out_of_band configuration ),服务提供方此时返回参数 oauth_callback_confirmed ,且值为 true 。在第二步即验证阶段(图中第三步)不接受该参数。

2. 验证完成后会返回验证码( oauth_verifier )为的是在没有 callback 的时候,服务提供方显示给用户,然后用户可以在第三方应用的设备上输入,标示自己已经授权(和未授权的用户分开),然后第三方应用必须加上该验证码去获取 Access Token。

 

由于各个平台对协议的实现有些细微的差别,不要奇怪,就像W3C标准之下各个浏览器的不兼容一样,很多地方会遇到平台之间不一致的实现。

比如规避这个漏洞的方式,腾讯微博采用的是不验证callback合法性,但是callback参数是在STEP1(这一步在第三方应用处执行,在授权流程一节中我们会看到)中确定,所以在腾讯微博开放平台注册应用时,不需要填写“回调地址”,而只需填写“应用地址”。

可以随便改换callback参数值,但是不会产生安全问题,因为这个参数是由第三方应用确定的。其他平台(比如新浪微博、腾讯互联)各有不同的实现,在其他文章再讲。

关于OAuth1.0/1.0a和OAuth2.0:

OAuth2.0是OAuth1.0a之后的版本,不向后兼容OAuth 1.0/1.0a,流程更为简单。

我多么希望这一句话就能说完他们的关系,可惜事与愿违。

版本更迭的过程中,总会有各种各样的原因来搅局,维基百科中提到“Facebook的新的Graph API只支持OAuth 2.0,Google在2011年3月亦宣布Google API对OAuth 2.0的支援,Windows Live 亦支援 OAuth 2.0“,但是在国内,只是“看上去很美”。

OAuth1.0/1.0a获取到的AccessToken时效为永久,直到用户取消授权/应用被注销。但是OAuth2.0中AccessToken开始变为时效性(每个平台有效时间不同),同时协议中规定RefreshToken的实现,在AccessToken失效后,使用RefreshToken去获取新的AccessToken。

在这一点的实现上,各个平台出现了巨大的分歧,以下简单列出几个平台关于OAuth2.0的实现。

新浪微博开放平台

没有RefreshToken,AccessToken过期时间如下图

image

过期了咋办?答案是:凉拌。重走授权流程。

QQ互联开放平台

没有RefreshToken,AccessToken过期时间为三个月

官方文档中原话为“access token由每次用户登录时生成,过期时间默认为三个月,用户再次登录时自动刷新,请网站或应用做好防过期策略,或过期后提示用户再次授权”。此处的登陆 我认为就是重新授权,因为QQ互联把OAuth授权流程成为“QQ登陆”。expires_in返回的时间也是代表三个月的7776000,但我未曾验 证。

腾讯微博开放平台

没有RefreshToken(官方原话为“目前暂不支持”),AccessToken过期时间如下图

image

人人网开放平台

永久生效的ResfreshToken,AccessToken过期时间从文档中的示例代码来看应该是一天(”expires_in”: 87063)。

开心网开放平台

滑动过期机制,官方原话为:

开心网开放平台的应用,无论通过OAuth2.0哪种方式获取Access Token,都会拿到一个有效期为1个月的Access Token和有效期为2个月的Refresh Token。对于这些应用,只要用户在两个月内登录,应用就可以使用Refresh Token刷新以获得新的Access Token(新的Refresh Token也会同时下发),因此用户只要在两个月内登录过你的应用,就不需要重新登录。

关于腾讯微博开放平台和QQ互联开放平台:

简单地说,QQ互联开放平台包括部分微博方面的功能,部分空间的功能,看这里

你可能会发现域名从connect.qq.com跳到了wiki.opensns什么的,不要奇怪。

我本来以为腾讯这么霸气,可能会有五六个开放平台。

后来发现…………这里能看到23个。。

二、准备工作

1.注册腾讯微博

2.在腾讯微博开放平台点击导航上的“我的应用”,如果没有获得开发者身份,填写资料获得开发者身份

3.创建应用,这里以常见的网页应用为例

在应用通过审核后,你发微博下面会有”来自XXX“,也就是常说的“来源”,会连接到这里填写的应用网址(此地址并非回调地址,腾讯微博开放平台没有这个填写项)。

Z4~$8)_CD[J}YJO_(MT@753

4.拿到AppKEY和AppSecret

AppKEY对应OAuth1.0文档中的Consumer Key,AppSecret对应Consumer Secret(在其他平台也有叫AppID和AppKey的,或者clientID和clientKey的)。

不管名称如何,在实现中这两个值的作用都是差不多的。

AppKey(一般是数字形式,相当于唯一标识)不用保密,在授权流程中也会出现在GET参数中。App Secret必须保密!我在这里直接截图出来因为是示例,我用小号建的,大家如果谁不想注册,只想直接开始,就用我贴出的就可以。

APPKEY:801165332

AppSecret:457bcc49f2a6694208820d2e4b16bf9e

image

三、授权流程(附代码)

这是雅虎提供的流程图,把注册应用获得AppKey和AppSecret也算到step1里面了。

列出了第三方应用、服务提供方、用户的详细动作,可以参考一下。

不过不要死记这个Step归类,OAuth官方和各个平台的划分方法各有不同,容易记混。

314d6612-3c76-37d8-8e73-d9f3d23d614c

 

接着按照我的理解,从第三方应用(client、开发者接入)角度来讲述一下授权流程。

其间附上核心代码,在文章最后提供完整项目示例。

Step1:请求未授权的RequestToken

OAuth1.0授权最最重要的参数是oauth_signature,也就是签名值,不单在授权流程,在请求OAuth1.0授权下的功能接口时,也都要用到。

由以下三个部分确定。

1.签名方法:目前大多数平台使用的都是“HMAC-SHA1”,.NET中由System.Security.Cryptography命名空间下的HMACSHA1类实现。

2.BaseString:part1 + ”&”+ part2 + “&” + part3

part1:HTTP请求方法,按照每个接口的规定选择GET/POST

part2:UrlEncode(URL),若无特别说明,UrlEncode符合RFC3986文档标准。

part3:UrlEncode(key1 + “=” + UrlEncode(para1) + “&” + key2 + “=” + UrlEncode(para2) + “&” + key3 + “=” +  UrlEncode(para3) …)

part3是QueryString的形式(key1=value1&key2=value2)的参数组合,包括本次请求除oauth_signature外的所有参数。

肯定会包括在每次请求中的主要是OAuth相关的,oauth_consumer_key(AppID/AppKey /Client_ID),oauth_signature_method(签名方法),oauth_timestamp(时间 戳),oauth_nonce(随机生成的单次值),oauth_version(OAuth版本),oauth_signature(计算 BaseString时要排除在外,因为BaseString是用来生成oauth_signature的,这是一个鸡生蛋的问题)。其他的普通参数看具 体接口而定,一般都会有一个列表。

官方给的BaseString伪码如下:

httpMethod + “&” + url_encode( base_uri ) + “&” + sorted_query_params.each { | k, v | url_encode ( k ) + “%3D” + url_encode ( v ) }.join(“%26”)其中part3部分把key部分(k)也编码了,v部分不管是否需要,也都全部做了编码。实际使用中,只要确定key部分,和有些value部分的字符都在以下这个范围的话,就可以不编码。“abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~”。

一点题外话,BaseString是计算签名值时最容易出错的地方,腾讯微博在签名出错时也只有401错误。新浪平台在这一点做得很好,在ResponseHeader中返回了服务器端组合出的BaseString和计算出的签名值,起码有个参考。

3.签名密钥:AppSecret + “&” + TokenSecret

AppSecret就是在我们在第二节中“准备工作”中提到过的。

TokenSecret的值有两种情况,一种是在授权流程中Step1这一步,此时的TokenSecret是Step1返回的oauth_token_secret参数。

另外一种情况就是Step3返回的oauth_token_secret(和授权后的Token,也就是AccessToken一起返回)。

 

说到这儿,你可能疑惑,在第一次发起请求的时候,手里面只有AppKey和AppSecret啊,这时候密钥中的TokenSecret从哪儿弄?

答案是留空就可以了,表达式依然是AppSecret + “&” + TokenSecret,Step1时TokenSecret值是空的。

但是,”&”符号必须保留。这也体现了OAuth的授权的一个方式,就是Step by Step,你后面需要的,前面总会给你。

另外,你可能对OAuthToken、RequestToken、AccessToken这三个名词比较迷惑,这里我是这样理解的:RequestToken有两种,是在授权流程中出现的,一段时间后就会失效。AccessToken是

(平台返回的时候,使用的参数值都是oauth_token,不管是授权前/后或者AccessToken,都是这个key值)。

 

弄清楚边边角角,核心代码是比较简单的,Util是用到工具类,在本文最后会提供。

string RequestUrl = "http://open.t.qq.com/cgi-bin/request_token";
string oauth_signature = "";
string result = "";

//Step1,构造请求获取未授权的RequestToken Dictionary<string, string> Paras = new Dictionary<string, string>();

Paras.Add("oauth_version", "1.0");
Paras.Add("oauth_signature_method", "HMAC-SHA1");
Paras.Add("oauth_timestamp", Util.GetTimeStamp(true));//腾讯微博使用的是UTC时间不是本地时间 Paras.Add("oauth_nonce", Util.GetNonce());
Paras.Add("oauth_consumer_key", WebConfigurationManager.AppSettings["AppKey"]);
Paras.Add("oauth_callback", Util.RFC3986_UrlEncode(WebConfigurationManager.AppSettings["CallBack"]));

oauth_signature = Util.CreateOauthSignature(Paras, RequestUrl, "GET", WebConfigurationManager.AppSettings["AppSecret"], string.Empty);

Paras.Add("oauth_signature" , Util.RFC3986_UrlEncode(oauth_signature));

result = Util.HttpGet(RequestUrl + "?" + Paras.ToQueryString());

Step2:使用获取到的未授权的RequestToken作为GET参数,重定向到腾讯的用户确认页面

在此页面,如果用户未在腾讯登陆,需要登陆,登陆后还需点击一下“授权”按钮。

这一步没想到有什么要解释的0.0。

Session["oauth_token_secret"] = result.Split('&')[1].Split('=')[1];
//Step2,使用未授权的RequestToken作为Get参数跳转到腾讯微博的授权页面 Response.Redirect("http://open.t.qq.com/cgi-bin/authorize?oauth_token=" + result.Split('&')[0].Split('=')[1]);

result为Step1的返回值,形式为“oauth_token=hdk48Djdsa&oauth_token_secret=xyz4992k83j47x0b&oauth_callback_confirmed=true”。

image

Step3:处理从腾讯重定向过来的请求,用授权后的RequestToken换取AccessToken

首先你可能会注意到接收到的oauth_token有点眼熟,没错,就是第一步获得的RequestToken。在第一步时未授权,在这里是已授权。

这里的方式和第一步时大同小异,只是计算BaseString时和发起请求时多了两个参数,oauth_verifier和oauth_token。

//Step3,解析返回到回调的参数 string RequestUrl = "http://open.t.qq.com/cgi-bin/access_token";
string oauth_signature = "";
string result = "";

string oauth_token = Request.QueryString["oauth_token"];
string oauth_verifier = Request.QueryString["oauth_verifier"];
string openid = Request.QueryString["openid"];//必须留着和之后获得的AccessToken持久化存储,因为是示例就暂时保存到Session中了 string openkey = Request.QueryString["openkey"];//没什么用,是另外一套授权体系的 Dictionary<string, string> Paras = new Dictionary<string, string>();

Paras.Add("oauth_version", "1.0");
Paras.Add("oauth_signature_method", "HMAC-SHA1");
Paras.Add("oauth_timestamp", Util.GetTimeStamp(true));//腾讯微博使用的是UTC时间不是本地时间 Paras.Add("oauth_nonce", Util.GetNonce());
Paras.Add("oauth_consumer_key", WebConfigurationManager.AppSettings["AppKey"]);
Paras.Add("oauth_verifier", oauth_verifier);
Paras.Add("oauth_token", oauth_token);

oauth_signature = Util.CreateOauthSignature(Paras, RequestUrl, "GET", WebConfigurationManager.AppSettings["AppSecret"], Session["oauth_token_secret"].ToString());

Paras.Add("oauth_signature", Util.RFC3986_UrlEncode(oauth_signature));

result = Util.HttpGet(RequestUrl + "?" + Paras.ToQueryString());

result返回值的形式为“oauth_token=nnch734d00ls2jdk&oauth_token_secret=pdkkdhi9sl3r4s00”。

 

oauth_token此时就是AccessToken,和oauth_token_secret(此时就是AccessTokenSecret)一起持久化存储起来吧。

腾讯微博开放平台需要存储的有AppKey、AppSecret、AccessToken、AccessTokenSecret、OpenID。

OpenID是OAuth协议之外的,一个OpenID唯一对应一个QQ号,可能腾讯是考虑不泄露QQ号,或者是别的原因。

四、需要注意的细节

1.编码方式使用RFC3986文档中规定的UrlEncode,不使用不一定出问题,使用了一定不在这里出问题。

2.腾讯微博开放平台,修改密码会导致用户的所有授权失效。

3.https和http,因为涉及到计算签名时候的part2(URL),所以要注意如果请求的是https地址,那么签名时候也要用https的地址,反之亦然。

 

本文以腾讯微博开放平台为例讲述了OAuth1.0的授权流程,对于其他平台的OAuth1.0实现,有很大程度上的参考意义。

最后附上本文的完整示例代码

[转载]类图及类图中的关系

mikel阅读(937)

[转载]类图及类图中的关系 – wawlian – 博客园.

1.类图和对象图  

类图(Class Diagram)是显示出类、接口以及他们之间的静态结构与关系的图。其中最基本的单元是类或接口。

类图不但可以表示类(或者接口)之间的关系,也可以表示对象之间的关系。下面是一个典型的类图:

类图一般分为几个部分:类名、属性、方法。下面分别讲解。

(1)类名

上面的Car就是类名,如果类名是正体字,则说明该类是一个具体的类,如果类名是斜体字,则说明类是一个抽象类abstract。

(2)属性列表

属性可以是public、protected、private。public前面的图标是菱形,protected对应的是菱形加钥 匙,private对应的是菱形加锁。当然,这只是一种表现方式。我是用的是Rational Rose,如果用的是别的软件,还可能使用+、-、#表示:+代表public、-代表private、#代表protected。

(3)方法列表

方法可以是public、protected、private。public前面的图标是菱形,protected对应的是菱形加钥 匙,private对应的是菱形加锁。当然,这只是一种表现方式。我是用的是Rational Rose,如果用的是别的软件,还可能使用+、-、#表示:+代表public、-代表private、#代表protected。

对于静态属性,属性名会加上一条下划线。如上图所示。

此外,类图既能表示类之间的关系,还能表示对象之间的关系。二者的区别是:对象图中对象名下面会加上一条下划线。

2.类图中的关系

(1)Generalization:泛化、一般化

Generalization表示的是类与类之间的继承关系、接口与接口之间的继承关系、类与接口之间的实现关系。如果体现到Java语言中,那就是反应extends和implements关键字。其典型类图如下所示:

(2)Association:关联关系

关联关系描述的是类与类之间的连接,他表示一个类知道另一个类的属性和方法。关联关系可以是单向的或者双向的。在Java语言中,单向的关联关系是通过以实例变量的方式持有被关联对象的引用来实现的。一般来说是不建议使用双向的关联关系的。下面举例介绍单向的关联关系。

上面的类图表现的是骑手和马之间的关系。Rider中有一个实例变量类型是Horse。

每个连接都会有两个端点,上面的Rider和Horse就是端点,且每个端点都可以有(optional)一个基数(multiplicity),表示这个类可以有几个实例。这个类似于数据库中的1:n、m:n这些关系。我们可以给上面的例子加上基数:

上面表示的是骑手与马之间的1对n关系。

(3)Aggregation:聚合关系

聚合关系是关联关系的一部分,是非常强的关联关系。聚合关系表现的更多的是整体与部分的关系。例如汽车和车门、发动机之间的关系。如图所示:

与关联关系一样,聚合关系也是通过实例变量实现的。单纯从语法的角度基本上无法判断出关联关系和聚合关系。

(4)Composition:组合关系

组合关系同样也是关联关系中的一种,这种关系是比聚合关系更加强的关系。我们前面提到,聚合关系表现的是整体与部分之间的关系,组合关系是在聚合关系的基础上,表示不可分割的整体与部分之间的关系。也就是说表示整体的对象需要负责表示部分的对象的生命周期。

“代表整体的对象负责保持代表部分的对象的存活,在一些情况下负责将代表部分的对象湮灭掉。代表整体的对象某些时候可以将代表部分的对 象传递给另外一个对象,并由它负责代表部分的对象的生命周期。换言之,代表部分的对象同一时刻只能与一个对象构成组合关系。并且由后者排他的负责其生命周 期。”——《Java与模式》

我们以人和手臂的关系举例,组合关系的类图如下:

(5)Dependency:依赖关系

依赖关系表示一个类依赖于另一个类的定义。依赖关系是单方向的。人吃苹果,那么人依赖苹果。类图如下:

一般来说,被依赖的对象往往是以局部变量、方法参数的形式存在于来对象中,与关联关系不同,它不会以成员变量的形式存在于以来对象中。这一点值得注意。另外,每一个依赖都有一个名称。上面这个依赖关系的名称就是eats。

以上就是类图和常见的类图之间的关系。最近看一些相关的书籍,发现对常见的类关系还是不熟,把自己之前的一篇博客转过来再好好看看。

author:wawlian
save me from myself

[转载]JavaScript动态设置滚动条高度

mikel阅读(988)

转载[原]JavaScript动态设置滚动条高度 – purediy – 博客园.

工作中遇到情形如下:一个ul标签,里面有很多li标签,其中有一个代表初始化已选中的<li class=”li-on”><li>。
如果ul设置了高度,如下面的ul的style,并且有很多li子标签,那选中的li就被淹没在滚动条下面。
<ul id="ul_module" style="height:180px; overflow-y:scroll;">
    <li>...</li>
    <li>...</li>
    <li>...</li>
    <li>...</li>
    <li>...</li>
    <li>...</li>
    <li>...</li>
    <li>...</li>
    <li>...</li>
    <li>000</li>
    <li>111</li>
    <li>222</li>
    <li>333</li>
    <li>444</li>
    <li>555</li>
    <li>666</li>
    <li>777</li>
    <li>888</li>
    <li class="li-on">999</li>
    <li>...</li>
    <li>...</li>
    <li>...</li>
    <li>...</li>
    <li>...</li>
    <li>...</li>
    <li>...</li>
    <li>...</li>
    <li>...</li>
</ul>

要做的就是要把这个选中的li标签,现在到可见区域内,可以通过js动态的设置滚动条的高度。
具体如下:获得在当前选中的li前面的节点的高度,然后减去ul高度的一半设置给ul的scrollTop,基本上可以把滚动条设置在中间位置。

var ul_module = $('#ul_module');
var ul_height = ul_module.height();
var seleted_li = ul_module.find('.li-on');
if(seleted_li.length) {
    var height = seleted_li.height();
    var prevCount = seleted_li.prevAll().length;
    ul_module.scrollTop(height * prevCount - ul_height/2);
}

[转载]ASP.NET MVC 4 RC的JS/CSS打包压缩功能

mikel阅读(1025)

[转载]ASP.NET MVC 4 RC的JS/CSS打包压缩功能 – 张善友 – 博客园.

打包(Bundling)及压缩(Minification)指的是将多个js文件或 css文件打包成单一文件并压缩的做法,如此可减少浏览器需下载多个文件案才能完成网页显示的延迟感,同时通过移除JS/CSS文件案中空白、批注及修改 JavaScript内部函数、变量名称的压缩手法,能有效缩小文件案体积,提高传输效率,提供使用者更流畅的浏览体验。

ASP.NET MVC 4中可以使用BundleTable捆绑多个css文件和js文件,以提高网络加载速度和页面解析速度。更为重要的是通过捆绑可以解决IE浏览器的31个 CSS文件连接的限制。在做ASP.NET项目时很多时候会使用一些开源的JavaScript控件。无形中增加了css和javascript文件的引 用。如果手工将这些css文件合并将给将来版本升级造成很大的麻烦。于是,我们只好小心翼翼的处理这些css文件在页面中的引用。ASP.NET捆绑是 ASP.NET 4.5的新功能,是System.Web.Optimization命名空间下。他提供了一些ASP.NET运行性能方面的优化,比如,一个页面可能有很 多CSS/JS/图片,通过灵活的应用BundleTable类,他可以帮你将文件合并压缩代码优化成一个最理想的文件,然后输出到客户端,从而提高了浏 览器下载速度。

现在我们终于有一个以相对比较完美的解决方案来解决css文件和javascirpt文件给我们带来的麻烦,BundleTable捆绑技术很好的解决了这个问题。

ASP.NET MVC 4 Beta时代便已内建打包压缩功能,做法是在global.asax.cs的Application_Start加入

BundleTable.Bundles.EnableDefaultBundles();

如此,便可使用以下写法一口气将整个Scripts目录下的JS及Contents目录下所有CSS打包并压缩成单一文件案,改善网页载入效率: (参考)

<script src=”@Url.Content(“~/Scripts/js”)” type=”text/javascript”></script>
<script src=”@Url.Content(“~/Content/CSS”)” type=”text/javascript”></script>

最近在看ASP.NET MVC 4 RC,发现RC版在打包压缩做法上又有所革新,变得更加弹性有条理。

原本打包规则被藏在global.asax.cs Application_Start中,RC版起则多了一个新目录App_Start,其中包含RouteConfig.cs、FilterConfig.cs、BundleConfig.cs三个类,做法上改为通过WebActivator启动。新的系统配置将路由规则、过滤器及打包规则等注册逻辑由Application_Start中拆出来,各自放在独立文件案中,管理及修改起来一目了然,架构上更漂亮。

BundleConfig.cs的预设内容如下:

using System.Web;

using System.Web.Optimization;

namespace MyMvcApplicaiton

{

public class BundleConfig

{

public static void RegisterBundles(BundleCollection bundles)

{

bundles.Add(new ScriptBundle(“~/bundles/JQuery”).Include(

“~/Scripts/JQuery-1.*”));

bundles.Add(new ScriptBundle(“~/bundles/JQueryui”).Include(

“~/Scripts/jQuery-ui*”));

bundles.Add(new ScriptBundle(“~/bundles/jQueryval”).Include(

“~/Scripts/jquery.unobtrusive*”,

“~/Scripts/jquery.validate*”));

bundles.Add(new ScriptBundle(“~/bundles/modernizr”).Include(

“~/Scripts/modernizr-*”));

bundles.Add(new StyleBundle(“~/Content/css”).Include(“~/Content/site.css”));

bundles.Add(new StyleBundle(“~/Content/themes/base/css”).Include(

“~/Content/themes/base/jquery.ui.core.css”,

“~/Content/themes/base/jquery.ui.resizable.css”,

“~/Content/themes/base/jquery.ui.selectable.css”,

“~/Content/themes/base/jquery.ui.accordion.css”,

“~/Content/themes/base/jquery.ui.autocomplete.css”,

“~/Content/themes/base/jquery.ui.button.css”,

“~/Content/themes/base/jquery.ui.dialog.css”,

“~/Content/themes/base/jquery.ui.slider.css”,

“~/Content/themes/base/jquery.ui.tabs.css”,

“~/Content/themes/base/jquery.ui.datepicker.css”,

“~/Content/themes/base/jquery.ui.progressbar.css”,

“~/Content/themes/base/jquery.ui.theme.css”));

}

}

}

跟Beta时代很大的差异是将JS与CSS加以群组化,分别定义出jquery, jqueryui, jqueryval, modernizr, css及themes/base/css等群组,让网页可以视需要只加载必要的JS及CSS文件群组,不像先前每次得打包整个目录,对于JS文件的加载顺 序及相依性也能做较精准的调控。

而在.cshtml中,则使用Styles.Render及Scripts.Render载入BundleConfig.cs所定义的JS及CSS群组,例如:

<!DOCTYPE html>

<html>

<head>

<meta charset=”utf-8″ />

<meta name=”viewport” content=”width=device-width” />

<title>@ViewBag.Title</title>

@Styles.Render(“~/Content/themes/base/css”, “~/Content/css”)

@Scripts.Render(“~/bundles/modernizr”)

@Scripts.Render(“~/bundles/jquery”, “~/bundles/jqueryui”,

“~/bundles/jqueryval”)

@RenderSection(“scripts”, required: false)

</head>

<body>

@RenderBody()

</body>

</html>

接着来实测一下,做一个简单的Index.cshtml,中间只有<div>Hello</div>一行,配合上述的_Layout.cshtml,进行测试,没想到呈现的源代码如下,一个个CSS及JS文件都是分开的,没打包也没压缩?

<!DOCTYPE html>

<html>

<head>

<meta charset=”utf-8″ />

<meta name=”viewport” content=”width=device-width” />

<title></title>

<link href=”/Content/themes/base/jquery.ui.core.css” rel=”stylesheet” type=”text/css” />

<link href=”/Content/themes/base/jquery.ui.resizable.css” rel=”stylesheet” type=”text/css” />

<link href=”/Content/themes/base/jquery.ui.selectable.css” rel=”stylesheet” type=”text/css” />

<link href=”/Content/themes/base/jquery.ui.accordion.css” rel=”stylesheet” type=”text/css” />

<link href=”/Content/themes/base/jquery.ui.autocomplete.css” rel=”stylesheet” type=”text/css” />

<link href=”/Content/themes/base/jquery.ui.button.css” rel=”stylesheet” type=”text/css” />

<link href=”/Content/themes/base/jquery.ui.dialog.css” rel=”stylesheet” type=”text/css” />

<link href=”/Content/themes/base/jquery.ui.slider.css” rel=”stylesheet” type=”text/css” />

<link href=”/Content/themes/base/jquery.ui.tabs.css” rel=”stylesheet” type=”text/css” />

<link href=”/Content/themes/base/jquery.ui.datepicker.css” rel=”stylesheet” type=”text/css” />

<link href=”/Content/themes/base/jquery.ui.progressbar.css” rel=”stylesheet” type=”text/css” />

<link href=”/Content/themes/base/jquery.ui.theme.css” rel=”stylesheet” type=”text/css” />

<link href=”/Content/site.css” rel=”stylesheet” type=”text/css” />

<script src=”/Scripts/modernizr-2.0.6-development-only.js” type=”text/javascript”></script>

<script src=”/Scripts/jquery-1.6.2.js” type=”text/javascript”></script>

<script src=”/Scripts/jquery-ui-1.8.11.js” type=”text/javascript”></script>

<script src=”/Scripts/jquery.unobtrusive-ajax.js” type=”text/javascript”></script>

<script src=”/Scripts/jquery.validate.js” type=”text/javascript”></script>

<script src=”/Scripts/jquery.validate.unobtrusive.js” type=”text/javascript”></script>

</head>

<body>

<div>Hello</div>

</body>

</html>

原来,这也是ScriptBundle及StyleBundle的贴心之处,在调试模式下,会展现CSS及JS原貌,方便开发人员检查源代码找问题 与除错。要见识它的打包压缩效果,记得要设定<compilation Debug=”false” targetFramework=”4.0″ />。

关闭调试模式后,网页的源代码就变成以下的样子,一个群组只有一个<link>或<script>,而href及src会 指向/Content/css?v=ji3nXsakWko…(包含哈希码参数,以确保文件案变动时只会载入新版)格式的连结,传回多个文件案打包及压缩 后的内容:

<!DOCTYPE html>

<html>

<head>

<meta charset=”utf-8″ />

<meta name=”viewport” content=”width=device-width” />

<title></title>

<link href=”/Content/themes/base/css?v=UM62…略” rel=”stylesheet” type=”text/css” />

<link href=”/Content/css?v=ji3n…略” rel=”stylesheet” type=”text/css” />

<script src=”/bundles/modernizr?v=XGaE…略” type=”text/javascript”></script>

<script src=”/bundles/jquery?v=3AwA…略” type=”text/javascript”></script>

<script src=”/bundles/jqueryui?v=bMdf…略” type=”text/javascript”></script>

<script src=”/bundles/jqueryval?v=uFE7…略” type=”text/javascript”></script>

</head>

<body>

<div>Hello</div>

</body>

</html>

最后,实际测量二者的性能差别:

clip_image001
在未打包压缩前,加载网页需要发出20个请求,总共传输5,992+812,541=818,533 Bytes的资料。

clip_image002
打包压缩后,请求数下降到7个,数据传输量也减少为2,274+352,454=354,728 Bytes,数据传输量只有原本的43.33%!

在开发ASP.NET MVC 4项目时,不要忘记这个有用的机制。

最后介绍一个System.Web.Optimization的扩展库 http://bundletransformer.codeplex.com/,推荐在ASP.NET MVC 4项目中使用。

[转载]ASP.NET MVC 扩展之 JsonpResult用来提供轻量级跨域调用服务

mikel阅读(1034)

[转载]ASP.NET MVC 扩展之 JsonpResult ,用来提供轻量级跨域调用服务 – 自由的生活 – 博客园.

关于 JSONP,我想大家都已经很熟悉了,还不是很清楚的童鞋可以在网上搜索一下。众所周知, Ajax 是不能跨域请求的,默认情况下,浏览器是阻止的。那如何来实现跨域提供服务呢?举一个很简单的例子。比如我现在有一个网站 www.abc.com ,其中有一个页面需要提供百度的搜索框,并且还要像百度首页一样,提供智能的提示,就是在我输入文字的同时,及时补全我的搜索,列出供我选择的项。

由于前面我们说到 Ajax 是不能跨域的,那怎样调用百度的搜索服务呢?答案就是 JSONP。JSONP 说白了就是在 www.abc.com 一 个页面里,通过 JavaScript 动态构造一个 <Script> </script>,其中 src= ‘http://search.baidu.com/service.php?word=博客园&callback=showSearch”, 其中 word=博客园 是我们在 www.abc.com 搜索框里面输入的文字,callback=showSearch 就是百度返回的结果里面回调函数的名称,比如 showSearch({ word: ‘博客园’, result : [{博客园a},{博客园b}] }) 。

 

由于动态构建 <script> 不是 Ajax 请求,而是普通的 GET 请求,所以它是可以跨域的。

废话了这么多,下面就贴出我的代码了:

JsonpResult:

using System.Web.Mvc;
using System.Web.Script.Serialization;
using System.Text;

namespace DearBruce.JsonpActionResultDemo.WebUI.Extensions
{
    /// <summary>
    /// Jsonp 返回结果
    /// </summary>
    public class JsonpResult : ActionResult
    {
        /// <summary>
        /// 默认的 callback 参数名称
        /// </summary>
        public static readonly string _defaultCallbackName = "jsoncallback";

        /// <summary>
        /// 默认的密码的参数名称
        /// </summary>
        public static readonly string _defaultPasswordName = "password";

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="data">要序列化为 Jsonp 数据的对象</param>
        public JsonpResult(object data)
            : this(data, null)
        {

        }

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="data">要序列化为 Jsonp 数据的对象</param>
        /// <param name="callbackName">回调函数的名字,如果为空,则默认为 "jsoncallback"</param>
        public JsonpResult(object data, string callbackName)
        {
            this.Data = data;
            this.CallBackName = callbackName;
        }

        /// <summary>
        /// 验证权限
        /// </summary>
        /// <param name="context">控制器上下文</param>
        /// <returns>授权是否成功,如果已经授权,则返回 true</returns>
        protected virtual bool ValidatePrivilege(ControllerContext context)
        {
            if (!string.IsNullOrEmpty(PasswordValue))
            {
                // 这里验证密码是否正确
                string password = context.HttpContext.Request.QueryString[PasswordName];
                if (password != PasswordValue)
                {
                    return false;
                }
            }
            return true;
        }

        /// <summary>
        /// 核心处理
        /// </summary>
        /// <param name="context"></param>
        public override void ExecuteResult(ControllerContext context)
        {
            if (!ValidatePrivilege(context))
            {
                HandleUnAuthorizedRequest(context);
                return;
            }
            string jsonData = null;
            if(Data != null)
            {
                jsonData = new JavaScriptSerializer().Serialize(Data);
            }
            string callbackValue = context.HttpContext.Request.QueryString[CallBackName];

            string result;
            string contentType;
            if (string.IsNullOrEmpty(callbackValue))
            {
                result = jsonData;
                contentType = "application/json";
            }
            else
            {
                result = callbackValue + "(" + jsonData + ")";
                contentType = "application/x-javascript";
            }
            context.HttpContext.Response.ContentType = contentType;
            if (this.ContentEncoding != null)
            {
                context.HttpContext.Response.ContentEncoding = this.ContentEncoding;
            }
            if(result != null)
            {
                context.HttpContext.Response.Write(result);
            }
        }

        /// <summary>
        /// 处理为授权的请求
        /// </summary>
        /// <param name="context">控制器上下文</param>
        protected virtual void HandleUnAuthorizedRequest(ControllerContext context)
        {
            // 暂时不实现
            //context.HttpContext.Response.ContentType = "text/plain";
            //context.HttpContext.Response.Write(string.Empty);
        }

        /// <summary>
        /// 要序列化为 Jsonp 数据的对象
        /// </summary>
        public object Data { get; set; }

        private Encoding _contentEncoding;

        /// <summary>
        /// 内容编码
        /// </summary>
        public Encoding ContentEncoding
        {
            get
            {
                return _contentEncoding; // ?? Encoding.UTF8;
            }
            set
            {
                _contentEncoding = value;
            }
        }

        private string _callBackName;

        /// <summary>
        /// 回调函数名称
        /// </summary>
        public string CallBackName
        {
            get
            {
                return _callBackName ?? _defaultCallbackName;
            }
            set
            {
                _callBackName = value;
            }
        }


        private string _passwordName { get; set; }

        /// <summary>
        /// 密码的参数名称,默认为 "password"
        /// </summary>
        public string PasswordName
        {
            get
            {
                return _passwordName ?? _defaultPasswordName;
            }
            set
            {
                _passwordName = value;
            }
        }

        /// <summary>
        /// 正确的密码值
        /// </summary>
        public string PasswordValue { get; set; }
    }
}

测试的 Controller:

public class HomeController : Controller
    {
        public ActionResult Index()
        {
            ViewBag.Message = "欢迎使用 ASP.NET MVC!";

            return View();
        }

        public ActionResult TestJsonp1()
        {
            return new JsonpResult(new { success = true, message = "测试 jsonp1" });
        }

        public ActionResult TestJsonp2()
        {
            return new JsonpResult(new { success = true, message = "测试 jsonp2" }, "callback");
        }

        public ActionResult TestJsonp3()
        {
            JsonpResult result = new JsonpResult(new { success = true, message = "测试 jsonp3" }, "callback")
            {
                PasswordValue = "bruce123456",
            };
            return result;
        }
    }

测试的 View:

<p>
    @Html.ActionLink("测试 Jsonp 之不带 jsoncallback 参数和不需要密码,则返回 json 数据", "TestJsonp1")
</p>
<p>
    @Html.ActionLink("测试 Jsonp 之带 callback 参数,则返回 javascript 代码", "TestJsonp2", new { callback = "ShowMessage" })
</p>
<p>
    @Html.ActionLink("测试 Jsonp 之需要密码 - 为空或错误的密码", "TestJsonp3")
</p>
<p>
    @Html.ActionLink("测试 Jsonp 之带 callback 参数和需要密码 - 正确的密码", "TestJsonp3", new { password = "bruce123456", callback = "ShowMessage" })
</p>

[转载]从赋值操作理解不同类型的函数传参

mikel阅读(979)

[转载]从赋值操作理解不同类型的函数传参 – 无风听海 – 博客园.

         我们都知道所谓的程序,就是对传入的数据进行操作,最终将处理的数据输出给用户。那么在我们的编程中的体现,就是通过函数(或者方法)参数传入数据,然后通过函数返回值输出处理后的数据。数据的传入涉及到函数的实参和形参的传递,数据的输出涉及到函数的返回值和返回值类型的参数。函数返回值没有什么特别之处,而其他的都是函数传参的问题,既然是传参,那么传参到底传过去了什么?

函数传参与赋值操作

         我们都知道函数传参涉及到形参和实参,那么实参和形参就是两个不同的变量,所以函数传参的过程,类似(或者说就是)使用实参给形参赋值的过程。那么我们先来分析按值传参的情况

         1是值类型变量的赋值操作图,我们大家都知道值类型变量直接保存变量的内容,所以赋值的过程就是将ValA保存的内容直接拷贝到ValB中,此时两个变量除了保存的值相同外,没有其他的任何关系,所以此时修改ValB的内容并不会引起ValA的变化。这是与值类型按值传参的效果是一样的。

1.值类型变量赋值

         2展示的是引用类型变量的赋值操作,我们都知道引用类型的变量保存的是一个地址,这个地址指向了变量内容所在的地址,也就是说引用变量保存的是一个地址。那么引用类型的赋值操作同样也是拷贝变量保存的内容,这样ValAValB就都指向了同样地址。如果我们修改ValB的成员,那么ValA中也会随之产生变化。我们可以看到值类型和引用类型的赋值操作是一样的,都是对变量直接保存的内容的拷贝过程。但是如果我们使用其他对象对ValB重新赋值,这时ValB就指向了其他对象,ValA还指向原来的对象并没有变化。

2.引用类型变量赋值

         我们知道C#中引入了ref关键字,使我们可以通过函数参数返回函数的处理结果,对于我们需要同时返回多项结果的时候比较有用。其实这也就是我们通常所说的按引用传参。图3展示了变量地址赋值操作,此时我们使用新的对象对ValB所指定的对象赋值,这是ValA也就指向了新的对象了。C#中的ref是否也是这样操作的呢?我们看以看一下下边的代码和相应的IL就知道了。

3.变量地址赋值操作

C#代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace FunctionParameter
{
    class PassByValue
    {
        public void PassValueParameterByReference(ref ReferencePara parameter)
        {
            parameter.a = 1;
            parameter = new ReferencePara();
        }

        public class ReferencePara
        {
            public int a;
            public int b;
        }
    }
}

对应的MSIL代码

//传递的是变量的地址
.method public hidebysig instance void  PassValueParameterByReference(class FunctionParameter.PassByValue/ReferencePara& parameter) cil managed
{
  // 代码大小       17 (0x11)
  .maxstack  8
  IL_0000:  nop
  //加载参数
  IL_0001:  ldarg.1
  //加载对象的引用
  IL_0002:  ldind.ref
  IL_0003:  ldc.i4.1
  IL_0004:  stfld      int32 FunctionParameter.PassByValue/ReferencePara::a
  IL_0009:  ldarg.1
  IL_000a:  newobj     instance void FunctionParameter.PassByValue/ReferencePara::.ctor()
  //绑定对象的引用
  IL_000f:  stind.ref
  IL_0010:  ret
} // end of method PassByValue::PassValueParameterByReference

总结

从我们大学一开始接触计算机编程开始,我们就开始记忆两种函数传参的异同,其实传参的过程就是实参对形参赋值的过程,只不过有时拷贝的是变量的内容,有时拷贝的是变量的地址罢了。

[转载]提高信息系统用户帐户安全的几个增强功能分享

mikel阅读(847)

[转载]提高信息系统用户帐户安全的几个增强功能分享 – 通用信息化建设平台 – 博客园.

有些功能,其实好几年前就知道有那样的需求了,但是一直没空去完善,今天心情好把一些改进的功能分享给大家,在通用权限管理系统里把下面的几个功能增强了一下。

1:当系统初始化数据后,往往希望用户登录系统后再修改系统密码,当然也不能强迫要求客户非要修改密码后才能做其他任务,当然是一直提醒用户修改密码比较好,所以需要系统管理员可以初始化用户的原始密码,若用户没修改密码,一直提醒用户修改密码,用户表里可以加一个字段保存用户最后是什么时候修改的密码的时间戳。

2:有的系统安全要求比较好,可能需要定期修改密码,而且最近几次密码不能重复,每个月修改一次密码,而且不能最近2-3次密码不能重复,所以还是需要有最后是什么时候修改的密码的时间戳,同时有一个历史记录功能,记录用户都用过什么密码的,然后比较最近的2次密码与新密码是否一样?若一样还是要求用户修改密码。

3:用户连续输入几次错误密码后,应该锁定用户帐户15分钟等, 防止暴力破解密码的要求,有的系统也是有需要有锁定用户帐户的功能,当然很多系统也不急15分钟等待时间,若真是意外输入错误了,要求马上使用系统的,当 然还可以叫系统管理员解锁锁定密码的功能,所以不只是需要有锁定帐户的功能,还需要有后台能解锁用户帐户的功能,当然还有连续输入几次错误后,就不让登录 了,需要重新启动程序也是有的,这也是为了给暴力破解的增加一点儿难度,加一道锁。

当然这些也都有个开关比较好,不是所有的系统都需要这么严谨的功能,有需要时开关打开好,不需要时开关关闭了,就像word的功能90%,我们都没怎么用 过,但是有需要时,再临时开发一个不是开玩笑的,还是功能有着,先不用也可以,等有用时,随时可以调试出来了,是比较理想了。

 

技术水平是否厉害其实体现在系统增加了某个功能后,数据库结构没修改很早就这么设计好了,以前的老版本的系统与新版本的兼容性很强,甚至在老的系统上用这 个新的功能也可以顺利运行,数据库结构的兼容性、程序结构的兼容性,程序结构的可扩展性其实真正体现了这个系统是否足够强壮。

今天加3个功能,下个月加5个功能,明年再需要增强10个功能,系统是否很稳定,数据库结构是否很稳定,还有不是别人说了一个功能,你跟着人家屁股后面实现一个功能永远是个模仿者而已,模仿别人永远没个出头的日子的。

别人一说某个功能你也会,那干脆别人购买个彩票中奖了,你也跟着去买一个,那就没啥意义了。苹果手机做得好,大家都去模仿,永远也难超越人家,老外都打技术战,创新战,很少打价格战,而我们是天天打价格战,最后谁也没得到好处,好处都让老外赚走了。

 

贴接代码图:

 

 

 

将权限管理、工作流管理做到我能力的极致,一个人只能做好那么很少的几件事情。

[转载]c# list排序的三种实现方式

mikel阅读(1048)

[转载]c# list排序的三种实现方式 – Bradwarden – 博客园.

用了一段时间的gridview,对gridview实现的排序功能比较好奇,而且利用C#自带的排序方法只能对某一个字段进行排序,今天demo了一下,总结了三种对list排序的方法,并实现动态传递字段名对list进行排序。

首先先介绍一下平时最常用的几种排序方法。

第一种:实体类实现IComparable接口,而且必须实现CompareTo方法

实体类定义如下:

View Code 
 class Info:IComparable
     {
         public int Id { get; set; }
         public string Name { get; set; }
 
         public int CompareTo(object obj) {
             int result;
             try
             {
                 Info info = obj as Info;
                 if (this.Id > info.Id)
                 {
                     result = 0;
                 }
                 else
                     result = 1;
                 return result;
             }
             catch (Exception ex) { throw new Exception(ex.Message); }
         }
     }

调用方式如下,只需要用sort方法就能实现对list进行排序。

View Code 
 private static void ReadAccordingCompare() {
             List<Info> infoList = new List<Info>();
             infoList.Add(
                 new Info() { Id = 1, Name = "abc" });
             infoList.Add(new Info() { Id = 3, Name = "rose" });
             infoList.Add(new Info() { Id = 2, Name = "woft" });
                infoList.Sort();
             foreach (var item in infoList)
             {
                 Console.WriteLine(item.Id + ":" + item.Name); 
             }
         }

第二种方法:linq to list进行排序

运用linq实现对list排序,在实体类定义的时候就不需用实现IComparable接口,调用方式如下:

View Code 
 private static void ReadT(string str) {
             List<Info> infoList = new List<Info>();
             infoList.Add(
                 new Info() { Id = 1, Name = "woft" });
             infoList.Add(new Info() { Id=3,Name="rose"});
             infoList.Add(new Info() { Id = 2, Name = "abc" });
             Console.WriteLine("ReadT*********************");
             IEnumerable<Info> query = null;
             query = from items in infoList orderby items.Id select items;
             foreach (var item in query)
             {
                 Console.WriteLine(item.Id+":"+item.Name);
             }
         }

但是上面两种方式都只能对一个实体属性排序,如果对不同的属性排序的话只能写很多的if进行判断,这样显得很麻烦。

且看下面的方式实现根据传入参数进行排序。

View Code 
 private static void ListSort(string field,string rule)
         {
             if (!string.IsNullOrEmpty(rule)&&(!rule.ToLower().Equals("desc")||!rule.ToLower().Equals("asc")))
             {
                 try
                 {
                     List<Info> infoList = GetList();
                     infoList.Sort(
                         delegate(Info info1, Info info2)
                         {
                             Type t1 = info1.GetType();
                             Type t2 = info2.GetType();
                             PropertyInfo pro1 = t1.GetProperty(field);
                             PropertyInfo pro2 = t2.GetProperty(field);
                             return rule.ToLower().Equals("asc") ?
                                 pro1.GetValue(info1, null).ToString().CompareTo(pro2.GetValue(info2, null).ToString()) :
                                 pro2.GetValue(info2, null).ToString().CompareTo(pro1.GetValue(info1, null).ToString());
                         });
                     Console.WriteLine("*****ListSort**********");
                     foreach (var item in infoList)
                     {
                         Console.WriteLine(item.Id + "," + item.Name);
                     }
                 }
                 catch (Exception ex)
                 {
                     Console.WriteLine(ex.Message);
                 }
             } Console.WriteLine("ruls is wrong");
 
         }

调用方式:

ListSort("Name","desc");//表示对Name进行desc排序
ListSort("Id","asc");//表示对Id进行asc排序。如此如果参数很多的话减少了很多判断。

如果有更好的方法欢迎提出,共同学习………..

后续:受一位留言着的提醒,在用反射实现多字段排序时只需一次反射,多余的一次放而会影响性能,现更新如下:

View Code 
  private static void ListSort(string field,string rule)
         {
             if (!string.IsNullOrEmpty(rule)&&(!rule.ToLower().Equals("desc")||!rule.ToLower().Equals("asc")))
             {
                 try
                 {
                     List<Info> infoList = GetList();
                     infoList.Sort(
                         delegate(Info info1, Info info2)
                         {
                             //Type t1 = info1.GetType();
                             //Type t2 = info2.GetType();
                             Type t = typeof(Info);
                             PropertyInfo pro1 = t.GetProperty(field);
                             PropertyInfo pro2 = t.GetProperty(field);
                            // PropertyInfo pro1 = t1.GetProperty(field);
                            // PropertyInfo pro2 = t2.GetProperty(field);
                             return rule.ToLower().Equals("asc") ?
                                 pro1.GetValue(info1, null).ToString().CompareTo(pro2.GetValue(info2, null).ToString()) :
                                 pro2.GetValue(info2, null).ToString().CompareTo(pro1.GetValue(info1, null).ToString());
                         });
                     Console.WriteLine("*****ListSort**********");
                     foreach (var item in infoList)
                     {
                         Console.WriteLine(item.Id + "," + item.Name);
                     }
                 }
                 catch (Exception ex)
                 {
                     Console.WriteLine(ex.Message);
                 }
             }
             Console.WriteLine("ruls is wrong");
         }

[转载]网络爬虫基本原理(一)

mikel阅读(1116)

[转载]网络爬虫基本原理(一) – wawlian – 博客园.

    网络爬虫是捜索引擎抓取系统的重要组成部分。爬虫的主要目的是将互联网上的网页下载到本地形成一个或联网内容的镜像备份。这篇博客主要对爬虫以及抓取系统进行一个简单的概述。

一、网络爬虫的基本结构及工作流程

一个通用的网络爬虫的框架如图所示:

网络爬虫的基本工作流程如下:

1.首先选取一部分精心挑选的种子URL;

2.将这些URL放入待抓取URL队列;

3.从待抓取URL队列中取出待抓取在URL,解析DNS,并且得到主机的ip,并将URL对应的网页下载下来,存储进已下载网页库中。此外,将这些URL放进已抓取URL队列。

4.分析已抓取URL队列中的URL,分析其中的其他URL,并且将URL放入待抓取URL队列,从而进入下一个循环。

二、从爬虫的角度对互联网进行划分

对应的,可以将互联网的所有页面分为五个部分:

1.已下载未过期网页

2.已下载已过期网页:抓取到的网页实际上是互联网内容的一个镜像与备份,互联网是动态变化的,一部分互联网上的内容已经发生了变化,这时,这部分抓取到的网页就已经过期了。

3.待下载网页:也就是待抓取URL队列中的那些页面

4.可知网页:还没有抓取下来,也没有在待抓取URL队列中,但是可以通过对已抓取页面或者待抓取URL对应页面进行分析获取到的URL,认为是可知网页。

5.还有一部分网页,爬虫是无法直接抓取下载的。称为不可知网页。

三、抓取策略

在爬虫系统中,待抓取URL队列是很重要的一部分。待抓取URL队列中的URL以什么样的顺序排列也是一个很重要的问题,因为这涉及到先抓取那个页面,后抓取哪个页面。而决定这些URL排列顺序的方法,叫做抓取策略。下面重点介绍几种常见的抓取策略:

1.深度优先遍历策略

深度优先遍历策略是指网络爬虫会从起始页开始,一个链接一个链接跟踪下去,处理完这条线路之后再转入下一个起始页,继续跟踪链接。我们以下面的图为例:

遍历的路径:A-F-G  E-H-I B C D

2.宽度优先遍历策略

宽度优先遍历策略的基本思路是,将新下载网页中发现的链接直接插入待抓取URL队列的末尾。也就是指网络爬虫会先抓取起始网页中链接的所有网页,然后再选择其中的一个链接网页,继续抓取在此网页中链接的所有网页。还是以上面的图为例:

遍历路径:A-B-C-D-E-F G H I

3.反向链接数策略

反向链接数是指一个网页被其他网页链接指向的数量。反向链接数表示的是一个网页的内容受到其他人的推荐的程度。因此,很多时候搜索引擎的抓取系统会使用这个指标来评价网页的重要程度,从而决定不同网页的抓取先后顺序。

在真实的网络环境中,由于广告链接、作弊链接的存在,反向链接数不能完全等他我那个也的重要程度。因此,搜索引擎往往考虑一些可靠的反向链接数。

4.Partial PageRank策略

Partial PageRank算法借鉴了PageRank算法的思想:对于已经下载的网页,连同待抓取URL队列中的URL,形成网页集合,计算每个页面的 PageRank值,计算完之后,将待抓取URL队列中的URL按照PageRank值的大小排列,并按照该顺序抓取页面。

如果每次抓取一个页面,就重新计算PageRank值,一种折中方案是:每抓取K个页面后,重新计算一次PageRank值。但是这种情况还会有一个问 题:对于已经下载下来的页面中分析出的链接,也就是我们之前提到的未知网页那一部分,暂时是没有PageRank值的。为了解决这个问题,会给这些页面一 个临时的PageRank值:将这个网页所有入链传递进来的PageRank值进行汇总,这样就形成了该未知页面的PageRank值,从而参与排序。下 面举例说明:

5.OPIC策略策略

该算法实际上也是对页面进行一个重要性打分。在算法开始前,给所有页面一个相同的初始现金(cash)。当下载了某个页面P之后,将P的现金分摊给所有从P中分析出的链接,并且将P的现金清空。对于待抓取URL队列中的所有页面按照现金数进行排序。

6.大站优先策略

对于待抓取URL队列中的所有网页,根据所属的网站进行分类。对于待下载页面数多的网站,优先下载。这个策略也因此叫做大站优先策略。

 

参考书目:

1.《这就是搜索引擎——核心技术详解》  张俊林  电子工业出版社

2.《搜索引擎技术基础》         刘奕群等 清华大学出版社