自学Web开发第十三天-基于VB和ASP.NET;重新设计网站应用架构,重写登录页面,自定义函数和类,DataSet及GridView
经过这些天的学习,对ASP.NET了解了很多,所以决定重新设计前台后台架构,重新写一个登录页面,并用自定义的类进行访问控制和日志写入。
重新设计前后台架构
前台
前台页面使用Forms身份验证,禁止匿名用户访问。建立登录页,登录成功后跳转至默认页。
默认页和其他的功能页使用母版,在母版页上使用Menu控件作为导航条。页面样式先不设置,准备以后使用主题模板。
设计一个管理员页面,用于创建用户,信息包括身份信息、角色信息、权限信息等。并能够进行查询和更改。
后台
登录页面进行数据库比对后,确认身份进行FormsAuthentication身份验证,然后用session记录用户信息及角色和权限。在写入登录日志后,跳转到默认页面。
设计个函数或类,在登录和更改数据库等重要操作时写入日志文件。
设计个自定义类,用作每个页面加载时的权限控制,导航条的内容也由权限更新。
在管理员页面中,加入对用户信息的增删改查等功能。并且执行数据更新操作时写入日志文件。
重写登录页面
在确定好目前的目标后,就可以一步一步来了。先是重写登录页面。这里的改动不会太大,主要是身份验证成功后的日志写入工作。因为以后设计数据库内容更新都要进行日志写入,所以准备写个函数或类来进行复用。
另外每次更换页面都需要进行基于角色的权限认证,角色方面决定自己写,就需要有一个声明周期跟随Session的全局的变量来存储角色信息了。原计划自定义一个树形的数据结构或者类,来存储角色和访问权限信息,但是自定义动态的树形结构有些复杂,等以后再研究,暂时使用一个权限数据表DataSet类来存储权限信息,并放在Session中。
还有就是在登录页面加载时,获取是否已经有验证信息,如果有验证信息则调用登录函数进行日志写入和权限处理,然后跳转。如果没有验证信息,则在用户名、密码输入后进行判断。
自定义变量、函数和类
首先研究下自定义的全局变量如何使用
自定义全局变量
自定义全局变量有3种方法:使用Web.config、使用Global.asax,或使用Session对象进行缓存。
使用Web.config设置全局变量
在Web.config里<configration>下新增<appSettings>标签,可以新增变量<add key=“key1” value=“value1”/>,变量名是key1,值为value1。
<configuration>
<appSettings>
<add key ="key1" value="value1"/>
</appSettings>
</configuration>
调用方法为:
Dim str As String = ConfigurationSettings.AppSettings["key1"]
即把key1的值value1赋给了str。
使用Global.asax设置全局变量
在项目中添加新建项,选择全局应用程序类即添加了Global.asax文件。在此文件的<script>标签内声明的Public变量即为application对象定义的静态变量。
<script runat="server">
Public key1 As String = "value1"
</script>
使用方法为:
Dim str As String = ASP.global_asax.key1
使用Session对象进行缓存
具体使用方法和Session对象操作的方法一样,我们只要在Global.asax文件中的Session_Start事件中给Session创建键值对,然后使用就行了。
在了解全局变量后,就可以自己写类文件了。
自定义函数和类
自定义的函数和类方式大致相同,功能相同的若干函数可以封装在一个类里方便使用。
一般自己写的类文件都放在App_Code下。在此文件夹下创建类文件,即可以自己写类。类分为静态类和非静态类两种,区别在于静态类在创建时被初始化(实例化)后,所有使用的都是同一个实例,而非静态类可以根据需要进行实例化,使用的时候是每个单独的实例。
静态类
C#定义静态类时,加上static即可,而VB.net定义静态类时使用Module。静态类创建时就实例化,以后所有使用的都是同一个实例,且无法再被实例化。所以如果静态类定义的有数据成员,其数据在生命周期内不会丢失。同样的,使用其成员函数时,如果涉及到数据成员,则需注意是有数据的,而不是空的。
非静态类
非静态类需要实例化之后才能使用,使用时内部的数据成员或成员函数如果想变为静态(即所有实例使用同一个),则声明为Shared即可。
对于非静态类,实例化后将随容器的生命周期一直存在。过程结束可能定义的变量消失了,但是实例还是存在的。换句话说,变量存的只是指向了一个实例的内存地址。对类型为类的变量操作,实际上是传址而不是传值。
例:类Class1的代码
Public Class Class1
Private i As Integer
Sub New(ByVal num As Integer)
i = 0
i = i + num
End Sub
Public Function add(ByVal num As Integer) As Integer
i += num
Return i
End Function
Public Function value() As Integer
Return i
End Function
End Class
测试1:
Dim a As New Class1(2) '实例化一个Class1类,定义了一个变量a,a指向实例
dim b As Class1 '定义一个变量,没有实例化
b = a 'b也指向a所在的实例
Response.Write(b.value) '值为2,等同于a.value
a.add(5) 'a加5,即a.value的值为7
Response.Write(b.value) '值为7,等同于a.value
'结论:此时对a的操作等价于对b的操作,都是对同一实例进行操作
测试2:
Dim a As New Class1(2) '实例化一个Class1类,定义了一个变量a,a指向实例
dim b As New Class1(3) '实例化另一个Class1类,定义变量b,b指向实例
Response.Write(b.value) '值为3,是实例b的值
b = a 'b也指向a所在的实例
Response.Write(b.value) '值为2,变成实例a的值
a.add(5) 'a加5,即a.value的值为7
Response.Write(b.value) '值为7,等同于a.value
'结论:此时对a的操作等价于对b的操作,都是对同一实例进行操作。而b原先指向的实例还存在,只是丢失了,无法使用
在目前制作的项目中,进行访问控制及日志写入均是操作类的函数,所以使用静态类即可。
在写对数据库操作时记录至日志的函数时,发现有一点:使用Command对象操作时还好,因为会有cmdText,可以记录至日志中。使用DataSet进行Update操作时,因为是批量操作,所以要对DataSet的每一行状态进行判断,是否需要更新,需要对DataSet进行深入的研究了。
DataSet
之前研究的而DataSet对象则可以存储多个数据表、视图等,DataSet里每行数据都有个状态,即Rows的RowState属性。研究下关于这个属性及相关问题。
在之前的研究中,RowState属性如果是Unchanged则是本条数据未更改,其他的则是有过更改。所以在准备的日志记录中,可以循环判断更改后的DataSet对象里的RowState属性,然后和未更改的数据表进行比对,进行日志记录。
所以主体上显示使用GridView,绑定DataSet,然后在控件旁边增加一些按钮及文本控件,以做到增删改查的功能。
这里详细记录下GridView控件
GridView控件
GridView控件以表格式布局显示数据。该控件与新的数据源控件系统紧密结合,只要底层的数据源对象支持,它还可以直接处理数据源的更新。
GridVie控件可以以静态形式绑定数据源(在设计页面点击控件小三角),也可以使用动态绑定数据源。这里以一个实际例子来说明GridView控件的比较重要的属性、事件等。
前台
在前台新建一个GridView控件。
预设样式
可以先设置下控件预设样式,点小三角,自动套用格式,然后选择合适的。
设置列
GridView控件如果不预设列,则显示所有的绑定数据源的列,表头名称即列名。通常使用中可能只显示其中的几列,或者更换下列名称,这里就需要手动设置列。
手动设置列时候注意需要将AutoGenerateColumns属性设置为False。这个属性控制是否自动设置列,如果是,则会自动添加数据源的列(即使手动添加过列),所以要设成否。
之后可以手动设置显示的列,点控件小三角,然后编辑列。这里可用字段都有:
BoundField,最普通最常用的绑定的数据列
CheckBoxField,显示CheckBox列,如果数据是Boolean型,则可以绑定到这个列上
HyperLinkField,显示超链接列
ImageField,图片列
ButtonField,按钮列
CommandField,命令列,包含选择钮、编辑更新取消钮、删除钮。使用方法在后面
TemplateField,模板列,详细情况在后面
DynamicField,动态列,可以动态的增加列
使用中最常用的就是BoundField数据列、CommandField命令列和TemplateField模板列了。
BoundField数据列
数据列是最常用的列,添加数据列后,在DataField属性中,绑定数据源中的相应数据列的列名,然后HeaderText输入想要在GridView显示的列名即可。需要注意的是ReadOnly属性,如果值为True则此列不可编辑。
CommandField命令列
命令列可以显示一列,里面有个按钮,可以进行选择、编辑、删除的操作。
如果绑定的是数据源控件,则可以直接返写至数据库,如果绑定的是其他数据源,则可以通过相应的事件进行具体操作。
此列常用的属性有:
ButtonType 按钮类型,可选超链接、按钮、图片
CancelText 取消按钮的文本
CancelImageUrl 如果按钮类型为图片,取消按钮的图片Url
ShowCancelButton,显示取消按钮
删除、编辑、新建、选择、插入、更新等功能同取消,
TemplateField模板列
添加了模板列后,在列属性HeaderText设置列名称,然后就建立了一个空的模板列。编辑具体的模板列在控件的小箭头下编辑模板,然后在显示里选择列号(从0开始)下的ItemTemplate,即编辑相应的模板列了。在ItemTemplate里可以输入文字,也可以添加CheckBox、Button等ASP控件,然后每行数据都会有这些。
这里我们新建个模板列名字为选择,放置个CheckBox。然后增加2个数据列分别是用户名和密码,绑定到数据源的用户名和密码列。最后增加一个命令列,添加编辑、删除、选择按钮。
其他设置
分页
如果数据多了,则可以设置自动分页。将GridView控件的AllowPaging属性设置为True则可以自动分页,PageSize为每页显示的行数量。另外可以在PagerSettings设置分页的样式,在PagerSettings的Mode属性设置分页模式。例如NextPrevious显示上一页下一页,NumericFirstLast显示编号的链接按钮和第一页、最后一页的链接按钮。
主键
GridView控件的DataKeyNames的值就是主键,设置为数据源主键的列名称,则以后可以通过控件主键进行一些操作。
模板页的其他模式
在行编辑模式(例如点击命令列中的编辑,则转到编辑模式。此模式中各数据列转为TextBox控件以供编辑),模板列显示的控件或文字在模板编辑下显示的EditItemTemplate中进行编辑。如果在需要编辑页脚,则在FooterTemplate中进行编辑。
另,可以自定义导航条,在PagerTemplate下进行编辑。如果使用自定义导航条,则系统默认的导航失效。
添加其他按钮
可以根据自己需要,添加其他的按钮用于各种功能,这里添加了4个按钮,分别是全选、全不选(用于已经添加的CheckBox)、新增(增加数据)、批量删除(删除所有Checked的数据)。
前台代码如下:
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" CellPadding="4" ForeColor="#333333" GridLines="None" AllowPaging="True" DataKeyNames="用户编码">
<AlternatingRowStyle BackColor="White" ForeColor="#284775" />
<Columns>
<asp:TemplateField HeaderText="选择">
<ItemTemplate>
<asp:CheckBox ID="CheckBox" runat="server" />
</ItemTemplate>
</asp:TemplateField>
<asp:BoundField DataField="用户名" HeaderText="用户名" />
<asp:BoundField DataField="密码" HeaderText="密码" />
<asp:CommandField ShowDeleteButton="True" ShowEditButton="True" ShowSelectButton="True" />
</Columns>
<EditRowStyle BackColor="#999999" />
<FooterStyle BackColor="#5D7B9D" Font-Bold="True" ForeColor="White" />
<HeaderStyle BackColor="#5D7B9D" Font-Bold="True" ForeColor="White" />
<PagerStyle BackColor="#284775" ForeColor="White" HorizontalAlign="Center" />
<RowStyle BackColor="#F7F6F3" ForeColor="#333333" />
<SelectedRowStyle BackColor="#E2DED6" Font-Bold="True" ForeColor="#333333" />
<SortedAscendingCellStyle BackColor="#E9E7E2" />
<SortedAscendingHeaderStyle BackColor="#506C8C" />
<SortedDescendingCellStyle BackColor="#FFFDF8" />
<SortedDescendingHeaderStyle BackColor="#6F8DAE" />
</asp:GridView>
<asp:Button ID="CheckAll" runat="server" Text="全选" /><asp:Button ID="UnCheckAll" runat="server" Text="全不选" /><br />
<asp:Button ID="AddNew" runat="server" Text="新增" /><asp:Button ID="Detete" runat="server" Text="批量删除" />
后台
在后台先进行数据源的获取和绑定,然后通过各种事件完善各个功能。
数据源的获取和绑定
GridView能够绑定很多种数据源,最常用的是绑定DataSet或DataTable。因为绑定操作在GridView使用过程中使用很频繁,基本每进行操作后,需要刷新控件显示都需要重新绑定。所以可以写一个绑定函数,在需要的时候调用比较方便。
Private sub Bind()
'建立数据库连接
Dim cmdstr As String = "select * from 用户信息表 "
mycon.Open()
'获取数据
Dim da As New SqlDataAdapter(cmdstr, mycon)
Dim ds As New DataSet
da.Fill(ds)
mycon.Close()
da.Dispose()
Dim dt As DataTable = ds.Tables(0)
'绑定到GridView
GridView.DataSource = dt
GridView.DataBind()
End sub
页面加载
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If IsPostBack Then Exit Sub
Call Bind()
End Sub
需要注意的是,要进行回传判断。如果不进行回传判断,每次页面加载时会对GridView控件重置,之后的更新等操作时就会找不到回传数据。
GridView页面跳转事件
Private Sub GridView_PageIndexChanging(sender As Object, e As GridViewPageEventArgs) Handles GridView.PageIndexChanging
GridView.PageIndex = e.NewPageIndex
Call Bind()
End Sub
点击CommandField的删除按钮
Private Sub GridView_RowDeleting(sender As Object, e As GridViewDeleteEventArgs) Handles GridView.RowDeleting
'删除确认
Dim msg As MsgBoxResult = MsgBox("确认要删除 " & GridView.Rows(e.RowIndex).Cells(1).Text & " 的用户数据吗?", vbOKCancel, "删除确认")
If msg = vbCancel Then Exit Sub
'通过主键确认删除数据所在行,写入SQL命令
Dim cmdstr As String = "delete from 用户信息表 where 用户编码 = '" & GridView.DataKeys(e.RowIndex).Value.ToString.Trim() & "'"
'操作数据库
mycon.Open()
Dim cmd As New SqlCommand(cmdstr, mycon)
Try
cmd.ExecuteNonQuery()
Catch ex As Exception
MsgBox("数据库操作失败!")
Exit Sub
Finally
cmd.Dispose()
mycon.Close()
End Try
Call Bind()
End Sub
此例需注意的有:
- 删除确认时使用了控件的用户名数据,定位时即
e.RowIndex
的Cells(1)
单元格,因为Cells()
是从0开始计数,而第1列即Cells(0)
是CheckBox列,所以使用Cells(1)
GridView.DataKeys(e.RowIndex).Value.ToString.Trim()
用于查找选定行的主键,这里主键在前台的控件属性中绑定,也在后台使用GridView.DataKeyNames = New String() {"主键列名"}
进行绑定
点击CommandField的编辑按钮,进入编辑模式
Private Sub GridView_RowEditing(sender As Object, e As GridViewEditEventArgs) Handles GridView.RowEditing
GridView.EditIndex = e.NewEditIndex
Call Bind()
End Sub
取消编辑模式
Private Sub GridView_RowCancelingEdit(sender As Object, e As GridViewCancelEditEventArgs) Handles GridView.RowCancelingEdit
GridView.EditIndex = -1
Call Bind()
End Sub
编辑完成,更新数据库(即在编辑模式点更新)
因为之后会有增加数据行,所以需要判断是更新现有数据还是新增数据
Private Sub GridView_RowUpdating(sender As Object, e As GridViewUpdateEventArgs) Handles GridView.RowUpdating
Dim str(2) As String '用于写入数据库的数组
Dim cmdstr As String
'获取各列的值
str(0) = GridView.DataKeys(e.RowIndex).Value.ToString.Trim '第一列是主键
str(1) = CType(GridView.Rows(e.RowIndex).Cells(1).Controls(0), TextBox).Text.ToString.Trim '因为编辑模式中,各列的数据是在TextBox里,所以查找TextBox控件获取数据
str(2) = CType(GridView.Rows(e.RowIndex).Cells(2).Controls(0), TextBox).Text.ToString.Trim
If str(0) = "" Then '无主键,即新增数据
'新增数据主键为数据库中最后一条数据的主键值加1(注意格式)
mycon.Open()
Dim sqlcmd As New SqlCommand("select top 1 用户编码 from 用户信息表 order by 用户编码 desc", mycon)
Try
str(0) = Format(sqlcmd.ExecuteScalar() + 1, "#000")
Catch ex As Exception
MsgBox("数据库操作失败!")
Exit Sub
Finally
sqlcmd.Dispose()
mycon.Close()
End Try
cmdstr = "insert into 用户信息表 (用户编码,用户名,密码) values ( '" & str(0) & "' , '" & str(1) & "' , '" & str(2) & "')"
Else '有主键,即更新现有数据
cmdstr = "update 用户信息表 set 用户名 = '" & str(1) & "' , 密码 = '" & str(2) & "' where 用户编码 = '" & str(0) & "'"
End If
'更新数据库
mycon.Open()
Dim cmd As New SqlCommand(cmdstr, mycon)
Try
cmd.ExecuteNonQuery()
Catch ex As Exception
MsgBox("数据库操作失败!")
Exit Sub
Finally
cmd.Dispose()
mycon.Close()
End Try
GridView.EditIndex = -1
Call Bind()
End Sub
这里要注意的有:
- 获取数据的问题,因为是在编辑模式里查找单元格里的TextBox控件里的数据,所以需要注意。
CType(GridView.Rows(e.RowIndex).Cells(1).Controls(0), TextBox).Text.ToString.Trim
即找第e行的第2格里第1个控件,转换成TextBox类型,然后获取其中的数据。 - 如果获取不到,通过调试能找到控件,但是控件里没数据,很大可能是在页面加载时没有判断是否回传。点击更新按钮进行回传时,加载页面重新绑定控件,则里面的数据就重置了。
- 更新完成后EditIndex要设成-1,表示退出编辑模式。
CheckBox全选/全不选
Protected Sub CheckAll_Click(sender As Object, e As EventArgs) Handles CheckAll.Click
For Each row As GridViewRow In GridView.Rows
Dim cb As CheckBox = CType(row.FindControl("CheckBox"), CheckBox)
If cb IsNot Nothing Then cb.Checked = True
Next
End Sub
Protected Sub UnCheckAll_Click(sender As Object, e As EventArgs) Handles UnCheckAll.Click
For Each row As GridViewRow In GridView.Rows
Dim cb As CheckBox = CType(row.FindControl("CheckBox"), CheckBox)
If cb IsNot Nothing Then cb.Checked = False
Next
End Sub
注意:这里查找控件使用的是控件ID,而不是控件集合的索引。因为控件是手动添加的,有固定ID,而编辑模式的控件是GridView控件动态添加的,没有固定ID。
新增数据行
Protected Sub AddNew_Click(sender As Object, e As EventArgs) Handles AddNew.Click
'建个空表
Dim dt As New DataTable
dt.Columns.Add("用户编码")
dt.Columns.Add("用户名")
dt.Columns.Add("密码")
Dim dr As DataRow = dt.NewRow
dt.Rows.Add(dr)
dt.AcceptChanges()
'绑定空表到GridView
GridView.DataSource = dt
GridView.DataBind()
GridView.DataKeyNames = New String() {"用户编码"}
'进入编辑模式编辑第一行
GridView.EditIndex = 0
End Sub
批量删除行
Protected Sub Detete_Click(sender As Object, e As EventArgs) Handles Detete.Click
'哪些CheckBox被选取了,则记录主键
Dim arr As New ArrayList
For Each row As GridViewRow In GridView.Rows
Dim cb As CheckBox = CType(row.FindControl("CheckBox"), CheckBox)
If (cb IsNot Nothing) And cb.Checked Then
arr.Add(GridView.DataKeys(row.RowIndex).Value.ToString.Trim())
End If
Next
'删除确认
Dim msg As MsgBoxResult = vbCancel
If arr.Count > 0 Then msg = MsgBox("确认要删除这" & arr.Count & "条数据吗?", vbOKCancel, "删除确认")
If msg = vbCancel Then Exit Sub
'操作数据库
Dim cmd As New SqlCommand
mycon.Open()
cmd.Connection = mycon
Dim cmdstr As String
For i As Integer = 0 To arr.Count - 1
'设置SQL语句
cmdstr = "delete from 用户信息表 where 用户编码 = '" & arr(i) & "'"
cmd.CommandText = cmdstr
Try
cmd.ExecuteNonQuery()
Catch ex As Exception
MsgBox("数据库操作失败!")
cmd.Dispose()
mycon.Close()
Exit Sub
End Try
Next
cmd.Dispose()
mycon.Close()
Call Bind()
End Sub