本文源自 ASP.NET 1.x / .NET Framework 1.0 時代 (Web Forms、.aspx、內聯代碼、`csc.exe` 命令行編譯)。**現代開發請直接使用 ASP.NET Core + Razor Pages / MVC / Minimal APIs + Entity Framework Core**。本文保留為架構演進參考,核心思想 (分層、組件化、參數化查詢、數據綁定) 至今有效。
┌─────────────────────────────────────┐
│ Presentation Layer (UI) │ ← Default.aspx, ProductList.aspx
│ (Web Forms / ASPX + CodeBehind) │
└──────────────┬──────────────────────┘
│ 僅調用業務組件
┌──────────────▼──────────────────────┐
│ Business Logic Layer (BLL) │ ← CommerceDotNet.dll
│ Category.cs, Product.cs (組件) │
└──────────────┬──────────────────────┘
│ 僅調用數據訪問 / 存儲過程
┌──────────────▼──────────────────────┐
│ Data Access Layer (DAL) │ ← 存儲過程
│ SQL Server + Stored Procedures │
└─────────────────────────────────────┘
// CategoryDB.cs
namespace CommerceDotNet {
using System.Data;
using System.Data.SqlClient;
using System.Configuration;
public class Categories {
public DataSet CategoryList() {
using (var conn = new SqlConnection(
ConfigurationManager.ConnectionStrings["ConnStr"].ConnectionString))
using (var cmd = new SqlCommand("sp_GetCategories", conn))
using (var da = new SqlDataAdapter(cmd)) {
cmd.CommandType = CommandType.StoredProcedure;
var ds = new DataSet();
da.Fill(ds);
return ds;
}
}
}
}
// ProductDB.cs
namespace CommerceDotNet {
public class Products {
public DataSet ProductsList(int categoryId) {
using (var conn = new SqlConnection(...))
using (var cmd = new SqlCommand("sp_GetProductsByCategory", conn))
using (var da = new SqlDataAdapter(cmd)) {
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("@CategoryId", categoryId);
var ds = new DataSet();
da.Fill(ds);
return ds;
}
}
}
}
csc /target:library /out:bin/CommerceDotNet.dll /reference:System.Data.dll \
CategoryDB.cs ProductDB.cs
現代專案:`dotnet build` 輸出 DLL,NuGet 管理依賴,`project.json` / `.csproj` 替代命令行參數。
<%@ Page Language="C#" %>
<%@ Import Namespace="CommerceDotNet" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Collections" %>
產品類別
<%# DataBinder.Eval(Container.DataItem, "CategoryName") %>
| 原則 | 經典做法 | 現代對應 |
|---|---|---|
| **參數化查詢** | `cmd.Parameters.AddWithValue("@Id", id)` | EF Core 參數化 / Dapper 參數化 / 原生 `SqlParameter` |
| **存儲過程封裝業務邏輯** | T-SQL `sp_GetProductsByCategory` | 視複雜度決定:EF Core LINQ / 存儲過程 / View / Function |
| **連接池與 using** | `using (var conn = new SqlConnection())` | EF Core `DbContext` 池化 / `SqlConnection` 池化 (預設開啟) |
| **分層解耦** | UI → BLL DLL → SP | Controller/Service/Repository 或 Minimal API + MediatR |
| **數據綁定模板** | `<%# Eval("Field") %>` / `DataBinder.Eval` | Razor `@item.Field` / Blazor `@item.Field` / Vue/React Props |
| **配置分離** | `Web.config` ` | `appsettings.json` + `IConfiguration` + User Secrets / Key Vault |
CREATE PROCEDURE sp_GetCategories
AS
SELECT CategoryId, CategoryName, Description FROM Categories ORDER BY CategoryName;
CREATE PROCEDURE sp_GetProductsByCategory
@CategoryId INT
AS
SELECT ProductId, ProductName, UnitPrice, UnitsInStock
FROM Products
WHERE CategoryId = @CategoryId AND Discontinued = 0
ORDER BY ProductName;
// 經典:DataSet + DataTable + DefaultView
DataSet ds = bll.GetData();
grid.DataSource = ds.Tables[0].DefaultView;
grid.DataBind();
// 現代 (EF Core + Razor Pages):
public class IndexModel : PageModel {
private readonly AppDbContext _db;
public List Products { get; set; }
public async Task OnGetAsync(int categoryId) {
Products = await _db.Products
.Where(p => p.CategoryId == categoryId && !p.Discontinued)
.OrderBy(p => p.Name)
.ToListAsync();
}
}
// Razor 頁面
@foreach (var p in Model.Products) {
@p.Name @p.Price.ToString("C")
}
雖然 Web Forms、DataSet、內聯腳本、命令行編譯已成歷史,但**分層架構、參數化查詢、存儲過程封裝、聲明式數據綁定、配置外部化**這些核心工程思想穿越版本長存。理解經典有助於洞察現代框架設計初衷,避免重複造輪子。