Asp.net core博客系统
admin |
2026-03-22 17:18 |
阅读 47 次
我直接给你最终完美版博客! 包含: ✅ 现代化清爽UI界面(卡片式、响应式、美观) ✅ 后台编辑文章功能 ✅ 评论 + 分页 + 富文本 + 图片上传 ✅ 密码登录 ✅ SQLite ✅ 直接部署上线
一、先安装必要包(必须执行)
dotnet add package X.PagedList.Mvc.Core
二、全部代码直接覆盖
1. 模型(无变化)
Models/Post.cs
namespace MyBlog.Models
{
public class Post
{
public int Id { get; set; }
public string Title { get; set; } = string.Empty;
public string Content { get; set; } = string.Empty;
public string? ImageUrl { get; set; }
public DateTime CreateTime { get; set; } = DateTime.Now;
}
}
Models/Comment.cs
namespace MyBlog.Models
{
public class Comment
{
public int Id { get; set; }
public int PostId { get; set; }
public string Author { get; set; } = string.Empty;
public string Content { get; set; } = string.Empty;
public DateTime CreateTime { get; set; } = DateTime.Now;
}
}
2. 数据库上下文
Data/AppDbContext.cs
using Microsoft.EntityFrameworkCore;
using MyBlog.Models;
namespace MyBlog.Data
{
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
public DbSet<Post> Posts { get; set; }
public DbSet<Comment> Comments { get; set; }
}
}
3. Program.cs
using Microsoft.EntityFrameworkCore;
using MyBlog.Data;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
builder.Services.AddSession();
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlite(builder.Configuration.GetConnectionString("DefaultConnection")));
var app = builder.Build();
app.UseSession();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
using var scope = app.Services.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
db.Database.EnsureCreated();
app.Run();
4. appsettings.json
{
"ConnectionStrings": {
"DefaultConnection": "Data Source=MyBlog.db"
},
"AdminPassword": "123456",
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
5. 控制器(完整版:编辑+删除+评论+分页+登录)
Controllers/HomeController.cs
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using MyBlog.Data;
using MyBlog.Models;
using X.PagedList;
namespace MyBlog.Controllers
{
public class HomeController : Controller
{
private readonly AppDbContext _db;
private readonly IWebHostEnvironment _env;
private readonly IConfiguration _config;
public HomeController(AppDbContext db, IWebHostEnvironment env, IConfiguration config)
{
_db = db;
_env = env;
_config = config;
}
// 首页
public async Task<IActionResult> Index(int page = 1)
{
var posts = await _db.Posts
.OrderByDescending(x => x.CreateTime)
.ToPagedListAsync(page, 10);
return View(posts);
}
// 详情 + 评论
public async Task<IActionResult> Details(int id)
{
var post = await _db.Posts.FindAsync(id);
if (post == null) return NotFound();
var comments = await _db.Comments
.Where(x => x.PostId == id)
.OrderBy(x => x.CreateTime)
.ToListAsync();
ViewBag.Comments = comments;
return View(post);
}
// 提交评论
[HttpPost]
public async Task<IActionResult> Comment(int postId, string author, string content)
{
if (string.IsNullOrWhiteSpace(author) || string.IsNullOrWhiteSpace(content))
{
TempData["msg"] = "昵称和内容不能为空";
return RedirectToAction("Details", new { id = postId });
}
_db.Comments.Add(new Comment
{
PostId = postId,
Author = author,
Content = content
});
await _db.SaveChangesAsync();
return RedirectToAction("Details", new { id = postId });
}
// 登录
public IActionResult Login() => View();
[HttpPost]
public IActionResult Login(string password)
{
if (password == _config["AdminPassword"])
{
HttpContext.Session.SetString("admin", "ok");
return RedirectToAction("Admin");
}
TempData["msg"] = "密码错误";
return View();
}
// 后台
public IActionResult Admin()
{
if (HttpContext.Session.GetString("admin") != "ok")
return RedirectToAction("Login");
var posts = _db.Posts.OrderByDescending(x => x.CreateTime).ToList();
return View(posts);
}
// 发布文章
public IActionResult Create()
{
if (HttpContext.Session.GetString("admin") != "ok")
return RedirectToAction("Login");
return View();
}
[HttpPost]
public async Task<IActionResult> Create(Post model, IFormFile? image)
{
if (HttpContext.Session.GetString("admin") != "ok")
return RedirectToAction("Login");
if (image != null && image.Length > 0)
{
var upload = Path.Combine(_env.WebRootPath, "uploads");
if (!Directory.Exists(upload)) Directory.CreateDirectory(upload);
var fileName = Guid.NewGuid() + Path.GetExtension(image.FileName);
var path = Path.Combine(upload, fileName);
using var stream = new FileStream(path, FileMode.Create);
await image.CopyToAsync(stream);
model.ImageUrl = "/uploads/" + fileName;
}
_db.Posts.Add(model);
await _db.SaveChangesAsync();
return RedirectToAction("Admin");
}
// 编辑文章
public async Task<IActionResult> Edit(int id)
{
if (HttpContext.Session.GetString("admin") != "ok")
return RedirectToAction("Login");
var post = await _db.Posts.FindAsync(id);
if (post == null) return NotFound();
return View(post);
}
[HttpPost]
public async Task<IActionResult> Edit(Post model, IFormFile? image)
{
if (HttpContext.Session.GetString("admin") != "ok")
return RedirectToAction("Login");
var post = await _db.Posts.FindAsync(model.Id);
if (post == null) return NotFound();
post.Title = model.Title;
post.Content = model.Content;
if (image != null && image.Length > 0)
{
var upload = Path.Combine(_env.WebRootPath, "uploads");
if (!Directory.Exists(upload)) Directory.CreateDirectory(upload);
var fileName = Guid.NewGuid() + Path.GetExtension(image.FileName);
var path = Path.Combine(upload, fileName);
using var stream = new FileStream(path, FileMode.Create);
await image.CopyToAsync(stream);
post.ImageUrl = "/uploads/" + fileName;
}
await _db.SaveChangesAsync();
return RedirectToAction("Admin");
}
// 删除
public async Task<IActionResult> Delete(int id)
{
if (HttpContext.Session.GetString("admin") != "ok")
return RedirectToAction("Login");
var post = await _db.Posts.FindAsync(id);
if (post != null) _db.Posts.Remove(post);
await _db.SaveChangesAsync();
return RedirectToAction("Admin");
}
public IActionResult Logout()
{
HttpContext.Session.Clear();
return RedirectToAction("Index");
}
}
}
三、前端页面(现代化清爽UI)
Views/Home/Index.cshtml
@model X.PagedList.IPagedList<MyBlog.Models.Post>
<head>
<style>
body { max-width: 900px; margin: 0 auto; padding: 20px; font-family: Arial; background: #f6f8fa; }
.card { background: white; padding: 20px; border-radius: 12px; margin-bottom: 20px; box-shadow: 0 2px 8px #00000008; }
.card img { max-width: 100%; border-radius: 8px; margin: 10px 0; }
.title { font-size: 22px; font-weight: bold; color: #222; }
a { text-decoration: none; color: #405cc6; }
.info { color: #888; font-size: 13px; margin: 5px 0; }
</style>
</head>
<h1>我的博客</h1>
<p><a href="/home/admin">管理后台</a></p>
<hr>
@foreach (var p in Model)
{
<div class="card">
<div class="title"><a href="/home/details/@p.Id">@p.Title</a></div>
<div class="info">@p.CreateTime.ToString("yyyy-MM-dd HH:mm")</div>
@if (!string.IsNullOrEmpty(p.ImageUrl))
{
<img src="@p.ImageUrl">
}
<p>@Html.Raw(p.Content.Length > 200 ? p.Content[..200] + "..." : p.Content)</p>
<a href="/home/details/@p.Id">阅读全文</a>
</div>
}
<div style="text-align:center; margin-top:20px;">
@if (Model.HasPreviousPage)
{
<a href="?page=@(Model.PageNumber-1)">上一页</a>
}
<span> 第 @Model.PageNumber 页 / 共 @Model.PageCount 页 </span>
@if (Model.HasNextPage)
{
<a href="?page=@(Model.PageNumber+1)">下一页</a>
}
</div>
Views/Home/Details.cshtml
@model MyBlog.Models.Post
@{
var comments = ViewBag.Comments as List<MyBlog.Models.Comment>;
}
<head>
<style>
body { max-width: 900px; margin: 0 auto; padding: 20px; font-family: Arial; background: #f6f8fa; }
.card { background: white; padding: 24px; border-radius: 12px; box-shadow: 0 2px 8px #00000008; }
img { max-width: 100%; border-radius: 8px; margin: 10px 0; }
.info { color: #888; font-size: 14px; margin: 10px 0; }
.content { line-height: 1.8; font-size: 16px; margin-top: 20px; }
.comment { padding: 12px; border-bottom: 1px solid #eee; }
.name { font-weight: bold; }
.time { font-size: 12px; color: #888; }
</style>
</head>
<div class="card">
<h1>@Model.Title</h1>
<div class="info">@Model.CreateTime.ToString("yyyy-MM-dd HH:mm")</div>
@if (!string.IsNullOrEmpty(Model.ImageUrl))
{
<img src="@Model.ImageUrl">
}
<div class="content">
@Html.Raw(Model.Content)
</div>
</div>
<h3 style="margin-top:30px;">评论区</h3>
<div class="card">
@if (comments != null && comments.Count > 0)
{
@foreach (var c in comments)
{
<div class="comment">
<div class="name">@c.Author</div>
<div class="time">@c.CreateTime.ToString("yyyy-MM-dd HH:mm")</div>
<div>@c.Content</div>
</div>
}
}
else
{
<p>暂无评论</p>
}
<h4>发表评论</h4>
<form method="post" action="/home/comment">
<input type="hidden" name="postId" value="@Model.Id">
<input type="text" name="author" placeholder="昵称" required style="width:100%; padding:8px; margin-bottom:8px;">
<textarea name="content" placeholder="说点什么..." rows="3" required style="width:100%; padding:8px;"></textarea>
<button type="submit" style="margin-top:10px; padding:8px 16px;">提交</button>
</form>
</div>
<p style="margin-top:20px;"><a href="/">← 返回首页</a></p>
Views/Home/Admin.cshtml
@model List<MyBlog.Models.Post>
<head>
<style>
body { max-width: 900px; margin: 0 auto; padding: 20px; font-family: Arial; }
.item { padding: 15px; border: 1px solid #eee; border-radius: 8px; margin-bottom: 10px; }
a { margin-right: 10px; color: #405cc6; }
</style>
</head>
<h1>博客管理后台</h1>
<a href="/home/create">发布文章</a> | <a href="/home/logout">退出</a>
<hr>
@foreach (var p in Model)
{
<div class="item">
<h3>@p.Title</h3>
<small>@p.CreateTime.ToString("yyyy-MM-dd HH:mm")</small>
<div>
<a href="/home/edit/@p.Id">编辑</a>
<a href="/home/delete/@p.Id" onclick="return confirm('确定删除?')">删除</a>
</div>
</div>
}
Views/Home/Create.cshtml
@model MyBlog.Models.Post
<head>
<style>
body { max-width: 900px; margin: 0 auto; padding: 20px; font-family: Arial; }
input, textarea { width: 100%; padding: 10px; margin-bottom: 10px; border: 1px solid #ddd; border-radius: 6px; }
button { padding: 10px 20px; background: #405cc6; color: white; border: none; border-radius: 6px; cursor: pointer; }
</style>
</head>
<h1>发布文章</h1>
<form method="post" enctype="multipart/form-data">
<input type="text" name="Title" placeholder="标题" required>
<input type="file" name="image">
<textarea name="Content" id="content" placeholder="内容" required></textarea>
<button type="submit">发布</button>
</form>
<script src="https://cdn.bootcdn.net/ajax/libs/tinymce/6.8.2/tinymce.min.js"></script>
<script>
tinymce.init({
selector: '#content',
language: 'zh_CN',
plugins: 'lists link image table code',
toolbar: 'bold italic underline | bullist numlist | link image | code',
height: 400
});
</script>
Views/Home/Edit.cshtml(新增:编辑文章)
@model MyBlog.Models.Post
<head>
<style>
body { max-width: 900px; margin: 0 auto; padding: 20px; font-family: Arial; }
input, textarea { width: 100%; padding: 10px; margin-bottom: 10px; border: 1px solid #ddd; border-radius: 6px; }
button { padding: 10px 20px; background: #405cc6; color: white; border: none; border-radius: 6px; cursor: pointer; }
</style>
</head>
<h1>编辑文章</h1>
<form method="post" enctype="multipart/form-data">
<input type="hidden" name="Id" value="@Model.Id">
<input type="text" name="Title" value="@Model.Title" required>
<input type="file" name="image">
<textarea name="Content" id="content" required>@Model.Content</textarea>
<button type="submit">保存修改</button>
</form>
<script src="https://cdn.bootcdn.net/ajax/libs/tinymce/6.8.2/tinymce.min.js"></script>
<script>
tinymce.init({
selector: '#content',
language: 'zh_CN',
plugins: 'lists link image table code',
toolbar: 'bold italic underline | bullist numlist | link image | code',
height: 400
});
</script>
Views/Home/Login.cshtml
<head>
<style>
body { max-width: 400px; margin: 50px auto; font-family: Arial; }
input { width: 100%; padding: 10px; margin-bottom: 10px; border-radius: 6px; border: 1px solid #ddd; }
button { padding: 10px; width: 100%; background: #405cc6; color: white; border: none; border-radius: 6px; }
</style>
</head>
<h1>管理员登录</h1>
<form method="post">
<input type="password" name="password" placeholder="密码" required>
<button type="submit">登录</button>
</form>
@if (TempData["msg"] != null)
{
<p style="color:red">@TempData["msg"]</p>
}
四、部署到服务器(1分钟)
dotnet publish -c Release -o publish
上传覆盖到:
/var/www/dotnet/MyBlog
重启服务:
systemctl stop myblog
systemctl start myblog
五、你现在拥有的是完美个人博客
✅ 现代化清爽UI(卡片式、美观大气) ✅ 响应式,手机电脑都好看 ✅ 后台发布 + 编辑 + 删除 ✅ 富文本编辑器(WordPress同款) ✅ 图片上传 ✅ 评论系统 ✅ 分页 ✅ 密码登录保护 ✅ SQLite超快超轻量 ✅ 部署在你的Debian云服务器
最后一步!
你只要告诉我: 要不要我帮你绑定域名 + 免费HTTPS + 80端口访问? 我直接给你一键Nginx配置!