NopCommerce 4.9.3全栈开发实战 - 7.2 管理控制器与视图开发

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 通知机制

使用SuccessNotificationErrorNotification等方法向用户显示通知消息)

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">&times;</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">&times;</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">&times;</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">&times;</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 控制器设计

  1. 单一职责:每个控制器只负责一个功能模式2. **动作方法简)*:动作方法应简洁,将复杂逻辑封装到服务层
  2. 使用异步方法:优先使用异步方法,提高并发处理能力
  3. 遵循RESTful设计:使用适当的HTTP动词和路)5. 错误处理:妥善处理异常,返回友好的错误信)

4.2 视图设计

  1. **使用强类型视)*:优先使用强类型视图,避免使用ViewBag和ViewData
  2. *模块化设计:将复杂视图拆分为部分视)3. *响应式设计:确保视图在不同设备上都能正常显)4. 性能优化:减少DOM元素数量,优化JavaScript执行
  3. 一致的UI风格:保持与管理区域其他页面一致的UI风格

4.3 模型设计

  1. 扁平结构:使用扁平的模型结构,便于视图绑)2. 验证规则:添加适当的验证规则,确保数据完整)3. 可用选项:为下拉列表等控件提供可用选项
  2. 关联数据:包含必要的关联数据,减少视图中的数据查)5. DTO模式:使用数据传输对象(DTO),避免直接暴露实体

5. 总结

管理控制器和视图是NopCommerce管理区域的核心组件,它们负责处理管理员的请求并提供用户界面。在实际开发中,应遵循以下原则:

  1. 遵循NopCommerce的代码结构和命名规范
  2. 使用适当的过滤器进行权限验证和安全防)3. 设计清晰、易用的用户界面
  3. 实现完整的CRUD功能
  4. 提供良好的错误处理和用户反馈
  5. 优化性能,提高响应速度

通过遵循这些原则,可以开发出高质量、易用、安全的管理功能,为管理员提供良好的管理体验

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AI题库

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值