当前位置: 首页 > news >正文

.NET MVC中实现后台商品列表功能

详细讲解在.NET MVC中实现后台商品列表的增删改查和图片上传功能。

📋 功能概览

功能模块主要职责关键技术
商品模型定义商品数据结构Entity Framework, Data Annotations
商品控制器处理CRUD操作和图片上传MVC Controller, HttpPost
列表视图显示商品表格Razor语法, HTML Helpers
表单视图创建/编辑商品Bootstrap表单, 文件上传
图片处理文件上传和存储HttpPostedFileBase, 路径处理

🗂️ 数据模型设计

商品模型 (Product.cs)

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;public class Product
{public int ProductID { get; set; }[Required(ErrorMessage = "商品名称不能为空")][StringLength(100, ErrorMessage = "商品名称不能超过100个字符")][Display(Name = "商品名称")]public string Name { get; set; }[Display(Name = "商品描述")][DataType(DataType.MultilineText)]public string Description { get; set; }[Required(ErrorMessage = "价格不能为空")][Range(0.01, 10000, ErrorMessage = "价格必须在0.01到10000之间")][Display(Name = "价格")]public decimal Price { get; set; }[Display(Name = "库存数量")]public int StockQuantity { get; set; }[Display(Name = "是否上架")]public bool IsActive { get; set; } = true;[Display(Name = "创建时间")]public DateTime CreateTime { get; set; } = DateTime.Now;[Display(Name = "图片路径")]public string ImagePath { get; set; }// 分类外键(可选)[Display(Name = "商品分类")]public int? CategoryID { get; set; }public virtual Category Category { get; set; }[NotMapped][Display(Name = "商品图片")]public HttpPostedFileBase ImageFile { get; set; }
}// 商品分类模型(可选)
public class Category
{public int CategoryID { get; set; }[Required][StringLength(50)][Display(Name = "分类名称")]public string CategoryName { get; set; }public virtual ICollection<Product> Products { get; set; }
}

数据库上下文 (ApplicationDbContext.cs)

