一个精益求精的博客引擎,第二部分






4.35/5 (20投票s)
2007年2月27日
20分钟阅读

74130

499
这是我三天内编写博客引擎系列文章的第二部分。
这是两部分系列文章的第二部分。
引言
首先,您可以在 这里 实时查看该博客。
以下是一篇“有态度”的文章,关于使用 .NET 2.0 构建一个基本的博客引擎。
我为什么要写一个博客引擎?因为我从未找到过我喜欢的。它们要么
- 需要我不愿意学习或支持的平台或提供商(我运行一台配备 W2003 Web Edition 和 IIS 的专用服务器)
- 它们有 bug
- 它们过于复杂
- 它们对演示文稿做出了假设
- 几乎不可能定制
- 需要我不愿意添加到服务器的数据库
而且因为我是一个特立独行的人,我喜欢做自己的事情。我试过 DotText,它笨拙且过时。我试过 SubText,但我无法弄清楚如何更改简单的东西,比如边距的宽度。其他的博客引擎呢?算了吧。我甚至无法弄清楚它们的网站,无法了解它们的要求或如何安装它们。
令人惊讶的是,CodeProject 上没有任何关于博客引擎的文章。所以这是第一篇(假设在接下来的几天里没有发布其他文章)。
买家请注意
我可能会在这篇文章中多次提到这一点,但我认为这是第一次:我对 Web 应用程序相当不了解。所以我这样做是为了学习如何不写 Web 应用程序,因为我确信我将从中了解到更多“如何不写”而不是“如何写”。因此,这也是一次进入精彩的 Web 应用程序世界之旅,以及我在编写一个本应简单的应用程序时发现的东西。您应该知道,如果您使用此代码(我无法想象有人真的想使用它),这很可能是做事的错误方式。
我们进行到哪儿了?
在第一部分中,我已经让基本引擎工作起来了,具备了一些样式功能(我自己的,不是 CSS)以及单击博客条目并返回主页的功能。在本文中,我将涵盖:
- 限制从数据库检索的博客条目数
- 添加归档列表
- 添加类别
- 添加菜单
限制博客条目数
对于普通列表,我想将显示的博客条目数限制为,比如 10 条。嗯。让我们将此数字放入数据库的 BlogInfo 表中。在向数据库添加“MaxEntries”字段后,我只需将该属性添加到 BlogInfo 类中
protected long maxEntries; public long MaxEntries { get { return maxEntries; } set { maxEntries = value; } }
我的穷人 ORM 方法将正确地填充它。现在,如何限制从 SQLite 检索的行数?嗯。SELECT 语句有一个“Limit”关键字
public static DataTable LoadBlogEntries(SQLiteConnection conn, string id, long max) { SQLiteCommand cmd = conn.CreateCommand(); if (id == null) { cmd.CommandText = "select * from BlogEntry order by Date desc limit "+max.ToString(); } else { cmd.CommandText = "select * from BlogEntry where ID=@id"; cmd.Parameters.Add(new SQLiteParameter("id", id)); } SQLiteDataAdapter da = new SQLiteDataAdapter(cmd); DataTable dt = new DataTable(); da.Fill(dt); return dt; }
看起来有效。
Archives(归档)
数据库部分
归档应显示每个至少有一个博客条目的月份/年份的链接,括号中显示条目数。这部分最困难的不是 ASP.NET,而是弄清楚 SQLite 中一个优雅的查询。似乎适用于我的查询是 SELECT strftime('%Y-%m', date) as Date, count(strftime('%Y-%m', date)) as Num from blogentry group by strftime('%Y-%m', date) order by strftime('%Y-%m', date) desc
。请注意,我想要反向时间顺序的列表。我为结果行创建了一个辅助类
public class ArchiveEntry { protected string date; protected long num; public long Num { get { return num; } set { num = value; } } public string Date { get { return date; } set { date = value; } } public ArchiveEntry() { } }
ASP.NET 部分
我将把归档放在右侧边栏(是的,目前这是硬编码的),将类别放在左侧边栏。因此,我需要右侧边栏单元格来创建列出归档条目的表,这只需将 cellArchive 分配给 ID 标记即可。趁此机会,我也将 cellCategory 分配给左侧单元格。我在测试时看到的问题是,归档列表在页面上垂直居中,而不是像我想要的那样对齐在顶部。设置单元格的 VerticalAlign 属性解决了这个问题。归档还需要是提供查询该月份博客条目信息的链接。
现在我有了这个用于添加归档表的调用方法
protected void LoadArchives() { // Set the style for the entire cell. SetStyle(cellArchive, "CellArchive"); // Get the archive list. DataTable dtArchives = Blog.LoadHistory(conn); // Create a table for the cell. Table tblArchive = new Table(); // Set the style for the table. SetStyle(tblArchive, "ArchiveTable"); cellArchive.Controls.Add(tblArchive); // For each archive listing... foreach (DataRow row in dtArchives.Rows) { // Create the helper class. ArchiveEntry archive = new ArchiveEntry(); // Load it up. Blog.LoadProperties(archive, row); // Create a row. TableRow rowArchive = new TableRow(); // Set the row style. SetStyle(rowArchive, "ArchiveRow"); tblArchive.Rows.Add(rowArchive); // Create a cell. TableCell tblCellArchive = new TableCell(); // Set the cell style. SetStyle(tblCellArchive, "ArchiveEntry"); rowArchive.Cells.Add(tblCellArchive); // Get the date. DateTime dt = Convert.ToDateTime(archive.Date); // Convert it to our display format (hardcoded!). string text = dt.ToString("MMMM yyyy") + " (" + archive.Num.ToString() + ")"; // Set the cell text. tblCellArchive.Text = "<a href=\"" + url + "?Year=" + dt.Year + "&Month=" + dt.Month + "\">" + text + "</a>"; } }
并且结果,在添加一些样式信息后,看起来像
还不错!而且看,我在未来写博客!现在我只需要处理点击链接时的参数。我们尝试获取参数
... string year = Request.QueryString["Year"]; string month = Request.QueryString["Month"]; string archiveDate = null; if ((year != null) && (month != null)) { archiveDate = year + "-" + month.PadLeft(2, '0'); }
以及 LoadBlogEntries 查询中的
... else if (archiveDate != null) { cmd.CommandText = "select * from BlogEntry where strftime('%Y-%m', date)==@archiveDate order by Date desc"; cmd.Parameters.Add(new SQLiteParameter("archiveDate", archiveDate)); }
分类
在第一部分中我是否说过一个博客条目将有一个或多个类别?错了!一个博客条目可以有一个或零个类别。我很懒,我现在只想处理一个类别。否则,我需要另一个表来管理博客条目和零个或多个类别之间的一对多关系。
数据库部分
类别表已存在,我只需要我的小辅助类。您会注意到我添加了一个 Num 属性,因为我希望类别像归档一样——显示类别中的帖子数量。
public class Category { protected long id; protected string name; protected string description; protected long num; .. etc ...
查询是 select a.id as Id, a.name as Name, count(b.id) as Num from blogcategory a, blogentry b where a.id=b.categoryid group by a.name order by a.name
。
结果(创建一些示例数据后)是
您会注意到我没有把您弄烦 ASP.NET 的细节,因为它们与归档列表基本相同。事实上,代码应该统一起来!
菜单栏
我现在保存了我认为最糟糕的部分。根据“保持简单”的原则,我将只创建表单元格并使用我的样式设置器来确定外观。但我很不情愿硬编码菜单。我宁愿使用数据库生成菜单并让它们链接到页面。
数据库部分
首先,辅助类
public class MenuItem { protected long id; protected long menuBarId; protected string text; protected string page; ... etc ...
然后,数据库 MenuBar 表中的一些默认菜单栏内容
您会注意到我使用一个特殊的博客条目来链接“联系我们”,而不是一个单独的页面。
ASP.NET 部分
我注意到一个令人讨厌的问题是菜单项在页面上按比例间距排列。所以如果我有两个菜单项,第一个在左边,第二个在菜单栏中间。我宁愿它们在页面上均匀分布。我尝试了 ASP.NET Menu 类,WTF!?!?它做同样的事情!!!而且我很好奇为什么。当我查看页面源代码时,它在使用表格!啊!但真正的原因是我将宽度设置为 100%。我打赌我原来在表格中也这样做了。如果我保持宽度不变,看起来还行,只是菜单项太近了。但我认为我会坚持使用 ASP.NET 菜单栏,因为它有很多有趣的特性。菜单项之间的间距由菜单栏的 StaticMenuItemStyle 的 HorizontalPadding 属性设置。找了好久才找到。当然,现在菜单栏的任何背景颜色都只会设置菜单项所覆盖的区域!所以我回到了最初的问题。对此我唯一的解决方案是创建一个 Panel 对象并将菜单栏放在 Panel 中。Panel 延伸到窗口的边界,然后我可以将 Panel 和菜单栏都设置为相同的背景颜色。
动态加载菜单栏很容易
protected void LoadMenuBar(int menuBarId) { menuBar.Items.Clear(); // Set styles for the table elements. SetStyle(menuPanel, "MenuPanel"); SetStyle(menuBar, "MenuBar"); // Get the menu bar. DataTable dtMenus = Blog.LoadMenuBar(conn, menuBarId); // Our helper class. MenuItem item=new MenuItem(); // For each menu bar item... foreach (DataRow row in dtMenus.Rows) { // Load the helper class. Blog.LoadProperties(item, row); menuBar.Items.Add(new System.Web.UI.WebControls.MenuItem( item.Text, null, null, StringHelpers.LeftOfRightmostOf(url, '/') + item.Page)); } }
并设置 Orientation 属性为 horizontal 后,我得到了我想要的
登录和管理
是时候处理登录和基本管理了,这样我就可以摆脱手动编辑数据库行了。首先,我需要在数据库中有一个用户名和密码!默认用户名和密码将是“Admin”。登录页面如果登录有效,会设置一个 IsAdmin 会话键,然后重定向到管理页面
protected void btnLogin_Click(object sender, EventArgs e) { // Go to the admin page if the login validates. if ((tbUsername.Text == blogInfo.Username) && (tbPassword.Text == blogInfo.Password)) { Session["IsAdmin"] = true; Response.Redirect("admin.aspx"); } }
相反,注销页面会将 IsAdmin 键置空,然后重定向到主页
protected void Page_Load(object sender, EventArgs e) { Session["IsAdmin"] = null; Response.Redirect("default.aspx"); }
因为我希望网站具有一致的外观和感觉,所以管理页面使用相同的页眉、菜单和页脚
并且设置与主页基本相同,只是指定了菜单栏 ID 为 1。必须为每个管理页面检查我们是否处于管理模式。
protected void Page_Load(object sender, EventArgs e) { if (Session["IsAdmin"] != null) { url = StringHelpers.LeftOf(Request.Url.ToString(), '?'); conn = (SQLiteConnection)Application["db"]; blogInfo = Blog.LoadInfo(conn); Page.Title = blogInfo.Title; Style.SetStyle(conn, tblHeader, "HeaderTable"); PageCommon.InitializeHeaderAndFooter(conn, url, blogInfo, cellTitle, cellSubtitle, cellCopyright); PageCommon.LoadMenuBar(conn, url, 1, menuBar, menuPanel); } else { Response.Redirect("login.aspx"); } }
由于这种共性,我已将初始化代码移到一个静态类中,因此所有页面的调用都变为
PageCommon.LoadCommonElements(this, -1, out blogInfo, out conn, out url, tblHeader, cellTitle, cellSubtitle, cellCopyright, menuBar, menuPanel);
当然,这需要每个页面的布局都包含页眉、菜单和页脚“部分”。
新博客条目
新博客条目包含一个用于类别、标题和副标题的下拉菜单,以及一个多行文本框,用于粘贴条目的 HTML。如前所述,在线编辑博客条目很笨拙,而且我发现它也有 bug。我想当你在网吧时这很好,但坦率地说,这只占我发博客场景的 0.1%。我宁愿使用一个漂亮的 HTML 编辑器,然后简单地将 HTML 复制粘贴到我的博客上。为什么博客引擎不提供这种更简单的界面,我实在不解。相反,你得到一个富文本框编辑器,它通常需要半分钟才能加载,然后你必须在发布 HTML 之前点击“HTML”按钮。叹气。过于复杂,耗时,点击太多。
ASP.NET 部分
因为我允许在条目中使用 HTML 标签,所以我必须显式地关闭页面验证
您可以通过在 Page 指令或配置部分中将 validateRequest 设置为 false 来禁用请求验证。但是,强烈建议您的应用程序在这种情况下显式检查所有输入。
好吧,既然只有管理员会发帖,我没看到关闭验证有什么问题。我还没有弄清楚如何仅为新博客条目页面正确关闭验证,所以目前,这个
<system.web> <pages validateRequest="false" />
存在于 Web.Config 文件中。当然,这会关闭所有页面的验证,这也不是我想要的。
这是输入博客条目的代码。注意 BlogEntry 辅助类的使用
public partial class NewEntry : System.Web.UI.Page { protected BlogInfo blogInfo; protected SQLiteConnection conn; protected string url; protected void Page_Load(object sender, EventArgs e) { if (Session["IsAdmin"] != null) { PageCommon.LoadCommonElements(this, 1, out blogInfo, out conn, out url, tblHeader, cellTitle, cellSubtitle, cellCopyright, menuBar, menuPanel); if (!IsPostBack) { cbCategory.DataSource = Blog.LoadCategories(conn); cbCategory.DataTextField = "Name"; cbCategory.DataValueField = "ID"; cbCategory.DataBind(); } } else { Response.Redirect("login.aspx"); } } protected void btnPost_Click(object sender, EventArgs e) { BlogEntry blogEntry = new BlogEntry(); blogEntry.CategoryId = Convert.ToInt64(cbCategory.SelectedValue); blogEntry.Date = DateTime.Now; blogEntry.Title = tbTitle.Text; blogEntry.Subtitle = tbSubtitle.Text; blogEntry.BlogText = tbBlogEntry.Text; Blog.SavePost(conn, blogEntry); } }
数据库部分
写记录没什么难的。我使用反射来创建参数列表,这过于简化,并且可能无法按原样重用
public static void SavePost(SQLiteConnection conn, BlogEntry blogEntry) { SQLiteCommand cmd = conn.CreateCommand(); cmd.CommandText = "insert into BlogEntry (Title, Subtitle, CategoryID, Date, BlogText) values (@Title, @Subtitle, @CategoryID, @Date, @BlogText)"; AddParameters(cmd, blogEntry); cmd.ExecuteNonQuery(); } public static void AddParameters(SQLiteCommand cmd, object source) { foreach (PropertyInfo pi in source.GetType().GetProperties()) { cmd.Parameters.Add(new SQLiteParameter(pi.Name, pi.GetValue(source, null))); } }
编辑类别
对于编辑类别,我想尝试使用内置的网格控件。我必须说,使用内置的 GridView 非常糟糕。事实上,我已经花了三个小时试图弄清楚为什么当我编辑一个值时,它不会更新底层表行。我此刻能想到的唯一一件事是,我需要手动将数据移动到底层表,这与我看到的所有示例都背道而驰。然后似乎我必须使用模板来访问单元格中正在编辑的数据。这里有一个动态模板字段的示例 here,但我实在无法相信与 GridView 控件的交互如此困难!另一篇文章 here,展示了当您拥有数据源时,如何通过设计器完成此操作。难以置信。这完全将演示层与数据层纠缠在一起,我宁愿学习如何动态地做到这一点(使用第一个链接中的 G. Mohyuddin 的文章),因为我绝对拒绝用表架构中的信息硬编码我的页面。
最终,我让事情顺利工作了。可能不是最好的方法,甚至不是正确的方法,但它有效,而且它有效,这要归功于 G. Mohyuddin 的例子。
ASP.NET 部分
最有意思的部分是模板字段的创建。因为字段是动态创建的,所以需要大量额外的工作。
protected void CreateTemplatedGridView() { // Must do this so that BoundField columns are replaced with the template // field columns. gvCategories.Columns.Clear(); foreach (DataColumn dc in dtCategories.Columns) { DataControlField dcf = null; if (dc.ColumnName.ToLower() == "id") { BoundField bf = new BoundField(); bf.ReadOnly = true; bf.Visible = false; dcf = bf; } else { TemplateField tf = new TemplateField(); tf.HeaderTemplate = new DynamicallyTemplatedGridViewHandler( ListItemType.Header, dc.ColumnName, dc.DataType.ToString()); tf.ItemTemplate = new DynamicallyTemplatedGridViewHandler( ListItemType.Item, dc.ColumnName, dc.DataType.ToString()); tf.EditItemTemplate = new DynamicallyTemplatedGridViewHandler( ListItemType.EditItem, dc.ColumnName, dc.DataType.ToString()); dcf = tf; } gvCategories.Columns.Add(dcf); } }
有关正在发生的事情的更完整描述,请参阅 here 的文章。我绝对不可能自己弄明白!
RowUpdating 事件处理程序现在可以访问模板字段的 Text 属性,该属性是一个 TextBox。它将数据加载到我的 Category 辅助类中,并将该类传递给“数据访问层”,哈哈,来更新记录。
void OnRowUpdating(object sender, GridViewUpdateEventArgs e) { GridViewRow row = gvCategories.Rows[e.RowIndex]; Category cat = new Category(); DataRow dataRow = dtCategories.Rows[e.RowIndex]; foreach (DataColumn dc in dtCategories.Columns) { TextBox tb = row.FindControl(dc.ColumnName) as TextBox; // Hidden fields return null. if (tb != null) { string val = tb.Text; dataRow[dc] = val; } } Blog.LoadProperties(cat, dataRow); Blog.UpdateCategory(conn, cat); dtCategories.AcceptChanges(); gvCategories.EditIndex = -1; BindData(); }
数据库部分
UpdateCategory 方法使用我小小的反射、穷人的 ORM 工具
public static void UpdateCategory(SQLiteConnection conn, Category cat) { SQLiteCommand cmd = conn.CreateCommand(); cmd.CommandText = "update BlogCategory set mailto:Name=@Name">Name=@Name, Description=@Description where ID=@ID"; AddParameters(cmd, cat); cmd.ExecuteNonQuery(); }
删除和插入类别的代码几乎相同;当然 SQL 不同。这里有一个警告是,我没有实现级联删除,因此删除一个类别不会删除引用该类别的博客条目。
编辑样式
这就是为什么我讨厌 ASP.NET 开发。因为创建 EditStyle 页面的最简单解决方案,无论是在演示文稿和行为方面,都与编辑类别相同,就是复制粘贴该死的代码。呸。好吧,代码,第二版。我以为我可以使用泛型,但由于必须挂钩的 GridView 事件,泛型似乎并非真正可行。代码重用方法唯一的缺点是,调用数据访问层进行更新、插入和删除是特定于方法名称的。有多种解决方法,我选择传递一个字符串来表示正在操作的表(我这样做是为了实现一定程度的一致性,但它仍然是一个糟糕的实现)。
我在一个地方使用了泛型——新记录处理程序
protected void btnNewCategory_Click(object sender, EventArgs e) { GridEdit.NewRecord<Category>(); }
我现在可以创建样式编辑器(一个简单的网格,但目前来说不错),并且整个 Page_Load 变为
protected void Page_Load(object sender, EventArgs e) { if (Session["IsAdmin"] != null) { PageCommon.LoadCommonElements(this, 1, out blogInfo, out conn, out url, tblHeader, cellTitle, cellSubtitle, cellCopyright, menuBar, menuPanel); Session["Conn"] = conn; if (!IsPostBack) { dtStyles = Blog.LoadStyles(conn); Session["Styles"] = dtStyles; } else { dtStyles = (DataTable)Session["Styles"]; } GridEdit.PageLoad(gvStyles, dtStyles, "StyleRecord"); } else { Response.Redirect("login.aspx"); } }
在我看来,这比在页面之间复制粘贴 99.9% 相同的代码要可重用得多,然后是下一页,依此类推。
因为我的穷人 ORM/反射方法已经与业务对象一起工作,而不是硬编码的类类型,所以数据访问层在转换为通用 GridEdit 助手时所需的工作量很小。例如,NewRecord(以前是 NewCategory)现在看起来像
public static long NewRecord(SQLiteConnection conn, object rec) { SQLiteCommand cmd = conn.CreateCommand(); switch (rec.GetType().Name) { case "Category": cmd.CommandText = "insert into BlogCategory (Name, Description) values (@Name, @Description);select last_insert_rowid() AS [ID]"; break; case "StyleREcord": cmd.CommandText = "insert into Style (CellType, PropertyName, FontPropertyName, Value) values (@CellType, @PropertyName, @FontPropertyName, @Value);select last_insert_rowid() as [ID]"; break; } AddParameters(cmd, rec); long id = Convert.ToInt64(cmd.ExecuteScalar()); return id; }
现在,这至少是关于处理功能几乎相同但仅数据不同的页面时,如何进行 ASP.NET 编程的一个开端!
博客设置 UI
既然通用网格编辑器已经工作了,这变得很简单。我将使用相同的机制来编辑博客设置字段,只是它没有添加新记录的按钮,因为博客只有一个设置行。而且,不允许删除记录。五分钟后
该页面已全部完成。
编辑博客条目
自言自语…
博客条目也是如此。大约花了 5 分钟创建。现在,我将变得非常懒。博客条目包含一个类别 ID,目前显示为整数。当然,显示下拉列表会更好。我以后会做的。我能想象要弄清楚如何用模板字段实现这一点还需要我三个小时。我敢打赌你一定在想:“哦,这比 Marc 的网格重用方法要好!”没那么快。我计划使用属性来装饰属性,以便我可以为属性关联一个下拉列表。
另一个问题是,在未编辑时,博客条目会完整显示在一个巨大的文本框中。这很快就会使列表变得笨拙。当你去编辑博客条目时,你得到一个很小的文本框。我可以接受,因为我会将原始条目复制到 HTML 编辑器中,然后粘贴更改。
当我想到这一点时,与其显示一个网格,不如在管理模式下选择博客来编辑博客条目会更简单。我更喜欢这个主意!这样我就不必处理属性、巨大的文本框、分页等了。哇!即时设计 Web 应用程序!各位,不要在家这样做!
好的,为了支持此功能,我添加了一个隐藏的 ID 和日期字段,并根据用户是否处于管理模式以及是否正在编辑条目来添加按钮可见性逻辑。更新和删除方法很简单。注意删除条目时重定向到主页,更新条目时重定向并附带条目 ID。
protected void btnUpdate_Click(object sender, EventArgs e) { BlogEntry blogEntry = new BlogEntry(); blogEntry.Id = Convert.ToInt64(tbID.Text); blogEntry.CategoryId = Convert.ToInt64(cbCategory.SelectedValue); blogEntry.Date = Convert.ToDateTime(tbDate.Text); blogEntry.Title = tbTitle.Text; blogEntry.Subtitle = tbSubtitle.Text; blogEntry.BlogText = tbBlogEntry.Text; Blog.UpdateRecord(conn, blogEntry); Response.Redirect("default.aspx?ID="+blogEntry.Id.ToString()); } protected void btnDelete_Click(object sender, EventArgs e) { BlogEntry blogEntry = new BlogEntry(); Blog.DeleteRecord(conn, "BlogEntry", Convert.ToInt64(tbID.Text)); Response.Redirect("default.aspx"); }
页面标题
嗯。我刚刚注意到页面标题,就像它在 Firefox 标签页中显示的那样,“未标题页面”。呸!在 Page_Load 方法中的一行代码就可以解决这个问题!
Page.Title = blogInfo.Title;
RSS
好了,这是要解决的下一个大问题。让 RSS 链接工作,以便我的博客可以与聚合器一起使用。Wikipedia 有一些关于 RSS 格式 的信息,我想我会尝试 RSS 2.0 格式。 这个链接 提供了所需和可选元素的精彩摘要。生成 RSS 的代码没有什么令人兴奋的——它很丑陋且是蛮力。我没有支持所有可选的 RSS 标签。每当发布或更改博客文章时,RSS 都会更新。好处是 DateTime.ToString() 方法有一个格式说明符“r”,可以将字符串格式化为 RSS 的正确格式!
我已将 RSS 链接添加到归档列中,因为这似乎是放置它的合理位置。我用 FeedReader 测试了 RSS,它工作正常。用 Google Reader 测试时,链接仍然卡在不同的页眉和内容上。Google 必须缓存链接并且不经常更新它。或者我没有提供它期望的某些信息。奇怪。
我学到的东西
学习 ASP.NET 让我想起了学习为 Commodore PET 编写游戏。它似乎很笨拙,我不确定我是否以正确的方式做事。有太多的时间花在调整演示文稿层上。ASP.NET 开发是一个非常适合进行各种自动化以帮助平滑演示文稿、可用性和数据管理方面的粗糙边缘的平台。似乎没有 UI 层、业务层和数据层之间的干净分离。您添加的任何分离都感觉不对,因为演示文稿层及其复杂性始终摆在您面前。
我确信我在组合这个 Web 应用程序时犯了很多错误,但它教会了我一些东西,我想,如果我再做一次,我会更仔细地研究第三方工具,使演示文稿层更容易处理,一些真正的自动化工具来连接演示文稿层和业务对象,以及业务层和数据层之间更好的关注点分离。另外,需要认真对待的是页面之间发生的重复和冗余。在许多方面,网页就像小小的自治岛屿。在它们之间建立一些共同且一致的东西非常困难。
而且因为一切都是无状态的,我发现我最终编写了许多静态类来促进重用,并将对象传递给这些类。所以 ASP.NET 开发中很少有东西让我觉得面向对象。当然,ASP.NET 开发环境非常光滑。调试应用程序、在设计视图和 HTML 代码之间切换、自动生成的 Javascript 等能力,使得快速构建看起来不错、功能齐全的网站变得非常容易。
GridView 是一个丑陋的控件。显然,人们花费了大量心思来使其尽可能可定制。这并没有改变它很丑的事实。在我看来,演示文稿在进入编辑模式时发生的变化是不可接受的。该控件的视觉演示和可用性非常差,我可以看到为什么人们会抢购第三方软件包,它们提供了比 ASP.NET 开箱即用的更好的演示效果。
虽然网页上的元素应该“流动”,但我发现实际上网页元素的流动是有问题的,并且充满困难。有时网页元素会毫无缘由地“换行”,并且设置元素的可见性行为不一致——水平方向上,其他元素会收缩,但垂直方向上,不可见元素占据的空间仍然存在。流动的规律性或可控性似乎很小。因此,由于对齐问题,基本的网页布局看起来很难看。
让我抓狂的一件事是紫色的“您已点击此链接”的着色。点击几下后,整个网站看起来就像患了麻疹。不过,这是我以后会处理的事情。
在这个项目中我不得不与以下内容打交道
- ASP.NET(几乎不知道)
- IIS 配置(为解决问题进行了大量搜索)
- CSS 样式(一点不知道,不得不问 Mark Harris)
- 数据库内容(SQLite 有其细微之处,但至少我是一个经验丰富的数据库人员,鉴于我没有在数据库中放入任何 FK 等,您可能注意不到)
- C#(我认为自己是专家,但 Web 应用程序开发是另一回事,并且影响到我才刚开始接触的设计问题)
Web 应用程序经常处理而我没有处理的事情/问题
- 图形(我不是图形艺术家)
- Javascript(说什么?)
- AJAX(叹气)
- 性能(负载平衡、数据库性能、流量等)
- CSS 用于皮肤/主题(一无所知)
- 安全性(非常重要!我基本上忽略了这一点)
这仅仅是针对 ASP.NET。我真的很钦佩那些懂 ASP.NET 和 PHP、Apache Web 服务器、Linux 以及 Ruby 等技术的人。他们哪有时间做实际的事情?
安装
将文件复制到您的 Web 服务器。您还需要安装 SQLite ADO.NET(请参阅下面的链接)。相应地调整 web.config 文件。
由于这是一个 ASP.NET 2.0 应用程序,我了解到如何处理运行同时拥有 ASP.NET 1.1 和 2.0 的 IIS。有关应用程序池的更多信息,请参阅 here,如果您同时运行 1.1 和 2.0,您将需要为 ASP.NET 2.0 应用程序创建一个单独的应用程序池。
数据库 blog.db 和 rss.xml 文件必须设置写入权限。我不得不更改 IIS_WPG 帐户,以便为 App_Data 文件夹和根文件夹设置写入权限,以分别启用数据库和 rss.xml 文件的写入。这可能是完全错误的做法。
如果您删除数据库,应用程序将创建一个带有初始值的数据库。您需要为样式列表创建记录才能使其美观,否则看起来像这样
结论
(与 Mark Harris 的对话)
Mark:所以用这个,一个人如何改变博客的布局?
Marc:你不能。不是在“要求”中:)
Mark:哈哈,好吧
Mark:我希望你使用了 MasterPages,这样人们就可以轻松地美化它
Marc:Pushkin 想知道“Master Pages”是什么。(我想,如果猫问了,我看起来就不会像个无知的人)
我想有一天可能会有第三部分:“重构博客引擎”。
那么,让我们看看我如何与我最初对其他人博客引擎的抱怨进行比较
- 已解决:需要我不愿意学习或支持的平台或提供商(我运行一台配备 W2003 Web Edition 和 IIS 的专用服务器)
- 它们有 bug:未发现(著名的最后一句)
- 它们过于复杂(对我来说它似乎很简单,但有人指责我需要简化我曾经认为简单的代码)
- 它们对演示文稿做出了假设(我完全失败了!)
- 几乎不可能定制(没有 CSS,但也不是最容易定制的东西!)
- 需要我不愿意添加到服务器的数据库(好的,这方面成功了。SQLite 很方便且可移植)。
不算很好。但这是一次有趣的冒险!