[转载]ASP.NET 购物车功能分析及实现代码下载

mikel阅读(1141)

[转载]购物车功能分析 – intcry的博客 – 博客园.

最近学习做购物车,所以把我在学习购物车功能实现的一些体会整理下来。
购物车是电子商务平台的一种重要功能,指的是应用于网店的在线购买功能,它类 似于超市购物时使用的推车或篮子,可以暂时把挑选商品放入购物车、删除或更改购买数量,并对多个商品进行一次结款,是网上商店里的一种快捷购物工具。网站 购物车的作用和现实中的超市购物车一样,随意添加,删除,修改商品数量,方便购物者选好商品后一次性付款结账。
购物车的功能应该包括以下几项:
把商品添加到购物车(订购)
删除购物车中已订购的商品、
修改购物车中某一商品的订购数量
清空购物车
显示购物车中商品的清单及数量、价格
购物车实现方式一般主要通过cookie,session或结合数据库;
从我分析国内大型商务网站,如淘宝,当当等,当用户未登录的时候是使用 cookie存数据的,当用户登录了之后,都存到了与用户对应的数据库。当一个顾客把它选中的产品加入购物车后,产品就会暂时存储在网站购物车里,顾客可 以继续选择其它产品,一直到付款提交订单。在未提交订单前购物车内的商品可以保存一定的时间如30、60天等。
现在一些网上说购物车是个临时存储数据的模块,可以将其存放在Session 对象中,个人感觉用session存购物信息只能说是学校学习老师让练习时做的,只能说用它做个玩玩,实际开发中基本上是不会用它的,如果大家都将其存储 在Session对象中,成千上万个用户一同购物的话,想必服务器必将承受巨大的负载,影响服务器的性能。而且Session是有有效期的, 根据服务器的设置不同而不一样长, 如果你在购物的过程中Session超时了, 那么购物车中的东西就会全没了。
而使用数据库存取,在这种购物流程中涉及到对数据库表的频繁操作,尤其是用户 每选购一次商品,都要与数据库进行连接,当用户很多的时候就加大了服务器与数据库的负荷。那么用户选择商品的时候就很有可能频繁更新数据库,增加数据库的 负担。所以个人感觉,在商业网站的购物车设计上,最好使用cookie+数据库方式来存取数据。而且放到购物车里的商品,一般都是有购买意向的商品,但并 不一定会成为真实的订单,这时候,保留这份数据,对数据挖掘、业务分析有至关重要的作用,所以如果有条件,最好把这些数据也存入数据库的。
当然,使用cookie也是有缺点的,cookie是由服务器产生,存储在客户端的一段信息。大多数浏览器支持最大为 4096 字节的 Cookie。由于这限制了 Cookie 的大小,最好用 Cookie 来存储少量数据,或者存储用户 ID 之类的标识符。
浏览器还限制站点可以在用户计算机上存储的 Cookie 的数量。大多数浏览器只允许每个站点存储 20 个 Cookie;如果试图存储更多 Cookie,则最旧的 Cookie 便会被丢弃。有些浏览器还会对它们将接受的来自所有站点的 Cookie 总数作出绝对限制,通常为 300 个。
基于cookie的购物车要求用户浏览器必须支持并设置为启用cookie,否则购物车则失效;
在使用cookie存数据的时候,一个重要的问题就是cookie不能存数组,对象。只能存字符串。所以在存数据的时候需要把购物车中的数据转换成字符串形式存入cookie中,需要的时候再还原加载入购物车。
以下是我使用DataTable虚拟表存入cookie的方法:
public static DataTable dt;   
/// <summary>   
/// 创建虚拟表   
/// </summary>    
protected void BuildDataTables()   
{   
    dt = new DataTable("Product");//新建一张商品表,表名为"Product"   
    dt.Columns.Add(new DataColumn("ProductID"));    //商品编号   
    dt.Columns.Add(new DataColumn("ProductName"));  //商品名称   
    dt.Columns.Add(new DataColumn("quantity"));     //购买数量   
    dt.Columns.Add(new DataColumn("totalPrice"));   //商品总价   
    dt.Columns.Add(new DataColumn("Price"));        //商品单价   
    dt.PrimaryKey = new DataColumn[] { dt.Columns["ProductID"] }; //设置主键为ProductID   
  
}    
/// <summary>   
/// 把虚拟表中的商品编号和购买数量转换成字符串并写入cookie   
/// </summary>    
protected void WriteInCookie()   
{   
    string str = "";   
    foreach (DataRow row in dt.Rows)   
    {   
        str = row["ProductID"] + "|" + row["quantity"] + "," + str;   
    }   
    str = str.TrimEnd(',');   
  
    //往购物车中添加商品   
    HttpCookie aCookie = null;   
    if (HttpContext.Current.Request.Cookies["ShoppingCart"] == null)   
    {   
        //如果Cookies中不存在ShoppingCart,则创建   
        aCookie = new HttpCookie("ShoppingCart");   
    }   
    else  
    {   
        //如果Cookies中存在ShoppingCart,则清空   
        aCookie = HttpContext.Current.Request.Cookies["ShoppingCart"];   
        aCookie.Value = null;   
  
    }   
    aCookie.Value = str;   
    //设置cookie有效期为1天   
    aCookie.Expires = DateTime.Now.AddDays(1);   
    HttpContext.Current.Response.Cookies.Add(aCookie);   
}   
/// <summary>   
/// 从cookie中取出数据填充到虚拟表中   
/// </summary>   
protected void ReadCookie()   
{   
    if (HttpContext.Current.Request.Cookies["ShoppingCart"] != null)   
    {   
        dt = null;   
        BuildDataTables();   
        //如果Cookies中存在ShoppingCart,则取出数据添加到Datatable中   
        HttpCookie aCookie = HttpContext.Current.Request.Cookies["ShoppingCart"];   
  
        string cart = aCookie.Value;   
  
        string[] arr = cart.Split(',');   
  
        for (int i = 0; i < arr.Length; i++)   
        {   
            string[] str = arr[i].Split('|');   
            AddDatatable(Convert.ToInt32(str[0]), Convert.ToInt32(str[1]));   
        }   
    }   
}   
/// <summary>   
/// 往虚拟表中添加数据   
/// </summary>   
/// <param name="ProductID">商品编号</param>   
/// <param name="Quantity">购买数量</param>   
public void AddDatatable(int ProductID, int Quantity)   
{   
    //按照产品编号查询所有产品信息   
    product prod = new ProductInfo().GetProductByProductId(ProductID)[0];   
  
    //新建一行数据   
    DataRow dr = dt.NewRow();   
    dr["ProductID"] = ProductID;   
    dr["ProductName"] = prod.productName;   
    dr["quantity"] = Quantity;   
    dr["Price"] = prod.price;   
    dr["totalPrice"] = Quantity * prod.price;   
    //将这一行数据添加到虚拟表中   
    dt.Rows.Add(dr);   
}            

