[转载]URL中的特殊字符编码

mikel阅读(1041)

[转载]URL中的特殊字符编码 – zsw的个人空间 – 开源中国社区.

有些符号在URL中是不能直接传递的,如果要在URL中传递这些特殊符号。

编码的格式为:%加对应字符的ASCII(16进制)码值。例如:空格的编码值是”%20″ 。
一些URL特殊符号及编码(十六进制值)
1.   +         URL中+号表示空格                            %2B
2.   空格      URL中的空格可以用+号或者编码            %20
3.   /         分隔目录和子目录                              %2F
4.   ?         分隔实际的   URL   和参数                   %3F
5.   %         指定特殊字符                                  %25
6.   #         表示书签                                        %23
7.   &         URL   中指定的参数间的分隔符              %26
8.   =         URL   中指定参数的值                         %3D

java中URL的编码和解码函数

  • java.net.URLEncoder.encode(String   s)
  • java.net.URLDecoder.decode(String   s);

JavaScript中URL的编码和解码函数

  •   escape(String   s)
  •   unescape(String   s)   ;

[转载]如何使你的应用程序调试进.NET Framework 4.5源代码内部(适用B/S,C/S)

mikel阅读(770)

[转载]如何使你的应用程序调试进.NET Framework 4.5源代码内部(适用B/S,C/S) – dotNetDR_ – 博客园.

I:下载.NET 4.5源代码安装包

.NET 4.5 源代码的下载地址:官方地址 / 百度网盘镜像

image

官方下载页

 

 

image

百度云盘下载

安装过程我就不详细说了,大家都懂的~呵。

II:配置Visual Studio 2012环境

点击vs菜单栏上的 工具 –> 选项 在左侧树形列表中将[调试]节点展开然后选取[符号]子节点
image
设置为你.NET 4.5源代码安装的目录。
注意:缓存符号哪里应该选择安装包安装完成的路径,不要使用C:\Users\{UserName}\AppData\Local\Temp\SymbolCache否则vs会自动下载源代码pdb包,很慢的。

然后选择同级的[常规]节点配置成如下图所示
image
至此Visual Studio 2012配置完成,如果你是用vs2010的,那么这些步骤将会相同。唯一的区别是你需要下载.NET 4.0的源码而不是.NET 4.5版本

配置参考地址:http://blogs.msdn.com/b/dotnet/archive/2012/08/15/announcing-the-release-of-net-framework-4-5-rtm-product-and-source-code.aspx

III:调试进入.NET 4.5源代码内部当中

进行到这里时,本人先在自己的电脑上用vs2012建立一个基于.NET 4.5的控制台应用程序,并测试以上的配置过程是否成功,至此我们继续往下看步骤流程。
image
建立一个控制台应用程序

image
编写Main入口点函数的执行代码

image
查看.NET 4.5源代码内部之 – DateTime.Now内部。

image
查看.NET 4.5源代码内部之 – new String(char[])内部

image
运行结果。

到目前为止,作者已经能够顺利地调试进C/S的.NET 4.5内部了。

接下来,作者现在再试一试在WebApplication的项目类型下调试进.NET 4.5源代码内部,拭目以待。


我们继续建立了一个在.NET 4.5下的WebApplication类型的项目,但是开始调试的时候发现加载太多需要下载的dll了,出现下图的等待情况比较久的情况。。。

image

于是作者就重新开始建立一个空白的.NET 4.5 WebApplication类型的项目,就不需要引用太多的dll了。

image
然后添加Default.aspx和Global.asax文件,并再Global.asax.cs内打上一个断点

image
以方便顺利找到ASP.NET在进入System.Web.dll时的处理入口点。
下图为顺利Debug进.NET 4.5源代码内部的效果示例图,至此整编文章所要表达的核心内容已顺利表达完成。
image
从上图中的调用堆栈可以看到客户端请求进入到clr环境进行处理时,开始进行处理的一个环节是位于C:\dotNetFramework_src\net45\ReferenceSource\Source\.NET 4.5\4.5.50709.0\net\ndp\fx\src\xsp\System\Web\Hosting\IPipelineRuntime.cs\目录

image
未知的细节。。。

作为博客园内一名忠实的园友,当看到站长默默无闻地更新升级博客园程序且遇到问题时,应该贡献出一份力量维护咱们的家园原有的稳健秩序!!!虽然力量甚微,但园友情节影响深远。

本文到此结束!致谢。

作者的文章帮助很大
声明: 本文版权归作者dotNetDR_和博客园共有,转载必须保留此段声明。

[转载]FCKeditor编辑器中设置默认文本行高和字体大小

mikel阅读(875)

[转载]FCKeditor编辑器中设置默认文本行高和字体大小 – 不懂戀愛魚兒.

这个行高可以自己设置 找到\editor\css\fck_editorarea.
在 body
{
background-color: #ffffff;
padding: 5px 5px 5px 5px;
margin: 0px;
line-height:150%;
} 中 我加了一行控制line-height:150%; 行边距的就可以了.

如果要改变默认字体大小,可以修改

body, td
{
font-family: Arial, Verdana, sans-serif;
font-size: 14px;
}

[转载]Fckeditor编辑器添加行间距

mikel阅读(901)

[转载]Fckeditor编辑器添加【行间距】 – 不懂戀愛魚兒.

http://www.souab.com/post-218.html

上一篇文章介绍了默认的行高。这篇介绍一下可以调整的行高。

这里我采用的将编辑器中【样式】换成【行距】。

第一,在fckconfig.js里面

FCKConfig.CustomStyles =
{
‘Red Title’ : { Element : ‘h3’, Styles : { ‘color’ : ‘Red’ } }
};
替换成
FCKConfig.CustomStyles =
{
//mark090522
’12px’ : { Element : ‘span’, Styles : {‘line-height’ : ’12px’ } },
’14px’ : { Element : ‘span’, Styles : {‘line-height’ : ’14px’ } },
’18px’ : { Element : ‘span’, Styles : {‘line-height’ : ’18px’ } },
’22px’ : { Element : ‘span’, Styles : {‘line-height’ : ’22px’ } },
’26px’ : { Element : ‘span’, Styles : {‘line-height’ : ’26px’ } },
’30px’ : { Element : ‘span’, Styles : {‘line-height’ : ’30px’ } },
’36px’ : { Element : ‘span’, Styles : {‘line-height’ : ’36px’ } },
’42px’ : { Element : ‘span’, Styles : {‘line-height’ : ’42px’ } },
’50px’ : { Element : ‘span’, Styles : {‘line-height’ : ’50px’ } },
’64px’ : { Element : ‘span’, Styles : {‘line-height’ : ’64px’ } },
’80px’ : { Element : ‘span’, Styles : {‘line-height’ : ’80px’ } }
//’Red Title’ : { Element : ‘h3’, Styles : { ‘color’ : ‘Red’ } }
//markend
};
第二:在fckstyles.xml里
<Style name=”Marker: Yellow” element=”span”>
   <Style name=”background-color” value=”Yellow” />
</Style>
<Style name=”Marker: Green” element=”span”>
   <Style name=”background-color” value=”Lime” />
</Style>