using System.Data.Entity;public class ApplicationDbContext : DbContext
{public ApplicationDbContext() : base("DefaultConnection"){}public DbSet<Product> Products { get; set; }public DbSet<Category> Categories { get; set; }protected override void OnModelCreating(DbModelBuilder modelBuilder){// 配置关系modelBuilder.Entity<Product>().HasOptional(p => p.Category).WithMany(c => c.Products).HasForeignKey(p => p.CategoryID);}
}

⚙️ 商品控制器实现

ProductsController.cs

using System.Data.Entity;
using System.IO;
using System.Linq;
using System.Web;
using System.Web.Mvc;public class ProductsController : Controller
{private ApplicationDbContext db = new ApplicationDbContext();// GET: 商品列表public ActionResult Index(string searchString, string sortOrder, int? categoryId){ViewBag.NameSortParam = string.IsNullOrEmpty(sortOrder) ? "name_desc" : "";ViewBag.PriceSortParam = sortOrder == "price" ? "price_desc" : "price";var products = db.Products.Include(p => p.Category);// 搜索过滤if (!string.IsNullOrEmpty(searchString)){products = products.Where(p => p.Name.Contains(searchString) || p.Description.Contains(searchString));}// 分类过滤if (categoryId.HasValue){products = products.Where(p => p.CategoryID == categoryId);}// 排序switch (sortOrder){case "name_desc":products = products.OrderByDescending(p => p.Name);break;case "price":products = products.OrderBy(p => p.Price);break;case "price_desc":products = products.OrderByDescending(p => p.Price);break;default:products = products.OrderBy(p => p.Name);break;}// 分类下拉列表数据ViewBag.CategoryID = new SelectList(db.Categories, "CategoryID", "CategoryName");return View(products.ToList());}// GET: 商品详情public ActionResult Details(int? id){if (id == null){return new HttpStatusCodeResult(HttpStatusCode.BadRequest);}Product product = db.Products.Include(p => p.Category).FirstOrDefault(p => p.ProductID == id);if (product == null){return HttpNotFound();}return View(product);}// GET: 创建商品public ActionResult Create(){ViewBag.CategoryID = new SelectList(db.Categories, "CategoryID", "CategoryName");return View();}// POST: 创建商品[HttpPost][ValidateAntiForgeryToken]public ActionResult Create(Product product){if (ModelState.IsValid){// 处理图片上传if (product.ImageFile != null && product.ImageFile.ContentLength > 0){product.ImagePath = SaveImage(product.ImageFile);}db.Products.Add(product);db.SaveChanges();TempData["SuccessMessage"] = "商品创建成功!";return RedirectToAction("Index");}ViewBag.CategoryID = new SelectList(db.Categories, "CategoryID", "CategoryName", product.CategoryID);return View(product);}// GET: 编辑商品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, "CategoryID", "CategoryName", product.CategoryID);return View(product);}// POST: 编辑商品[HttpPost][ValidateAntiForgeryToken]public ActionResult Edit(Product product){if (ModelState.IsValid){var existingProduct = db.Products.Find(product.ProductID);if (existingProduct == null){return HttpNotFound();}// 处理图片上传if (product.ImageFile != null && product.ImageFile.ContentLength > 0){// 删除旧图片if (!string.IsNullOrEmpty(existingProduct.ImagePath)){DeleteImage(existingProduct.ImagePath);}existingProduct.ImagePath = SaveImage(product.ImageFile);}// 更新其他字段existingProduct.Name = product.Name;existingProduct.Description = product.Description;existingProduct.Price = product.Price;existingProduct.StockQuantity = product.StockQuantity;existingProduct.IsActive = product.IsActive;existingProduct.CategoryID = product.CategoryID;db.Entry(existingProduct).State = EntityState.Modified;db.SaveChanges();TempData["SuccessMessage"] = "商品更新成功!";return RedirectToAction("Index");}ViewBag.CategoryID = new SelectList(db.Categories, "CategoryID", "CategoryName", product.CategoryID);return View(product);}// GET: 删除商品public ActionResult Delete(int? id){if (id == null){return new HttpStatusCodeResult(HttpStatusCode.BadRequest);}Product product = db.Products.Include(p => p.Category).FirstOrDefault(p => p.ProductID == id);if (product == null){return HttpNotFound();}return View(product);}// POST: 删除商品[HttpPost, ActionName("Delete")][ValidateAntiForgeryToken]public ActionResult DeleteConfirmed(int id){Product product = db.Products.Find(id);if (product == null){return HttpNotFound();}// 删除图片文件if (!string.IsNullOrEmpty(product.ImagePath)){DeleteImage(product.ImagePath);}db.Products.Remove(product);db.SaveChanges();TempData["SuccessMessage"] = "商品删除成功!";return RedirectToAction("Index");}// 图片保存方法private string SaveImage(HttpPostedFileBase imageFile){try{// 验证文件类型string[] allowedExtensions = { ".jpg", ".jpeg", ".png", ".gif" };string fileExtension = Path.GetExtension(imageFile.FileName).ToLower();if (!allowedExtensions.Contains(fileExtension)){throw new Exception("只支持 JPG, JPEG, PNG, GIF 格式的图片");}// 验证文件大小(最大2MB)if (imageFile.ContentLength > 2 * 1024 * 1024){throw new Exception("图片大小不能超过2MB");}// 生成唯一文件名string fileName = Guid.NewGuid().ToString() + fileExtension;string virtualPath = "~/Content/ProductImages/" + fileName;string physicalPath = Server.MapPath(virtualPath);// 确保目录存在string directory = Path.GetDirectoryName(physicalPath);if (!Directory.Exists(directory)){Directory.CreateDirectory(directory);}// 保存文件imageFile.SaveAs(physicalPath);return virtualPath;}catch (Exception ex){ModelState.AddModelError("ImageFile", "图片上传失败: " + ex.Message);return null;}}// 删除图片方法private void DeleteImage(string imagePath){try{string physicalPath = Server.MapPath(imagePath);if (System.IO.File.Exists(physicalPath)){System.IO.File.Delete(physicalPath);}}catch (Exception ex){// 记录日志,但不中断操作System.Diagnostics.Debug.WriteLine("删除图片失败: " + ex.Message);}}protected override void Dispose(bool disposing){if (disposing){db.Dispose();}base.Dispose(disposing);}
}

🖥️ 视图实现

Index.cshtml (商品列表)

@model IEnumerable<Product>@{ViewBag.Title = "商品管理";
}<h2>商品管理</h2><div class="well">@using (Html.BeginForm("Index", "Products", FormMethod.Get, new { @class = "form-inline" })){<div class="form-group"><input type="text" name="searchString" class="form-control" placeholder="搜索商品..." value="@ViewBag.SearchString" /></div><div class="form-group">@Html.DropDownList("categoryId", ViewBag.CategoryID as SelectList, "所有分类", new { @class = "form-control" })</div><button type="submit" class="btn btn-primary">搜索</button>@Html.ActionLink("重置", "Index", null, new { @class = "btn btn-default" })}
</div><p>@Html.ActionLink("创建新商品", "Create", null, new { @class = "btn btn-success" })
</p>@if (TempData["SuccessMessage"] != null)
{<div class="alert alert-success">@TempData["SuccessMessage"]</div>
}<table class="table table-striped table-bordered"><thead><tr><th>图片</th><th>@Html.ActionLink("商品名称", "Index", new { sortOrder = ViewBag.NameSortParam, searchString = ViewBag.SearchString })</th><th>描述</th><th>@Html.ActionLink("价格", "Index", new { sortOrder = ViewBag.PriceSortParam, searchString = ViewBag.SearchString })</th><th>库存</th><th>状态</th><th>操作</th></tr></thead><tbody>@foreach (var item in Model){<tr><td>@if (!string.IsNullOrEmpty(item.ImagePath)){<img src="@Url.Content(item.ImagePath)" alt="@item.Name" style="max-width: 60px; max-height: 60px;" class="img-thumbnail" />}else{<span class="text-muted">无图片</span>}</td><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.StockQuantity)</td><td>@if (item.IsActive){<span class="label label-success">上架</span>}else{<span class="label label-danger">下架</span>}</td><td><div class="btn-group">@Html.ActionLink("详情", "Details", new { id = item.ProductID }, new { @class = "btn btn-xs btn-info" })@Html.ActionLink("编辑", "Edit", new { id = item.ProductID }, new { @class = "btn btn-xs btn-warning" })@Html.ActionLink("删除", "Delete", new { id = item.ProductID }, new { @class = "btn btn-xs btn-danger" })</div></td></tr>}</tbody>
</table>

