
在 .NET 开发中,很容易陷入编码实践,这些实践可能会悄无声息地降低应用程序的质量、安全性和可维护性。这些“无声代码剧透”可能会引入错误,导致安全漏洞,并使代码难以阅读和更新。在本文中,我们将探讨 .NET 应用程序中的不良代码示例,并逐步演示如何根据干净的代码原则重构它,包括命名约定、配置管理、SQL 注入预防和更好的结构。
我们将探讨关键原则,例如依赖项注入、关注点分离、错误处理和结构化日志记录,同时我们将示例重构为干净、专业的解决方案。
让我们从 .NET 中订单处理工作流的基本示例开始。此示例存在几个影响可读性、可维护性和安全性的问题。我们将以此为起点,并在整篇文章中将其转换为干净、可维护的代码。
此示例代码执行订单处理、验证并更新数据库中的订单状态。但是,它充满了问题,包括命名不一致、硬编码值、缺乏关注点分离以及 SQL 注入漏洞。
using System.Data.SqlClient;
publicclassorder_service
{
privatestring conn_string ="your_connection_string_here";
publicboolprocessOrder(Order order)
{
if(order !=null)
{
if(order.Items !=null&& order.Items.Count >)
{
if(order.CustomerId !=null)
{
decimal discount =0.0m;
if(order.TotalAmount >)
{
discount =0.05m;// Apply discount for orders over 100
}
elseif(order.TotalAmount >)
{
discount =0.10m;// Apply discount for orders over 500
}
order.TotalAmount -= order.TotalAmount * discount;
if(order.ShippingAddress ==null|| order.ShippingAddress =="")
{
Console.WriteLine("Shipping address is required.");
returnfalse;
}
Console.WriteLine("Processing payment...");
// Assume payment is processed
UpdateOrderStatus(order.OrderId,"Processed");
Console.WriteLine("Order processed for customer "+ order.CustomerId);
returntrue;
}
else
{
Console.WriteLine("Invalid customer.");
returnfalse;
}
}
else
{
Console.WriteLine("No items in order.");
returnfalse;
}
}
else
{
Console.WriteLine("Order is null.");
returnfalse;
}
}
privatevoidUpdateOrderStatus(int orderId,string status)
{
SqlConnection connection =newSqlConnection(conn_string);
connection.Open();
// SQL Injection Vulnerability
string query =$"UPDATE Orders SET Status = '{status}' WHERE OrderId = {orderId}";
SqlCommand command =newSqlCommand(query, connection);
command.ExecuteNonQuery();
connection.Close();
}
}
要重构此代码,我们将:
让我们来演练一下重构过程的每个步骤。
为避免硬编码值,让我们将折扣阈值和费率移动到文件中。这种方法无需修改代码即可轻松更新,并提高跨环境的一致性。discountSettings.json
{
"ConnectionStrings":{
"DefaultConnection":"your_connection_string_here"
},
"DiscountSettings":{
"SmallOrderDiscount":0.05,
"LargeOrderDiscount":0.10,
"SmallOrderThreshold":100.0,
"LargeOrderThreshold":500.0
}
}
定义一个类以从 JSON 文件映射折扣设置。
public classDiscountSettings
{
publicdecimal SmallOrderDiscount {get;set;}
publicdecimal LargeOrderDiscount {get;set;}
publicdecimal SmallOrderThreshold {get;set;}
publicdecimal LargeOrderThreshold {get;set;}
}
将配置部分绑定到类,并在 的依赖项注入容器中注册它。DiscountSettingsStartup.cs
public classStartup
{
publicvoidConfigureServices(IServiceCollection services)
{
services.Configure<DiscountSettings>(Configuration.GetSection("DiscountSettings"));
services.AddTransient<OrderService>();
services.AddScoped<IPaymentProcessor, PaymentProcessor>();// Example dependency
services.AddScoped<OrderRepository>();
services.AddLogging();
}
}
让我们将数据库交互移动到单独的类 中,以使用 Dapper 处理数据库交互。这可确保安全的参数化 SQL 查询,从而防止 SQL 注入攻击。OrderRepository
using System.Data;
usingDapper;
usingMicrosoft.Extensions.Configuration;
usingSystem.Data.SqlClient;
publicclassOrderRepository
{
privatereadonlystring _connectionString;
publicOrderRepository(IConfiguration configuration)
{
_connectionString = configuration.GetConnectionString("DefaultConnection");
}
publicvoidUpdateOrderStatus(int orderId,string status)
{
using(IDbConnection connection =newSqlConnection(_connectionString))
{
// Using Dapper with parameterized queries to prevent SQL injection
string query ="UPDATE Orders SET Status = @Status WHERE OrderId = @OrderId";
connection.Execute(query,new{ Status = status, OrderId = orderId });
}
}
}
现在,我们将重构以使用 for database 交互,以及其他干净的代码改进,例如依赖项注入和关注点分离。OrderServiceOrderRepository
using Microsoft.Extensions.Options;
publicclassOrderService
{
privatereadonlydecimal _smallOrderDiscount;
privatereadonlydecimal _largeOrderDiscount;
privatereadonlydecimal _smallOrderThreshold;
privatereadonlydecimal _largeOrderThreshold;
privatereadonlyIPaymentProcessor _paymentProcessor;
privatereadonlyILogger<OrderService> _logger;
privatereadonlyOrderRepository _orderRepository;
publicOrderService(IOptions<DiscountSettings> discountSettings,IPaymentProcessor paymentProcessor,ILogger<OrderService> logger,OrderRepository orderRepository)
{
var settings = discountSettings.Value;
_smallOrderDiscount = settings.SmallOrderDiscount;
_largeOrderDiscount = settings.LargeOrderDiscount;
_smallOrderThreshold = settings.SmallOrderThreshold;
_largeOrderThreshold = settings.LargeOrderThreshold;
_paymentProcessor = paymentProcessor;
_logger = logger;
_orderRepository = orderRepository;
}
publicboolProcessOrder(Order order)
{
if(!ValidateOrder(order))returnfalse;
ApplyDiscount(order);
if(!ProcessPayment(order))returnfalse;
// Update order status in the database using Dapper
_orderRepository.UpdateOrderStatus(order.OrderId,"Processed");
_logger.LogInformation($"Order processed successfully for customer {order.CustomerId}");
returntrue;
}
privateboolValidateOrder(Order order)
{
if(order ==null)
{
_logger.LogError("Order is null.");
returnfalse;
}
if(string.IsNullOrWhiteSpace(order.CustomerId))
{
_logger.LogError("Invalid customer.");
returnfalse;
}
if(order.Items ==null||!order.Items.Any())
{
_logger.LogError("Order has no items.");
returnfalse;
}
if(string.IsNullOrWhiteSpace(order.ShippingAddress))
{
_logger.LogError("Shipping address is required.");
returnfalse;
}
returntrue;
}
privatevoidApplyDiscount(Order order)
{
decimal discount = order.TotalAmount >_largeOrderThreshold ? _largeOrderDiscount :
order.TotalAmount >_smallOrderThreshold ? _smallOrderDiscount :;
order.TotalAmount -= order.TotalAmount * discount;
}
privateboolProcessPayment(Order order)
{
try
{
_paymentProcessor.Process(order);
returntrue;
}
catch(Exception ex)
{
_logger.LogError(ex,"Payment processing failed.");
returnfalse;
}
}
}
SQL 注入预防:
存储库模式:
配置管理:
依赖注入:
改进的日志记录:
更简洁的代码结构:
为了重构此代码,我们将使用 Entity Framework Core 实现一个干净的体系结构,用于数据访问,使用 Unit of Work 和 Repository Pattern 来组织数据逻辑,使用 CQRS with MediatR 来分离读取和写入操作,并使用 FluentValidation 进行验证。
这种方法将产生一个结构良好、可维护和可测试的解决方案。让我们来了解一下每个步骤。
使用 Entity Framework Core 使我们能够使用强类型 ORM 处理数据库交互,从而消除了对原始 SQL 和手动连接管理的需求。
using Microsoft.EntityFrameworkCore;
publicclassOrderDbContext:DbContext
{
publicDbSet<Order> Orders {get;set;}
publicOrderDbContext(DbContextOptions<OrderDbContext> options):base(options){}
protectedoverridevoidOnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Order>().ToTable("Orders");
// Additional configurations if needed
}
}
BaseRepository 类将处理任何实体的常见 CRUD 操作,而 OrderRepository 将根据需要继承以包含特定于订单的逻辑。BaseRepository
using Microsoft.EntityFrameworkCore;
usingSystem.Collections.Generic;
usingSystem.Threading.Tasks;
publicclassBaseRepository<T>:IRepository<T>whereT:class
{
protectedreadonlyDbContext _context;
protectedreadonlyDbSet<T> _dbSet;
publicBaseRepository(DbContext context)
{
_context = context;
_dbSet = context.Set<T>();
}
publicvirtualasyncTask<T>GetByIdAsync(int id)=>await _dbSet.FindAsync(id);
publicvirtualasyncTaskAddAsync(T entity)=>await _dbSet.AddAsync(entity);
publicvirtualasyncTaskUpdateAsync(T entity)
{
_dbSet.Attach(entity);
_context.Entry(entity).State = EntityState.Modified;
}
publicvirtualasyncTaskDeleteAsync(T entity)=> _dbSet.Remove(entity);
publicvirtualasyncTask<IEnumerable<T>>GetAllAsync()=>await _dbSet.ToListAsync();
}
public classOrderRepository:BaseRepository<Order>
{
publicOrderRepository(OrderDbContext context):base(context)
{
}
// Add any Order-specific methods here if needed
}
Unit of Work 模式有助于协调跨多个存储库保存更改,从而允许所有操作作为单个事务完成。
public interfaceIUnitOfWork:IDisposable
{
IRepository<Order> Orders {get;}
Task<int>CompleteAsync();
}
public classUnitOfWork:IUnitOfWork
{
privatereadonlyOrderDbContext _context;
publicIRepository<Order> Orders {get;}
publicUnitOfWork(OrderDbContext context,OrderRepository orderRepository)
{
_context = context;
Orders = orderRepository;
}
publicasyncTask<int>CompleteAsync()=>await _context.SaveChangesAsync();
publicvoidDispose()=> _context.Dispose();
}
实施 **CQRS(命令查询责任分离)**允许我们将读取和写入操作分开,使每个操作更易于测试、修改和扩展。我们将使用 MediatR 来处理命令和查询,将业务逻辑与控制器解耦。
using MediatR;
publicclassProcessOrderCommand: IRequest\<bool>
{
publicint OrderId {get;set;}
}
using System.Threading;
usingSystem.Threading.Tasks;
usingMediatR;
usingMicrosoft.Extensions.Logging;
publicclassProcessOrderCommandHandler:IRequestHandler<ProcessOrderCommand, bool>
{
privatereadonlyIUnitOfWork _unitOfWork;
privatereadonlyIPaymentProcessor _paymentProcessor;
privatereadonlyILogger<ProcessOrderCommandHandler> _logger;
publicProcessOrderCommandHandler(IUnitOfWork unitOfWork,IPaymentProcessor paymentProcessor,ILogger<ProcessOrderCommandHandler> logger)
{
_unitOfWork = unitOfWork;
_paymentProcessor = paymentProcessor;
_logger = logger;
}
publicasyncTask<bool>Handle(ProcessOrderCommand request,CancellationToken cancellationToken)
{
var order =await _unitOfWork.Orders.GetByIdAsync(request.OrderId);
if(order ==null)
{
_logger.LogError("Order not found.");
returnfalse;
}
ApplyDiscount(order);
if(!awaitProcessPayment(order))
{
returnfalse;
}
order.Status ="Processed";
await _unitOfWork.Orders.UpdateAsync(order);
await _unitOfWork.CompleteAsync();
_logger.LogInformation($"Order processed successfully for customer {order.CustomerId}");
returntrue;
}
privatevoidApplyDiscount(Order order)
{
// Discount logic based on thresholds from config
}
privateasyncTask<bool>ProcessPayment(Order order)
{
try
{
await _paymentProcessor.ProcessAsync(order);
returntrue;
}
catch(Exception ex)
{
_logger.LogError(ex,"Payment processing failed.");
returnfalse;
}
}
}
使用 FluentValidation 使我们能够编写干净且可重用的验证逻辑,这些逻辑可以很容易地进行单元测试并在整个应用程序中应用。
using FluentValidation;
publicclassOrderValidator:AbstractValidator<Order>
{
publicOrderValidator()
{
RuleFor(order => order.CustomerId).NotEmpty().WithMessage("Customer ID is required.");
RuleFor(order => order.Items).NotEmpty().WithMessage("Order must contain at least one item.");
RuleFor(order => order.ShippingAddress).NotEmpty().WithMessage("Shipping address is required.");
RuleFor(order => order.TotalAmount).GreaterThan().WithMessage("Total amount must be greater than zero.");
}
}
配置 MediatR、FluentValidation 和 EF Core 以进行依赖项注入,确保所有内容都已注册并可供使用。Startup.cs
public classStartup
{
publicvoidConfigureServices(IServiceCollection services)
{
services.AddDbContext<OrderDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddScoped<IRepository<Order>, OrderRepository>();
services.AddScoped<IUnitOfWork, UnitOfWork>();
services.AddMediatR(typeof(ProcessOrderCommandHandler).Assembly);
services.AddTransient<IValidator<Order>, OrderValidator>();
services.AddControllers();
}
}
完成上述步骤后,我们的代码现在组织如下:
设置 MediatR 后,控制器可以轻松发送命令并处理响应。
[ApiController]
[Route("api/[controller]")]
publicclassOrdersController:ControllerBase
{
privatereadonlyIMediator _mediator;
publicOrdersController(IMediator mediator)
{
_mediator = mediator;
}
[HttpPost("order/process/{id}")]
publicasyncTask<IActionResult>ProcessOrder(int id)
{
var result =await _mediator.Send(newProcessOrderCommand{ OrderId = id });
returnresult ?Ok("Order processed successfully"):BadRequest("Order processing failed");
}
}
通过重构原始代码以使用 Entity Framework Core、Unit of Work、Repository Pattern、CQRS with MediatR 和 FluentValidation,我们已将紧密耦合、易受攻击的代码库转换为干净、可扩展且专业的 .NET 解决方案:
这种方法可确保您的应用程序易于维护、可扩展且具有弹性,从而为长期成功做好准备。