<Style name=”Big” element=”big” />
<Style name=”Small” element=”small” />
<Style name=”Typewriter” element=”tt” />

<Style name=”Computer Code” element=”code” />
<Style name=”Keyboard Phrase” element=”kbd” />
<Style name=”Sample Text” element=”samp” />
<Style name=”Variable” element=”var” />

<Style name=”Deleted Text” element=”del” />
<Style name=”Inserted Text” element=”ins” />

<Style name=”Cited Work” element=”cite” />
<Style name=”Inline Quotation” element=”q” />

<Style name=”Language: RTL” element=”span”>
   <Attribute name=”dir” value=”rtl” />
</Style>
<Style name=”Language: LTR” element=”span”>
   <Attribute name=”dir” value=”ltr” />
</Style>
<Style name=”Language: RTL Strong” element=”bdo”>
   <Attribute name=”dir” value=”rtl” />
</Style>
<Style name=”Language: LTR Strong” element=”bdo”>
   <Attribute name=”dir” value=”ltr” />
</Style>
将上面的代码注释掉
第三:在zh-cn.js
将Style     : “样式”,替换成 Style     : “行距”,

[转载]HTML5开发 拖拽文件上传

mikel阅读(956)

[转载]HTML5开发 拖拽文件上传 – 爱玩 – 博客园.

Drag&Drop 拖拽功能的处理

关于HTML5拖拽文件上传,其实国外已经有很多网站有这样的应用,最早推出拖拽上传应用的是 Gmail,它支持标准浏览器下拖拽本地文件到浏览器中作为邮件的附件发送,但其实现在利用HTML5的功能实现,主要借助于新版支持的浏览器来实现,IE还是弱很多。

  • 拖拽上传应用主要使用了以下 HTML5技术:
  • Drag&Drop : HTML5基于拖拽的事件机制.
  • File API :  可以很方便的让 Web 应用访问文件对象,File API 包括FileList、Blob、File、FileReader、URI scheme,本文主要讲解拖拽上传中用到的 FileList 和 FileReader 接口。
  • FormData : FormData 是基于 XMLHttpRequest Level 2的新接口,可以方便 web 应用模拟 Form 表单数据,重要的是它支持文件的二进制流数据,这样我们就能够通过它来实现 AJAX 向后端发送文件数据了。

HTML5 Drag & Drop 事件过去我们想实现网页中的拖拽效果,基本上都是使用DOM事件 模型中的mousedown、mousemove、mouseup的事件监听来模拟拖拽效果,为了实现实时的拖拽移动效果,还要不停地获取鼠标的坐标,还 要不停的修改元素的位置,代码要堆很多,而且性能上也很不好(不停地修改元素位置会导致页面reflow,除非绝对定位),现在有了html5原生的 Drag & Drop 拖拽事件,真的是方便了许多,用”事半功倍”来形容绝不为过。

  • Drag & Drop 包括以下事件:
  • dragstart: 要被拖拽的元素开始拖拽时触发,这个事件对象是被拖拽元素
  • dragenter: 拖拽元素进入目标元素时触发,这个事件对象是目标元素
  • dragover: 拖拽某元素在目标元素上移动时触发,这个事件对象是目标元素
  • dragleave: 拖拽某元素离开目标元素时触发,这个事件对象是目标元素
  • dragend: 在drop之后触发,就是拖拽完毕时触发,这个事件对象是被拖拽元素
  • drop: 将被拖拽元素放在目标元素内时触发,这个事件对象是目标元素

完成一次成功页面元素拖拽的行为事件过程: dragstart –> dragenter –> dragover –> drop –> dragend 要想实现拖拽,首页需要阻止浏览器默认行为,一共四个事件。

$(document).on({
dragleave:function(e){ //拖离
e.preventDefault();
$('.dashboard_target_box').removeClass('over');
},
drop:function(e){ //拖后放
e.preventDefault();
},
dragenter:function(e){ //拖进
e.preventDefault();
$('.dashboard_target_box').addClass('over');
},
dragover:function(e){ //拖来拖去
e.preventDefault();
$('.dashboard_target_box').addClass('over');
}
});

获取文件数据 HTML5 File API

File API 中的 FileList 接口,它主要通过两个途径获取本地文件列表:
一种是 的表单形式,
一种是 e.dataTransfer.files拖拽事件传递的文件信息

var fileList = e.dataTransfer.files;

使用files 方法将会获取到拖拽文件的数组形势的数据,每个文件占用一个数组的索引,如果该索引不存在文件数据,将返回 null 值。可以通过length属性获取文件数量.

var fileNum = fileList.length;

判断文件类型

fileList[0].type.indexOf (’image’);

FormData 模拟表单实现Ajax文件上传

file.getAsBinary获取文件流很简单,但是要想上传数据,就要模拟一下表单的数据格式了,首先看看模拟表单的 js 代码, FormData模拟表单数据时更是简洁,不用麻烦的去拼字符串,而是直接将数据 append 到 formdata 对象中即可

xhr = new XMLHttpRequest();
xhr.open("post", "test.php", true);
xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");

var fd = new FormData();
fd.append('ff', fileList[0]);

xhr.send(fd);

点击此处查看拖拽文件上传示例>>

[转载]Android 网络连接工具类封装,支持wifi以及3G网络

mikel阅读(1043)

[转载]Android 网络连接工具类封装,支持wifi以及3G网络 | 麦洛工作室.

市场上几乎每款APP都具有网络连接的功能。而对于开发项目来说,连接网络也是经常会用到的。现在麦洛将网络连接的代码封装成工具类,
这些代码在写与服务器交互的应用时,是非常有用的。开发的同学可以直接拿来使用。而对于麦洛自己也是一个知识积累的过程,何乐而不为呢?

在写网络连接代码之前,先了解一下HTTP协议。HTTP协议概念不用多解释了,无非就是基于TCP协议的一个应用协议。客户端对服务端发送请求,
服务端对客户端的请求作出响应。
Android中对网络连接的支持是非常优秀的。除了java.net.*包和apache包中的API,Android本身也对网络连接进行了封装,体现在android.net.*包中。
所以在Android中实现网络连接是非常容易的。

本文的实现涉及到三个类,Utility.java,RequestParameters.java,RequestException.java。Utility类就是封装网络连接的最主要接口,而RequestParameters封装了请求
数据,而RequestException是封装了请求error时的数据。
其实,这几个类是麦洛在写新浪微博时,从新浪微博中抽取出来的。麦洛觉得好的东西应该不断积累,然后在实际项目中熟悉和运用,最后形成自己独特的知识。
而现在就是积累的第一步。

首先看Utility类的实现,这个类的封装其实也不难,看看新浪微博中的实现,学习一下吧

/**
* Utility class for http request.
*
*/

