1. 管理控制器开发
管理控制器是NopCommerce管理区域的核心组件,负责处理管理请求、执行业务逻辑并返回视图或数据。管理控制器继承自BaseAdminController类,该类提供了管理区域所需的基本功能)
1.1 基本结构
using Microsoft.AspNetCore.Mvc;
using Nop.Web.Framework.Controllers;
using Nop.Web.Framework.Mvc.Filters;
using Nop.Web.Areas.Admin.Infrastructure.Mapper.Extensions;
using Nop.Services.Catalog;
using Nop.Web.Areas.Admin.Models.Catalog;
namespace Nop.Web.Areas.Admin.Controllers
{
[Area("Admin")]
[AuthorizeAdmin]
[AutoValidateAntiforgeryToken]
[ValidateIpAddress]
public partial class ProductController : BaseAdminController
{
private readonly IProductService _productService;
private readonly ICategoryService _categoryService;
private readonly IPictureService _pictureService;
public ProductController(
IProductService productService,
ICategoryService categoryService,
IPictureService pictureService)
{
_productService = productService;
_categoryService = categoryService;
_pictureService = pictureService;
}
// GET: Admin/Product/List
public virtual async Task<IActionResult> List()
{
return View();
}
// GET: Admin/Product/Create
public virtual async Task<IActionResult> Create()
{
var model = new ProductModel();
// 准备模型数据
await PrepareProductModelAsync(model, null, true);
return View(model);
}
// POST: Admin/Product/Create
[HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")]
public virtual async Task<IActionResult> Create(ProductModel model, bool continueEditing)
{
if (!ModelState.IsValid)
{
await PrepareProductModelAsync(model, null, true);
return View(model);
}
var product = model.ToEntity<Product>();
await _productService.InsertProductAsync(product);
SuccessNotification("Product added successfully");
return continueEditing ) RedirectToAction("Edit", new { id = product.Id }) : RedirectToAction("List");
}
// 其他动作方法...
// 准备产品模型
protected virtual async Task PrepareProductModelAsync(ProductModel model, Product product, bool excludeProperties = false)
{
// 准备分类列表
var categories = await _categoryService.GetAllCategoriesAsync(showHidden: true);
model.AvailableCategories = categories.Select(c => new SelectListItem {
Text = c.GetFormattedBreadCrumb(categories),
Value = c.Id.ToString()
}).ToList();
// 准备制造商列表
// ...
// 准备产品属性 // ...
}
}
}
1.2 核心特性
1.2.1 权限验证
管理控制器使用AuthorizeAdmin过滤器进行权限验证:
[AuthorizeAdmin]
public partial class ProductController : BaseAdminController
{
// 控制器实现}
1.2.2 防跨站请求伪造(CSRF)
使用AutoValidateAntiforgeryToken过滤器防止CSRF攻击)
[AutoValidateAntiforgeryToken]
public partial class ProductController : BaseAdminController
{
// 控制器实现}
1.2.3 IP地址验证
使用ValidateIpAddress过滤器限制只有允许的IP地址才能访问管理区域)
[ValidateIpAddress]
public partial class ProductController : BaseAdminController
{
// 控制器实现}
1.2.4 通知机制
使用SuccessNotification、ErrorNotification等方法向用户显示通知消息)
SuccessNotification("Product added successfully");
ErrorNotification("Failed to add product: " + ex.Message);
WarningNotification("Product stock is low");
InformationNotification("Product updated");
1.3 动作方法设计
1.3.1 CRUD动作
管理控制器通常实现以下CRUD动作)
- List:显示数据列)- Create:显示创建表- Edit:显示编辑表- Delete:删除数- BatchDelete:批量删除数
1.3.2 AJAX动作
对于异步操作,使用JsonResult返回JSON数据库
// GET: Admin/Product/GetProductAttributes
[HttpGet]
public virtual async Task<JsonResult> GetProductAttributes(int productId)
{
var productAttributes = await _productAttributeService.GetProductAttributesAsync(productId);
var result = productAttributes.Select(pa => new {
Id = pa.Id,
Name = pa.Name,
Description = pa.Description
});
return Json(result);
}
1.3.3 导出动作
实现数据导出功能)
// GET: Admin/Product/ExportToExcel
public virtual async Task<IActionResult> ExportToExcel()
{
var products = await _productService.GetAllProductsAsync();
var exportData = products.Select(p => new {
p.Id,
p.Name,
p.Sku,
p.Price,
p.StockQuantity,
p.CreatedOnUtc
});
var bytes = await _exportManager.ExportToExcelAsync(exportData);
return File(bytes, MimeTypes.TextXlsx, "products.xlsx");
}
2. 管理视图开发
管理视图使用Razor语法编写,继承自管理区域的布局视图,提供了一致的用户界面)
2.1 视图结构
2.1.1 列表视图
@model Nop.Web.Areas.Admin.Models.Catalog.ProductListModel
@{
ViewBag.Title = "Products";
Layout = "_Layout.cshtml";
}
<h1>Products</h1>
<div class="card">
<div class="card-header">
<h3 class="card-title">Product List</h3>
<div class="card-tools">
<a href="@Url.Action("Create", "Product")" class="btn btn-primary">
<i class="fas fa-plus"></i> Add New
</a>
</div>
</div>
<div class="card-body">
<div class="table-responsive">
<table id="products-grid" class="table table-striped table-bordered table-hover">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Sku</th>
<th>Price</th>
<th>Stock</th>
<th>Status</th>
<th>Created On</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<!-- 数据将通过AJAX加载 -->
</tbody>
</table>
</div>
</div>
</div>
@section Scripts {
<script>
$(document).ready(function() {
$('#products-grid').DataTable({
ajax: {
url: '@Url.Action("List", "Product")',
type: 'POST',
dataType: 'json'
},
columns: [
{ data: 'Id' },
{ data: 'Name' },
{ data: 'Sku' },
{ data: 'Price' },
{ data: 'StockQuantity' },
{ data: 'Published', render: function(data) { return data ) '<span class="badge badge-success">Published</span>' : '<span class="badge badge-danger">Unpublished</span>'; } },
{ data: 'CreatedOnUtc' },
{ data: 'Id', render: function(data) {
return '<a href="@Url.Action("Edit", "Product"))id=' + data + '" class="btn btn-sm btn-primary"><i class="fas fa-edit"></i></a> ' +
'<a href="@Url.Action("Delete", "Product"))id=' + data + '" class="btn btn-sm btn-danger"><i class="fas fa-trash"></i></a>';
} }
],
order: [[0, 'desc']],
pageLength: 10,
processing: true,
serverSide: true
});
});
</script>
}
2.1.2 创建/编辑视图
@model Nop.Web.Areas.Admin.Models.Catalog.ProductModel
@{
ViewBag.Title = Model.Id > 0 ) "Edit Product" : "Create Product";
Layout = "_Layout.cshtml";
}
<h1>@ViewBag.Title</h1>
@Html.Notification()
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
<div class="card">
<div class="card-header">
<h3 class="card-title">Product Information</h3>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label asp-for="Name" class="control-label required"></label>
<input asp-for="Name" class="form-control" />
<span asp-validation-for="Name" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Sku" class="control-label"></label>
<input asp-for="Sku" class="form-control" />
<span asp-validation-for="Sku" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Price" class="control-label required"></label>
<input asp-for="Price" class="form-control" />
<span asp-validation-for="Price" class="text-danger"></span>
</div>
<!-- 其他基本信息字段 -->
</div>
<div class="col-md-6">
<div class="form-group">
<label asp-for="CategoryIds" class="control-label"></label>
<select asp-for="CategoryIds" class="form-control select2" multiple>
<option value="">-- Select Categories --</option>
@foreach (var item in Model.AvailableCategories)
{
<option value="@item.Value" selected="@Model.CategoryIds.Contains(int.Parse(item.Value))">@item.Text</option>
}
</select>
<span asp-validation-for="CategoryIds" class="text-danger"></span>
</div>
<!-- 其他分类和属性字)-->
</div>
</div>
</div>
<div class="card-footer">
<div class="float-right">
<input type="submit" name="save" value="Save" class="btn btn-primary" />
<input type="submit" name="save-continue" value="Save and continue editing" class="btn btn-secondary" />
<a href="@Url.Action("List", "Product")" class="btn btn-default">Cancel</a>
</div>
</div>
</div>
}
@section Scripts {
<script>
$(document).ready(function() {
$('.select2').select2();
// 其他客户端脚) });
</script>
}
2.2 核心组件
2.2.1 布局视图
管理区域的布局视图位于/Themes/Admin/Views/Shared/_Layout.cshtml)
<!DOCTYPE html>
<html lang="@_workContext.WorkingLanguage.LanguageCulture">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewBag.Title - @_storeInformationSettings.StoreName</title>
<!-- CSS引用 -->
<link href="~/Themes/Admin/Content/css/bootstrap.min.css" rel="stylesheet" />
<link href="~/Themes/Admin/Content/css/font-awesome.min.css" rel="stylesheet" />
<link href="~/Themes/Admin/Content/css/icheck/icheck.min.css" rel="stylesheet" />
<link href="~/Themes/Admin/Content/css/admin.css" rel="stylesheet" />
@await RenderSectionAsync("Styles", required: false)
</head>
<body class="hold-transition sidebar-mini">
<div class="wrapper">
<!-- 顶部导航)-->
<nav class="main-header navbar navbar-expand navbar-white navbar-light">
@await Html.PartialAsync("_AdminHeader.cshtml")
</nav>
<!-- 侧边)-->
<aside class="main-sidebar sidebar-dark-primary elevation-4">
@await Html.PartialAsync("_AdminSidebar.cshtml")
</aside>
<!-- 主内)-->
<div class="content-wrapper">
<section class="content-header">
<div class="container-fluid">
<div class="row mb-2">
<div class="col-sm-6">
<h1>@ViewBag.Title</h1>
</div>
<div class="col-sm-6">
<ol class="breadcrumb float-sm-right">
<li class="breadcrumb-item"><a href="@Url.Action("Index", "Home", new { area = "Admin" })">Home</a></li>
@RenderSection("Breadcrumb", required: false)
<li class="breadcrumb-item active">@ViewBag.Title</li>
</ol>
</div>
</div>
</div>
</section>
<section class="content">
<div class="container-fluid">
@RenderBody()
</div>
</section>
</div>
<!-- 页脚 -->
<footer class="main-footer">
@await Html.PartialAsync("_AdminFooter.cshtml")
</footer>
</div>
<!-- JavaScript引用 -->
<script src="~/Themes/Admin/Content/js/jquery/jquery.min.js"></script>
<script src="~/Themes/Admin/Content/js/bootstrap/bootstrap.bundle.min.js"></script>
<script src="~/Themes/Admin/Content/js/icheck/icheck.min.js"></script>
<script src="~/Themes/Admin/Content/js/adminlte/adminlte.min.js"></script>
<script src="~/Themes/Admin/Content/js/common.js"></script>
@await RenderSectionAsync("Scripts", required: false)
</body>
</html>
2.2.2 通知组件
管理区域使用_Notification.cshtml组件显示通知消息)
@if (TempData.ContainsKey(NotificationDefaults.SuccessNotificationType))
{
<div class="alert alert-success alert-dismissible">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
<h5><i class="icon fas fa-check"></i> Success!</h5>
@TempData[NotificationDefaults.SuccessNotificationType]
</div>
}
@if (TempData.ContainsKey(NotificationDefaults.ErrorNotificationType))
{
<div class="alert alert-danger alert-dismissible">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
<h5><i class="icon fas fa-ban"></i> Error!</h5>
@TempData[NotificationDefaults.ErrorNotificationType]
</div>
}
@if (TempData.ContainsKey(NotificationDefaults.WarningNotificationType))
{
<div class="alert alert-warning alert-dismissible">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
<h5><i class="icon fas fa-exclamation-triangle"></i> Warning!</h5>
@TempData[NotificationDefaults.WarningNotificationType]
</div>
}
@if (TempData.ContainsKey(NotificationDefaults.InformationNotificationType))
{
<div class="alert alert-info alert-dismissible">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
<h5><i class="icon fas fa-info"></i> Information!</h5>
@TempData[NotificationDefaults.InformationNotificationType]
</div>
}
3. 模型绑定与验)
3.1 模型定义
管理区域的模型通常继承自BaseNopModel类:
public partial class ProductModel : BaseNopEntityModel
{
public ProductModel()
{
ProductAttributes = new List<ProductAttributeModel>();
ProductSpecifications = new List<ProductSpecificationModel>();
AvailableCategories = new List<SelectListItem>();
AvailableManufacturers = new List<SelectListItem>();
}
[Required]
[DisplayName("Name")]
public string Name { get; set; }
[DisplayName("Sku")]
public string Sku { get; set; }
[Required]
[DisplayName("Price")]
[Range(0.01, double.MaxValue, ErrorMessage = "Price must be greater than zero")]
public decimal Price { get; set; }
[DisplayName("Stock Quantity")]
public int StockQuantity { get; set; }
[DisplayName("Published")]
public bool Published { get; set; }
// 关联数据
public IList<int> CategoryIds { get; set; }
public IList<int> ManufacturerIds { get; set; }
// 可用选项
public IList<SelectListItem> AvailableCategories { get; set; }
public IList<SelectListItem> AvailableManufacturers { get; set; }
// 产品属性和规格
public IList<ProductAttributeModel> ProductAttributes { get; set; }
public IList<ProductSpecificationModel> ProductSpecifications { get; set; }
}
3.2 模型验证
管理区域使用数据注解进行模型验证)
[Required(ErrorMessage = "Product name is required")]
[StringLength(400, ErrorMessage = "Product name cannot exceed 400 characters")]
public string Name { get; set; }
[Range(0.01, double.MaxValue, ErrorMessage = "Price must be greater than zero")]
public decimal Price { get; set; }
[RegularExpression(@"^[a-zA-Z0-9-]*$", ErrorMessage = "SeName can only contain letters, numbers, and hyphens")]
public string SeName { get; set; }
4. 最佳实现
4.1 控制器设计
- 单一职责:每个控制器只负责一个功能模式2. **动作方法简)*:动作方法应简洁,将复杂逻辑封装到服务层
- 使用异步方法:优先使用异步方法,提高并发处理能力
- 遵循RESTful设计:使用适当的HTTP动词和路)5. 错误处理:妥善处理异常,返回友好的错误信)
4.2 视图设计
- **使用强类型视)*:优先使用强类型视图,避免使用ViewBag和ViewData
- *模块化设计:将复杂视图拆分为部分视)3. *响应式设计:确保视图在不同设备上都能正常显)4. 性能优化:减少DOM元素数量,优化JavaScript执行
- 一致的UI风格:保持与管理区域其他页面一致的UI风格
4.3 模型设计
- 扁平结构:使用扁平的模型结构,便于视图绑)2. 验证规则:添加适当的验证规则,确保数据完整)3. 可用选项:为下拉列表等控件提供可用选项
- 关联数据:包含必要的关联数据,减少视图中的数据查)5. DTO模式:使用数据传输对象(DTO),避免直接暴露实体
5. 总结
管理控制器和视图是NopCommerce管理区域的核心组件,它们负责处理管理员的请求并提供用户界面。在实际开发中,应遵循以下原则:
- 遵循NopCommerce的代码结构和命名规范
- 使用适当的过滤器进行权限验证和安全防)3. 设计清晰、易用的用户界面
- 实现完整的CRUD功能
- 提供良好的错误处理和用户反馈
- 优化性能,提高响应速度
通过遵循这些原则,可以开发出高质量、易用、安全的管理功能,为管理员提供良好的管理体验

781

被折叠的 条评论
为什么被折叠?



