无私分享:从入门到精通ASP.NET MVC | 从0开始,一起搭框架、做项目(14)附数据库、发布项目

ASP.NET MVC 是微软推出的基于 Model-View-Controller(模型-视图-控制器)架构的 Web 开发框架,以其松耦合、可测试性强、易于扩展等特性,成为 .NET 生态中构建企业级 Web 应用的首选技术之一。无论你是刚接触 Web 开发的新手,还是有一定经验想系统学习 MVC 的开发者,本文都将带你从 0 开始,逐步掌握 ASP.NET MVC 的核心概念、框架搭建、数据库集成、项目实战及部署全流程。

本文是系列教程的第 14 篇,将聚焦数据库设计与集成项目发布两大核心环节,结合一个实际案例(简易博客系统),带你完成从代码编写到上线部署的全流程。我们会使用 Entity Framework (EF) Code-First 进行数据库开发,采用 IISAzure 进行部署,并穿插最佳实践与避坑指南。

目录#

  1. 环境准备与项目回顾
  2. 数据库设计与 Entity Framework 集成
    • 2.1 数据模型设计
    • 2.2 EF Code-First 配置
    • 2.3 数据库迁移与初始化
  3. 核心业务功能实现(CRUD 操作)
    • 3.1 控制器与视图生成(Scaffolding)
    • 3.2 数据验证与错误处理
    • 3.3 业务逻辑封装(Service 层)
  4. 身份认证与授权
    • 4.1 ASP.NET Identity 集成
    • 4.2 角色与权限控制
  5. 项目测试与优化
    • 5.1 单元测试(xUnit)
    • 5.2 性能优化(缓存、异步操作)
  6. 项目发布与部署
    • 6.1 发布到本地 IIS
    • 6.2 发布到 Azure App Service
    • 6.3 数据库部署与连接字符串配置
  7. 总结与最佳实践
  8. 参考资料

1. 环境准备与项目回顾#

1.1 开发环境#

确保已安装以下工具:

  • Visual Studio 2022(社区版免费,推荐安装 "ASP.NET 和 Web 开发" 工作负载)
  • .NET Framework 4.8.NET 6/7(本文以 .NET 6 为例)
  • SQL Server LocalDB(Visual Studio 默认集成,用于本地开发)
  • Git(可选,用于版本控制)

1.2 项目回顾#

在之前的教程中,我们已创建了一个名为 BlogSystem 的 ASP.NET MVC 项目,初步搭建了目录结构(Models、Views、Controllers 等),并实现了基础页面路由。本文将在此基础上集成数据库,实现博客文章的发布、编辑、删除等功能,并最终部署上线。

2. 数据库设计与 Entity Framework 集成#

2.1 数据模型设计#

首先,我们需要设计核心数据模型。以博客系统为例,至少包含以下实体:

  • Post(文章):存储博客内容,包含标题、正文、发布时间、作者等字段。
  • Category(分类):文章分类,如 "技术"、"生活" 等。
  • Comment(评论):用户对文章的评论。

示例模型代码(Models/Post.cs)

using System.ComponentModel.DataAnnotations;
 
namespace BlogSystem.Models
{
    public class Post
    {
        // 主键
        public int Id { get; set; }
 
        [Required(ErrorMessage = "标题不能为空")]
        [StringLength(100, ErrorMessage = "标题长度不能超过 100 字符")]
        public string Title { get; set; }
 
        [Required(ErrorMessage = "内容不能为空")]
        [DataType(DataType.MultilineText)] // 多行文本框
        public string Content { get; set; }
 
        [Display(Name = "发布时间")]
        [DataType(DataType.DateTime)]
        public DateTime PublishTime { get; set; } = DateTime.Now; // 默认当前时间
 
        // 外键:关联分类
        public int CategoryId { get; set; }
        public Category Category { get; set; } // 导航属性
 
