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 MVC | Web 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)}")] // 整数且最小值为14. 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>© 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 的执行流程?
- 请求到达 - 路由匹配到对应的 Controller 和 Action
- 模型绑定 - 将请求数据绑定到参数或模型
- 过滤器执行 - 授权、动作、异常过滤器依次执行
- Action 执行 - 执行业务逻辑
- 结果处理 - 返回 View、JSON 等结果
- 视图渲染 - 如果是 ViewResult,渲染视图
Q2: TempData、ViewData、ViewBag 的区别?
| 特性 | TempData | ViewData | ViewBag |
|---|---|---|---|
| 类型 | Dictionary | Dictionary | dynamic |
| 生命周期 | 跨 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 { }七、最佳实践
- ✅ 使用异步方法 -
async Task<IActionResult> - ✅ 使用依赖注入 - 通过构造函数注入服务
- ✅ 分离关注点 - Controller 只处理请求,业务逻辑放在 Service 层
- ✅ 使用 ViewModel - 不要直接传递 Domain Model 到视图
- ✅ 验证输入 - 使用 Data Annotations 或 FluentValidation
- ✅ 错误处理 - 使用全局异常过滤器
- ✅ 使用特性路由 - 更清晰、更灵活
- ❌ 避免在 Controller 中写业务逻辑
- ❌ 避免在视图中写复杂逻辑
- ❌ 不要忽略异常处理
八、性能优化
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);
}
}