65.9K
CodeProject 正在变化。 阅读更多。
Home

精益高效的博客引擎

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.96/5 (19投票s)

2007年2月27日

15分钟阅读

viewsIcon

99984

downloadIcon

580

我用了 3 天时间尝试编写一个博客引擎。

这是系列文章的第一部分。

引言

首先,您可以在此处实时查看该博客。

以下是一篇“态度鲜明”的文章,讲述了如何使用 .NET 2.0 构建一个基本的博客引擎。

我为什么要写一个博客引擎?因为我从来没有找到过我喜欢的。它们要么

  • 需要我不愿学习或支持的平台或提供商(我运行的是带有 W2003 Web Edition 和 IIS 的专用服务器)
  • 它们有 bug
  • 它们过于复杂
  • 它们对演示文稿做出了假设
  • 几乎不可能进行自定义
  • 需要一个我不想添加到服务器的数据库

而且因为我是一个特立独行的人,我喜欢做自己的事情。我尝试了 DotText,它很笨拙而且过时。我尝试了 SubText,但我无法弄清楚如何更改诸如边距宽度之类的简单内容。其他博客引擎呢?算了。我甚至无法浏览它们的网站来弄清楚需求是什么或如何安装它们。

令人惊讶的是,CodeProject 上没有一篇关于博客引擎的文章。所以这是第一篇(假设在接下来的几天里没有其他文章发布)。

买家请注意

我可能在这篇文章中会说几次,但我认为这是第一次:我对 Web 应用程序相当不熟悉。所以我这样做是为了学习如何不编写 Web 应用程序,因为我肯定会从中更多地学到“如何不写”,而不是“如何写”。所以这也是对精彩的 Web 应用程序世界的一次探索,以及我在编写本应是一个简单应用程序的过程中发现的各种事物。如果您使用此代码(我无法想象任何人真的想使用它),您应该知道这很可能是错误的做事方式。

要求

使用轻量级数据库

博客引擎不应需要 SQL Server 或 MySQL 或其他任何类型的重型数据库。它几乎应该可以用 XML 完成!但是,数据库确实有一些优点,而 SQLite 是一个不错且易于使用的软件包。所以数据库引擎将是 SQLite。

只做博客,不做其他事

除了 RSS。我基本上想要一个可以发布帖子的引擎。仅此而已。能有多复杂?我实在无法理解为什么有人愿意使用笨拙的基于浏览器的文本/HTML 编辑器,而不是在舒适的家中从一个漂亮的 HTML 编辑器来完成,该编辑器能够进行拼写检查、正确设置格式等。使用瘦客户端是一个好主意,但除非你是谷歌,否则我宁愿使用我的桌面工具来舒适地编辑我的博客,谢谢。

RSS

是的,我们需要 RSS,这样人们就可以使用他们的聚合器并收到更新通知。

博客、博文和分类

博客本身

包含

  • 页眉,包括博客标题、副标题和菜单
  • 主体,包含博文
  • 页脚,包含任何内容,例如版权信息
  • 可选的左右边距,用于显示存档和分类
  • RSS 链接

存档应按月列出条目,排除未发布任何条目的月份。

分类应按类别名称列出。

坦白说,我对菜单不太满意。我需要菜单有什么用,除非将博客置于管理模式,这样我就可以删除或更新条目、修改 UI 或添加新条目。哦,好吧。如果我需要菜单,它最好在一定程度上是可自定义的。稍后会详细介绍。

博文

博文应包含

  • 标题
  • 副标题/摘要
  • 可选的分类
  • 发布日期/时间
  • 博客文本本身

分类应包含

  • 类别名称
  • 描述

保持简单

代码应该简单。它应该优雅。它应该经过设计。它应该有注释。我看了其他博客引擎,觉得“WTF?”。SubText 的源代码有 18.5 MB!

我不是 CSS 专家,也不是 HTML 专家,我猜很多人也不是。所以我的博客引擎需要体现“简单的可配置性”的理念。好吧,那些懂 CSS 和 HTML 的人会抱怨非标准的这个或那个。我真的不在乎。它需要为工作,而不是为他们工作。

很容易陷入对自定义的追求。字体、颜色、对齐方式、定位方式,等等。自定义可以等等,重点是“精简高效”。

我不想要什么

注释