public class Utility {

private static RequestParameters mRequestHeader = new RequestParameters();

public static final String BOUNDARY = "7cd4a6d158c";
public static final String MP_BOUNDARY = "--" + BOUNDARY;
public static final String END_MP_BOUNDARY = "--" + BOUNDARY + "--";
public static final String MULTIPART_FORM_DATA = "multipart/form-data";

public static final String HTTPMETHOD_POST = "POST";
public static final String HTTPMETHOD_GET = "GET";
public static final String HTTPMETHOD_DELETE = "DELETE";

private static final int SET_CONNECTION_TIMEOUT = 50000;
private static final int SET_SOCKET_TIMEOUT = 200000;

// 设置http头,如果authParam不为空,则表示当前有token认证信息需要加入到头中
public static void setHeader(String httpMethod, HttpUriRequest request,
RequestParameters authParam, String url) throws RequestException {
if (!isBundleEmpty(mRequestHeader)) {
for (int loc = 0; loc &lt; mRequestHeader.size(); loc++) {
String key = mRequestHeader.getKey(loc);
request.setHeader(key, mRequestHeader.getValue(key));
}
}
if (!isBundleEmpty(authParam)) {
String authHeader = "";
if (authHeader != null) {
request.setHeader("Authorization", authHeader);
}
}
request.setHeader("User-Agent", System.getProperties().getProperty("http.agent")
+ " WeiboAndroidSDK");
}

public static boolean isBundleEmpty(RequestParameters bundle) {
if (bundle == null || bundle.size() == 0) {
return true;
}
return false;
}

// 填充request bundle
public static void setRequestHeader(String key, String value) {
// mRequestHeader.clear();
mRequestHeader.add(key, value);
}

public static void setRequestHeader(RequestParameters params) {
mRequestHeader.addAll(params);
}

public static void clearRequestHeader() {
mRequestHeader.clear();

}

public static String encodePostBody(Bundle parameters, String boundary) {
if (parameters == null)
return "";
StringBuilder sb = new StringBuilder();

for (String key : parameters.keySet()) {
if (parameters.getByteArray(key) != null) {
continue;
}

sb.append("Content-Disposition: form-data; name=\"" + key + "\"\r\n\r\n"
+ parameters.getString(key));
sb.append("\r\n" + "--" + boundary + "\r\n");
}

return sb.toString();
}

public static String encodeUrl(RequestParameters parameters) {
if (parameters == null) {
return "";
}

StringBuilder sb = new StringBuilder();
boolean first = true;
for (int loc = 0; loc &lt; parameters.size(); loc++) {
if (first)
first = false;
else
sb.append("&amp;");
sb.append(URLEncoder.encode(parameters.getKey(loc)) + "="
+ URLEncoder.encode(parameters.getValue(loc)));
}
return sb.toString();
}

public static Bundle decodeUrl(String s) {
Bundle params = new Bundle();
if (s != null) {
String array[] = s.split("&amp;");
for (String parameter : array) {
String v[] = parameter.split("=");
params.putString(URLDecoder.decode(v[0]), URLDecoder.decode(v[1]));
}
}
return params;
}

/**
* Parse a URL query and fragment parameters into a key-value bundle.
*
* @param url
* the URL to parse
* @return a dictionary bundle of keys and values
*/
public static Bundle parseUrl(String url) {
// hack to prevent MalformedURLException
url = url.replace("weiboconnect", "http");
try {
URL u = new URL(url);
Bundle b = decodeUrl(u.getQuery());
b.putAll(decodeUrl(u.getRef()));
return b;
} catch (MalformedURLException e) {
return new Bundle();
}
}

/**
* Construct a url encoded entity by parameters .
*
* @param bundle
* :parameters key pairs
* @return UrlEncodedFormEntity: encoed entity
*/
public static UrlEncodedFormEntity getPostParamters(Bundle bundle) throws RequestException {
if (bundle == null || bundle.isEmpty()) {
return null;
}
try {
List form = new ArrayList();
for (String key : bundle.keySet()) {
form.add(new BasicNameValuePair(key, bundle.getString(key)));
}
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(form, "UTF-8");
return entity;
} catch (UnsupportedEncodingException e) {
throw new RequestException(e);
}
}

/**
* Implement a weibo http request and return results .
*
* @param context
* : context of activity
* @param url
* : request url of open api
* @param method
* : HTTP METHOD.GET, POST, DELETE
* @param params
* : Http params , query or postparameters
* @return UrlEncodedFormEntity: encoed entity
*/

public static String openUrl(Context context, String url, String method,
RequestParameters params) throws RequestException {
String rlt = "";
String file = "";
if(params!=null){
for (int loc = 0; loc &lt; params.size(); loc++) { String key = params.getKey(loc); if (key.equals("pic")) { file = params.getValue(key); params.remove(key); } } } if (TextUtils.isEmpty(file)) { rlt = openUrl(context, url, method, params, null); } else { rlt = openUrl(context, url, method, params, file); } return rlt; } public static String openUrl(Context context, String url, String method, RequestParameters params, String file) throws RequestException { String result = ""; try { HttpClient client = getNewHttpClient(context); HttpUriRequest request = null; ByteArrayOutputStream bos = null; if (method.equals("GET")) { url = url + "?" + encodeUrl(params); HttpGet get = new HttpGet(url); request = get; } else if (method.equals("POST")) { HttpPost post = new HttpPost(url); byte[] data = null; bos = new ByteArrayOutputStream(1024 * 50); if (!TextUtils.isEmpty(file)) { Utility.paramToUpload(bos, params); post.setHeader("Content-Type", MULTIPART_FORM_DATA + "; boundary=" + BOUNDARY); Bitmap bf = BitmapFactory.decodeFile(file); Utility.imageContentToUpload(bos, bf); } else { post.setHeader("Content-Type", "application/x-www-form-urlencoded"); String postParam = encodeParameters(params); data = postParam.getBytes("UTF-8"); bos.write(data); } data = bos.toByteArray(); bos.close(); // UrlEncodedFormEntity entity = getPostParamters(params); ByteArrayEntity formEntity = new ByteArrayEntity(data); post.setEntity(formEntity); request = post; } else if (method.equals("DELETE")) { request = new HttpDelete(url); } setHeader(method, request, params, url); HttpResponse response = client.execute(request); StatusLine status = response.getStatusLine(); int statusCode = status.getStatusCode(); if (statusCode != 200) { result = read(response); throw new RequestException(String.format(status.toString()), statusCode); } // parse content stream from response result = read(response); return result; } catch (IOException e) { throw new RequestException(e); } } public static HttpClient getNewHttpClient(Context context) { try { KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); trustStore.load(null, null); SSLSocketFactory sf = new MySSLSocketFactory(trustStore); sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); HttpParams params = new BasicHttpParams(); HttpConnectionParams.setConnectionTimeout(params, 10000); HttpConnectionParams.setSoTimeout(params, 10000); HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1); HttpProtocolParams.setContentCharset(params, HTTP.UTF_8); SchemeRegistry registry = new SchemeRegistry(); registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80)); registry.register(new Scheme("https", sf, 443)); ClientConnectionManager ccm = new ThreadSafeClientConnManager(params, registry); // Set the default socket timeout (SO_TIMEOUT) // in // milliseconds which is the timeout for waiting for data. HttpConnectionParams.setConnectionTimeout(params, Utility.SET_CONNECTION_TIMEOUT); HttpConnectionParams.setSoTimeout(params, Utility.SET_SOCKET_TIMEOUT); HttpClient client = new DefaultHttpClient(ccm, params); WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); if (!wifiManager.isWifiEnabled()) { // 获取当前正在使用的APN接入点 Uri uri = Uri.parse("content://telephony/carriers/preferapn"); Cursor mCursor = context.getContentResolver().query(uri, null, null, null, null); if (mCursor != null &amp;&amp; mCursor.moveToFirst()) { // 游标移至第一条记录,当然也只有一条 String proxyStr = mCursor.getString(mCursor.getColumnIndex("proxy")); if (proxyStr != null &amp;&amp; proxyStr.trim().length() &gt; 0) {
HttpHost proxy = new HttpHost(proxyStr, 80);
client.getParams().setParameter(ConnRouteParams.DEFAULT_PROXY, proxy);
}
mCursor.close();
}
}
return client;
} catch (Exception e) {
return new DefaultHttpClient();
}
}