[原创]修改ASP.NET MVC站点默认首页不使用HomeController

mikel阅读(1017)

最近由于业务需要,网站首页不再只想HomeController的Index而是需要只想StoreController的Index,结果觉得修改Global.asax.cs的MapRoute就行了!可是调试还是访问HomeController的Index

百思不得其解啊!

结果打开Default.aspx.cs一看,傻眼了!

public partial class _Default : Page
{
public void Page_Load(object sender, System.EventArgs e)
{
Response.Redirect("~/Home");
}
}

我勒个去!~~~~不带这样儿的,重定向啊!

直接改之!如下:

public partial class _Default : Page
{
public void Page_Load(object sender, System.EventArgs e)
{
Response.Redirect("~/Store");
}
}

感想就是简单问题复杂化,害得我还去翻了翻ASP.NET MVC的源码!丢死人列!

[转载]html5全接触(二)--BounceBall小游戏简易教程

mikel阅读(1751)

[转载]html5全接触(二)–BounceBall小游戏简易教程 – 叶落为重生每片落下的叶子都是为了下一次的涅槃…^_^ – 博客园.

最近一段时间都比较忙,好久没更新博客了,遵循着“时间就像那啥,挤挤总会有的”的原则,承接着上一篇html5先关的博文,继续我们的趣味html5之旅。

前一段时间很流行用html5写小游戏,当了解了一些常用的api之后,你会发现,写一些简单的小游戏自娱自乐也不会那么困难,当然,做逻辑和界面复杂的游戏除外。以下会提供一个弹球小游戏的简单教程,希望感兴趣的朋友能在编码中找到一点乐趣。

<!– 注:以下demo木有神马高深的东东,大牛们觉得无味请略过。同时,由于砖块厚度与弹球的纵向变换单元的比例不协调,故没做砖块的侧向碰撞监测.. –>

既然是教程,咱们还是一步一步来:(代码可以直接在textarea里看到,源码就不贴了)
【step 1】画个小球

【step 2】为了方便扩展以及养成良好的编码习惯,我们稍微做点封装和结构化;同时增加画矩形的方法rect和清除画布的方法clear

【step 3】让小球动起来,因为canvas是画布,想让小球动起来,最直接的想法莫过于通过定时器setInterval之类,每次先清除画布,然后重绘一个小 球,定位到路径的下一点即可,视觉上连贯起来就动起来了。咱们还是上代码(我这个是低配版,所以每次的位置变化量我都写死的)

【step 4】做边界碰撞的反弹效果,也很简单,判断下小球的坐标x y,监测坐标与canvas高宽,在临界点让对应的变化量做反转即可,比如碰撞上下边界,就让dy = -dy即可。思路化为代码:


【step 5】绘制弹板,并且绑定键盘事件,让挡板可以左右移动。绘制的方法很简单,直接用canvas的rect方法,前面已经写好了,绑定键盘事件,咱们直接绑 在左右按钮上,监测keydown,keyup事件,判断keyCode,然后每次重绘的时候让挡板移位即可。


【step 6】修改碰撞监测,小球落在挡板内可以继续,落在挡板外游戏结束。只需要修改撞击下边界的条件。即当球的y坐标大于画布总高度减去挡板厚度时,判断此时球 的x坐标是否在挡板范围内。如果是那么小球弹回,游戏继续,否则游戏结束。游戏结束也很简单,直接清掉计数器。


【step 7】经过以上阶段,下面可以开始画砖块了,也很简单,定义一个二维的矩阵,根据行列数,算好每个砖块的宽度,通过之前写好的rect把这个二维矩阵转换成小方块就行了。

initBricks : function (row, col) {
B.row
= row;
B.col
= col;
B.w
= W/col – 1;
B.h = 15;
B.pad
= 1;
B.bricks
= new Array(row);

for (var i=0; i<row; i++) {
B.bricks[i]
= new Array(col);
for (var j=0; j<col; j++) {
B.bricks[i][j]
= 1;
}
}
},
drawBricks :
function () {
for (var i=0; i<B.row; i++) {
for (var j=0; j<B.col; j++) {
B.bricks[i][j]
=== 1 && this.rect(j*(B.w+B.pad) + B.pad, i*(B.h+B.pad)+B.pad, B.w, B.h);
}
}
},


【step 8】砖块碰撞。这里只做粗糙的碰撞监测,而且由于砖块厚度太低,dy都比砖块厚度更大,所以侧向的碰撞这里没做考虑。考虑纵向的碰撞的话,简单点,可以实 时监控小球的y坐标,当满足小球坐标在砖块区并且砖块存在,那么就表示小球有和砖块碰撞,那么清除当前碰撞砖块,小球反向即可。

hitBrick : function (x, y) {
var rh = B.h + B.pad,
cw
= B.w + B.pad,
row
= Math.floor(y/rh),
col = Math.floor(x/cw);
if (y < B.row*rh && row >= 0 && col >= 0 && B.bricks[row][col] === 1) {
dy
= dy;
B.bricks[row][col]
= 0;
}
},