Create.cshtml (创建商品)

@model Product@{ViewBag.Title = "创建商品";
}<h2>创建商品</h2>@using (Html.BeginForm("Create", "Products", FormMethod.Post, new { enctype = "multipart/form-data" }))
{@Html.AntiForgeryToken()<div class="form-horizontal"><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.TextAreaFor(model => model.Description, new { @class = "form-control", rows = 4 })@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.StockQuantity, htmlAttributes: new { @class = "control-label col-md-2" })<div class="col-md-10">@Html.EditorFor(model => model.StockQuantity, new { htmlAttributes = new { @class = "form-control" } })@Html.ValidationMessageFor(model => model.StockQuantity, "", new { @class = "text-danger" })</div></div><div class="form-group">@Html.LabelFor(model => model.CategoryID, "商品分类", htmlAttributes: new { @class = "control-label col-md-2" })<div class="col-md-10">@Html.DropDownList("CategoryID", null, "请选择分类", new { @class = "form-control" })@Html.ValidationMessageFor(model => model.CategoryID, "", new { @class = "text-danger" })</div></div><div class="form-group">@Html.LabelFor(model => model.ImageFile, htmlAttributes: new { @class = "control-label col-md-2" })<div class="col-md-10">@Html.TextBoxFor(model => model.ImageFile, new { type = "file", @class = "form-control" })@Html.ValidationMessageFor(model => model.ImageFile, "", new { @class = "text-danger" })<span class="help-block">支持 JPG, PNG, GIF 格式,最大 2MB</span></div></div><div class="form-group">@Html.LabelFor(model => model.IsActive, htmlAttributes: new { @class = "control-label col-md-2" })<div class="col-md-10"><div class="checkbox">@Html.EditorFor(model => model.IsActive)@Html.ValidationMessageFor(model => model.IsActive, "", new { @class = "text-danger" })</div></div></div><div class="form-group"><div class="col-md-offset-2 col-md-10"><input type="submit" value="创建" class="btn btn-primary" />@Html.ActionLink("返回列表", "Index", null, new { @class = "btn btn-default" })</div></div></div>
}@section Scripts {@Scripts.Render("~/bundles/jqueryval")
}

Edit.cshtml (编辑商品)

@model Product@{ViewBag.Title = "编辑商品";
}<h2>编辑商品</h2>@using (Html.BeginForm("Edit", "Products", FormMethod.Post, new { enctype = "multipart/form-data" }))
{@Html.AntiForgeryToken()@Html.HiddenFor(model => model.ProductID)<div class="form-horizontal"><hr />@Html.ValidationSummary(true, "", new { @class = "text-danger" })<!-- 表单字段与Create视图类似,这里省略重复部分 --><div class="form-group">@Html.LabelFor(model => model.ImageFile, htmlAttributes: new { @class = "control-label col-md-2" })<div class="col-md-10"><!-- 显示现有图片 -->@if (!string.IsNullOrEmpty(Model.ImagePath)){<div class="current-image"><img src="@Url.Content(Model.ImagePath)" alt="当前图片" class="img-thumbnail" style="max-width: 200px;" /><br /><small>当前图片</small></div>}@Html.TextBoxFor(model => model.ImageFile, new { type = "file", @class = "form-control" })@Html.ValidationMessageFor(model => model.ImageFile, "", new { @class = "text-danger" })<span class="help-block">如要更换图片,请选择新图片</span></div></div><div class="form-group"><div class="col-md-offset-2 col-md-10"><input type="submit" value="保存" class="btn btn-primary" />@Html.ActionLink("返回列表", "Index", null, new { @class = "btn btn-default" })</div></div></div>
}@section Scripts {@Scripts.Render("~/bundles/jqueryval")
}