public static class MySSLSocketFactory extends SSLSocketFactory {
SSLContext sslContext = SSLContext.getInstance("TLS");

public MySSLSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException,
KeyManagementException, KeyStoreException, UnrecoverableKeyException {
super(truststore);

TrustManager tm = new X509TrustManager() {
public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
}

public void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
}

public X509Certificate[] getAcceptedIssuers() {
return null;
}
};

sslContext.init(null, new TrustManager[] { tm }, null);
}

@Override
public Socket createSocket(Socket socket, String host, int port, boolean autoClose)
throws IOException, UnknownHostException {
return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose);
}

@Override
public Socket createSocket() throws IOException {
return sslContext.getSocketFactory().createSocket();
}
}

/**
* Get a HttpClient object which is setting correctly .
*
* @param context
* : context of activity
* @return HttpClient: HttpClient object
*/
public static HttpClient getHttpClient(Context context) {
BasicHttpParams httpParameters = new BasicHttpParams();
// Set the default socket timeout (SO_TIMEOUT) // in
// milliseconds which is the timeout for waiting for data.
HttpConnectionParams.setConnectionTimeout(httpParameters, Utility.SET_CONNECTION_TIMEOUT);
HttpConnectionParams.setSoTimeout(httpParameters, Utility.SET_SOCKET_TIMEOUT);
HttpClient client = new DefaultHttpClient(httpParameters);
WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
if (!wifiManager.isWifiEnabled()) {
// 获取当前正在使用的APN接入点
Uri uri = Uri.parse("content://telephony/carriers/preferapn");
Cursor mCursor = context.getContentResolver().query(uri, null, null, null, null);
if (mCursor != null &amp;&amp; mCursor.moveToFirst()) {
// 游标移至第一条记录,当然也只有一条
String proxyStr = mCursor.getString(mCursor.getColumnIndex("proxy"));
if (proxyStr != null &amp;&amp; proxyStr.trim().length() &gt; 0) {
HttpHost proxy = new HttpHost(proxyStr, 80);
client.getParams().setParameter(ConnRouteParams.DEFAULT_PROXY, proxy);
}
mCursor.close();
}
}
return client;
}

/**
* Upload image into output stream .
*
* @param out
* : output stream for uploading weibo
* @param imgpath
* : bitmap for uploading
* @return void
*/
private static void imageContentToUpload(OutputStream out, Bitmap imgpath)
throws RequestException {
StringBuilder temp = new StringBuilder();

temp.append(MP_BOUNDARY).append("\r\n");
temp.append("Content-Disposition: form-data; name=\"pic\"; filename=\"")
.append("news_image").append("\"\r\n");
String filetype = "image/png";
temp.append("Content-Type: ").append(filetype).append("\r\n\r\n");
byte[] res = temp.toString().getBytes();
BufferedInputStream bis = null;
try {
out.write(res);
imgpath.compress(CompressFormat.PNG, 75, out);
out.write("\r\n".getBytes());
out.write(("\r\n" + END_MP_BOUNDARY).getBytes());
} catch (IOException e) {
throw new RequestException(e);
} finally {
if (null != bis) {
try {
bis.close();
} catch (IOException e) {
throw new RequestException(e);
}
}
}
}

/**
* Upload weibo contents into output stream .
*
* @param baos
* : output stream for uploading weibo
* @param params
* : post parameters for uploading
* @return void
*/
private static void paramToUpload(OutputStream baos, RequestParameters params)
throws RequestException {
String key = "";
for (int loc = 0; loc &lt; params.size(); loc++) { key = params.getKey(loc); StringBuilder temp = new StringBuilder(10); temp.setLength(0); temp.append(MP_BOUNDARY).append("\r\n"); temp.append("content-disposition: form-data; name=\"").append(key).append("\"\r\n\r\n"); temp.append(params.getValue(key)).append("\r\n"); byte[] res = temp.toString().getBytes(); try { baos.write(res); } catch (IOException e) { throw new RequestException(e); } } } /** * Read http requests result from response . * * @param response * : http response by executing httpclient * * @return String : http response content */ private static String read(HttpResponse response) throws RequestException { String result = ""; HttpEntity entity = response.getEntity(); InputStream inputStream; try { inputStream = entity.getContent(); ByteArrayOutputStream content = new ByteArrayOutputStream(); Header header = response.getFirstHeader("Content-Encoding"); if (header != null &amp;&amp; header.getValue().toLowerCase().indexOf("gzip") &gt; -1) {
inputStream = new GZIPInputStream(inputStream);
}

// Read response into a buffered stream
int readBytes = 0;
byte[] sBuffer = new byte[512];
while ((readBytes = inputStream.read(sBuffer)) != -1) {
content.write(sBuffer, 0, readBytes);
}
// Return result from buffered stream
result = new String(content.toByteArray());
return result;
} catch (IllegalStateException e) {
throw new RequestException(e);
} catch (IOException e) {
throw new RequestException(e);
}
}

/**
* Read http requests result from inputstream .
*
* @param inputstream
* : http inputstream from HttpConnection
*
* @return String : http response content
*/
private static String read(InputStream in) throws IOException {
StringBuilder sb = new StringBuilder();
BufferedReader r = new BufferedReader(new InputStreamReader(in), 1000);
for (String line = r.readLine(); line != null; line = r.readLine()) {
sb.append(line);
}
in.close();
return sb.toString();
}

/**
* Clear current context cookies .
*
* @param context
* : current activity context.
*
* @return void
*/
public static void clearCookies(Context context) {
@SuppressWarnings("unused")
CookieSyncManager cookieSyncMngr = CookieSyncManager.createInstance(context);
CookieManager cookieManager = CookieManager.getInstance();
cookieManager.removeAllCookie();
}

/**
* Display a simple alert dialog with the given text and title.
*
* @param context
* Android context in which the dialog should be displayed
* @param title
* Alert dialog title
* @param text
* Alert dialog message
*/
public static void showAlert(Context context, String title, String text) {
Builder alertBuilder = new Builder(context);
alertBuilder.setTitle(title);
alertBuilder.setMessage(text);
alertBuilder.create().show();
}

public static String encodeParameters(RequestParameters httpParams) {
if (null == httpParams || Utility.isBundleEmpty(httpParams)) {
return "";
}
StringBuilder buf = new StringBuilder();
int j = 0;
for (int loc = 0; loc &lt; httpParams.size(); loc++) {
String key = httpParams.getKey(loc);
if (j != 0) {
buf.append("&amp;");
}
try {
buf.append(URLEncoder.encode(key, "UTF-8")).append("=")
.append(URLEncoder.encode(httpParams.getValue(key), "UTF-8"));
} catch (java.io.UnsupportedEncodingException neverHappen) {
}
j++;
}
return buf.toString();

}