好了,其实到这里差不多可以结束了,算是一个最最基础的打砖块小游戏,当然,感兴趣的同学可以自己再加其他的元素,增加其可玩性,比如我最开始那个 加了暂停功能,加了颜色,加了结束画面。甚至你可以自己去加砖块道具,设置不同关卡,增加不同砖块属性,增加各种玩法来增强其可玩性。
仅仅作为教程,我觉得基本目的达到了。由于篇幅原因,有些具体的细节没法一一讲清楚。请包涵。如果对canvas的api不是很熟悉的同学可以先去查阅下资料。

[转载]Android开发——MediaProvider源码分析(1)

mikel阅读(1183)

[转载]Android开发——MediaProvider源码分析(1) – 努力吧,专注Android – 博客园.

转载请注明出处。

————–START————

MediaProvider包括五个类:

  • com.Android.providers.media.MediaProvider
  • com.Android.providers.media.MediaScannerCursor
  • com.Android.providers.media.MediaScannerReceiver
  • com.android.providers.media.MediaScannerService
  • com.android.providers.media.MediaThumbRequest

1.MediaProvider

此类继承ContentProvider,实现一个内容提供者。主要用于创建媒体库的数据库表。有自己创建过ContentProvider的同学相信都比较清楚的。

特别说明一下在MediaProvider中有个广播接收者,代码如下:

private BroadcastReceiver mUnmountReceiver = new BroadcastReceiver() {

@Override

public void onReceive(Context context, Intent intent) {

if (intent.getAction().equals(Intent.ACTION_MEDIA_EJECT)) {

// Remove the external volume and then notify all cursors backed by

// data on that volume

detachVolume(Uri.parse("content://media/external"));

sFolderArtMap.clear();

MiniThumbFile.reset();

}

}

};

此接收者是用来接收Sdcard卸载的广播。当Sdcard从手机中分离出来的时候,Sdcard中的媒体文件相对应的数据库将无法操作。

