|
|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
引言
ASP.NET MVC 4是一个强大的Web应用程序框架,它基于Model-View-Controller(MVC)设计模式,使开发人员能够构建可测试、可维护且高效的应用程序。与传统的Web Forms相比,MVC模式提供了更好的控制、更清晰的关注点分离和更易于测试的代码结构。
本教程将带您深入了解ASP.NET MVC 4的核心概念和最佳实践,通过实际示例展示如何构建高效、可扩展的Web应用程序。我们将从基础概念开始,逐步深入到高级主题,包括依赖注入、安全性、性能优化和测试策略。
ASP.NET MVC 4基础
MVC架构概述
Model-View-Controller(MVC)是一种软件设计模式,它将应用程序分为三个主要组件:
• Model(模型):表示应用程序的数据和业务逻辑。
• View(视图):负责显示用户界面。
• Controller(控制器):处理用户输入,与模型交互并选择视图来呈现。
这种分离使得应用程序更易于管理、测试和修改。
ASP.NET MVC 4的核心组件
ASP.NET MVC 4框架包含以下核心组件:
1. 路由系统:将URL映射到控制器和动作方法。
2. 控制器:处理HTTP请求并生成响应。
3. 动作方法:控制器中的方法,用于执行特定操作。
4. 模型绑定:将HTTP请求数据映射到动作方法参数。
5. 视图引擎:生成HTML响应,默认使用Razor视图引擎。
6. 过滤器:提供在请求处理管道中执行额外逻辑的机制。
项目设置与配置
创建ASP.NET MVC 4项目
让我们开始创建一个新的ASP.NET MVC 4项目:
1. 打开Visual Studio。
2. 选择”文件” > “新建” > “项目”。
3. 在”新建项目”对话框中,选择”Web”模板,然后选择”ASP.NET MVC 4 Web应用程序”。
4. 输入项目名称,例如”MvcDemoApp”,然后点击”确定”。
5. 在”新建ASP.NET MVC 4项目”对话框中,选择”Internet应用程序”模板,确保Razor视图引擎被选中,然后点击”确定”。
项目结构分析
创建项目后,您将看到以下主要文件夹和文件:
• App_Data:用于存储数据库文件。
• App_Start:包含应用程序启动时执行的代码,如路由配置、捆绑配置等。
• Content:包含CSS文件和图像等静态内容。
• Controllers:包含控制器类。
• Models:包含模型类。
• Scripts:包含JavaScript文件。
• Views:包含视图文件,每个控制器有一个对应的文件夹。
• Global.asax:包含应用程序级别的事件处理程序。
• Web.config:包含应用程序配置设置。
配置路由
路由是ASP.NET MVC的核心功能之一,它定义了URL如何映射到控制器和动作方法。默认路由配置在App_Start\RouteConfig.cs文件中:
- public class RouteConfig
- {
- public static void RegisterRoutes(RouteCollection routes)
- {
- routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
- routes.MapRoute(
- name: "Default",
- url: "{controller}/{action}/{id}",
- defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
- );
- }
- }
复制代码
这个默认路由模式{controller}/{action}/{id}将URL映射到控制器的动作方法,并传递一个可选的id参数。例如,URL/Home/Index/3将映射到HomeController的Index方法,并传递id值为3。
构建模型层
创建模型类
模型是应用程序的核心,它表示数据和业务逻辑。让我们创建一个简单的产品模型:
- public class Product
- {
- public int Id { get; set; }
- public string Name { get; set; }
- public string Description { get; set; }
- public decimal Price { get; set; }
- public int CategoryId { get; set; }
- public virtual Category Category { get; set; }
- }
- public class Category
- {
- public int Id { get; set; }
- public string Name { get; set; }
- public string Description { get; set; }
- public virtual ICollection<Product> Products { get; set; }
- }
复制代码
使用Entity Framework
Entity Framework (EF) 是一个对象关系映射(ORM)框架,它使开发人员能够使用.NET对象与数据库交互。让我们配置EF来管理我们的产品数据:
首先,创建一个DbContext类:
- public class ProductContext : DbContext
- {
- public ProductContext() : base("ProductContext")
- {
- }
- public DbSet<Product> Products { get; set; }
- public DbSet<Category> Categories { get; set; }
- protected override void OnModelCreating(DbModelBuilder modelBuilder)
- {
- modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
- }
- }
复制代码
然后,在Web.config文件中添加连接字符串:
- <connectionStrings>
- <add name="ProductContext"
- connectionString="Data Source=(LocalDb)\v11.0;Initial Catalog=ProductContext;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|\ProductContext.mdf"
- providerName="System.Data.SqlClient" />
- </connectionStrings>
复制代码
数据库迁移
Entity Framework Code First迁移允许您随着模型的变化逐步更新数据库架构。要启用迁移,请按照以下步骤操作:
1. 打开包管理器控制台(工具 > NuGet包管理器 > 包管理器控制台)。
2. 运行命令Enable-Migrations。
3. 运行命令Add-Migration InitialCreate以创建初始迁移。
4. 运行命令Update-Database以应用迁移并创建数据库。
当您更改模型类时,可以创建新的迁移并更新数据库:
- Add-Migration AddProductPrice
- Update-Database
复制代码
构建控制器层
创建控制器
控制器负责处理用户请求,与模型交互并返回视图。让我们创建一个产品控制器:
- public class ProductsController : Controller
- {
- private ProductContext db = new ProductContext();
- // GET: Products
- public ActionResult Index()
- {
- var products = db.Products.Include(p => p.Category).ToList();
- return View(products);
- }
- // GET: Products/Details/5
- public ActionResult Details(int? id)
- {
- if (id == null)
- {
- return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
- }
- Product product = db.Products.Find(id);
- if (product == null)
- {
- return HttpNotFound();
- }
- return View(product);
- }
- // GET: Products/Create
- public ActionResult Create()
- {
- ViewBag.CategoryId = new SelectList(db.Categories, "Id", "Name");
- return View();
- }
- // POST: Products/Create
- [HttpPost]
- [ValidateAntiForgeryToken]
- public ActionResult Create([Bind(Include = "Id,Name,Description,Price,CategoryId")] Product product)
- {
- if (ModelState.IsValid)
- {
- db.Products.Add(product);
- db.SaveChanges();
- return RedirectToAction("Index");
- }
- ViewBag.CategoryId = new SelectList(db.Categories, "Id", "Name", product.CategoryId);
- return View(product);
- }
- // GET: Products/Edit/5
- public ActionResult Edit(int? id)
- {
- if (id == null)
- {
- return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
- }
- Product product = db.Products.Find(id);
- if (product == null)
- {
- return HttpNotFound();
- }
- ViewBag.CategoryId = new SelectList(db.Categories, "Id", "Name", product.CategoryId);
- return View(product);
- }
- // POST: Products/Edit/5
- [HttpPost]
- [ValidateAntiForgeryToken]
- public ActionResult Edit([Bind(Include = "Id,Name,Description,Price,CategoryId")] Product product)
- {
- if (ModelState.IsValid)
- {
- db.Entry(product).State = EntityState.Modified;
- db.SaveChanges();
- return RedirectToAction("Index");
- }
- ViewBag.CategoryId = new SelectList(db.Categories, "Id", "Name", product.CategoryId);
- return View(product);
- }
- // GET: Products/Delete/5
- public ActionResult Delete(int? id)
- {
- if (id == null)
- {
- return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
- }
- Product product = db.Products.Find(id);
- if (product == null)
- {
- return HttpNotFound();
- }
- return View(product);
- }
- // POST: Products/Delete/5
- [HttpPost, ActionName("Delete")]
- [ValidateAntiForgeryToken]
- public ActionResult DeleteConfirmed(int id)
- {
- Product product = db.Products.Find(id);
- db.Products.Remove(product);
- db.SaveChanges();
- return RedirectToAction("Index");
- }
- protected override void Dispose(bool disposing)
- {
- if (disposing)
- {
- db.Dispose();
- }
- base.Dispose(disposing);
- }
- }
复制代码
控制器最佳实践
以下是一些控制器最佳实践:
1. 保持控制器精简:控制器应该只协调模型和视图之间的交互,而不包含业务逻辑。
2. 使用依赖注入:避免在控制器中直接实例化依赖项,而是通过构造函数注入它们。
3. 使用异步操作:对于I/O密集型操作,使用异步动作方法以提高性能和可伸缩性。
4. 正确处理错误:使用try-catch块和错误过滤器来处理异常。
5. 使用模型绑定:利用ASP.NET MVC的模型绑定功能,而不是手动从请求中提取数据。
异步控制器
异步控制器可以提高应用程序的可伸缩性,特别是在处理I/O密集型操作时。以下是一个异步控制器的示例:
- public class ProductsController : Controller
- {
- private ProductContext db = new ProductContext();
- // GET: Products
- public async Task<ActionResult> Index()
- {
- var products = await db.Products.Include(p => p.Category).ToListAsync();
- return View(products);
- }
- // GET: Products/Details/5
- public async Task<ActionResult> Details(int? id)
- {
- if (id == null)
- {
- return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
- }
- Product product = await db.Products.FindAsync(id);
- if (product == null)
- {
- return HttpNotFound();
- }
- return View(product);
- }
- // ... 其他动作方法
- protected override void Dispose(bool disposing)
- {
- if (disposing)
- {
- db.Dispose();
- }
- base.Dispose(disposing);
- }
- }
复制代码
构建视图层
Razor视图引擎
Razor是ASP.NET MVC的默认视图引擎,它提供了一种简洁、优雅的方式来生成HTML。Razor语法使用@符号来从HTML过渡到C#代码。
以下是一个基本的Razor视图示例:
- @model IEnumerable<MvcDemoApp.Models.Product>
- @{
- ViewBag.Title = "Index";
- }
- <h2>Products</h2>
- <p>
- @Html.ActionLink("Create New", "Create")
- </p>
- <table class="table">
- <tr>
- <th>
- @Html.DisplayNameFor(model => model.Name)
- </th>
- <th>
- @Html.DisplayNameFor(model => model.Description)
- </th>
- <th>
- @Html.DisplayNameFor(model => model.Price)
- </th>
- <th>
- @Html.DisplayNameFor(model => model.Category.Name)
- </th>
- <th></th>
- </tr>
- @foreach (var item in Model) {
- <tr>
- <td>
- @Html.DisplayFor(modelItem => item.Name)
- </td>
- <td>
- @Html.DisplayFor(modelItem => item.Description)
- </td>
- <td>
- @Html.DisplayFor(modelItem => item.Price)
- </td>
- <td>
- @Html.DisplayFor(modelItem => item.Category.Name)
- </td>
- <td>
- @Html.ActionLink("Edit", "Edit", new { id=item.Id }) |
- @Html.ActionLink("Details", "Details", new { id=item.Id }) |
- @Html.ActionLink("Delete", "Delete", new { id=item.Id })
- </td>
- </tr>
- }
- </table>
复制代码
布局和部分视图
布局类似于Web Forms中的母版页,它定义了网站的整体结构。默认布局位于Views\Shared\_Layout.cshtml:
- <!DOCTYPE html>
- <html>
- <head>
- <meta charset="utf-8" />
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>@ViewBag.Title - My ASP.NET Application</title>
- @Styles.Render("~/Content/css")
- @Scripts.Render("~/bundles/modernizr")
- </head>
- <body>
- <div class="navbar navbar-inverse navbar-fixed-top">
- <div class="container">
- <div class="navbar-header">
- <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
- <span class="icon-bar"></span>
- <span class="icon-bar"></span>
- <span class="icon-bar"></span>
- </button>
- @Html.ActionLink("Application name", "Index", "Home", new { area = "" }, new { @class = "navbar-brand" })
- </div>
- <div class="navbar-collapse collapse">
- <ul class="nav navbar-nav">
- <li>@Html.ActionLink("Home", "Index", "Home")</li>
- <li>@Html.ActionLink("About", "About", "Home")</li>
- <li>@Html.ActionLink("Contact", "Contact", "Home")</li>
- </ul>
- </div>
- </div>
- </div>
- <div class="container body-content">
- @RenderBody()
- <hr />
- <footer>
- <p>© @DateTime.Now.Year - My ASP.NET Application</p>
- </footer>
- </div>
- @Scripts.Render("~/bundles/jquery")
- @Scripts.Render("~/bundles/bootstrap")
- @RenderSection("scripts", required: false)
- </body>
- </html>
复制代码
部分视图是可重用的视图片段,它们可以包含在多个视图中。创建部分视图的步骤如下:
1. 在Views\Shared文件夹中,右键单击并选择”添加” > “视图”。
2. 勾选”创建为部分视图”复选框。
3. 输入视图名称,例如_ProductList,然后点击”添加”。
以下是一个部分视图的示例:
- @model IEnumerable<MvcDemoApp.Models.Product>
- <table class="table">
- <tr>
- <th>
- @Html.DisplayNameFor(model => model.Name)
- </th>
- <th>
- @Html.DisplayNameFor(model => model.Price)
- </th>
- <th></th>
- </tr>
- @foreach (var item in Model) {
- <tr>
- <td>
- @Html.DisplayFor(modelItem => item.Name)
- </td>
- <td>
- @Html.DisplayFor(modelItem => item.Price)
- </td>
- <td>
- @Html.ActionLink("Details", "Details", new { id=item.Id })
- </td>
- </tr>
- }
- </table>
复制代码
要在其他视图中使用这个部分视图,可以使用Html.Partial或Html.RenderPartial方法:
- @Html.Partial("_ProductList", Model)
复制代码
强类型视图和HTML助手
强类型视图使用@model指令指定模型类型,这提供了编译时类型检查和IntelliSense支持。HTML助手是生成HTML元素的方法,它们可以分为三类:
1. 标准HTML助手:如Html.TextBox、Html.DropDownList等。
2. 强类型HTML助手:如Html.TextBoxFor、Html.DropDownListFor等,它们使用lambda表达式指定模型属性。
3. 模板化HTML助手:如Html.DisplayFor和Html.EditorFor,它们根据模型属性的数据类型和元数据生成HTML。
以下是一个使用强类型HTML助手的表单示例:
- @model MvcDemoApp.Models.Product
- @{
- ViewBag.Title = "Create";
- }
- <h2>Create Product</h2>
- @using (Html.BeginForm())
- {
- @Html.AntiForgeryToken()
-
- <div class="form-horizontal">
- <h4>Product</h4>
- <hr />
- @Html.ValidationSummary(true, "", new { @class = "text-danger" })
- <div class="form-group">
- @Html.LabelFor(model => model.Name, htmlAttributes: new { @class = "control-label col-md-2" })
- <div class="col-md-10">
- @Html.EditorFor(model => model.Name, new { htmlAttributes = new { @class = "form-control" } })
- @Html.ValidationMessageFor(model => model.Name, "", new { @class = "text-danger" })
- </div>
- </div>
- <div class="form-group">
- @Html.LabelFor(model => model.Description, htmlAttributes: new { @class = "control-label col-md-2" })
- <div class="col-md-10">
- @Html.EditorFor(model => model.Description, new { htmlAttributes = new { @class = "form-control" } })
- @Html.ValidationMessageFor(model => model.Description, "", new { @class = "text-danger" })
- </div>
- </div>
- <div class="form-group">
- @Html.LabelFor(model => model.Price, htmlAttributes: new { @class = "control-label col-md-2" })
- <div class="col-md-10">
- @Html.EditorFor(model => model.Price, new { htmlAttributes = new { @class = "form-control" } })
- @Html.ValidationMessageFor(model => model.Price, "", new { @class = "text-danger" })
- </div>
- </div>
- <div class="form-group">
- @Html.LabelFor(model => model.CategoryId, "CategoryId", htmlAttributes: new { @class = "control-label col-md-2" })
- <div class="col-md-10">
- @Html.DropDownList("CategoryId", null, htmlAttributes: new { @class = "form-control" })
- @Html.ValidationMessageFor(model => model.CategoryId, "", new { @class = "text-danger" })
- </div>
- </div>
- <div class="form-group">
- <div class="col-md-offset-2 col-md-10">
- <input type="submit" value="Create" class="btn btn-default" />
- </div>
- </div>
- </div>
- }
- <div>
- @Html.ActionLink("Back to List", "Index")
- </div>
- @section Scripts {
- @Scripts.Render("~/bundles/jqueryval")
- }
复制代码
数据访问
Repository模式
Repository模式是一种设计模式,它抽象了数据访问层,使应用程序与数据存储解耦。使用Repository模式可以提高代码的可测试性和可维护性。
首先,定义一个通用的Repository接口:
- public interface IRepository<T> where T : class
- {
- IEnumerable<T> GetAll();
- T GetById(int id);
- void Add(T entity);
- void Update(T entity);
- void Delete(T entity);
- void Save();
- }
复制代码
然后,实现这个接口:
- public class Repository<T> : IRepository<T> where T : class
- {
- protected ProductContext context;
- protected DbSet<T> dbSet;
- public Repository(ProductContext context)
- {
- this.context = context;
- this.dbSet = context.Set<T>();
- }
- public virtual IEnumerable<T> GetAll()
- {
- return dbSet.ToList();
- }
- public virtual T GetById(int id)
- {
- return dbSet.Find(id);
- }
- public virtual void Add(T entity)
- {
- dbSet.Add(entity);
- }
- public virtual void Update(T entity)
- {
- dbSet.Attach(entity);
- context.Entry(entity).State = EntityState.Modified;
- }
- public virtual void Delete(T entity)
- {
- if (context.Entry(entity).State == EntityState.Detached)
- {
- dbSet.Attach(entity);
- }
- dbSet.Remove(entity);
- }
- public virtual void Save()
- {
- context.SaveChanges();
- }
- }
复制代码
接下来,创建特定的Repository接口和实现:
- public interface IProductRepository : IRepository<Product>
- {
- IEnumerable<Product> GetProductsByCategory(int categoryId);
- }
- public class ProductRepository : Repository<Product>, IProductRepository
- {
- public ProductRepository(ProductContext context) : base(context)
- {
- }
- public IEnumerable<Product> GetProductsByCategory(int categoryId)
- {
- return context.Products.Where(p => p.CategoryId == categoryId).ToList();
- }
- }
复制代码
Unit of Work模式
Unit of Work模式维护一个受业务事务影响的对象列表,并协调写入更改和解决并发问题。它通常与Repository模式一起使用。
- public interface IUnitOfWork : IDisposable
- {
- IProductRepository ProductRepository { get; }
- ICategoryRepository CategoryRepository { get; }
- void Save();
- }
- public class UnitOfWork : IUnitOfWork
- {
- private ProductContext context = new ProductContext();
- private IProductRepository productRepository;
- private ICategoryRepository categoryRepository;
- public IProductRepository ProductRepository
- {
- get
- {
- if (this.productRepository == null)
- {
- this.productRepository = new ProductRepository(context);
- }
- return productRepository;
- }
- }
- public ICategoryRepository CategoryRepository
- {
- get
- {
- if (this.categoryRepository == null)
- {
- this.categoryRepository = new CategoryRepository(context);
- }
- return categoryRepository;
- }
- }
- public void Save()
- {
- context.SaveChanges();
- }
- private bool disposed = false;
- protected virtual void Dispose(bool disposing)
- {
- if (!this.disposed)
- {
- if (disposing)
- {
- context.Dispose();
- }
- }
- this.disposed = true;
- }
- public void Dispose()
- {
- Dispose(true);
- GC.SuppressFinalize(this);
- }
- }
复制代码
使用Repository和Unit of Work
现在,我们可以更新控制器以使用Repository和Unit of Work模式:
- public class ProductsController : Controller
- {
- private IUnitOfWork unitOfWork;
- public ProductsController(IUnitOfWork unitOfWork)
- {
- this.unitOfWork = unitOfWork;
- }
- // GET: Products
- public ActionResult Index()
- {
- var products = unitOfWork.ProductRepository.GetAll();
- return View(products);
- }
- // GET: Products/Details/5
- public ActionResult Details(int? id)
- {
- if (id == null)
- {
- return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
- }
- Product product = unitOfWork.ProductRepository.GetById(id.Value);
- if (product == null)
- {
- return HttpNotFound();
- }
- return View(product);
- }
- // GET: Products/Create
- public ActionResult Create()
- {
- ViewBag.CategoryId = new SelectList(unitOfWork.CategoryRepository.GetAll(), "Id", "Name");
- return View();
- }
- // POST: Products/Create
- [HttpPost]
- [ValidateAntiForgeryToken]
- public ActionResult Create([Bind(Include = "Id,Name,Description,Price,CategoryId")] Product product)
- {
- if (ModelState.IsValid)
- {
- unitOfWork.ProductRepository.Add(product);
- unitOfWork.Save();
- return RedirectToAction("Index");
- }
- ViewBag.CategoryId = new SelectList(unitOfWork.CategoryRepository.GetAll(), "Id", "Name", product.CategoryId);
- return View(product);
- }
- // GET: Products/Edit/5
- public ActionResult Edit(int? id)
- {
- if (id == null)
- {
- return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
- }
- Product product = unitOfWork.ProductRepository.GetById(id.Value);
- if (product == null)
- {
- return HttpNotFound();
- }
- ViewBag.CategoryId = new SelectList(unitOfWork.CategoryRepository.GetAll(), "Id", "Name", product.CategoryId);
- return View(product);
- }
- // POST: Products/Edit/5
- [HttpPost]
- [ValidateAntiForgeryToken]
- public ActionResult Edit([Bind(Include = "Id,Name,Description,Price,CategoryId")] Product product)
- {
- if (ModelState.IsValid)
- {
- unitOfWork.ProductRepository.Update(product);
- unitOfWork.Save();
- return RedirectToAction("Index");
- }
- ViewBag.CategoryId = new SelectList(unitOfWork.CategoryRepository.GetAll(), "Id", "Name", product.CategoryId);
- return View(product);
- }
- // GET: Products/Delete/5
- public ActionResult Delete(int? id)
- {
- if (id == null)
- {
- return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
- }
- Product product = unitOfWork.ProductRepository.GetById(id.Value);
- if (product == null)
- {
- return HttpNotFound();
- }
- return View(product);
- }
- // POST: Products/Delete/5
- [HttpPost, ActionName("Delete")]
- [ValidateAntiForgeryToken]
- public ActionResult DeleteConfirmed(int id)
- {
- Product product = unitOfWork.ProductRepository.GetById(id);
- unitOfWork.ProductRepository.Delete(product);
- unitOfWork.Save();
- return RedirectToAction("Index");
- }
- protected override void Dispose(bool disposing)
- {
- if (disposing)
- {
- unitOfWork.Dispose();
- }
- base.Dispose(disposing);
- }
- }
复制代码
实现依赖注入
依赖注入概述
依赖注入(DI)是一种设计模式,它允许我们将依赖项从外部注入到类中,而不是在类内部创建它们。这有助于实现控制反转(IoC),提高代码的可测试性和可维护性。
使用Unity容器
Unity是一个轻量级、可扩展的依赖注入容器。让我们在ASP.NET MVC 4应用程序中配置Unity:
1. 安装Unity NuGet包:Install-Package Unity.Mvc4
2. 在App_Start文件夹中创建UnityConfig.cs文件:
安装Unity NuGet包:
- Install-Package Unity.Mvc4
复制代码
在App_Start文件夹中创建UnityConfig.cs文件:
- public static class UnityConfig
- {
- public static void RegisterComponents()
- {
- var container = new UnityContainer();
- // 注册所有组件
- container.RegisterType<IUnitOfWork, UnitOfWork>();
- container.RegisterType<IProductRepository, ProductRepository>();
- container.RegisterType<ICategoryRepository, CategoryRepository>();
- DependencyResolver.SetResolver(new UnityDependencyResolver(container));
- }
- }
复制代码
1. 在Global.asax.cs文件中调用UnityConfig.RegisterComponents():
- protected void Application_Start()
- {
- AreaRegistration.RegisterAllAreas();
- FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
- RouteConfig.RegisterRoutes(RouteTable.Routes);
- BundleConfig.RegisterBundles(BundleTable.Bundles);
- AuthConfig.RegisterAuth();
-
- UnityConfig.RegisterComponents();
- }
复制代码
依赖注入的好处
使用依赖注入有以下好处:
1. 提高可测试性:可以轻松地用模拟对象替换真实依赖项,使单元测试更容易编写。
2. 降低耦合度:类不再负责创建其依赖项,而是通过构造函数或属性接收它们。
3. 提高可维护性:依赖项的更改不会影响使用它们的类。
4. 提高可重用性:类可以在不同的上下文中重用,只需提供不同的依赖项实现。
安全性最佳实践
身份验证和授权
ASP.NET MVC 4提供了多种身份验证和授权机制:
1. Forms身份验证:基于cookie的身份验证,适用于大多数Web应用程序。
2. Windows身份验证:适用于Intranet应用程序。
3. OAuth和OpenID:用于第三方身份验证,如Facebook、Google等。
以下是如何在ASP.NET MVC 4中实现基于角色的授权:
- [Authorize(Roles = "Admin")]
- public class AdminController : Controller
- {
- // 只有管理员可以访问这些动作方法
- public ActionResult Index()
- {
- return View();
- }
- }
- public class ProductsController : Controller
- {
- // 任何经过身份验证的用户都可以访问
- [Authorize]
- public ActionResult Create()
- {
- return View();
- }
- // 允许匿名访问
- [AllowAnonymous]
- public ActionResult Details(int id)
- {
- return View();
- }
- }
复制代码
防止常见攻击
ASP.NET MVC 4默认对Razor视图中的输出进行HTML编码,这有助于防止XSS攻击。但是,在使用Html.Raw方法时要小心:
- <!-- 危险:可能导致XSS攻击 -->
- @Html.Raw(Model.UserContent)
- <!-- 安全:输出被HTML编码 -->
- @Model.UserContent
复制代码
ASP.NET MVC 4提供了内置的CSRF保护。在表单中使用@Html.AntiForgeryToken(),并在动作方法上应用[ValidateAntiForgeryToken]特性:
- @using (Html.BeginForm())
- {
- @Html.AntiForgeryToken()
-
- <!-- 表单字段 -->
- }
复制代码- [HttpPost]
- [ValidateAntiForgeryToken]
- public ActionResult Create(Product product)
- {
- // 处理表单提交
- }
复制代码
使用参数化查询或ORM(如Entity Framework)来防止SQL注入攻击:
- // 危险:容易受到SQL注入攻击
- string query = "SELECT * FROM Products WHERE Name = '" + productName + "'";
- // 安全:使用参数化查询
- string query = "SELECT * FROM Products WHERE Name = @productName";
- SqlCommand command = new SqlCommand(query, connection);
- command.Parameters.AddWithValue("@productName", productName);
- // 更安全:使用Entity Framework
- var products = db.Products.Where(p => p.Name == productName).ToList();
复制代码
安全配置
在Web.config文件中,可以配置以下安全设置:
- <system.web>
- <!-- 启用SSL/TLS -->
- <httpCookies requireSSL="true" httpOnlyCookies="true" />
-
- <!-- 禁用不必要的HTTP方法 -->
- <httpProtocol>
- <customHeaders>
- <add name="X-Content-Type-Options" value="nosniff" />
- <add name="X-Frame-Options" value="SAMEORIGIN" />
- <add name="X-XSS-Protection" value="1; mode=block" />
- </customHeaders>
- </httpProtocol>
-
- <!-- 配置身份验证 -->
- <authentication mode="Forms">
- <forms loginUrl="~/Account/Login" timeout="2880" requireSSL="true" />
- </authentication>
- </system.web>
复制代码
性能优化
缓存策略
缓存是提高Web应用程序性能的有效方法。ASP.NET MVC 4提供了多种缓存选项:
使用[OutputCache]特性缓存控制器的输出:
- [OutputCache(Duration = 3600, Location = OutputCacheLocation.Client)]
- public ActionResult Index()
- {
- var products = db.Products.ToList();
- return View(products);
- }
复制代码
使用System.Runtime.Caching.MemoryCache缓存数据:
- public ActionResult Index()
- {
- var cacheKey = "Products";
- var products = MemoryCache.Default.Get(cacheKey) as List<Product>;
-
- if (products == null)
- {
- products = db.Products.ToList();
-
- var policy = new CacheItemPolicy
- {
- AbsoluteExpiration = DateTimeOffset.Now.AddHours(1)
- };
-
- MemoryCache.Default.Add(cacheKey, products, policy);
- }
-
- return View(products);
- }
复制代码
对于大型应用程序,可以使用分布式缓存,如Redis或AppFabric缓存:
- public ActionResult Index()
- {
- var cacheKey = "Products";
- var products = Cache.Get(cacheKey) as List<Product>;
-
- if (products == null)
- {
- products = db.Products.ToList();
- Cache.Add(cacheKey, products, null, DateTime.Now.AddHours(1),
- Cache.NoSlidingExpiration, CacheItemPriority.Default, null);
- }
-
- return View(products);
- }
复制代码
捆绑和缩小
捆绑和缩小是减少HTTP请求数量和文件大小的有效方法。ASP.NET MVC 4提供了内置的捆绑和缩小功能。
在App_Start\BundleConfig.cs文件中配置捆绑:
- public class BundleConfig
- {
- public static void RegisterBundles(BundleCollection bundles)
- {
- bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
- "~/Scripts/jquery-{version}.js"));
- bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
- "~/Scripts/jquery.validate*"));
- bundles.Add(new ScriptBundle("~/bundles/modernizr").Include(
- "~/Scripts/modernizr-*"));
- bundles.Add(new ScriptBundle("~/bundles/bootstrap").Include(
- "~/Scripts/bootstrap.js",
- "~/Scripts/respond.js"));
- bundles.Add(new StyleBundle("~/Content/css").Include(
- "~/Content/bootstrap.css",
- "~/Content/site.css"));
- }
- }
复制代码
在视图中使用捆绑:
- @Styles.Render("~/Content/css")
- @Scripts.Render("~/bundles/jquery")
- @Scripts.Render("~/bundles/bootstrap")
复制代码
异步处理
使用异步控制器和动作方法可以提高应用程序的可伸缩性,特别是在处理I/O密集型操作时:
- public async Task<ActionResult> Index()
- {
- var products = await db.Products.ToListAsync();
- return View(products);
- }
复制代码
优化数据库访问
优化数据库访问可以显著提高应用程序性能:
1. 使用延迟加载和预先加载:根据场景选择适当的加载策略。
2. 避免N+1查询问题:使用Include方法预先加载相关实体。
3. 使用存储过程:对于复杂查询,考虑使用存储过程。
4. 优化索引:确保数据库表有适当的索引。
- // 使用Include预先加载相关实体
- var products = await db.Products
- .Include(p => p.Category)
- .ToListAsync();
- // 使用Select只查询需要的字段
- var productNames = await db.Products
- .Where(p => p.Price > 100)
- .Select(p => p.Name)
- .ToListAsync();
复制代码
测试策略
单元测试
单元测试是验证代码单元(如方法或类)是否按预期工作的过程。ASP.NET MVC 4的可测试性设计使得编写单元测试变得容易。
以下是一个使用NUnit和Moq的单元测试示例:
- [TestFixture]
- public class ProductsControllerTests
- {
- private ProductsController controller;
- private IUnitOfWork unitOfWork;
- private IProductRepository productRepository;
- private List<Product> products;
- [SetUp]
- public void Setup()
- {
- // 创建测试数据
- products = new List<Product>
- {
- new Product { Id = 1, Name = "Product 1", Price = 10.00m },
- new Product { Id = 2, Name = "Product 2", Price = 20.00m }
- };
- // 模拟Repository
- productRepository = new Mock<IProductRepository>();
- productRepository.Setup(r => r.GetAll()).Returns(products);
- productRepository.Setup(r => r.GetById(It.IsAny<int>()))
- .Returns<int>(id => products.FirstOrDefault(p => p.Id == id));
- // 模拟UnitOfWork
- unitOfWork = new Mock<IUnitOfWork>();
- unitOfWork.Setup(u => u.ProductRepository).Returns(productRepository.Object);
- // 创建控制器
- controller = new ProductsController(unitOfWork.Object);
- }
- [Test]
- public void Index_ReturnsAllProducts()
- {
- // Act
- var result = controller.Index() as ViewResult;
- var model = result.Model as List<Product>;
- // Assert
- Assert.IsNotNull(result);
- Assert.AreEqual(2, model.Count);
- Assert.AreEqual("Product 1", model[0].Name);
- Assert.AreEqual("Product 2", model[1].Name);
- }
- [Test]
- public void Details_ValidId_ReturnsProduct()
- {
- // Act
- var result = controller.Details(1) as ViewResult;
- var model = result.Model as Product;
- // Assert
- Assert.IsNotNull(result);
- Assert.AreEqual(1, model.Id);
- Assert.AreEqual("Product 1", model.Name);
- }
- [Test]
- public void Details_InvalidId_ReturnsHttpNotFound()
- {
- // Act
- var result = controller.Details(99);
- // Assert
- Assert.IsInstanceOf<HttpNotFoundResult>(result);
- }
- }
复制代码
集成测试
集成测试验证多个组件一起工作时是否按预期执行。以下是一个使用Entity Framework的集成测试示例:
- [TestFixture]
- public class ProductRepositoryIntegrationTests
- {
- private ProductContext context;
- private IProductRepository repository;
- [SetUp]
- public void Setup()
- {
- // 创建内存数据库
- Database.DefaultConnectionFactory = new SqlCeConnectionFactory("System.Data.SqlServerCe.4.0");
- context = new ProductContext("Product_Test");
- context.Database.CreateIfNotExists();
- repository = new ProductRepository(context);
- }
- [TearDown]
- public void TearDown()
- {
- context.Database.Delete();
- context.Dispose();
- }
- [Test]
- public void Add_Product_AddsToDatabase()
- {
- // Arrange
- var product = new Product { Name = "Test Product", Price = 10.00m };
- // Act
- repository.Add(product);
- repository.Save();
- // Assert
- var result = context.Products.FirstOrDefault(p => p.Name == "Test Product");
- Assert.IsNotNull(result);
- Assert.AreEqual(10.00m, result.Price);
- }
- [Test]
- public void GetAll_ReturnsAllProducts()
- {
- // Arrange
- context.Products.Add(new Product { Name = "Product 1", Price = 10.00m });
- context.Products.Add(new Product { Name = "Product 2", Price = 20.00m });
- context.SaveChanges();
- // Act
- var result = repository.GetAll();
- // Assert
- Assert.AreEqual(2, result.Count());
- }
- }
复制代码
测试驱动开发(TDD)
测试驱动开发是一种开发方法,它要求在编写代码之前先编写测试。以下是TDD的基本步骤:
1. 编写失败的测试:编写一个测试,验证尚未实现的功能。
2. 编写最少的代码:编写足够的代码使测试通过。
3. 重构:改进代码,同时确保测试仍然通过。
以下是一个使用TDD开发产品搜索功能的示例:
- // 步骤1:编写失败的测试
- [Test]
- public void Search_ByKeyword_ReturnsMatchingProducts()
- {
- // Arrange
- var products = new List<Product>
- {
- new Product { Id = 1, Name = "Laptop", Description = "High performance laptop" },
- new Product { Id = 2, Name = "Mouse", Description = "Wireless mouse" },
- new Product { Id = 3, Name = "Keyboard", Description = "Mechanical keyboard" }
- };
-
- var mockRepository = new Mock<IProductRepository>();
- mockRepository.Setup(r => r.GetAll()).Returns(products);
-
- var controller = new ProductsController(mockUnitOfWork.Object);
-
- // Act
- var result = controller.Search("laptop") as ViewResult;
- var model = result.Model as List<Product>;
-
- // Assert
- Assert.AreEqual(1, model.Count);
- Assert.AreEqual("Laptop", model[0].Name);
- }
- // 步骤2:编写最少的代码使测试通过
- public ActionResult Search(string keyword)
- {
- var products = unitOfWork.ProductRepository.GetAll();
- var result = products.Where(p => p.Name.Contains(keyword)).ToList();
- return View(result);
- }
- // 步骤3:重构代码
- public ActionResult Search(string keyword)
- {
- if (string.IsNullOrEmpty(keyword))
- {
- return RedirectToAction("Index");
- }
-
- var products = unitOfWork.ProductRepository.GetAll();
- var result = products.Where(p =>
- p.Name.Contains(keyword) || p.Description.Contains(keyword)).ToList();
- return View(result);
- }
复制代码
部署与维护
部署策略
Visual Studio提供了Web部署功能,可以直接将应用程序部署到IIS服务器:
1. 在解决方案资源管理器中右键单击项目,选择”发布”。
2. 选择”Web部署”作为发布方法。
3. 输入服务器、站点名称和用户凭据。
4. 配置数据库连接字符串和其他设置。
5. 点击”发布”。
创建部署包以便手动部署:
1. 在解决方案资源管理器中右键单击项目,选择”发布”。
2. 选择”Web部署包”作为发布方法。
3. 指定包位置和IIS网站名称。
4. 点击”发布”。
5. 将生成的包复制到目标服务器并运行部署命令。
使用TeamCity、Jenkins或Azure DevOps等工具设置CI/CD流水线:
1. 配置源代码管理(如Git)。
2. 设置构建服务器,在代码提交时自动构建项目。
3. 配置自动测试,确保构建通过所有测试。
4. 设置自动部署到测试环境。
5. 配置批准流程,然后自动部署到生产环境。
监控和日志记录
使用日志记录框架(如NLog或log4net)记录应用程序事件:
- public class ProductsController : Controller
- {
- private readonly IUnitOfWork unitOfWork;
- private readonly ILogger logger;
- public ProductsController(IUnitOfWork unitOfWork, ILogger logger)
- {
- this.unitOfWork = unitOfWork;
- this.logger = logger;
- }
- public ActionResult Index()
- {
- try
- {
- var products = unitOfWork.ProductRepository.GetAll();
- logger.Info("Retrieved {0} products", products.Count());
- return View(products);
- }
- catch (Exception ex)
- {
- logger.Error(ex, "Error retrieving products");
- throw;
- }
- }
- }
复制代码
使用Application Insights或New Relic等工具监控应用程序性能:
1. 安装NuGet包:Install-Package Microsoft.ApplicationInsights.Web
2. 在ApplicationInsights.config文件中配置检测键。
3. 在代码中添加自定义遥测:
安装NuGet包:
- Install-Package Microsoft.ApplicationInsights.Web
复制代码
在ApplicationInsights.config文件中配置检测键。
在代码中添加自定义遥测:
- public ActionResult Details(int id)
- {
- var telemetry = new TelemetryClient();
- var stopwatch = Stopwatch.StartNew();
-
- try
- {
- var product = unitOfWork.ProductRepository.GetById(id);
- if (product == null)
- {
- telemetry.TrackEvent("ProductNotFound", new Dictionary<string, string> { { "ProductId", id.ToString() } });
- return HttpNotFound();
- }
-
- return View(product);
- }
- finally
- {
- stopwatch.Stop();
- telemetry.TrackMetric("ProductDetailsLoadTime", stopwatch.ElapsedMilliseconds);
- }
- }
复制代码
维护和更新
定期更新应用程序以修复错误、添加功能或提高性能:
1. 使用版本控制系统(如Git)管理代码。
2. 实施功能切换,以便在不重新部署的情况下启用或禁用功能。
3. 使用数据库迁移管理数据库架构更改。
4. 实施蓝绿部署或金丝雀发布策略,以减少部署风险。
保持应用程序安全:
1. 定期更新NuGet包以修复安全漏洞。
2. 监控安全公告和CVE(常见漏洞和暴露)。
3. 定期进行安全审计和渗透测试。
4. 实施Web应用程序防火墙(WAF)以防止常见攻击。
总结与展望
本教程详细介绍了ASP.NET MVC 4的核心概念和最佳实践,包括:
• MVC架构和ASP.NET MVC 4的核心组件
• 项目设置和配置
• 构建模型、控制器和视图
• 数据访问模式和Entity Framework
• 依赖注入和控制反转
• 安全性最佳实践
• 性能优化技术
• 测试策略和方法
• 部署和维护策略
通过遵循这些最佳实践,您可以构建高效、可扩展且易于维护的ASP.NET MVC 4应用程序。
未来发展方向
虽然ASP.NET MVC 4是一个强大的框架,但Microsoft已经发布了更新的版本,如ASP.NET MVC 5和ASP.NET Core MVC。这些新版本提供了许多改进和新功能,包括:
• 更好的性能和可伸缩性
• 跨平台支持(ASP.NET Core)
• 改进的依赖注入支持
• 标记助手(Tag Helpers)
• 视图组件(View Components)
• 内置的Web API支持
如果您正在开始一个新项目,建议考虑使用ASP.NET Core MVC,它是Microsoft的最新Web开发框架,结合了ASP.NET MVC、Web API和Web Pages的优点。
持续学习资源
要继续学习ASP.NET MVC和相关技术,以下资源可能会有所帮助:
1. Microsoft文档:https://docs.microsoft.com/en-us/aspnet/mvc
2. ASP.NET网站:https://www.asp.net/mvc
3. Pluralsight课程:提供各种ASP.NET MVC课程
4. Stack Overflow:解决特定问题的问答社区
5. GitHub:查看开源ASP.NET MVC项目
通过不断学习和实践,您可以掌握ASP.NET MVC的高级概念和技术,成为一名高效的Web开发人员。 |
|