不希望人们发表评论。如果您想说什么,请给我发电子邮件。我还没有见过任何一个博客引擎能够抵御垃圾邮件发送者,除非完全禁用评论。而且,我不想读你们的评论,我想让你们读我的博客。

皮肤

啊!我不知道 CSS,我也不想知道 CSS。我希望它自己看起来还不错。好吧,我承认,人们希望他们的博客能表达他们的个性。这可以等。鉴于我对网页几乎一无所知,学习如何制作一个“可 CSS 化”的东西目前不在计划中。

VB

我不要任何 VB 代码。无需多言。

UI 设计

以下是 UI 元素的图示

UI 实现

初始设计,第一版

因此,在确保 IIS 已安装并且我重新安装了 .NET 2.0 以与 IIS 配合使用后,我启动了 VS2005 并开始了一个新的网站项目。我在设计器中打开了 default.aspx 页面,并自言自语该做什么。好吧,让我们从简单开始。一个用于页眉和页脚的面板,一个用于主体的表格。添加几个标签,然后发现标签不能被对齐。哦,是的,我喜欢 HTML。所以,将标签包装在一个表格里!这可能更有意义,因为表格可以有多行。这意味着我们也可以摆脱面板。

这是基本概念的屏幕截图

侧边距为 20% 宽度,主体为 60% 宽度,页眉和页脚为 100% 宽度。

至于博文,它也需要是一个表格。当然,我不能在设计器中将一个表格作为单元格!哦不!这些表格不是 HTML 表格,它们是 ASP.NET 表格!!!

<asp:TableCell...>Left Gutter</asp:TableCell>
<asp:TableCell ...>Blog Entry</asp:TableCell>
<asp:TableCell ...>Right Gutter</asp:TableCell>

啊!这并不是我想要的!好吧,TableCell 是一个 WebControl,如果我创建一个具有 Table 作为子控件的特殊 TableCell 呢?这不起作用(至少我没能让它工作)。相反,让我们在页面加载时以编程方式添加表格。而且当你思考的时候,我们实际上拥有的是

  • 博客是由博文行组成的集合
  • 博文行由另一个表格组成,其中包含标题、副标题、分类、日期和博客文本的行。

初始设计,第二版

所以,我们需要在 Page_Load 方法中添加一些代码。但首先,我需要将 ID(我称之为“cellBogEntry”)分配给博文表格的中间单元格,这样我们就可以将表格添加到其 Controls 集合中。

protected void Page_Load(object sender, EventArgs e)
{
  Table blogTable = CreateTable(new string[] { "Blog Entry" });
  Table blogEntry = CreateTable(new string[] {"Title", "Subtitle", 
      "Category", "Date", "Blog Text"});
  blogTable.Rows[0].Cells[0].Controls.Add(blogEntry);
  cellBlogEntry.Controls.Add(blogTable);
}

并定义 CreateTable 方法

protected Table CreateTable(string[] rowNames)
{
  Table table = new Table();
  table.Width = Unit.Percentage(100);
  table.BorderStyle = BorderStyle.Solid;
  table.BorderWidth = 1;

  foreach (string rowName in rowNames)
  {
    TableRow tr = new TableRow();
    TableCell tc = new TableCell();
    tc.Width = Unit.Percentage(100);
    tr.Cells.Add(tc);
    tc.Text = rowName;
    table.Rows.Add(tr);
  }

  return table;
}

结果是:

很好!这看起来更符合我的想法。

现在我们从 UI 暂时休息一下,开始处理数据库。

数据库设计

我们需要一些表来开始

BlogInfo

包含字段

  • 标题
  • Subtitle
  • 电子邮件
  • Copyright

BlogCategory

  • ID
  • 名称
  • 描述

BlogEntry

  • 标题
  • Subtitle
  • CategoryID
  • 日期
  • BlogText

数据库实现

由于我计划使用 SQLite,我希望考虑在处理无状态运行时环境时需要注意的事项。首先,我不想频繁地打开和关闭数据库连接。我最好有一个连接并保持打开状态。

应用程序状态

数据库实例将存储在应用程序状态中,以便每个应用程序实例都可以使用相同的数据库实例。坦率地说,在阅读文档时,我对应用程序状态和会话状态感到困惑。我原本以为应用程序状态是永久的,但似乎发生的是每个客户端都创建了一个应用程序实例。但那么会话是什么呢?无论如何,这是我正在寻找的关键信息