/**
* Base64 encode mehtod for weibo request.Refer to weibo development
* document.
*
*/
public static char[] base64Encode(byte[] data) {
final char[] alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
.toCharArray();
char[] out = new char[((data.length + 2) / 3) * 4];
for (int i = 0, index = 0; i &lt; data.length; i += 3, index += 4) {
boolean quad = false;
boolean trip = false;
int val = (0xFF &amp; (int) data[i]);
val &lt;&gt;= 6;
out[index + 2] = alphabet[(trip ? (val &amp; 0x3F) : 64)];
val &gt;&gt;= 6;
out[index + 1] = alphabet[val &amp; 0x3F];
val &gt;&gt;= 6;
out[index + 0] = alphabet[val &amp; 0x3F];
}
return out;
}

}

另外两个类比较简单,可以到googlecode中下载完整的项目来查看

http://code.google.com/p/http-wrap/

[转载]《构建高性能的web站点》读书笔记

mikel阅读(937)

[转载]《构建高性能的web站点》读书笔记–分离之后 – for certain – 博客园.

这一篇之所以命名分离之后,是因为当我们前面负载均衡的介绍,假设每台实际服务器都是相同的,就是拥有相同的文件和程序。实际上如何要实现的每台实际服务器都有相同的资源呢?这篇我们将分享网络共享、内容分发同步和分布式文件系统。

网络共享

这个方式就是把资源放到一个网络上的服务器,多个实际请求处理服务器都可以以相同的方式访问到这些资源,像我们常见的数据库服务器,我们的程序都可以网络 的方式访问数据库,获得数据后,生成HTML,返回给用户,还有我们前面关于缓存中提到的缓存服务器,这里我们再介绍下文件共享系统。

文件共享系统让你不必考虑网络访问和传输的细节,你可以像访问本地文件系统一样访问网络上其它服务器文件系统上的文件。文件共享系统并不是通常意义上的磁 盘文件系统,它不能用于存储和管理磁盘数据,而只是定义了文件在网络传输过程中的组织格式和传输协议,当我们实际服务器应用共享文件系统规定的方式访问一 个文件时,内部实际要进行两次格式的转换,分别发生在从实际文件服务器进入网络时和从网络离开到达实际处理服务器时。

常用的文件共享系统有NFS和Samba,其中NFS主要用于UNIX/Lniux平台,而Samba设计初衷是将UNIX/Linux的文件映射到 Windows的网上邻居中,来实现UNIX/Linux到Windows的文件共享,同时也支持UNIX/Linux平台之间的文件共享。当然还有其它 的系统,我们可以根据实际需求和各系统的特性来决定采用哪个。

虽然它解决了我们文件的访问问题,但是我们必须明白:不同于磁盘IO,通过文件共享系统,文件服务器本身磁盘的吞吐率上限、并发处理能力和网络带宽都成了 制约我们系统的重大因素。除此之外,目前为止它还是一个依赖单点的解决方案,如果大量共享文件存在,我们必须考虑其它方案,如将文件复制到更多的服务器, 建立多级冗余。

内容分发和同步

由于上段提到的原因,我们要把文件复制到各个服务器,让服务器访问本地磁盘的文件来处理HTTP请求,我们一般通过两种方式来实现复制,主动分发和被动同步。

所谓主动分发,就是更新的时机在有文件改动时,如用户上传了新的图片,这时应用程序就会把此图片同步到其它相同功能的服务器。如果相同服务器太多,或者部 署在不同区域,我们可以通过多级分发来解决这些问题,将任务分离到不同的机器上,发起服务器在同步几个结点之后,几个结点服务器再负责把该集群内其它服务 器的文件同步。在php中我们有SSH、SFTP、WebDAV来实现。如果文件操作过于频繁,实时的文件同步的请求就可能是一个灾难,服务器每时每刻都 要去处理文件的分发。

被动同步就是文件的一端主动向文件服务器发起请求,如果两者存在差异,进行有选择的更新,从而保证各服务器文件一致。linux下rsync工具可以出色 的完成此项工作,我们可以设置更新的时间间隙,每一隔一段时间去同步下,似乎一定程度减少了服务器压力,但是如果更新的文件过多,磁盘扫描文件来和本地磁 盘文件进行对比的开销将是非常的大。不过书中提到一种方法来减少扫描的开销,启用linux内核的inotify模块,通过它监视文件的动静,如果任何文 件修改时间变化后,发出事件通知,这样我们就可以编写程序来更新该文件的上级目录的修改时间,直到根目录。这样扫描时,从最早时候就能得知是否有文件改 动,没有改动时及时退出扫描。

分布式文件系统

通过以上方法,我们似乎能解决文件的同步问题,但是共享的单点瓶颈,而分发和同步,又需要我们实现太多的脚本和逻辑并且缺乏整体的管理和监控。幸好我们有 另一个考虑:分布式文件系统。分布式文件系统不是传统意义的操作系统,它工作在用户空间,由应用程序来实现,如MogileFs或Hadoop等,它们更 像底层操作系统的抽象,避免了文件系统的限制,拥有自己的内容组织结构,如MogileFS支持域和类,便于对大规模存储和复制进行合理规划。

当我们通过特定的接口操作分布式操作系统时,我们看到的是一个整体,但是在简单的接口背后,分布式文件系统内部,可以跨越多台服务器,根据自己的规则进行 自动的文件复制。当然现如今已有不少的分布式文件系统供大家选择使用,至于如何使用或内部的实现逻辑、优缺点还需要深入的了解。

 

文件系统这篇说的比较简单,都是大概性的东西,但是实用时却不是那么容易的,这里仅仅是提供一个思路,实际问题再实际解决吧,“车到山前必有路”嘛。文件之后,下篇分享数据库的优化和扩展。

[转载]分享两套MVC3开源程序:YQBlog个人博客系统以及YQCMS通用建站系统

mikel阅读(1077)

[转载]分享两套MVC3开源程序:YQBlog个人博客系统以及YQCMS通用建站系统 – YQ君 – 博客园.

开发环境: Visual Studio 2010(MVC3+EF4.0) + SQL2005
运行环境: .net framework 4.0 + SQL2005

 

预览 源码下载

 

1.为什么有两套程序?
YQBlog之前已经发布了两个版本,现在是1.2版本,YQCMS则是首次发布的1.0版本。
目 前很多流行的博客程序,除了用作博客网站外,很多人还会把它进行改造来做企业站。虽然实质上都是内容管理,但基于业务的偏差还是会有不少程序上的需求差 异。企业站的需求千差万别,如果我们想要去适用这些需求,则会导致程序的不断增长而变得庞大。如果仅仅作为一个博客程序来说话,这显然是不恰当的,所以就 有的这样的分离。

 

YQBlog更专注于个人博客系统的需求。
YQCMS则更注重网站程序的通用性,以期最大化的适应更多类型网站的需求。

 

2.程序架构简述

 