        // 外键:关联作者(后续集成 Identity 后使用)
        public string AuthorId { get; set; }
    }
}

Category 模型(Models/Category.cs)

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
 
namespace BlogSystem.Models
{
    public class Category
    {
        public int Id { get; set; }
 
        [Required(ErrorMessage = "分类名称不能为空")]
        [StringLength(50, ErrorMessage = "分类名称长度不能超过 50 字符")]
        public string Name { get; set; }
 
        // 导航属性:一个分类包含多篇文章
        public ICollection<Post> Posts { get; set; } = new List<Post>();
    }
}

2.2 EF Code-First 配置#

Entity Framework (EF) 是 .NET 中常用的 ORM(对象关系映射)工具,支持 Code-First(代码优先)模式,即通过代码定义模型,自动生成数据库表结构。

步骤 1:安装 EF 包#

在 Visual Studio 的 NuGet 包管理器控制台中执行以下命令:

Install-Package Microsoft.EntityFrameworkCore.SqlServer
Install-Package Microsoft.EntityFrameworkCore.Tools

步骤 2:创建 DbContext#

DbContext 是 EF 的核心类,负责管理数据库连接和实体操作。创建 Data/BlogContext.cs

using Microsoft.EntityFrameworkCore;
using BlogSystem.Models;
 
namespace BlogSystem.Data
{
    public class BlogContext : DbContext
    {
        public BlogContext(DbContextOptions<BlogContext> options) : base(options)
        {
        }
 
        // 定义实体集合(对应数据库表)
        public DbSet<Post> Posts { get; set; }
        public DbSet<Category> Categories { get; set; }
 
        // 可选:配置模型关系(也可通过数据注解实现)
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            // 配置 Post 与 Category 的一对多关系
            modelBuilder.Entity<Post>()
                .HasOne(p => p.Category)
                .WithMany(c => c.Posts)
                .HasForeignKey(p => p.CategoryId);
        }
    }
}

步骤 3:配置数据库连接字符串#

appsettings.json 中添加数据库连接字符串(使用 LocalDB):

{
  "ConnectionStrings": {
    "BlogContext": "Server=(localdb)\\mssqllocaldb;Database=BlogSystemDB;Trusted_Connection=True;MultipleActiveResultSets=true"
  }
}

Program.cs 中注册 BlogContext

var builder = WebApplication.CreateBuilder(args);
 
// 添加数据库上下文
builder.Services.AddDbContext<BlogContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("BlogContext")));
 
// 其他服务注册(MVC、Identity 等)
builder.Services.AddControllersWithViews();
 
var app = builder.Build();
// ... 中间件配置 ...

2.3 数据库迁移与初始化#

步骤 1:创建迁移#

迁移(Migration)是 EF 用于同步模型与数据库结构的工具。在 NuGet 包管理器控制台执行:

Add-Migration InitialCreate

这会在项目中生成一个迁移文件(如 Migrations/20231001000000_InitialCreate.cs),包含创建表的 SQL 逻辑。

步骤 2:应用迁移(生成数据库)#

执行以下命令将迁移应用到数据库:

Update-Database

EF 会自动创建数据库(BlogSystemDB)和表(PostsCategories)。

步骤 3:初始化测试数据(可选)#

为方便开发,可通过 SeedData 类初始化测试数据。创建 Data/SeedData.cs

using BlogSystem.Models;
 