Application_Start 和 Application_End 方法是特殊方法,不代表 HttpApplication 事件。ASP.NET 会在应用程序域的整个生命周期中调用它们一次,而不是为每个 HttpApplication 实例调用。

这就是我想创建数据库实例并根据需要进行初始化的地方。我了解到这可以在 global.asax 文件中完成。嗯。没有。啊,但有一个“全局应用程序类”模板可以添加到项目中。添加文件后,我还添加了对 System.Data.SQLite 的引用。我发现引用的程序集会被添加到 web.config 文件中(我对 Web 开发一无所知,唉)。处理 global.asax 文件对我来说非常奇怪,因为类需要完全限定,因为我无法弄清楚在哪里放置“using”语句。

网站路径

问题是,它把 blog.db 文件放在哪里了!?!?好吧,如果我添加一个 System.IO.Path.GetFullPath("blog.db") 的监视,我发现文件被放在了 c:\Program Files\Microsoft Visual Studio 8\Common7\IDE\blog.db!哦天哪,这真不是我想要的。应该有一个更合理的地方,一个与网站本身更相关的地方。为什么文件没有创建在“App_Data”文件夹中?阅读有关网站路径的信息,MSDN 说

您可以在服务器控件的任何路径相关属性中使用 ~ 运算符。

但是 bool dbExists = System.IO.File.Exists("~/App_Data/blog.db"); 的不起作用。但存在一个替代方法(可能是一个错误的方法),如代码所示

void Application_Start(object sender, EventArgs e) 
{
  string dbPath = Server.MapPath("~") + "\\App_Data\\blog.db";
  bool dbExists = System.IO.File.Exists(dbPath);
  System.Data.SQLite.SQLiteConnection conn = 
        new System.Data.SQLite.SQLiteConnection();
  conn.ConnectionString = "Data Source="+dbPath;
  // conn.SetPassword("my_password");

  conn.Open();

  if (!dbExists)
  {
    CreateDatabase(conn);
  }

  Application.Add("db", conn);
}

void CreateDatabase(System.Data.SQLite.SQLiteConnection conn)
{
  using (System.Data.SQLite.SQLiteCommand cmd = conn.CreateCommand())
  {
    cmd.CommandText = "create table BlogInfo (Title text, Subtitle text,
         Email text, Copyright text)";
    cmd.ExecuteNonQuery();
    cmd.CommandText = "create table BlogCategory (ID integer primary key 
         autoincrement, Name text, Description text)";
    cmd.ExecuteNonQuery();
    cmd.CommandText = "create table BlogEntry (ID integer primary key 
         autoincrement, Title text, Subtitle text, CategoryID integer, 
         Date datetime, BlogText text)";
    cmd.ExecuteNonQuery();
    cmd.CommandText = "insert into BlogInfo (Title, Subtitle, Email, 
         Copyright) values ('Title', 'Subtitle', 'your email', 
         'your copyright')";
    cmd.ExecuteNonQuery();
  }
}

void Application_End(object sender, EventArgs e) 
{
  System.Data.SQLite.SQLiteConnection conn = 
     (System.Data.SQLite.SQLiteConnection)Application["db"];
  conn.Close();
}

加载带数据的页面

页眉和页脚

那么,让我们回到 UI,让一些数据显示在博客上。您会注意到我为 BlogInfo 表创建了一些初始数据,因为这是一个单行表。所以,回到设计器,我在页眉表格的单元格中添加了 cellTitle、cellSubtitle 和 cellCopyright ID。我还使用了有用的 SQLite 查询分析器来修改 BlogInfo 数据。

所以我的下一个问题是我想要添加一些辅助类,我认为我不需要将它们添加到 App_Code 文件夹(Visual Studio 会询问)。嗯,我错了。它们是必需的。我完全不知道为什么。

初始化页眉的代码非常直接。我获取连接对象并填充 TableCell 的文本。BlogInfo 只是具有 getter 和 setter 的属性集合。

protected void InitializeHeader()
{
  SQLiteConnection conn = (SQLiteConnection)Application["db"];
  BlogInfo blogInfo = Blog.LoadInfo(conn);
  cellTitle.Text = blogInfo.Title;
  cellSubtitle.Text = blogInfo.Subtitle;
  cellCopyright.Text = blogInfo.Copyright;
}

LoadInfo 读取唯一的一行