网站–>types–>categories–>articles
articledetail(SEO信息)
extend(扩展信息)

 

这两套程序在数据库建模部分比较大的特点就是没有根据业务功能的差异去建表,比如,这里没有专门的留言表,评论表,论坛帖子表,相册表,文章表等。 只有ariticle表以及articledetail表(SEO信息)。YQCMS多一个extend表(扩展信息)。好比我们硬盘上不管你是放的苍井 空还是东方时空都是表现为0和1.这里数据库里的数据表存的也就只是看起来差不多的数据。这些数据通过categories得以区分,表示它属于哪个分 类,categories在前台界面的表现相当于网站导航,categories再通过types予以区分,表示它是属于论坛,相册,文章还是留言板,进 而对数据进行差异的处理以及呈现,从而构成了我们整个网站。
types部分是硬编码在程序里的。可以理解为程序提供了哪些功能模块,比如YQBlog提供了文章,单页,相册,留言板,首页自定义区域,全网自定义区域这几个功能模块。YQCMS在其基础上还增加了论坛模块。基本上这些就是你利用目前的程序能做的事。

 

扩展新的功能模块:假如我们还需要扩展商城,微博,问答等模块。则只需要再增加一个对应的type,同时编写对应的业务逻辑代码。如果需求没有大的差异的话,基本上在article,categories部分的程序是通用的,我们只需要重点处理界面层即可。

 

总的来说,目前的程序架构很好的保证了功能模块可扩展性以及程序代码的通用性。
对于使用者来说,你可以把网站做的很简单,比如我只做一个日记本,那么仅仅创建一个文章类型的分类就好了,也可以很复杂,这完全取决于你在后台创建的分类模块的多少以及你的前台表现层视图。

 

3.多语言与模板切换

 

MVC在这两方面提供了很好的解决方案。
拿到源码后,打开会发现除了注释就再不到一个中文字,是的,我们把所有的文字描述都集中在了Resource资源项目中。如图。

 

 
3个文件分别表示英文,中文,繁体,上图是根据程序模块以不同文件夹归类,如果需要扩展其它语言,对应新增其语言文件就好了。基本上就是简单的复制粘贴工作。

 

模板切换
MVC中的模板切换实质是即时的改变视图引擎路径。实现很简单:

 

复制代码
 ViewEngines.Engines.Clear(); ViewEngines.Engines.Add(new BlogViewEngine(theme)); /// <summary>
    /// 自定义视图路径 /// </summary>
    public class BlogViewEngine : RazorViewEngine { public BlogViewEngine(string key) { string path = string.IsNullOrWhiteSpace(key)||key=="default" ? "~/Views" : "~/Themes/" + key; ViewLocationFormats = new[] { path+"/{1}/{0}.cshtml", path+"/{1}/{0}.vbhtml", path+"/Shared/{0}.cshtml", path+"/Shared/{0}.vbhtml" }; PartialViewLocationFormats = ViewLocationFormats; } }
复制代码

 

 

 
MVC的Razor视图引擎个人觉得相比其它流行程序的模板制作来得更加直观,容易且得心应手。Razor语法也很简单,稍微会一些就可以 轻松驾驭模板定制了,不懂后台编程的人,它就相当于一个html页,你可以完全把它当做html页来操作。懂后台编程的,你也可以在其中写C#程序,非常 灵活。

 

打开http://www.yqhome.net/,右上角分别是语言以及模板的切换链接,实现即时交替切换,你可以发现这是很灵活的。模板切换也并不只是换了一个css显示样式,而是更换了整个
表现层视图路径,就是说,你可以在多个完全不同的视图之间实现切换,使得看起来他们就是完全不同的网站。

 

目前提供了3种语言2个模板,相当于2*3=6个不同样式网站。假如后期我又扩展到了6个不同语言以及10个模板风格。那我就有了60个不同的网站了:)

 
YQBLOG默认模板以及多语言设置

 

 
YQCMS默认模板以及多语言设置

 

 

YQCMS不同的语言可以设定不同的模板,这样可以让你的网站在不同语言切换时候呈现出完全不同的表现风格。

多语言网站内容独立:除了不同语言界面的差异外,这个设置可以使得你的不同语言的网站内容也完全独立。你可以根据你的需求来予以设置或者变更。

 

如何操作不同语言下的数据? 切换到不同的语言,即可对当前语言的下的数据进行操作。非常方便。

 

 

4.静态化
静态化在后台的相关设置:

 


是否启用静态url:前台文章页面可以在动态与静态url之间实时切换。不启用的时候则表现为其动态url.
比如:
http://www.yqhome.net/archive/671
http://www.yqhome.net/static/2012/09/671.html
他们是同一个页面,前者是动态路径,后者是已经生成为html页面的静态路径。

 

这里也许你会有一个问题,静态化与多语言多模板之间的关系。前面说到假如你6个不同语言以及10个模板风格。那就相当于是60个不同的网站,静态化的时候也是一个页面生成60个版本的html页吗?

 

这里的处理是页面会依据你设置的默认模板,默认语言,以及勾选的多语言项来进行生成。6个不同语言以及10个模板风格,则会生成6个不同语言的静态页。

 

也就是,一个文章页会基于你的“默认模板”,生成你“勾选的多语言项”个数的不同语言静态页面,其中除默认语言外的其它语言静态页都会加入其语言关键字路径。如:
http://www.yqhome.net/static/en-us/2012/09/671.html
http://www.yqhome.net/static/zh-tw/2012/09/671.html

 

当切换到非默认模板的其它模板时候,页面会以动态url来呈现。

 

如果你中途更换默认模板或者默认语言,同时又需要启用静态url。那么你需要把之前的静态页再重新整体生成。

 

是否生成文章html页 :如果 是否启用静态url 是 yes,这里也必须是yes.

 

静态化目录(可用{year}{month}{day}表示当前年,月,日):
这个格式是约定好了的,然后你可以自定义路径名称以及层级。

 
页面批量生成:

 

 

5.扩展字段

 

 
YQCMS中加入了扩展字段功能,综合了很多方案,最后还是采用了最简单直接的预留字段的方法。原理很简单,新增一个extend表10个字段,用来应对可能的差异化需求。这个部分处理的重点主要在于后台相关功能的界面处理,让它对于用户来说会“很好用”。

 

在“网站–>types–>categories–>articles”结构层级下
扩展是表现于articles 部分的扩展,但扩展的设置可以分别定义在types或者categories上,这里会遵循一个覆盖以及继承的关系。就是说你可以整体定义文章类型所需的 扩展字段,同时也可以在下面某个具体分类下定义扩展。规则就是:下级没有时会继承,有的话则采用自身的定义。

 

6.提交到YQHome.net
如果你的网站上线了,可以通过后台首页的这个功能把你的网站信息提交到YQHome.net。分享的同时也提高网站的点击率。
这个过程原理上就是生成一个site.htm静态页,提交过程就是访问一下www.yqhome.net的指定页面,该页会负责采集你的site.htm数据予以入库。这里是生成一条”案例”文章记录以及在论坛”案例展示”的帖子下追加一条回帖。

 