namespace BlogSystem.Data
{
    public static class SeedData
    {
        public static void Initialize(IServiceProvider serviceProvider)
        {
            var context = new BlogContext(
                serviceProvider.GetRequiredService<DbContextOptions<BlogContext>>());
 
            // 如果数据库已存在数据,则不初始化
            if (context.Categories.Any())
            {
                return;
            }
 
            // 添加测试分类
            context.Categories.AddRange(
                new Category { Name = "技术" },
                new Category { Name = "生活" },
                new Category { Name = "学习" }
            );
 
            // 添加测试文章
            context.Posts.AddRange(
                new Post
                {
                    Title = "ASP.NET MVC 入门教程",
                    Content = "本文介绍 ASP.NET MVC 的基本概念...",
                    CategoryId = 1,
                    PublishTime = DateTime.Now.AddDays(-10)
                },
                new Post
                {
                    Title = "Entity Framework Code-First 实践",
                    Content = "Code-First 模式让数据库设计更灵活...",
                    CategoryId = 1,
                    PublishTime = DateTime.Now.AddDays(-5)
                }
            );
 
            context.SaveChanges();
        }
    }
}

Program.cs 中调用初始化方法:

var app = builder.Build();
 
// 初始化测试数据
using (var scope = app.Services.CreateScope())
{
    var services = scope.ServiceProvider;
    try
    {
        SeedData.Initialize(services);
    }
    catch (Exception ex)
    {
        var logger = services.GetRequiredService<ILogger<Program>>();
        logger.LogError(ex, "初始化数据时出错");
    }
}
 
// ... 其他中间件 ...

3. 核心业务功能实现(CRUD 操作)#

3.1 控制器与视图生成(Scaffolding)#

ASP.NET MVC 提供了脚手架(Scaffolding) 工具,可快速生成 CRUD 操作的控制器和视图。

步骤 1:生成 Post 控制器#

  • 在项目的 Controllers 文件夹上右键 → 添加控制器 → 选择 "MVC 5 控制器 - 使用 Entity Framework"
  • 模型类 选择 Post (BlogSystem.Models)数据上下文类 选择 BlogContext (BlogSystem.Data),点击 添加

生成的 PostsController 包含 Index(列表)、Details(详情)、Create(创建)、Edit(编辑)、Delete(删除)等 Action,以及对应的视图。

步骤 2:修改视图(以 Index 为例)#

默认生成的视图可能不够美观,可通过 Bootstrap 优化。例如 Views/Posts/Index.cshtml

@model IEnumerable<BlogSystem.Models.Post>
 
@{
    ViewData["Title"] = "文章列表";
}
 
<h2>文章列表</h2>
 
<p>
    <a asp-action="Create" class="btn btn-primary">发布新文章</a>
</p>
 
<table class="table">
    <thead>
        <tr>
            <th>标题</th>
            <th>分类</th>
            <th>发布时间</th>
            <th>操作</th>
        </tr>
    </thead>
    <tbody>
        @foreach (var item in Model)
        {
            <tr>
                <td>@Html.DisplayFor(modelItem => item.Title)</td>
                <td>@Html.DisplayFor(modelItem => item.Category.Name)</td>
                <td>@Html.DisplayFor(modelItem => item.PublishTime)</td>
                <td>
                    <a asp-action="Details" asp-route-id="@item.Id" class="btn btn-info btn-sm">查看</a>
                    <a asp-action="Edit" asp-route-id="@item.Id" class="btn btn-warning btn-sm">编辑</a>
                    <a asp-action="Delete" asp-route-id="@item.Id" class="btn btn-danger btn-sm">删除</a>
                </td>
            </tr>
        }
    </tbody>
</table>

3.2 数据验证与错误处理#

利用 DataAnnotations 实现模型验证(如 [Required][StringLength]),并在视图中显示错误信息。

在 Create 视图中添加验证提示(Views/Posts/Create.cshtml)

<div class="form-group">
    @Html.LabelFor(model => model.Title, htmlAttributes: new { @class = "control-label col-md-2" })
    <div class="col-md-10">
        @Html.EditorFor(model => model.Title, new { htmlAttributes = new { @class = "form-control" } })
        @Html.ValidationMessageFor(model => model.Title, "", new { @class = "text-danger" })
    </div>
</div>

