- 文章管理:发布、编辑、删除文章。
- 分类管理:对文章进行分类。
- 标签管理:为文章打上标签。
- 评论系统:访客可以对文章进行评论。
- 用户管理:博主可以登录进行管理。
- 响应式设计:适配电脑和手机。
技术栈选择
我们选择现代、高性能且社区活跃的技术栈:
- 后端框架:ASP.NET Core 8.0 (或更高版本)
- Web 框架:MVC (Model-View-Controller) 模式,结构清晰,易于理解。
- 数据库:Entity Framework Core (EF Core) 作为 ORM,SQLite 作为数据库(轻量级,无需安装数据库服务器,非常适合个人博客)。
- 前端样式:Bootstrap 5 或 Tailwind CSS,快速构建响应式布局,这里我们以 Bootstrap 5 为例,因为它开箱即用。
- 身份认证:ASP.NET Core Identity,用于用户登录和权限管理。
项目结构
一个标准的 ASP.NET Core MVC 项目结构如下:
MyPersonalBlog/
├── Controllers/ # 控制器,处理用户请求
│ ├── HomeController.cs
│ ├── BlogPostsController.cs
│ └── AccountController.cs
├── Models/ # 数据模型 (EF Core)
│ ├── BlogPost.cs
│ ├── Category.cs
│ ├── Tag.cs
│ ├── Comment.cs
│ └── ApplicationUser.cs (来自 Identity)
├── Views/ # 视图,展示 HTML 页面
│ ├── Home/
│ ├── BlogPosts/
│ ├── Account/
│ ├── Shared/ # 共享布局 _Layout.cshtml
│ │ └── _Layout.cshtml
│ └── _ViewStart.cshtml
├── Data/ # 数据库上下文
│ └── ApplicationDbContext.cs
├── wwwroot/ # 静态文件 (CSS, JS, 图片)
│ ├── css/
│ │ └── site.css
│ └── js/
├── appsettings.json # 应用配置
├── Program.cs # 应用入口点
└── MyPersonalBlog.csproj # 项目文件
核心代码实现
步骤 1:创建项目
- 打开终端,运行命令:
dotnet new mvc -n MyPersonalBlog cd MyPersonalBlog
- 安装必要的 NuGet 包:
dotnet add package Microsoft.EntityFrameworkCore.Sqlite dotnet add package Microsoft.EntityFrameworkCore.Tools dotnet add package Microsoft.AspNetCore.Identity.EntityFrameworkCore dotnet add package Microsoft.AspNetCore.Identity.UI
步骤 2:创建数据模型
在 Models 文件夹中创建以下类:
BlogPost.cs
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace MyPersonalBlog.Models
{
public class BlogPost
{
public int Id { get; set; }
[Required]
[StringLength(100)]
public string Title { get; set; }
[Required]
[StringLength(200, MinimumLength = 10)]
public string Slug { get; set; } // URL 友好的标题,如 "my-first-post"
[Required]
[Column(TypeName = "text")]
public string Content { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime? UpdatedAt { get; set; }
public int CategoryId { get; set; }
public virtual Category Category { get; set; }
public List<Tag> Tags { get; set; } = new List<Tag>();
public List<Comment> Comments { get; set; } = new List<Comment>();
}
}
Category.cs
using System.ComponentModel.DataAnnotations;
namespace MyPersonalBlog.Models
{
public class Category
{
public int Id { get; set; }
[Required]
[StringLength(50)]
public string Name { get; set; }
}
}
Tag.cs
using System.ComponentModel.DataAnnotations;
namespace MyPersonalBlog.Models
{
public class Tag
{
public int Id { get; set; }
[Required]
[StringLength(50)]
public string Name { get; set; }
public List<BlogPost> BlogPosts { get; set; } = new List<BlogPost>();
}
}
Comment.cs
using System.ComponentModel.DataAnnotations;
namespace MyPersonalBlog.Models
{
public class Comment
{
public int Id { get; set; }
[Required]
[StringLength(200)]
public string Author { get; set; }
[Required]
[StringLength(200)]
[EmailAddress]
public string Email { get; set; }
[Required]
[Column(TypeName = "text")]
public string Content { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public int BlogPostId { get; set; }
public virtual BlogPost BlogPost { get; set; }
}
}
步骤 3:配置数据库上下文
在 Data 文件夹中创建 ApplicationDbContext.cs:
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using MyPersonalBlog.Models;
namespace MyPersonalBlog.Data
{
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
public DbSet<BlogPost> BlogPosts { get; set; }
public DbSet<Category> Categories { get; set; }
public DbSet<Tag> Tags { get; set; }
public DbSet<Comment> Comments { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
// 配置多对多关系
builder.Entity<BlogPost>()
.HasMany(p => p.Tags)
.WithMany(t => t.BlogPosts)
.UsingEntity(j => j.ToTable("BlogPostTags"));
}
}
}
步骤 4:注册服务和数据库迁移
打开 Program.cs,在 var app = builder.Build(); 之前添加以下代码:
// 1. 添加数据库上下文
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlite(builder.Configuration.GetConnectionString("DefaultConnection")));
// 2. 添加 Identity 服务
builder.Services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
// 3. 添加服务
builder.Services.AddControllersWithViews();
然后在 appsettings.json 中添加数据库连接字符串:
{
"ConnectionStrings": {
"DefaultConnection": "Data Source=blog.db"
},
// ... 其他配置
}
在终端运行数据库迁移命令:
dotnet ef migrations add InitialCreate dotnet ef database update
这会创建一个 blog.db 文件并生成所有必要的表。
步骤 5:创建控制器和视图
这是 MVC 的核心部分。
HomeController.cs (首页)
using Microsoft.AspNetCore.Mvc;
using MyPersonalBlog.Data;
using MyPersonalBlog.Models;
using Microsoft.EntityFrameworkCore;
namespace MyPersonalBlog.Controllers
{
public class HomeController : Controller
{
private readonly ApplicationDbContext _context;
public HomeController(ApplicationDbContext context)
{
_context = context;
}
public async Task<IActionResult> Index()
{
// 获取最新的5篇文章
var latestPosts = await _context.BlogPosts
.Include(p => p.Category)
.OrderByDescending(p => p.CreatedAt)
.Take(5)
.ToListAsync();
return View(latestPosts);
}
}
}
Views/Home/Index.cshtml
@model IEnumerable<MyPersonalBlog.Models.BlogPost>
@{
ViewData["Title"] = "首页";
}
<div class="p-5 mb-4 bg-light rounded-3">
<div class="container-fluid py-5">
<h1 class="display-5 fw-bold">欢迎来到我的博客</h1>
<p class="col-md-8 fs-4">这里记录了我的技术学习、生活感悟和项目经验,希望能对你有所帮助!</p>
<a class="btn btn-primary btn-lg" asp-controller="BlogPosts" asp-action="Index">浏览文章</a>
</div>
</div>
<h2 class="mb-4">最新文章</h2>
<div class="row">
@foreach (var post in Model)
{
<div class="col-md-6 mb-4">
<div class="card h-100">
<div class="card-body">
<h5 class="card-title">
<a asp-controller="BlogPosts" asp-action="Details" asp-route-id="@post.Id" asp-route-slug="@post.Slug">@post.Title</a>
</h5>
<p class="card-text">@post.Content.Substring(0, Math.Min(post.Content.Length, 150))...</p>
<small class="text-muted">分类: @post.Category.Name | @post.CreatedAt.ToString("yyyy-MM-dd")</small>
</div>
</div>
</div>
}
</div>
BlogPostsController.cs (文章相关)
using Microsoft.AspNetCore.Mvc;
using MyPersonalBlog.Data;
using MyPersonalBlog.Models;
using Microsoft.EntityFrameworkCore;
namespace MyPersonalBlog.Controllers
{
public class BlogPostsController : Controller
{
private readonly ApplicationDbContext _context;
public BlogPostsController(ApplicationDbContext context)
{
_context = context;
}
// GET: /BlogPosts
public async Task<IActionResult> Index()
{
var posts = _context.BlogPosts.Include(p => p.Category);
return View(await posts.ToListAsync());
}
// GET: /BlogPosts/Details/5
public async Task<IActionResult> Details(int? id, string slug)
{
if (id == null)
{
return NotFound();
}
var blogPost = await _context.BlogPosts
.Include(p => p.Category)
.Include(p => p.Tags)
.Include(p => p.Comments)
.FirstOrDefaultAsync(m => m.Id == id);
if (blogPost == null)
{
return NotFound();
}
// 验证 Slug 是否匹配,防止恶意请求
if (blogPost.Slug != slug)
{
return RedirectToAction("Details", new { id = blogPost.Id, slug = blogPost.Slug });
}
return View(blogPost);
}
// ... [博主登录后才能使用的] Create/Edit/Delete 方法 ...
}
}
Views/BlogPosts/Details.cshtml (文章详情页)
@model MyPersonalBlog.Models.BlogPost
@{
ViewData["Title"] = Model.Title;
}
<h1>@Model.Title</h1>
<p class="text-muted">分类: @Model.Category.Name | 发布于 @Model.CreatedAt.ToString("yyyy-MM-dd")</p>
<hr />
<div class="blog-post-content">
@Html.Raw(Model.Content) // 使用 Raw 显示 HTML 格式的内容
</div>
<hr />
<h3>评论 (@Model.Comments.Count)</h3>
@foreach (var comment in Model.Comments)
{
<div class="card mb-3">
<div class="card-body">
<h6 class="card-subtitle mb-2 text-muted">@comment.Author - @comment.CreatedAt.ToString("yyyy-MM-dd HH:mm")</h6>
<p class="card-text">@comment.Content</p>
</div>
</div>
}
<!-- 添加评论的表单 -->
<h4>发表评论</h4>
<form asp-action="AddComment" asp-controller="BlogPosts" asp-route-id="@Model.Id" method="post">
<div class="mb-3">
<label for="author" class="form-label">昵称</label>
<input type="text" class="form-control" id="author" name="Author" required>
</div>
<div class="mb-3">
<label for="email" class="form-label">邮箱</label>
<input type="email" class="form-control" id="email" name="Email" required>
</div>
<div class="mb-3">
<label for="content" class="form-label">评论内容</label>
<textarea class="form-control" id="content" name="Content" rows="3" required></textarea>
</div>
<button type="submit" class="btn btn-primary">提交评论</button>
</form>
BlogPostsController.cs 中添加评论方法
// POST: /BlogPosts/AddComment/5
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> AddComment(int id, [Bind("Author,Email,Content")] Comment comment)
{
if (ModelState.IsValid)
{
comment.BlogPostId = id;
comment.CreatedAt = DateTime.UtcNow;
_context.Add(comment);
await _context.SaveChangesAsync();
return RedirectToAction("Details", new { id = id, slug = (await _context.BlogPosts.FindAsync(id)).Slug });
}
// 如果模型验证失败,返回详情页并显示错误
var blogPost = await _context.BlogPosts
.Include(p => p.Category)
.Include(p => p.Tags)
.Include(p => p.Comments)
.FirstOrDefaultAsync(m => m.Id == id);
return View("Details", blogPost);
}
美化与扩展
使用 Markdown 编辑器
为了更好的写作体验,可以使用 Markdown 作为文章内容格式,然后在前端渲染成 HTML。
-
后端:安装
MarkdigNuGet 包。dotnet add package Markdig
-
保存:在
BlogPost模型中,将Content字段类型改为string,并直接存储 Markdown 字符串。 -
显示:在
Details.cshtml中,使用Markdig渲染。@using Markdig; @* ... *@ <div class="blog-post-content"> @Html.Raw(Markdown.ToHtml(Model.Content)) </div>
使用富文本编辑器
如果你更喜欢传统的富文本编辑器(如 TinyMCE, CKEditor):
- 在
wwwroot中引入编辑器的 JS 和 CSS 文件。 - 在创建/编辑文章的表单中,将
textarea替换为编辑器的初始化代码。 - 在提交表单时,编辑器会将 HTML 内容填充到
textarea中,你直接保存这个 HTML 字符串到数据库即可。
部署
完成开发后,你可以将项目部署到各种平台:
- Windows:Azure App Service, IIS
- Linux:Azure App Service, Docker 容器
- 静态站点:可以导出为静态 HTML 文件(使用 Jekyll, Hugo 等工具),但 ASP.NET MVC 的动态功能(如评论)需要后端支持。
这个模板为你提供了一个功能完整、结构清晰的 ASP.NET Core 个人博客起点,你可以基于此进行扩展,
- 文章归档:按月份或年份归档文章。
- 搜索功能:集成全文搜索引擎。
- RSS 订阅:生成 RSS feed。
- 图片上传:集成
Azure Blob Storage或本地存储。 - SEO 优化:添加
sitemap.xml和robots.txt。
希望这个详细的指南能帮助你快速搭建起自己的 ASP.NET 个人博客!
