掌握ASP.NET项目开发核心技能实战案例全程解析与经验分享
1. ASP.NET概述与发展历程
ASP.NET是微软推出的用于构建Web应用程序的开发框架,自2002年首次发布以来,经历了多个版本的演进。从最初的ASP.NET Web Forms,到ASP.NET MVC,再到如今的ASP.NET Core,这个框架不断适应着Web开发的变化和需求。
1.1 ASP.NET的演进
- ASP.NET Web Forms:最初的ASP.NET版本,提供了事件驱动的编程模型,类似于Windows Forms开发。
- ASP.NET MVC:引入了模型-视图-控制器架构模式,提供了更好的控制和可测试性。
- ASP.NET Web API:专门用于构建HTTP服务的框架,便于创建RESTful服务。
- ASP.NET Core:跨平台、高性能的开源框架,整合了MVC、Web API和Web Pages的功能。
1.2 ASP.NET Core的优势
ASP.NET Core作为当前最新的版本,具有以下优势:
- 跨平台支持:可在Windows、macOS和Linux上运行
- 高性能:经过优化的运行时和请求处理管道
- 模块化设计:只包含需要的功能,减少应用体积
- 开源:社区驱动的开发模式
- 统一编程模型:同时支持MVC和Web API
2. ASP.NET项目开发的核心技能
2.1 C#编程基础
C#是ASP.NET开发的主要语言,掌握C#的基础知识是必要的:
// 基本语法示例 public class HelloWorld { public static void Main(string[] args) { Console.WriteLine("Hello, ASP.NET Core!"); // 变量声明与使用 string message = "Welcome to ASP.NET Core development"; int version = 6; Console.WriteLine($"{message} - Version {version}"); } }
2.2 依赖注入
依赖注入(DI)是ASP.NET Core的核心概念,有助于实现松耦合的代码:
// 定义服务接口 public interface IMessageService { string GetMessage(); } // 实现服务 public class HelloWorldMessageService : IMessageService { public string GetMessage() { return "Hello from DI!"; } } // 在Startup.cs中注册服务 public void ConfigureServices(IServiceCollection services) { services.AddScoped<IMessageService, HelloWorldMessageService>(); } // 在控制器中使用依赖注入 public class HomeController : Controller { private readonly IMessageService _messageService; public HomeController(IMessageService messageService) { _messageService = messageService; } public IActionResult Index() { ViewBag.Message = _messageService.GetMessage(); return View(); } }
2.3 中间件(Middleware)
中间件是处理HTTP请求和响应的组件,它们构成一个管道:
// 自定义中间件示例 public class RequestTimeMiddleware { private readonly RequestDelegate _next; public RequestTimeMiddleware(RequestDelegate next) { _next = next; } public async Task Invoke(HttpContext context) { var startTime = DateTime.UtcNow; // 调用管道中的下一个中间件 await _next(context); var endTime = DateTime.UtcNow; var duration = endTime - startTime; // 将请求处理时间添加到响应头 context.Response.Headers["X-Response-Time-Ms"] = duration.TotalMilliseconds.ToString(); } } // 在Startup.cs中配置中间件管道 public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { // 使用自定义中间件 app.UseMiddleware<RequestTimeMiddleware>(); // 其他内置中间件 app.UseStaticFiles(); app.UseRouting(); app.UseEndpoints(endpoints => { endpoints.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); }); }
2.4 实体框架(EF) Core
Entity Framework Core是ASP.NET Core中的ORM框架,用于数据访问:
// 定义实体模型 public class Product { public int Id { get; set; } public string Name { get; set; } public decimal Price { get; set; } public string Description { get; set; } public int CategoryId { get; set; } public Category Category { get; set; } } public class Category { public int Id { get; set; } public string Name { get; set; } public List<Product> Products { get; set; } } // 定义DbContext public class AppDbContext : DbContext { public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { } public DbSet<Product> Products { get; set; } public DbSet<Category> Categories { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { // 配置实体关系 modelBuilder.Entity<Product>() .HasOne(p => p.Category) .WithMany(c => c.Products) .HasForeignKey(p => p.CategoryId); } } // 在Startup.cs中注册DbContext public void ConfigureServices(IServiceCollection services) { services.AddDbContext<AppDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))); }
2.5 身份认证与授权
ASP.NET Core提供了强大的身份认证和授权系统:
// 在Startup.cs中配置身份认证 public void ConfigureServices(IServiceCollection services) { services.AddDbContext<AppDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))); services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true) .AddEntityFrameworkStores<AppDbContext>(); services.AddRazorPages(); // 添加基于策略的授权 services.AddAuthorization(options => { options.AddPolicy("RequireAdminRole", policy => policy.RequireRole("Admin")); options.AddPolicy("AtLeast18", policy => policy.Requirements.Add(new MinimumAgeRequirement(18))); }); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { // ... 其他中间件 app.UseAuthentication(); app.UseAuthorization(); // ... 路由配置 } // 自定义授权要求 public class MinimumAgeRequirement : IAuthorizationRequirement { public int MinimumAge { get; } public MinimumAgeRequirement(int minimumAge) { MinimumAge = minimumAge; } } public class MinimumAgeHandler : AuthorizationHandler<MinimumAgeRequirement> { protected override Task HandleRequirementAsync( AuthorizationHandlerContext context, MinimumAgeRequirement requirement) { var dateOfBirthClaim = context.User.FindFirst(c => c.Type == ClaimTypes.DateOfBirth); if (dateOfBirthClaim == null) { return Task.CompletedTask; } var dateOfBirth = Convert.ToDateTime(dateOfBirthClaim.Value); int calculatedAge = DateTime.Today.Year - dateOfBirth.Year; if (dateOfBirth > DateTime.Today.AddYears(-calculatedAge)) { calculatedAge--; } if (calculatedAge >= requirement.MinimumAge) { context.Succeed(requirement); } return Task.CompletedTask; } } // 在控制器或操作上应用授权 [Authorize(Policy = "RequireAdminRole")] public class AdminController : Controller { public IActionResult Index() { return View(); } [Authorize(Policy = "AtLeast18")] public IActionResult AdultContent() { return View(); } }
3. 实战案例:构建一个电子商务网站
3.1 项目规划与架构设计
在开始开发之前,我们需要规划项目的整体架构。我们将采用分层架构,包括:
- 表示层(Presentation Layer):MVC控制器和视图
- 应用服务层(Application Service Layer):业务逻辑
- 领域层(Domain Layer):核心业务模型和逻辑
- 基础设施层(Infrastructure Layer):数据访问和外部服务集成
3.2 创建项目结构
首先,我们创建一个ASP.NET Core MVC项目:
dotnet new mvc -n ECommerceApp cd ECommerceApp
然后,我们添加必要的类库项目:
# 创建领域层 dotnet new classlib -n ECommerceApp.Domain # 创建应用服务层 dotnet new classlib -n ECommerceApp.Application # 创建基础设施层 dotnet new classlib -n ECommerceApp.Infrastructure
3.3 实现领域模型
在ECommerceApp.Domain项目中,我们定义核心业务实体:
// ECommerceApp.Domain/Entities/Product.cs namespace ECommerceApp.Domain.Entities { public class Product { public int Id { get; set; } public string Name { get; set; } public string Description { get; set; } public decimal Price { get; set; } public string ImageUrl { get; set; } public int StockQuantity { get; set; } public int CategoryId { get; set; } public Category Category { get; set; } public bool IsFeatured { get; set; } public DateTime CreatedAt { get; set; } public DateTime? UpdatedAt { get; set; } } } // ECommerceApp.Domain/Entities/Category.cs namespace ECommerceApp.Domain.Entities { public class Category { public int Id { get; set; } public string Name { get; set; } public string Description { get; set; } public string ImageUrl { get; set; } public List<Product> Products { get; set; } } } // ECommerceApp.Domain/Entities/Customer.cs using System.Collections.Generic; namespace ECommerceApp.Domain.Entities { public class Customer { public int Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public string Email { get; set; } public string PhoneNumber { get; set; } public List<Address> Addresses { get; set; } public List<Order> Orders { get; set; } } } // ECommerceApp.Domain/Entities/Order.cs using System.Collections.Generic; namespace ECommerceApp.Domain.Entities { public class Order { public int Id { get; set; } public DateTime OrderDate { get; set; } public decimal TotalAmount { get; set; } public string Status { get; set; } public int CustomerId { get; set; } public Customer Customer { get; set; } public int ShippingAddressId { get; set; } public Address ShippingAddress { get; set; } public List<OrderItem> OrderItems { get; set; } } } // ECommerceApp.Domain/Entities/OrderItem.cs namespace ECommerceApp.Domain.Entities { public class OrderItem { public int Id { get; set; } public int ProductId { get; set; } public Product Product { get; set; } public int Quantity { get; set; } public decimal UnitPrice { get; set; } public int OrderId { get; set; } public Order Order { get; set; } } } // ECommerceApp.Domain/Entities/Address.cs namespace ECommerceApp.Domain.Entities { public class Address { public int Id { get; set; } public string Street { get; set; } public string City { get; set; } public string State { get; set; } public string PostalCode { get; set; } public string Country { get; set; } public int CustomerId { get; set; } public Customer Customer { get; set; } } }
3.4 实现数据访问层
在ECommerceApp.Infrastructure项目中,我们实现数据访问:
// ECommerceApp.Infrastructure/Data/AppDbContext.cs using ECommerceApp.Domain.Entities; using Microsoft.EntityFrameworkCore; namespace ECommerceApp.Infrastructure.Data { public class AppDbContext : DbContext { public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { } public DbSet<Product> Products { get; set; } public DbSet<Category> Categories { get; set; } public DbSet<Customer> Customers { get; set; } public DbSet<Order> Orders { get; set; } public DbSet<OrderItem> OrderItems { get; set; } public DbSet<Address> Addresses { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { // 配置Product实体 modelBuilder.Entity<Product>() .Property(p => p.Price) .HasColumnType("decimal(18,2)"); modelBuilder.Entity<Product>() .HasOne(p => p.Category) .WithMany(c => c.Products) .HasForeignKey(p => p.CategoryId) .OnDelete(DeleteBehavior.Restrict); // 配置Order实体 modelBuilder.Entity<Order>() .Property(o => o.TotalAmount) .HasColumnType("decimal(18,2)"); modelBuilder.Entity<Order>() .HasOne(o => o.Customer) .WithMany(c => c.Orders) .HasForeignKey(o => o.CustomerId) .OnDelete(DeleteBehavior.Restrict); modelBuilder.Entity<Order>() .HasOne(o => o.ShippingAddress) .WithMany() .HasForeignKey(o => o.ShippingAddressId) .OnDelete(DeleteBehavior.Restrict); // 配置OrderItem实体 modelBuilder.Entity<OrderItem>() .Property(oi => oi.UnitPrice) .HasColumnType("decimal(18,2)"); modelBuilder.Entity<OrderItem>() .HasOne(oi => oi.Product) .WithMany() .HasForeignKey(oi => oi.ProductId) .OnDelete(DeleteBehavior.Restrict); modelBuilder.Entity<OrderItem>() .HasOne(oi => oi.Order) .WithMany(o => o.OrderItems) .HasForeignKey(oi => oi.OrderId) .OnDelete(DeleteBehavior.Cascade); // 配置Address实体 modelBuilder.Entity<Address>() .HasOne(a => a.Customer) .WithMany(c => c.Addresses) .HasForeignKey(a => a.CustomerId) .OnDelete(DeleteBehavior.Cascade); } } } // ECommerceApp.Infrastructure/Repositories/Repository.cs using ECommerceApp.Domain.Entities; using Microsoft.EntityFrameworkCore; using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Threading.Tasks; namespace ECommerceApp.Infrastructure.Repositories { public class Repository<T> : IRepository<T> where T : class { protected readonly AppDbContext _context; protected readonly DbSet<T> _dbSet; public Repository(AppDbContext context) { _context = context; _dbSet = context.Set<T>(); } public async Task<T> GetByIdAsync(int id) { return await _dbSet.FindAsync(id); } public async Task<IEnumerable<T>> GetAllAsync() { return await _dbSet.ToListAsync(); } public async Task<IEnumerable<T>> FindAsync(Expression<Func<T, bool>> predicate) { return await _dbSet.Where(predicate).ToListAsync(); } public async Task AddAsync(T entity) { await _dbSet.AddAsync(entity); } public void Update(T entity) { _dbSet.Attach(entity); _context.Entry(entity).State = EntityState.Modified; } public void Delete(T entity) { if (_context.Entry(entity).State == EntityState.Detached) { _dbSet.Attach(entity); } _dbSet.Remove(entity); } public async Task SaveChangesAsync() { await _context.SaveChangesAsync(); } } } // ECommerceApp.Infrastructure/Repositories/IRepository.cs using System; using System.Collections.Generic; using System.Linq.Expressions; using System.Threading.Tasks; namespace ECommerceApp.Infrastructure.Repositories { public interface IRepository<T> where T : class { Task<T> GetByIdAsync(int id); Task<IEnumerable<T>> GetAllAsync(); Task<IEnumerable<T>> FindAsync(Expression<Func<T, bool>> predicate); Task AddAsync(T entity); void Update(T entity); void Delete(T entity); Task SaveChangesAsync(); } } // ECommerceApp.Infrastructure/Repositories/ProductRepository.cs using ECommerceApp.Domain.Entities; using Microsoft.EntityFrameworkCore; using System.Collections.Generic; using System.Threading.Tasks; namespace ECommerceApp.Infrastructure.Repositories { public class ProductRepository : Repository<Product>, IProductRepository { public ProductRepository(AppDbContext context) : base(context) { } public async Task<IEnumerable<Product>> GetFeaturedProductsAsync() { return await _dbSet .Include(p => p.Category) .Where(p => p.IsFeatured) .ToListAsync(); } public async Task<IEnumerable<Product>> GetProductsByCategoryAsync(int categoryId) { return await _dbSet .Include(p => p.Category) .Where(p => p.CategoryId == categoryId) .ToListAsync(); } public async Task<Product> GetProductWithDetailsAsync(int id) { return await _dbSet .Include(p => p.Category) .FirstOrDefaultAsync(p => p.Id == id); } } } // ECommerceApp.Infrastructure/Repositories/IProductRepository.cs using ECommerceApp.Domain.Entities; using System.Collections.Generic; using System.Threading.Tasks; namespace ECommerceApp.Infrastructure.Repositories { public interface IProductRepository : IRepository<Product> { Task<IEnumerable<Product>> GetFeaturedProductsAsync(); Task<IEnumerable<Product>> GetProductsByCategoryAsync(int categoryId); Task<Product> GetProductWithDetailsAsync(int id); } }
3.5 实现应用服务层
在ECommerceApp.Application项目中,我们实现业务逻辑:
// ECommerceApp.Application/Services/ProductService.cs using ECommerceApp.Domain.Entities; using ECommerceApp.Infrastructure.Repositories; using System.Collections.Generic; using System.Threading.Tasks; namespace ECommerceApp.Application.Services { public class ProductService : IProductService { private readonly IProductRepository _productRepository; public ProductService(IProductRepository productRepository) { _productRepository = productRepository; } public async Task<Product> GetProductByIdAsync(int id) { return await _productRepository.GetProductWithDetailsAsync(id); } public async Task<IEnumerable<Product>> GetAllProductsAsync() { return await _productRepository.GetAllAsync(); } public async Task<IEnumerable<Product>> GetFeaturedProductsAsync() { return await _productRepository.GetFeaturedProductsAsync(); } public async Task<IEnumerable<Product>> GetProductsByCategoryAsync(int categoryId) { return await _productRepository.GetProductsByCategoryAsync(categoryId); } public async Task AddProductAsync(Product product) { product.CreatedAt = System.DateTime.Now; await _productRepository.AddAsync(product); await _productRepository.SaveChangesAsync(); } public async Task UpdateProductAsync(Product product) { product.UpdatedAt = System.DateTime.Now; _productRepository.Update(product); await _productRepository.SaveChangesAsync(); } public async Task DeleteProductAsync(int id) { var product = await _productRepository.GetByIdAsync(id); if (product != null) { _productRepository.Delete(product); await _productRepository.SaveChangesAsync(); } } } } // ECommerceApp.Application/Services/IProductService.cs using ECommerceApp.Domain.Entities; using System.Collections.Generic; using System.Threading.Tasks; namespace ECommerceApp.Application.Services { public interface IProductService { Task<Product> GetProductByIdAsync(int id); Task<IEnumerable<Product>> GetAllProductsAsync(); Task<IEnumerable<Product>> GetFeaturedProductsAsync(); Task<IEnumerable<Product>> GetProductsByCategoryAsync(int categoryId); Task AddProductAsync(Product product); Task UpdateProductAsync(Product product); Task DeleteProductAsync(int id); } } // ECommerceApp.Application/Services/OrderService.cs using ECommerceApp.Domain.Entities; using ECommerceApp.Infrastructure.Repositories; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace ECommerceApp.Application.Services { public class OrderService : IOrderService { private readonly IRepository<Order> _orderRepository; private readonly IRepository<OrderItem> _orderItemRepository; private readonly IProductService _productService; public OrderService( IRepository<Order> orderRepository, IRepository<OrderItem> orderItemRepository, IProductService productService) { _orderRepository = orderRepository; _orderItemRepository = orderItemRepository; _productService = productService; } public async Task<Order> GetOrderByIdAsync(int id) { return await _orderRepository.GetByIdAsync(id); } public async Task<IEnumerable<Order>> GetOrdersByCustomerIdAsync(int customerId) { return await _orderRepository.FindAsync(o => o.CustomerId == customerId); } public async Task<Order> CreateOrderAsync(int customerId, int shippingAddressId, Dictionary<int, int> productQuantities) { var orderItems = new List<OrderItem>(); decimal totalAmount = 0; foreach (var item in productQuantities) { var product = await _productService.GetProductByIdAsync(item.Key); if (product == null || product.StockQuantity < item.Value) { throw new Exception($"Product {item.Key} is not available in sufficient quantity."); } var orderItem = new OrderItem { ProductId = item.Key, Quantity = item.Value, UnitPrice = product.Price }; orderItems.Add(orderItem); totalAmount += orderItem.Quantity * orderItem.UnitPrice; // Update product stock product.StockQuantity -= item.Value; await _productService.UpdateProductAsync(product); } var order = new Order { CustomerId = customerId, ShippingAddressId = shippingAddressId, OrderDate = DateTime.Now, TotalAmount = totalAmount, Status = "Pending", OrderItems = orderItems }; await _orderRepository.AddAsync(order); await _orderRepository.SaveChangesAsync(); return order; } public async Task UpdateOrderStatusAsync(int orderId, string status) { var order = await _orderRepository.GetByIdAsync(orderId); if (order != null) { order.Status = status; _orderRepository.Update(order); await _orderRepository.SaveChangesAsync(); } } } } // ECommerceApp.Application/Services/IOrderService.cs using ECommerceApp.Domain.Entities; using System.Collections.Generic; using System.Threading.Tasks; namespace ECommerceApp.Application.Services { public interface IOrderService { Task<Order> GetOrderByIdAsync(int id); Task<IEnumerable<Order>> GetOrdersByCustomerIdAsync(int customerId); Task<Order> CreateOrderAsync(int customerId, int shippingAddressId, Dictionary<int, int> productQuantities); Task UpdateOrderStatusAsync(int orderId, string status); } }
3.6 实现控制器和视图
在主项目中,我们实现MVC控制器和视图:
// ECommerceApp/Controllers/HomeController.cs using ECommerceApp.Application.Services; using Microsoft.AspNetCore.Mvc; using System.Threading.Tasks; namespace ECommerceApp.Controllers { public class HomeController : Controller { private readonly IProductService _productService; public HomeController(IProductService productService) { _productService = productService; } public async Task<IActionResult> Index() { var featuredProducts = await _productService.GetFeaturedProductsAsync(); return View(featuredProducts); } public IActionResult About() { return View(); } public IActionResult Contact() { return View(); } public IActionResult Privacy() { return View(); } [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] public IActionResult Error() { return View(new ErrorViewModel { RequestId = System.Diagnostics.Activity.Current?.Id ?? HttpContext.TraceIdentifier }); } } } // ECommerceApp/Controllers/ProductsController.cs using ECommerceApp.Application.Services; using Microsoft.AspNetCore.Mvc; using System.Threading.Tasks; namespace ECommerceApp.Controllers { public class ProductsController : Controller { private readonly IProductService _productService; public ProductsController(IProductService productService) { _productService = productService; } // GET: Products public async Task<IActionResult> Index() { var products = await _productService.GetAllProductsAsync(); return View(products); } // GET: Products/Details/5 public async Task<IActionResult> Details(int id) { var product = await _productService.GetProductByIdAsync(id); if (product == null) { return NotFound(); } return View(product); } // GET: Products/Create public IActionResult Create() { return View(); } // POST: Products/Create [HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> Create([Bind("Name,Description,Price,ImageUrl,StockQuantity,CategoryId,IsFeatured")] Product product) { if (ModelState.IsValid) { await _productService.AddProductAsync(product); return RedirectToAction(nameof(Index)); } return View(product); } // GET: Products/Edit/5 public async Task<IActionResult> Edit(int id) { var product = await _productService.GetProductByIdAsync(id); if (product == null) { return NotFound(); } return View(product); } // POST: Products/Edit/5 [HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> Edit(int id, [Bind("Id,Name,Description,Price,ImageUrl,StockQuantity,CategoryId,IsFeatured,CreatedAt")] Product product) { if (id != product.Id) { return NotFound(); } if (ModelState.IsValid) { await _productService.UpdateProductAsync(product); return RedirectToAction(nameof(Index)); } return View(product); } // GET: Products/Delete/5 public async Task<IActionResult> Delete(int id) { var product = await _productService.GetProductByIdAsync(id); if (product == null) { return NotFound(); } return View(product); } // POST: Products/Delete/5 [HttpPost, ActionName("Delete")] [ValidateAntiForgeryToken] public async Task<IActionResult> DeleteConfirmed(int id) { await _productService.DeleteProductAsync(id); return RedirectToAction(nameof(Index)); } } } // ECommerceApp/Controllers/CartController.cs using ECommerceApp.Application.Services; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace ECommerceApp.Controllers { public class CartController : Controller { private readonly IProductService _productService; public CartController(IProductService productService) { _productService = productService; } // 获取购物车 private Dictionary<int, int> GetCart() { var cart = HttpContext.Session.GetObject<Dictionary<int, int>>("Cart"); if (cart == null) { cart = new Dictionary<int, int>(); HttpContext.Session.SetObject("Cart", cart); } return cart; } // 保存购物车 private void SaveCart(Dictionary<int, int> cart) { HttpContext.Session.SetObject("Cart", cart); } // GET: Cart public async Task<IActionResult> Index() { var cart = GetCart(); var cartItems = new List<CartItemViewModel>(); foreach (var item in cart) { var product = await _productService.GetProductByIdAsync(item.Key); if (product != null) { cartItems.Add(new CartItemViewModel { Product = product, Quantity = item.Value }); } } return View(cartItems); } // POST: Cart/Add/5 [HttpPost] public IActionResult Add(int id, int quantity = 1) { var cart = GetCart(); if (cart.ContainsKey(id)) { cart[id] += quantity; } else { cart.Add(id, quantity); } SaveCart(cart); return RedirectToAction("Index"); } // POST: Cart/Update/5 [HttpPost] public IActionResult Update(int id, int quantity) { var cart = GetCart(); if (cart.ContainsKey(id)) { if (quantity > 0) { cart[id] = quantity; } else { cart.Remove(id); } } SaveCart(cart); return RedirectToAction("Index"); } // POST: Cart/Remove/5 [HttpPost] public IActionResult Remove(int id) { var cart = GetCart(); if (cart.ContainsKey(id)) { cart.Remove(id); } SaveCart(cart); return RedirectToAction("Index"); } // POST: Cart/Clear [HttpPost] public IActionResult Clear() { HttpContext.Session.Remove("Cart"); return RedirectToAction("Index"); } } // 购物车项视图模型 public class CartItemViewModel { public Product Product { get; set; } public int Quantity { get; set; } } }
3.7 实现视图
以下是一些关键视图的示例:
<!-- ECommerceApp/Views/Home/Index.cshtml --> @model IEnumerable<ECommerceApp.Domain.Entities.Product> @{ ViewData["Title"] = "Home"; } <div class="text-center"> <h1 class="display-4">Welcome to ECommerceApp</h1> <p>Check out our featured products below.</p> </div> <div class="row"> @foreach (var product in Model) { <div class="col-md-4 mb-4"> <div class="card h-100"> <img src="@product.ImageUrl" class="card-img-top" alt="@product.Name"> <div class="card-body"> <h5 class="card-title">@product.Name</h5> <p class="card-text">@product.Description</p> <p class="card-text"><strong>Price: @product.Price.ToString("C")</strong></p> <a asp-controller="Products" asp-action="Details" asp-route-id="@product.Id" class="btn btn-primary">View Details</a> </div> </div> </div> } </div>
<!-- ECommerceApp/Views/Products/Details.cshtml --> @model ECommerceApp.Domain.Entities.Product @{ ViewData["Title"] = "Product Details"; } <div class="container"> <div class="row"> <div class="col-md-6"> <img src="@Model.ImageUrl" class="img-fluid" alt="@Model.Name"> </div> <div class="col-md-6"> <h2>@Model.Name</h2> <p class="text-muted">Category: @Model.Category.Name</p> <h3 class="text-primary">@Model.Price.ToString("C")</h3> <p>@Model.Description</p> <p>Stock: @Model.StockQuantity units available</p> <form asp-controller="Cart" asp-action="Add" method="post"> <input type="hidden" name="id" value="@Model.Id" /> <div class="form-group"> <label for="quantity">Quantity:</label> <input type="number" id="quantity" name="quantity" class="form-control" value="1" min="1" max="@Model.StockQuantity" /> </div> <button type="submit" class="btn btn-primary">Add to Cart</button> </form> </div> </div> </div>
<!-- ECommerceApp/Views/Cart/Index.cshtml --> @model IEnumerable<ECommerceApp.Controllers.CartItemViewModel> @{ ViewData["Title"] = "Shopping Cart"; } <h2>Shopping Cart</h2> @if (!Model.Any()) { <div class="alert alert-info"> Your cart is empty. </div> } else { <table class="table"> <thead> <tr> <th>Product</th> <th>Price</th> <th>Quantity</th> <th>Total</th> <th>Actions</th> </tr> </thead> <tbody> @foreach (var item in Model) { <tr> <td> <div class="d-flex align-items-center"> <img src="@item.Product.ImageUrl" style="width: 50px; height: 50px; object-fit: cover;" alt="@item.Product.Name" /> <div class="ml-2"> <a asp-controller="Products" asp-action="Details" asp-route-id="@item.Product.Id">@item.Product.Name</a> </div> </div> </td> <td>@item.Product.Price.ToString("C")</td> <td> <form asp-action="Update" method="post" class="form-inline"> <input type="hidden" name="id" value="@item.Product.Id" /> <input type="number" name="quantity" value="@item.Quantity" min="1" max="@item.Product.StockQuantity" class="form-control form-control-sm" style="width: 70px;" /> <button type="submit" class="btn btn-sm btn-outline-primary ml-1">Update</button> </form> </td> <td>@((item.Product.Price * item.Quantity).ToString("C"))</td> <td> <form asp-action="Remove" method="post"> <input type="hidden" name="id" value="@item.Product.Id" /> <button type="submit" class="btn btn-sm btn-danger">Remove</button> </form> </td> </tr> } </tbody> <tfoot> <tr> <td colspan="3" class="text-right"><strong>Total:</strong></td> <td><strong>@Model.Sum(i => i.Product.Price * i.Quantity).ToString("C")</strong></td> <td></td> </tr> </tfoot> </table> <div class="d-flex justify-content-between"> <form asp-action="Clear" method="post"> <button type="submit" class="btn btn-outline-danger">Clear Cart</button> </form> <a asp-controller="Checkout" asp-action="Index" class="btn btn-primary">Proceed to Checkout</a> </div> }
3.8 配置依赖注入
在Startup.cs中配置依赖注入:
// ECommerceApp/Startup.cs using ECommerceApp.Application.Services; using ECommerceApp.Infrastructure.Data; using ECommerceApp.Infrastructure.Repositories; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; namespace ECommerceApp { public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddDbContext<AppDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))); // 注册仓储 services.AddScoped<IProductRepository, ProductRepository>(); services.AddScoped<IRepository<Order>, Repository<Order>>(); services.AddScoped<IRepository<OrderItem>, Repository<OrderItem>>(); services.AddScoped<IRepository<Customer>, Repository<Customer>>(); services.AddScoped<IRepository<Address>, Repository<Address>>(); services.AddScoped<IRepository<Category>, Repository<Category>>(); // 注册应用服务 services.AddScoped<IProductService, ProductService>(); services.AddScoped<IOrderService, OrderService>(); services.AddControllersWithViews(); services.AddRazorPages(); // 添加Session支持 services.AddDistributedMemoryCache(); services.AddSession(options => { options.IdleTimeout = System.TimeSpan.FromMinutes(30); options.Cookie.HttpOnly = true; options.Cookie.IsEssential = true; }); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { 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.UseSession(); app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); endpoints.MapRazorPages(); }); } } }
3.9 添加Session扩展方法
为了在Session中存储复杂对象,我们需要添加扩展方法:
// ECommerceApp/Extensions/SessionExtensions.cs using Microsoft.AspNetCore.Http; using System.Text.Json; namespace ECommerceApp.Extensions { public static class SessionExtensions { public static void SetObject<T>(this ISession session, string key, T value) { session.SetString(key, JsonSerializer.Serialize(value)); } public static T GetObject<T>(this ISession session, string key) { var value = session.GetString(key); return value == null ? default(T) : JsonSerializer.Deserialize<T>(value); } } }
4. 开发过程中的经验分享
4.1 架构设计经验
分层架构的重要性:清晰的分层架构有助于代码的维护和扩展。在我们的电子商务网站中,我们将应用分为表示层、应用服务层、领域层和基础设施层,每一层都有明确的职责。
依赖注入的使用:依赖注入是实现松耦合设计的关键。通过构造函数注入,我们可以轻松地替换实现,便于单元测试和维护。
仓储模式的应用:仓储模式抽象了数据访问逻辑,使业务逻辑层不直接依赖于数据访问技术。这样,我们可以轻松地切换数据存储方式,而不影响业务逻辑。
4.2 性能优化经验
异步编程:在ASP.NET Core中,使用异步方法可以显著提高应用程序的吞吐量。特别是在I/O操作中,如数据库访问和外部API调用,异步编程可以释放线程来处理其他请求。
缓存策略:对于不经常变化的数据,使用缓存可以减少数据库访问次数,提高响应速度。ASP.NET Core提供了多种缓存选项,包括内存缓存、分布式缓存和响应缓存。
EF Core性能优化:
- 使用AsNoTracking()进行只读查询
- 批量操作减少数据库往返
- 选择性加载需要的字段
- 使用编译查询提高重复查询的性能
// 使用AsNoTracking()进行只读查询 public async Task<IEnumerable<Product>> GetAllProductsAsync() { return await _context.Products .AsNoTracking() .ToListAsync(); } // 批量插入示例 public async Task BulkInsertProductsAsync(IEnumerable<Product> products) { await _context.Products.AddRangeAsync(products); await _context.SaveChangesAsync(); } // 选择性加载字段 public async Task<IEnumerable<Product>> GetProductNamesAsync() { return await _context.Products .Select(p => new Product { Id = p.Id, Name = p.Name }) .ToListAsync(); } // 编译查询示例 private static readonly Func<AppDbContext, int, Task<Product>> GetProductByIdCompiled = EF.CompileAsyncQuery((AppDbContext context, int id) => context.Products.FirstOrDefault(p => p.Id == id)); public async Task<Product> GetProductByIdAsync(int id) { return await GetProductByIdCompiled(_context, id); }
4.3 安全性经验
- 输入验证:始终验证用户输入,防止SQL注入、XSS和其他安全漏洞。ASP.NET Core提供了内置的验证特性,可以轻松地实现验证。
public class Product { public int Id { get; set; } [Required(ErrorMessage = "Product name is required.")] [StringLength(100, ErrorMessage = "Product name cannot exceed 100 characters.")] public string Name { get; set; } [StringLength(500, ErrorMessage = "Description cannot exceed 500 characters.")] public string Description { get; set; } [Range(0.01, 10000, ErrorMessage = "Price must be between 0.01 and 10000.")] public decimal Price { get; set; } [Url(ErrorMessage = "Please enter a valid URL.")] public string ImageUrl { get; set; } [Range(0, int.MaxValue, ErrorMessage = "Stock quantity cannot be negative.")] public int StockQuantity { get; set; } }
身份认证和授权:使用ASP.NET Core Identity进行用户管理,并实现基于角色的访问控制和基于策略的授权。
HTTPS使用:在生产环境中始终使用HTTPS,确保数据传输的安全性。
敏感数据保护:使用ASP.NET Core的数据保护API来保护敏感数据,如密码、连接字符串等。
// 在Startup.cs中配置数据保护 public void ConfigureServices(IServiceCollection services) { services.AddDataProtection() .PersistKeysToFileSystem(new DirectoryInfo(@"c:keys")) .ProtectKeysWithDpapi(); }
4.4 测试经验
- 单元测试:为业务逻辑编写单元测试,确保代码的正确性。使用xUnit、NUnit或MSTest等测试框架,以及Moq等模拟框架。
// ProductService单元测试示例 using ECommerceApp.Application.Services; using ECommerceApp.Domain.Entities; using ECommerceApp.Infrastructure.Repositories; using Moq; using System.Collections.Generic; using System.Threading.Tasks; using Xunit; namespace ECommerceApp.Tests { public class ProductServiceTests { [Fact] public async Task GetProductByIdAsync_ShouldReturnProduct() { // Arrange var mockRepo = new Mock<IProductRepository>(); var productId = 1; var expectedProduct = new Product { Id = productId, Name = "Test Product" }; mockRepo.Setup(repo => repo.GetProductWithDetailsAsync(productId)) .ReturnsAsync(expectedProduct); var service = new ProductService(mockRepo.Object); // Act var result = await service.GetProductByIdAsync(productId); // Assert Assert.NotNull(result); Assert.Equal(productId, result.Id); Assert.Equal("Test Product", result.Name); } [Fact] public async Task AddProductAsync_ShouldSetCreatedAt() { // Arrange var mockRepo = new Mock<IProductRepository>(); var service = new ProductService(mockRepo.Object); var product = new Product { Name = "New Product" }; // Act await service.AddProductAsync(product); // Assert Assert.NotEqual(default(DateTime), product.CreatedAt); mockRepo.Verify(repo => repo.AddAsync(product), Times.Once); mockRepo.Verify(repo => repo.SaveChangesAsync(), Times.Once); } } }
- 集成测试:使用ASP.NET Core的测试服务器进行集成测试,验证组件之间的交互。
// 集成测试示例 using Microsoft.AspNetCore.Mvc.Testing; using System.Net.Http; using System.Threading.Tasks; using Xunit; namespace ECommerceApp.Tests { public class HomeControllerIntegrationTests : IClassFixture<WebApplicationFactory<Startup>> { private readonly WebApplicationFactory<Startup> _factory; public HomeControllerIntegrationTests(WebApplicationFactory<Startup> factory) { _factory = factory; } [Fact] public async Task IndexPage_ReturnsSuccessAndCorrectContentType() { // Arrange var client = _factory.CreateClient(); // Act var response = await client.GetAsync("/"); // Assert response.EnsureSuccessStatusCode(); // Status Code 200-299 Assert.Equal("text/html; charset=utf-8", response.Content.Headers.ContentType.ToString()); } } }
- 测试驱动开发(TDD):考虑采用TDD方法,先编写测试,然后实现功能,这样可以确保代码的可测试性和高质量。
5. 常见问题及解决方案
5.1 数据库迁移问题
问题:在开发过程中,数据库模型变更后如何更新数据库结构?
解决方案:使用Entity Framework Core的迁移功能:
# 添加迁移 dotnet ef migrations add AddNewPropertyToProduct # 更新数据库 dotnet ef database update
如果需要回滚到之前的迁移:
# 回滚到特定迁移 dotnet ef database update PreviousMigrationName # 删除最后一个迁移(如果尚未应用到数据库) dotnet ef migrations remove
5.2 依赖注入生命周期问题
问题:如何选择正确的服务生命周期(Scoped、Singleton、Transient)?
解决方案:理解不同生命周期的含义和适用场景:
- Transient:每次请求都会创建一个新的实例。适用于轻量级、无状态服务。
- Scoped:每个作用域(如每个HTTP请求)创建一个实例。适用于EF DbContext和每个请求需要保持状态的服务。
- Singleton:整个应用程序生命周期中只创建一个实例。适用于全局配置、日志服务等。
示例:
public void ConfigureServices(IServiceCollection services) { // DbContext应该是Scoped的 services.AddDbContext<AppDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))); // 仓储通常也是Scoped的,与DbContext保持一致 services.AddScoped<IProductRepository, ProductRepository>(); // 应用服务通常也是Scoped的 services.AddScoped<IProductService, ProductService>(); // 日志服务通常是Singleton的 services.AddSingleton<ILoggerService, LoggerService>(); // 某些工具类可能是Transient的 services.AddTransient<IEmailService, EmailService>(); }
5.3 跨域资源共享(CORS)问题
问题:当前端应用与后端API不在同一个域时,如何处理跨域请求?
解决方案:配置CORS策略:
// 在Startup.cs中配置CORS public void ConfigureServices(IServiceCollection services) { services.AddCors(options => { options.AddPolicy("AllowSpecificOrigin", builder => { builder.WithOrigins("https://example.com", "https://www.example.com") .AllowAnyHeader() .AllowAnyMethod(); }); }); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { // ... 其他中间件 app.UseCors("AllowSpecificOrigin"); // ... 其他中间件 }
5.4 性能问题:N+1查询
问题:在使用EF Core时,如何避免N+1查询问题?
解决方案:使用Include和ThenInclude进行预加载:
// 错误示例:会导致N+1查询问题 var orders = _context.Orders.ToList(); foreach (var order in orders) { // 每个order都会触发一次数据库查询 var customer = _context.Customers.Find(order.CustomerId); // ... } // 正确示例:使用Include预加载相关实体 var orders = _context.Orders .Include(o => o.Customer) .Include(o => o.OrderItems) .ThenInclude(oi => oi.Product) .ToList();
5.5 内存泄漏问题
问题:在ASP.NET Core应用中,如何避免内存泄漏?
解决方案:
- 正确处理IDisposable资源:确保实现了IDisposable接口的服务被正确释放。
public class MyService : IDisposable { private readonly IDisposable _resource; public MyService() { _resource = new SomeDisposableResource(); } public void Dispose() { _resource?.Dispose(); } }
避免静态引用:静态变量会一直存在于应用程序的整个生命周期,可能导致内存泄漏。
取消长时间运行的操作:使用CancellationToken来取消长时间运行的操作。
public async Task<long> ProcessDataAsync(CancellationToken cancellationToken) { var result = 0L; for (int i = 0; i < int.MaxValue; i++) { // 检查是否已请求取消 cancellationToken.ThrowIfCancellationRequested(); // 执行一些计算 result += i; // 定期检查取消请求 if (i % 1000 == 0) { await Task.Delay(10, cancellationToken); } } return result; }
- 使用内存分析工具:使用dotMemory、ANTS Memory Profiler等工具来检测内存泄漏。
6. 最佳实践和进阶建议
6.1 代码组织和结构
遵循SOLID原则:
- 单一职责原则(SRP):每个类应该只有一个改变的理由。
- 开放封闭原则(OCP):软件实体应该对扩展开放,对修改封闭。
- 里氏替换原则(LSP):子类型必须能够替换其基类型。
- 接口隔离原则(ISP):客户端不应该依赖于它不使用的接口。
- 依赖倒置原则(DIP):高层模块不应该依赖于低层模块,两者都应该依赖于抽象。
使用CQRS模式:对于复杂的应用程序,考虑使用命令查询职责分离(CQRS)模式,将读取和写入操作分开。
// 查询服务 public interface IProductQueryService { Task<Product> GetProductByIdAsync(int id); Task<IEnumerable<Product>> GetAllProductsAsync(); Task<IEnumerable<Product>> GetProductsByCategoryAsync(int categoryId); } // 命令服务 public interface IProductCommandService { Task<int> CreateProductAsync(CreateProductCommand command); Task UpdateProductAsync(UpdateProductCommand command); Task DeleteProductAsync(int id); } // 命令示例 public class CreateProductCommand { public string Name { get; set; } public string Description { get; set; } public decimal Price { get; set; } public string ImageUrl { get; set; } public int StockQuantity { get; set; } public int CategoryId { get; set; } public bool IsFeatured { get; set; } }
- 使用领域驱动设计(DDD):对于复杂的业务领域,考虑使用DDD方法,将业务逻辑集中在领域模型中。
// 领域实体示例 public class Order : Entity { private readonly List<OrderItem> _orderItems = new List<OrderItem>(); public DateTime OrderDate { get; private set; } public decimal TotalAmount { get; private set; } public string Status { get; private set; } public int CustomerId { get; private set; } public Customer Customer { get; private set; } public int ShippingAddressId { get; private set; } public Address ShippingAddress { get; private set; } public IReadOnlyCollection<OrderItem> OrderItems => _orderItems.AsReadOnly(); // 私有构造函数,通过工厂方法创建 private Order() { } public static Order Create(int customerId, int shippingAddressId) { return new Order { CustomerId = customerId, ShippingAddressId = shippingAddressId, OrderDate = DateTime.Now, Status = "Pending", TotalAmount = 0 }; } public void AddOrderItem(Product product, int quantity) { if (product == null) throw new ArgumentNullException(nameof(product)); if (quantity <= 0) throw new ArgumentException("Quantity must be greater than zero", nameof(quantity)); var orderItem = new OrderItem(product, quantity); _orderItems.Add(orderItem); UpdateTotalAmount(); } public void UpdateTotalAmount() { TotalAmount = _orderItems.Sum(oi => oi.TotalPrice); } public void Confirm() { if (Status != "Pending") throw new InvalidOperationException("Only pending orders can be confirmed"); Status = "Confirmed"; } public void Ship() { if (Status != "Confirmed") throw new InvalidOperationException("Only confirmed orders can be shipped"); Status = "Shipped"; } public void Cancel() { if (Status == "Shipped" || Status == "Delivered") throw new InvalidOperationException("Cannot cancel shipped or delivered orders"); Status = "Cancelled"; } }
6.2 性能优化最佳实践
- 响应缓存:对于不经常变化的数据,使用响应缓存减少服务器负载。
[ResponseCache(Duration = 60, Location = ResponseCacheLocation.Client)] public IActionResult FeaturedProducts() { var products = _productService.GetFeaturedProducts(); return View(products); }
- 压缩响应:启用响应压缩减少网络传输时间。
public void ConfigureServices(IServiceCollection services) { services.AddResponseCompression(options => { options.EnableForHttps = true; options.Providers.Add<BrotliCompressionProvider>(); options.Providers.Add<GzipCompressionProvider>(); }); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { app.UseResponseCompression(); // ... }
- 使用缓存标记:使用缓存标记实现缓存失效。
public void ConfigureServices(IServiceCollection services) { services.AddOutputCache(options => { options.AddBasePolicy(builder => builder .With(c => c.HttpContext.Request.Path.StartsWithSegments("/products")) .Tag("products")); }); } // 在控制器中使用 [OutputCache(Tags = new[] { "products" })] public IActionResult Details(int id) { var product = _productService.GetProductById(id); return View(product); } // 在更新产品时使缓存失效 public IActionResult Update(Product product) { _productService.UpdateProduct(product); _outputCache.EvictByTagAsync("products"); return RedirectToAction(nameof(Index)); }
6.3 安全性最佳实践
- 使用HTTPS和HSTS:确保所有通信都通过HTTPS进行,并启用HTTP严格传输安全。
public void ConfigureServices(IServiceCollection services) { services.AddHsts(options => { options.Preload = true; options.IncludeSubDomains = true; options.MaxAge = TimeSpan.FromDays(365); }); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Error"); app.UseHsts(); } app.UseHttpsRedirection(); // ... }
- 实施CSRF保护:使用ASP.NET Core内置的防伪令牌功能。
// 在Startup.cs中配置 public void ConfigureServices(IServiceCollection services) { services.AddAntiforgery(options => { options.HeaderName = "X-XSRF-TOKEN"; options.Cookie.SameSite = SameSiteMode.Strict; options.Cookie.SecurePolicy = CookieSecurePolicy.Always; }); } // 在表单中包含防伪令牌 <form method="post"> @Html.AntiForgeryToken() <!-- 表单字段 --> </form> // 在AJAX请求中包含防伪令牌 function getCookie(name) { const value = `; ${document.cookie}`; const parts = value.split(`; ${name}=`); if (parts.length === 2) return parts.pop().split(';').shift(); } fetch('/api/products', { method: 'POST', headers: { 'Content-Type': 'application/json', 'RequestVerificationToken': getCookie('XSRF-TOKEN') }, body: JSON.stringify(data) });
- 安全配置:使用安全头增强应用程序安全性。
public void ConfigureServices(IServiceCollection services) { services.AddHsts(options => { options.Preload = true; options.IncludeSubDomains = true; options.MaxAge = TimeSpan.FromDays(365); }); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { // ... 其他中间件 app.Use(async (context, next) => { context.Response.Headers.Add("X-Content-Type-Options", "nosniff"); context.Response.Headers.Add("X-Frame-Options", "DENY"); context.Response.Headers.Add("X-XSS-Protection", "1; mode=block"); context.Response.Headers.Add("Referrer-Policy", "strict-origin-when-cross-origin"); context.Response.Headers.Add("Content-Security-Policy", "default-src 'self'; " + "script-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com; " + "style-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com; " + "img-src 'self' data: https://picsum.photos; " + "font-src 'self' https://cdnjs.cloudflare.com; " + "connect-src 'self'; " + "frame-ancestors 'none';"); await next(); }); // ... 其他中间件 }
6.4 DevOps和部署最佳实践
- CI/CD管道:设置持续集成和持续部署管道,自动化构建、测试和部署过程。
# Azure Pipelines示例 trigger: - main pool: vmImage: 'windows-latest' variables: solution: '**/*.sln' buildPlatform: 'Any CPU' buildConfiguration: 'Release' steps: - task: NuGetToolInstaller@1 - task: NuGetCommand@2 inputs: restoreSolution: '$(solution)' - task: VSBuild@1 inputs: solution: '$(solution)' msbuildArgs: '/p:DeployOnBuild=true /p:WebPublishMethod=Package /p:PackageAsSingleFile=true /p:SkipInvalidConfigurations=true /p:PackageLocation="$(build.artifactStagingDirectory)"' platform: '$(buildPlatform)' configuration: '$(buildConfiguration)' - task: VSTest@2 inputs: platform: '$(buildPlatform)' configuration: '$(buildConfiguration)' - task: PublishBuildArtifacts@1 inputs: pathtoPublish: '$(Build.ArtifactStagingDirectory)' artifactName: 'drop'
- 容器化部署:使用Docker容器化应用程序,简化部署和环境一致性。
# Dockerfile示例 FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base WORKDIR /app EXPOSE 80 EXPOSE 443 FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build WORKDIR /src COPY ["ECommerceApp/ECommerceApp.csproj", "ECommerceApp/"] COPY ["ECommerceApp.Domain/ECommerceApp.Domain.csproj", "ECommerceApp.Domain/"] COPY ["ECommerceApp.Application/ECommerceApp.Application.csproj", "ECommerceApp.Application/"] COPY ["ECommerceApp.Infrastructure/ECommerceApp.Infrastructure.csproj", "ECommerceApp.Infrastructure/"] RUN dotnet restore "ECommerceApp/ECommerceApp.csproj" COPY . . WORKDIR "/src/ECommerceApp" RUN dotnet build "ECommerceApp.csproj" -c Release -o /app/build FROM build AS publish RUN dotnet publish "ECommerceApp.csproj" -c Release -o /app/publish FROM base AS final WORKDIR /app COPY --from=publish /app/publish . ENTRYPOINT ["dotnet", "ECommerceApp.dll"]
- 监控和日志记录:实现全面的监控和日志记录策略,以便及时发现和解决问题。
// 在Startup.cs中配置日志记录 public void ConfigureServices(IServiceCollection services) { services.AddLogging(loggingBuilder => { loggingBuilder.AddConsole(); loggingBuilder.AddDebug(); loggingBuilder.AddEventSourceLogger(); if (Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == "Production") { loggingBuilder.AddApplicationInsights(); } }); } // 在控制器中使用日志记录 public class ProductsController : Controller { private readonly ILogger<ProductsController> _logger; public ProductsController(ILogger<ProductsController> logger) { _logger = logger; } public IActionResult Details(int id) { try { _logger.LogInformation("Getting product details for ID: {ProductId}", id); var product = _productService.GetProductById(id); if (product == null) { _logger.LogWarning("Product not found for ID: {ProductId}", id); return NotFound(); } return View(product); } catch (Exception ex) { _logger.LogError(ex, "Error getting product details for ID: {ProductId}", id); return StatusCode(500, "An error occurred while processing your request."); } } }
7. 总结
通过本文的实战案例和经验分享,我们深入探讨了ASP.NET项目开发的核心技能,从基础概念到高级技术,从代码实现到最佳实践。我们构建了一个完整的电子商务网站,涵盖了产品管理、购物车功能、订单处理等核心功能,并分享了在开发过程中的经验教训和解决方案。
ASP.NET Core作为一个强大、灵活的框架,为Web开发提供了丰富的功能和工具。通过掌握其核心技能,如依赖注入、中间件、Entity Framework Core、身份认证和授权等,开发者可以构建高性能、安全、可维护的Web应用程序。
在实际开发中,我们需要注重代码质量、性能优化、安全性和可维护性,遵循最佳实践,不断学习和探索新的技术和方法。希望本文的内容能够帮助读者更好地理解和掌握ASP.NET项目开发的核心技能,在实际项目中取得成功。