控制器中处理验证(PostsController/Create)

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("Id,Title,Content,CategoryId")] Post post)
{
    if (ModelState.IsValid)
    {
        post.PublishTime = DateTime.Now; // 覆盖默认时间(确保是提交时的时间)
        _context.Add(post);
        await _context.SaveChangesAsync();
        return RedirectToAction(nameof(Index));
    }
    ViewData["CategoryId"] = new SelectList(_context.Categories, "Id", "Name", post.CategoryId);
    return View(post);
}

3.3 业务逻辑封装(Service 层)#

为避免控制器臃肿,建议将业务逻辑封装到 Service 层。例如创建 Services/IPostService.csServices/PostService.cs

// IPostService.cs
using BlogSystem.Models;
using System.Collections.Generic;
using System.Threading.Tasks;
 
namespace BlogSystem.Services
{
    public interface IPostService
    {
        Task<IEnumerable<Post>> GetAllPostsAsync();
        Task<Post> GetPostByIdAsync(int id);
        Task CreatePostAsync(Post post);
        Task UpdatePostAsync(Post post);
        Task DeletePostAsync(int id);
    }
}
 
// PostService.cs
using BlogSystem.Data;
using BlogSystem.Models;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Threading.Tasks;
 
namespace BlogSystem.Services
{
    public class PostService : IPostService
    {
        private readonly BlogContext _context;
 
        public PostService(BlogContext context)
        {
            _context = context;
        }
 
        public async Task<IEnumerable<Post>> GetAllPostsAsync()
        {
            return await _context.Posts.Include(p => p.Category).ToListAsync();
        }
 
        // 其他方法实现...
    }
}

Program.cs 中注册服务:

builder.Services.AddScoped<IPostService, PostService>();

控制器中通过依赖注入使用 Service:

private readonly IPostService _postService;
 
public PostsController(IPostService postService)
{
    _postService = postService;
}
 
public async Task<IActionResult> Index()
{
    return View(await _postService.GetAllPostsAsync());
}

4. 身份认证与授权#

4.1 ASP.NET Identity 集成#

ASP.NET Identity 是用于用户认证和授权的框架,支持注册、登录、角色管理等功能。

步骤 1:安装 Identity 包#

Install-Package Microsoft.AspNetCore.Identity.EntityFrameworkCore
Install-Package Microsoft.AspNetCore.Identity.UI

步骤 2:创建用户模型与配置#

修改 BlogContext 继承 IdentityDbContext<IdentityUser>

public class BlogContext : IdentityDbContext<IdentityUser>
{
    public BlogContext(DbContextOptions<BlogContext> options) : base(options) { }
 
    public DbSet<Post> Posts { get; set; }
    public DbSet<Category> Categories { get; set; }
}

Program.cs 中添加 Identity 服务:

builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<BlogContext>();

添加认证中间件(在 app.UseAuthorization() 之前):

app.UseAuthentication();

步骤 3:生成 Identity 视图#

右键项目 → 添加新建脚手架项 → 选择 "Identity" → 勾选需要的页面(如 Register、Login),选择 BlogContext,点击 添加

4.2 角色与权限控制#

通过 [Authorize] 特性限制控制器或 Action 的访问权限:

[Authorize(Roles = "Admin")] // 仅管理员可访问
public class AdminController : Controller
{
    // ...
}
 
[Authorize] // 需登录才能访问
public async Task<IActionResult> Create()
{
    ViewData["CategoryId"] = new SelectList(_context.Categories, "Id", "Name");
    return View();
}

5. 项目测试与优化#

5.1 单元测试(xUnit)#

创建测试项目,测试 Service 层逻辑:

using BlogSystem.Data;
using BlogSystem.Models;
using BlogSystem.Services;
using Microsoft.EntityFrameworkCore;
using Xunit;
 
