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配置

评论 (0)

发表评论
暂无评论,快来发表第一条评论吧!