Skip to content

ASP.NET MVC

一、ASP.NET MVC 基础

1. MVC 模式是什么?

MVC(Model-View-Controller) 是一种软件架构模式:

  • Model(模型) - 数据和业务逻辑
  • View(视图) - 用户界面展示
  • Controller(控制器) - 处理用户输入,协调 Model 和 View

2. MVC 的优势

关注点分离 - 职责清晰,易于维护
可测试性 - 易于单元测试
可扩展性 - 组件独立,便于扩展
SEO 友好 - 支持友好的 URL

3. ASP.NET Core MVC vs Web Forms

特性ASP.NET Core MVCWeb Forms
架构MVC 模式页面-代码分离
控制完全控制 HTML服务器控件
测试性易于测试测试困难
性能更高相对较低
现代化✅ 推荐⚠️ 已过时

二、MVC 核心组件

1. Controller(控制器)

Controller 的职责:

  • 处理用户请求
  • 调用 Model 获取数据
  • 选择合适的 View 返回
csharp
public class HomeController : Controller
{
    private readonly IUserService _userService;
    
    public HomeController(IUserService userService)
    {
        _userService = userService;
    }
    
    // GET: /Home/Index
    public async Task<IActionResult> Index()
    {
        var users = await _userService.GetAllUsersAsync();
        return View(users);
    }
    
    // GET: /Home/Details/5
    public async Task<IActionResult> Details(int id)
    {
        var user = await _userService.GetUserByIdAsync(id);
        if (user == null)
        {
            return NotFound();
        }
        return View(user);
    }
}

2. Action 方法返回类型

csharp
// ViewResult - 返回视图
public IActionResult Index()
{
    return View(); // 返回对应的 View
}

// JsonResult - 返回 JSON
public IActionResult GetData()
{
    return Json(new { name = "Alice", age = 20 });
}

// RedirectResult - 重定向
public IActionResult Redirect()
{
    return Redirect("/Home/Index");
}

// ContentResult - 返回文本内容
public IActionResult GetContent()
{
    return Content("Hello World");
}

// FileResult - 返回文件
public IActionResult DownloadFile()
{
    var fileBytes = System.IO.File.ReadAllBytes("file.pdf");
    return File(fileBytes, "application/pdf", "file.pdf");
}

// StatusCodeResult - 返回状态码
public IActionResult NotFound()
{
    return NotFound();
}

3. 路由(Routing)

约定路由:

csharp
// Program.cs
app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

特性路由:

csharp
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
    [HttpGet]
    [Route("all")]
    public IActionResult GetAllUsers()
    {
        return Ok(users);
    }
    
    [HttpGet("{id:int}")]
    public IActionResult GetUser(int id)
    {
        return Ok(user);
    }
    
    [HttpPost]
    public IActionResult CreateUser([FromBody] User user)
    {
        // 创建用户
        return CreatedAtAction(nameof(GetUser), new { id = user.Id }, user);
    }
}

路由约束:

csharp
[Route("users/{id:int}")]        // 只匹配整数
[Route("users/{name:alpha}")]    // 只匹配字母
[Route("users/{id:int:min(1)}")] // 整数且最小值为1

4. Model Binding(模型绑定)

自动绑定:

csharp
// GET: /Users/Details?id=1&name=Alice
public IActionResult Details(int id, string name)
{
    // 自动绑定查询字符串参数
    return View();
}

// POST: /Users/Create
[HttpPost]
public IActionResult Create(User user)
{
    // 自动绑定表单数据到 User 对象
    if (ModelState.IsValid)
    {
        // 保存用户
        return RedirectToAction(nameof(Index));
    }
    return View(user);
}

绑定来源:

  • [FromQuery] - 查询字符串
  • [FromRoute] - 路由参数
  • [FromBody] - 请求体(JSON/XML)
  • [FromForm] - 表单数据
  • [FromHeader] - HTTP 头部
csharp
public IActionResult Create(
    [FromBody] User user,        // JSON 数据
    [FromQuery] int page = 1,    // 查询字符串
    [FromHeader] string authToken) // HTTP 头部
{
    // ...
}

5. Model Validation(模型验证)

csharp
public class User
{
    [Required(ErrorMessage = "用户名不能为空")]
    [StringLength(20, MinimumLength = 3)]
    public string Username { get; set; }
    
    [Required]
    [EmailAddress]
    public string Email { get; set; }
    
    [Range(18, 100, ErrorMessage = "年龄必须在18-100之间")]
    public int Age { get; set; }
    
    [RegularExpression(@"^[0-9]{11}$", ErrorMessage = "手机号格式不正确")]
    public string Phone { get; set; }
}

