这是几个月前做的一个项目吧,一直没有时间把做的总结打成电子版的,现在把项目里的一些经验和问题解决办法写出来分享,给一些初做小型网站的同学,让他们能少走些弯路吧,毕竟各种问题查找也很费时,我这里给总结了。我也是初学者,只是多些经验,可能也有一些地方说的不对,希望大家指出。
用到的平台:VS2005(ASP.NET),SQL SERVER 2005
1 网站架构的一些经验
我做的项目是一个小系统,编码时间周期短。网站的后台代码是参照的三层架构搭的,因为网站比较小,没必要做的调用关系太复杂,就是两个Helper类做底层,一个Manager类做业务层,上面的页面做表示层。开始觉得没什么用处,后来发现好处是真的有的。
首先,出了什么错,各页面都只要去Manager类里找就行了。其次,如果后来数据库什么的有大规模改动的时候只要在Manager类里批量改就可以,省了很多时间。而且,层次分明,函数复用什么的也都方便。
还有一点很重要的,数据库一定要在写代码前就设计好。做这个项目时就是因为没走这个流程,于是乎在我写完了Manager类后,开会讨论数据库大改,于是乎我在修改Manager类的时间那个花的太不值了。
关于网站的页面架构,由于之前有过调试DIV+CSS布局的各种串位还依然无解的经验,加之时间紧张,于是用的frameset分窗体,然后子窗体中用table来布局。
我编代码的顺序是:先写底层的两个Helper类,然后写页面文件,最后再按每个页面的调用关系等写各自的后台代码,后台代码中调用到的操作数据库的函数全写到Manager类中。写完后就是边测边完善了。
说点题外话,代码前一定要先设计好写成哪几个函数,一定要注意函数的复用,只是会复用的代码部分就尽量写成单个函数,此许不同的变量就换成函数参数的形式。我现在想起来接手一同学写的那一个文件里有2000行,大片大片重复代码,我还心惊肉跳的。那个文件里我最后改成可重用的部分只有300行,几乎是重写了。
2 底层Helper类
在项目中涉及的内容不多,所以只写sqlHelper和XMLHelper两个类。sqlHelper类建议可以上网下载一个前人写过的来进行参考,自己写的不一定比前人反复过的好。
(1)复杂数据库SQL语句的编写
由于数据库需要可扩展,各种表分的很细,要查询的一条语句可能要综合几个表才能得出结果,之前的语句都自己手动编写,写完还要调看语句各种关系是否正确,很是头痛。后来一学长告知一个方法很好用:
在VS05的任一页面上拖一个SqlDataSource,在配置数据源中选择“指定自定义SQL语句或存储过程”,然后就会有图形界面,选择你要综合的几个表及各字段关系等后,就可以自动生成所想要的SQL语句了,超方便!
(2)页面防止SQL注入攻击
之前同学编写的时候全部用的是下面例子中的前一种方法,后来我去研究前人写的sqlHelper类时发现用的都是后一种方式,上网查得结果是可以防止页面上的SQL注入攻击,研究前人代码其实收获是很大的。防止SQL注入要使用参数化的SQL或存储过程,对密码等敏感信息要进行HASH,不要使用动态SQL语句,例如:
被SQL注入的句子:string sqlstring = "SELECT * FROM usre WHERE name=’” + name +”’ and pw=’” + pw +”’";
防SQL注入的句子:string sqlstring = "SELECT * FROM usre WHERE name=@name and pw=@pw";
SqlParameter name = new SqlParameter("@name", SqlDbType.Char, 10, name) };
SqlParameter pw = new SqlParameter("@pw", SqlDbType.Char, 10, pw) };
(3)XML的写入
这部分同学开始写时,是参照书上,书上用的是XmlTextWriter myXmlTextWriter,myXmlTextWriter.WriteStartElement("Summary"),myXmlTextWriter.WriteElementString("Version", "1.0")这两个主要函数来将内容写入XML文件,这几个函数有一点缺陷就是只能写一级节点,但是项目需要是要写入多级节点。可以使用下面类和函数:
XmlDataDocument xd = new XmlDataDocument(); //创建对象
xd.Load(new StringReader("<?xml version=/"1.0/" encoding=/"UTF-8/" ?>< Cmd></Cmd>"));
XmlElement cn1 = xd.CreateElement("Summary");
xd.DocumentElement.AppendChild(cn1); //创建一级节点
XmlElement temp = xd.CreateElement(“Version”);
temp.InnerText = str2; // Version节点的内容
cn1.AppendChild(temp); //在Summary下创建二级节点,三级节点的话再依次类推
xd.Save(“C://1.xml”); //最后SAVE对象到“C://1.xml”中
还有一个是,为某一节点插入属性,例如< Summary Num=”2”>中的Num就是Count节点的一个属性。例如:
XmlAttribute ab_List = xd.CreateAttribute("Num");;
cn1.Attributes.Append(ab_List);
cn1.SetAttribute("Num", 2);
3 页面文件
当时设计页面的时候,讨论了很久,很多方案直到编码了还没定下来,那时候主要原因有个:一是技术上,有些控件不了解,一些可以用控件实现的功能没有想到,以至于不知道如何实现;二是在布局上有分歧,放一起觉得多了,分开就觉得版面有点太空了。对于第一点,就先做成一些固定的,把版面定下来,技术后来再学的,才解决。对于第二点,是做成可重用的,不同类型动态显示不同内容就好,一个页面就没那么拥挤了。这种解决也是没有办法的办法了,因为只有两个星期做,一个星期测,而且只有我一个人。
(1)关于frameset分窗体后子窗体的跳转问题
第一次做服务器端,就是因为子窗体跳转问题找不到解决方案,于是放弃。这一次查到的可用的方法有两种:
一是使用JS脚本的方法
Response.Write("<script>window.parent.location=/"login.aspx/"</script>"); //使父窗体跳转至login.aspx页面
Response.Write("<script>window.parent.frames.item(2).location=/"main.aspx/"</script>"); //使frameset分窗体时的第3个子窗体跳转至main.aspx页面
二是使用超链接里的target属性实现
<a href="main.aspx" target="main" shape="rect">返回首页</a> //使name为main(target属性中)的子窗体跳转到main.aspx
我的方法可能不是什么正统方法,但这两种方法都能用。
(2)网站登录页面控件Login使用
登录使用的是ASP.NET框架里自带的Login控件,使用
Login1.UserName//用户名
Login1.Password//密码
这两个来提取控件中用户输入的用户名和密码,(注意:这里所有的Login1都是该控件的ID号,如”<asp:Login ID="Login1" runat="server" ”,当控件ID不同时,其对应也要修改)双击该控件即转到其对应事件函数,以下是一个示例代码:
protected void Login1_Authenticate(object sender, AuthenticateEventArgs e)
{
//……
//在此之前需要自行编写数据库连接、验证过程
//if (……)
e.Authenticated = true;
//在代码中使用e(函数参数中的AuthenticateEventArgs e).Authenticated属性来告知Login控件验证通过
//else
//e.Authenticated = false;// 告知Login控件验证未通过
}
Login控件通过验证后跳到指定的页面,此属性在属性中的DestinationPageUrl字段中修改。
(3)JS代码的调用问题
之前在页面上本来有很多效果想用JS做,但苦于写好了JS代码,在按钮事件里死活调用不了。后来看了很多帖子之后,估计这种情况是这个原因:ASP.NET的Button是runat=”server”的,JS是客户代码,当点击Button时,自动提交到服务器,在其OnClick=””里写JS函数调用估计没用,不过看网上说OnClientClick事件是可以的,因为这个事件是在提交到服务器前触发的,但是在ASP.NET的Button控件中没有这个事件。
还有在JS代码中,对ASP控件无法操作,原因可能也是因为是runat=”server”。
另外,在C#代码中如果想调用JS代码,可以用下面的函数:
Page.ClientScript.RegisterStartupScript(Page.GetType(), "错误", "<script>alert('错误!');location='main.aspx'</script>");
(4)网页中弹出新窗体(系统信息)问题
这个问题是在想做个弹出系统信息窗口时碰到的。
当时首先想到的就是像QQ空间一些应用要填信息时是让整个窗体变暗,中间一个窗口。查到这种实现的方法是在原来的窗体上再加一层DIV,覆盖整个窗体,但是由于要实现的功能也不需要获取输入法数据什么的,不需要这么复杂,就没用。当时查到有一个技术是叫“Lightbox JS”,有兴趣的可以去搜搜。
我使用的方法是一个弹出模态窗口,然后窗口中显示的是你自己事先写好的一个网页。
Page.ClientScript.RegisterStartupScript(Page.GetType(), "系统信息", "<script language='javascript' defer>window.showModalDialog( 'SystemInformation.aspx','系统信息','dialogLeft=400px;dialogTop=200px;dialogWidth=305px;dialogHeight=380px')</script>");
这里说说showModalDialog函数的几个参数:窗口用的页面文件名称,标题,窗口属性(距离窗口左、顶的距离,窗口的宽和高)。
(5)页面实时刷新解决方案
由于直接刷新整个页面会闪屏,于是用了AJAX的UpdatePanel控件,这个控件要使用要先下载安装AJAX工具包,这个安装过程就不说了,网上资料很多。当时没有找到UpdatePanel控件的触发更新的机制,我使用的这种方法可能会不对,但是算是实现功能了,因为没时间去深入学习AJAX了。方法如下:
在要局部刷新的页面中写入JS函数:id = setInterval("click()",3000);,即每隔3000毫秒执行一次click 函数,要停止则用window.clearInterval(id);语句。而function click(){ document.all.Button2.click(); } 即产生Button2按钮点击事件,则整个页面会提交,也就刷新了。要局部刷新只将Button2按钮放入UpdatePanel标签内的范围,例如:
<asp:ScriptManager ID="ScriptManager1" runat="server">
</asp:ScriptManager>
<asp:UpdatePanel ID="UpdatePanel1" runat="server">
<ContentTemplate>
<asp:Button ID="Button2" runat="server" Text="Button" OnClick="Button2_Click" style="display:none"/>
</ContentTemplate>
</asp:UpdatePanel>
(6)UpdatePanel无效果问题
在页面加入上面代码后,页面中却没有用,还是整个页面刷新,解决方法:
首先,将ScriptManager 标签放在form标签后,并设属性EnablePartialRendering="true"。
然后在Web.config文件中的<system.web> </system.web>标签中加入下面的代码:
<httpHandlers>
<remove verb="*" path="*.asmx"/>
<add verb="*" path="*.asmx" validate="false" type="Microsoft.Web.Script.Services.ScriptHandlerFactory, Microsoft.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
<add verb="*" path="*_AppService.axd" validate="false" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
<add verb="GET,HEAD" path="ScriptResource.axd" type="System.Web.Handlers.ScriptResourceHandler, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" validate="false"/>
</httpHandlers>
4 后台代码
(1)IsPostBack属性
IsPostBack是Page类的一个属性,一般出现在Page_Load函数中.当页面是第一次打开时,其值为False,若当前页面是一个提交后的页面,其值为True。
也就是当你的页面第一次浏览时会调用if (Page.IsPostBack) {……}中的语句,而在此页面未被关闭之前再次进入该页面时,其中语句不会被调用。
而每次服务器端控件发生事件时,都会对Page类重构,调用Page_Load函数,然后才会调用事件响应函数。
(2)页面传值
a) 可用Response.Redirect(“aaa.aspx?Id=” + Id);,但只能最长255字节,并且会在URL中显示。在跳转后的页面获取使用:Request.QuryString[“Id”]或Request.Params[“Id”]。
b) 不在URL中显示,可用Session,Cookie,Application
c) 最常用Post,利用Form提交。常用是把隐秘的数据存在隐藏域中由Form提交,适用大量数据,包括文件上传。在客户端form指定action目标后submit,在ASP. NET的伺服器中使用server.Transfer(url) 提交。用Request.Form[“FormFieldID”]获取。
d) Cache,更新不及时,但大提高效率。
e) ViewState,将页面各控件及其所有数据代存在name为_ViewState的隐藏域中,存在HTML中,安全性低。
(3)使用TABLE控件动态显示页面内容及样式控制
当时碰到的问题是要根据不同的传入值显示不同的table,table的行列都不一样。开始想可能可以用JS来解决,但发现用table控件会更好,只要在页面中加入<asp:Table ID="Table1" runat="server"></asp:Table>控件,在后台代码中即可根据参数来对table控件进行控制。示例:
TableRow row1 = new TableRow(); //定义一个行对象
TableCell cell1 = new TableCell(); //定义一个单元格对象
cell1.Text = str; //给单元格cell1中的文本赋值
cell1.Font.Bold = true; //设定单元格cell1中的文字为粗体
cell1.ForeColor = Color.White; //设定行对象cell1的前景色
row1.Cells.Add(cell1); //将cell1加入到row1中
row1.BackColor = Color.FromArgb(80, 124, 209); //设定行对象row1的背景色
Table1.Rows.Add(row1); //将row1加入Table1到中
TABLE控件和GridView控件一样在属性中没法对其边框样式进行控制,这里可以用CSS。例如下面代码可以对cssclass=gridview的控件的每行的样式进行控制:
.gridview td {
border-bottom: dotted 1px #333;
}
这里是对底部边框样式的设定。
(4)正则表达式的使用
对于输入验证其合法性,加入命名空间“System.Text.RegularExpressions”,假设对IP地址进行验证,示例:
Regex rg = new Regex("^((2[0-4]//d|25[0-5]|1//d//d|[1-9]//d?|0)//.){3}(2[0-4]//d|25[0-5]| 1//d//d|[1-9]//d?|0)$"); //定义一个正则表达式对象
Match m = rg.Match(IP); //IP为要验证的字符串
if (!m.Success) // m.Success如果为true表示验证通过
return false;