public static BlogInfo LoadInfo(SQLiteConnection conn)
{
  SQLiteCommand cmd = conn.CreateCommand();
  cmd.CommandText = "select * from BlogInfo";
  SQLiteDataAdapter da = new SQLiteDataAdapter(cmd);
  DataTable dt = new DataTable();
  da.Fill(dt);
  BlogInfo blogInfo = new BlogInfo();
  LoadProperties(blogInfo, dt.Rows[0]);

  return blogInfo;
}

由于我将使用 BlogInfo 之类的辅助类来处理其他表格,我将使用反射来实现一个简陋的 ORM,以在辅助类和 DataRow 之间移动数据

public static void LoadProperties(object target, DataRow row)
{
  foreach (PropertyInfo pi in target.GetType().GetProperties())
  {
    if (row.Table.Columns.Contains(pi.Name))
    {
      pi.SetValue(target, row[pi.Name], null);
    }
  }
}

结果正在缓慢地变得生动起来

一些示例博文

现在让我们添加几个示例博文,看看它们如何显示。我将使用之前创建的 blogEntry 表(但需要将其移动,以便在本请求中可供类访问)。我遇到的下一个问题与 ASP.NET 无关。SQLite 中的 DateTime 没有转换为 .NET 可理解的内容。经过大约 20 分钟的研究,我发现可能是因为我在插入时没有正确输入日期/时间信息,如处所示。如果我使用“datetime('now')”插入测试数据,我不会收到字符串转换异常。不幸的是,我得到一个完全错误的日期 1/1/0001 12:00:00 AM!而且,除其他问题外,ID 需要是 long 而不是 int,因为它们在 SQLite 中是 int64,并且 LoadProperties 方法需要处理 DBNull.Value 值,因为 .NET 太愚蠢而无法将其转换为可空值类型的 null。啊!

public static void LoadProperties(object target, DataRow row)
{
  foreach (PropertyInfo pi in target.GetType().GetProperties())
  {
    if (row.Table.Columns.Contains(pi.Name))
    {
      object value = row[pi.Name];

      if (value == DBNull.Value)
      {
        value = null;
      }

      pi.SetValue(target, value, null);
    }
  }
}

事实证明,错误的日期是我的错——BlogEntry 辅助类中的属性名称与表列名不匹配。

这就是我们现在的情况

请注意,条目按相反顺序排序。代码有点丑陋。首先,填充 ASP.NET 行和单元格(更多时髦的反射)

protected void LoadBlogEntries()
{
  SQLiteConnection conn = (SQLiteConnection)Application["db"];
  DataTable dt = Blog.LoadBlogEntries(conn);
  blogTable.Rows.Clear();
  // Create a BlogEntry helper instance.

  BlogEntry blogEntry=new BlogEntry();

  foreach (DataRow row in dt.Rows)
  {
    // Create a table for the blog entry.

    Table blogEntryTable=new Table();
    // Add it to the blog collection.

    AddEntryToTable(blogEntryTable);
    // Load the helper class with the blog entry row.

    Blog.LoadProperties(blogEntry, row);
    // For each property in the following list...


    foreach (string propName in new String[] { "Title", "Subtitle", "Date", 
      "BlogText" })
    {
      // Add a row to the table.

      TableRow tr = new TableRow();
      blogEntryTable.Rows.Add(tr);
      // Add a cell to the row.

      TableCell tc = new TableCell();
      tr.Cells.Add(tc);
      // Use reflection to get the property.

      PropertyInfo pi = blogEntry.GetType().GetProperty(propName);
      // Set the cell text.

      tc.Text = pi.GetValue(blogEntry, null).ToString();
    }
  }
}

protected void AddEntryToTable(Table blogEntryTable)
{
  // Create a new row in the blog table.

  TableRow tr = new TableRow();
  // Add to the blog table collection.

  blogTable.Rows.Add(tr);
  // Create a cell.

  TableCell tc = new TableCell();
  // Add to the row collection.

  tr.Cells.Add(tc);
  tc.Width = Unit.Percentage(100);
  // Add the blog entry table as a child control to the cell.

  tc.Controls.Add(blogEntryTable);
}

还有,从数据库获取数据

public static DataTable LoadBlogEntries(SQLiteConnection conn)
{
  SQLiteCommand cmd = conn.CreateCommand();
  cmd.CommandText = "select * from BlogEntry order by Date desc";
  SQLiteDataAdapter da = new SQLiteDataAdapter(cmd);
  DataTable dt = new DataTable();
  da.Fill(dt);

  return dt;
}