eg:http://www.yqhome.net/forum/thread/664#f_1

 

 
7.最后播几段广告
(1)如果你正在学习MVC,这两套源码会是你不错的选择
(2)如果你正打算建一个独立博客,并不那么想去接触php的东西,更倾向于.net的程序,自己还可以修修改改,YQBlog会是不错的选择。
(3)如果你需要建一个个人网站,企业网站,政府网站,内容管理网站,YQCMS会是不错的选择,有了它,你真的只需要集中精力处理界面就可以了。
(3)如果你需要建一个企业站,需要多语言,以及多个语言还需要不同的样式呈现。你更应该选择YQCMS.
(5)如果你的企业近期有招聘计划,需要一个.net方面的熟手,我会是你不错的选择。【工作联系 tel:13619206326 或者qq:12833318 联系人:陈生,地点:西安】

 

 

 

题外话:在上一家公司干了有5年,俗话说不进则退,真的是没有什么前进的空间,那就是一直在退步了,于是开始想要改变,这是年初的时候的状况,应该 是3月份,于是开始留意一些招聘信息,有一家公司挺吸引我的,但一看要求,mvc,不会,wcf不会。不会就赶紧学唄,兴许能赶上。结果越学越觉得自己浅 薄,到现在,过去了差不多半年时间,有了现在的YQBlog,YQCMS.不过那家公司估计也早就招聘结束了。但这个过程还是让我收获颇丰,除了这两套程 序本身外,让我更加懂得了专注与坚持,之前工作那么多年,什么都没留下,然后这半年时间的自学却成就了真正属于我自己的两套程序。好比我们有时候看身边成 功的案例,你会觉得他们并没有什么过人的地方,甚至某些条件还不如自己,但也许他们多出的仅仅只是一份坚持。我们成长过程很多时候都缺乏主观意愿,被这个 大时代裹挟着就向前走了,可能你都想不起是从什么时候开始,具体是什么因由你就开始变成了一枚程序员了。那么既然上了这条贼船,那就一黑走到底吧。某份工 作或许不满意,但还是要坚持coding到死。今年30了。再写30年吧。

 

 

如果你觉得我的源码对你有帮助,或者乐意帮助我尽快找到新工作。请小小的推荐一下吧

[转载]ASP.NET MVC:自定义 Route 以生成小写的 Url

mikel阅读(933)

[转载]ASP.NET MVC:自定义 Route 以生成小写的 Url_知识库_博客园.

先给出本文中测试用的 controller:

public class PersonsController : Controller
{
    public ActionResult Query(string name)
    {
        return View();
    }
}

  ASP.NET 中 Url 大小写

不严格来讲,ASP.NET MVC 对 Url 是不敏感的,以下 Url 都是相同的,都可以访问到 PersonController 的 Query 方法:

  1. ~/Persons/Query
  2. ~/PERSONS/QUERY
  3. ~/persons/query

但对 MVC 的数据绑定来说,大小写似乎还是有区别的:

  1. ~/Persons/Query?Name=Bob
  2. ~/Persons/Query?Name=bob

对以上两个 Url,Query 中 name 参数会接收到两个不同值:Bobbob。Action 中的参数只是原样接收,并没有作任何处理。至于name 字符串的大小写是否敏感要看具体的应用了。

再回头看前面的三个 Url:

  1. ~/Persons/Query: 是 MVC 中默认生成的,因为在 .Net 中方法命名通常采用 PascalCase;
  2. ~/PERSONS/QUERY: 全部大写,这种写法很不友好,很难读,应该杜绝采用这种方式;
  3. ~/persons/query:这种方式比较好,易读,也是大多数人选择的方式。

本文探讨如何在 MVC 中使用第三种方式,也就是小写(但不完全小写),目标如下:

在不影响程序正常运行的前提下,将所有能小写的都小写,如:

~/persons/query?name=Bob&age=18

~/persons/query/name/Bob/age/18

  MVC 中 Url 的生成

在 View 中生成超级链接有多种方式:

<%: Html.ActionLink("人员查询", "Query", "Persons", new { name = "Bob" }, null) %>
<%: Html.RouteLink("人员查询", new { controller = "Persons", action = "Query", name = "Bob" })%>
<a href="<%:Url.Action("Query", "Persons", new { name="Bob" }) %>">人员查询</a>

在 Action 中,可以使用 RedirectTo 来调转至新的页面:

return RedirectToAction("Query", "Persons", new { name = "Bob" });
return RedirectToRoute(new { controller = "Persons", action = "Query", name = "Bob" });

ActionLink、RouteLink、RedirectToAction 和 RedirectToRouter 都会生成 Url,并最终显示在浏览器的地址栏中。

这四个方法都有很多重载,想从这里下手控制 Url 小写实在是太麻烦了。当然也不可行,因为可能还有其它方式来生成 Url。

MVC 是一个非常优秀的框架,但凡优秀的框架都会遵循 DRY(Don’t repeat yourself) 原则,MVC 也不例外。MVC 中 RouteBase 负责 Url 的解析和生成:

public abstract class RouteBase
{
    public abstract RouteData GetRouteData(HttpContextBase httpContext);
    public abstract VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values);
}

GetRouteData 用来解析 Url,GetVirtualPath 用来生成 Url。ActionLink、RouteLink、RedirectToAction 和 RedirectToRouter 内部都会调用 GetVirtualPath 方法来生成 Url。

因此我们的入手点就是 GetVirtualPath 方法。

  自定义 Route 以生成小写的 Url

MVC 中 RouteBase 的具体实现类是 Route,我们经常在 Global.asax 中经常使用:

public class MvcApplication : System.Web.HttpApplication
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

        routes.MapRoute(
            "Default", // Route name
            "{controller}/{action}/{id}", // URL with parameters
            new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
        );
    }
    //...
}

MapRoute 返回 Route,MapRoute 有很多重载,用来简化我们构建 Route 的过程。

Route 类没有给我们提供可直接扩展的地方,因此我们只能自定义一个新的 Route 来实现我们的小写 Url。但处理路由的细节也是相当麻烦的,因此我们最简单的方式就是写一个继承自 Route 的类,然后重写它的 GetVirtualPath 方法:

public class LowerCaseUrlRoute : Route
{
    public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
    {
        //在此处进行小写处理
        return base.GetVirtualPath(requestContext, values);
    }
}

再来看下我们的目标:

~/persons/query?name=Bob&age=18

~/persons/query/name/Bob/age/18

其实我们只需要进行两步操作:

  1. 将路由中的 area、controller、action 的值都变成小写;
  2. 将路由中其它键值对的键变成小写,如:Name=Bob 中的 Name。

那我们先来完成这个功能吧:

private static readonly string[] requiredKeys = new [] { "area", "controller", "action" };

private void LowerRouteValues(RouteValueDictionary values)
{
    foreach (var key in requiredKeys)
    {
        if (values.ContainsKey(key) == false) continue;

        var value = values[key];
        if (value == null) continue;

        var valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
        if (valueString == null) continue;

        values[key] = valueString.ToLower();
    }

    var otherKyes = values.Keys
        .Except(requiredKeys, StringComparer.InvariantCultureIgnoreCase)
        .ToArray();

    foreach (var key in otherKyes)
    {
        var value = values[key];
        values.Remove(key);
        values.Add(key.ToLower(), value);
    }
}

