[转载]c#导出Excel文件,多表加分页

mikel阅读(1099)

[转载]c#导出Excel文件,多表加分页。。。 – {{°莪乖乖 – 博客园.

最近项目中需要导出Excel文件:

要求是这样的:

实现的没啥技术,就是调用了几个方法工具方法而已,以后备用


——————————-

——————————–

先看下基本功能吧–:

——————————–

——————————–

——————————–

——————————–

前台页面,只有俩按钮–~!

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="ToExcle.aspx.cs" Inherits="Maps_ToExcle" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <title></title> <script type="text/javascript" src="static/jquery-1.4.1.min.js"></script> <script type="text/javascript"> var currpage; var downurl = ""; //下载文件地址 var timer = ""; //定时器 var runcount = 0; //要输出的>个数 $(function() {//初始化绑定 $("#btn_toExcel").click(function() { $(this).attr("disabled", "disabled"); saveToExcelData(); }); $("#btn_downExcel").click(function() { btn_downExcel() }); }) function getAjax(_url) {//ajax操作 $.ajax({ url: _url, type: 'get', beforeSend: function(XMLHttpRequest) { $('#ajaxloadimg,#ajax_msg').show(); timer = window.setInterval("intervalRun()", 500); }, success: function(data, textStatus) { downurl = data; $('#ajaxloadimg').hide(); $("#btn_downExcel").attr("disabled", ""); window.clearInterval(timer); $('#ajax_msg').text("生成完毕,如没有自动下载,请点击下载按钮。"); openfile(data); }, error: function(XMLHttpRequest, textStatus) { $("#btn_toExcel").attr("disabled", ""); $('#ajaxloadimg').hide(); window.clearInterval(timer); $('#ajax_msg').text("生成错误,请重试。"); }, complete: function(XMLHttpRequest, textStatus) { window.clearInterval(timer); } }); } function saveToExcelData() {//设置要请求的地址数据 var url = 'ToExcelHandler.ashx?' + new Date() + '&action=test'; getAjax(url); } function openfile(_serverPath) {//打开下载对话框 var _left = (screen.width - 350) / 2; var _top = (screen.height - 130 - 63) / 2; var a = window.open(_serverPath, "_blank", "scrollbars=yes, resizable=yes,menubar=no,toolbar=yes,width=350,height=130,left=" + _left + ",top=" + _top); a.document.execCommand("SaveAs"); //a.close(); } function intervalRun() {//定时运行方法,提示用 runcount++; if (runcount > 3) { runcount = 0; } var _str = ""; for (var _i = 1; _i <= runcount; _i++) { _str += ">"; } $("#ajax_msg").text("正在生成打印文件,请稍后" + _str); } function btn_downExcel() {//下载按钮事件 openfile(downurl); } </script> </head> <body> <form id="form1" runat="server"> <div> <input id="btn_toExcel" type="button" value="导出" /> <input id="btn_downExcel" type="button" value="下载" disabled="disabled" /> <img style='display: none;' id='ajaxloadimg' src='image/ajax-loader.gif' alt="请稍后" />&nbsp;&nbsp;<span style="display: none; font-size: 12px; height: 20px; line-height: 20px;" id="ajax_msg"></span> </div> </form> </body> </html>

后台处理代码:

using System; using System.Web; using System.Reflection; using Microsoft.Office.Interop.Excel; using System.IO; using System.Collections; using System.Collections.Generic; using System.Web.SessionState; namespace prientExcel { public class ToExcelHandler : IHttpHandler, IRequiresSessionState { #region HttpRequest request = null; HttpResponse response = null; string action = string.Empty; object miss = Missing.Value; Dictionary<string, string> diction = new Dictionary<string, string>(); ArrayList arrayColName; ArrayList arrayShowName; ArrayList arrayType; dbcommeninit dbcmm = new dbcommeninit(); Application excelApp = null; Worksheet workSheet = null; string FilePath = @"打印数据.xls"; string DirPath = @"Download"; int TYPE = 0; System.Data.DataTable dt = null; List<string> tablename_list = new List<string>(); int recount = 0; int onpage = 1; int pagesize = 8; string tablename = ""; int LOOP1 = 1;//需要输出的分类数量 int LOOP2 = 1;//需要输出的工作表数量 #endregion public void ProcessRequest(HttpContext context) { request = context.Request; response = context.Response; action = request.Params["action"]; codeHandle(action); } #region 请求处理 private void codeHandle(string _action) { switch (_action) { case "test": createExcel(); break; default: break; } } #endregion private void DataTest() { tablename_list.Add("YG_PL_RPTDWZD"); tablename_list.Add("YG_PL_DIARYLEDGER"); tablename_list.Add("YG_PL_DWLEDGER"); //tablename_list.Add("YG_PL_KMYE"); //tablename_list.Add("YG_PL_WLLEDGER"); //tablename_list.Add("yg_pl_voucher"); } private void getDataTable(string _tablename) { recount = Convert.ToInt32(YG.DBUtility.DbHelperOra.ExecuteSql_obj("select count(*) from " + _tablename)); if (TYPE == 1) { recount = 10; } dt = YG.DBUtility.DbHelperOra.PageBind(_tablename, "", getColumns(_tablename), " id DESC", 20, onpage, out recount); dt.Columns.Remove("rk"); } #region Excel文件操作 /// <summary> /// 创建excel /// </summary> private void createExcel() { excelApp = new Application(); excelApp.Workbooks.Add(miss); DataTest(); int _workSheetCount = getWorkSheetCount();//获取需要的工作表数量 this.initExcel();//初始化EXCEL try { do { TYPE = LOOP1; workSheet = (Worksheet)excelApp.Worksheets[LOOP2]; workSheet.Activate(); tablename = tablename_list[LOOP1 - 1];//测试 getDataTable(tablename_list[LOOP1 - 1]); addDataExcelSheet(); LOOP1++; if (LOOP1 != tablename_list.Count + 1) { excelApp.Worksheets.Add(miss, (Worksheet)excelApp.Worksheets[LOOP2], miss, miss); LOOP2++; } workSheet = null; } while (LOOP1 != tablename_list.Count + 1); } catch (Exception ex) { } finally { getFilePath(); saveExcel(); resExcelStream(); } } /// <summary> /// Excel初始化,删除初始工作表,只留一个 /// </summary> /// <param name="_excelApp"></param> private void initExcel() { for (int i = excelApp.Worksheets.Count; i > 1; i--) { workSheet = (Worksheet)excelApp.Worksheets[i]; workSheet.Delete(); } } /// <summary> /// 保存excel文件 /// </summary> private void saveExcel() { workSheet = (Worksheet)excelApp.Worksheets[1]; workSheet.Activate(); Workbook _workBook = excelApp.Workbooks[1]; _workBook.RefreshAll(); string _truepath = HttpContext.Current.Server.MapPath(FilePath); System.IO.FileInfo _fi = new System.IO.FileInfo(_truepath); if (!Directory.Exists(_fi.DirectoryName))//判断目录是否存在 { Directory.CreateDirectory(_fi.DirectoryName); } if (System.IO.File.Exists(_truepath))//判断文件是否存在 { System.IO.File.Delete(_truepath); } _workBook.SaveAs(_truepath, miss, miss, miss, miss, miss, XlSaveAsAccessMode.xlNoChange, miss, miss, miss, miss, miss); _workBook.Close(false, miss, miss); _workBook = null; Kill(); GC.Collect(); } /// <summary> /// 杀死EXCEL进程 /// </summary> /// <param name="_eApp"></param> private void Kill() { IntPtr t = new IntPtr(excelApp.Hwnd); int k = 0; GetWindowThreadProcessId(t, out k); //得到本进程唯一标志k System.Diagnostics.Process p = System.Diagnostics.Process.GetProcessById(k); //得到对进程k的引用 p.Kill(); //关闭进程k } [System.Runtime.InteropServices.DllImport("User32.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto)] public static extern int GetWindowThreadProcessId(IntPtr hwnd, out int ID); /// <summary> /// 输出生成的EXCEL地址 /// </summary> /// <param name="_path">相对路径</param> private void resExcelStream() { System.IO.FileInfo _fi = new System.IO.FileInfo(HttpContext.Current.Server.MapPath(FilePath));//FilePath为文件在服务器上的地址 response.Clear(); response.Buffer = true; response.Charset = "GBK"; //设置了类型为中文防止乱码的出现 //response.AppendHeader("Content-Disposition", String.Format("attachment;filename={0}", _path)); //定义输出文件和文件名 //response.AppendHeader("Content-Length", _fi.Length.ToString()); response.ContentEncoding = System.Text.Encoding.Default; response.ContentType = "text/HTML";//设置输出文件类型。因为是ajax接收不了文件流,只能返回文件地址了。 response.Write(reques t.UrlReferrer.OriginalString.Substring(0, request.UrlReferrer.OriginalString.LastIndexOf("/") + 1) + FilePath); response.Flush(); response.End(); } #endregion #region 工作表样式设置及初始化 /// <summary> /// Sheet样式初始化 /// </summary> /// <param name="_type">标志哪一类工作表,如果为1,则是第一张工作表</param> /// <param name="_workSheet"></param> private void initExcelSheet(int _rowCount, int _colCount) { switch (TYPE) { case 1: this.setStyleSheet_1(_rowCount, _colCount); break; default: this.setStyleSheet_default(_rowCount, _colCount); break; } } /// <summary> /// 设置公共样式 /// </summary> /// <param name="_workSheet"></param> /// <param name="_rowCount"></param> /// <param name="_colCount"></param> private void setStyleSheet(int _rowCount, int _colCount) { workSheet.get_Range(workSheet.Cells[1, 1], workSheet.Cells[_rowCount, _colCount]).Columns.AutoFit();//自动适应宽度 workSheet.get_Range(workSheet.Cells[1, 1], workSheet.Cells[_rowCount, _colCount]).Borders.LineStyle = 1; workSheet.get_Range(workSheet.Cells[1, 1], workSheet.Cells[_rowCount, _colCount]).HorizontalAlignment = Microsoft.Office.Interop.Excel.Constants.xlLeft; //_workSheet.get_Range(_workSheet.Cells[1, 1], _workSheet.Cells[_rowCount, 1]).Borders[XlBordersIndex.xlEdgeLeft].Weight = XlBorderWeight.xlThick;//设置左边线加粗 //_workSheet.get_Range(_workSheet.Cells[1, 1], _workSheet.Cells[1, _colCount]).Borders[XlBordersIndex.xlEdgeTop].Weight = XlBorderWeight.xlThick;//设置上边线加粗 //_workSheet.get_Range(_workSheet.Cells[1, _colCount], _workSheet.Cells[_rowCount, _colCount]).Borders[XlBordersIndex.xlEdgeRight].Weight = XlBorderWeight.xlThick;//设置右边线加粗 //_workSheet.get_Range(_workSheet.Cells[_rowCount, 1], _workSheet.Cells[_rowCount, _colCount]).Borders[XlBordersIndex.xlEdgeBottom].Weight = XlBorderWeight.xlThick;//设置下边线加粗 //double top = 0;//上边距 //double left = 0;//左边距 //double right = 0;//右边距 //double footer = 0; //下边距 //workSheet.PageSetup.TopMargin = excelApp.InchesToPoints(top / 2.54);////workSheet.PageSetup.BottomMargin = excelApp.InchesToPoints(footer / 2.54);////workSheet.PageSetup.LeftMargin = excelApp.InchesToPoints(left / 2.54);////workSheet.PageSetup.RightMargin = excelApp.InchesToPoints(right / 2.54);// workSheet.DisplayAutomaticPageBreaks = true;//显示分页线 workSheet.PageSetup.CenterFooter = "第 &P 页,共 &N 页"; workSheet.PageSetup.CenterHorizontally = true;//水平居中 //_workSheet.PageSetup.PrintTitleRows = "$1:$1";//顶端标题行 workSheet.PageSetup.PaperSize = Microsoft.Office.Interop.Excel.XlPaperSize.xlPaperA4;//A4纸张大小 workSheet.PageSetup.Orientation = Microsoft.Office.Interop.Excel.XlPageOrientation.xlPortrait;//纸张方向.纵向 } private void setStyleSheet_1(int _rowCount, int _colCount)//首页待打印属于特例,需要单独设置 { setStyleSheet(12 + _rowCount, 8);//页面设置 workSheet.get_Range(workSheet.Cells[1, 1], workSheet.Cells[12 + _rowCount, 8]).Borders.LineStyle = 0;//去掉所有边框 //实体数据上部表格设置 workSheet.get_Range(workSheet.Cells[1, 1], workSheet.Cells[1, 8]).MergeCells = true; workSheet.get_Range(workSheet.Cells[2, 1], workSheet.Cells[2, 8]).MergeCells = true; workSheet.get_Range(workSheet.Cells[3, 2], workSheet.Cells[3, 4]).MergeCells = true; workSheet.get_Range(workSheet.Cells[4, 2], workSheet.Cells[4, 4]).MergeCells = true; workSheet.get_Range(workSheet.Cells[3, 6], workSheet.Cells[3, 8]).MergeCells = true; workSheet.get_Range(workSheet.Cells[4, 6], workSheet.Cells[4, 8]).MergeCells = true; workSheet.get_Range(workSheet.Cells[5, 1], workSheet.Cells[5, 8]).MergeCells = true; workSheet.get_Range(workSheet.Cells[6, 1], workSheet.Cells[6, 8]).MergeCells = true; workSheet.get_Range(workSheet.Cells[7, 1], workSheet.Cells[7, 8]).MergeCells = true; //实体数据部分 for (int i = 7; i <= _rowCount + 7 + 1; i++) { workSheet.get_Range(workSheet.Cells[i, 1], workSheet.Cells[i, 4]).MergeCells = true; //workSheet.get_Range(workSheet.Cells[i, 3], workSheet.Cells[i, 4]).MergeCells = true; workSheet.get_Range(workSheet.Cells[i, 5], workSheet.Cells[i, 8]).MergeCells = true; } workSheet.get_Range(workSheet.Cells[8, 1], workSheet.Cells[7 + _rowCount, 8]).Borders.LineStyle = 1; //接收人上下表格设置 workSheet.get_Range(workSheet.Cells[8 + _rowCount, 1], workSheet.Cells[8 + _rowCount, 8]).MergeCells = true; workSheet.get_Range(workSheet.Cells[9 + _rowCount, 1], workSheet.Cells[9 + _rowCount, 8]).MergeCells = false; workSheet.get_Range(workSheet.Cells[9 + _rowCount, 2], workSheet.Cells[9 + _rowCount, 8]).MergeCells = true; //workSheet.get_Range(workSheet.Cells[9 + _rowCount, 2], workSheet.Cells[9 + _rowCount, 8]).Borders.LineStyle = 1; workSheet.get_Range(workSheet.Cells[10 + _rowCount, 1], workSheet.Cells[10 + _rowCount, 8]).MergeCells = true; //审核接收意见部分设置 workSheet.get_Range(workSheet.Cells[11 + _rowCount, 1], workSheet.Cells[11 + _rowCount, 8]).MergeCells = true; workSheet.get_Range(workSheet.Cells[12 + _rowCount, 1], workSheet.Cells[12 + _rowCount, 1]).MergeCells = true; workSheet.get_Range(workSheet.Cells[12 + _rowCount, 2], workSheet.Cells[12 + _rowCount, 4]).MergeCells = true; workSheet.get_Range(workSheet.Cells[12 + _rowCount, 5], workSheet.Cells[12 + _rowCount, 5]).MergeCells = true; workSheet.get_Range(workSheet.Cells[12 + _rowCount, 6], workSheet.Cells[12 + _rowCount, 8]).MergeCells = true; workSheet.get_Range(workSheet.Cells[1, 1], workSheet.Cells[1, 8]).Font.Size = 28; workSheet.get_Range(workSheet.Cells[5, 1], workSheet.Cells[5, 1]).Font.Size = 16; workSheet.get_Range(workSheet.Cells[1, 1], workSheet.Cells[1, 8]).Font.Bold = true; workSheet.get_Range(workSheet.Cells[6, 1], workSheet.Cells[6, 1]).Font.Bold = true; workSheet.get_Range(workSheet.Cells[1, 1], workSheet.Cells[1, 8]).HorizontalAlignment = Microsoft.Office.Interop.Excel.Constants.xlCenter; workSheet.get_Range(workSheet.Cells[8, 1], workSheet.Cells[8, 8]).Interior.ColorIndex = 15; workSheet.Cells[1, 1] = "移交单"; workSheet.Cells[2, 1] = "移交信息:"; workSheet.Cells[3, 1] = "移交单号:"; workSheet.Cells[3, 2] = "_________________________"; workSheet.Cells[3, 5] = "移交日期:"; workSheet.Cells[3, 6] = "_________________________"; workSheet.Cells[4, 1] = "移 交 人:"; workSheet.Cells[4, 2] = "_________________________"; workSheet.Cells[4, 5] = "所属部门:"; workSheet.Cells[4, 6] = "_________________________"; workSheet.Cells[6, 1] = @"目录结构\..\.."; workSheet.Cells[7, 1] = @"移交内容:"; workSheet.Cells[8, 1] = @"分类"; workSheet.Cells[8, 5] = @"数量"; workSheet.Cells[9 + _rowCount, 1] = @"接收人:"; workSheet.Cells[9 + _rowCount, 2] = "_________________________"; workSheet.Cells[11 + _rowCount, 1] = @"意见:"; workSheet.Cells[12 + _rowCount, 1] = @"提交意见"; workSheet.Cells[12 + _rowCount, 2] = "_________________________"; workSheet.Cells[12 + _rowCount, 5] = @"接收意见"; workSheet.Cells[12 + _rowCount, 6] = "_________________________"; } private void setStyleSheet_default(int _rowCount, int _colCount) { this.setStyleSheet(_rowCount, _colCount); workSheet.get_Range(workSheet.Cells[1, 1], workSheet.Cells[1, _colCount]).Font.Bold = true; workSheet.PageSetup.PrintTitleRows = "$1:$1";//设置打印表头 } #endregion #region 向工作表添加数据 /// <summary> /// 向工作表添加数据 /// </summary> /// <param name="_type"></param> /// <param name="_workSheet"></param> /// <param name="_dt"></param> private void addDataExcelSheet() { switch (TYPE) { case 1: this.addDataSheet_1(); break; default: this.addDataSheet_default(); break; } } private void addDataSheet_1() { workSheet.Name = "移交单"; initExcelSheet(dt.Rows.Count, dt.Columns.Count); } private void addDataSheet_default() { int _pagecount = getPageCount(pagesize, dt.Rows.Count); System.Data.DataTable _dt = null; for (int _i = 1; _i <= _pagecount; _i++)//分页打印数据 { workSheet.Name = tablename + "-" + _i;//重命名工作表 _dt = new System.Data.DataTable(); _dt = GetPagedTable(pagesize, _i);//获取分页(工作表)数据 initExcelSheet(_dt.Rows.Count + 1, _dt.Columns.Count);//当前工作表初始化 for (int i = 1; i <= _dt.Rows.Count + 1; i++)//要打印的工作表行数(由于加上表头,所以加1) { for (int j = 1; j <= _dt.Columns.Count; j++)//待打印数据的列数 { if (i == 1)//设置表头数据 { workSheet.Cells[i, j] = diction.ContainsKey(_dt.Columns[j - 1].ColumnName.ToLower()) ? diction[_dt.Columns[j - 1].ColumnName.ToLower()] : ""; } else//其余行 { workSheet.Cells[i, j] = _dt.Rows[i - 2][j - 1]; } } } if (_i != _pagecount)//判断待打印数据的当前页是不是最后一页,不是则在当前工作表后添加一张工作表 { excelApp.Worksheets.Add(miss, (Worksheet)excelApp.Worksheets[LOOP2], miss, miss); LOOP2++; workSheet = (Worksheet)excelApp.Worksheets[LOOP2]; workSheet.Activate(); } } } #endregion #region GetPagedTable DataTable分页 /// <summary> /// DataTable分页 /// </summary> /// <param name="dt">DataTable</param> /// <param name="PageIndex">页索引,注意:从1开始</param> /// <param name="PageSize">每页大小</param> /// <returns></returns> public System.Data.DataTable GetPagedTable(int _PageSize, int _PageIndex) { if (_PageIndex == 0) return dt; System.Data.DataTable newdt = dt.Copy(); newdt.Clear(); int rowbegin = (_PageIndex - 1) * _PageSize; int rowend = _PageIndex * _PageSize; if (rowbegin >= dt.Rows.Count) return newdt; if (rowend > dt.Rows.Count) rowend = dt.Rows.Count; for (int i = rowbegin; i <= rowend - 1; i++) { System.Data.DataRow newdr = newdt.NewRow(); System.Data.DataRow dr = dt.Rows[i]; foreach (System.Data.DataColumn column in dt.Columns) { newdr[column.ColumnName] = dr[column.ColumnName]; } newdt.Rows.Add(newdr); } return newdt; } #endregion /// <summary> ///计算总页数,循环添加数据 /// </summary> /// <param name="_pagesize"></param> /// <param name="_recount"></param> /// <returns></returns> private int getPageCount(int _pagesize, int _recount) { int _pagecount = 0; _pagecount = (_recount + _pagesize - 1) / _pagesize; if (_pagecount == 0) { _pagecount = 1; } return _pagecount; } /// <summary> /// 计算工作表数量(此时返回数量不包括将来分页后的数量) /// </summary> private int getWorkSheetCount() { int _workSheetCount = tablename_list.Count; if (_workSheetCount > excelApp.Sheets.Count) { return _workSheetCount; } return _workSheetCount; } /// <summary> /// 添加表头,作废,暂时只用于插入一行改变dt的行数 /// </summary> private void insertTableName() { System.Data.DataRow _dr = dt.NewRow(); //for (int i = 0; i < _dt.Columns.Count; i++) //{ // _dr[i] = diction.ContainsKey(_dt.Columns[i].ColumnName.ToLower()) ? diction[_dt.Columns[i].ColumnName.ToLower()] : _dt.Columns[i].ColumnName; //} dt.Rows.InsertAt(_dr, 0); } /// <summary> /// 检索表信息,取得汉化表头信息 /// </summary> /// <returns>返回select字段</returns> private string getColumns(string _tabName) { string allColumns = dbcmm.f_str0(_tabName).ToLower(); arrayColName = dbcmm.getFieldName0(allColumns, 0); arrayShowName = dbcmm.getFieldName0(allColumns, 1); arrayType = dbcmm.getFieldName0(allColumns, 2); string _select = dbcmm.getColsName0(arrayColName, arrayType); ArryToDict(arrayColName, arrayShowName); return _select; } /// <summary> /// 获取ColName和ShowName键值对 /// </summary> /// <param name="_arrayColName">列名</param> /// <param name="_arrayShowName">汉化名</param> private void ArryToDict(ArrayList _arrayColName, ArrayList _arrayShowName) { for (int _i = 0; _i < _arrayColName.Count; _i++) { if (!diction.ContainsKey(_arrayColName[_i].ToString())) { diction.Add(_arrayColName[_i].ToString(), _arrayShowName[_i].ToString()); } } } private void getFilePath() { string _fileName = string.Empty; _fileName = DateTime.Now.ToString("yyyyMMddhhmmss"); FilePath = DirPath + "/" + _fileName + ".xls"; } public bool IsReusable { get { return false; } } } }

[转载]新浪微博客户端开发笔记(3) 如何添加异步测试并模拟异步方法

mikel阅读(923)

[转载]新浪微博客户端围脖七开发笔记(3) 如何添加异步测试并模拟异步方法 – 上尉 – 博客园.

这几天在研究如何进行异步测试, silverlight unit test framework提供了相应支持,异步在这个framework中的含义就是把一些额外的任务排队并稍后执行。 比如有一个request()方法是异步的,一般情况下呼叫这个方法之后无法直接测试返回的结果;但在测试方法TestRequest()中,呼叫 request()后可以加入一些额外任务到一个队列中,在退出TestRequest()后执行这些任务。说起来有些拗口看下图会明白些:

左边是正常的测试方法,右边是异步测试

一般运用下面四个方法添加任务到队列中:

EnqueueTestComplete() – 添加一个TestComplete()到队列中,这个方法告知framework测试结束了。
EnqueueCallback() – 添加一个任务到队列
EnqueueConditional() – 添加一个条件判断到队列,如果为true才继续执行
EnqueueDelay() – 添加一些等待时间到队列

下图演示了如何实际使用:

下面回到项目中,看看具体如何做:

1.添加一个MockSinaService:

代码

public class MockSinaService : ISinaService { public List<Status> StatusList{get;set;} public void GetStatuses(System.Action<IEnumerable<Status>> onGetStatusesCompleted = null, System.Action<System.Exception> onError = null, System.Action onFinally = null) { DispatcherTimer timer = new DispatcherTimer(); timer.Interval = TimeSpan.FromSeconds(2); timer.Tick += delegate(object sender, EventArgs e) { Status testStatus = new Status { Text = "this is test status", CreatedAt = DateTime.Now }; onGetStatusesCompleted(new List<Status>() { testStatus }); timer.Stop(); }; timer.Start(); } }

这个类是为了模拟SinaService,减少MainPageViewModel对它的依赖。因为实际中SinaService会调用 webclient异步取得数据,但MainPageViewModel其实并不关心数据是怎么来的,添加这个mock会方便单元测试。注意在实现中使用 定时器来模拟webclient的异步行为。

2. 添加一个公共属性SinaService到MainPageViewModel中,这样测试项目可以把它指向mock类

代码

public ISinaService SinaService { get { if (_sinaService == null) { _sinaService = new SinaService(); } return _sinaService; } set { if (value != _sinaService) { _sinaService = value; } } }

3. 添加RefreshHomeList()方法到MainPageViewModel中

代码

public void RefreshHomeList() { Trace.DetailMsg("RefreshHomeList"); IsHomeRefreshing = true; SinaService.GetStatuses( delegate(IEnumerable<Status> statuses) { IsHomeRefreshing = false; foreach (Status status in statuses) { HomeList.Add(status); } }, delegate(Exception exception) { IsHomeRefreshing = false; }); }

4. 修改TestInitialize如下,植入mock类

代码

[TestInitialize] public void Initialize() { _mainViewModel = new MainPageViewModel(); _mockSinaService = new MockSinaService(); _mainViewModel.SinaService = _mockSinaService; }

5. 添加测试方法:

代码

[TestMethod] [Asynchronous] public void Refresh_HomeList_Success() { bool isHomeListRefreshed = false; _mainViewModel.HomeList.CollectionChanged += (s, e) => { isHomeListRefreshed = true; Assert.AreEqual(NotifyCollectionChangedAction.Add, e.Action); Assert.AreEqual(1, e.NewItems.Count, "only should be +1 item"); }; _mainViewModel.RefreshHomeList(); EnqueueConditional(() => isHomeListRefreshed); EnqueueCallback(() => Assert.AreEqual(_mainViewModel.HomeList.Count, 1, "Expected non-empty products list.")); EnqueueTestComplete();

注意这里的Asynchronous关键字,而且要让MainPageViewModelTests这个类继承SilverlightTest。

最后测试结果如下:

References:

1. silverlight2-unit-testing by jeff wilcox

2. Asynchronous test support – Silverlight unit test framework and the UI thread by jeff wilcox

3. Silverlight Unit Testing, RhinoMocks, Unity and Resharper by Justin Angel

[转载]GCC强大背后

mikel阅读(915)

[转载]GCC强大背后 – wwang’s blog – 博客园.

前记: 经常浏览博客园的同学应该会觉得本文有标题党之嫌,这个标题的句式来自于MiloYip大牛的大作《C++强大背后》,在此,向Milo兄致意。

GCC,全称GNU Compiler Collection,是一套GNU开发的编译器环境,它的创始人便是大名鼎鼎的Richard.M.Stallman。 最初GCC刚开始开发时,它还叫做GNU C Compiler,随着开发的深入,GCC很快得到了扩展,不仅可以支持C语言,还可以处理C++,Pascal,Object-C,Java以及Ada 等其他语言。目前,GCC不仅是GNU的官方编译器,也成为编译和创建其他操作系统的编译器,包括BSD家族以及MAC OS X等。另外,GCC也是跨平台交叉编译的首选,它不仅支持Intel的x86系列,同时也支持MIPS,ARM,PowerPC,SPARC等等处理器。 可以这么说,即使GCC不是世界上效率最高的编译器,它也一定是世界上最全面的编译器。

1. GCC简介

我们先回到一个常识性的问题,什么是编译器呢?简单地说,编译器可以看作是一个语言翻译器。就像把中文翻译成英语一样,编译器可以把高级语言翻译成计算机能够执行的机器语言。这样看来,GCC可以算得上是一个精通多国语言的高级翻译官了。
最简单的GCC使用指令如下所示:
gcc  hello.c  -o  hello
GCC接受hello.c作为输入,最后产生目标可执行代码hello。这个简单的流程实际上经历了很多步骤,如下图所示:
虽然我们只用了一条命令就完成了编译,但实际上gcc命令依次呼叫了cpp,gcc自己,gas以及ld来进行完整的编译流程,最后生成最终的可执行文件hello。
下面我们看一下分解动作:
cpp  hello.c  >  hello.i
gcc  -S  hello.i
as  hello.s  -o  hello.o
ld  -dynamic-linker  /lib/ld-linux.so.2  /usr/lib/crt1.o  /usr/lib/crti.o  /usr/lib/gcc/i686-linux-gnu/4.4.5/crtbegin.o -L/usr/lib/gcc/i686-linux-gnu/4.4.5 hello.o  -lgcc  -lgcc_eh  -lc  /usr/lib/gcc/i686-linux-gnu/4.4.5/crtend.o  /usr/lib/crtn.o  -o  hello
看完这些步骤有没有晕头转向的感觉呢?对于普通的用户来说,还是让GCC帮我们做这些事情比较好。
对于如何学习使用GCC,可以参考GCC官方的手册,如果大家觉得官方的手册太罗嗦,我这里推荐一本GCC的入门书籍《An Introduction to GCC》,这本书详尽的介绍了GCC的使用方法,内容浅显易懂,很适合初学者。

2. GCC强大的背后

学过编译原理这门课程的同学对下面这副图应该很熟悉,这是经典的编译流程。

GCC作为经典的编译器,自然也是遵循这个教科书流程(实际GCC的处理更复杂点,但本质上是一样的)。我们先简化一下上面这幅图,以中间代码为分界,前面的词法分析、语法分析、语义分析我们把它称之为前端处理,后面的优化和目标代码生成我们称之为后端处理。

试想一下,是否可以为不同的高级语言单独写一个前端,然后为不同的处理器架构单独写一个后端呢?

GCC基本上也是这么实现的,不过不要误会,并没有一个统一的gcc执行程序能够处理如此多的前端和后端,每个语言的编译器都是一个独立的程序(如 C语言的编译器是gcc,C++的编译器是g++),而不同的后端也要对应不同的可执行程序。你可以下载单独的一份GCC源代码,通过不同的 configure来编译自己需要的编译器。

而且,编译器的实现也比上图要复杂的多,前端的主要功能是产生一个可供后端处理的语法树,而语法树结构实际上很难与处理器架构脱钩,这些都是编译器应用中需要解决的问题。

GCC强大的真正原因是什么?是因为它支持了众多的前端和后端吗?这些都不过是一个表象而已。GCC是一款真正自由的编译器,我们可以随时把代码拿 过来修改以实现自己需要的功能。如果你的硬件平台增加了一些指令,而普通的编译器并不能产生这些指令怎么办?在GCC后端添加这些指令吧。如果你觉得C语 言用的不太顺手,想给它添加一些功能怎么办?修改GCC的前端吧。因为有了GCC,我们才拥有这些自由,以及迅速实现自己想法的能力,而这些才是GCC强 大背后的基础。

2010年1月份的时候,Google的Go语言前端被允许进入GCC编译器家族,GCC更加强大了。

3. GCC的多样性

GCC因为其灵活性被应用到了很多领域和系统,从PC上的开发到嵌入式开发,都可以见到GCC的影子。

3.1   PC开发

我们先看看PC。MAC自从投入Intel的怀抱,是否也可以看作是一种PC呢?

Linux

Linux系统应该是GCC的主战场,但也是最没必要去说的一个系统,除了GCC,难道我们还有更好的选择吗?Linux内核、Apache服务器、MySQL数据库,等等一系列伟大的作品都是通过GCC来构建的,GCC可以说是GNU/Linux系统的基石。

MAC OS X

MAC OS X也是GCC的重度用户,其应用程序开发环境Cocoa就是使用的GCC,所以在MAC OS X下开发也是离不开GCC的。

Windows

现在在Windows下开发C/C++程序一般都是用微软的编译器,当年的Borland已经成为传说。但是如果你不想付钱的话,也可以考虑Windows下的GCC。
在Windows下体验GCC最常用的有两种方式:一是在Cygwin下使用GCC,另外一种是使用MinGW。
Cygwin是一个自由软件的集合,最初由Cygnus Solutions开发,目的是在Windows系统上运行类Unix的软件。通过Cygwin编译的程序可以在Windows上运行,但必须使用cygwin.dll。
MinGW(Minimalist GNU for Windows),是将GNU开发工具移植到Windows平台的产物,包括一系列头文件、库和可执行文件,用MinGW开发的程序不需要额外的第三方 DLL就可以直接在Windows上运行。Nokia的图形开发包QT在Windows下就是调用MinGW来编译的。

DOS

在DOS系统下也是可以用GCC的,国内的DOS开发者可能更熟悉Turbo C或者Open Watcom,GCC的DOS版本DJGPP其 实也是32位DOS程序开发的主流环境之一。最初DJGPP的发起人DJ Delorie曾经询问过Richard Stallman,FSF是否考虑过把GCC移植到MS-DOS下,当时Richard的回答是GCC太庞大,而MS-DOS只不过是个16位的操作系 统,所以官方并没有考虑这件事。DJ Delorie并没有因此而退缩,最终给我们带来了这个优秀的开发平台。DJGPP刚开始开发时叫做djgcc,在引入了C++之后改为现在这个名字 (DJ’s GNU Programming Platform)。

3.2   嵌入式开发

对于嵌入式开发领域来说,因为开发板的能力限制,是无法运行编译环境的,这样就需要在PC上通过交叉编译来生成目标可执行程序,GCC的高度灵活性在嵌入式开发上发挥了极大的作用。

Android

看看当今最火的移动平台Android,就是完全用GCC来构建的,请注意,这里指的不是Android应用程序,Android下的应用程序是运行在Dalvik虚拟机上的Java程序。

iOS

Apple的iOS应用程序开发也是用Cocoa来进行,这怎么会离开GCC呢?

MeeGo/Symbian

在Apple和Google的冲击下,Nokia似乎已经日薄西山了,但对于中国国情来说,真的是这样吗?iPhone高高在上,拥有者只会是 少数,Google退出中国之后,Android Market很难在国内有所作为,再加上Nokia手机一贯皮实的口碑,现在的Symbian以及将来的MeeGo的保有量应该不会低。Nokia已经把 Symbian和MeeGo的开发环境统一到QT上了,这里依然是GCC的一亩三分田。
GCC是强大的,但它并不是一个人在战斗,在它的背后站着GNU工具链,包括make,GCC,Binutils,GDB等一系列工具,这些工具之间是相辅相成的,只有把它们组合起来使用才能发挥其最大的威力。

[转载]Qt Model/View 概论(-)、

mikel阅读(1073)

[转载]Qt Model/View 概论(-) – 博水 – 博客园.

Model-View-Controller(MVC), 是从Smalltalk发展而来的一种设计模式,常被用于构建用户界面。它强制性的使应用程序的输入、处理和输出分开。

在Qt中引入了一个MVC的变体—model/view结构。这个结构依然是把数据存储与数据表示进行了分离,它与MVC都基于同样的思想,但 它更简单一些。这种分离使得在几个不同的view上显示同一个数据成为可能,也可以重新实现新的view,而不必改变底层的数据结构。为了更灵活的对用户 输入进行处理,引入了delegate这个概念。它的好处是,数据项的渲染与编程可以进行定制。其具体工作机制如下图:

从上图可看出,Model直接读取数据,View可以直接显示数据,也可以显示经过Delegate处理后的数据。同时用户可以直接通过 Delegate直接编辑数据通过model存入数据文件中。models,views,delegates之间通过信号,槽机制来进行通讯。

工作机制:

传统中View只用来负责数据显示,因此在view创建时并不需要model只有当其显示信息的时候才会用到model。model通过QAbstractItemMode提供统一的接口,view会调用model的index来获得一个indexmodel,然后再通过indexmodel来获得想要得到的data.

model负责从数据集里面选取合适的数据提供给View因此model可以充当数据的选择和过滤器,另一方面可以接收Delegate发回的信息更新数据集中的数据信息。

Deletegate是由View层通过各种signal或者event事件引发,实现数据的更改并通过Model写入数据集中。

[转载]流行数据库SQL差异分析之“限制结果集行数”

mikel阅读(1053)

[转载][笔记]流行数据库SQL差异分析之“限制结果集行数” – Astar之学习.NET路程 – 博客园.

前言:近期所参与的项目共涉及了三种数据库,SQLServer、Oracle、MySQL。所以就找来这些资料,并记录为笔记供以后参考。

限制结果集行数

在进行数据检索的时候需要只检索结果集中的部分行,比如说“检索成绩排前三名的学生”、“检索工资水平排在第3位到第7位的员工信息”,这种功能被 称为“限制结果集行数”。虽然主流的数据库系统中都提供了限制结果集行数的方法,但是无论是语法还是使用方式都存在着很大的差异。即使是同一个数据库系统 的不同版本(比如MSSQLServer2000和MSSQLServer2005)也存在着一定的差异。

MYSQL

MYSQL中提供了LIMIT关键字用来限制返回的结果集,LIMIT放在SELECT语句的最后位置,语法为“LIMIT首行行号,要返回的结果集的最大数目”,比如下面的SQL语句将返回按照工资降序排列的从第二行开始(行号从0开始)的最多五条记录:

SELECT * FROM T_Employee ORDER BYFSalary DESC LIMIT 2,5      //返回第二行到第七行

SELECT * FROM T_Employee ORDER BYFSalary DESC LIMIT 0,5      //返回前五条

SQLServer2000

SQLServer2000中提供了TOP关键字用来返回结果集中的前N条记录,其语法为”SELECT TOP限制结果集数目字段列表SELEC下语句其余部分”,比如下面的SQL语句用来检索工资水平排在前五位(按照工资从高到低)的员工信息:

select top 5 * from T_Employee orderby FSalary Desc

SQLServer2000没有直接提供返回提供“检索从第5行开始的10条数据”、“检索第五行至第十二行的数据”等这样的取区间范围的功能,不 过可以采用其他方法来变通实现,最常使用的方法就是用子查询,比如要实现检索按照工资从高到低排序检索从第六名开始一共三个人的信息,那么就可以首先将前 五名的主键取出来,在检索的时候检索排除了这五名员工的前三个人,SQL如下:

SELECT top 3 * FROM T_Employee

WHERE FNumber NOT IN

(SELECT TOP 5 FNumber FROM T_EmployeeORDER BY FSalary DESC)

ORDER BY FSalary DESC

SQLServer2005

SQLServer2005兼容几乎所有的SQLServer2000的语法,所以可以使用上面提到的方式来在SQLServer2005中实现限 制结果集行数,不过SQLServer2005提供了新的特性来帮助更好的限制结果集行数的功能。这个新特性就是窗口函数 ROW_NUMBER(),ROW_NUMBER()函数可以计算每一行数据在结果集中的行号(从1开始计数),其使用语法如下:

ROW_NUMBEROVER(排序规则)

比如执行下而的SOL语句:

SELECTROW_NUMBER() OVER(ORDER BY FSalary),FNumber,FName,FSalary,FAge FROM T_Employee //返回排序过的行号

ROW_NUMBER()不能用在WHERE语句中,可以用子查询来返回第3行到第5行的数据:

SELECT * FROM

(

SELECT ROW_NUMBER() OVER(ORDER BY FSalary DESC)AS rownum,

FNumber,FName,FSalary,FAge FROMT_Employee

) AS a

WHERE a.rownum>=3 AND a.rownum<=5

Oracle

Oracle中支持窗口函数ROW_NUMBER(),其用法和SQLServer2005中相同,比如执行下面的SQL语句:

SELECT * FROM

(

SELECT ROW_NUMBER() OVER(ORDER BYFSalary DESC) row_num,

FNumber,FName,FSalary,FAge FROMT_Employee

) a

WHERE a.row_num>=3 AND a.row_num<=5

注意:rownum在Oracle中为保留字,所以这里将SQLServer2005中用到的rownum替换为row_num,Oracle中定 义表别名的时候不能使用AS关键字,所以这里也去掉了AS,Oracle支持标准的函数ROW_NUMBER(),不过Oracle中提供了更方便的特性 用来计算行号,也就在Oracle中可以无需自行计算行号,Oracle为每个结果集都增加了一个默认的表示行号的列,这个列的名称为rownum,比如 执行下面的SQL语句:

SELECT rownum,FNumber,FNas.e,FSalary,FAgeFROM T _Employee

执行完毕我们就能在输出结果中看到下面的执行结果:

使用rownum可以很轻松的取得结果集中前N条的数据行,比如执行下面的SQL语句可以得到按工资从高到底排序的前6名员工的信息:

SELECT * FROM _Employee WHERE rownum<=6 ORDER BY FSalary Desc

下面的SQL就可以非常容易的实现“按照工资从高到低的顺序取出第三个到第五个员工信息”的功能了:

SELECTrownum,FNumber,FName,FSalary,FAge FROM T_Employee

WHERE rownum BETWEEN 3 AND 5

ORDER BY FSalary DESC                     //执行结果为空,结论看下面。

回顾一下rownum的含义:rownum为结果集中每一行的行号(从1开始计数)。对于下面的SQL:

SELECT * FROM_Employee WHERE rownum<=6 ORDER BY FSalary Desc

当进行检索的时候,对于第一条数据,其rownum为1,因为符合“WHERErownum<=6″所以被放到了检索结果中,当检索到第二条数据的时候,其rownum为2,因为符合“WHERE

rownum<=6″所以被放到了检索结中。依次类推,直到第七行。所以这句SQL语句能够实现“按照工资从高到低的顺序取出第三个到第五个员工信息”的功能。

而对于这句SQL语句:

SELECTrownum,FNumber,FName,FSalary,FAge FROM T_Employee

WHERE rownum BETWEEN 3 AND 5

ORDER BY FSalary DESC

当进行检索的时候,对于第一条数据,其rownum为1,因为不符合“WHERErownum BETWEEN 3 AND 5″,所以没有被放到了位索结果中,当检索到第二条数据的时候,因为第一条数据没有放到结果集中,所以第二条数据的rownum仍然为1,而不是我们想像 的2,所以因为不符合‘WHERErownum<=6″,没有被放到了植索结果中,当检索到第三条数据的时候,因为第一、二条数据没有放到结果集 中,所以第三条数据的rownum仍然为1,而不是我们想像的3,所以因为不符合“WHERErownum<=6″,没有被放到了检索结果中。依此 类推,这样所有的数据行都没有被放到结果集中。

因此如果要使用rownum来实现“按照工资从高到低的顺序取出第三个到第五个员工信息”的功能,就必须借助于窗口函数ROW_NUMBER()。

DB2

DB2中支持窗口函数ROW_NUMBER(),其用法和SQLServer2005以及Oracle中相同,比如执行下面的SQL语句:

SELECT * FROM

(

SELECT ROW_NUMBER() OVER(ORDER BYFSalary DESC) row_num,

FNumber,FName,FSalary,FAge FROMT_Employee

) a

WHERE a.row_num>=3 ANDa.row_num<=5

除此之外,DB2还提供了FETCH关键字用来提取结果集的前N行,其语法为“FETCH FIRST 条数 ROWS ONLY”,比如我们执行下面的SQL语句可以得到按工资从高到底排序的前6名员工的信息:

SELECT * FROM T_Employee ORDER BYFSalary Desc FETCH FIRST 6 ROWSONLY

注意:FETCH子句要放到ORDERBY语句的后面。

DB2没有直接提供返回提供“检索从第5行开始的10条数据”、“检索第五行至第十二行的数据”等这样的取区间范围的功能,不过可以采用其他方法来 变通实现,最常使用的方法就是用子查询,比如要实现检索按照工资从高到低排序检索从第六名开始一共三个人的信息,那么就可以首先将前五名的主键取出来,在 检索的时候检索排除了这五名员工的前三个人。

SELECT * FROM T_Employee

WHERE FNumber NOT IN

(

SELECT FNumber FROM T_Employee

ORDER BY FSalary DESC

FETCH FIRST 5 ROWS ONLY

)

ORDER BY FSalary DESC

FETCH FIRST 3 ROWS ONLY

数据库分页

实现数据库分页的核心技术就是“限制结果集行数“,假设每一页显示的数据条数为PageSize,当前页数(从0开始技术)为 Currentlndex,那么我们只要查询从第PageSize*CurrentIndex开始的PageSize条数据得到的结果就是当前页中的数 据,当用户点击【上一页】按钮的时候,将Currentlndex设置为Currentlndex-1,然后重新检索。当用户点击【下一页】按钮的时候, 将Currentlndex设置为Currentlndex+1,然后重新检索。当用户点击【首页】按钮的时候,将Currentlndex设置为0,然 后重新检索。当用户点击【首页】按钮的时候,将Currentlndex设置为“总条数/PageSize”,然后重新检索。

[转载]HttpUtility.UrlEncode

mikel阅读(1038)

[转载]HttpUtility.UrlEncode 在 Encode 的时候, 将空格转换成加号(‘+’), 在 Decode 的时候将加号转为空格, 但是浏览器是不能理解加号为空格的, 所以如果文件名包含了空格, 在浏览器下载得到的文件, 空格就变成了加号 – 爱晚红枫技术部广东分部 – 博客园.

在用 ASP.NET 开发页面的时候, 我们常常通过 System.Web.HttpUtility.UrlEncode 和 UrlDecode 在页面间通过 URL 传递参数. 成对的使用 Encode 和 Decode 是没有问题的.

但是, 我们在编写文件下载的页面的时候, 常常用如下方法来指定下载的文件的名称:
Response.AddHeader(
Content-Disposition,attachment; filename=
+ HttpUtility.UrlEncode(fileName, Encoding.UTF8));
之所以转换成 UTF8 是为了支持中文文件名.

这时候问题就来了, 因为 HttpUtility.UrlEncode 在 Encode 的时候, 将空格转换成加号(
+), 在 Decode 的时候将加号转为空格, 但是浏览器是不能理解加号为空格的, 所以如果文件名包含了空格, 在浏览器下载得到的文件, 空格就变成了加号.

一个解决办法是, 在 HttpUtility 的 UrlEncode 之后, 将
+ 替换成 %20( 如果原来是 + 则被转换成 %2b ) , 如:
fileName
= HttpUtility.UrlEncode(fileName, Encoding.UTF8);
fileName
= fileName.Replace(+, %20);
不明白微软为什么要把空格转换成加号而不是
%20. 记得 JDK 的 UrlEncoder 是将空格转换成 %20的.
经检查, 在 .Net
2.0 也是这样.

HttpUtility.UrlEncode 在 Encode 的时候, 将空格转换成加号(+), 在 Decode 的时候将加号转为空格, 但是浏览器是不能理解加号为空格的, 所以如果文件名包含了空格, 在浏览器下载得到的文件, 空格就变成了加号

<html>
<head>
<meta http-equiv=’Content-Type’ content=’text/html; charset=gb2312′>
<title>URL解码(Decode)/编码(Encode)</title>
</head>
<body>
<center><font color=green size=+2>URL解码(Decode)/编码(Encode)</font><br>
需要解码的字符串:
<TEXTAREA ID=”String1″ ROWS=”10″ COLS=”30″></TEXTAREA> 解码后的字符串:<TEXTAREA ID=”String2″ ROWS=”10″ COLS=”30″></TEXTAREA><br>
需要编码的字符串:
<TEXTAREA ID=”String3″ ROWS=”10″ COLS=”30″></TEXTAREA> 编码后的字符串:<TEXTAREA ID=”String4″ ROWS=”10″ COLS=”30″></TEXTAREA><br>
<INPUT TYPE=”button” ID=”Decode” value=”解码(Decode)” onClick=”JavaScript:String2.value=decodeURI(String1.value);”>
<INPUT TYPE=”button” ID=”Encode” value=”编码(Encode)” onClick=”JavaScript:String4.value=encodeURI(String3.value);”>
</center>
</body>
</html>

url传递中文的解决方案总结

1.设置web.config文件。(我不喜欢设置成这样)
<system.web>
……
<globalization requestEncoding=”gb2312″ responseEncoding=”gb2312″ culture=”zh-CN” fileEncoding=”gb2312″ />
……
</system.web>

或者:
aspx文件中:
<meta http-equiv=”Content-Type” content=”text/html; charset=gb2312″>

2.传递中文之前,将要传递的中文参数进行编码,在接收时再进行解码。
>> 进行传递
string Name = “中文参数”;
Response.Redirect(“B.aspx?Name=”+Server.UrlEncode(Name));

>> 进行接收
string Name = Request.QueryString[“Name”];
Response.Write(Server.UrlDecode(Name));
或者:

NavigateURL='<%# “WebForm2.aspx?Singer=” + HttpUtility.UrlEncode(“中国人”, System.Text.Encoding.GetEncoding(“GB2312”)) %>’


3.如果是从 .HTML 文件向 .Aspx 文件进行传递中文参数的话(即不从后台用 Redirect()方法进行 Url 转换)。一样要将传递的中文参数进行编码,在接收时再进行解码。
>> 进行传递
<script language=”JavaScript”>
function GoUrl()
{
var Name = “中文参数”;
location.href = “B.aspx?Name=”+escape(Name);
}
</script>
<body onclick=”GoUrl()”>
>> 进行接收
string Name = Request.QueryString[“Name”];
Response.Write(Server.UrlDecode(Name));

一般来说。设置web.config文件就可以了。但是如果你用 JavaScript 调用 webservice 方法的话(往webservice里面传递中文参数)。设置 web.config 文件好象无效。

————————————————————
在html中实现编解码:

<script language=”javascript”>
function openUrl(src)
{
var strUrl=escape(src);
window.open(strUrl);
}

function change_url(src)
{
document.location.href=escape(src);
}

</script>

在新窗口保存
<a href=’javascript:openUrl(“css/20040603123628交易中心烟叶网上集中交易系统合同.doc”);’ >20040603123628交易中心网上集中交易系统合同</a>

当前位置保存,无闪烁。
<a href=”#” onclick=javascript:change_url(“css/20040603123628交易中心烟叶网上集中交易系统合同.doc”)>20040603123628交易中心网上集中交易系统合同</a>

注意:路径中的斜线是:“/”,而不是“\”,否则也不行啊。

----------------------------------------
前一阵遇到在做.net Web开发时,碰到一个很奇怪的问题,就是Url中,如果将中文字符作为参数值传递时,QueryString得到的值可能会出错。简单的说,比如下面这个Url:
UrlParmTest.aspx?parm1=中国&parm2=中国人
在Request.QueryString时,parm1和parm2得到都是”中国”,显然出现了问题,可是在某些情况下却是正常的。

如果请求不是直接通过URL,而使用Response.Redirect在服务器端操作,没有遇到过类似的问题。

当时我想中文是双字节编码,可能传递的时候就是有不确定性,还是用英文好。

可是为什么在Server端Redirect就是正常的,问题在哪里呢?


如果在.cs文件中设置中文参数,请在中文参数外使用Server.UrlEncode(“中文”)对中文进行Encode
如果在.aspx文件中设置,请使用<%=Server.UrlEncode(“中文”)%>进行Encode
在QueryString时,不用再进行Decode,可以获得正常的中文字符串

下面是给出的一些解释:
UrlEncode把一些多字节字符转换成url里允许的单字节字符,本来浏览器就会自动做的,但是目前确实存在一些问题,所以自己再Encode一下,在接受端会自动对Url进行Decode。

我想Response.Redirect可能可以确保作Encode的工作,所以没有问题。

JavaScript Base64编码和解码,实现URL参数传递

为什么需要对参数进行编码?相信有过开发的经验的广大程序员都知道,在Web中,若是直接在Url地址上传递参数值,若是中文,或者+等什么的就会出现乱码现象,若是数字或者英文的好象没有什么问题,简言之,传递过来的参数是需要进行编码的。
在这里,也许有人会说,为什么不直接用Server.UrlDecode和Server.UrlEncode这两个来进行编码和解码的操作呢?

的确,这两个服务器端对象很好使用,用起来也很方便,但是,若在客户端是HTML的Input,查询的时候页面是HTML或者其他的,反正不是.NET的,那这个对象还可以用吗?


我现在就遇到这样的问题,查询的东东放在页面,而且那个页面我根本不想让他是.aspx结尾的,哈,感觉HTML的挺不错,而且里面的控件也是用HTML对象的。

下面先来看两个函数,UTF16转UTF8和UTF8转Utf16的。
function utf16to8(str)
{
var
out, i, len, c;

out = “”;
len
= str.length;
for(i = 0; i < len; i++) {
c
= str.charCodeAt(i);
if ((c >= 0x0001) && (c <= 0x007F)) {
out += str.charAt(i);
}
else if (c > 0x07FF) {
out += String.fromCharCode(0xE0 | ((c >> 12) & 0x0F));
out += String.fromCharCode(0x80 | ((c >> 6) & 0x3F));
out += String.fromCharCode(0x80 | ((c >> 0) & 0x3F));
}
else {
out += String.fromCharCode(0xC0 | ((c >> 6) & 0x1F));
out += String.fromCharCode(0x80 | ((c >> 0) & 0x3F));
}

}

return out;
}


function utf8to16(str)
{
var
out, i, len, c;
var char2, char3;

out = “”;
len
= str.length;
i
= 0;
while(i < len) {
c
= str.charCodeAt(i++);
switch(c >> 4)
{
case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
// 0xxxxxxx
out += str.charAt(i1);
break;
case 12: case 13:
// 110x xxxx   10xx xxxx
char2 = str.charCodeAt(i++);
out += String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F));
break;
case 14:
// 1110 xxxx  10xx xxxx  10xx xxxx
char2 = str.charCodeAt(i++);
char3
= str.charCodeAt(i++);
out += String.fromCharCode(((c & 0x0F) << 12) |
((char2
& 0x3F) << 6) |
((char3
& 0x3F) << 0));
break;
}

}


return out;
}


那么为什么需要进行转化呢?因为在JavaScript中获得的中文字符是用UTF16进行编码的,和我们统一的页面标准格式UTF
8可不一样哦,所以需要先进行转化,上面的函数UTF16到UTF8,然后再进行Base64的编码。

下面是关于Js进行Base64编码和解码的相关操作:

var base64EncodeChars
= ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/;
var base64DecodeChars
= new Array(
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 62, 1, 1, 1, 63,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 1, 1, 1, 1, 1, 1,
1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 1, 1, 1, 1, 1,
1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 1, 1, 1, 1, 1);
//客户端Base64编码
function base64encode(str) {
var
out, i, len;
var c1, c2, c3;

len
= str.length;
i
= 0;
out = “”;
while(i < len) {
c1
= str.charCodeAt(i++) & 0xff;
if(i == len)
{
out += base64EncodeChars.charAt(c1 >> 2);
out += base64EncodeChars.charAt((c1 & 0x3) << 4);
out += ==;
break;
}

c2
= str.charCodeAt(i++);
if(i == len)
{
out += base64EncodeChars.charAt(c1 >> 2);
out += base64EncodeChars.charAt(((c1 & 0x3)<< 4) | ((c2 & 0xF0) >> 4));
out += base64EncodeChars.charAt((c2 & 0xF) << 2);
out += =;
break;
}

c3
= str.charCodeAt(i++);
out += base64EncodeChars.charAt(c1 >> 2);
out += base64EncodeChars.charAt(((c1 & 0x3)<< 4) | ((c2 & 0xF0) >> 4));
out += base64EncodeChars.charAt(((c2 & 0xF) << 2) | ((c3 & 0xC0) >>6));
out += base64EncodeChars.charAt(c3 & 0x3F);
}

return out;
}

//客户端Base64解码
function base64decode(str) {
var c1, c2, c3, c4;
var i, len,
out;

len
= str.length;
i
= 0;
out = “”;
while(i < len) {
/* c1 */
do {
c1
= base64DecodeChars[str.charCodeAt(i++) & 0xff];
}
while(i < len && c1 == 1);
if(c1 == 1)
break;

/* c2 */
do {
c2
= base64DecodeChars[str.charCodeAt(i++) & 0xff];
}
while(i < len && c2 == 1);
if(c2 == 1)
break;

out += String.fromCharCode((c1 << 2) | ((c2 & 0x30) >> 4));

/* c3 */
do {
c3
= str.charCodeAt(i++) & 0xff;
if(c3 == 61)
return out;
c3
= base64DecodeChars[c3];
}
while(i < len && c3 == 1);
if(c3 == 1)
break;

out += String.fromCharCode(((c2 & 0XF) << 4) | ((c3 & 0x3C) >> 2));

/* c4 */
do {
c4
= str.charCodeAt(i++) & 0xff;
if(c4 == 61)
return out;
c4
= base64DecodeChars[c4];
}
while(i < len && c4 == 1);
if(c4 == 1)
break;
out += String.fromCharCode(((c3 & 0x03) << 6) | c4);
}

return out;
}


这样传递过去的值就可以在服务器端解码操作了。
下面是C#的Base64加码和解码的相关类:
using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;

namespace CNVP.Base64
{
/// <summary>
/// MyBase64 的摘要说明
/// </summary>

public class MyBase64
{
public MyBase64()
{
//
// TODO: 在此处添加构造函数逻辑
//
}

/// <summary>
/// 服务器端Base64编码
/// </summary>
/// <param name=”data”></param>
/// <returns></returns>

public string base64Encode(string data)
{
try
{
byte[] encData_byte = new byte[data.Length];
encData_byte
= System.Text.Encoding.UTF8.GetBytes(data);
string encodedData = Convert.ToBase64String(encData_byte);
return encodedData;
}

catch (Exception e)
{
throw new Exception(Error in base64Encode + e.Message);
}

}

/// <summary>
/// 服务器端Base64解码
/// </summary>
/// <param name=”data”></param>
/// <returns></returns>

public string base64Decode(string data)
{
try
{
System.Text.UTF8Encoding encoder
= new System.Text.UTF8Encoding();
System.Text.Decoder utf8Decode
= encoder.GetDecoder();
byte[] todecode_byte = Convert.FromBase64String(data);
int charCount = utf8Decode.GetCharCount(todecode_byte, 0, todecode_byte.Length);
char[] decoded_char = new char[charCount];
utf8Decode.GetChars(todecode_byte,
0, todecode_byte.Length, decoded_char, 0);
string result = new String(decoded_char);
return result;
}

catch (Exception e)
{
throw new Exception(Error in base64Decode + e.Message);
}

}

}

}


var Keyword
=base64encode(utf16to8(document.all.Keyword.value));
Keyword
=Keyword.replace(+,%2B);//替换+,否则在服务器解码的时候会出错
服务器端使用以下代码调用:
CNVP.Base64.MyBase64 base64
= new CNVP.Base64.MyBase64();
Keyword
=base64.base64Decode(Keyword);


[转载]用ISAPI Filter设置HttpOnly属性

mikel阅读(1298)

[转载]用ISAPI Filter设置HttpOnly属性 – tonyqus.cn – 博客园.

作者:Tony Qu

说到ISAPI很多人会觉得很陌生,因为如果你是做ASP.NET开发的话,ISAPI的方式已经过时,取而代之的是HttpHandler和 HttpModule,说到这两个东西很多人估计明白了,ISAPI可以说是早期实现请求拦截和处理的唯一途径,只是随着ASP.NET的流行,渐渐淡出 了开发人员的视野。

此文的开发场景是这样的,我们公司使用古老的ASP语言,但是ASP的Response.Cookies属性中没有HttpOnly(但.Net的 Cookie对象是有HttpOnly属性的),有帖子说可以利用Path属性来设置HttpOnly,可以这么做是因为我们在页面中设置cookie值 的动作都会被转换成Set-Cookie头,如下

Set-Cookie: user=t=bfabf0b1c1133a822; path=/
但如果要让cookie变成HttpOnly,就需要用如下格式:
Set-Cookie: user=t=bfabf0b1c1133a822; path=/;HttpOnly

理论上讲设置Path是完全可行的,因为说白了就是在原来Path的基础上增加;HttpOnly,

但经试验表明这行不通,比如我用下面的代码

Response.Cookies(“user”).Path+=”;HttpOnly”;

得到的结果却是

Set-Cookie: user=t=bfabf0b1c1133a822; path=/3B%;HttpOnly

这显然是不行的,所以我们不得不考虑用ISAPI来实现。

ISAPI基础

首先,请不要把ISAPI Extension和ISAPI Filter混为一谈,这两个东西虽然只差一个字,但却完全是两样东西,所提供的接口是完全不一样的。ISAPI Extension是一个类似页面的dll,你可以对它做post或get提交,如http://localhost/abc.dll?a=1, 从严格意义上讲它没有拦截的功能,和cgi差不多。而ISAPI Filter则是具有过滤功能的,你可以在IIS网站的属性中添加需要加载的ISAPI Filter,例如ASP.NET的实现也使用了一个ISAPI Filter,叫做aspnet_filter.dll。

ISAPI Filter说到底就是一个DLL,它有两个主要的接口:GetFilterVersion和HttpFilterProc,如下所示:

BOOL WINAPI __stdcall GetFilterVersion(HTTP_FILTER_VERSION *pVer)
{
 /* Specify the types and order of notification */

 pVer->dwFlags = (SF_NOTIFY_PREPROC_HEADERS | SF_NOTIFY_AUTHENTICATION |
 SF_NOTIFY_URL_MAP | SF_NOTIFY_SEND_RAW_DATA | SF_NOTIFY_LOG | SF_NOTIFY_END_OF_NET_SESSION );

 pVer->dwFilterVersion = HTTP_FILTER_REVISION;

 strcpy(pVer->lpszFilterDesc, "Upper case conversion filter, Version 1.0");

 CFile myFile("c:\\mylist.html", CFile::modeCreate | CFile::modeWrite);
 myFile.SeekToEnd();
 char myText[40];
 strcpy(myText,"<B>GetFilterVersion </B><BR><BR>");
 myFile.Write(myText,strlen(myText));
 myFile.Close();

 return TRUE;
}

GetFilterVersion不仅仅是用来获得Filter版本这么简单,它可以用来过滤需要触发的事件,这些事件的详细信息你可以参考http://msdn.microsoft.com/en-us/library/ms825957.aspx。请注意,这里做的是或操作,而不是与操作,学过数理逻辑的应该明白这个是干嘛用的,就是值的叠加,说的再直接点,EventA|EventB就是我既要Event A也要Event B。

DWORD WINAPI __stdcall HttpFilterProc(HTTP_FILTER_CONTEXT *pfc, DWORD NotificationType, VOID *pvData)
{
 CFile myFile("c:\\mylist.html", CFile::modeWrite);
 myFile.SeekToEnd();

 switch (NotificationType) {

 case SF_NOTIFY_ACCESS_DENIED :

 myFile.Write("SF_NOTIFY_ACCESS_DENIED<BR>",strlen("SF_NOTIFY_ACCESS_DENIED<BR>"));
 break;

 case SF_NOTIFY_AUTH_COMPLETE :

 myFile.Write("SF_NOTIFY_AUTH_COMPLETE<BR>",strlen("SF_NOTIFY_AUTH_COMPLETE<BR>"));
 break;

 case SF_NOTIFY_AUTHENTICATION :

 myFile.Write("SF_NOTIFY_AUTHENTICATION<BR>",strlen("SF_NOTIFY_AUTHENTICATION<BR>"));
 break;

 case SF_NOTIFY_END_OF_NET_SESSION :

 myFile.Write("SF_NOTIFY_END_OF_NET_SESSION<BR>",strlen("SF_NOTIFY_END_OF_NET_SESSION<BR>"));
 break;

 case SF_NOTIFY_END_OF_REQUEST :

 myFile.Write("SF_NOTIFY_END_OF_REQUEST<BR>",strlen("SF_NOTIFY_END_OF_REQUEST<BR>"));
 break;

 case SF_NOTIFY_LOG :

 myFile.Write("SF_NOTIFY_LOG<BR>",strlen("SF_NOTIFY_LOG<BR>"));
 break;

 case SF_NOTIFY_PREPROC_HEADERS :

 myFile.Write("SF_NOTIFY_PREPROC_HEADERS<BR>",strlen("SF_NOTIFY_PREPROC_HEADERS<BR>"));
 break;

 case SF_NOTIFY_READ_RAW_DATA :

 myFile.Write("SF_NOTIFY_READ_RAW_DATA<BR>",strlen("SF_NOTIFY_READ_RAW_DATA<BR>"));
 break;

 case SF_NOTIFY_SEND_RAW_DATA :

 myFile.Write("SF_NOTIFY_SEND_RAW_DATA<BR>",strlen("SF_NOTIFY_SEND_RAW_DATA<BR>"));
 break;

 case SF_NOTIFY_SEND_RESPONSE :

 myFile.Write("SF_NOTIFY_SEND_RESPONSE<BR>",strlen("SF_NOTIFY_SEND_RESPONSE<BR>"));
 break;

 case SF_NOTIFY_URL_MAP :

 myFile.Write("SF_NOTIFY_URL_MAP<BR>",strlen("SF_NOTIFY_URL_MAP<BR>"));
 break;

 case SF_NOTIFY_SECURE_PORT :

 myFile.Write("SF_NOTIFY_SECURE_PORT<BR>",strlen("SF_NOTIFY_SECURE_PORT<BR>"));
 break;

 case SF_NOTIFY_NONSECURE_PORT :

 myFile.Write("SF_NOTIFY_NONSECURE_PORT<BR>",strlen("SF_NOTIFY_NONSECURE_PORT<BR>"));
 break;

 default :
 break;
 }


 myFile.Close();

 return SF_STATUS_REQ_NEXT_NOTIFICATION;
}

HttpFilterProc是主要入口,相当于Console程序中的main。上面这段代码是在这些事件触发时写入一个日志,这样便于调试。

说到这里我们来了解下通常开发一个ISAPI Filter的流程。

a. 获得一个现有的ISAPI Filter项目,当做模板,这个网上很多,google一下就有了。

b. 修改GetFilterVersion中的dwFlags的值来决定需要哪些事件

c. 修改HttpFilterProc中的case分支,删除不需要的事件

d. 在需要处理的事件中写代码。

有一件事必须提醒大家,在写ISAPI时,你千万不要忘了把这两个接口暴露出去,也就是定义DLL的EXPORTS,如下:

LIBRARY "isapi_sample"
EXPORTS
HttpFilterProc
GetFilterVersion

事件的执行顺序

在ASP.NET中我们有Page Life Cycle,ISAPI Filter也是如此,这些时间的执行顺序可以在 http://msdn.microsoft.com/en-us/library/ms524855(VS.90).aspx 上找到,下面的事件就是按执行顺序排列的。

SF_NOTIFY_READ_RAW_DATA

SF_NOTIFY_PREPROC_HEADERS

SF_NOTIFY_URL_MAP

SF_NOTIFY_AUTHENTICATION

SF_NOTIFY_AUTH_COMPLETE

SF_NOTIFY_SEND_RESPONSE

SF_NOTIFY_SEND_RAW_DATA

SF_NOTIFY_END_OF_REQUEST

SF_NOTIFY_LOG

SF_NOTIFY_END_OF_NET_SESSION

通过分析,我们知道要想获得Set-Cookie header必须在ASP把页面处理完之后,因为ASP页面代码有可能会设置Cookie值,所以SF_NOTIFY_PREPROC_HEADERS事 件并不合适,因为它是在收到请求后,处理页面前触发的,我们需要的是在页面处理完,发送前触发的事件,所以 SF_NOTIFY_SEND_RESPONSE最合适。在下一节我们将讲解如何在该事件中添加处理代码。

如何遍历Set-Cookie

HttpFilterProc函数的第三个参数VOID *pvData是对应事件的数据,为了获得header里面的数据,我们会把它转换成PHTTP_FILTER_PREPROC_HEADERS,因为我们先要把Set-cookie的数据读出来,然后才能处理。

代码如下:

 1: case SF_NOTIFY_SEND_RESPONSE :
 2: pPH = (PHTTP_FILTER_PREPROC_HEADERS)pvData;
 3: pPH->GetHeader(pfc, "Set-Cookie:", szBuffer, &dwSize);
 4:
 5: cookieNum=sizeof(strtok(szBuffer,","));
 6: if(cookieNum>0)
 7: {
 8: //handle the cookies that are read from header
 9: ...
 10: }

这里的szBuffer就是我们获得的Set-Cookie的字符串,这里要讲一下Set-Cookie到底是啥,因为很多程序员对Set-Cookie的含义和表示形式不是特别了解。

每次我们在页面中设置Cookie值,无论是ASP还是ASP.NET,都会把设置的操作转换为Set-Cookie中的一段字符串,如果你使用 Fiddler或者HttpFox跟踪这些请求的话,你会发现头里面有一项就是Set-Cookie项,这项仅在有设置Cookie的操作时才会有。另 外,Set-Cookie中的每一个Cookie字符串使用逗号分隔开的,如下

Set-Cookie: test1=a; path=/, test2=b; path=/

这里设置了名为test1和test2的两个cookie值,单个cookie的属性之间使用分号分隔的。也正是因为如此,这段代码中使用 strtok来获得字符串中每一段用逗号分隔的cookie字符串,这里的cookieNum表示Set-cookie中cookie字符串的总数(注 意,不是字符的总数)。

一旦我们获得了每一个cookie的字符串,我们就可以把;HttpOnly附加到这些字符串的最后,并最终把字符串拼起来组成Set-Cookie字符串,关于如何做字符串拼接本文就不多讲了,这完全是C++实现的问题。

如何覆盖Set-Cookie字符串

这里的设置cookie和我们平时在代码里做的可不太一样,因为我们要直接修改请求中的Set-Cookie,之所以是修改而不是增加新的Set-Cookie,是因为Set-Cookie在请求的header中只能有一个,

HTTP_FILTER_SEND_RESPONSE * pResponse=(HTTP_FILTER_SEND_RESPONSE *)pvData;
BOOL fServer = TRUE;
fServer = pResponse->SetHeader(pfc, "Set-Cookie:",szHeader);

上面的代码把pvData转换成HTTP_FILTER_SEND_RESPONSE类型,这样我们就可以对Response进行操作,并通过调用它的SetHeader方法来设置Set-Cookie header。

完整代码下载:isapi_sample.zip (VC6项目),最主要的是MyISAPI.cpp和MyISAPI.def文件,其他都是工程文件。

[转载]重构:代码之丑(一)

mikel阅读(1216)

[转载]代码之丑(一) – 梦想风暴 – 博客大巴.

版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明
http://dreamhead.blogbus.com/logs/80612297.html

诸位看官,上代码:
if (0 == iRetCode) {
this->SendPeerMsg(“000”, “Process Success”, outRSet);
} else {
this->SendPeerMsg(“000”, “Process Failure”, outRSet);
}

乍一看,这段代码还算比较简短。那下面这段呢?
if(!strcmp(pRec->GetRecType(), PUB_RECTYPE::G_INSTALL)) {
CommDM.jkjtVPDNResOperChangGroupInfo(
const_cast(CommDM.GetProdAttrVal(“vpdnIPAddress”,
&(pGroupSubs->m_ProdAttr))),
true);
} else {
CommDM.jkjtVPDNResOperChangGroupInfo(
const_cast(CommDM.GetProdAttrVal(“vpdnIPAddress”,
&(pGroupSubs->m_ProdAttr))),
false);
}

看出来问题了吗?经过仔细的对比,我们发现,对于如此华丽的代码,if/else的执行语句真正的差异只在于一个参数。第一段代码,二者的差异只是发送的消息,第二段代码,差异在于最后那个参数。

看破这个差异之后,新的写法就呼之欲出了,以第一段代码为例:
const char* msg = (0 == iRetCode ? “Process Success” : “Process Failure”);
this->SendPeerMsg(“000”, msg, outRSet);

为了节省篇幅,我选择了条件表达式。我知道,很多人不是那么喜欢它。如果if/else依旧是你的大爱,勇敢追求去吧!

由这段代码调整过程,我们得出一个简单的规则:

  • 让判断条件做真正的选择。

这里判断条件真正判断的内容是消息的内容,而不是消息发送的过程。经过我们的调整,得到消息内容和和发送消息的过程严格分离开来。

消除了代码中的冗余,代码也更容易理解,同时,给未来留出了可扩展性。如果将来iRetCode还有更多的情形,我们只要在消息获取的时候进行调整就好了。当然,封装成一个函数是一个更好的选择,这样代码就变成了:
this->SendPeerMsg(“000”, peerMsgFromRetCode(iRetCode), outRSet);

至于第二段代码的调整,留给你练手了。

这样丑陋的代码是如何从众多代码中脱颖而出的呢?很简单,只要看到,if/else两个执行块里面的内容相差无几,需要我们人工比字符寻找差异,恭喜你,你找到它了。

[转载]重构:代码之丑(二)

mikel阅读(1127)

[转载]代码之丑(二) – 梦想风暴 – 博客大巴.

版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明
http://dreamhead.blogbus.com/logs/81144620.html

这是一个长长的判断条件:
if ( strcmp(rec.type, “PreDropGroupSubs”) == 0
|| strcmp(rec.type, “StopUserGroupSubsCancel”) == 0
|| strcmp(rec.type, “QFStopUserGroupSubs”) == 0
|| strcmp(rec.type, “QFStopUserGroupSubsCancel”) == 0
|| strcmp(rec.type, “QZStopUserGroupSubs”) == 0
|| strcmp(rec.type, “QZStopUserGroupSubsCancel”) == 0
|| strcmp(rec.type, “SQStopUserGroupSubs”) == 0
|| strcmp(rec.type, “SQStopUserGroupSubsCancel”) == 0
|| strcmp(rec.type, “StopUseGroupSubs”) == 0
|| strcmp(rec.type, “PreDropGroupSubsCancel”) == 0)

之所以注意到它,因为最后两个条件是最新修改里面加入的,换句话说,这不是一次写就的代码。单就这一次而言,只改了两行,这是可以接受的。但这是遗留代码。每次可能只改了一两行,通常我们会不只一次踏入这片土地。经年累月,代码成了这个样子。

这并非我接触过的最长的判断条件,这种代码极大的开拓了我的视野。现在的我,即便面对的是一屏无法容纳的条件,也可以坦然面对了,虽然显示器越来越大。

其实,如果这个判断条件是这个函数里仅有的东西,我也就忍了。遗憾的是,大多数情况下,这只不过是一个更大函数中的一小段而已。

为了让这段代码可以接受一些,我们不妨稍做封装:
bool shouldExecute(Record& rec) {
return (strcmp(rec.type, “PreDropGroupSubs”) == 0
|| strcmp(rec.type, “StopUserGroupSubsCancel”) == 0
|| strcmp(rec.type, “QFStopUserGroupSubs”) == 0
|| strcmp(rec.type, “QFStopUserGroupSubsCancel”) == 0
|| strcmp(rec.type, “QZStopUserGroupSubs”) == 0
|| strcmp(rec.type, “QZStopUserGroupSubsCancel”) == 0
|| strcmp(rec.type, “SQStopUserGroupSubs”) == 0
|| strcmp(rec.type, “SQStopUserGroupSubsCancel”) == 0
|| strcmp(rec.type, “StopUseGroupSubs”) == 0
|| strcmp(rec.type, “PreDropGroupSubsCancel”) == 0);
}

if (shouldExecute(rec)) {

}

现在,虽然条件依然还是很多,但和原来庞大的函数相比,至少它已经被控制在一个相对较小的函数里了。更重要的是,通过函数名,我们终于有机会说出这段代码判断的是什么了。

提取函数把这段代码混乱的条件分离开来,它还是可以继续改进的。比如,我们把判断的条件进一步提取:
bool shouldExecute(Record& rec) {
static const char* execute_type[] = {
“PreDropGroupSubs”,
“StopUserGroupSubsCancel”,
“QFStopUserGroupSubs”,
“QFStopUserGroupSubsCancel”,
“QZStopUserGroupSubs”,
“QZStopUserGroupSubsCancel”,
“SQStopUserGroupSubs”,
“SQStopUserGroupSubsCancel”,
“StopUseGroupSubs”,
“PreDropGroupSubsCancel”
};

int size = ARRAY_SIZE(execute_type);
for (int i = 0; i < size; i++) {
if (strcmp(rec.type, execute_type[i]) == 0) {
return true;
}
}

return false;
}

这样的话,再加一个新的type,只要在数组中增加一个新的元素即可。如果我们有兴趣的话,还可以进一步对这段代码进行封装,把这个type列表变成声明式,进一步提高代码的可读性。

发现这种代码很容易,只要看到在长长的判断条件,就是它了。要限制这种代码的存在,我们只要以设定一个简单的规则:

  • 判断条件里面不允许多个条件的组合

在实际的应用中,我们会把“3”定义为“多”,也就是如果有两个条件的组合,可以接受,如果是三个,还是改吧!

虽然通过不断调整,这段代码已经不同于之前,但它依然不是我们心目中的理想代码。出现这种代码,往往意味背后有更严重的设计问题。不过,它并不是这里讨论的内容,这里的讨论就到此为止吧!

sinojelly在《代码之丑(二) 》的评论里问了个问题,“把这个type列表变成声明式”,什么样的声明式?

好吧!我承认,我偷懒了,为了省事,一笔带过了。简单理解声明式的风格,就是把描述做什么,而不是怎么做。一个声明式编程的例子是Rails里面的数据关联,为人熟知的has_many和belongs_to。通过声明,模型类就会具备一些数据关联的能力。

具体到实际开发里,声明式编程需要有两个部分:一方面是一些基础的框架性代码,另一方面是应用层面如何使用。框架代码通常来说,都不像应用层面代码那么好理解,但有了这个基础,应用代码就会变得简单许多。

针对之前的那段代码,按照声明性编程风格,我改造了代码,下面是框架部分的代码:

#define BEGIN_STR_PREDICATE(predicate_name) \
bool predicate_name(const char* field) { \
static const char* predicate_true_fields[] = {

#define STR_PREDICATE_ITEM(item) #item ,

#define END_STR_PREDICATE \
};\
\
int size = ARRAY_SIZE(predicate_true_fields);\
for (int i = 0; i < size; i++) { \
if (strcmp(field, predicate_true_fields[i]) == 0) {\
return true;\
}\
}\
\
return false;\
}

这里用到了C/C++常见的宏技巧,为的就是让应用层面的代码写起来更像声明。对比一下之前的函数,就会发现,实际上二者几乎是一样的。有了框架,就该应用了:

BEGIN_STR_PREDICATE(shouldExecute)
STR_PREDICATE_ITEM(PreDropGroupSubs)
STR_PREDICATE_ITEM(StopUserGroupSubsCancel)
STR_PREDICATE_ITEM(QFStopUserGroupSubs)
STR_PREDICATE_ITEM(QFStopUserGroupSubsCancel)
STR_PREDICATE_ITEM(QZStopUserGroupSubs)
STR_PREDICATE_ITEM(QZStopUserGroupSubsCancel)
STR_PREDICATE_ITEM(SQStopUserGroupSubs)
STR_PREDICATE_ITEM(SQStopUserGroupSubsCancel)
STR_PREDICATE_ITEM(StopUseGroupSubs)
STR_PREDICATE_ITEM(SQStopUserGroupSubsCancel)
STR_PREDICATE_ITEM(StopUseGroupSubs)
STR_PREDICATE_ITEM(PreDropGroupSubsCancel)
END_STR_PREDICATE

shouldExecute就此重现出来了。不过,这段代码已经不再像一个函数,而更像一段声明,这就是我们的目标。有了这个基础,实现一个新的函数,不过是做一段新的声明而已。

接下来就是如何使用了,与之前略有差异的是,这里为了更好的通用性,把字符串作为参数传了进去,而不是原来的整个类对象。
shouldExecute(r.type);

虽然应用代码变得简单了,但写出框架的结构是需要一定基础的。它不像应用代码那样来得平铺直叙,但其实也没那么难,只不过很多人从没有考虑把代码写成这样。只要换个角度去思考,多多练习,也就可以驾轻就熟了。

[转载]重构:代码之丑(三)

mikel阅读(1203)

[转载]代码之丑(三) – 梦想风暴 – 博客大巴.

版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明
http://dreamhead.blogbus.com/logs/82256362.html

又见switch:
switch(firstChar) {
case ‘N’:
nextFirstChar = ‘O’;
break;
case ‘O’:
nextFirstChar = ‘P’;
break;
case ‘P’:
nextFirstChar = ‘Q’;
break;
case ‘Q’:
nextFirstChar = ‘R’;
break;
case ‘R’:
nextFirstChar = ‘S’;
break;
case ‘S’:
nextFirstChar = ‘T’;
break;
case ‘T’:
throw Ebusiness();
default:
}

出于多年编程养成的条件反射,我对于switch总会给予更多的关照。研习面向对象编程之后,看见switch就会想到多态,遗憾的是,这段代码和多态没 什么关系。仔细阅读这段代码,我找出了其中的规律,nextFirstChar就是firstChar的下一个字符。于是,我改写了这段代码:
switch(firstChar) {
case ‘N’:
case ‘O’:
case ‘P’:
case ‘Q’:
case ‘R’:
nextFirstChar = firstChar + 1;
break;
case ‘T’:
throw Ebusiness();
default:
}

现在,至少看起来,这段代码已经比原来短了不少。当然这么做基于一个前提,也就是这些字母编码的顺序确确实实连续的。从理论上说,开始那段代码适用性更强。但在实际开发中,我们碰到字母不连续编码的概率趋近于0。

但这段代码究竟是如何产生的呢?我开始研读上下文,原来这段代码是用当前ID产生下一个ID的,比如当前是N0000,下一个就是N0001。如果数字满 了,就改变字母,比如当前ID是R9999,下一个就是T0000。在这里,字母也就相当于一位数字,根据情况进行进位,所以有了这段代码。

代码上的注释告诉我,字母的序列只有从N到T,根据这个提示,我再次改写了这段代码:
if (firstChar >= ‘N’ && firstChar <= ‘S”) {
nextFirstChar = firstChar + 1;
} else {
throw Ebusiness();
}

这里统一处理了字母为T和default的情形,严格说来,这和原有代码并不完全等价。但这是了解了需求后做出的决定,换句话说,原有代码在这里的处理中存在漏洞。

修改这段代码,只是运用了非常简单的编程技巧。遗憾的是,即便如此简单的编程技巧,也不是所有开发人员都驾轻就熟的,很多人更习惯于“平铺直叙”。 这种直白造就了代码中的许多鸿篇巨制。我听过不少“编程是体力活”的抱怨,不过,能把写程序干成体力活,也着实不值得同情。写程序,不动脑子,不体力才 怪。

无论何时何地,只要switch出现在眼前,请提高警惕,那里多半有坑。