1 创建 Web 应用
选择“ASP.NET Core Web 应用(模型-视图-控制器)模板
“项目名称”输入 MvcMovie
选择“.NET 6.0 (长期支持)”
1.1 默认生成的Program.cs
namespace MvcTest1
{
public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllersWithViews();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
}
}
}
不进行调试的情况下,通过选择 Ctrl+F5 启动应用
2 添加HelloWorld控制器
HelloWorldController.cs
添加空控制器默认生成
using Microsoft.AspNetCore.Mvc;
namespace MvcTest1.Controllers
{
public class HelloWorldController : Controller
{
public IActionResult Index()
{
return View();
}
}
}
修改为
using Microsoft.AspNetCore.Mvc;
using System.Text.Encodings.Web;
namespace MvcMovie.Controllers
{
public class HelloWorldController : Controller
{
//
// GET: /HelloWorld/
public string Index()
{
return "This is my default action...";
}
//
// GET: /HelloWorld/Welcome/
public string Welcome()
{
return "This is the Welcome action method...";
}
}
}
3 添加HelloWorld的视图
3.1 添加Index视图
public IActionResult Index()
{
return View();
}
Views/HelloWorld/Index.cshtml
@{
ViewData["Title"] = "Index";
}
<h2>Index</h2>
<p>Hello from our View Template!</p>
3.2 更改布局文件中的标题、页脚和菜单链接
默认布局文件
Views\Shared\Layout.cshtml
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - MvcTest1</title>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
<link rel="stylesheet" href="~/MvcTest1.styles.css" asp-append-version="true" />
</head>
<body>
<header>
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
<div class="container-fluid">
<a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">MvcTest1</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse" aria-controls="navbarSupportedContent"
aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
<ul class="navbar-nav flex-grow-1">
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
</li>
</ul>
</div>
</div>
</nav>
</header>
<div class="container">
<main role="main" class="pb-3">
@RenderBody()
</main>
</div>
<footer class="border-top footer text-muted">
<div class="container">
© 2023 - MvcTest1 - <a asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
</div>
</footer>
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
@await RenderSectionAsync("Scripts", required: false)
</body>
</html>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="HelloWorld" asp-action="Index">HelloWorld</a>
</li>
3.3 将数据从控制器传递给视图
public IActionResult Welcome(string name, int numTimes = 1)
{
ViewData["Message"] = "Hello " + name;
ViewData["NumTimes"] = numTimes;
return View();
}
Views/HelloWorld/Welcome.cshtml
@{
ViewData["Title"] = "Welcome";
}
<h2>Welcome</h2>
<ul>
@for (int i = 0; i < (int)ViewData["NumTimes"]!; i++)
{
<li>@ViewData["Message"]</li>
}
</ul>
4 添加模型
4.1 添加数据模型类Movie
using System.ComponentModel.DataAnnotations;
namespace MvcMovie.Models
{
public class Movie
{
public int Id { get; set; }
public string? Title { get; set; }
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
public string? Genre { get; set; }
public decimal Price { get; set; }
}
}
4.2 添加数据库上下文类
Install-Package Microsoft.EntityFrameworkCore.Design
Install-Package Microsoft.EntityFrameworkCore.SqlServer
4.3 添加包含视图的 MVC 控制器(使用实体框架)
MoviesController.cs
并发修改同一条数据时候,默认情况后来更改的数据会覆盖原来的
默认生成的内容如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore;
using MvcTest1.Models;
namespace MvcTest1.Controllers
{
public class MoviesController : Controller
{
private readonly MvcTest1DbContext _context;
public MoviesController(MvcTest1DbContext context)
{
_context = context;
}
// GET: Movies
public async Task<IActionResult> Index()
{
return _context.Movie != null ?
View(await _context.Movie.ToListAsync()) :
Problem("Entity set 'MvcTest1DbContext.Movie' is null.");
}
// GET: Movies/Details/5
public async Task<IActionResult> Details(int? id)
{
if (id == null || _context.Movie == null)
{
return NotFound();
}
var movie = await _context.Movie
.FirstOrDefaultAsync(m => m.Id == id);
if (movie == null)
{
return NotFound();
}
return View(movie);
}
// GET: Movies/Create
public IActionResult Create()
{
return View();
}
// POST: Movies/Create
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("Id,Title,ReleaseDate,Genre,Price")] Movie movie)
{
if (ModelState.IsValid)
{
_context.Add(movie);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(movie);
}
// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
if (id == null || _context.Movie == null)
{
return NotFound();
}
var movie = await _context.Movie.FindAsync(id);
if (movie == null)
{
return NotFound();
}
return View(movie);
}
// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price")] Movie movie)
{
if (id != movie.Id)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(movie);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(movie.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(movie);
}
// GET: Movies/Delete/5
public async Task<IActionResult> Delete(int? id)
{
if (id == null || _context.Movie == null)
{
return NotFound();
}
var movie = await _context.Movie
.FirstOrDefaultAsync(m => m.Id == id);
if (movie == null)
{
return NotFound();
}
return View(movie);
}
// POST: Movies/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{
if (_context.Movie == null)
{
return Problem("Entity set 'MvcTest1DbContext.Movie' is null.");
}
var movie = await _context.Movie.FindAsync(id);
if (movie != null)
{
_context.Movie.Remove(movie);
}
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
private bool MovieExists(int id)
{
return (_context.Movie?.Any(e => e.Id == id)).GetValueOrDefault();
}
}
}
4.4 数据库迁移
修改默认生成的内容
"ConnectionStrings": {
"MvcTest1DbContext": "Server=.;Database=MvcTest1Db;Trusted_Connection=True;MultipleActiveResultSets=true"
}
Add-Migration InitialCreate
# 第一次运行会生成数据库
Update-Database
4.5 设定数据库种子
using Microsoft.EntityFrameworkCore;
using MvcTest1.Models;
namespace MvcTest1.Data
{
public static class SeedData
{
public static void Initialize(IServiceProvider serviceProvider)
{
using (var context = new MvcTest1DbContext(
serviceProvider.GetRequiredService<
DbContextOptions<MvcTest1DbContext>>()))
{
if (context == null || context.Movie == null)
{
throw new ArgumentNullException("Null RazorPagesMovieContext");
}
// Look for any movies.
if (context.Movie.Any())
{
return; // DB has been seeded
}
context.Movie.AddRange(
new Movie
{
Title = "When Harry Met Sally",
ReleaseDate = DateTime.Parse("1989-2-12"),
Genre = "Romantic Comedy",
Price = 7.99M
},
new Movie
{
Title = "Ghostbusters ",
ReleaseDate = DateTime.Parse("1984-3-13"),
Genre = "Comedy",
Price = 8.99M
},
new Movie
{
Title = "Ghostbusters 2",
ReleaseDate = DateTime.Parse("1986-2-23"),
Genre = "Comedy",
Price = 9.99M
},
new Movie
{
Title = "Rio Bravo",
ReleaseDate = DateTime.Parse("1959-4-15"),
Genre = "Western",
Price = 3.99M
}
);
context.SaveChanges();
}
}
}
}
var app = builder.Build();
using (var scope = app.Services.CreateScope())
{
var services = scope.ServiceProvider;
SeedData.Initialize(services);
}
5 添加搜索功能
5.1 添加标题搜索
MoviesContrller.cs
public async Task<IActionResult> Index(string searchString)
{
var movies = from m in _context.Movie
select m;
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title!.Contains(searchString));
}
return View(await movies.ToListAsync());
}
Index.cshtml
<form asp-controller="Movies" asp-action="Index">
<p>
Title: <input type="text" name="SearchString" />
<input type="submit" value="Filter" />
</p>
</form>
5.2 添加按流派搜索(使用ViewModel)
MovieGenreViewModel.cs
using Microsoft.AspNetCore.Mvc.Rendering;
using MvcTest1.Models;
namespace MvcTest1.ViewModels
{
public class MovieGenreViewModel
{
public List<Movie>? Movies { get; set; }
public SelectList? Genres { get; set; }
public string? MovieGenre { get; set; }
public string? SearchString { get; set; }
}
}
// GET: Movies
public async Task<IActionResult> Index(string movieGenre, string searchString)
{
// Use LINQ to get list of genres.
IQueryable<string> genreQuery = from m in _context.Movie
orderby m.Genre
select m.Genre;
var movies = from m in _context.Movie
select m;
if (!string.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title!.Contains(searchString));
}
if (!string.IsNullOrEmpty(movieGenre))
{
movies = movies.Where(x => x.Genre == movieGenre);
}
var movieGenreVM = new MovieGenreViewModel
{
Genres = new SelectList(await genreQuery.Distinct().ToListAsync()),
Movies = await movies.ToListAsync()
};
return View(movieGenreVM);
}
6 添加新字段
6.1 添加字段Rating
public string? Rating { get; set; }
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace MvcMovie.Models
{
public class Movie
{
public int Id { get; set; }
public string? Title { get; set; }
[Display(Name = "Release Date")]
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
public string? Genre { get; set; }
[Column(TypeName = "decimal(18, 2)")]
public decimal Price { get; set; }
public string? Rating { get; set; }
}
}
6.2 更新数据库架构,迁移数据库
add-migration AddMovieRating
update-database
{
Title = "Ghostbusters ",
ReleaseDate = DateTime.Parse("1984-3-13"),
Genre = "Comedy",
Price = 8.99M
Rating="R"
},
7 将验证规则添加到电影模型
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace RazorPagesMovie.Models
{
public class Movie
{
public int ID { get; set; }
[StringLength(60, MinimumLength = 3)]
[Required]
public string Title { get; set; } = string.Empty;
[Display(Name = "Release Date")]
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
[Range(1, 100)]
[DataType(DataType.Currency)]
[Column(TypeName = "decimal(18, 2)")]
public decimal Price { get; set; }
[RegularExpression(@"^[A-Z]+[a-zA-Z\s]*$")]
[Required]
[StringLength(30)]
public string Genre { get; set; } = string.Empty;
[RegularExpression(@"^[A-Z]+[a-zA-Z0-9""'\s-]*$")]
[StringLength(5)]
[Required]
public string Rating { get; set; } = string.Empty;
}
}
7.1 客户端验证
Edit.cshtml
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Shared/_ValidationScriptsPartial.cshtml
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Movie.Title" class="control-label"></label>
<input asp-for="Movie.Title" class="form-control" />
<span asp-validation-for="Movie.Title" class="text-danger"></span>
</div>
7.2 服务器端验证
浏览器中禁用 JavaScript
if (!ModelState.IsValid)
{
return Page();
}
7.3 验证特性和格式特性
验证特性
[Required] 和 [MinimumLength] 特性指示属性必须具有一个值。 不阻止用户输入空格来满足此验证。
[RegularExpression] 特性用于限制可输入的字符。 在上述代码中,Genre:
只能使用字母。
第一个字母必须为大写。 允许使用空格,但不允许使用数字和特殊字符。
RegularExpressionRating:
要求第一个字符为大写字母。
允许在后续空格中使用特殊字符和数字。 “PG-13”对“分级”有效,但对于“Genre”无效。
[Range] 特性将值限制在指定的范围内。
[StringLength] 特性可以设置字符串属性的最大长度,以及可选的最小长度。
从本质上来说,需要值类型(如 decimal、int、float、DateTime),但不需要 [Required] 特性。
格式特性
[Display(Name = "Release Date")]
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
[Range(1, 100)]
[DataType(DataType.Currency)]
[Column(TypeName = "decimal(18, 2)")]
public decimal Price { get; set; }
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime ReleaseDate { get; set; }
7.4 数据库迁移
Add-Migration add_validate
Update-Database