[转载]Asp.NET MVC Widget开发 - Controller控制器

mikel阅读(1942)

[转载]Asp.NET MVC Widget开发 – Controller控制器 – Creative dream – 博客园.

ASP.NET开发博客类系统,我们经常都会用到Widget,像在线好友、最近访问 好友、最新留言等,关于ASP.NET MVC与ASP.NET视图的差异,这里不再说了,大家可去查一下,接下来我以“我的好友”列表来要介绍在Asp.NET MVC实现这一功能以及结构设计。

  • 开发工具:VS 2010 EN
  • 开发语言:Visual C#
  • ASP.NET MVC 3
    1. Asp.NET MVC Widget – 设计
    2. Asp.NET MVC Widget – Controller控制器
    3. Asp.NET MVC Widget – ViewEngine
    4. Asp.NET MVC Widget – Mobile支持

      首先发布一下Controller的代码

      如上图中代码,关于控制器几个需要注意

      • 必须使用[ChildActionOnly]属性,不允许直接访问
      • 返回PartialView
      • 其它部分无区别

      当然,我们可加入更多的Action,只要符合以上规定

      [转载]Android新手入门基本知识(一)

      mikel阅读(956)

      关于Android

      Android一词的本义指“机器人”,同时也是Google于2007年11月5日宣布的基于Linux平台的开源手机操作系统的名称,该平台由操作系统、中间件、用户界面和应用软件组成,号称是首个为移动终端打造的真正开放和完整的移动软件。
      2008年9月22日,美国运营商T-Mobile USA在纽约正式发布第一款Google手机——T-Mobile G1。该款手机为台湾宏达电代工制造,是世界上第一部使用Android操作系统的手机,支持WCDMA/HSPA网络,理论下载速率7.2Mbps,并支持Wi-Fi。
      Android 是Google开发的基于Linux平台的开源手机操作系统。它包括操作系统、用户界面和应用程序 —— 移动电话工作所需的全部软件,而且不存在任何以往阻碍移动产业创新的专有权障碍。Google与开放手机联盟合作开发了 Android,这个联盟由包括中国移动、摩托罗拉、高通、宏达电和 T-Mobile 在内的 30 多家技术和无线应用的领军企业组成。Google通过与运营商、设备制造商、开发商和其他有关各方结成深层次的合作伙伴关系,希望借助建立标准化、开放式的移动电话软件平台,在移动产业内形成一个开放式的生态系统。
      Android 作为谷歌企业战略的重要组成部分,将进一步推进”随时随地为每个人提供信息”这一企业目标的实现。我们发现,全球为数众多的移动电话用户从未使用过任何基 于 Android 的电话。谷歌的目标是让(移动通讯)不依赖于设备甚至平台。出于这个目的,Android 将补充,而不会替代谷歌长期以来奉行的移动发展战略:通过与全球各地的手机制造商和移动运营商结成合作伙伴,开发既有用又有吸引力的移动服务,并推广这些 产品。
      开放手机联盟的成立和 Android 的推出是对现状的重大改变,在带来初步效益之前,还需要不小的耐心和高昂的投入。但是,我们认为全球移动用户从中能获得的潜在利益是值得付出这些努力的。如果你也是一个开发者,并对我们的想法感兴趣,就请再给我们一星期的时间, 届时谷歌便能提供 SDK 了。如果你是一名移动用户,只需再等一段时间,我们的一些合作伙伴计划在 2008 年下半年推出基于 Android 平台的电话产品。如果你已经拥有一部你了解并喜爱的电话,请登录 mobile.google.com ,确保你已经安装谷歌手机地图、Gmail 以及其他一些专为你的手机开发的精彩应用。谷歌将继续努力,让这些服务变得更好,同时也将添加更有吸引力的特性、应用和服务。


      目录

      第  一  讲:android专有名词介绍 不明白的进来
      第  二  讲:android是什么?android现有成员有哪些?
      第  三  讲:手把手教你给android手机设置WIFI无线网络
      第  四  讲:关于CMWAP,CMNET,GPRS,EDGE问题集合
      第  五  讲:查询android手机详细地址方法
      第  六  讲:android手机如何用CMWAP上网?
      第  七  讲:什么是APK文件?他和android手机是什么关系?
      第  八  讲:TF/MicroSD卡的SDHC标准与Class速度等级详解
      第  九  讲:如何安装APK文件到自己的android手机里?
      第  十  讲:如何卸载自己手机上的APK文件程序
      第 十一 讲:android手机添加删除桌面图标和插件,设置壁纸
      第 十二 讲:如何导入删除联系人
      第 十三 讲:android G1蓝牙与无线网络,APN接入点设置
      第 十四 讲:android手机如何设置个性短信和来电铃声
      第 十五 讲:教你如何使用任务管理器以及分享小窍门
      第 十六 讲:关于G1的GPS软件介绍及打开GPS模块方法
      第 十七 讲:将G1内的SIM卡联系人导入到GMAIL的联系人中
      第 十八 讲:android手机如何**信息
      第 十九 讲:手机彩信图片如何储存
      第 二十 讲:android手机连接PC后怎样切换为U 盘模式
      第二十一讲:几个小方法解决手机烧流量问题
      第二十二讲:android手机怎样通过蓝牙传输文件
      第二十三讲:如何清空android G1手机里的系统垃圾
      第二十四讲:轻轻松松为你的手机截图
      第二十五讲:怎样快速更新已安装的软件?
      第二十六讲:什么是SPL?与非SPL的区别
      第二十七讲:如何辨别你的android G1手机是否刷新SPL?



      具体内容


      第一讲:android专有名词介绍 不明白的进来

      1.  固件…………操作系统

      所谓的固件就是将操作系统固定在手机中的一个固定的位置(硬件上),平时不随意改动,活动的数据另外单独放一边。
      这样做的目的在于保护内层的系统程序不受文件操作的影响,提高了稳定性。
      但相对的,如果想要升级系统,就必然涉及对固件的读写更新工作。
      谷歌的Android系统就被写入在了固件中,即使想要查看也要专门的软件和权限。

      2.  刷固件…………安装(or 重装)操作系统

      正是因为固件固定的原因,在我们想要更新系统的时候。就需要将原固件的文件删掉,将新的文件拷进去。
      于是就有了刷固件这么一回事。
      就像Windows安装系统一样,把安装盘放进去就可以直接安装。
      A系统毕竟还没满一岁(从开始用算),还没有什么直接安装系统的软件。(以后肯定有!)
      所以让我们自己动手安装吧。

      3.  ROM(包)…………系统的安装盘

      这个东西就是A系统的打包。我们刷机的时候就是将这个东西刷进固件的。
      当然,由于A系统的开源性质,我们可以自由的改动这个包。
      涉及高端知识。回避这个问题。

      像经常提到的RC XX,G2 Rom,还有x.x.x的之类的东西,都是指的包的版本。
      就是被改动成各式各样的包。

      4.  固件版本

      由于是谷歌公司在专门开发这个系统,官方放出的话必然是所有G友手机升级的风向标。
      固件版本就是官方制定的基础系统,所有的改包都会从官方的包开始改。
      所以官方的一次升级必然会影响整个手机系统的使用情况。
      就像现在大家都在用XP,微软放出了一个补丁包,于是大家都去升级了。
      目前RC33的固件版本是1.4。。(有G友指出这个版本是1.1。。没有仔细研究)
      但Cupcake和G2ROM都是1.5的固件版本,有了很大的更新。

      5.  ROOT

      Root是一个你使用系统的权限,是最高的,有了root权限就可以更改内部系统的文件了。
      为了防止软件更改系统文件(病毒?),A系统隐藏了root权限,也就是一般情况下不能修改系统文件。
      但是。。我们要刷机。所以我们就要取得Root权限

      6.  JF自制固件

      JF自制固件是某达人改的固件的一个,主要特点就是它有自制的recovery恢复模式。可以刷写你自己的包
      我们就用JF的固件作为刷机的基础,开始无穷无尽的刷机(汗)。

      7.何为 Radio 包

      无线通信模块的驱动程序,ROM 是系统程序 Radio 负责网络通信,ROM 和 Radio 可以分开刷,互不影响。如果你的手机刷新了 ROM 后有通讯方面的问题可以刷新 RADIO 试一试。

      8.ROM 的分类

      一般分为两大类,一种是出自手机制造商官方的原版ROM,特点是稳定,功能上 随厂商定制而各有不同;另一种是开发爱好者利用 GOOLGE 官方发布的源代码自主编译的原生 ROM(如啊兴编译的安卓网友协作 ROM 、国外的 CM 系列等等),特点是根据用户具体需求进行调整,使 ROM 更符合不同地区用户的使用习惯,如 啊兴的安卓系列 ROM 就是专门针对中文用户制作,CM 系列的 ROM 则更加适合国外用户。

      另外还有一些热心网友自己进行美化或修改的 ROM ,一般都是基于原生 ROM 制作,也很受大家欢迎。

      9.ROM 的选择

      不同版本的 ROM 有不同的特点,稳定型,速度型,美观型,自己喜欢什么就选择什么,正所谓萝卜青菜各有所爱。
      需要注意的是,同学们在刷机之前,要认真地查阅 此版本 ROM 的注意事项,个别情况下会出现不同 ROM 之间互不兼容的现象。

      10.刷机方法的种类

      1. 恢复模式,用“update.zip”文件;
      2. 进入工程模式,刷新“.NBH”文件;
      3. 使用开发版的工程模式,进行 fastboot 刷机。


      第二讲:android是什么?android现有成员有哪些?

      一:Android是什么?

      android一词的本义指“机器人”,同时也是Google于2007年11月5日宣布的基于Linux平台的开源手机操作系统的名称, 该平台由操作系统、中间件、用户界面和应用软件组成,号称是首个为移动终端打造的真正开放和完整的移动软件。它包括操作系统、用户界面和应用程序 —— 移动电话工作所需的全部软件,而且不存在任何以往阻碍移动产业创新的专有权障碍。Google与开放手机联盟合作开发了 Android,这个联盟由包括中国移动、摩托罗拉、高通、宏达电和 T-Mobile 在内的 30 多家技术和无线应用的领军企业组成。Google通过与运营商、设备制造商、开发商和其他有关各方结成深层次的合作伙伴关系,希望借助建立标准化、开放式 的移动电话软件平台,在移动产业内形成一个开放式的生态系统。

      二:Android团队成员有哪些?

      Android平台的研发队伍阵容强大,包括Google、HTC(宏达电)、T-Mobile、高通、魅族、摩托罗拉、三星、 LG以 及中国移动在内的34家企业都将基于该平台开发手机的新型业务,应用之间的通用性和互联性将在最大程度上得到保持。“开放手机联盟”表示,Android 平台可以促使移动设备的创新,让用户体验到最优越的移动服务,同时,开发商也将得到一个新的开放级别,更方便的进行协同合作,从而保障新型移动设备的研发 速度。

      三:android在中国

      android在中国的前景十分广阔,首先是有成熟的消费者,在国内,android社区十分红火比如androidin.net,这些社区为 android在中国的普及做了很好的推广作用。国内厂商和运营商也纷纷加入了android阵营,包括魅族,中国移动,中国联通,华为通讯,联想等大企 业。我们公司不大,但也花费了一部分精力在android项目的研究上,包括项目思路android的发展前景等····

      第三讲:手把手教你给android手机设置WIFI无线网络

      大家都知道WIFI是一个好东西,简单的来说它就是可以免费再有热点的地方上网,搭载Android系统的G1内置了WiFi,通过WiFi你可以使用YouTube观看网上视频、使用Browser浏览网页、使用Market来下载免费软件、使用IM上MSN进行聊天、使用Gmail来收发邮件,使用……当然,还有很多很多,就不一一举例了。

      下面我们就来叫你如何给你的android手机设置WIFI无线网络

      1.设置WIFI前,首先你要去一个有WIFI热点的地,最平常的地方比如麦当劳、肯德基、星巴克等。
      在主菜单上找到设置一项

      本帖最后由 mycrown 于 2010-10-3 01:18 编辑
      【小白课堂】手把手教你给android手机设置WIFI无线网络
      大家都知道WIFI是一个好东西,简单的来说它就是可以免费再有热点的地方上网,搭载Android系统的G1内置了WiFi,通过WiFi你可以使用 YouTube观看网上视频、使用Browser浏览网页、使用Market来下载免费软件、使用IM上MSN进行聊天、使用Gmail来收发邮件,使 用……当然,还有很多很多,就不一一举例了。

      下面我们就来叫你如何给你的android手机设置WIFI无线网络
      1.设置WIFI前,首先你要去一个有WIFI热点的地,最平常的地方比如麦当劳、肯德基、星巴克等。
      在主菜单上找到设置一项


      Android, android, USA, Google, 应用软件 - Android新手入门基本知识

      然后点击进入设置,接着点击无线控件这一项

      Android, android, USA, Google, 应用软件 - Android新手入门基本知识

      进入无线控件你就会看到关于wifi设置 点击进入

      Android, android, USA, Google, 应用软件 - Android新手入门基本知识

      接着你打开网络 搜索你所在的地点的无线网络

      Android, android, USA, Google, 应用软件 - Android新手入门基本知识

      点击一无线网络进入 然后输入密码

      Android, android, USA, Google, 应用软件 - Android新手入门基本知识

      Android, android, USA, Google, 应用软件 - Android新手入门基本知识

      接着就是等待几十秒钟 然后它会提示你已连接 之后你可以点击浏览器 它就会直接上网了

      Android, android, USA, Google, 应用软件 - Android新手入门基本知识



      第四讲:关于CMWAP,CMNET,GPRS,EDGE问题集合

      1,我知道GPRS,那为啥我设置好的GPRS,G1上面图标显示个E,又看到某些教程说可以上EDGE,这和GPRS啥关系啊

      答:GPRS是通用分组无线业务(General Packet Radio Service)的简称,它是GSM移动电话用户可用的一种移动数据业务。 它经常被描述成“2.5G”,也就是说这项技术位于第二代(2G)和第三代(3G)移动通讯技术之间
      EDGE是英文Enhanced Data Rate for GSM Evolution 的缩写,即增强型数据速率GSM演进技术。EDGE是一种从GSM到3G的过渡技术,说的简单点就是,GPRS是一种上网方式,应用与2G的GSM网络上 的,我们叫2.5G,而EDGE其实也是通过GPRS上,不过就是加强版的GPRS,我们叫2.75GEDGE的速度比GPRS快,北京这里我平均能达到30多K每秒,峰值也达到过40K多。以上是本人亲自测试的。
      一句话:显示EDGE其实就是上了GPRS,而且是加强版的GPRS,速度更快。

      2,什么是CMNET,什么是CMWAP啊

      答:CMWAP和CMNET只是中国移动为其划分的两个GPRS接入方式。中国移动对CMWAP作了一定的限制,主要表现在CMWAP接入时只能访问 GPRS网络内的IP(10.*.*.*),而无法通过路由访问Internet,我们用CMWAP浏览Internet上的网页就是通过WAP网关协议 或它提供的HTTP代理服务实现的。 因此,只有满足以下两个条件的应用才能在中国移动的CMWAP接入方式下正常工作:
      1.应用程序的网络请求基于HTTP协议。
      2.应用程序支持HTTP代理协议或WAP网关协议。
      这也就是为什么我们的G1无法正常用CMWAP的原因。
      一句话:CMWAP是移动限制的,理论上只能上WAP网,而CMNET可以用GPRS浏览WWW。

      3,看那么多我头都大了,你痛快点,G1能不能上中国移动的GPRS

      答:G1的理念就是与网络的完美结合,主打就是上WWW网,而且也并没有行货,CMWAP这个概念只存在与中国移动,国外是没有这概念的所以,如果我们上GPRS,就要设置CMNET。
      一句话:用CMNET方式通过EDGE可以上网

      4,啊,那上CMNET,这资费,是不是比CMWAP贵啊?

      答:因为本人并没有周游世界,所以我也不太清楚其他地区的资费,还希望网友们可以多多提供当地资费政策,方便用户。但是我可以说北京的非包月用户的 CMNET和CMWAP是不区分的,资费一样,也就说假如我动感地带用户定了5元30M,那我用G1上CMNET就可以享受这个资费套餐。包月的是区分资 费的。
      一句话:根据当地运营商政策而定,可以电询当地10086

      5,哦,那我还想问,能不能接受彩信啊?

      答:根据之前写过的教程,有测试成功的,但是,依然有很多用户测试失败,而且即使接受成功了播放时间不能控制。这基本上还是因为CMWAP的原因。所以还是等待有没有更好的教程。
      一句话:按照教程再试试,不行的话,还是再等待下看看有没有更好的方法。

      第五讲:查询android手机详细地址方法

      通过该官方页面我们可以查询我们纤细的手机信息,对买家有很大帮助

      EU:就是欧盟/German:德国/T Mobile (UK):英国;

      Pure Black:全黑/Pure White:全白;

      w/oSIM:表示有无锁卡;

      按键排序:QWERTZ(键位倒置ZY)/QWERTY(键位正常);

      HSUPA :enable(支持)

      第六讲:android手机如何用CMWAP上网?

      在 设置-无线控件-移动网络设置-接入点名称-中国移动WAP设置下
      按Menu -New APN
      按照下面格式填入

      name cmwap
      apn cmwap
      proxy 10.0.0.172
      port 80
      username 空
      pasword 空
      mmsc http://mmsc.monternet.com
      mms proxy 010.000.000.172
      port80

      如图:

      Android, android, USA, Google, 应用软件 - Android新手入门基本知识

      Android, android, USA, Google, 应用软件 - Android新手入门基本知识

      Android, android, USA, Google, 应用软件 - Android新手入门基本知识

      第七讲:什么是APK文件?他和android手机是什么关系?


      APK是Android Package的缩写,即Android安装包。APK是类似Symbian Sis或Sisx的文件格式。通过将APK文件直接传到Android模拟器或Android手机中执行即可安装。

      APK文件其实是zip格式,但后缀名被修改为apk,通过UnZip解压后,可以看到Dex文件,Dex是Dalvik VM executes的全称,即Android Dalvik执行程序,并非Java ME的字节码而是Dalvik字节码。

      一个APK文件结构为:
      mete-INF\   Jar文件中常可以看到
      res\ 存放资源文件的目录
      AndroidManifest.xml  程序全局配置文件
      classes.dex Dalvik字节码
      resources.arsc 编译后的二进制资源文件

      总结下我们发现Android在运行一个程序时首先需要UnZip,然后类似Symbian那样直接,和Windows Mobile中的PE文件有区别,这样做对于程序的保密性和可靠性不是很高,通过dexdump命令可以反编译,但这样做符合发展规律,微软的 Windows Gadgets或者说WPF也采用了这种构架方式。

      在Android平台中dalvik vm的执行文件被打包为apk格式,最终运行时加载器会解压然后获取编译后的androidmanifest.xml文件中的permission分支相 关的安全访问,但仍然存在很多安全限制,如果你将apk文件传到/system/app文件夹下会发现执行是不受限制的。最终我们平时安装的文件可能不是 这个文件夹,而在android rom中系统的apk文件默认会放入这个文件夹,它们拥有着root权限。

      第八讲:TF/MicroSD卡的SDHC标准与Class速度等级详解

      什么是SDHC标准?


      Android, android, USA, Google, 应用软件 - Android新手入门基本知识

      SDHC是“High Capacity SD Memory Card”的缩写,即“高容量SD存储卡”。2006年5月SD协会发布了最新版的SD 2.0的系统规范,在其中规定SDHC是符合新的规范、且容量大于2GB小于等于32GB的SD卡。

      SDHC最大的特点就是高容量(2GB-32GB)。另外,SD协会规定SDHC必须采用FAT32 文件系统,这是因为之前在SD卡中使用的FAT16文件系统所支持的最大容量为2GB,并不能满足SDHC的要求。

      SDHC标志如下图:

      Android, android, USA, Google, 应用软件 - Android新手入门基本知识

      所有大于2G容量的SD卡必须符合SDHC规范,规范中指出SDHC至少需符合Class 2的速度等级,并且在卡片上必须有SDHC标志和速度等级标志。

      Class传输等级:

      Class 0:包括低于Class 2和未标注Speed Class的情况;
      Class 2:能满足观看普通MPEG4 MPEG2 的电影、SDTV、数码摄像机拍摄
      Class 4:可以流畅播放高清电视(HDTV),数码相机连拍等需求;
      Class 6:满足单反相机连拍和专业设备的使用要求;

      购买总结:

      1、市场上常见的2GB以下存储卡(含2GB)都不属于SDHC卡,其等级为Class0.
      2、并不是所有的4GB卡都是Class4,也有Class2的,不同品牌和规格也是有分类和区别的.
      3、4GB Class4还是可以值得考虑的.
      4、认清TF卡上SDHC以及Class等级标识.根据自己的实际需要购卡.不要盲目追求高速度和高容量.目前市场上常用的还是以Kingston,Sandisk,Kingmax为主,各品牌之间存在一定的差异.电脑测试并不完全体现存储卡的性质和特点.卡是要装到手机里的,你电脑里飞速20+,在手机里运行也许只有2+,选卡要选择适合自己需要的,装几首MP3非赶个潮流装个8GB,到时候受折磨的不仅仅是自己,影响更重的是手机的CPU,还有电池的续航时间,要知道,卡的容量越大,会导致文件的寻址时间增加,电池就是这样被消耗和损伤的!

      第九讲:如何安装APK文件到自己的android手机里?

      很多朋友刚拿到G1的时候大概首先就是要往里面装软件了,在ANDROID平台下安装文件的后缀名为“.apk”,就好像PC上的安装文件的后缀名为“.exe”塞班平台安装文件的后缀名为“.sis”一样,所以我们一般称ANDROID平台下安装文件为“APK”。

      手机上首先要进行一些设置,设置——应用程序——勾选“未知源”
      设置——应用程序——开发——勾选“USB 调试”

      方法一
      运用APK安装器,就是将APK文件安装到手机上的一个PC端软件,我们首先需要下载一个USB驱动,在PC上安装好“APK安装器”,这 个软件会自动关联你的APK程序,只要双击一下APK程序就可以自动安装到你的手机里了。    点击下载APK安装器(HiAPK Installer)1.0版正式发

      大家可能问 我们的手机和电脑连不上怎么办?那说明你没有安装驱动?驱动在哪下?其实不用下驱动 在这里我们推荐一个软件 就是91助理FOR android系统的。

      你把你的android手机和电脑连接后打开91 for android手机助理 然后他就会自动帮你下载驱动 同时会连接手机。

      Android, android, USA, Google, 应用软件 - Android新手入门基本知识


      Android, android, USA, Google, 应用软件 - Android新手入门基本知识

      界面上显示我的G1和电脑已连接

      方法二

      大家需要先下载一个资源管理器软件,可以通过网络在 MARKET 中下载一个资源管理器,这里推荐 ASTRO。你也可以直接下载
      过方法一把资源管理器安装好,之后再安装软件只要把 “.APK”的安装文件拷贝到 SD 卡里,之后使用资源管理器软件在SD卡下打开 “.APK”的安装文件,直接安装即可.

      然后把在电脑上下载好了的APK文件 丢到电脑上SD移动设备 Android, android, USA, Google, 应用软件 - Android新手入门基本知识

      然后从手机的资源管理器上找到该文件 安装就行

      方法三

      设置——应用程序——APK安装器
      只要把APK程序都放到SD卡上,就可以直接在这个内置的APK安装器上进行软件的安装与卸载。

      Android, android, USA, Google, 应用软件 - Android新手入门基本知识

      选择应用程序

      Android, android, USA, Google, 应用软件 - Android新手入门基本知识

      点击APK安装器

      Android, android, USA, Google, 应用软件 - Android新手入门基本知识

      然后他就会自动扫描SDCARD上的文件

      Android, android, USA, Google, 应用软件 - Android新手入门基本知识

      显示该文件

      Android, android, USA, Google, 应用软件 - Android新手入门基本知识

      点击安装即可 然后再从你的菜单里面找到该文件 安装成功!

      第十讲:如何卸载自己手机上的APK文件程序


      其实方法很简单,如果你刷新简体版系统的话就更容易找到删除的方法了。

      桌面状态下按下“MENU”——“设置”——“应用程序”——“管理应用程序”——找到你要删除的应用程序,点击卸载。

      Android, android, USA, Google, 应用软件 - Android新手入门基本知识

      点击应用程序进入下一级菜单

      Android, android, USA, Google, 应用软件 - Android新手入门基本知识

      然后点击管理应用程序

      Android, android, USA, Google, 应用软件 - Android新手入门基本知识

      找到你想卸载的应用程序

      Android, android, USA, Google, 应用软件 - Android新手入门基本知识

      点击卸载

      Android, android, USA, Google, 应用软件 - Android新手入门基本知识

      再按确定就OK了

      卸载其实就是这么简单!

      [转载]新浪微博布局学习——妙用Android组件TabHost

      mikel阅读(1020)

      [转载]新浪微博布局学习——妙用TabHost – 农民伯伯 – 博客园.

      前言

      为了更好的开发Android应用程序,除了熟练掌握基本的UI组件和API外,还需要掌握一些技巧,而这些技巧可以通过阅读一些代码来提高,本系列将与大家分享一些新浪微博布局方面的收获,欢迎交流!

      声明

      欢迎转载,但请保留文章原始出处:)

      博客园:http://www.cnblogs.com

      农民伯伯: http://www.cnblogs.com/over140

      版本

      新浪微博 weibo_10235010.apk

      正文

      一、效果图

      红色部分是本文要实现的目标。

      二、实现

      maintabs.xml

      <?xml version=”1.0″ encoding=”UTF-8″?>
      <TabHost Android:id=”@android:id/tabhost” android:layout_width=”fill_parent” android:layout_height=”fill_parent”
      xmlns:android
      =”http://schemas.android.com/apk/res/android”>
      <LinearLayout android:orientation=”vertical” android:layout_width=”fill_parent” android:layout_height=”fill_parent”>
      <FrameLayout android:id=”@android:id/tabcontent” android:layout_width=”fill_parent” android:layout_height=”0.0dip” android:layout_weight=”1.0″ />
      <TabWidget android:id=”@android:id/tabs” android:visibility=”gone” android:layout_width=”fill_parent” android:layout_height=”wrap_content” android:layout_weight=”0.0″ />
      <RadioGroup android:gravity=”center_vertical” android:layout_gravity=”bottom” android:orientation=”horizontal” android:id=”@id/main_radio” android:background=”@drawable/maintab_toolbar_bg” android:layout_width=”fill_parent” android:layout_height=”wrap_content”>
      <RadioButton android:text=”@string/main_home” android:checked=”true” android:id=”@+id/radio_button0″ android:layout_marginTop=”2.0dip” android:drawableTop=”@drawable/icon_1_n” style=”@style/main_tab_bottom” />
      <RadioButton android:id=”@+id/radio_button1″ android:layout_marginTop=”2.0dip” android:text=”@string/main_news” android:drawableTop=”@drawable/icon_2_n” style=”@style/main_tab_bottom” />
      <RadioButton android:id=”@+id/radio_button2″ android:layout_marginTop=”2.0dip” android:text=”@string/main_my_info” android:drawableTop=”@drawable/icon_3_n” style=”@style/main_tab_bottom” />
      <RadioButton android:id=”@+id/radio_button3″ android:layout_marginTop=”2.0dip” android:text=”@string/menu_search” android:drawableTop=”@drawable/icon_4_n” style=”@style/main_tab_bottom” />
      <RadioButton android:id=”@+id/radio_button4″ android:layout_marginTop=”2.0dip” android:text=”@string/more” android:drawableTop=”@drawable/icon_5_n” style=”@style/main_tab_bottom” />
      </RadioGroup>
      </LinearLayout>
      </TabHost>

      styles.xml

      <style name=”main_tab_bottom”>
      <item name=”android:textSize”>@dimen/bottom_tab_font_size</item>
      <item name=”android:textColor”>#ffffffff</item>
      <item name=”android:ellipsize”>marquee</item>
      <item name=”android:gravity”>center_horizontal</item>
      <item name=”android:background”>@drawable/home_btn_bg</item>
      <item name=”android:paddingTop”>@dimen/bottom_tab_padding_up</item>
      <item name=”android:layout_width”>fill_parent</item>
      <item name=”android:layout_height”>wrap_content</item>
      <item name=”android:button”>@null</item>
      <item name=”android:singleLine”>true</item>
      <item name=”android:drawablePadding”>@dimen/bottom_tab_padding_drawable</item>
      <item name=”android:layout_weight”>1.0</item>
      </style>

      home_btn_bg.xml

      <selector
      xmlns:android=”http://schemas.android.com/apk/res/android”>
      <item android:state_focused=”true” android:state_enabled=”true” android:state_pressed=”false” android:drawable=”@drawable/home_btn_bg_s” />
      <item android:state_enabled=”true” android:state_pressed=”true” android:drawable=”@drawable/home_btn_bg_s” />
      <item android:state_enabled=”true” android:state_checked=”true” android:drawable=”@drawable/home_btn_bg_d” />
      <item android:drawable=”@drawable/transparent” />
      </selector>

      代码说明:

      1.  需要注意的是他这里把TabWidget的Visibility设置成了gone!也就是默认难看的风格不见了:,取而代之的是5个带风格的单选按钮.

      2.  注意为单选按钮设置的style,其中最重要的是为其background设置了home_btn_bg.xml,也就是自定义了选中效果。

      Java文件

      public class MainTabActivity extends TabActivity implements
      OnCheckedChangeListener {

      private TabHost mHost;
      private Intent mMBlogIntent;
      private Intent mMoreIntent;
      private Intent mInfoIntent;
      private Intent mSearchIntent;
      private Intent mUserInfoIntent;

      @Override
      protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      requestWindowFeature(Window.FEATURE_NO_TITLE);
      setContentView(R.layout.maintabs);

      // ~~~~~~~~~~~~ 初始化
      this.mMBlogIntent = new Intent(this, HomeListActivity.class);
      this.mSearchIntent = new Intent(this, SearchSquareActivity.class);
      this.mInfoIntent = new Intent(this, MessageGroup.class);
      this.mUserInfoIntent = new Intent(this, MyInfoActivity.class);
      this.mMoreIntent = new Intent(this, MoreItemsActivity.class);

      initRadios();

      setupIntent();
      }

      /**
      * 初始化底部按钮
      */
      private void initRadios() {
      ((RadioButton) findViewById(R.id.radio_button0)).setOnCheckedChangeListener(
      this);
      ((RadioButton) findViewById(R.id.radio_button1)).setOnCheckedChangeListener(
      this);
      ((RadioButton) findViewById(R.id.radio_button2)).setOnCheckedChangeListener(
      this);
      ((RadioButton) findViewById(R.id.radio_button3)).setOnCheckedChangeListener(
      this);
      ((RadioButton) findViewById(R.id.radio_button4)).setOnCheckedChangeListener(
      this);
      }

      /**
      * 切换模块
      */
      @Override
      public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
      if (isChecked) {
      switch (buttonView.getId()) {
      case R.id.radio_button0:
      this.mHost.setCurrentTabByTag(mblog_tab);
      break;
      case R.id.radio_button1:
      this.mHost.setCurrentTabByTag(message_tab);
      break;
      case R.id.radio_button2:
      this.mHost.setCurrentTabByTag(userinfo_tab);
      break;
      case R.id.radio_button3:
      this.mHost.setCurrentTabByTag(search_tab);
      break;
      case R.id.radio_button4:
      this.mHost.setCurrentTabByTag(more_tab);
      break;
      }
      }
      }

      private void setupIntent() {
      this.mHost = getTabHost();
      TabHost localTabHost
      = this.mHost;

      localTabHost.addTab(buildTabSpec(mblog_tab, R.string.main_home,
      R.drawable.icon_1_n,
      this.mMBlogIntent));

      localTabHost.addTab(buildTabSpec(message_tab, R.string.main_news,
      R.drawable.icon_2_n,
      this.mInfoIntent));

      localTabHost.addTab(buildTabSpec(userinfo_tab, R.string.main_my_info,
      R.drawable.icon_3_n,
      this.mUserInfoIntent));

      localTabHost.addTab(buildTabSpec(search_tab, R.string.menu_search,
      R.drawable.icon_4_n,
      this.mSearchIntent));

      localTabHost.addTab(buildTabSpec(more_tab, R.string.more,
      R.drawable.icon_5_n,
      this.mMoreIntent));

      }

      private TabHost.TabSpec buildTabSpec(String tag, int resLabel, int resIcon,
      final Intent content) {
      return this.mHost
      .newTabSpec(tag)
      .setIndicator(getString(resLabel),
      getResources().getDrawable(resIcon))
      .setContent(content);
      }

      代码说明

      1.  由于TabWidget被隐藏,所以相关的事件也会无效,这里取巧用RadioGroup与RadioButton的特性来处理切换,然后监听事件调用setCurrentTabByTag来切换Activity。

      2.  注意即使TabWidget被隐藏,也要为其设置indicator,否则会保持。

      三、总结

      在这之前如果要做这种效果我恐怕第一时间就会想到用ActivityGroup来做,主要是因为TabHost的TabWidget非常难看,用起 来也不方便。其实从源码可以看出,TabHost也是继承自ActivityGroup,这里结合了单选按钮和TabHost,各取其长,有时间可以专门 写一个这样的自定义控件:)

      四、相关文章

      [Android]使用ActivityGroup来切换Activity和Layout

      结束

      本文中使用的资源均反编译自apk文件,这里主要是讲思路,欢迎大家交流。

      [转载]ASP.NET MVC中的Json Binding和Validate

      mikel阅读(1313)

      [转载]ASP.NET MVC中的Json Binding和Validate – 迭戈 – 博客园.

      引子:电子商务网站支付功能页面往往会有很多信息,对于这些信息的保存,往往是分步完成的,那么使用Ajax最合适不过了,比如其中的收货人信息模块。这些信息的新建和编辑保存都是用Ajax来完成的。那么有几种方式完成这个操作呢,我想到如下几种。

      先来看看该功能的截图:

      一般情况下这些信息会对应一个实体类,就命名为:ReceiverInfo,简单起见,我定义ReceiverInfo如下:


      1、将需要的值拼接成json文本,再Action里面处理

      首先您需要将要保存的值拼接成一个json文本,类似:

      var test = "{ ReceiverId: 5, ReceiverName: 'will', Sex: 'F', CreateDate: '2011-02-21' }";

      然后用JQuery保存到数据库,代码如下:

      $.ajax({ url: "/Home/test1", type: "post", cache: false, data: test });

      然后您在Action里面这样操作:

      StreamReader reader = new StreamReader(Request.InputStream); string bodyText = reader.ReadToEnd(); JavaScriptSerializer js = new JavaScriptSerializer(); ReceiverInfo receiver = js.Deserialize<ReceiverInfo>(bodyText); //保存。。。


      2、利用自定义的ModelBinder实现

      JsonBinder

      1 public class JsonBinder<T> : IModelBinder 2 { 3 public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) 4 { 5 StreamReader reader = new StreamReader(controllerContext.HttpContext.Request.InputStream); 6 string json = reader.ReadToEnd(); 7 8 if (string.IsNullOrEmpty(json)) 9 return json; 10 11 JavaScriptSerializer serializer = new JavaScriptSerializer(); 12 object jsonData = serializer.DeserializeObject(json); 13 return serializer.Deserialize<T>(json); 14 } 15 }

      我们继承IModelBinder接口,实现其 方法:

      public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)

      即可。我们可以在Action里面这样使用:

      public ActionResult Test1([ModelBinder(typeof(JsonBinder<ReceiverInfo>))] ReceiverInfo receiverInfo)

      这样我们自定义的 IModelBinder就会取代DefaultModelBinder完成数据绑定。


      3、直接传递一个Json对象

      上面两种方法并没有利用MVC的System.ComponentModel.DataAnnotations进行有效的数据验证。您可能需要自己手动验证,无疑增加了工作量。

      我们试试这种方式。

      前端的写法:

      var b = { ReceiverId: 5, ReceiverName: "will", Sex: "F", CreateDate: "2011-02-21" }; $.ajax({ url: "/Home/test1", type: "post", cache: false, data: b, success: function(data) { alert(data.message); }, error: function(xhr, a, b) { alert(xhr.responseText); } });

      Action的写法:

      public ActionResult Test1(ReceiverInfo receiverInfo)

      我们能正常的得到绑定后的数据。而且我们还能利用System.ComponentModel.DataAnnotations进行数据验证。我们为ReceiverInfo做如下改动:

      [System.ComponentModel.DataAnnotations.Required(ErrorMessage = "收货人必须填写")] public string ReceiverName { get; set; }

      并在前端为ReceiverName赋值为空字符串,再次执行,得到提示:

      很好,不过我们有新的要求了,那就是传递更复杂的对象,比如对象套嵌对象,对象有集合属性,这种方式不能胜任了。


      4、利用MvcFutures的JsonValueProviderFactory

      每一版的MVC都有一个MvcFutures,里面会有一些额外的功能,这些功能有些会加入下一个版本中,而这些功能在某些时候很有用处。我查看了 里面的类,发现有一个类JsonValueProviderFactory正是处理复杂对象的提交和数据验证。由于json对象需要特定解析才能使用默认 的DefaultModelBinder,而这个解析过程需要在ValueProvider阶段完成,所以需要实现特定的ValueProvider给 DefaultModelBinder。我们需要实现一个ValueProviderFactory和IValueProvider,而MVC里面的 DictionaryValueProvider<TValue>(继承了IValueProvider)已经足够使用了,所以只需要继承 ValueProviderFactory实现其方法:public override IValueProvider GetValueProvider(ControllerContext controllerContext)即可,具体代码您可以看JsonValueProviderFactory。

      我们定义另一个类:

      ReceiverInfoChild

      public class ReceiverInfoChild { [System.ComponentModel.DataAnnotations.Required(ErrorMessage = "ChildId必须填写")] public string ChildId { get; set; } }

      并为类ReceiverInfo增加一个属性public List<ReceiverInfoChild> ReceiverInfoChild { get; set; }我们把JsonValueProviderFactory拿出来放在项目里面,然后在Global.asax里面注册一下,就可以使用了。

      protected void Application_Start() { AreaRegistration.RegisterAllAreas(); RegisterRoutes(RouteTable.Routes); ValueProviderFactories.Factories.Add(new JsonValueProviderFactory()); }

      因为JsonValueProviderFactory中有:if (!controllerContext.HttpContext.Request.ContentType.StartsWith(“application/json”, StringComparison.OrdinalIgnoreCase))来判断进来的请求是不是json对象,所以我们提交数据的时候需要这样写:

      var ReceiverInfo = [ { ReceiverInfoChild: [{ ChildId: "1" }, { ChildId: "11"}], ReceiverId: 5, ReceiverName: "will", Sex: "F", CreateDate: "2011-02-21" }, { ReceiverInfoChild: [{ ChildId: "2" }, { ChildId: "22"}], ReceiverId: 5, ReceiverName: "will", Sex: "F", CreateDate: "2011-02-21" } ]; $.ajax({ url: "/Home/test1", type: "post", cache: false, contentType: "application/json;charset=utf-8", data: JSON.stringify(ReceiverInfo), success: function(data) { alert(data.message); }, error: function(xhr, a, b) { alert(xhr.responseText); } });

      其中JSON.stringify(ReceiverInfo)是将json对象转换成字符串,您可以到这里下载该类库。

      在Action里面,我们这样写就可以了:

      public ActionResult Test1(List<ReceiverInfo> receiverInfo)

      看一下调试的结果:

      完全正常绑定了值。我们再看看数据验证:


      至此,我们实验了四种方案:

      第一种方案,最麻烦,而且容易出错(可能跟我个人不喜欢拼接字符串有关系);

      第二种方案,有一定的通用性,但是不利于数据验证;

      第三种方案,通用,可以进行有效的数据验证,应对一般的需求够用了,但是处理更复杂的对象不行;

      第四种方案,几乎可以处理我们遇到的所有情况

      另外,这是在ASP.NET MVC2中的使用,到了ASP.NET MVC3,微软已经把JsonValueProviderFactory作为内置的功能了。

      [转载]关于Android系统微博服务端API的OAuth认证实现

      mikel阅读(1613)

      [转载]关于微博服务端API的OAuth认证实现 – 遇见未知的自己 – 博客园.

      新浪微博跟update相关的api已经挂了很多天了一直没有恢复正常,返回错误:40070 Error limited application access api!,新浪开放平台的论坛里n多的人都在等这个恢复,新浪官方也相当的恶心出问题了连个公告都没有,既不说什么原因又不说什么时候能恢复,。还是有版主说是api正在升级礼拜1恢复正常今天都礼拜2了还是不行。基于这个原因我的Android版的新浪微博客户端已经停工好几天了,刚好是跟update相关的一些功能。

      客户端开发不成了,就自己做做服务端程序,提供类似新浪微博rest api服务, api其实说简单也很简单了,无法是通过链接对外提供json或者xml格式的数据和接收外部提供的数据进去相应的存储、删除、更新等操作。过程中碰到的最麻烦的问题就是OAuth认证功能了,在做Android版的新浪微博客户端时候也花了蛮长的时间对OAuth认证进行研究,在客户端原先是采用oauth-signpost开源项目,后来由于某些原因就放弃了这个开源类库,自己重新写了OAuth认证部分的实现, 现在做服务端的OAuth认证,其实有过做客户端的经验做服务端也差不多,简单的说无非是客户端对参数字符串进行签名然后把签名值传输到服务端,服务端也对同样对参数字符串进行签名,把从客户端传过来的签名值进去比较,简单的说就这么个过程,具体实现肯定比这个要复杂多了,不明真相的同学可以google一下OAuth进行深入的学习研究了。

      服务端程序用ASP.NETC#编写了而非java,理由很简单本人对.net更加熟悉。由于想快速的实现效果采用了oauth-dot-net开源项目并没有全部自己写。

      一、首先新建名为Rest Api的ASP.NET Web应用程序,然后添加 oauth-dot-net开源项目相关的几个dll(Castle.Core.dll、 Castle.MicroKernel.dll、Castle.Windsor.dll、 CommonServiceLocator.WindsorAdapter.dll、 Microsoft.Practices.ServiceLocation.dll、OAuth.Net.Common.dll、 OAuth.Net.Components.dll、OAuth.Net.ServiceProvider.dll)。

      二、在Web.config文件里添加相应的配置,具体可以参考OAuth.Net.Examples.EchoServiceProvider项目,然后在Global.asax.cs添加如下代码:

      public override void Init()
      {
      IServiceLocator injector
      =
      new WindsorServiceLocator(
      new WindsorContainer(
      new XmlInterpreter(
      new ConfigResource(oauth.net.components))));

      ServiceLocator.SetLocatorProvider(() => injector);

      }

      接下来是比较重要,就是request_token、authorize、access_token的实现,OAuth认证实现的几个过程,不理解可以看android开发我的新浪微博客户端-OAuth篇(2.1) ,具体代码实现很多是参考OAuth.Net.Examples.EchoServiceProvider示例项目。

      三、 首先新建ConsumerStore.cs类,用来存储Consumer信息,由于测试项目所以存储在内存中并没有考虑保存到数据库,真实项目的时候请把相应的Consumer信息保存到数据库中。Consumer信息对应新浪微博其实就是应用的App Key和App Secret,当开发者在新浪微博建一个新的应用获取App Key和App Secret,所以完整的应该还需要一个开发一个提供给第三方开发者申请获取App Key和App Secret的功能页面,这里就不具体实现,直接在代码里写死了一个名为测试应用的Consumer,App Key:2433927322,App Secret:87f042c9e8183cbde0f005a00db1529f,这个提供给客户端测试用。 具体代码如下:

      public sealed class ConsumerStore : InMemoryConsumerStore, IConsumerStore
      {
      internal static readonly IConsumer FixedConsumer = new OAuthConsumer(2433927322, 87f042c9e8183cbde0f005a00db1529f, 测试应用, ConsumerStatus.Valid);

      public ConsumerStore()
      {
      this.ConsumerDictionary.Add(
      ConsumerStore.FixedConsumer.Key,
      ConsumerStore.FixedConsumer);
      }

      public override bool Add(IConsumer consumer)
      {
      throw new NotSupportedException(Consumers cannot be added to this store–it is fixed.);
      }

      public override bool Contains(string consumerKey)
      {
      return ConsumerStore.FixedConsumer.Key.Equals(consumerKey);
      }

      public override bool Update(IConsumer consumer)
      {
      throw new NotSupportedException(Consumers cannot be updated in this store–it is fixed.);
      }

      public override bool Remove(IConsumer consumer)
      {
      throw new NotSupportedException(Consumers cannot be removed from this store–it is fixed.);
      }

      }

      四、接下来就是request_token功能,新建 RequestTokenHandler.cs ,这个是OAuth.Net.ServiceProvider.RequestTokenHandler 子类,并且是httpHandlers所以需要在Web.config中添加httpHandlers配置,这个用来接收客户端程序的请求,返回给客户端 程序Request Token和Request Secret用,具体代码如下:

      public sealed class RequestTokenHandler : OAuth.Net.ServiceProvider.RequestTokenHandler
      {
      protected override void IssueRequestToken(HttpContext httpContext, OAuthRequestContext requestContext)
      {
      //产生RequestToken
      IRequestToken token = this.GenerateRequestToken(httpContext, requestContext);

      requestContext.RequestToken = token;
      Uri callbackUri;
      if (Uri.TryCreate(requestContext.Parameters.Callback, UriKind.Absolute, out callbackUri))
      {
      if (!ServiceProviderContext.CallbackStore.ContainsCallback(token))
      {
      //保存Callback地址了
      ServiceProviderContext.CallbackStore.AddCallback(token, callbackUri);
      }
      }
      else
      OAuthRequestException.ThrowParametersRejected(
      new string[] { Constants.CallbackParameter }, Not a valid Uri.);

      //把token.Token和token.Secret输出到客户端,
      requestContext.ResponseParameters[Constants.TokenParameter] = token.Token;
      requestContext.ResponseParameters[Constants.TokenSecretParameter]
      = token.Secret;
      }

      protected override IRequestToken GenerateRequestToken(HttpContext httpContext, OAuthRequestContext requestContext)
      {

      return ServiceProviderContext.TokenGenerator.CreateRequestToken(requestContext.Consumer, requestContext.Parameters);
      }

      }

      五、 接着是authorize功能,新建名为 authorize.aspx的页面,用来给用户输入账号和密码进行授权的页面,这个页面很简单具体如下图,在这个页面中获取用户输入的账户和密码跟数据 库中存储的用户账号和密码进行验证,如果验证通过返回之前客户端提供的callback地址,并且给这个地址添加一个校验码,具体代码如下:

      public partial class authorize : System.Web.UI.Page
      {
      protected void Page_Load(object sender, EventArgs e)
      {

      }

      protected void Button1_Click(object sender, EventArgs e)
      {
      if (loginName.Text == test && password.Text == 123)
      {
      string toke = Request.Params[oauth_token];
      IRequestToken tk
      = ServiceProviderContext.TokenStore.GetRequestToken(toke);
      Uri callback
      = ServiceProviderContext.CallbackStore.GetCalback(tk);
      string oauth_verifier = ServiceProviderContext.VerificationProvider.Generate(tk);
      Response.Redirect(callback.ToString()
      + ?oauth_verifier= + oauth_verifier);
      }

      }

      }

      六、接下来就是access_token功能,新建AccessTokenHandler.cs , 这个是OAuth.Net.ServiceProvider.AccessTokenHandler子类,并且是httpHandlers所以需要在Web.config中添加httpHandlers配置,这个用来接收客户端程序的请求,返回给客户端程序Access Token和Access Secret用,具体代码如下:

      public sealed class AccessTokenHandler : OAuth.Net.ServiceProvider.AccessTokenHandler
      {
      protected override void IssueAccessToken(HttpContext httpContext, OAuthRequestContext requestContext)
      {
      //产生access token
      IAccessToken accessToken = this.GenerateAccessToken(httpContext, requestContext);

      accessToken.Status = TokenStatus.Authorized;

      // 把accessToken和accessSecret输出到客户端,
      requestContext.ResponseParameters[Constants.TokenParameter] = accessToken.Token;
      requestContext.ResponseParameters[Constants.TokenSecretParameter]
      = accessToken.Secret;
      }

      protected override IAccessToken GenerateAccessToken(HttpContext httpContext,  OAuthRequestContext requestContext)
      {
      return ServiceProviderContext.TokenGenerator.CreateAccessToken(requestContext.Consumer, requestContext.RequestToken);
      }
      }

      public class TokenGenerator : ITokenGenerator
      {
      internal static readonly IRequestToken FixedRequestToken = new OAuthRequestToken(requestkey,
      requestsecret,
      ConsumerStore.FixedConsumer,
      TokenStatus.Authorized,
      null,
      ServiceProviderContext.DummyIdentity,
      new string[] { });

      internal static readonly IAccessToken FixedAccessToken = new OAuthAccessToken(
      accesskey,
      accesssecret,
      ConsumerStore.FixedConsumer,
      TokenStatus.Authorized,
      TokenGenerator.FixedRequestToken);

      public IRequestToken CreateRequestToken(IConsumer consumer, OAuthParameters parameters)
      {
      return TokenGenerator.FixedRequestToken;
      }

      public IAccessToken CreateAccessToken(IConsumer consumer, IRequestToken requestToken)
      {
      return TokenGenerator.FixedAccessToken;
      }
      }

      public class TokenStore : InMemoryTokenStore, ITokenStore
      {
      public TokenStore()
      {
      this.RequestTokenDictionary.Add(
      TokenGenerator.FixedRequestToken.Token,
      TokenGenerator.FixedRequestToken);

      this.AccessTokenDictionary.Add(
      TokenGenerator.FixedAccessToken.Token,
      TokenGenerator.FixedAccessToken);
      }

      public override bool Add(IRequestToken token)
      {
      throw new NotSupportedException(Tokens cannot be added to the token store–it is fixed.);
      }

      public override bool Add(IAccessToken token)
      {
      throw new NotSupportedException(Tokens cannot be added to the token store–it is fixed.);
      }

      public override bool Update(IRequestToken token)
      {
      throw new NotSupportedException(Tokens cannot be updated in the token store–it is fixed.);
      }

      public override bool Update(IAccessToken token)
      {
      throw new NotSupportedException(Tokens cannot be updated in the token store–it is fixed.);
      }

      public override bool Remove(IRequestToken token)
      {
      throw new NotSupportedException(Tokens cannot be removed from the token store–it is fixed.);
      }

      public override bool Remove(IAccessToken token)
      {
      throw new NotSupportedException(Tokens cannot be removed from the token store–it is fixed.);
      }

      }

      这样就完成了一个最最简单小型的服务端OAuth认证,然后用android客户端进行测试ok通过。

      注意点:

      一、android模拟器访问本地服务地址为10.0.2.2,比如http://localhost:3423/authorize.aspx在模拟器中用http://10.0.2.2:3423/authorize.aspx。

      二、OAuth.Net类库的OAuth.Net.Common项目中的interface ICallbackStore 添加了一个Uri GetCalback(IRequestToken token);并且在具体的实现类InMemoryCallbackStore添加了实习代码:

      public Uri GetCalback(IRequestToken token)

      {
      lock (this.callbackStore)
      {
      if (this.callbackStore.ContainsKey(token))
      {
      return this.callbackStore[token];
      }
      else
      {
      return null;
      }
      }
      }

      三、为了能用我前面做的给新浪用的android客户端,对于类库源代码AccessTokenHandler的ParseParameters方法做了如下修改,因为新浪请求api的时候都会加一个source的参数:

      protected virtual void ParseParameters(HttpContext httpContext, OAuthRequestContext requestContext)

      {
      …….
      parameters.AllowOnly(
      Constants.ConsumerKeyParameter,
      Constants.TokenParameter,
      Constants.SignatureMethodParameter,
      Constants.SignatureParameter,
      Constants.TimestampParameter,
      Constants.NonceParameter,
      Constants.VerifierParameter,
      Constants.VersionParameter, // (optional)
      Constants.RealmParameter, // (optional)
      “source”);
      ……
      }

      [转载]百度地图API建立全国银行位置查询系统(三)——如何在地图上添加标注

      mikel阅读(1098)

      [转载]【百度地图API】建立全国银行位置查询系统(三)——如何在地图上添加标注 – 酸奶小妹 – 博客园.

      <摘要>你将在第三章中学会以下知识:

      1. 如何在地图上添加带银行logo的标注?(你也可以换成商场logo,酒店logo等)
      2. 如何在标注上显示信息窗口,以及添加文字标签等其他覆盖物;
      3. 最后,介绍一个获取坐标的给力工具。

      —————————————————————————————————————–

      一、如何添加标注、标签和信息窗口?

      首先,我们需要创建一个点坐标,利用该点坐标来创建一个标注(Maker),最后将该标注显示在地图上。

      比如我们了解到国家博物馆的坐标是116.407804,39.912123(如何获取这个坐标,请查看下文中的坐标拾取工具)。

      所以创建这个点,再创建一个标注在这个点上。最关键的一步就是将它显示出来,显示覆盖物用addOverlay。

      var pointMarker = new BMap.Point(116.407804,39.912123); // 创建标注的坐标 var marker = new BMap.Marker(pointMarker); // 创建标注 map.addOverlay(marker); // 将标注添加到地图中

      接下来,我们需要在这个标注的基础上,添加一个信息窗口。

      为了在标注上添加一个信息窗口,我们需要对该标注建立一个监听事件,当鼠标点击标注后,方能显示信息窗口。

      添加事件请使用addEventListener.

      var infoWindow = new BMap.InfoWindow("点击标注后信息窗口就显示了"); // 创建信息窗口对象 marker.addEventListener("click", function(){ //给标注添加点击事件 this.openInfoWindow(infoWindow); });

      同理,你可以自己添加文字标签label。

      其中point是文字标签显示的位置,offset可以设置它的偏移量。{}里的东西默认是可以不写的。

      var label = new BMap.Label("请点击红色标注",{point : pointMarker, 
              offset: new BMap.Size(3,-6)}); //定义一个文字标签 map.addOverlay(label);

      点击这里,运行该代码。右键点击新打开的窗口,可以查看源代码哦。

      二、如何添加银行的标注?

      但是,大家会不会觉得默认的这个红色标注很丑呢?那么我们一起来换一个标注吧!下面开始讲解,如何自定义标注。

      为了看清楚,我把标注放得比较大。你自己可以换张小图片试试。

      首先,我们要准备的是一张标注的图片。注意,一定需要一张背景透明的图片。然后定义三家银行的标注样式。有4个地方需要我们来设置。

      第一就是银行图标的地址,我们可以把多张银行图标放在一张图上。比如这张图,点击这里下载。

      第二是这个标注的大小BMap.Size,需要根据你银行图标的大小来定。

      第三个是标注的偏移量offset。为什么要有偏移量呢?因为我们希望图标下面那个小尖尖刚好指在我们需要的坐标点上。

      第四个就是相当于CSS sprites的设置了。由于我们的银行图标都放在了同一张图上,所以需要靠imageOffset这个设置来调整显示位置。

      具体代码如下:

      // 创建招商银行的标注图标 var zsIcon = new BMap.Icon("http://ui-love.com/baidumap/bank/marker.gif", //图片地址 new BMap.Size(40, 64), // 标注显示大小 { offset: new BMap.Size(20, 64), // 标注底部小尖尖的偏移量 imageOffset: new BMap.Size(0, 0) // 这里相当于CSS sprites }); // 创建中国银行的标注图标 var zgIcon = new BMap.Icon("http://ui-love.com/baidumap/bank/marker.gif", //图片地址 new BMap.Size(40, 64), // 标注显示大小 { offset: new BMap.Size(20, 64), // 标注底部小尖尖的偏移量 imageOffset: new BMap.Size(0, -64) // 这里相当于CSS sprites }); // 创建建设银行的标注图标 var jsIcon = new BMap.Icon("http://ui-love.com/baidumap/bank/marker.gif", //图片地址 new BMap.Size(40, 64), // 标注显示大小 { offset: new BMap.Size(20, 64), // 标注底部小尖尖的偏移量 imageOffset: new BMap.Size(0, -128) // 这里相当于CSS sprites });

      然后我们分别添加3个银行标注在地图上。点击这里运行代码。源代码如下:

      <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>酸奶小妹——百度地图API学习</title> <style type="text/css"> html{height:100%} body{height:100%;margin:0px;padding:0px} #milkMap{height:400px;width:600px;border:1px solid blue;} </style> <script type="text/javascript" src="http://api.map.baidu.com/api?v=1.1&services=false"></script> </head> <body> <div id="milkMap"></div> </body> <script type="text/javascript"> var map = new BMap.Map("milkMap"); // 创建地图实例 var point = new BMap.Point(116.404, 39.915); // 创建点坐标 map.centerAndZoom(point, 16); // 初始化地图,设置中心点坐标和地图级别 map.addControl(new BMap.OverviewMapControl({isOpen: 1, anchor: BMAP_ANCHOR_TOP_RIGHT})); //为地图添加鹰眼 map.addControl(new BMap.NavigationControl()); //为地图添加鱼骨 var myCopyright = new BMap.CopyrightControl({offset: new BMap.Size(485, 0)}) //设置版权信息偏移量 map.addControl(myCopyright); //为地图添加版权控件 myCopyright.addCopyright({id : 1, content : '<a style="line-height:30px;height:30px;display:block;color:red;background:yellow" href="http://www.cnblogs.com/milkmap/"><img src="http://www.ui-love.com/static/img/uiico.ico" />酸奶小妹的博客园</a>'}); // 创建招商银行的标注图标 var zsIcon = new BMap.Icon("http://ui-love.com/baidumap/bank/marker.gif", //图片地址 new BMap.Size(40, 64), // 标注显示大小 { offset: new BMap.Size(20, 64), // 标注底部小尖尖的偏移量 imageOffset: new BMap.Size(0, 0) // 这里相当于CSS sprites }); // 创建中国银行的标注图标 var zgIcon = new BMap.Icon("http://ui-love.com/baidumap/bank/marker.gif", //图片地址 new BMap.Size(40, 64), // 标注显示大小 { offset: new BMap.Size(20, 64), // 标注底部小尖尖的偏移量 imageOffset: new BMap.Size(0, -64) // 这里相当于CSS sprites }); // 创建建设银行的标注图标 var jsIcon = new BMap.Icon("http://ui-love.com/baidumap/bank/marker.gif", //图片地址 new BMap.Size(40, 64), // 标注显示大小 { offset: new BMap.Size(20, 64), // 标注底部小尖尖的偏移量 imageOffset: new BMap.Size(0, -128) // 这里相当于CSS sprites }); var pointMarker1 = new BMap.Point(116.403704,39.912123); // 创建招商银行标注的坐标 var pointMarker2 = new BMap.Point(116.407804,39.916123); // 创建中国银行标注的坐标 var pointMarker3 = new BMap.Point(116.400804,39.915123); // 创建建设银行标注的坐标 var marker1 = new BMap.Marker(pointMarker1, {icon: zsIcon}); // 创建招商银行标注 var marker2 = new BMap.Marker(pointMarker2, {icon: zgIcon}); // 创建中国银行标注 var marker3 = new BMap.Marker(pointMarker3, {icon: jsIcon}); // 创建建设银行标注 map.addOverlay(marker1); // 将招商银行标注添加到地图中 map.addOverlay(marker2); // 将中国银行标注添加到地图中 map.addOverlay(marker3); // 将建设银行标注添加到地图中 var infoWindow1 = new BMap.InfoWindow("你点击了招商银行的标注",{offset: new BMap.Size(0, -64)}); marker1.addEventListener("click", function(){ //给招商银行标注添加点击事件 this.openInfoWindow(infoWindow1); //打开招商银行的窗口 }); var infoWindow2 = new BMap.InfoWindow("你点击了中国银行的标注",{offset: new BMap.Size(0, -64)}); marker2.addEventListener("click", function(){ //给中国银行标注添加点击事件 this.openInfoWindow(infoWindow2); //打开中国银行的窗口 }); var infoWindow3 = new BMap.InfoWindow("你点击了建设银行的标注",{offset: new BMap.Size(0, -64)}); marker3.addEventListener("click", function(){ //给建设银行标注添加点击事件 this.openInfoWindow(infoWindow3); //打开建设银行的窗口 }); </script> </html>

      ——————————————————————————————————————–

      小贴士:什么是覆盖物?

      答:覆盖物包括了标注Marker、文字标签Label、信息窗口InfoWindow、圆形Circle、多边形Polygon,以及折线Polyline。

      添加任何一个覆盖物都需要map.addOverlay();这个函数。

      如何添加其他覆盖物呢?你可以到API的官方网站上查询类参考->覆盖物

      ——————————————————————————————————————–

      三、找坐标的使用工具——坐标拾取系统

      1、进入该工具网址:http://openapi.baidu.com/map/pick/index.html

      2、在搜索框中输入你想查询位置的中文名称,例如“安定门”

      3、找到合适的位置,点击鼠标右键开启添加标注功能。(小窍门:地图级别越高,位置越精确,建议把地图级别开到17或者18级哦~)

      4、开启功能后,你就可以左键点击地图了。无论你点在哪里,网页的右边都会出现一组坐标信息,这就是当前小红点的坐标了。

      5、如果你想改变小红点的位置,可以直接点击地图上另外的点,也可以拖动小红点。

      注意:如果你要拖动地图,请先点击鼠标右键,关闭添加标注的功能。

      ———————————————————————————————————————–

      学到这里,大家已经可以动手自己做一张自定义的银行分布图了。

      主要步骤:

      1、利用工具找到银行的地理位置,也就是坐标。

      2、把银行名称、地址、坐标等内容存入数据库。

      3、创建一个搜索条,查询用户输入的关键字。比如,用户输入“大望路 招商银行”。

      4、查询数据库,找到数据2符合条件。于是在(106.40,30.91)这个点上,添加一个招商银行的标注。

      注意:这里显示的标注是自定义标注,就是说,如果查询的是招商银行,就显示招商银行的标注;查询的是建设银行,就显示建设银行的标注。

      至于怎样建立数据库,我这里就不多说了,因为不太涉及到GIS或者API的东西。

      有兴趣的同学可以去学习一下数据库,还有PHP或者.NET等后台程序。

      ————————————————————————————————————————

      如果我们没有自己的数据库,也想建立一个银行网点的分布图,应该如何做呢?

      在下一章,我们将学会,如何利用百度地图的数据库来建立全国银行网点的查询地图。

      [转载]最终用户在线设计和修改Web报表

      mikel阅读(1204)

      [转载]最终用户在线设计和修改Web报表 – Web打印 – 博客园.

      我在最近主持开发的人力资源管理系统中遇到一个新的问题:就是最终用户要能够设计和编辑报表。在人力资源管理系统中有一个基本的功能,就是编辑和打印员工 的工作证,不同职务的员工的工作证可能不一样,同一个员工不同工作时期(比如实习员工、正式员工)的工作证也可能不一样。项目方要求我们一定要做到操作员 可以在线设计和修改工作证的格式(实际上就是报表),否则就一切免谈。客户是上帝!呵呵,努力做吧。于是大家搜罗了所有的Web打印软件,结果包括大名鼎 鼎的水晶报表也没有这个功能。
      好在天无绝人之路,我在以前所设计的一个基于FastReport的Web打印控件之上进行了大量的修改,终于实现了最终用户的在线设计和编辑报表的功能,用户也比较满意。
      报表的预览窗口如下图:

      报表的最终用户在线设计和编辑窗口如下图:

      当然报表的在线编辑窗口有一点专业,操作员需要经过一点培训才会使用,不过一般情况是我们先帮用户设计好了模板,操作员平时只是要做一些微调,比如改个字或改个格式等。报表编辑好后,点击就可以自动把报表保存到服务器,且提示
      下面详细的讲解一下怎样利用我设计的Web打印控件实现最终用户的在线设计和修改报表,且自动把报表保存至服务器。讲解以ASP.NET为例,当然其它语 言也可以举一反三,很容易实现。因为调用打印控件统一是JavaScirpt,提交服务器的数据是Http协议的Post方式,这些都是通用的。
      最终用户在线设计和修改Web报表的部分代码如下:

      调用Web打印控件

      protected void BtnDepsitAmtDesignPost_Click(object sender, EventArgs e) { string FileValue, PrintValue, ParaName, ParaValue; FileValue = FileToString(".\\Frp\\DepositAmt.fr3"); ParaName = "ShopName`~PrintDepositAdd`~PrintPaperNo`~Title"; //`~为各参数的分隔符 ParaValue = "测试酒楼" + "`~说明:本单据为贵客押金收取凭证,盖章有效。退房时请出示,遗失者自负,请妥善保存。退房时间为12:00时,延时退房18:00时以前按半天房费收取,18:00时以后算全天房价。押金单有效期为一个月,过期作废。 贵重物品请交前台寄存,未寄存丢失自负。 谢谢!" + "`~身份证:4325011980639512" + "`~押金单"; SqlConnection ConPrintTest = new SqlConnection(ConfigurationManager.ConnectionStrings["PrintTestConnectionString"].ToString()); ConPrintTest.Open(); DataSet DsCashLog = new DataSet(); SqlDataAdapter DaCashLog = new SqlDataAdapter("Select top 1 CashNo, CashDate, CashAmt, PayName, GuestName, RoomNo, ItemRemark, CashUserName, Remark From CashLog", ConPrintTest); DaCashLog.FillSchema(DsCashLog, SchemaType.Source, "CashLog"); DaCashLog.Fill(DsCashLog, "CashLog"); PrintValue = TableToXml(DsCashLog.Tables["CashLog"]); DsCashLog.Dispose(); ConPrintTest.Close(); string ScriptStr; ScriptStr = "<script language='javascript'>window.onload = function() { try { var ObjPrintMange = new ActiveXObject('WebPrint.WebPrintUnit'); } catch(e) { if( confirm('打印控件未安装,现在下载吗?') ) { window.location='./PrintActivex.exe'; } return; } var OldVersion=ObjPrintMange.Version; NewVerion='3.5(2011-02-17)'; if(OldVersion < NewVerion) { ObjPrintMange = null; alert('打印控件需升级。请先进行下载,下载后关闭IE,然后安装升级版。'); window.location='./PrintActivex.exe'; return; } " + " ObjPrintMange.CheckReg('公司名称', '3B8E5B998A3125EE89983EA940BB2AEE'); " //注册码 + " ObjPrintMange.ReportFileName='DepositAmt.fr3'; " + " ObjPrintMange.PostURL='http://www.xinyuerj.com/ASPPost/Show.asp?FileName=DepsitAmt.fr3'; " + " ObjPrintMange.DesignReport('" + FileValue + "' , '" + ParaName + "', '" + ParaValue + "', '" + PrintValue + "', '', '', '', '', '');" + "ObjPrintMange = null; } </script>"; ScriptStr = ScriptStr.Replace(System.Environment.NewLine, string.Empty); Response.Write(ScriptStr); }

      报表控件在以前的基础上增加了PostURL属性,设置此属性,则在报表在线编辑时,单击“保存”按钮或“保存”菜单项,则把报表内容组织为 String,且通过Http的Post方式直接提交到所设置的URL页面,用户可以在URL接收报表内容,把报表内容保存在指定文件或数据库中。 Post的字段名指定为ReportFileValue,用户可以通过接收此字段的值保存在线编辑的报表内容。报表的内容为String,打印或预览时可 以直接调用此内容,无需用FileToStr函数进行转换。
      报表控件的DesignReport函数,功能为在线编辑报表,参数:报表文件字符串,报表参数名称字符串,报表参数值字符串,数据集1的字符串,数据集 2的字符串,数据集3的字符串,数据集4的字符串,数据集5的字符串,数据集6的字符串。报表文件字符串调用FileToStr函数产生;报表参数名称字 符串为报表中所使用的参数的名称,各名称之间以`~分隔;报表参数值字符串为报表中所使用的参数所对应的值,各参数值之间以`~分隔;数据集的字符串通过 调用FileToStr函数产生,若没有数据则为空。
      作为接收Http的Post数据的服务器的页面设计如下:

      接收提交的报表内容

      public partial class Show : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { string FileName, FileValue; FileName = Request.QueryString["FileName"]; FileValue = Request.Form["ReportFileValue"]; WriteReportFile(FileName, FileValue); Response.Write("File Name" + FileName); Response.Write("File Value" + FileValue); } private void WriteReportFile(string FileName, string FileValue) { if ( FileName == null || FileName == "" || FileValue == null ||FileValue == "") return; File.WriteAllText(Server.MapPath(FileName), FileValue); } }

      FileName是PostURL中所设置的保存的报表文件名,ReportFileValue则为报表文件的内容。
      如果你的项目也需要最终用户在线设计和修改报表,我所设计这个Web打印控件是一种比较快捷方便的解决方法。Web打印控件目前对于个人学习研究是免费注册的,有什么问题请加入QQ群:135506194或者请直接Q我:12988672。
      报表预览和编辑的网址:http://www.xinyuerj.com/ASP/
      http://www.xinyuerj.com/ASP.NET/

      [原创]听客户说然后再做开发

      mikel阅读(1336)

      不一定什么事儿都想好了才开始做,不一定什么情况都考虑到了才开始!因为不变只有变化!既然我们不能预见未来,那就适应未来的变化才是正道

      —这才是真正的开发思路

      最近一直在改版网站,原本以为需要考虑周全的东西以及流程在真正的客户到来后才发现都是浮云!

      客户不要求你把不可预料的神马情况都考虑好了再做,而是要求你立刻马上赶快做出一个网站来用,因为客户也不知道网站将来需要什么功能,他只知道现在我要做个在线购物网站,浏览者能够通过商城买东西,下订单就行了!至于神马库存管理、销售管理、物流管理不用考虑,客户都不知道这个网站将来怎么处理这些问题,所以只需要有个可以查看前台订单的列表,知道谁买的东西,联系电话和送货地址就行了!想其他的都是多余!

      客户说了等到时候遇到问题再解决问题,然后需要开发功能就开发功能,需要人工处理就人工处理,一切走着看!

      做了这么多年开发,今天突然好像突然被一个雷给击中了脑袋,开窍了!这才是真理啊!原理我一直用开发者的思路去思考问题,把简单的东西复杂化了!想得太多了!

      [转载]使用IntelliTrace调试跟踪ASP.NET MVC框架Action调用

      mikel阅读(1076)

      [转载]使用IntelliTrace调试跟踪MVC框架Action调用 – lipan – 博客园.

      IntelliTrace调试跟普通断点加单步跟踪模式的区别在于,它支持对历史过程的模拟重新调试。当我们在普通调试下想了解应用程序曾经的 执行情况,一般情况下我们会停止调试,重新加断点启动调试。而有了IntelliTrace之后,我们可以用其独有的历史调试功能“回到过去”,这样一次 调试就可以有效定位问题。现在我要用这个功能,在开源MVC框架中寻找控制器的Action方法是如何被调用的。

      大家都知道,MVC通过URL路由截获地址栏参数获取Controller和Action的值,并通过这两个两个字符串型的去定位控制器和控制 器的方法,再由这个方法返回视图。可问题在于,只知道字符串的类名和方法名是没有办法直接实现类并调用方法的。于是“很自然的”就想到了反射。由于反射的 性能代价太大,很多人就开始抱怨,微软的新特性都是以牺牲性能为代价的,C#是性能低下的语言。然而事实是什么样呢,话说没有调查就没有发言权,我们先展 开调查。

      将MVC开源文件引入项目

      1. 下载MVC框架源码: ASP.NET MVC 2 源码 ASP.NET MVC 3 源码 ,本文用的是MVC2。

      2. 在VS2010新建一个MVC项目,删除引用“System.Web.Mvc”。将源码包解压,将src下SystemWebMvc目录拷贝至项目文件夹,在解决方案中添加项目,再添加对这个开源项目的引用。

      点击下载配置好的项目

      使用IntelliTrace调试

      第一步,由于IntelliTrace调试默认是未启用的,首先你要开启它。安F5进入调试状态,看到右边的“IntelliTrace”窗 口,单击打开IntelliTrace设置按钮。勾选“启用IntelliTrace”,单选组合点选“IntelliTrace事件和调用信息”,如下 图所示。

      图1

      配置好后点确定,然后停止调试,在HomeController的Index方法处加以断点,启动调试,程序请求Index页面命中断点,这时你会发现围绕断点处多了几个箭头符号,这就是IntelliTrace调试要用到的。

      好了,现在我们就要展示IntelliTrace调试的神奇之处了。由于我们想知道Index方法到底被谁调用了,我们怎么操作?见证奇迹的时刻就要到了!我 们要让程序倒着执行,是不是就很容易知道它的调用者?看到有个向上的双箭头,我叫他“单步回退”,单击一次,程序定位到了某个类的Execute方法中, 它调用了名为_executor委托,然后我们分析代码发现这个委托在其下方的GetExecutor函数中被实现,我们重点关注 GetExecutor,我将其贴在下面。

      01 private static ActionExecutor GetExecutor(MethodInfo methodInfo) {
      02 // Parameters to executor
      03 ParameterExpression controllerParameter = Expression.Parameter(typeof(ControllerBase), "controller");
      04 ParameterExpression parametersParameter = Expression.Parameter(typeof(object[]), "parameters");
      05
      06 // Build parameter list
      07 List<Expression> parameters = new List<Expression>();
      08 ParameterInfo[] paramInfos = methodInfo.GetParameters();
      09 for (int i = 0; i < paramInfos.Length; i++) {
      10 ParameterInfo paramInfo = paramInfos[i];
      11 BinaryExpression valueObj = Expression.ArrayIndex(parametersParameter, Expression.Constant(i));
      12 UnaryExpression valueCast = Expression.Convert(valueObj, paramInfo.ParameterType);
      13
      14 // valueCast is "(Ti) parameters[i]"
      15 parameters.Add(valueCast);
      16 }
      17
      18 // Call method
      19 UnaryExpression instanceCast = (!methodInfo.IsStatic) ? Expression.Convert(controllerParameter, methodInfo.ReflectedType) : null;
      20 MethodCallExpression methodCall = methodCall = Expression.Call(instanceCast, methodInfo, parameters);
      21
      22 // methodCall is "((TController) controller) method((T0) parameters[0], (T1) parameters[1], ...)"
      23 // Create function
      24 if (methodCall.Type == typeof(void)) {
      25 Expression<VoidActionExecutor> lambda = Expression.Lambda<VoidActionExecutor>(methodCall, controllerParameter, parametersParameter);
      26 VoidActionExecutor voidExecutor = lambda.Compile();
      27 return WrapVoidAction(voidExecutor);
      28 }
      29 else {
      30 // must coerce methodCall to match ActionExecutor signature
      31 UnaryExpression castMethodCall = Expression.Convert(methodCall, typeof(object));
      32 Expression<ActionExecutor> lambda = Expression.Lambda<ActionExecutor>(castMethodCall, controllerParameter, parametersParameter);
      33 return lambda.Compile();
      34 }
      35 }

      研究过表达式树的同鞋一定知道,在这个方法中构建了一个调用Action方法的表达式树,并通过lambda.Compile()返回调用过程的委托。lambda表达式树调用方法的效率如何,老赵早已在这篇文章[点击学习]分析过,它的效率跟静态调用相差无几的。所以担心“反射”降低性能的朋友大可以放心了。

      后记

      本文虽然主题不够明确,讲了调试又讲MVC(我就喜欢跟着思维走,呵呵),也反映了些问题。假如我们通过一般的调试手段去分析这个问题,那么我 们得先把MVC源码给分析一边吧,然后呢,我们好容易找到了入口点,Controller的 Execute方法,加了断点了,然后就开始淌水了,趟了好深好深的一趟水,然后执行Action了,然后我们要再次调试,回忆刚才从哪里跳到 Action,终于找到了。好辛苦!而用IntelliTrace只是轻轻点击下鼠标,就完成了任务。另外,选MVC框架源码的作用,说明项目足够复杂 时,这样的调试功能的价值才能体现出来。另外,不是也有个收获么,知道了MVC框架怎么通过地址栏参数映射到方法了。

      [转载]Web性能优化实践——应用层性能优化

      mikel阅读(1091)

      [转载]Web性能优化实践——应用层性能优化 – 木子博客 – 博客园.

      随着公司项目的进一步推广,用户数量的增加,已经面临着单台服务器不能负载的问题。

      这次的优化由于时间关系主要分两步走,首先优化应用层代码以提高单台服务器的负载和吞吐率。之后再进行分表,引入队列、MemCached等分布式应用。

      项目背景:这是一个在线竞赛的项目(http://race.gwy.591up.com),在竞赛的时间段内数据库的写入压力很大。

      当前问题:1、服务器带宽压力。2、数据库压力。

      下图是Web服务器CPU使用率报表。

      总体上应用层服务器的CPU使用率不高。

      下图是Web服务器带宽报表。

      从这个报表可以看到,每块竞赛带宽的占用都会出现一个很高的峰值。服务器是百兆的带宽,理论上可用的最大流量是12.5M,有些时候已经快接近理论的峰值了。

      我们再看数据库服务器的带宽报表。

      同样的,数据库服务器同样的在竞赛的时间点流量超大,很明显这种情况是不正常的,查询肯定是有问题的。

      面对这样的问题,确定了第一期主要做以下的优化。

      1、 用flash storage做用户做题断点记录。(做题断点:类似程序断点,用户做到第N题时退出做答,下次进入时依然定位到第N题。)这里原来是用数据库存储的,但 用户每做一题都会执行一条UPDATE语句,而数据库是MySQL的MyISAM引擎,更新操作经常被堵塞。

      2、更改系统交卷行为。原来系统在用户做完提交竞赛后,会执行一条UPDATE语句更新用户提交试卷的时间点。同样的这个UPDATE也是在同一时间段内执行,和产品经理沟通后,确认在最后一分钟的时候可以不用再执行这个更新,允许用户的作答时间有1分钟的误差。

      3、调整数据库的更新语句为插入语句,这个优化点因为时间问题,推迟到第二个优化阶段再处理。

      4、调整应用服务器以支持LVS集群。对当前系统进行分析后,暂时可以不用调整代码直接部署集群,问题是在多台服务器内都会存在相同的进程内缓存,这种情况暂时是可以接受的,后期需要改到MemCached集中管理缓存。

      5、 等待成绩页面同一时间跳转的压力问题。在线竞赛的提交时间点很集中,用户做答完题目后,会统一跳转到一个页面等待答案(这时后台的Windows 服务在进行竞赛统计),这里服务器的并发、带宽压力都非常大。因此,优化这里不进行跳转,而是在当前的页面等待,并且会自动给不同的用户分配不同的等待时 间,以避免占满服务器的带宽。

      6、 每场完整的公务员考试试卷,题目资源有150K-200K,因为作答和查看解析是在不同的页面,之前的实现会造成题目的多次加载,严重的浪费了带宽资源。 于是这里优化成Handler输入静态资源加载,从服务器加载一次之后,后面所有的地方调用到题目都可以从浏览器的本地缓存中加载带省服务器带宽。同时, 服务器上只对静态资源服务器开启了GZip压缩,对动态文件进行压缩会浪费服务器的CPU资源,而只对Handler输出的题目进行GZip压缩,一方面 节省了CPU,另一方面把150K-200K的题目资源压缩到了50K左右。

      7、数据库性能优化。调整了代码中查询的各个条件的位置,使查询语句能够更多的使用到索引。同时把原来每次一条的插入操作修改为一次插入多条等一些数据库查询优化。

      任何一个优化都要针对已经存在的问题,从服务器监控的报表可以看到我们这个网站应用服务器带宽压力、数据库服务器带宽压力都很大,应用服务器的CPU使用率不高,因此,主要的优化是对应用服务器带宽和数据库服务器的写入压力做的优化,因为目的很明确,效果也是比较明显的。

      文中提到了用Handler来输出静态资源让浏览器缓存,附上这个代码,其它的优化针对性很高,就不再啰嗦了,主要的还是记录下这次优化的工作方式和工作方法。

      Handler输出的静态资源使用了.NET流压缩,于是我们声明一个压缩器接口。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      using System.IO;
      namespace ND.Race.Compressor
      {
      /// <summary>
      /// 压缩器接口
      /// </summary>
      public interface ICompressor
      {
      string EncodingName { get; }
      bool CanHandle(string acceptEncoding);
      void Compress(string content, Stream targetStream);
      }
      }

      GZip压缩器实现这个接口。

      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
      using System.IO;
      using System.IO.Compression;
      using System.Text;
      namespace ND.Race.Compressor
      {
      public sealed class GZipCompressor : ICompressor
      {
      public string EncodingName
      {
      get { return "gzip"; }
      }
      public bool CanHandle(string acceptEncoding)
      {
      return !string.IsNullOrEmpty(acceptEncoding) &&
      acceptEncoding.Contains("gzip");
      }
      public void Compress(string content, Stream targetStream)
      {
      using (var writer = new GZipStream(targetStream, CompressionMode.Compress))
      {
      var bytes = Encoding.UTF8.GetBytes(content);
      writer.Write(bytes, 0, bytes.Length);
      }
      }
      }
      }

      同样的Deflate压缩器也实现这个接口。

      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
      using System.IO;
      using System.IO.Compression;
      using System.Text;
      namespace ND.Race.Compressor
      {
      public sealed class DeflateCompressor : ICompressor
      {
      public string EncodingName
      {
      get { return "deflate"; }
      }
      public bool CanHandle(string acceptEncoding)
      {
      return !string.IsNullOrEmpty(acceptEncoding) &&
      acceptEncoding.Contains("deflate");
      }
      public void Compress(string content, Stream targetStream)
      {
      using (var writer = new DeflateStream(targetStream, CompressionMode.Compress))
      {
      var bytes = Encoding.UTF8.GetBytes(content);
      writer.Write(bytes, 0, bytes.Length);
      }
      }
      }
      }

      如果浏览器不支持流压缩,那我们只能直接输出内容了,因此我们还需要一个不进行压缩的处理类。

      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
      using System.IO;
      using System.Text;
      namespace ND.Race.Compressor
      {
      public sealed class NullCompressor : ICompressor
      {
      public string EncodingName
      {
      get { return "utf-8"; }
      }
      public bool CanHandle(string acceptEncoding)
      {
      return true;
      }
      public void Compress(string content, Stream targetStream)
      {
      using (targetStream)
      {
      var bytes = Encoding.UTF8.GetBytes(content);
      targetStream.Write(bytes, 0, bytes.Length);
      }
      }
      }
      }

      现在我们就可以开始编码我们的Handler了。

      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
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      91
      92
      93
      94
      95
      96
      97
      98
      99
      100
      101
      102
      103
      104
      105
      106
      107
      108
      109
      110
      public class QuestionCacheHandler : IHttpHandler
      {
      #region 静态变量
      /// <summary>
      /// 资源过期时间
      /// </summary>
      private static readonly int durationInDays = 30;
      /// <summary>
      /// 流压缩接口
      /// </summary>
      private static readonly ICompressor[] Compressors = {
      new GZipCompressor(),
      new DeflateCompressor(),
      new NullCompressor()
      };
      #endregion
      #region 私有变量
      /// <summary>
      /// 内存流压缩类
      /// </summary>
      private ICompressor compressor;
      /// <summary>
      /// ETAG
      /// </summary>
      private string eTagCacheKey;
      /// <summary>
      /// 竞赛场次Id
      /// </summary>
      private long raceId;
      #endregion
      public void ProcessRequest(HttpContext context)
      {
      if (context == null) return;
      long.TryParse(context.Request.QueryString["raceId"], out raceId);
      if (raceId == 0) return;
      var acceptEncoding = context.Request.Headers["Accept-Encoding"];
      compressor = Compressors.First(o => o.CanHandle(acceptEncoding));
      eTagCacheKey = string.Concat(raceId, "/etag");
      if (IsInBrowserCache(context, eTagCacheKey)) return;
      SendOutputToClient(context, true, eTagCacheKey);
      }
      /// <summary>
      /// 发送内容到客户端
      /// </summary>
      /// <param name="context"></param>
      /// <param name="insertCacheHeaders"></param>
      /// <param name="etag"></param>
      private void SendOutputToClient(HttpContext context, bool insertCacheHeaders, string etag)
      {
      string content = "";
      MemoryStream memoryStream = new MemoryStream();
      compressor.Compress(content, memoryStream);
      byte[] bytes = memoryStream.ToArray();
      HttpResponse response = context.Response;
      if (insertCacheHeaders)
      {
      HttpCachePolicy cache = context.Response.Cache;
      cache.SetETag(etag);
      cache.SetOmitVaryStar(true);
      cache.SetMaxAge(TimeSpan.FromDays(durationInDays));
      cache.SetLastModified(DateTime.Now);
      cache.SetExpires(DateTime.Now.AddDays(durationInDays)); // HTTP 1.0 的浏览器使用过期时间
      cache.SetValidUntilExpires(true);
      cache.SetCacheability(HttpCacheability.Public);
      cache.SetRevalidation(HttpCacheRevalidation.AllCaches);
      cache.VaryByHeaders["Accept-Encoding"] = true;
      }
      response.AppendHeader("Content-Length", bytes.Length.ToString(System.Globalization.CultureInfo.InvariantCulture));
      response.ContentType = "text/plain";
      response.ContentType = "application/x-JavaScript";
      response.AppendHeader("Content-Encoding", compressor.EncodingName);
      if (bytes.Length > 0)
      response.OutputStream.Write(bytes, 0, bytes.Length);
      if (response.IsClientConnected)
      response.Flush();
      }
      /// <summary>
      /// 是否浏览器已经缓存
      /// </summary>
      /// <param name="context"></param>
      /// <param name="etag"></param>
      /// <returns></returns>
      private bool IsInBrowserCache(HttpContext context, string etag)
      {
      string incomingEtag = context.Request.Headers["If-None-Match"];
      if (String.Equals(incomingEtag, etag, StringComparison.Ordinal))
      {
      context.Response.Cache.SetETag(etag);
      context.Response.AppendHeader("Content-Length", "0");
      context.Response.StatusCode = (int)System.Net.HttpStatusCode.NotModified;
      context.Response.End();
      return true;
      }
      return false;
      }
      public bool IsReusable
      {
      get
      {
      return false;
      }
      }
      }

      服务器端代码通过Http请求Header的Accept-Encoding来判断是否支持流压缩,再通过Header的etag来判断浏览器中是否已经有缓存副本。

      关注更多相关内容,请移步:  http://blog.moozi.net/