使其更美观

好的,让我们暂时忽略菜单、分类和存档以及管理,让现有的东西看起来更好一点。让我们也忘掉 CSS 和样式表什么的。我真的不应该这样做,因为这是“正确”的做法。我甚至不会尝试弄清楚主题是如何工作的!然后继续前进...

我想能够设置

  • 字体名称
  • 字体样式
  • size
  • 对齐方式
  • 背景颜色
  • 前景颜色

针对每种类型的单元格。那么,到目前为止我们有哪些类型的单元格

  • 页眉表格
  • 页眉标题
  • 页眉副标题
  • 博文表格
  • 博文标题
  • 博文副标题
  • 博文日期
  • 博文
  • 版权

所以,让我们使用数据库来存储我们想要管理的样式信息,以及一些使用反射处理这些属性的信息,因为我讨厌硬编码这些东西。我将添加 Style 表,包含以下字段

  • CellType
  • PropertyName
  • FontPropertyName

这些字段(除其他外)足够通用,可以指定边框样式。所以,首先,这里有一些 SQL 来设置一些样式

insert into style (CellType, PropertyName, FontPropertyName, Value)
values ('HeaderTitle', null, 'Bold', 'true');
insert into style (CellType, PropertyName, FontPropertyName, Value)
values ('HeaderTitle', 'BackColor', null, 'LightBlue');
insert into style (CellType, PropertyName, FontPropertyName, Value)
values ('HeaderTitle', null, 'Size', '20');
insert into style (CellType, PropertyName, FontPropertyName, Value)
values ('HeaderTitle', null, 'Name', 'verdana');

现在,我很快意识到“CellType”并不是真正的正确字段名。“ObjectType”更准确,因为我们可以更改表格、行和单元格的属性。所以这需要重构。但代码非常简单。任何对象都可以传递给 SetStyle 方法,该方法会调整对象属性或对象 Font 属性的属性(假设它有)。

protected void SetStyle(object obj, string key)
{
  SQLiteConnection conn = (SQLiteConnection)Application["db"];
  DataTable styles=Blog.GetStyles(conn, key);

  foreach (DataRow style in styles.Rows)
  {
    if (style["PropertyName"] != DBNull.Value)
    {
      PropertyInfo pi = obj.GetType().
         GetProperty(style["PropertyName"].ToString());
      object val = style["Value"];
      // Helper to convert strings to their correct types (booleans, enums, 

      // etc).

      object newVal = Converter.Convert(val, pi.PropertyType);
      pi.SetValue(obj, newVal, null);
    }
    else if (style["FontPropertyName"] != DBNull.Value)
    {
      PropertyInfo piFont = obj.GetType().GetProperty("Font");
      FontInfo fontInfo = (FontInfo)piFont.GetValue(obj, null);
      PropertyInfo pi = fontInfo.GetType().GetProperty(
         style["FontPropertyName"].ToString());
      object val = style["Value"];
      // Helper to convert strings to their correct types

      // (booleans, enums, etc).

      object newVal = Converter.Convert(val, pi.PropertyType);
      pi.SetValue(fontInfo, newVal, null);
    }
  }
}

并且数据库查询是

public static DataTable GetStyles(SQLiteConnection conn, string key)
{
  SQLiteCommand cmd = conn.CreateCommand();
  cmd.CommandText = "select * from Style where CellType=@key";
  cmd.Parameters.Add(new SQLiteParameter("key", key));
  SQLiteDataAdapter da = new SQLiteDataAdapter(cmd);
  DataTable dt = new DataTable();
  da.Fill(dt);

  return dt;
}

所以,重新审视一下初始化页眉之类的东西,注意增加了 SetStyle 调用

protected void InitializeHeader()
{
  SQLiteConnection conn = (SQLiteConnection)Application["db"];
  BlogInfo blogInfo = Blog.LoadInfo(conn);
  cellTitle.Text = blogInfo.Title;
  SetStyle(cellTitle, "HeaderTitle");
  cellSubtitle.Text = blogInfo.Subtitle;
  SetStyle(cellSubtitle, "HeaderSubtitle");
  cellCopyright.Text = blogInfo.Copyright;
  SetStyle(cellCopyright, "Copyright");
}

在数据库中进行大量样式条目后,我们得到

所以,我认为它进展得相当顺利!(边框可以用我的样式功能去除,但我暂时将它们保留。)