private void detachVolume(Uri uri) {

//判断是否是同一个进程

if (Process.supportsProcesses() &amp;&amp; Binder.getCallingPid() != Process.myPid()) {

throw new SecurityException(

"Opening and closing databases not allowed.");

}

//此方法只是操作Sdcard的媒体数据库,不支持手机内存的媒体数据库

String volume = uri.getPathSegments().get(0);

if (INTERNAL_VOLUME.equals(volume)) {

throw new UnsupportedOperationException(

"Deleting the internal volume is not allowed");

} else if (!EXTERNAL_VOLUME.equals(volume)) {

throw new IllegalArgumentException(

"There is no volume named " + volume);

}

synchronized (mDatabases) {

DatabaseHelper database = mDatabases.get(volume);

if (database == null) return;

try {

// touch the database file to show it is most recently used

File file = new File(database.getReadableDatabase().getPath());

file.setLastModified(System.currentTimeMillis());

} catch (SQLException e) {

Log.e(TAG, "Can't touch database file", e);

}

//移除数据库

mDatabases.remove(volume);

database.close();

}

getContext().getContentResolver().notifyChange(uri, null);

if (LOCAL_LOGV) Log.v(TAG, "Detached volume: " + volume);

注意移除数据库并非删除数据库文件(*.db),mDatabases是一个HashMap<String,DatabaseHelper>,移除的含义是暂时无法操作,也可以说说是查询返回的数据都是空的。

2.MediaScannerCursor

一个自定义游标,用来查询媒体文件的扫描状态。主要有一个volume字段,用来区分是内置媒体数据库还是Sdcard的媒体数据库。

3.MediaScannerReceiver

此类实现广播接收者。接收到广播的时候对手机的媒体文件进行扫描。

public class MediaScannerReceiver extends BroadcastReceiver

{

private final static String TAG = "MediaScannerReceiver";

@Override

public void onReceive(Context context, Intent intent) {

String action = intent.getAction();

Uri uri = intent.getData();

String externalStoragePath = Environment.getExternalStorageDirectory().getPath();

//系统启动完毕

if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {

// scan internal storage

scan(context, MediaProvider.INTERNAL_VOLUME);

} else {

if (uri.getScheme().equals("file")) {

// handle intents related to external storage

String path = uri.getPath();

if (action.equals(Intent.ACTION_MEDIA_MOUNTED/*Sdcard挂载广播*/) &amp;&amp;

externalStoragePath.equals(path)) {

scan(context, MediaProvider.EXTERNAL_VOLUME);

} else if (action.equals(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE/*单个文件扫描广播*/) &amp;&amp;

path != null &amp;&amp; path.startsWith(externalStoragePath + "/")) {

scanFile(context, path);

}

}

}

}

扫描分为两种三种情况:

a,启动完毕扫面手机内存中的媒体文件

b.sdcard挂载完毕扫描扩展卡的媒体文件

c,扫描单个文件

应用实例:我们可以发送不同的广播让系统去扫描媒体文件。当需要扫描单个文件的时候需要设置一些参数,如下:


/**

* 扫描文件

*

* @param filePath 文件路径

* @author http://t.sina.com.cn/halzhang

*/

public void scanOneFile(final String filePath) {

Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);

Uri uri = Uri.parse("file://" + filePath);

intent.setData(uri);

sendBroadcast(intent);

}

接着看一下scanscenFile两个方法:

private void scan(Context context, String volume/*内置卡或者外置卡*/) {

Bundle args = new Bundle();

args.putString("volume", volume);

context.startService(

new Intent(context, MediaScannerService.class).putExtras(args));

}

private void scanFile(Context context, String path/*文件路径*/) {

Bundle args = new Bundle();

args.putString("filepath", path);

context.startService(

new Intent(context, MediaScannerService.class).putExtras(args));

}

两个方法都是启动MediaScannerService去扫描媒体文件的。

关于MediaScannerSerive且听下回分解。

——————-END————–

[转载]Android 使用AsyncTask 后监听异步加载完毕的动作

mikel阅读(893)

[转载]Android 使用AsyncTask 后监听异步加载完毕的动作 – Terry_龙 – 博客园.

AsyncTask 的使用方法网上有很多例子,使用起来也非常的方便。这里就不详细说具体的使用方法了,同学可以Google 一下,很多。

场景模拟

当我们在加载一个列表的时候,比如GridView ,这时候我们考虑到不阻塞UI的做法,一般会使用线程Thread 、Timer 或者使用AsyncTask ,而这些操作都是在在后台另外开一个线程给我们找数据,具体得到的数据需要使用Handler 去更新UI,AsyncTask 也是一样使用到的Handler 只是它将Handler 封装在了onPostExecute 执行操作中。而这一操作可能会产生一个问题,比如你有一个列表更新数据库使用到的是AsyncTask 异步操作的方式更新UI,而你的需求是当我一进来这个列表就统计这个列表的数据的数量或者让某一行数据的状态为选中状态。 传统做法是直接new 一个AsyncTask 类让它execute(); 之后会再操作UI。想法是对的,但有一个问题我们要注意到,因为它是异步加载数据的方式,而你的数据量比较多或许查找数据需要一定的时间的时候,这时使用 AsyncTask 执行异步加载后更新UI再操作UI对象,可能会报空指针。

这个问题的产生是,我们都知道代码的执行是自上而下执行,当你使用异步加载数据的时候,代码让你去执行异步操作就不管了(多线程),而继续会往下执行代 码,你下面的代码就是操作列表里面的UI,这时可想而知,异步加载数据还没有结束还没有对你的UI进行更新,这些你的列表应该是空的,而操作一个空的列表 就会报空指针。

分析问题

使用过AsyncTask 的同学都知道一个异步加载数据最少要重写以下这两个方法:

  • doInBackground   后台执行,比较耗时的操作都可以放在这里。注意这里不能直接操作UI。
  • onPostExecute   相当于Handler 处理UI的方式,在这里面可以使用在doInBackground 得到的结果处理操作UI。

有必要的话你还得重写以下这三个方法,但不是必须的:

  • onProgressUpdate   可以使用进度条增加用户体验度。
  • onPreExecute           这里是最终用户调用Excute时的接口
  • onCancelled             用户调用取消时,要做的操作

根据上面的思路,可以看出,最终数据加载并显示出来这一系列的操作都在onPostExecute  这个方法里面,那么如何监听所有UI都己经在onPostExecute   里面处理完成了,再去执行我们自己要操作呢?

解决问题

这里给出我自己解决这一问题的思路,有更好想法的朋友欢迎跟贴共同探讨。

首先创建一个接口

private interface isLoadDataListener {
public void loadComplete();
}

声明这一接口变量

private isLoadDataListener loadLisneter;

给接口赋值,得到接口对象

public void setLoadDataComplete(isLoadDataListener dataComplete) {
this.loadLisneter = dataComplete;
}

之后在AsyncTask 的onPostExecute处理UI完成后调用该接口,下面给出一个我以前项目使用到的AsyncTask 类:

class loadGridAsyncTask extends AsyncTask<Integer, Integer, AppsAdapter> {

private int poindex;

public loadGridAsyncTask(int positionindex) {
this.poindex = positionindex;
}

@Override
protected AppsAdapter doInBackground(Integer… params) {
// TODO Auto-generated method stub
// mAppsModel.clear();
Cursor temp = dbHelper.queryPageById(poindex);
loadPage(mApps, temp);
temp.close();
return new AppsAdapter(STB.this, mAppsModel);
}

@Override
protected void onPostExecute(AppsAdapter result) {

gridViewExt itemGrid = (gridViewExt) viewFlipper
.getChildAt(poindex);
itemGrid.setColumnCount(pageColumnCount);
itemGrid.setAdapter(result);
if (loadLisneter != null) {
loadLisneter.loadComplete();
}

}

}

通过上面的代码,我们就得到一个数据加载完成后返回的接口,接下来的问题就是我们利用这个接口来处理我们的UI了,比如让某一UI选中,得到这个列表的UI数量等,看下面的代码:

new loadGridAsyncTask(1).execute();
setLoadDataComplete(
new isLoadDataListener() {

@Override
public void loadComplete() {
// TODO Auto-generated method stub
//这里执行你要的操作,当UI更新完成后会自动调用这里面的代码                                        }
});

此篇文章希望能对入门不久的Android 开发者有帮助。

[转载]Android系统数据共享---Content Provider学习小结

mikel阅读(818)

[转载]Android系统数据共享—Content Provider学习小结 – 宁 静 致 远 – 博客园.

Content Provider作为Android应用程序中的四大组件之一,主要是为了实现在各应用程序之间数据共享,增强应用程序的复用,例如,在开发过程中,需要获取手机中的通讯录信息,这时完全不需要自己重新开发读取数据的整个过程,

而是直接访问系统自带的Content Provider对象来直接获取数据(此例子只是说明有现成的能满足需要的ContentProvider即可随时“拿来”,只要有相应权限, 不用管它是来自哪个应用程序里)。

在Content Provider使用过程中,还需要借用ContentResolver对象作为代理,间接操作ContentProvider来获取数据。 ContentProvider可以看作是对应着一个或多个数据表(类似查询后返回的数据集)。另外重要的概念还有Uri,通过它来找到相应的 ContentProvider、并能定位到对应的数据表或字段。

相关的知识经过整理,特列出下面这张思维导图,以方便自己经常温故,也希望能给大家带来一丝思维上的提示,也算是抛砖引玉了,呵呵

Content Provider知识

夜已经深了,明天又要上班了,面对新的一周,整理思绪,继续前行……

[转载]蛙蛙推荐:用.net 4.0构建新浪微博开发平台应用

mikel阅读(875)

[转载]蛙蛙推荐:用.net 4.0构建新浪微博开发平台应用 – 蛙蛙池塘 – 博客园.

摘要:现在很多互联网应用都推出了开放平台,开心人人新浪淘宝豆瓣腾讯,还有飞信也即将推出开放平台,大多数开发平台都会用OAuth认证,并提供返回json数据的Rest接口,用.NET 4.0的新特性来开发这些平台的应用有着天然的优势,一起来看看。

认证

要想使用开发平台的数据和接口,第一步肯定是登录和认证,OAuth被越来越多的开放平台所认可和使用,TerryLee已经介绍过在.NET里如何实现OAuth认证,不过是针对豆瓣平台的一些示例代码,新浪微博的OAuth认证有一些不同的地方,一会儿我会标识出来。sarlmolapple为新浪提供了新浪微博开放平台的c#版本SDK,但没有完全对新浪微博的所有接口进行支持。因此我借鉴了两位的代码写了个针对新浪微博的精简版的OAuth认证,因为简单,如果大家想开发其它开放平台的应用,也可以很快比猫画虎做出来。

首先还是要下载别人写好的OAuthBase.cs文 件,然后继承该类写一些自定义的逻辑,后来发现只通过子类来实现自定义逻辑还不好使,所以迫不得已还是修改了OAuthBase.cs类,因为新浪微博开 放平台要求生成OAuth签名时,多了一个Verifier参数,所以在生成basestring时(GenerateSignatureBase方法) 要加上这个参数。

//NOTE:add by onlytiancai@gmail.com 兼容sina登录,参考了http://code.google.com/p/opensinaapi/
if (!string.IsNullOrEmpty(Verifier) &amp;&amp; httpMethod == "GET")
{
parameters.Add(new QueryParameter(oAauthVerifier, Verifier));
}

准备一下OAuth认证的配置项,这些配置项大家可以在数据库里配置或者写个自定义配置节在Web.config里配置,为了简单,我就先写死了,如下:

public class OAuthConfig
{
public static OAuthConfig Instance = new OAuthConfig();
public OAuthConfig()
{
RequestTokenUri = "http://api.t.sina.com.cn/oauth/request_token";
AuthorizeUri = "http://api.t.sina.com.cn/oauth/authorize";
AccessTokenUri = "http://api.t.sina.com.cn/oauth/access_token";
ApiKey = "xxxx"; //在新浪微博开放平台申请
ApiKeySecret = "xxxxxxxxxxxxxxxxxx";//在新浪微博开放平台申请
}
public string RequestTokenUri { get; set; }

public string ApiKey { get; set; }

public string ApiKeySecret { get; set; }

public string AuthorizeUri { get; set; }

public string AccessTokenUri { get; set; }
}

请求open api接口时一般要拼写PostData格式的数据,而PostData的格式是类似”?p1=a&p2=b&p3=c”,这种格式,要手动拼写这样格式的字符串很麻烦,所以我们想要一种优雅的方式,比如:

get_request(new {p1="a", p2="b", p3="c"})

这个参数是一个C# 3.5的匿名类,然后在get_request方法里可以通过反射获取到属性名和值,构建成PostData格式,但这样做性能会稍差一些,我们可以用另外一种相近的格式,如下:

get_request(new Pairs { { "p1", "a" }, {"p2", "b"}, "p3", "c" } )

这里的参数是一个Pairs类,后面的大括号套着大括号是.net 3.5的集合初始化语法,这个Pairs是我在(Using C# 3.0 Anonymous Types as Dictionaries )这篇博客的评论里看到的,我略微修改了一下,如下:

public class Pairs : List&gt;
{
public void Add(String key, String value)
{
Add(new KeyValuePair(key, value));
}

public string ToPostData(Func valueHandler = null)
{
StringBuilder sb = new StringBuilder();
foreach (var pair in this)
{
if (sb.Length &gt; 0)
sb.Append("&amp;");
sb.AppendFormat("{0}={1}", pair.Key, valueHandler == null
? pair.Value : valueHandler(pair.Value));
}
return sb.ToString();
}
}

我只增加了一个ToPostData的方法,其中有一个可选的参数用来在拼接PostData时对参数值进行处理,可选参宿是.net 4.0提供的机制,如果不提供这个参数,我就按原文拼写参数值

我继承OAuthBase写了一个OAuthHelper类,其中OAuthRequest方法用来发送一个OAuth请求,如下:

public string OAuthRequest(string uri, Pairs data = null, string method = "GET")
{
//1、准备获取签名所需参数
string nonce = GenerateNonce();
string timeStamp = GenerateTimeStamp();
if (data == null)
data = new Pairs();
data.Add("source", OAuthConfig.Instance.ApiKey); //添加默认的source参数
uri += uri.IndexOf("?") &gt; 0 ? "&amp;" : "?";
uri += data.ToPostData(str =&gt; UrlEncode(str)); //防止参数里有",%,="等字符

//2、获取签名
string normalizeUrl, normalizedRequestParameters;
string sig = GenerateSignature(
new Uri(uri),
OAuthConfig.Instance.ApiKey,
OAuthConfig.Instance.ApiKeySecret,
Token,
TokenSecret,
method,
timeStamp,
nonce,
OAuthBase.SignatureTypes.HMACSHA1,
out normalizeUrl,
out normalizedRequestParameters);
normalizedRequestParameters = normalizedRequestParameters + "&amp;"
+ OAuthSignatureKey + "=" + HttpUtility.UrlEncode(sig);

//3、发送WEB请求并接受数据
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(
method == "POST" ? normalizeUrl : normalizeUrl + "?" + normalizedRequestParameters);
request.Method = method;
if (method == "POST")
{
using (StreamWriter sw = new StreamWriter(request.GetRequestStream()))
{
sw.Write(normalizedRequestParameters);
}
}

using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
{
using (StreamReader reader = new StreamReader(request.GetResponse()
.GetResponseStream(), System.Text.Encoding.UTF8))
{
return reader.ReadToEnd();
}
}
}

具体步骤我都做了注释,其中 GenerateNonce,GenerateTimeStamp,UrlEncode,GenerateSignature都是父类提供的方法,直接调 用即可,根据名字也很容易想出它的功能,如果你做的不是新浪微博开放平台的应用,这里可能要相应的做一些小改动。

然后是进行OAuth的三步认证,第一步获取RequestToken,第二步让用户进行授权,一般情况下会提示用户登录新浪微博或者提示用户授权或拒绝授权你的应用访问用户的新浪微博数据,第三步就是拿到AccessToken去访问受限的资源,具体过程大家可以看这里。有了上面我们写的方法,这三步就很简单了。

///
/// 登录第一步:获取RequestToken
///
public void GetRequestToken()
{
string response = OAuthRequest(OAuthConfig.Instance.RequestTokenUri);
if (response.Length &gt; 0)
{
NameValueCollection qs = HttpUtility.ParseQueryString(response);
if (qs[OAuthTokenKey] != null)
{
this.Token = qs[OAuthTokenKey];
this.TokenSecret = qs[OAuthTokenSecretKey];
}
}
}

///
/// 登录第二部:Response.Redirect到这个页面,提示用户授权
///
///
用户认证成功后返回的地址 ///
public string GetAuthorizeUri(string callbackUri)
{
return string.Format("{0}?oauth_token={1}&amp;&amp;oauth_callback={2}",
OAuthConfig.Instance.AuthorizeUri,
this.Token, callbackUri);
}

///
/// 登录第三部:获取AccessToken,成功登录
///
public void GetAccessToken()
{
string response = OAuthRequest(OAuthConfig.Instance.AccessTokenUri);
if (response.Length &gt; 0)
{
NameValueCollection qs = HttpUtility.ParseQueryString(response);
if (qs[OAuthTokenKey] != null)
{
this.Token = qs[OAuthTokenKey];
this.TokenSecret = qs[OAuthTokenSecretKey];
}
}
}

其中Token和TokenSecret是OAuthHelper的两个string类型成员。

获取JSON数据

一般开放平台的接口可以返回xml格式或JSON格式的数据,如果解析XML数据会很不通用,每一个接口需要写一个方法,而返回json的 话.NET 4.0提供的JavaScriptSerializer可以对json数据进行序列化和反序列化,json格式的数据可以反序列化成 Dictionary<string, object>类型或其类型的数组,其中object又是一个Dictionary<string, object>,但我们对Dictionary的操作会很麻烦,我们想办法吧它转换成一个C# 4.0的dynamic类型,这里有篇帖子(Turning JSON into a ExpandoObject )介绍了如何去做,我在此基础上改动了一下,并提供了一个直接把open api返回的数据转换成ExpandoObject的方法,如下。

///
/// 发送OAuth请求,并把返回的json结果转换成dynamic类型
///
///
请求地址,请确保返回json数据 ///
请求数据 ///
HttpMethod ///
public ExpandoObject OAuthRequestDynamic(string uri, Pairs data = null, string method = "GET")
{
string result = OAuthRequest(uri, data, method);
if (result == null)
return null;
return Expando(result);
}

public static ExpandoObject Expando(string json)
{
JavaScriptSerializer serializer = new JavaScriptSerializer();
object obj = serializer.DeserializeObject(json);
if (obj is ICollection)
return Expando(new Dictionary() { { "arr", obj } });
else
return Expando((Dictionary)obj);
}

//http://coderjournal.com/2010/07/turning-json-into-a-expandoobject/
private static ExpandoObject Expando(IDictionary dictionary)
{
var expando = new ExpandoObject();
var expandoDic = (IDictionary)expando;

foreach (var item in dictionary)
{
bool alreadyProcessed = false;

if (item.Value is IDictionary)
{
expandoDic.Add(item.Key, Expando((IDictionary)item.Value));
alreadyProcessed = true;
}
else if (item.Value is ICollection)
{
var itemList = new List<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" width="100" height="100" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0"><embed type="application/x-shockwave-flash" width="100" height="100"> </embed></object>

if (itemList.Count &gt; 0)
{
expandoDic.Add(item.Key, itemList);
alreadyProcessed = true;
}
}
if (!alreadyProcessed)
expandoDic.Add(item);
}

return expando;
}

这下可好了,访问opan api,直接返回的就是带类型的了,省了解析的工序了,和Python差不多,直接写出成员就行。

开发第一个应用

去新浪微博开放平台注册一个账户,得到app key和app key secret,然后就可以开发测试应用了,新建一个Default.aspx页,摆放如下控件。

<div><img src="&lt;%# Eval(" alt="" />" alt="" /&gt;
<strong>&lt;%# Eval("user.name") %&gt;:</strong>&lt;%# Eval("text")%&gt;(&lt;%# Eval("created_at")%&gt;)

</div>

控件的功能根据名字可以推测出来,先看登录

protected void btnLogin_Click(object sender, EventArgs e)
{
OAuthHelper oauth = new OAuthHelper();
oauth.GetRequestToken();
Session["oauth_token"] = oauth.Token;
Session["oauth_token_secret"] = oauth.TokenSecret;
string url = oauth.GetAuthorizeUri("http://localhost:1812/");
Response.Redirect(url);
}

先获取RequestToken,然后获取用户认证页地址,重定向过去,提示用户登录,用户登录完了之后会返回到calback uri,这里填写是本页,新浪会在返回的页面上提供oauth_verifier参数,如果有这个参数就说明是用户登录成功返回的,所以我们要在 Page_Load里做一些处理,困了,就不写那么多说明了,大家看看,大多都是借鉴了sarlmolapple的示例代码。

protected void Page_Load(object sender, EventArgs e)
{
Session[Guid.NewGuid().ToString()] = 1; //生成cookie里的sessionid,防止Redirect回来丢失Session
if (!Page.IsPostBack)
{
OAuthHelper oauth = new OAuthHelper();
if (Request["oauth_verifier"] != null)
{
oauth.Token = Session["oauth_token"].ToString();
oauth.TokenSecret = Session["oauth_token_secret"].ToString();
oauth.Verifier = Request["oauth_verifier"];
oauth.GetAccessToken();
Session["oauth_token"] = oauth.Token; ;
Session["oauth_token_secret"] = oauth.TokenSecret;

Response.Redirect("/Default.aspx");
}
if (Session["oauth_token"] != null)
{
this.lblMessage.Text = "登录成功";
this.btnLogin.Visible = false;
this.btnTimeline.Visible = true;
}
else
{
this.lblMessage.Text = "未登录";
this.btnLogin.Visible = true;
this.btnTimeline.Visible = false;
}
}
}

登录成功后,我们获取public_timeline,并直接绑定在Repeater上,不做任何解析,你猜能不能用。

protected void btnTimeline_Click(object sender, EventArgs e)
{
OAuthHelper oauth = new OAuthHelper();
oauth.Token = Session["oauth_token"].ToString();
oauth.TokenSecret = Session["oauth_token_secret"].ToString();
dynamic json = oauth.OAuthRequestDynamic(
"http://api.t.sina.com.cn/statuses/public_timeline.json",
new Pairs { { "count", "5" } });
rpt1.DataSource = json.arr;
rpt1.DataBind();
}

猜对了,不能用,但是加上如下代码就能用了

protected new object Eval(string expression)
{
string[] arr = expression.Split('.');
object obj = this.Page.GetDataItem();
IDictionary items = obj as IDictionary;
if (items != null)
{
if (arr.Length == 1)
return items[arr[0]];
else if (arr.Length == 2)
return ((IDictionary)items[arr[0]])[arr[1]];
else
throw new NotSupportedException("哥,表达式too长了,不支持呀。");
}
else return DataBinder.Eval(this.Page.GetDataItem(), expression);
}

以上代码是在网上找到的一段,出处忘了,我在它的基础上修改了一下,支持了json的二级数据的绑定,可以直接在Eval里写“user.name”这样的表达式。

public_timeline返回的json样子大概是如下:

[
{
"created_at" : "Tue Nov 30 14:34:35 +0800 2010",
"text" : "吃力不讨好的事情我是坚决不会再做了,RI你个仙人!发飙~~~~我只想说档次和素质在那里去了,
你也就只能在这种地方混!",
"truncated" : false,
"in_reply_to_status_id" : "",
"annotations" :
[

],
"in_reply_to_screen_name" : "",
"geo" : null,
"user" :
{
"name" : "习惯寂寞吗",
"domain" : "",
"geo_enabled" : true,
"followers_count" : 5,
"statuses_count" : 61,
"favourites_count" : 0,
"city" : "1",
"description" : "",
"verified" : false,
"id" : 1676792942,
"gender" : "f",
"friends_count" : 26,
"screen_name" : "习惯寂寞吗",
"allow_all_act_msg" : false,
"following" : false,
"url" : "http://1",
"profile_image_url" : "http://tp3.sinaimg.cn/1676792942/50/1284648784",
"created_at" : "Wed Dec 30 00:00:00 +0800 2009",
"province" : "51",
"location" : "四川 成都"
},
"favorited" : false,
"in_reply_to_user_id" : "",
"id" : 3978753419,
"source" : "<a rel="\&quot;nofollow\&quot;" href="\">新浪微博</a>"
},
...
]

最后来张图吧

[原创]修改SQLServer2008的内置账户方法

mikel阅读(1254)

刚装的SQLServer2008选择的内置账户是Network Services ,可是在附加数据库的时候,提示无权限访问文件,去google了一下发现时内置账户的问题,于是更改了内置账户为Local Services 结果 SQL Server服务没办法启动了!

于是去控制面板->管理工具->服务中将SQL Server的账户改为内置账户,重启服务,附加数据库成功!

[转载]Asp.NET MVC Widget开发 - Html.Widget扩展方法

mikel阅读(970)

[转载]Asp.NET MVC Widget开发 – Html.Widget扩展方法 – 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支持
  5. Asp.NET MVC Widget – Html.Widget扩展方法

前4篇文章中,已实现Widget具体开发,并支持当前流行的手机客户端,以及如何在页面中使用:

@{Html.RenderAction(“Friends”, “Widget”); }

每次这样总感觉有些烦琐,而我们想要的只是指定Widget名称就行了:

@Html.Widget(“Friends”)

实现方法是很简单,扩展HtmlHelper方法

具体代码:

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

namespace System.Web.Mvc.Html
{
public static class WidgetExtensions
{
public static MvcHtmlString Widget(this HtmlHelper htmlHelper, string widget)
{
htmlHelper.RenderAction(widget, "Widget");
return MvcHtmlString.Empty;
}
}
}

部署和安装SQL Server 2008数据库(二)

mikel阅读(1015)

SQL Server 2008SQL Server 2005安装过程有许多不同,比如网络环境的问题、账户的问题、安装向导的问题等等。目前很多公司的服务器从Windows Server 2003也逐渐升级升级到了2008,数据库升级到SQL Server 2008 也在所难免,所以平时有很多朋友在问部署SQL Server 2008过程中的问题,笔者在这里进行详细总结。
SQL Server 2008 安装的环境如下:
1, 工作组环境下的服务器
2, 域环境下的域控制器
3, 域环境下的成员服务器
4, 群集环境
事实上从开发和应用的角度讲,很多devoloper很少接触基于域控制器的开发应用,所以他们的应用多半都跑在工作环境下的服务器上,IIS中会发布很多网站,服务器本身会承载数据库的角色等,此时会涉及到在工作组环境下安装SQL Server 2008。但微软平台企业级应用开发很少有域不参与的地方,比如微软的CRMSharePointuc等产品的二次开发、.NET平台为了适应企业环境和组织架构在域模式下的应用开发等,此时会涉及到在域控制器、成员服务器和群集中安装SQL Server 2008
无论是哪一种环境下安装SQL Server 2008,都会碰到为每个SQL Server 2008服务配置账户的问题。安装SQL Server 2008时会用的账户如下:
1, 本地用户帐户
2, 域用户帐户
3, 内置系统帐户
l Network Service 帐户(网络服务)
l Local Service 帐户(本地服务)
l Local System 帐户(本地系统)
本地用户帐户
工作组环境,如果计算机不在域中,则建议使用不具有 Windows 管理员权限的本地用户帐户。
域用户帐户
如果服务必须与网络服务进行交互,则访问类似于文件共享的域资源;如果服务使用到运行 SQL Server 的其他计算机的链接服务器连接,则可以使用具有最低特权的域帐户。许多服务器到服务器的活动只能使用域用户帐户来执行。此帐户应由域管理员在域环境内预先创建。
Local Service 帐户:
Local Service 帐户是一个内置帐户,与 Users 组的成员具有相同级别的资源和对象访问权限。如果有个别服务或进程的安全性受到威胁,则此有限访问权限有助于保护系统的安全性。以 Local Service 帐户身份运行的服务将以一个没有凭据的 Null 会话形式访问网络资源。请注意,SQL Server SQL Server 代理服务不支持 Local Service 帐户。该帐户的实际名称为“NT AUTHORITY\LOCAL SERVICE”
Network Service 帐户:
Network Service 帐户是一个内置帐户,比 Users 组的成员拥有更多的对资源和对象的访问权限。以 Network Service 帐户身份运行的服务将使用计算机帐户的凭据访问网络资源。该帐户的实际名称为“NT AUTHORITY\NETWORK SERVICE”
Local System 帐户:
Local System 是一个具有高特权的内置帐户。它对本地系统有许多权限并作为网络上的计算机。该帐户的实际名称为“NT AUTHORITY\SYSTEM”
如果是在工作组环境下安装SQL Server 2008:
使用的账户是本地用户帐户,内置账户(网络服务账户、本地系统账户等)。
在域环境上(涵盖成员服务器)安装 SQL Server 2008:
出于安全方面的考虑,Microsoft 建议不要将 SQL Server 2008 安装在域控制器上。 SQL Server 安装程序不会阻止在作为域控制器的计算机上进行安装,但存在以下限制:
Windows Server 2003 上,SQL Server 服务可在域帐户或本地系统帐户下运行。
在域控制器上,无法在本地服务帐户或网络服务帐户下运行 SQL Server 服务。此时用的账户一般是域账户和本地系统账户。
SQL Server 安装到计算机上之后,无法将此计算机从域成员更改为域控制器。 必须先卸载 SQL Server,然后才能将主机计算机更改为域控制器。
SQL Server 安装到计算机上之后,无法将此计算机从域控制器更改为域成员。 必须先卸载 SQL Server,然后才能将主机计算机更改为域成员。
在群集节点用作域控制器的情况下,不支持 SQL Server 故障转移群集实例。
SQL Server 安装程序不能在只读域控制器上创建安全组或设置 SQL Server 服务帐户。 在这种情况下,安装将失败。
SQL Server 2008 安装程序安装该产品所需的以下软件组件:
l .NET Framework 3.5 SP1
l SQL Server Native Client
l SQL Server 安装程序支持文件
l SQL Server 安装程序要求使用 Microsoft Windows Installer 4.5 或更高版本
如果安装SQL Server 2008之前已经装了Visual Studio 2008,安装时出现错误,请先安装Visual Studio 2008 SP1
SQL Server 2008安装SSRS服务时,不再像SQL Server 2005必须要先安装IIS,安装SQL Server 2008不在需要IIS的支持。
下面我们以域环境下的域控制器为例安装SQL Server 2008
步骤1:启动安装程序后,如下图:

步骤2:在SQL Server 2008装中心的计划里面,可以用系统配置检查器检测服务器是否具备条件安装SQL Server 2008系统配置检查器在安装过程中会被安装程序多次调用。还可以通过在早期SQL Server版本的服务器上安装安装升级顾问来自动帮助你分析升级需要的成本和能升级什么服务,安装完安装升级顾问后,可以连接到早期版本的实例进行分析。这里单击系统配置检查器来检测系统。

系统配置检查器检测的情况如下图:

步骤3:单击安装后,在这里我们可以选择全新安装SQL Server 2008,安装群集,升级安装等。这里单击全新SQL Server独立安装或向现有安装添加功能,如下图:

步骤4:可以使用180天的企业评估版等,也可以输入你购买的产品密钥进行授权安装。

输入产品密钥:

如果你输入的是企业版的密钥就会看到企业版的许可条款,如果输入的是开发版的密钥就会看到开发版的许可条款:

步骤5:安装程序支持文件,如下图:

步骤6:安装程序支持规则,如下图:

步骤7:功能选择,根据需要选择想要安装的功能,如下图:

步骤8:可以配置成默认实例或者命名实例,如果默认实例还没被以前安装的实例占用,则可以使用默认实例。如下图:

可以修改命名实例的实例ID

我们这里使用默认实例进行安装:

步骤9:磁盘空间要求,如下图:

步骤10:配置各个服务使用的账户,如下图:

步骤11:配置排序规则,默认即可,如下图:

步骤12:选中“混合模式”,指定sa账户的密码,并指定SQL Server管理员,通过单击“添加当前用户”选择当前账户即可,也可以选择其他的账户。如下图:

步骤13:配置数据库引擎的数据目录的路径,根据需求修改,可以把各个目录的路径配置到不同的磁盘,减小I/O的负载。这里采用默认。如下图:

步骤14:可以启用FilesStream特性,这里默认没有启用,如下图:

步骤15:为Analysis Services服务指定管理员,这里选择当前用户,如下图:

步骤16:配置Analysis Services服务的数据目录,这里采用默认配置,如下图:

步骤17Reporting Serivices配置,默认即可,如下图:

步骤18:错误和使用报告,如下图
步骤19:检测安装规则,看是否能顺利安装,如下图:
步骤20:准备安装,如下图:
步骤21:安装进度,慢慢等吧 ,^_^。如下图:
步骤22:各个功能都安装完成,如下图:
步骤23:安装完成,如下图:
本节结束。

本文出自 “李涛的技术专栏” 博客,请务必保留此出处http://terryli.blog.51cto.com/704315/439573