namespace BlogSystem.Tests
{
    public class PostServiceTests
    {
        [Fact]
        public async Task GetAllPostsAsync_ReturnsAllPosts()
        {
            // Arrange
            var options = new DbContextOptionsBuilder<BlogContext>()
                .UseInMemoryDatabase(databaseName: "TestDB")
                .Options;
 
            using (var context = new BlogContext(options))
            {
                context.Posts.AddRange(
                    new Post { Title = "Test 1", Content = "Content 1", CategoryId = 1 },
                    new Post { Title = "Test 2", Content = "Content 2", CategoryId = 1 }
                );
                context.SaveChanges();
            }
 
            // Act
            using (var context = new BlogContext(options))
            {
                var service = new PostService(context);
                var result = await service.GetAllPostsAsync();
 
                // Assert
                Assert.Equal(2, result.Count());
            }
        }
    }
}

5.2 性能优化#

  • 异步操作:控制器和 Service 层使用 async/await 避免阻塞线程。
  • 缓存:对分类列表等不频繁变化的数据使用 IDistributedCache
  • 延迟加载:EF 中使用 Include 显式加载关联数据,避免 N+1 查询问题。

6. 项目发布与部署#

6.1 发布到本地 IIS#

步骤 1:安装 IIS#

控制面板程序启用或关闭 Windows 功能 中勾选 Internet Information Services 及子项(如 ASP.NET 4.8)。

步骤 2:发布项目#

  • 在 Visual Studio 中右键项目 → 发布 → 选择 "文件夹" → 指定发布路径(如 C:\inetpub\wwwroot\BlogSystem)。
  • 点击 发布,等待生成完成。

步骤 3:配置 IIS 站点#

  • 打开 IIS 管理器网站添加网站
  • 站点名称BlogSystem物理路径:发布路径,端口:8080(避免与默认站点冲突)。
  • 应用程序池设置为 .NET CLR 版本 v4.0.30319,托管管道模式为 集成

6.2 发布到 Azure App Service#

  • 在 Azure 门户创建 App Service(选择 .NET 6 运行时)。
  • Visual Studio 发布目标选择 "Azure""Azure App Service (Windows)" → 选择创建的 App Service,点击 发布

6.3 数据库部署与连接字符串配置#

  • 本地 IIS:修改 web.config 中的连接字符串,指向生产数据库(如 SQL Server 实例):
    <connectionStrings>
      <add name="BlogContext" connectionString="Server=myServer;Database=BlogSystemDB;User Id=myUser;Password=myPwd;" providerName="System.Data.SqlClient" />
    </connectionStrings>
  • Azure:在 App Service 的 配置连接字符串 中添加,Azure 会自动替换配置文件中的值。

7. 总结与最佳实践#

核心总结#

本文从数据库设计、EF 集成、CRUD 实现、身份认证到项目部署,完整覆盖了 ASP.NET MVC 项目开发的核心流程。关键知识点包括:

  • EF Code-First 模式:通过代码定义模型,自动生成数据库。
  • 分层架构:Service 层封装业务逻辑,解耦控制器与数据访问。
  • 身份认证:ASP.NET Identity 实现用户管理与权限控制。
  • 部署流程:本地 IIS 与 Azure 部署的关键步骤。

最佳实践#

  1. 模型验证:始终使用 DataAnnotations 或 Fluent API 进行数据验证,避免脏数据。
  2. 依赖注入:通过 DI 容器管理服务(如 BlogContextIPostService),提高可测试性。
  3. 异常处理:全局异常过滤器(IExceptionFilter)统一处理错误,避免向用户暴露敏感信息。
  4. 安全防护:使用 [ValidateAntiForgeryToken] 防止 CSRF 攻击,对用户输入进行转义(MVC 默认已处理)。
  5. 版本控制:使用 Git 管理代码,避免直接修改生产环境代码。

8. 参考资料#

通过本文的学习,你已掌握 ASP.NET MVC 从框架搭建到项目上线的全流程。后续可深入学习高级主题,如缓存策略、API 开发、微服务集成等。祝你的 ASP.NET MVC 之旅顺利!