博客链接

你能看出我不擅长写需求吗?博客应该允许您单击一个条目并仅查看该条目,并且 URL 应该可以用作引用,这样您就可以从其他地方直接链接到该条目。让我们弄清楚它是如何工作的。首先,博文标题需要是一个链接,所以我会在文本周围加上“a href”标签,看看效果如何。在我的测试用例中,这奏效了,但存在两个问题

  • 如何获取网站的 URL,而不是使用硬编码了它的条目?
  • 如何响应“?ID=1”(例如)查询字符串?

啊哈!这两个问题都可以通过 Page_Load 方法中的两行简单代码来解决。一个注意事项是,生成的 URL 必须去除任何查询字符串!

protected void Page_Load(object sender, EventArgs e)
{
  id=Request.QueryString["ID"];
  url = StringHelpers.LeftOf(Request.Url.ToString(), '?');
  ...

我修改了 Blog.LoadBlogEntries,如果存在 ID,则使用 ID 作为限定符。

public static DataTable LoadBlogEntries(SQLiteConnection conn, string id)
{
  SQLiteCommand cmd = conn.CreateCommand();

  if (id == null)
  {
    cmd.CommandText = "select * from BlogEntry order by Date desc";
  }
  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;
}

成功!

但现在我们需要博客标题能够点击返回主页。啊!功能蔓延!啊!糟糕的初始需求!

cellTitle.Text = "<a href=\"" + url+"\">" + blogInfo.Title + "</a>";

那底纹是怎么回事?如果标题不带下划线就好了。要做到这一点,你不会相信,但我们需要 CSS(谢谢 Mark Harris)!在每个 Page_Load 的开始时

protected void Page_Load(object sender, EventArgs e)
{
  Response.Write("<style>A {text-decoration: none}</style>");
  ...

现在下划线消失了!Mark Harris 说:“你不应该从代码中输出 HTML……那简直太邪恶了!” 呵呵。我想我应该直接将样式信息嵌入到页面的 HTML 中。

下一步

  • 好的,这篇文章已经够长了。告诉我我哪里做得不对,以及你们喜欢和不喜欢什么,我可能会对你们不喜欢的东西做些什么,但很可能不会。我会告诉你们我不喜欢什么——各种硬编码的常量、功能,以及无法定义页面各个部分的“流程”。
  • 在下一部分中,我将处理菜单、分类和存档。不过,此时,您会注意到我实际上不再处理 ASP.NET,而是处理从数据库中提取信息并将其呈现在页面上的繁琐工作,以及不存在的错误检查。
  • 我将 CSS 的复杂性与编辑数据库表的复杂性进行了交换!这不好,所以我不得不添加一些不错的管理功能,因为坦率地说,我不想让用户(或我自己)触摸数据库。
  • 最终的管理功能包括添加、更新和删除帖子。
  • 博文需要通过某种限制来限定。
  • 哦,别忘了 RSS!

啊!我感觉功能蔓延正在发生!

当所有工作完成后,我希望我能有一个精简高效的博客引擎,源代码不超过 100K。当然,然后我必须开始使用它!

我学到的东西

  • 编写基本的 ASP.NET 应用程序相当容易
  • 大部分工作在于演示、数据收集和管理
  • 定制化和可用性的细节才是真正的挑战
  • 要删除数据库,我必须终止“WebDev.WebServer.EXE”进程,该进程对 blog.db 文件有文件锁定。

安装

将文件复制到您的 Web 服务器。您还需要安装 SQLite(请参阅下面的链接)。相应地调整 web.config 文件。

由于这是一个 ASP.NET 2.0 应用程序,我学习了如何处理同时运行 ASP.NET 1.1 和 2.0 的 IIS。请在此处阅读有关应用程序池的更多信息,因为如果您同时运行 1.1 和 2.0,您需要为 ASP.NET 2.0 应用程序创建一个单独的应用程序池。

数据库所在的目录也必须设置为可写。我必须更改 IIS_WPG 帐户对 App_Data 文件夹和根文件夹的写权限,以分别启用数据库和 rss.xml 文件的写入。这可能完全是错误的。

如果您删除数据库,应用程序将创建一个带有初始值的数据库。您需要为样式列表创建记录才能使其美观,否则它看起来是这样的

工具

SQLite ADO.NET

SQLite 查询分析器

© . All rights reserved.