ASP.NET Web Forms实战:VB.NET与Access构建电商网站源码解析

📅 发布时间:2026/9/13 15:35:44
ASP.NET Web Forms实战:VB.NET与Access构建电商网站源码解析
简介汽车配件公司网站ASP.NET源码采用经典VB.NET与Access数据库组合完整呈现从前端页面到后台数据库的交互逻辑是ASP.NET初学者熟悉Web Forms开发模式和小型企业搭建在线展示平台的实用范例。压缩包大小约127KB解压即可获得全套网页文件与后台代码整体轻量便于快速部署和学习。已有293人学习下载适合零基础入门或短时间上手项目。通过该源码可掌握VB.NET编写业务逻辑及ADO.NET操作Access数据库的方法理解产品展示、购物车、订单管理、用户注册登录、后台管理等典型电商模块的构建思路并学习GridView、DetailsView等数据控件及母版页在真实站点中的应用方式。这份代码能为毕业设计、课程实训或小型网站二次开发提供直接参考帮助快速搭建一套功能完整的汽车配件展示与订购系统。1. 为什么还在学 ASP.NET Web Forms一个 VB.NET Access 配件站点的价值手头这份源码是一个用 VB.NET 写后台、Access 存数据的汽车配件公司网站。放在今天看ASP.NET Web Forms 确实不是新项目首选但如果你需要维护 2010 到 2018 年间大量遗留系统或者想搞懂“服务器控件 事件回发”到底怎么工作这套源码比任何教程都直接。它把产品展示、购物车、订单、后台管理这些电商基本动作全部用最传统的 Web Forms 方式实现了一遍。没有前后端分离没有 ORM连数据库都是单文件 Access反而让你看清楚一个动态网站从请求到响应的完整链路。适合两类人刚接触 .NET 生态、想理解 Web Forms 事件模型的初学者以及被派去接手老项目的开发。你能从里面学到的不是“怎么写更好”而是“当年这套东西为什么能跑起来”。2. 项目结构拆解VB.NET 后置代码与 Access 数据访问层2.1 输入源码后先看什么解压这份 ASP.NET 源码后不要急着双击解决方案文件。先打开根目录确认三块东西是否存在.aspx页面文件、.aspx.vb后置代码文件、.mdb或.accdb数据库文件。典型布局是每个业务页面一个文件夹比如Products/、Cart/、Order/、Admin/再加上App_Data/放 Access 文件App_Code/或App_Code/*.vb放公共类。文件/目录作用需要注意的点Default.aspx首页入口往往用母版页嵌套看MasterPageFile属性App_Data/*.accdbAccess 数据库默认不会被下载IIS 需要设置读取权限*.aspx.vb后置代码事件处理、数据绑定、业务逻辑都在这Web.config配置连接串重点看connectionStrings和compilationApp_Code/公共类/工具类如数据库帮助类看是否有 SqlHelper 类似封装许多老源码把业务逻辑写在Page_Load和按钮事件里比如btnLogin_Click。这是 Web Forms 的典型写法也和今天 MVC 的控制器 Action 处理思路对应——每个后台事件就是一个处理入口。先花十分钟找清这些入口后面改代码才不迷路。2.2 ADO.NET 连接 Access 的两种标准写法这个项目的核心数据访问手段是 ADO.NET。连接 Access 有两种常见驱动老式的Microsoft.Jet.OLEDB.4.0对应.mdb文件新版Microsoft.ACE.OLEDB.12.0同时支持.mdb和.accdb。源码里出现的连接串大概率是下面这种Dim connStr As String ConfigurationManager.ConnectionStrings(AccessDB).ConnectionString Dim conn As New OleDbConnection(connStr) Dim cmd As New OleDbCommand(SELECT ProductID, ProductName, Price, Stock FROM Products WHERE CategoryID cid, conn) cmd.Parameters.AddWithValue(cid, Request.QueryString(CategoryID)) Dim adapter As New OleDbDataAdapter(cmd) Dim dt As New DataTable() adapter.Fill(dt) GridView1.DataSource dt GridView1.DataBind()逻辑说明从Web.config读连接串创建OleDbConnection用带参数的OleDbCommand执行查询然后通过DataAdapter填充到DataTable最后绑定到GridView。这里用了cid参数而不是拼接字符串说明作者至少考虑了最基本的 SQL 注入防护。如果源码中直接写WHERE CategoryID Request.QueryString(CategoryID)那你要注意这是教学级代码生产环境必须改成参数化写法。参数说明AddWithValue的第一个参数是参数名必须与 SQL 里的占位符一致第二个参数是值如果 QueryString 为空要提前做Integer.TryParse校验否则 Access 驱动可能报类型转换异常。另外OleDbCommand不支持命名参数重复利用两条 SQL 同一参数名也要分别添加。2.3 GridView、DetailsView、FormView 的用法差异源码里最常出现的三个服务器控件分工很明确。GridView负责列表展示自带分页、排序、编辑、删除模板DetailsView展示单条记录的完整字段通常配合GridView的选中行使用FormView更自由完全自定义布局适合商品详情页这种需要图片和描述混排的场景。下面是从列表页跳转到详情页的常见实现。列表页GridView的RowDataBound事件里加一个跳转链接Protected Sub GridView1_RowDataBound(sender As Object, e As GridViewRowEventArgs) If e.Row.RowType DataControlRowType.DataRow Then Dim id As String DataBinder.Eval(e.Row.DataItem, ProductID).ToString() Dim lnk As New HyperLink() lnk.Text 查看详情 lnk.NavigateUrl ProductDetail.aspx?ID id e.Row.Cells(0).Controls.Add(lnk) End If End Sub逻辑说明每绑定一行数据就取该行的ProductID用HyperLink动态生成一个指向详情页的链接。这样点击不是表单回发而是普通 GET 请求刷新或收藏 URL 都不会丢状态比直接用按钮再Response.Redirect更稳妥。参数说明e.Row.DataItem只有在DataRow类型时才存在所以必须先判断RowType否则 Header 行会触发空引用异常。Eval方法反射取字段值性能比DataRowView[ProductID]慢一些但在数据量不超过千级时没有感知差异。3. 核心业务模块实现产品展示、购物车、订单与用户登录3.1 产品分类浏览与搜索这个配建网站的产品展示模块基本是按分类导航加关键词搜索展开。分类列表通常用Repeater或DropDownList绑定到一个分类表点击分类后通过QueryString传分类编号到产品列表页。搜索则是在产品列表页顶部放一个TextBox和一个按钮点击后拼接LIKE条件。Dim sql As String SELECT * FROM Products WHERE 11 If Not String.IsNullOrEmpty(txtKeyword.Text.Trim()) Then sql AND (ProductName LIKE kw OR ProductCode LIKE kw) End If If Request.QueryString(CategoryID) IsNot Nothing Then sql AND CategoryID cid End If Dim cmd As New OleDbCommand(sql, conn) cmd.Parameters.AddWithValue(kw, % txtKeyword.Text.Trim() %) cmd.Parameters.AddWithValue(cid, Convert.ToInt32(Request.QueryString(CategoryID)))逻辑说明用11开头是为了方便后续无条件地追加AND这是动态 SQL 的常见技巧。先判断搜索框是否为空再判断 URL 是否带分类参数分别加上过滤条件。注意LIKE的占位符传参时要把%包含在参数值里而不是拼进 SQL 字符串否则参数化就失去意义。参数说明拼接条件时cmd.Parameters的顺序必须和 SQL 中的出现顺序一致Access 的 OleDb 驱动对参数顺序敏感不像 SQL Server 那样可以按名字匹配。这是一个很容易踩的坑调试时发现“明明参数名没错却报参数太少”时先检查顺序。3.2 购物车与 Session 状态管理购物车实现不依赖数据库而是存在Session里这是 Web Forms 最典型的做法。源码里通常有一个ShoppingCart类放在App_Code下用DataTable或List(Of CartItem)保存用户选择的商品。每次加入购物车流程是从 Session 取出当前购物车对象如果不存在就新建然后遍历检查是否已有同一商品有则加数量没有则新增行。Public Shared Function AddItem(cart As DataTable, productId As Integer, qty As Integer) As DataTable Dim row As DataRow cart.AsEnumerable().FirstOrDefault(Function(r) CInt(r(ProductID)) productId) If row Is Nothing Then Dim newRow As DataRow cart.NewRow() newRow(ProductID) productId newRow(Quantity) qty cart.Rows.Add(newRow) Else row(Quantity) CInt(row(Quantity)) qty End If Return cart End Function逻辑说明这里用 LINQ to DataTable 查重复项FirstOrDefault找到第一个匹配行。找不到就新建DataRow找到就把原有数量累加。三行逻辑解决购物车最核心的问题。注意cart是引用类型直接在方法内修改后调用方持有的对象已经变化所以返回值其实是多余的但保留它可以让调用方更明确知道购物车已被更新。参数说明AsEnumerable()需要引用System.Data.DataSetExtensions。FirstOrDefault的Function是 VB 的 Lambda 表达式。如果项目运行在 .NET 2.0这段代码会编译失败需要改用Select(ProductID productId)的方式。购物车页面计算总价时通常会遍历DataTable中的每一行到Products表查单价然后Multiply加总。这是比较低效的 N1 查询但数据量小的时候完全够用。如果你想优化可以先把所有ProductID拼成IN子句一次查回来。3.3 用户注册登录与权限判断登录模块大多是Login.aspx页面提交后到Users表验证用户名和密码。老的源码密码可能明文存储这是安全隐患。你先看看数据库字段如果Password字段长度是 50 或 64可能存的是哈希如果只有 20 且是文本那就是明文。Protected Sub btnLogin_Click(sender As Object, e As EventArgs) Handles btnLogin.Click Dim sql As String SELECT UserID, UserName, Role FROM Users WHERE UserNamename AND Passwordpwd Using conn As New OleDbConnection(connStr) Using cmd As New OleDbCommand(sql, conn) cmd.Parameters.AddWithValue(name, txtUserName.Text.Trim()) cmd.Parameters.AddWithValue(pwd, txtPassword.Text) conn.Open() Dim reader As OleDbDataReader cmd.ExecuteReader() If reader.Read() Then Session(UserID) reader(UserID).ToString() Session(UserName) reader(UserName).ToString() Session(Role) reader(Role).ToString() Response.Redirect(Default.aspx) Else lblMsg.Text 用户名或密码错误 End If End Using End Using End Sub逻辑说明Using块保证连接和命令在结束后自动释放。ExecuteReader返回只进流式结果reader.Read()返回True表示有匹配记录。登录成功后把用户信息写入Session后续页面通过判断Session(UserID)是否为空来决定是否展示“欢迎 XXX”或跳转登录页。这里有个隐患密码明文对比如果Password字段是明文任何拿到数据库的人直接就能登录。参数说明name和pwd都用参数化传入避免引号拼进 SQL。注意验证码和防暴力破解在这个教学项目里通常没有你要在生产环境补上。密码存储应改为BCrypt或至少SHA256加盐验证时先Hash再比对。后台页面Admin/ProductManage.aspx的Page_Load里通常有一段权限检查If Session(Role) IsNot Nothing AndAlso Session(Role).ToString() Admin Then 正常加载 Else Response.Redirect(~/Login.aspx?returnUrlAdmin/ProductManage.aspx) End If逻辑说明AndAlso是 VB 的短路运算符前一个条件为False时不去读Session(Role).ToString()避免Nothing引用异常。后台页面的鉴权必须放在页面生命周期的早期Page_Load里可以但更严谨的是在Page_Init或重写OnPreInit因为控件事件在Page_Load之后触发如果权限不足提前跳转后面的编辑事件就不该执行。3.4 后台管理中的库存更新后台管理商品时GridView 启用编辑功能RowUpdating事件里取新值并执行 UPDATE。一个常见写法是Protected Sub GridView1_RowUpdating(sender As Object, e As GridViewUpdateEventArgs) Handles GridView1.RowUpdating Dim id As Integer Convert.ToInt32(GridView1.DataKeys(e.RowIndex).Value) Dim tbName As TextBox CType(GridView1.Rows(e.RowIndex).FindControl(txtProductName), TextBox) Dim tbPrice As TextBox CType(GridView1.Rows(e.RowIndex).FindControl(txtPrice), TextBox) Dim sql As String UPDATE Products SET ProductNamename, Priceprice WHERE ProductIDid 执行更新 GridView1.EditIndex -1 BindData() End Sub逻辑说明DataKeys是 GridView 用来保存主键值的集合必须在配置里设置DataKeyNamesProductID否则取不到更新行的 ID。FindControl在模板列中找文本框控件因为编辑模板里控件 ID 是运行时生成的不能用固定 ID 直接引用。更新完成后要把EditIndex设为 -1退出编辑状态再重新绑定数据。参数说明GridViewUpdateEventArgs里的e.NewValues也可以取新值但它是Hashtable键名对应字段名。用模板控件取值的优势是可以做更复杂的文本处理比如价格字符串转Decimal。注意Convert.ToInt32处理主键时如果数据库字段是自增 Long要用Convert.ToInt64。4. 部署与排错IIS 配置、Access 数据库权限和常见异常4.1 Web.config 里的连接串格式拿到的源码里Web.config的connectionStrings节点大约是这样connectionStrings add nameAccessDB connectionStringProviderMicrosoft.Jet.OLEDB.4.0;Data Source|DataDirectory|CarParts.mdb;Persist Security InfoTrue providerNameSystem.Data.OleDb / /connectionStrings|DataDirectory|是 ASP.NET 的特殊占位符指向站点的App_Data文件夹。默认情况下App_Data下的数据库文件只能通过这个相对路径访问IIS 不直接向外提供该目录的静态文件请求相对安全。如果你把数据库放到根目录别人就可以通过http://yourdomain/xxx.mdb直接下载这是安全事故。逻辑说明Persist Security InfoTrue表示连接成功后保留密码信息通常没必要建议改成False。Jet 驱动是 32 位如果你的应用池是 64 位会报未在本地计算机上注册 Microsoft.Jet.OLEDB.4.0 提供程序需要按下一节处理。参数说明Data Source也可以用物理路径比如D:\WebSites\CarParts\App_Data\CarParts.mdb但这样部署到其他服务器就要改配置不如|DataDirectory|方便。另外 Access 驱动不支持MultipleActiveResultSets连接串里加了会报错。4.2 64 位系统下 Jet 与 ACE 驱动选型Windows 64 位系统默认没有Microsoft.Jet.OLEDB.4.0驱动只有 32 位 Office 才自带。IIS 应用池默认 64 位运行直接运行 Jet 会崩溃。解决办法不是换服务器而是改用Microsoft.ACE.OLEDB.12.0并设置应用池。连接串改成add nameAccessDB connectionStringProviderMicrosoft.ACE.OLEDB.12.0;Data Source|DataDirectory|CarParts.accdb;Persist Security InfoFalse providerNameSystem.Data.OleDb /同时要在 IIS 管理器中找到你的网站的应用池右键高级设置把“启用 32 位应用程序”设为 True。注意这是让进程以 x86 模式运行Jet 驱动才能在 64 位系统上加载。如果用了 ACE 2010 64 位驱动则应用池可以保持 64 位但要确保安装的是 64 位版本且 Web 项目目标平台为 AnyCPU 或 x64。驱动适用文件位数安装包Microsoft.Jet.OLEDB.4.0.mdb32位系统自带/随旧版 OfficeMicrosoft.ACE.OLEDB.12.0.mdb/.accdb32/64位Microsoft Access Database Engine 2010 RedistributableMicrosoft.ACE.OLEDB.16.0.mdb/.accdb32/64位Access 2016 Runtime 附带一般来说下载安装 AccessDatabaseEngine_X64.exe然后应用池不用改位数连接串用 ACE.12.0 即可。如果页面报错说提供程序未注册先确认安装的是 64 位再检查有没有装错 32 位版本导致注册表冲突。4.3 常见的运行时异常与排查方向在浏览器里浏览这套源码最常遇到三类异常。第一是未找到提供程序。该程序可能未正确安装解决方案就是上面说的驱动问题或连接串的Provider拼错。第二是操作必须使用一个可更新的查询原因大多是 Access 数据库文件所在目录对 IIS 的工作进程没有写权限。报错发生在“加入购物车”“提交订单”“后台修改库存”这类写操作时。处理方法找到App_Data目录右键属性-安全给IUSR或Everyone至少写权限。生产环境可以给IIS_IUSRS修改权限。第三是Microsoft JET Database Engine 错误 80040e14一般在执行 UPDATE 语句时出现多为数据表字段名错误或 SQL 语法不符合 Access 标准。Access 的模糊查询要用*而不是 SQL Server 的%但LIKE参数化时参数里的%依然有效。注意 Access 的保留字如Password、Level、Date作为字段名时必须加上方括号[Password]否则也会报语法错误。建议把数据库里的密码字段改名为UserPwd以彻底避开。还有一类是类型不匹配因为 OLEDB 对数据类型的推断很严格。比如参数传入TextBox的字符串而表里字段是数字驱动会尝试自动转换一旦值里有空格或非数字字符就会报错。所以通常要先用TryParse或CInt()明确转换后再传参。4.4 ViewState 与安全配置思路ASP.NET Web Forms 把页面状态存在__VIEWSTATE隐藏字段里默认不加密。有些人会通过这个字段构造攻击项目里如果看到ViewStateUserKey未设置建议在页面或Web.config中打开 MAC 验证。配置如下system.web machineKey validationHMACSHA256 validationKeyAutoGenerate decryptionAES / pages viewStateEncryptionModeAlways / /system.webviewStateEncryptionModeAlways强制加密 ViewState代价是页面体积变大回发速度稍慢。对于这个配件网站后台管理页面建议开启前台产品页可以不开。另外一个安全点是防 CSRFWeb Forms 内置的__VIEWSTATE 事件验证提供一定防护但最好在敏感操作中再次校验来源。这种老项目没有防伪令牌机制你可以用Session加一个随机 Token写入HiddenField提交时比较是否一致。5. 把示例改造成可维护的项目分层、参数化与前端解耦5.1 从内联 SQL 到数据访问层源码里的 SQL 大部分写在aspx.vb中每页面重复连接、命令、DataAdapter。改造第一步就是抽出一个DbHelper.vb类封装 ExecuteQuery 和 ExecuteNonQuery 两个方法Public Class DbHelper Private Shared ReadOnly _connStr As String ConfigurationManager.ConnectionStrings(AccessDB).ConnectionString Public Shared Function Query(sql As String, ParamArray params As OleDbParameter()) As DataTable Using conn As New OleDbConnection(_connStr) Using cmd As New OleDbCommand(sql, conn) If params IsNot Nothing Then cmd.Parameters.AddRange(params) End If Dim adapter As New OleDbDataAdapter(cmd) Dim dt As New DataTable() adapter.Fill(dt) Return dt End Using End Using End Function Public Shared Function Execute(sql As String, ParamArray params As OleDbParameter()) As Integer Using conn As New OleDbConnection(_connStr) Using cmd As New OleDbCommand(sql, conn) If params IsNot Nothing Then cmd.Parameters.AddRange(params) End If conn.Open() Return cmd.ExecuteNonQuery() End Using End Using End Function End Class逻辑说明ParamArray允许调用方直接传多个参数对象不用先构造数组。Query负责查询返回DataTableExecute执行增删改返回影响行数。所有连接都用Using保证关闭避免连接泄漏。调用方式的差别可以从下面两行对比看出。改造前Dim conn As New OleDbConnection(connStr) Dim cmd As New OleDbCommand(SELECT * FROM Products, conn)改造后Dim dt As DataTable DbHelper.Query(SELECT * FROM Products WHERE CategoryIDcid, New OleDbParameter(cid, cid))参数说明Execute方法返回的是受影响的记录数而不是你设定的某个值。如果要取新插入的自增 IDAccess 不直接支持OUTPUT只能用独立的 ID 字段添加逻辑或使用Select Identity。5.2 用参数化消除 SQL 拼接在源码里搜索所有 txt和 Request这样的字符串拼接凡出现在 SQL 文本中的都要替换为参数。比如后台的删除操作Dim sql As String DELETE FROM Products WHERE ProductID Request.QueryString(id)改为Dim sql As String DELETE FROM Products WHERE ProductIDid Dim dt As DataTable DbHelper.Query(sql, New OleDbParameter(id, Convert.ToInt32(Request.QueryString(id))))这不是为了炫技而是很多配送站源码被注入的直接原因。Request.QueryString(id)如果被拼进命令攻击者传入1; DROP TABLE Products虽然 Access 不支持堆叠查询但1 OR 11就能删掉全部产品。参数化后驱动会把值当作纯数据而不是 SQL 命令。5.3 用母版页统一布局并减少重复代码源码里如果每个页面都自己写了html、head、菜单导航说明没有使用母版页。改用母版页的思路是把页头、菜单、页脚放到Site.Master每个内容页只需要声明Content控件。以导航菜单为例在母版页中写一个SiteMapDataSource绑定服务器菜单控件数据源来自Web.sitemapasp:SiteMapDataSource IDSiteMapDataSource1 runatserver / asp:Menu IDMenu1 runatserver DataSourceIDSiteMapDataSource1 /逻辑说明Web.sitemap是一个 XML 文件按站点结构定义导航节点。SiteMapDataSource读取它Menu控件自动渲染成树形菜单。这样做的好处是新增页面时只需在sitemap里加一行所有页面的导航自动更新。另外母版页里定义公共的 CSS 文件和 JavaScript 引用页面代码会干净很多。5.4 迁移到 ASP.NET Core 的路线与注意事项如果你在考虑把这个 VB.NET Web Forms 项目迁到 ASP.NET Core先别急着逐行翻译。Web Forms 的事件回发模型与 Core 的中间件和 Razor Pages 有本质区别。建议分三步走先把App_Code里的公共代码类编译成类库项目测试业务逻辑完全独立于 UI。将aspx页面按页面作用拆成 Razor Page 或 MVC View把Page_Load里的逻辑迁移到OnGet/OnPost方法中。数据访问层从OleDb换成System.Data.Odbc或继续留在旧项目里提供 API 接口新页面通过 HTTP 调用。迁移过程中Access 数据库可以继续保留但连接驱动推荐改用Microsoft.Data.Odbc配合 ACE 驱动。如果你还希望避开外置驱动可以选择把数据迁到 SQL Server Express。最后一个提醒是网页里的服务器控件如GridView在 Core 中不存在你需要熟悉循环渲染或多用前端表格或者选择 Blazor 的表格组件来做视图层。那样这套源码的开发经验和数据表设计思路就能直接延续到新技术栈中。本文还有配套的精品资源点击获取