[转载]ASP.NET MVC URL重写与优化(初级篇)-使用Global路由表定制URL

mikel阅读(1023)

[转载]ASP.NET MVC URL重写与优化(初级篇)-使用Global路由表定制URL – 菊花台泡茶 – 博客园.

引言—

在现今搜索引擎制霸天下的时代,我们不得不做一些东西来讨好爬虫,进而提示网站的排名来博得一个看得过去的流量。

URL优化就是搜索引擎优化的手段之一。

如果某站长的手机网站(基于ASP.NET MVC)分类页面URL是这样的,http://www.xxx.com/category /showcategory?categoryid=1000&view=list&orderby=price& page=1。

他看了一些SEO的资料后向手下的程序猿提出,URL要简短:http://www.xxx.com/category/1000。

等他对SEO更了解之后又提出,URL要语义化,更好的反应网站结构:http://www.xxx.com/categoryname。

这里将分为两篇来解决这两个问题。

  • 初级篇-使用Global路由表定制URL
  • 进阶篇-继承RouteBase玩转URL(传送门还在修,敬请期待)

这里我假设各位看官已经对ASP.NET MVC Web程序的运行机制已经有所了解。文中有任何问题欢迎指正。

———————————————引言END—————————————–

 

一,认识Global路由表

我们新建一个ASP.NET MVC Web程序的时候,会生成一个Global.asax文件。如下:

View Code

首先Application_Start()是Web应用程序启动的时候的入口。

  RegisterGlobalFilters()方法是用来注册全局筛选器的。这个与此篇内容无关。

  而RegisterRoutes()方法是用来注册路由表的

  这里已经有了两条默认的路由规则:

routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

  IgnoreRoute()是RouteCollection路由表类的扩展方法,用于忽略指定的路由请求。这句意思是忽略对扩展名为.axd文件的请求。这个方法不在此详述了。

 

 routes.MapRoute( "Default", // 路由名称
                "{controller}/{action}/{id}", // 带有参数的 URL
                new { controller = "Home", action = "Index", id = UrlParameter.Optional } // 参数默认值
            );

 

MapRoute()方法也是RouteCollection类的一个扩展方法。这里是它最常用的一个重载,映射指定的 URL 路由并设置默认路由值:

  • Default代表的是要映射的路由的名称,这个名称在应用程序的路由的集合(routes对象)中是唯一的,如果重名生成时就会报错。
  • {controller}/{action}/{id}代表的是路由的 URL 模式。通过{}来定义的是占位符。
  •  new { controller = “Home”, action = “Index”, id = UrlParameter.Optional } 声明了一个包含默认路由值的object匿名对象controller,action,id则对应上述的占位符。

这条语句设置了一个最基本的默认路由规则,通过controller名,action名和可选参数来进行路由匹配。

  发布网站的时候Global.asax文件会被编译成DLL。程序启动的时候就会首先调用Application_Start()方法,我们的路由表注册后,默认路由规则就生效了。

如果没有什么特殊要求的系统,比如网站后台,就不用再折腾了,一条默认路由规则足矣。

 

二,路由匹配规则

  1.首先我们需要科普一下如何来定义一条URL模式。

首先URL模式都是相对的,不包括主机域名部份。{}保存的是占位符,“/”,“.”则用来作为分隔符。什么都有没则是静态内容。比如:

  • URL /category/showcategory/1000  匹配 {controller}/{action}/{id}
  • URL /product/2012/4/28.html         匹配  “/product/{year}/{month}/{day}.html” 则对应的像 ,诸如此类。

这里需要注意的是{controller}{action}是保留的两个占位符,分别代表对应的控制器名称和操作名称。

{controller}对应控制器的名称,这里规定是CategoryController去掉Controller后缀的部份,即Category

{action}对应控制器内的Action方法的名称。

 

2.路由有两种不同的操作。

URL定向,当你在浏览器输入一个URL时,程序会在我们添加的路由表中通过对比URL模式进行匹配,找到对应的包含路由值的对象,从而定位到具体的资源上。

我们来看一个例子,我们来添加两条路由规则。

routes.MapRoute("Test", "where-are-you-going", new { controller = "Home", action = "Index" }); routes.MapRoute("Test1", "where-are-you-going", new { controller = "Home", action = "Others" });

假设HomeController里有两个Action 分别是IndexOthers

各位看官觉得在浏览器输入http://www.xxx.com/where-are-you-going 会进入哪个方法体中呢?如果上下颠倒一下呢?

 

获取URL,下面这段代码就使用Url.Action方法通过Controller和Action名完全限定了一个URL,

即我们有了一个路由值,通过在路由表中匹配,可以找到对应的URL模式,进而生成一个URL

<a href="@Url.Action("Index", "Home")">主页</a>

现在我们在默认的路由规则下再添加一条如下的路由规则

routes.MapRoute("MyHome", "myhome/{id}", new { controller = "Home", action = "Index", id = UrlParameter.Optional });

你觉得页面会显示什么呢?

<a href="http://www.xxx.com/">主页</a> or <a href="http://www.xxx.com/myhome">主页</a>

如果把这条路由规则写在默认规则的上面呢?

 

3.上面的问题大家可以自己尝试一下,很明显,路由的匹配是 自上而下 的,只要匹配到第一条记录,就会返回对应URL或者路由值。

  这一点非常重要。很多人在定制路由规则的时候,总是发现自己的规则不生效。那么你就应该检查是不是被前面的路由覆盖掉了。

 

三,解决开始的问题

从这个站的URL可以看出,http://www.xxx.com/category/showcategory?categoryid=1000&view=list&orderby=price&page=1,

用的应该只是默认路由规则,可以推断出有一个名为Category的控制器,其中有个方法名为ShowCategory,必选参数为categoryid,其他为可选参数。

按照站长的要求,我们只需要添加一条路由规则就完事了。是不是很简单?