验证处理:

csharp
[HttpPost]
public IActionResult Create(User user)
{
    if (!ModelState.IsValid)
    {
        // 返回验证错误
        return View(user);
    }
    
    // 保存用户
    return RedirectToAction(nameof(Index));
}

三、View(视图)

1. Razor 语法

razor
@* 代码块 *@
@{
    var name = "Alice";
    var age = 20;
}

@* 输出变量 *@
<p>姓名: @name</p>
<p>年龄: @age</p>

@* 条件语句 *@
@if (age >= 18)
{
    <p>已成年</p>
}
else
{
    <p>未成年</p>
}

@* 循环 *@
@foreach (var user in users)
{
    <li>@user.Name</li>
}

@* 三元运算符 *@
<p>状态: @(isActive ? "激活" : "未激活")</p>

2. 布局页(Layout)

_Layout.cshtml:

razor
<!DOCTYPE html>
<html>
<head>
    <title>@ViewData["Title"]</title>
</head>
<body>
    <header>
        <nav>导航栏</nav>
    </header>
    
    <main>
        @RenderBody()
    </main>
    
    <footer>
        <p>&copy; 2026</p>
    </footer>
    
    @RenderSection("Scripts", required: false)
</body>
</html>

视图页:

razor
@{
    Layout = "_Layout";
    ViewData["Title"] = "首页";
}

<div>
    <h1>首页内容</h1>
</div>

@section Scripts {
    <script>
        // 页面特定的脚本
    </script>
}

3. 部分视图(Partial View)

创建部分视图:

razor
@* _UserCard.cshtml *@
@model User

<div class="user-card">
    <h3>@Model.Name</h3>
    <p>@Model.Email</p>
</div>

使用部分视图:

razor
@foreach (var user in Model)
{
    @await Html.PartialAsync("_UserCard", user)
    @* 或者 *@
    <partial name="_UserCard" model="user" />
}

4. 视图组件(View Component)

创建视图组件:

csharp
public class MenuViewComponent : ViewComponent
{
    public async Task<IViewComponentResult> InvokeAsync()
    {
        var menuItems = await GetMenuItemsAsync();
        return View(menuItems);
    }
    
    private Task<List<MenuItem>> GetMenuItemsAsync()
    {
        // 获取菜单项
        return Task.FromResult(new List<MenuItem>());
    }
}

视图组件视图:

razor
@* Views/Shared/Components/Menu/Default.cshtml *@
@model List<MenuItem>

<ul>
    @foreach (var item in Model)
    {
        <li><a href="@item.Url">@item.Name</a></li>
    }
</ul>

使用视图组件:

razor
@await Component.InvokeAsync("Menu")
@* 或者 *@
<vc:menu></vc:menu>

四、过滤器(Filters)

1. 授权过滤器(Authorization Filter)

csharp
[Authorize]
public class AdminController : Controller
{
    [Authorize(Roles = "Admin")]
    public IActionResult AdminOnly()
    {
        return View();
    }
    
    [AllowAnonymous]
    public IActionResult Public()
    {
        return View();
    }
}

2. 动作过滤器(Action Filter)

csharp
public class LogActionFilter : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        // Action 执行前
        var controller = context.RouteData.Values["controller"];
        var action = context.RouteData.Values["action"];
        Console.WriteLine($"执行: {controller}.{action}");
    }
    
    public override void OnActionExecuted(ActionExecutedContext context)
    {
        // Action 执行后
        Console.WriteLine("执行完成");
    }
}

// 使用
[LogActionFilter]
public IActionResult Index()
{
    return View();
}

3. 结果过滤器(Result Filter)

csharp
public class CacheResultFilter : ResultFilterAttribute
{
    public override void OnResultExecuting(ResultExecutingContext context)
    {
        // 结果执行前(可以修改结果)
    }
    
    public override void OnResultExecuted(ResultExecutedContext context)
    {
        // 结果执行后(可以缓存)
    }
}

4. 异常过滤器(Exception Filter)

csharp
public class CustomExceptionFilter : IExceptionFilter
{
    public void OnException(ExceptionContext context)
    {
        if (context.Exception is ArgumentNullException)
        {
            context.Result = new BadRequestObjectResult(
                new { error = "参数不能为空" });
            context.ExceptionHandled = true;
        }
    }
}

// 全局注册
builder.Services.AddControllers(options =>
{
    options.Filters.Add<CustomExceptionFilter>();
});

五、依赖注入

1. 服务注册