🔧 路由配置

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 = "Products", action = "Index", id = UrlParameter.Optional });}
}

📝 数据库迁移

在程序包管理器控制台中执行:

# 启用迁移
Enable-Migrations# 添加迁移
Add-Migration InitialCreate# 更新数据库
Update-Database

💡 高级功能扩展

1. 分页功能

安装PagedList.Mvc NuGet包:

// 在控制器中添加分页
public ActionResult Index(string searchString, string sortOrder, int? categoryId, int? page)
{var pageNumber = page ?? 1;var pageSize = 10;var products = // ... 查询逻辑return View(products.ToPagedList(pageNumber, pageSize));
}

2. 图片缩略图生成

private string SaveImageWithThumbnail(HttpPostedFileBase imageFile)
{// 保存原图string originalPath = SaveImage(imageFile);// 生成缩略图using (var image = System.Drawing.Image.FromStream(imageFile.InputStream)){var thumbWidth = 200;var thumbHeight = (int)(image.Height * ((double)thumbWidth / image.Width));using (var thumb = new Bitmap(thumbWidth, thumbHeight))using (var graphic = Graphics.FromImage(thumb)){graphic.DrawImage(image, 0, 0, thumbWidth, thumbHeight);string thumbFileName = "thumb_" + Path.GetFileName(originalPath);string thumbPath = Server.MapPath("~/Content/ProductImages/Thumbs/" + thumbFileName);thumb.Save(thumbPath, ImageFormat.Jpeg);}}return originalPath;
}

🚀 部署注意事项

  1. 图片存储:生产环境中考虑使用云存储(如Azure Blob Storage、AWS S3)

  2. 安全性:添加权限验证,确保只有管理员可以访问后台

  3. 性能优化:实现图片缓存、数据库查询优化

  4. 错误处理:添加全局异常处理

这个完整的实现提供了商品管理的所有基本功能,你可以根据具体需求进行调整和扩展。

http://www.dtcms.com/a/426760.html

相关文章:

  • oracle logwr,ckpt,dbwn 如何协同工作的
  • C# 网络通讯核心知识点笔记
  • Ubuntu之apt安装ClickHouse数据库
  • 在线音乐网站开发现状网站全屏弹出窗口
  • 泛型在Java集合框架中的应用有哪些?
  • 服务器中使用Docker部署前端项目
  • mysql之二进制日志
  • 【完整源码+数据集+部署教程】染色体图像分割系统: yolov8-seg-KernelWarehouse
  • Docker MySQL 使用全流程
  • Visual Studio主题、字体、快捷键、开发环境设置,自用
  • 火山 19 混音伴音 接口
  • Bean精细化控制属性序列化反序列化
  • 前端权限模型——RBAC
  • 杭州知名网站建设公司电商网站建设好么
  • 不忘初心网站建设做网站的工具
  • VSCode实现字符串粘贴转义复制反转义
  • 第2章 三个小工具的编写(2)
  • 【DeepResearch】langgraph自主迭代研究能力的 AI 代理及分析
  • 专题:2025医药行业数智赋能与AI应用全景研究报告|附200+份报告PDF、数据仪表盘汇总下载
  • Alibaba Cloud Linux 3 +Docker 部署 ThinkPHP6 (宝塔环境)-问题篇
  • LeetCode 刷题【93. 复原 IP 地址】
  • Python类和对象----实例属性,类属性(这是我理解类和对象最透彻的一次!!)
  • 文档解析Logics-Parsing
  • 网站设计报价.doc网页端
  • IDEA中Add New Configuration找不到Tomcat
  • 学习笔记092——Windows如何将 jar 包启动设置成系统服务
  • React前端框架有哪些?
  • 文昌市建设局网站无锡工厂网站建设
  • 龙岗网站建设-信科网络深圳房地产论坛家在深圳
  • Ceph 分布式存储学习笔记(三):块存储和对象存储管理