GetVirtualPath 生成 Url 时,会将 requestContext.RouteData.Values、values(第二个参数) 以及 Defaults(当前 Router 的默认值)三个 RouteValueDictionary 进行合并,如在 View 写了如下的一个 ActionLinks:

<%: Html.ActionLink("查看") %>

生成的 Html 代码可能是:

<a href="/Home/Details">查看</a>

因为没有指定 Controller,MVC 会自动使用当前的,即从 requestContext.RouteData.Values 中获取 Controller,得到 ”Home“;”Details“来自 values;如果连 ActionLink 中 Action 也不指定,那将会从 Defaults 中取值。

因此我们必须将这三个 RouteValueDictionary 都进行处理才能达到我们的目标:

public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
    LowerRouteValues(requestContext.RouteData.Values);
    LowerRouteValues(values);
    LowerRouteValues(Defaults);
    return base.GetVirtualPath(requestContext, values);
}

再加上几个构造函数,完整的 LowerCaseUrlRoute 如下:

public class LowerCaseUrlRoute : Route
{
    private static readonly string[] requiredKeys = new [] { "area", "controller", "action" };

    public LowerCaseUrlRoute(string url, IRouteHandler routeHandler)
        : base(url, routeHandler) { }
    
    public LowerCaseUrlRoute(string url, RouteValueDictionary defaults, IRouteHandler routeHandler)
        : base(url, defaults, routeHandler){ }

    public LowerCaseUrlRoute(string url, RouteValueDictionary defaults, RouteValueDictionary constraints, IRouteHandler routeHandler)
        : base(url, defaults, constraints, routeHandler) { }
    public LowerCaseUrlRoute(string url, RouteValueDictionary defaults, RouteValueDictionary constraints, RouteValueDictionary dataTokens, IRouteHandler routeHandler)
        : base(url, defaults, constraints, dataTokens, routeHandler) { }    

    public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
    {
        LowerRouteValues(requestContext.RouteData.Values);
        LowerRouteValues(values);
        LowerRouteValues(Defaults);
        return base.GetVirtualPath(requestContext, values);
    }

    private void LowerRouteValues(RouteValueDictionary values)
    {
        foreach (var key in requiredKeys)
        {
            if (values.ContainsKey(key) == false) continue;

            var value = values[key];
            if (value == null) continue;

            var valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
            if (valueString == null) continue;

            values[key] = valueString.ToLower();
        }

        var otherKyes = values.Keys
            .Except(requiredKeys, StringComparer.InvariantCultureIgnoreCase)
            .ToArray();

        foreach (var key in otherKyes)
        {
            var value = values[key];
            values.Remove(key);
            values.Add(key.ToLower(), value);
        }
    }
}

有了 LowerCaseUrlRoute,我们就可以修改 Global.asax 文件中的路由了。

  创建 LowerCaseUrlRouteMapHelper

这一步不是必须的,但有了这个 MapHelper 我们在修改 Global.asax 文件中的路由时可以非常方便:

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

    routes.MapLowerCaseUrlRoute( //routes.MapRoute(
        "Default", // Route name
        "{controller}/{action}/{id}", // URL with parameters
        new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
    );
}

尤其是已经配置了很多路由的情况下,其代码如下:

public static class LowerCaseUrlRouteMapHelper
{
    public static LowerCaseUrlRoute MapLowerCaseUrlRoute(this RouteCollection routes, string name, string url){
        return routes.MapLowerCaseUrlRoute(name, url, null, null);
    }
    public static LowerCaseUrlRoute MapLowerCaseUrlRoute(this RouteCollection routes, string name, string url, object defaults){
        return routes.MapLowerCaseUrlRoute(name, url, defaults, null);
    }
    public static LowerCaseUrlRoute MapLowerCaseUrlRoute(this RouteCollection routes, string name, string url, string[] namespaces){
        return routes.MapLowerCaseUrlRoute(name, url, null, null, namespaces);
    }
    public static LowerCaseUrlRoute MapLowerCaseUrlRoute(this RouteCollection routes, string name, string url, object defaults, object constraints){
        return routes.MapLowerCaseUrlRoute(name, url, defaults, constraints, null);
    }
    public static LowerCaseUrlRoute MapLowerCaseUrlRoute(this RouteCollection routes, string name, string url, object defaults, string[] namespaces){
        return routes.MapLowerCaseUrlRoute(name, url, defaults, null, namespaces);
    }
    public static LowerCaseUrlRoute MapLowerCaseUrlRoute(this RouteCollection routes, string name, string url, object defaults, object constraints, string[] namespaces){
        if (routes == null) throw new ArgumentNullException("routes");
        if (url == null) throw new ArgumentNullException("url");
        LowerCaseUrlRoute route2 = new LowerCaseUrlRoute(url, new MvcRouteHandler());
        route2.Defaults = new RouteValueDictionary(defaults);
        route2.Constraints = new RouteValueDictionary(constraints);
        route2.DataTokens = new RouteValueDictionary();
        LowerCaseUrlRoute item = route2;
        if ((namespaces != null) && (namespaces.Length > 0))
            item.DataTokens["Namespaces"] = namespaces;
        routes.Add(name, item);
        return item;
    }

    public static LowerCaseUrlRoute MapLowerCaseUrlRoute(this AreaRegistrationContext context, string name, string url){
        return context.MapLowerCaseUrlRoute(name, url, null);
    }
    public static LowerCaseUrlRoute MapLowerCaseUrlRoute(this AreaRegistrationContext context, string name, string url, object defaults){
        return context.MapLowerCaseUrlRoute(name, url, defaults, null);
    }
    public static LowerCaseUrlRoute MapLowerCaseUrlRoute(this AreaRegistrationContext context, string name, string url, string[] namespaces){
        return context.MapLowerCaseUrlRoute(name, url, null, namespaces);
    }
    public static LowerCaseUrlRoute MapLowerCaseUrlRoute(this AreaRegistrationContext context, string name, string url, object defaults, object constraints)        {
        return context.MapLowerCaseUrlRoute(name, url, defaults, constraints, null);
    }
    public static LowerCaseUrlRoute MapLowerCaseUrlRoute(this AreaRegistrationContext context, string name, string url, object defaults, string[] namespaces){
        return context.MapLowerCaseUrlRoute(name, url, defaults, null, namespaces);
    }
    public static LowerCaseUrlRoute MapLowerCaseUrlRoute(this AreaRegistrationContext context, string name, string url, object defaults, object constraints, string[] namespaces)
    {
        if ((namespaces == null) && (context.Namespaces != null))
            namespaces = context.Namespaces.ToArray<string>();
        LowerCaseUrlRoute route = context.Routes.MapLowerCaseUrlRoute(name, url, defaults, constraints, namespaces);
        route.DataTokens["area"] = context.AreaName;
        bool flag = (namespaces == null) || (namespaces.Length == 0);
        route.DataTokens["UseNamespaceFallback"] = flag;
        return route;
    }
}

  总结

大功告成,如果你感兴趣,不妨尝试下!写到这里吧,如果需要,请下载本文中的示例代码:MvcLowerCaseUrlRouteDemo.rar(209KB)如果你有其它办法,欢迎交流!