routes.MapRoute("Category", "category/{categoryID}", new { controller = "Category", action = "ShowCategory" }

这时候一定要注意喔,不要写在默认路由的下面,你懂得。不然你就悲剧了。

 

———————————————END—————————————–

 

强烈建议大家自己动手玩一玩Global文件,写一些路由规则来亲身感受一下。其实有一个悬念我留给大家了,

为什么默认规则下获取首页的URL路由是“/”而不是“/home/index”呢。^_^

其实我自己也没有玩的很精,只是比较熟悉,有任何错误之处请指正。

使用Global还是有很多的局限性的,http://www.xxx.com/categoryname 像这样,每个分类都有自己的名字,不可能写在路由表中。

进阶篇中会教大家如何通过继承RouteBase类来完全Hold住场面。祝大家五一快乐。回来再写。。。

[转载]Json2String jQuery扩展

mikel阅读(890)

[转载]Json2String – 高捍得 – 博客园.

 

//扩展jQuery对json字符串的转换

jQuery.extend({ /** * @see 将json字符串转换为对象 * @param json字符串 * @return 返回object,array,string等对象 */

evalJSON: function (strJson) {

return eval("(" + strJson + ")");

}

});

jQuery.extend({ /** * @see 将javascript数据类型转换为json字符串 * @param 待转换对象,支持object,array,string,function,number,boolean,regexp * @return 返回json字符串 */

toJSON: function(object) { var type = typeof object; if ('object' == type) { if (Array == object.constructor) type = 'array'; else if (RegExp == object.constructor) type = 'regexp'; else type = 'object'; } switch (type) { case 'undefined': case 'unknown': return; break; case 'function': case 'boolean': case 'regexp': return object.toString(); break; case 'number': return isFinite(object) ? object.toString() : 'null'; break; case 'string': return '"' + object.replace(/(\\|\")/g, "\\$1").replace(/\n|\r|\t/g, function() { var a = arguments[0]; return (a == '\n') ? '\\n': (a == '\r') ? '\\r': (a == '\t') ? '\\t': "" }) + '"'; break; case 'object': if (object === null) return 'null'; var results = []; for (var property in object) { var value = jQuery.toJSON(object[property]); if (value !== undefined) results.push(jQuery.toJSON(property) + ':' + value); } return '{' + results.join(',') + '}'; break; case 'array': var results = []; for (var i = 0; i < object.length; i++) { var value = jQuery.toJSON(object[i]); if (value !== undefined) results.push(value); } return '[' + results.join(',') + ']'; break; } } });

上述代码 放在一个js文件。

通过 $.evalJSON() 和$.toJSON() 调用两个方法

[转载]Android应用自动更新功能的代码实现

mikel阅读(750)

[转载]Android应用自动更新功能的代码实现 – coolszy – 博客园.

由于Android项目开源所致,市面上出现了N多安卓软件市场。为了让我们开发的软件有更多的用户使用,我们需要向N多市场发布,软件升级后,我们也必须到安卓市场上进行更新,给我们增加了工作量。因此我们有必要给我们的Android应用增加自动更新的功能。

既然实现自动更新,我们首先必须让我们的应用知道是否存在新版本的软件,因此我们可以在自己的网站上放置配置文件,存放软件的版本信息:

<update>
    <version>2</version>
    <name>baidu_xinwen_1.1.0</name>
    <url>http://gdown.baidu.com/data/wisegame/f98d235e39e29031/baiduxinwen.apk</url>
</update>

在这里我使用的是XML文件,方便读取。由于XML文件内容比较少,因此可通过DOM方式进行文件的解析:

public class ParseXmlService
{
public HashMap parseXml(InputStream inStream) throws Exception
{
HashMap hashMap = new HashMap();

// 实例化一个文档构建器工厂
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
// 通过文档构建器工厂获取一个文档构建器
DocumentBuilder builder = factory.newDocumentBuilder();
// 通过文档通过文档构建器构建一个文档实例
Document document = builder.parse(inStream);
//获取XML文件根节点
Element root = document.getDocumentElement();
//获得所有子节点
NodeList childNodes = root.getChildNodes();
for (int j = 0; j &lt; childNodes.getLength(); j++)
{
//遍历子节点
Node childNode = (Node) childNodes.item(j);
if (childNode.getNodeType() == Node.ELEMENT_NODE)
{
Element childElement = (Element) childNode;
//版本号
if ("version".equals(childElement.getNodeName()))
{
hashMap.put("version",childElement.getFirstChild().getNodeValue());
}
//软件名称
else if (("name".equals(childElement.getNodeName())))
{
hashMap.put("name",childElement.getFirstChild().getNodeValue());
}
//下载地址
else if (("url".equals(childElement.getNodeName())))
{
hashMap.put("url",childElement.getFirstChild().getNodeValue());
}
}
}
return hashMap;
}
}

通过parseXml()方法,我们可以获取服务器上应用的版本、文件名以及下载地址。紧接着我们就需要获取到我们手机上应用的版本信息:

/**
* 获取软件版本号
*
* @param context
* @return
*/
private int getVersionCode(Context context)
{
int versionCode = 0;
try
{
// 获取软件版本号,
versionCode = context.getPackageManager().getPackageInfo("com.szy.update", 0).versionCode;
} catch (NameNotFoundException e)
{
e.printStackTrace();
}
return versionCode;
}

通过该方法我们获取到的versionCode对应AndroidManifest.xml下android:versionCode。 android:versionCode和android:versionName两个属性分别表示版本号,版本名称。versionCode是整数型, 而versionName是字符串。由于versionName是给用户看的,不太容易比较大小,升级检查时,就可以检查versionCode。把获取 到的手机上应用版本与服务器端的版本进行比较,应用就可以判断处是否需要更新软件。

处理流程


处理代码

package com.szy.update;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;

import android.app.AlertDialog;
import android.app.Dialog;
import android.app.AlertDialog.Builder;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.DialogInterface.OnClickListener;
import android.content.pm.PackageManager.NameNotFoundException;
import android.net.Uri;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.Toast;

/**
*@author coolszy
*@date 2012-4-26
*@blog http://blog.92coding.com
*/

public class UpdateManager
{
/* 下载中 */
private static final int DOWNLOAD = 1;
/* 下载结束 */
private static final int DOWNLOAD_FINISH = 2;
/* 保存解析的XML信息 */
HashMap mHashMap;
/* 下载保存路径 */
private String mSavePath;
/* 记录进度条数量 */
private int progress;
/* 是否取消更新 */
private boolean cancelUpdate = false;

private Context mContext;
/* 更新进度条 */
private ProgressBar mProgress;
private Dialog mDownloadDialog;

private Handler mHandler = new Handler()
{
public void handleMessage(Message msg)
{
switch (msg.what)
{
// 正在下载
case DOWNLOAD:
// 设置进度条位置
mProgress.setProgress(progress);
break;
case DOWNLOAD_FINISH:
// 安装文件
installApk();
break;
default:
break;
}
};
};

public UpdateManager(Context context)
{
this.mContext = context;
}

/**
* 检测软件更新
*/
public void checkUpdate()
{
if (isUpdate())
{
// 显示提示对话框
showNoticeDialog();
} else
{
Toast.makeText(mContext, R.string.soft_update_no, Toast.LENGTH_LONG).show();
}
}

/**
* 检查软件是否有更新版本
*
* @return
*/
private boolean isUpdate()
{
// 获取当前软件版本
int versionCode = getVersionCode(mContext);
// 把version.xml放到网络上,然后获取文件信息
InputStream inStream = ParseXmlService.class.getClassLoader().getResourceAsStream("version.xml");
// 解析XML文件。 由于XML文件比较小,因此使用DOM方式进行解析
ParseXmlService service = new ParseXmlService();
try
{
mHashMap = service.parseXml(inStream);
} catch (Exception e)
{
e.printStackTrace();
}
if (null != mHashMap)
{
int serviceCode = Integer.valueOf(mHashMap.get("version"));
// 版本判断
if (serviceCode &gt; versionCode)
{
return true;
}
}
return false;
}

/**
* 获取软件版本号
*
* @param context
* @return
*/
private int getVersionCode(Context context)
{
int versionCode = 0;
try
{
// 获取软件版本号,对应AndroidManifest.xml下android:versionCode
versionCode = context.getPackageManager().getPackageInfo("com.szy.update", 0).versionCode;
} catch (NameNotFoundException e)
{
e.printStackTrace();
}
return versionCode;
}

/**
* 显示软件更新对话框
*/
private void showNoticeDialog()
{
// 构造对话框
AlertDialog.Builder builder = new Builder(mContext);
builder.setTitle(R.string.soft_update_title);
builder.setMessage(R.string.soft_update_info);
// 更新
builder.setPositiveButton(R.string.soft_update_updatebtn, new OnClickListener()
{
@Override
public void onClick(DialogInterface dialog, int which)
{
dialog.dismiss();
// 显示下载对话框
showDownloadDialog();
}
});
// 稍后更新
builder.setNegativeButton(R.string.soft_update_later, new OnClickListener()
{
@Override
public void onClick(DialogInterface dialog, int which)
{
dialog.dismiss();
}
});
Dialog noticeDialog = builder.create();
noticeDialog.show();
}

/**
* 显示软件下载对话框
*/
private void showDownloadDialog()
{
// 构造软件下载对话框
AlertDialog.Builder builder = new Builder(mContext);
builder.setTitle(R.string.soft_updating);
// 给下载对话框增加进度条
final LayoutInflater inflater = LayoutInflater.from(mContext);
View v = inflater.inflate(R.layout.softupdate_progress, null);
mProgress = (ProgressBar) v.findViewById(R.id.update_progress);
builder.setView(v);
// 取消更新
builder.setNegativeButton(R.string.soft_update_cancel, new OnClickListener()
{
@Override
public void onClick(DialogInterface dialog, int which)
{
dialog.dismiss();
// 设置取消状态
cancelUpdate = true;
}
});
mDownloadDialog = builder.create();
mDownloadDialog.show();
// 现在文件
downloadApk();
}

/**
* 下载apk文件
*/
private void downloadApk()
{
// 启动新线程下载软件
new downloadApkThread().start();
}

/**
* 下载文件线程
*
* @author coolszy
*@date 2012-4-26
*@blog http://blog.92coding.com
*/
private class downloadApkThread extends Thread
{
@Override
public void run()
{
try
{
// 判断SD卡是否存在,并且是否具有读写权限
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED))
{
// 获得存储卡的路径
String sdpath = Environment.getExternalStorageDirectory() + "/";
mSavePath = sdpath + "download";
URL url = new URL(mHashMap.get("url"));
// 创建连接
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.connect();
// 获取文件大小
int length = conn.getContentLength();
// 创建输入流
InputStream is = conn.getInputStream();

File file = new File(mSavePath);
// 判断文件目录是否存在
if (!file.exists())
{
file.mkdir();
}
File apkFile = new File(mSavePath, mHashMap.get("name"));
FileOutputStream fos = new FileOutputStream(apkFile);
int count = 0;
// 缓存
byte buf[] = new byte[1024];
// 写入到文件中
do
{
int numread = is.read(buf);
count += numread;
// 计算进度条位置
progress = (int) (((float) count / length) * 100);
// 更新进度
mHandler.sendEmptyMessage(DOWNLOAD);
if (numread {
// 下载完成
mHandler.sendEmptyMessage(DOWNLOAD_FINISH);
break;
}
// 写入文件
fos.write(buf, 0, numread);
} while (!cancelUpdate);// 点击取消就停止下载.
fos.close();
is.close();
}
} catch (MalformedURLException e)
{
e.printStackTrace();
} catch (IOException e)
{
e.printStackTrace();
}
// 取消下载对话框显示
mDownloadDialog.dismiss();
}
};

/**
* 安装APK文件
*/
private void installApk()
{
File apkfile = new File(mSavePath, mHashMap.get("name"));
if (!apkfile.exists())
{
return;
}
// 通过Intent安装APK文件
Intent i = new Intent(Intent.ACTION_VIEW);
i.setDataAndType(Uri.parse("file://" + apkfile.toString()), "application/vnd.android.package-archive");
mContext.startActivity(i);
}
}

效果图

检查模拟器SDCARD是否存在下载文件:


参考代码下载:

 http://files.cnblogs.com/coolszy/UpdateSoftDemo.rar

[转载]再谈IE的浏览器模式和文档模式

mikel阅读(1095)

[转载]再谈IE的浏览器模式和文档模式 – BobLiu – 博客园.

以前在 “IE8兼容视图(IE7 mode)与独立IE7的区别”一文中曾经涉及过浏览器模式和文档模式,但二者的区别却不甚了了,现在有了新的认识,再补充一下。

1.浏览器模式与文档模式概念

在较新的IE浏览器中(如IE8, IE9, IE10),为了解决兼容性的问题,引入了浏览器模式和文档模式两个概念,浏览网页时可以通过按F12键看到这两种模式。

 

浏览器模式的主要作用是为兼容较早版本的IE,它会控制浏览器发出的UserAgent,表示以哪个版本的浏览器发出请求,以此来允许为某个特定IE版本设计的代码正确执行(举例来说:有些代码真是判断ie版本的,还有css里也有判断ie版本的)。

 

文档模式的主要作用是影响浏览器显示网页HTML的方式,在接到返回的HTML文件后,决定以哪个IE版本的文档模式解析该页面(举例来说:JS脚本就是依赖文档模式,IE9的js变化就需要IE9文档模式来支持

2.如何使WebBrowser运行在IE9浏览器模式下

在安装了IE9的计算机上,Webbrowser并不是直接运行在IE9模式下,而是默认工作在IE9兼容视图(也就是IE7 Mode)下,为了让Webbrowser工作在IE9下,需要修改注册表,为应用程序指明使用IE9版本。

32位计算机需要修改注册表
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Internet Explorer\MAIN\FeatureControl\FEATURE_BROWSER_EMULATION,增加YourApplication.exe,值为十进制9999

64位计算机需要修改注册表HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Internet Explorer\MAIN\FeatureControl\FEATURE_BROWSER_EMULATION,增加YourApplication.exe,值为十进制9999

注意:9999最正确,9000则不推荐用,因为它不彻底。

3.如何使Htm页面使用IE9文档模式

如果不修改交易页面,系统将不会使用IE9文档模式来解析交易页面,而是仍沿用之前兼容旧版IE Quirks模来渲染,解释交易页面,JS脚本也将同样处置。这样的缺点是无法使用Html5新特性,优点是您的系统升级到IE9的兼容性改造工作将会大为减少。

修改Htm页面的方法之一是,在Head->Title下添加<META http-equiv=”X-UA-Compatible” content=”IE=9″ > </META>,这样可确保HTM页面工作在IE9标准文档模式下。

注意:msdn上写的关于9999可以无视doctype而自动使页面运行在ie9文档模式的说法是错误的,这一点已经得到了权威的认可。

4.总结

欲使你的系统作为整体(包括WebBrowser和Htm页面)工作在IE9下,则以上2, 3两步均需遵守,缺一不可。

[转载]Asp.Net实现无刷新文件上传并显示进度条(非服务器控件实现) - usharei - 博客园

mikel阅读(833)

[转载]Asp.Net实现无刷新文件上传并显示进度条(非服务器控件实现) – usharei – 博客园.

相信通过ASP.NET的服务器控件上传文件在简单不过了,通过AjaxToolkit控件实现上传进度也不是什么难事,为什么还要自己辛辛苦苦来 实现呢?我并不否认”拿来主义“,只是我个人更喜欢凡是求个所以然。本篇将阐述通过Html,IHttpHandler和 IHttpAsyncHandler实现文件上传和上传进度的原理,希望对你有多帮助。

效果图:


本文涉及到的知识点:
1.前台用到Html,Ajax,JQuery,JQuery UI

2.后台用到一般处理程序(IHttpHandler)和一般异步处理程序(IHttpAsyncHandler),并涉及到”推模式“

一、创建Html网页

1、在创建的Web工程中添加一个Html文件,命名为UploadFile.htm,在头文件中引入JQuery,JQuery UI

<script type="text/javascript" src="Scripts/jquery-1.6.2.min.js"></script><script type="text/javascript" src="Scripts/jquery-ui-1.8.16.custom.min.js"></script>

2、关于无刷新文件上传

通过Ajax是不能上传文件的,无刷新上传是靠隐藏的iframe来实现的


<form id="form" enctype="multipart/form-data" target="frameFileUpload">
<div id="progressBar" style="font-size: 1em;"></div>
<input id="fileUpload" type="file" name="fileUpload" />
<iframe id="frameFileUpload" style="display: none;" name="frameFileUpload"></iframe>

<input id="submit" type="submit" value="上传" />

</form>

要将form标签的target属性设置为iframe的id,当然别忘了将form的enctype设置为multipart/form-data
<div id=”progressBar” style=”font-size: 1em;”></div>
是用来显示上传文件时的进度条

在JS中加入如下处理:

<script type="text/javascript">// <![CDATA[
          $(function () {             $("#submit").button();             $("#fileUpload").button();         });
// ]]></script>

此时效果:

二、实现文件上传

添加一个一般处理程序,命名为UploadFileHandler.ashx

public void ProcessRequest(HttpContext context)
{
//如果提交的文件名是空,则不处理
if (context.Request.Files.Count == 0 || string.IsNullOrWhiteSpace(context.Request.Files[0].FileName))
return;
//获取文件流
Stream stream = context.Request.Files[0].InputStream;
//获取文件名称
string fileName = Path.GetFileName(context.Request.Files[0].FileName);
//声明字节数组
byte[] buffer;
//为什么是4096呢?这是操作系统中最小的分配空间,如果你的文件只有100个字节,其实它占用的空间是4096个字节
int bufferSize = 4096;
//获取上传文件流的总长度
long totalLength = stream.Length;
//已经写入的字节数,用于做上传的百分比
long writtenSize = 0;
//创建文件
using (FileStream fs = new FileStream(@"C:\" + fileName, FileMode.Create, FileAccess.Write))
{
//如果写入文件的字节数小于上传的总字节数,就一直写,直到写完为止
while (writtenSize &lt; totalLength) { //如果剩余的字节数不小于最小分配空间 if (totalLength - writtenSize &gt;= bufferSize)
{
//用最小分配空间创建新的字节数组
buffer = new byte[bufferSize];
}
else
//用剩余的字节数创建字节数组
buffer = new byte[totalLength - writtenSize];
//读取上传的文件到字节数组
stream.Read(buffer, 0, buffer.Length);
//将读取的字节数组写入到新建的文件流中
fs.Write(buffer, 0, buffer.Length);
//增加写入的字节数
writtenSize += buffer.Length;
//计算当前上传文件的百分比
long percent = writtenSize * 100 / totalLength;
}
}
}

在form中添加action和method属性,修改之后的

<form action="UploadFileHandler.ashx" method="post" id="form" target = "frameFileUpload" enctype="multipart/form-data">

这样文件上传就完成了。

三、实现文件上传的进度显示

我的思路

文件上传的处理过程中,是不可以在处理过程中将信息传回客户端的,只有当所有的处理都完毕之后才会传回客户端,所以如果是在上面的处理程序中写 入context.Response.Write(percent);是不可能得到处理的过程,只能等到处理结束后,客户端一次性得到所有的值。

要想得到处理过程中的值,我的解决是这样,在文件上传时,要开启另一个请求,来获取进度信息。而这个请求是异步的,我指的是客户端异步请求和服 务端异步处理。因为要涉及到两个不同的请求处理程序之间信息的传递,将”处理文件上传的程序”得到的进度信息传递给”处理进度请求的程序”,而”处理进度 请求的处理程序”要依赖于”处理文件上传的处理程序”。处理图:

首先客户端同时(几乎是)发出两个请求,一个是文件上传,一个是进度请求。由于”处理请求进度的程序”是异步处理的,当该程序没有信息发给客户 端时,我们让它处于等待状态,这里有点像Tcp,这样客户端跟服务器就一直处于连接状态。当”处理文件上传的程序”开始处理时,通过把进度值赋值给”处理 请求进度程序”的异步操作的状态,并触发”处理请求进度的程序”返回值给客户端。客户端获取进度值,并处理。这样一次请求进度值的请求就结束了,我们知道 服务器是不会主动给客户端发送信息的,只有客户端请求,服务器才会响应。显然,要想在文件保存的过程中向客户端发送进度信息,客户端得到每得到一个返回结 果,都是一次请求。为了得到连续的请求值,客户端再向”处理请求进度的程序”发出请求,依次循环,知道文件上传结束。

技术实现:
异步处理用到接口IHttpAsyncHandler,新建一个一般处理程序,命名为RequestProgressAsyncHandler.ashx,将默认的接口改为IHttpAsyncHandler

public class RequestProgressAsyncHandler : IHttpAsyncHandler
{
public void ProcessRequest(HttpContext context)
{
}
public bool IsReusable
{
get
{
return false;
}
}
#region IHttpAsyncHandler 成员
public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData)
{
throw new NotImplementedException();
}
public void EndProcessRequest(IAsyncResult result)
{
throw new NotImplementedException();
}
#endregion
}

BeginProcessRequest和EndProcessRequest是两个核心的方法,其他的两个不用处理。当该处理程序处理请求时,BeginProcessRequest是第一个被调用的函数,返回一个包含异步状态信息的对象,该对象是IAsyncResult类型,是实现异步的关键,用于控制什么时候调用EndProcessRequest来结束处理程序的等待状态,BeginProcessRequest被调用之后,程序就处于等待状态。EndProcessRequest是在结束请求时的处理函数,通过该函数可以向客户端写入信息。

实现接口IAsyncResult

public class AsyncResult : IAsyncResult
{
// 标示异步处理的状态
private bool isComplete = false;

//保存异步处理程序中的Http上下文
private HttpContext context;

//异步回调的委托
private AsyncCallback callback;
///

<summary> /// 获取或设置保存下载文件的百分比数值部分
/// </summary>

&nbsp;

public long PercentNumber;

public AsyncResult(HttpContext context, AsyncCallback callback)
{
this.context = context;
this.callback = callback;
}
///

<summary> /// 向客户端写入信息
/// </summary>

&nbsp;

public void Send()
{
this.context.Response.Write(PercentNumber);
}
///

<summary> /// 完成异步处理,结束请求
/// </summary>

&nbsp;

public void DoCompleteTask()
{
if (callback != null)
callback(this);//会触发处理程序中的EndProcessRequest函数,结束请求
this.isComplete = true;
}
#region IAsyncResult 成员

public object AsyncState
{
get { return null; }
}

public System.Threading.WaitHandle AsyncWaitHandle
{
get { return null; }
}

public bool CompletedSynchronously
{
get { return false; }
}

public bool IsCompleted
{
get { return isComplete; }
}

#endregion

}

修改 RequestProgressAsyncHandler.ashx文件:

public class RequestProgressAsyncHandler : IHttpAsyncHandler
{
///

<summary> /// 保存异步处理状态信息的集合
/// </summary>

&nbsp;

public static ListAsyncResults = new List();
public void ProcessRequest(HttpContext context)
{
}
public bool IsReusable
{
get
{
return false;
}
}
#region IHttpAsyncHandler 成员

public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData)
{

AsyncResult result = new AsyncResult(context, cb);
AsyncResults.Add(result);
return result;
}

public void EndProcessRequest(IAsyncResult result)
{
//保证集合中只用一个元素
AsyncResults.Clear();
AsyncResult ar = (AsyncResult)result;
ar.Send();
}

#endregion
}

在UploadFileHandler.ashx添加如下代码:

private static void SendPercentToClient(long percent)
{
//当上传完毕后,保证处理程序能向客户端传回
while (RequestProgressAsyncHandler.AsyncResults.Count == 0 &amp;&amp; percent == 100)
{

}
//因为本处理程序和"处理请求进度的程序"是并发的,不能保证RequestProgressAsyncHandler.AsyncResults一定含有子项
if (RequestProgressAsyncHandler.AsyncResults.Count != 0)
{
RequestProgressAsyncHandler.AsyncResults[0].PercentNumber = percent;
RequestProgressAsyncHandler.AsyncResults[0].DoCompleteTask();
}
}

在函数ProcessRequest中加入以上方法:

...
...
//计算当前上传文件的百分比
long percent = writtenSize * 100 / totalLength;

SendPercentToClient(percent);

服务端OK!修改客户端,添加JS处理函数:

function RequestProgress() {
$.post("RequestProgressAsyncHandler.ashx", function (data, status) {
if (status == "success") {
$("#progressValue").text(data + "%");
data = parseInt(data);
$("#progressBar").progressbar({ value: data });//JQuery UI 设置进度条值
//如果进度不是 100,则重新请求
if (data != 100) {
RequestProgress();
}
}
});
}

在form中添加事件omsubmit的处理函数为RequestProgress

<form action="UploadFileHandler.ashx" onsubmit = "RequestProgress();" method="post" id="form" target = "frameFileUpload" enctype="multipart/form-data">

补充几点:
1.默认ASP.NET允许的上传文件的大小是4M,可以在Web.config中修改其大小限制

    <system.web> ... <httpRuntime maxRequestLength="444444"/>
    </system.web>

maxRequestLength的单位是KB

2.在IE 8.0测试中,在文件上传完毕后,状态栏还处于请求中 

反正不是后台还在请求,这个放心,只要把鼠标在按钮和浏览上面来回移动几下就没了,可能是jQuery UI 的问题。FF和Chrom下没这个问题,就是显示效果会有点差,但是上传没问题的。

源代码下载:UploadFileDemo.rar

[转载]开发托管ActiveX或第三方程序托管插件时调试问题解决方法

mikel阅读(936)

[转载]开发托管ActiveX或第三方程序托管插件时调试问题解决方法 – BinSys – 博客园.
问题描述:

用VS2010开发ActiveX控件,项目属性里,调试设置为:

启动外部程序:C:\Program Files\Internet Explorer\iexplore.exe

命令行参数:-noframemerging -nohangrecovery http://localhost/PrintSettingTest.htm

目标框架设置为2.0时,可调试,断点可命中,模块列表可看见待调试托管dll已经加载,一切正常。

但之后按照部署要求,目标框架统一改为.NET 4.0,问题出现了,无法命中任何断点,调试时模块列表为空,初步判断VS2010调试器加载调试引擎的

框架版本选择错误,未正确识别,google了一大堆,找到建立iexplore.exe.config并加入如下配置,强制指定clr版本的方法:

<!--?xml version="1.0"?-->





测试无效。又建立了TestActiveX.dll.config,内容如上,同样无效。

后尝试在欲调试代码前加入代码:

Debugger.Launch();
Debugger.Break();

运行,出现如下错误:

 

听他的,分离再附加:

 

 

可见模块正常加载,可以调试并命中断点。

但每次都要手动附加貌似非常麻烦哦,继续google,找到好方法:
http://through-the-interface.typepad.com/through_the_interface/2010/04/hitting-breakpoints-in-net-class-libraries-while-debugging-with-visual-studio-2010.html

解决方法:

因为我的托管dll的宿主是IE,那么我在解决方案上点右键,添加-》现有项目,定位到 C:\Program Files\Internet Explorer\ 并选择 iexplore.exe

在iexplore项目上右键:

然后把iexplore设为启动项。

注意,到此并未结束,因为IE8开始每个网页Tab都会开新进程,导致当只打开一个网页调试会出现一个框架进程,我们得取消他。使用下面的注册表取消:

Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Internet Explorer\MAIN]

"TabProcGrowth"=dword:00000000
;当不调试时用以下字段代替以上字段
;"TabProcGrowth"=-

把注册表另存为REG文件,并导入。这下你就可以痛痛快快地调试托管dll了。

[转载]开发人员和设计师应该安装的10个Android应用

mikel阅读(965)

[转载]开发人员和设计师应该安装的10个Android应用 – 行业动态 – eoe·Android 开发门户 – android开发者的必备网站 – 全球最大中文Android开发社区.

过去几年里有很多新开发的小工具出现,人们的生活越来越离不开智能机,当然包括Android手机,它已经成为了人们的最大需求量之一,市场上 出现 的Android手机也越来越多,人们也比较喜欢用Android手机。因为相对而言,价格比较能够让人接受,却包含了人们期望有的功能,可以说性价比比 较高。Android开发者不得不随时为了跟上最新的发展技术而作出不断的改进和努力。也随着涌现出了很多帮助开发者进行开发的工具,比如远程桌面连接客 户端,AndFtp等等。下面推荐很多开发者和设计师可能会用到的小工具。

  1、 Touchqode Editor

这是一款Android手机上的源代码编辑和查看工具。支持语法高亮和自动填充以及其他跟PC的IDE一样的特性。支持语言有Java、 html、 JavaScript、Python、 C++、 C#、 Ruby 和 PHP. 现在已经和FTP、SFTP客户端集成.

\" src=
 

  2、 Web Editor

Android Web Editor Lite – 一款web开发的手机工具,支持创建和编辑自己的htm、 php、JavaScript 等文件.

\" src=
 

  3、 Silver Edit

\" src=
 

Silver Edit 是 Android 手机的一款轻量级的源代码编辑程序,很它能编写 HTML, CSS, PHP, XML 文件并保存或发送到指定的邮箱帐户,并且支持语法高亮。再安装上 AndFTP 的话,就能使用 Android 手机建立并上传网站!

  4、 Color Picker

Magic Color Picker 是一款强大的颜色选择器,供开发和设计人员选择不通的颜色模型。

\" src=
 

  5、 JQuery mobile docs

\" src=
 

  6、 Color Palettes

\" src=
 

  7、 View Web Source

网页源代码查看和编辑工具。

\" src=
 

  8、 W3C Cheat Sheet

The W3C Cheatsheet 提供快速访问W3C发布的文档API的途径,包括HTML5, CSS, SVG, XPath) ,互联网主流的标准.

\" src=
 

  9、 AndFTP

AndFTP 是一款 在andorid手机上的FTP, SFTP, SCP, FTPS 客户端.运行管理连接多个FTP服务器,支持上传下载和同步。

\" src=
 

  10、 Dropbox

\" src=
 

多个终端同步用的,这个不说了。

[转载].Net 开发者必备的11款开发利器

mikel阅读(1169)

[转载].Net 开发者必备的11款开发利器_IT新闻_博客园.

现如今,技术发展十分迅猛,开发者只有通过不断的学习才能跟得上步伐。而为了便于学习和工作,涌现了很多优秀的开发工具用以帮助开发者提高工作 效率。同时,这些工具大都开源而且免费,性能也十分优异。以下就是为 .NET 开发者准备的 11 款开发利器,希望它们能让你的网站开发之路变得简单易行。

1、Webmatrix

WebMatrix 是微软最新的 Web 开发工具,它包含了构建网站所需要的一切元素。你可以从开源 Web 项目或者内置的 Web 模板开始,也可以直接从无到有编写代码。简单并且完全是免费的。开发网站从来没有如此简单。

  2、Visual Studio 2010 Express

Visual Studio 2010 Express 是一套免费、入门级的产品。它的用户界面十分精简,同时拥有 Visual Studio 的核心功能,常用于为某一平台开发应用。

  3、Notepad ++ 

Notepad++ 是一款 Windows 环境下免费开源的代码编辑器,支持多种编程语言,不仅有语法高亮度显示,也有语法折叠等功能。

  4、JustDecompile

JustDecompile 是一款新的、免费的 .NET 反编译软件。Telerik 公司根据多年的代码分析经验开发了这一反编译利器。它能让你毫不费力地分析编译好的 .NET 程序,同时只需点击一下按钮就可以进行反编译。

  5、Fiddler

Fiddler 是一款 Web 调试代理软件。它能够记录所有客户端和服务器间的 HTTP 请求,允许你监视、设置断点、甚至修改输入输出数据。Fiddler 包含了一个强大的基于事件脚本的子系统,并且能够使用 .NET 框架语言扩展。

  6、FileZilla

FileZilla 是一个免费开源的 FTP 客户端软件,分为客户端版本和服务器版本。两个版本都具备所有的 FTP 软件功能。可控性、有条理的界面和管理多站点的简化方式使 Filezilla 客户端版成为一个方便高效的 FTP 客户端工具。

  7、LINQPad

LINQPad 一款是集成了 IDE 和 SQL 的 LINQ 解析器。它有着简单而实用的用户界面,可即时执行 LINQ 查询,包括 LINQ to SQL、LINQ to Objects 和 LINQ to XML 等多语句查询。

  8、soapUI

soapUI 是一款世界领先的开源测试工具,主要用于网络服务测试领域。它提供了包括 SOAP、REST、HTTP、JMS、AMF 和 JDBC 在内的多种互联网协议的支持,确保为你提供业内领先的性能测试。

  9、NCrunch

NCrunch 是一款为 Visual Studio .NET 开发的的自动化并行连续测试工具。它能够运行自动测试,并且在 IDE 里显示相应的测试信息(如代码覆盖和性能指标)。

  10、KDiff3

KDiff3 是一款用来对文件或目录进行对比/合并的工具。在进行对比时,它可以同时针对两个或者三个文件/目录而进行。通过对比,它将文件/目录的差异按行加以显示。同时,KDiff3还提供了自动化的合并工具,方便使用者进行有关合并方面的操作。

  11、Sublime Text

Sublime Text 2 是一款轻量、简洁、高效、跨平台的代码编辑器。

  英文原文:10 Useful Tools for Microsoft .Net Developers

[转载]SQLServerDBA十大必备工具

mikel阅读(1251)

[转载]SQLServerDBA十大必备工具—让生活轻松点 – 飞洋过海 – 博客园.

曾经和一些DBA和数据库开发人员交流时,问他们都用过一些什么样的DB方面的工具,大部分人除了SSMS和Profile之外,基本就没有使用过其他工具了;

诚然,SSMS和Profile足够强大,工作的大部分内容都能通过它们搞定,但是MS、第三方公司甚至是个人开发者为SQLServer提供了很多其他的工具,如果你

能充分的掌握这些工具,无疑会给我们数据库的管理、优化、测试和排错节省大量的时间和精力,下面就来介绍除SSMS和Profile之外的其他有用的工具。

 

NO1: PD(PowerDesigner)

功能:SysBase公司提供的数据库设计工具,功能很强大,是做数据库设计时必备的工具;

下载:http://www.3ddown.com/soft/14524.htm

 

NO2: Log Explorer

功能:数据库日志读取工具,主要用来恢复误操作的数据(目前只支持到2005版本),详见:

http://blog.csdn.net/jinjazz/archive/2008/05/19/2459692.aspx ;

下载地址:http://www.pc6.com/softview/SoftView_57657.html

 

NO3Tuning Advisor

功能:优化顾问,会根据数据库的运行情况,提示您做相关的优化(可靠性不是太高,需要自行判断);

下载:SQLServer自带

 

NO4:SSMSTools

功能:SSMS工具的一个插件,能提供格式化代码、追溯历史等功能(通过它,也许你可以开发自己的插件);

下载: http://www.ssmstoolspack.com/

 

NO5: DBDiff

功能:比较两个数据库的差异;

下载:http://opendbiff.codeplex.com/

 

NO6PAL Tool

功能:Performance Analysis of Logs,Perfmon日志分析工具;

下载:http://pal.codeplex.com/

 

NO7RML

功能:这个工具非常强大,下图展示了完成安装后它的四个主要功能组件;ReadTrace工具能读取数据库的Profile跟踪文件,并生成报告;Ostress能将ReadTrace

生成的文件重播,而且还可以对数据库做压力测试;ORCA能保证重报时,按照事件发生的顺序播放;Reporter能将ReadTrace后的内容通过报表的形式展现,相当

的有用。

下载:http://support.microsoft.com/kb/944837

 

NO8SQLNexus

功能:先通过SQLServer自带的SQLdiag.exe工具收集信息,然后再用SqlNexus分析这些信息,它是前面一些工具的整合,为数据库管理人员寻找SQLServer服务器

的性能瓶颈和排查故障提供了相当强大的支持(MS工程师很多都用这个哦,买技术支持的朋友们有木有经历过MS要求你开启SQLDiag,然后将收集的数据回传给他们

的;现场支持时,是不是也开启SQLDiag收集数据,然后就出来了服务器性能报告,有木有;所以掌握它,1k/时的定期服务器检查技术支持费用可以省了);

下载:http://sqlnexus.codeplex.com/

 

NO9:SQLIO和SQLIOSim

功能:磁盘IO压力测试工具,SQLIO主要是模拟随机或者顺序的方式来测试磁盘IO的性能;SQLIOSim是模拟SQLServer的行为来测试IO性能;

下载:http://support.microsoft.com/kb/231619

 

NO10:SqlMonitor & SSBDiagnose

功能:SqlMonitor是监控Replication和Mirror的必会工具,SSBDiagnose是测试SSB配置的工具;

下载:SQLServer自带

[转载]SVN自动更新测试服务器工作副本(C#写winform程序实现)

mikel阅读(1524)

[转载]SVN自动更新测试服务器工作副本(C#写winform程序实现) – Machine Lee – 博客园.

根据工作需要,项目将采用SVN做版本控制,于是乎就安装了如下软件:

1、TortoiseSVN Version:1.6.7
2、Subversion Version:1.6.5
3、VisualSVN Version:2.0.6
其中1是SVN客户端,2是服务器,3是用于与VS .Net framework集成的组件。
具体安装步骤就不多讲了,网上很多帖子都详细描述过了,本文主要讲的是如何实现最新提交自动更新到测试服务器工作副本。

 

背景:

为什么要实现SVN自动更新呢?因为实际开发过程中,程序员一般都是在本地开发机上 开发,本地验证无误后上传至测试服务器验证生产环境正确性,修改代码多的时候,上传文件也是一件累人的活,还浪费时间,所以就有了实现SVN自动更新到测 试服务器工作副本的需求,既省时,又能保证文件不遗漏。

 

过程:

要实现SVN自动更新,无非就是使用SVN的钩子,网络上不少帖子都是讲如何通过版 本库hooks文件夹下post-commit文件实现自动更新的,有的是写成.bat文件,有的是shell脚本。笔者开始是借鉴网上的方法,写成了 post-commit.bat文件,实现了自动更新。但是,由于我们的项目比较大,写成.bat文件的话,就只能在根目录下执行update操作,速度 非常的慢,大概是2分钟。是可忍孰不可忍,于是上网查找,发现.exe文件也可以作为钩子程序嘛,这不就简单了,于是用C#写了个Winform程 序,commit+update瞬间完成!下面是C#代码,有详细的备注,供大家参考!

using System;
 using System.Collections.Generic;
 using System.ComponentModel;
 using System.Data;
 using System.Drawing;
 using System.Text;
 using System.Windows.Forms;
 using System.Diagnostics;
 using System.IO;
 using System.Text.RegularExpressions;
 
 namespace SVNGetTheLastRes
 {
     public partial class Form1 : Form
     {
         /// <summary>
         /// 
         /// </summary>
         public Form1()
         {
             InitializeComponent();
         }
 
         private void Form1_Load(object sender, EventArgs e)
         {
             try
             {
                 //查找最近更新文件,并将命令返回结果输出至txt文件
                 Execute("svnlook changed D:/subversion/project1 > D:/Subversion/project1/hooks/test.txt");
 
                 //读取生成的文件
                 string strPath = ResumeTxt("D:/Subversion/project1/hooks/test.txt");
 
                 //文件内容处理:按换行符将读取的字符串转换成字符串数组
                 string[] aryPath = strPath.Split('\n');
 
                 //循环更新文件
                 for (int i = 0; i < aryPath.Length; i++)
                 {
                     //处理掉回车符
                     aryPath[i].Replace('\r', ' ');
 
                     //经测试,文件中最后一行是空行,但为了避免遗漏,用非空判断,而不是循环的length-1
                     if (!aryPath[i].Trim().Equals(""))
                     {
                         //根据文件中的数据格式,从第五个字符开始才是文件路径
                         string strFile = aryPath[i].Trim().Substring(4);
                         //组织命令并执行,其中D:/是项目所在文件夹,根据自己的情况组织
                         string strCmd = "svn update D:/" + strFile + " --username *** --password ***";
                         Execute(strCmd);
                     }
                 }
             }
             catch (Exception ex)
             {
 
             }
             finally
             {
                 this.Close();
             }
         }
 
         public string ResumeTxt(string path)
         {
             string str = string.Empty;
 
             StreamReader reader = new StreamReader(path, System.Text.Encoding.Default);
             str = reader.ReadToEnd();
 
             //再通过查询解析出来的的字符串有没有GB2312的字段,来判断是否是GB2312格式的,如果是,则重新以GB2312的格式解析
             Regex reGB = new Regex("GB2312", RegexOptions.IgnoreCase);
             Match mcGB = reGB.Match(str);
             if (mcGB.Success)
             {
                 StreamReader reader2 = new StreamReader(path, System.Text.Encoding.GetEncoding("GB2312"));
                 str = reader2.ReadToEnd();
             }
 
             return str;
         }
 
         /// <summary>
         /// 执行DOS命令并返回结果
         /// </summary>
         /// <param name="dosCommand">Dos命令语句</param>
         /// <returns>DOS命令返回值</returns>
         public string Execute(string dosCommand)
         {
             return Execute(dosCommand, 0);
         }
 
         /// <summary> 
         /// 执行DOS命令,返回DOS命令的输出
         /// </summary> 
         /// <param name="dosCommand">dos命令</param> 
         /// <param name="milliseconds">等待命令执行的时间(单位:毫秒),如果设定为0,则无限等待</param> 
         /// <returns>返回DOS命令的输出</returns> 
         public static string Execute(string dosCommand, int seconds)
         {
             string output = ""; //输出字符串
             if (dosCommand != null && dosCommand != "")
             {
                 Process process = new Process();//创建进程对象
                 ProcessStartInfo startInfo = new ProcessStartInfo();
                 startInfo.FileName = "cmd.exe";//设定需要执行的命令
                 startInfo.Arguments = "/C " + dosCommand;//设定参数,其中的“/C”表示执行完命令后马上退出
                 startInfo.UseShellExecute = false;//不使用系统外壳程序启动
                 startInfo.RedirectStandardInput = false;//不重定向输入
                 startInfo.RedirectStandardOutput = true; //重定向输出
                 startInfo.CreateNoWindow = true;//不创建窗口
                 process.StartInfo = startInfo;
                 try
                 {
                     if (process.Start())//开始进程
                     {
                         if (seconds == 0)
                         {
                             process.WaitForExit();//这里无限等待进程结束
                         }
                         else
                         {
                             process.WaitForExit(seconds); //这里等待进程结束,等待时间为指定的毫秒
                         }
                         output = process.StandardOutput.ReadToEnd();//读取进程的输出
                     }
                 }
                 catch
                 {
 
                 }
                 finally
                 {
                     if (process != null)
                         process.Close();
                 }
             }
             return output;
         }
     }
 }

需要注意的是,用update命令更新时,要求测试服务器工作副本必须是受控的,否则,应该改用export命令,export命令用法请查看”SVN export –help”。

结果:

实现了SVN自动更新的功能。实际上,既然能用exe程序作为SVN钩子使用,那就可以扩展很多功能了,包括每次更新的邮件提醒,甚至是重要文件更新时的短信提醒,还能做文件更新日志等等。