csharp
// Program.cs
builder.Services.AddScoped<IUserService, UserService>();
builder.Services.AddSingleton<ICacheService, CacheService>();
builder.Services.AddTransient<ILogger, Logger>();

生命周期:

  • Singleton - 单例,整个应用生命周期
  • Scoped - 作用域,每次请求创建一个实例
  • Transient - 瞬时,每次注入都创建新实例

2. 构造函数注入

csharp
public class HomeController : Controller
{
    private readonly IUserService _userService;
    private readonly ILogger<HomeController> _logger;
    
    public HomeController(
        IUserService userService,
        ILogger<HomeController> logger)
    {
        _userService = userService;
        _logger = logger;
    }
    
    public async Task<IActionResult> Index()
    {
        var users = await _userService.GetAllUsersAsync();
        _logger.LogInformation("获取了 {Count} 个用户", users.Count);
        return View(users);
    }
}

六、常见面试题

Q1: MVC 的执行流程?

  1. 请求到达 - 路由匹配到对应的 Controller 和 Action
  2. 模型绑定 - 将请求数据绑定到参数或模型
  3. 过滤器执行 - 授权、动作、异常过滤器依次执行
  4. Action 执行 - 执行业务逻辑
  5. 结果处理 - 返回 View、JSON 等结果
  6. 视图渲染 - 如果是 ViewResult,渲染视图

Q2: TempData、ViewData、ViewBag 的区别?

特性TempDataViewDataViewBag
类型DictionaryDictionarydynamic
生命周期跨 Action当前 Action当前 Action
类型安全
使用场景重定向传递数据传递数据到视图传递数据到视图
csharp
// TempData - 跨 Action
TempData["Message"] = "保存成功";
return RedirectToAction("Index");
// 在 Index Action 中可以读取

// ViewData - 当前 Action
ViewData["Users"] = users;
return View();

// ViewBag - 当前 Action
ViewBag.Users = users;
return View();

Q3: ActionResult vs IActionResult?

csharp
// ActionResult<T> - 强类型,有类型提示
public async Task<ActionResult<User>> GetUser(int id)
{
    var user = await _userService.GetUserByIdAsync(id);
    if (user == null)
    {
        return NotFound();
    }
    return user; // 自动转换为 Ok(user)
}

// IActionResult - 更灵活
public async Task<IActionResult> GetUser(int id)
{
    var user = await _userService.GetUserByIdAsync(id);
    if (user == null)
    {
        return NotFound();
    }
    return Ok(user);
}

Q4: 如何处理跨站请求伪造(CSRF)?

csharp
// 在视图中添加防伪令牌
@using (Html.BeginForm("Create", "Users", FormMethod.Post))
{
    @Html.AntiForgeryToken()
    <!-- 表单内容 -->
}

// 在 Action 中验证
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Create(User user)
{
    // ...
}

Q5: 如何实现自定义模型绑定?

csharp
public class CustomModelBinder : IModelBinder
{
    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        var value = bindingContext.ValueProvider.GetValue("custom").FirstValue;
        
        // 自定义绑定逻辑
        var model = ParseCustomValue(value);
        
        bindingContext.Result = ModelBindingResult.Success(model);
        return Task.CompletedTask;
    }
}

// 使用
[ModelBinder(typeof(CustomModelBinder))]
public class CustomModel { }

七、最佳实践

  1. 使用异步方法 - async Task<IActionResult>
  2. 使用依赖注入 - 通过构造函数注入服务
  3. 分离关注点 - Controller 只处理请求,业务逻辑放在 Service 层
  4. 使用 ViewModel - 不要直接传递 Domain Model 到视图
  5. 验证输入 - 使用 Data Annotations 或 FluentValidation
  6. 错误处理 - 使用全局异常过滤器
  7. 使用特性路由 - 更清晰、更灵活
  8. 避免在 Controller 中写业务逻辑
  9. 避免在视图中写复杂逻辑
  10. 不要忽略异常处理

八、性能优化

1. 启用响应压缩

csharp
builder.Services.AddResponseCompression();
app.UseResponseCompression();

2. 启用缓存

csharp
[ResponseCache(Duration = 3600)]
public IActionResult Index()
{
    return View();
}

3. 异步操作

csharp
public async Task<IActionResult> GetUsers()
{
    var users = await _userService.GetAllUsersAsync();
    return View(users);
}

4. 使用异步视图组件

csharp
public class MenuViewComponent : ViewComponent
{
    public async Task<IViewComponentResult> InvokeAsync()
    {
        var items = await GetMenuItemsAsync();
        return View(items);
    }
}

基于 VitePress 构建 | Copyright © 2026-present