[TOC]

1、紧密耦合

​ 避免在控制器中声明特定的依赖项实例,应该使用依赖注入系统将依赖项注入到控制器中。后者避免了紧密耦合,更加易于维护和测试。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Avoid
[ApiController]
[Route("[controller]")]
public class ProductController : ControllerBase
{
ProductService productService = new ProductService();
// ...
}

// Prefer:
[ApiController]
[Route("[controller]")]
public class ProductController : ControllerBase
{
private readonly ILogger<ProductController> _logger;
private readonly IProductService _productService;

public ProductController(IProductService productService, ILogger<ProductController> logger)
{
_logger = logger;
_productService = productService;
}
// ...
}

2、Mixing Concerns功能混淆

​ 控制器应专注于 HTTP 请求和生成响应。避免混合身份验证、授权或任何其他验证等问题,而是使用中间件或任何单独的类或服务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Avoid
[HttpPost]
public async Task<IActionResult> CreateProduct(ProductDto productDto)
{
// Authentication and authorization logic here
// ...
// Data access logic here
// ...
return Ok(StatusCodes.Status201Created);
}

// Prefer:
[HttpPost]
[Authorize]
public async Task<IActionResult> CreateProduct(ProductDto productDto)
{
await _productService.CreateAsync(productDto);

return Ok(StatusCodes.Status201Created);
}

3、缺乏异常处理

​ 避免在控制器中使用 try-catch 块,而是使用异常中间件来更好地处理异常以返回一般错误消息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// Avoid (Inconsistent error handling or try catch everywhere
[HttpPost]
public async Task<IActionResult> CreateProduct(ProductDto productDto)
{
try
{
await _productService.CreateAsync(productDto);

return Ok(StatusCodes.Status201Created);
}
catch (ProductValidationException ex)
{
return BadRequest(ex.Message);
}
catch (Exception ex)
{
_logger.LogError(ex, "An error occurred while creating the product.");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}


// Prefer Exception filters or Middleware
[HttpPost]
public async Task<IActionResult> CreateProduct(ProductDto productDto)
{
await _productService.CreateAsync(productDto);

return Ok(StatusCodes.Status201Created);
}

4、长时操作

​ 避免在控制器中执行长时间运行的操作。相反,应该把长时操作加入到后台运行的队列中,以避免系统停止服务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Avoid
[HttpGet]
public async Task<IActionResult> GenerateReport(Report report)
{
// Long-running operation
// ...
// ...

return Ok(report);
}

// Prefer
[HttpPost]
public async Task<IActionResult> GenerateReport(Report report)
{
var taskIdentifier = await _messageQueueService.EnqueueAsync(report);

return StatusCode(StatusCodes.Status202Accepted, taskIdentifier);
}

5、缺少验证

​ 输入验证对于确保系统的完整性和安全性至关重要。避免忽视控制器中的输入验证。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Avoid
[HttpPost]
public async Task<IActionResult> CreateProduct(ProductDto productDto)
{
// No validation
await _productService.CreateAsync(productDto);
return Ok(StatusCodes.Status201Created);
}

// Prefer:
[HttpPost]
public async Task<IActionResult> CreateProduct([FromBody] ProductDto productDto)
{
if (!ModelState.IsValid)
{
return StatusCode(StatusCodes.Status400BadRequest, ModelState);
}

await _productService.CreateAsync(productDto);
return Ok(StatusCodes.Status201Created);
}

6、直接访问数据库

​ 避免直接访问数据库,而是使用服务或存储库将控制器与特定的数据访问技术分离。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Avoid
[HttpGet]
public IActionResult GetProduct(int productId)
{
var product = dbContext.Products.Find(productId);
return Ok(product);
}

// Prefer:
[HttpGet]
public async Task<IActionResult> GetProduct(int productId)
{
var product = await _productService.GetByIdAsync(productId);
return Ok(product);
}

7、缺乏缓存

​ 在适当的时候实施缓存机制。利用缓存来提高性能并减少服务器上的负载。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Avoi
[HttpGet]
public async Task<IActionResult> GetProducts()
{
var products = await _productService.GetAllAsync();
return Ok(products);
}


// Prefer
[HttpGet]
[ResponseCache(Duration = 60)] // Cache the response for 60 seconds
public async Task<IActionResult> GetProducts()
{
var products = await _productService.GetAllAsync();
return Ok(products);
}

8、缺乏身份验证和授权

​ 对敏感操作实施身份验证和授权。相应地保护控制器和操作方法的安全访问。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Avoi
[HttpPost]
public async Task<IActionResult> DeleteProduct(int productId)
{
// No authentication or authorization
await _productService.DeleteAsync(productId);
return StatusCode(StatusCodes.Status200OK);
}

// Prefer
[HttpPost]
[Authorize(Roles = "Admin")]
public async Task<IActionResult> DeleteProduct(int productId)
{
await _productService.DeleteAsync(productId);
return StatusCode(StatusCodes.Status200OK);
}

9、逻辑过多

​ 避免过多的逻辑。控制器应主要负责处理传入请求和返回响应。对于任何复杂的逻辑,应使用单独的实用程序或服务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Avoid
[HttpGet]
public async Task<IActionResult> GetProducts()
{
// Complex business logic here
// ...
// ...
return Ok(products);
}

// Prefer:
[HttpGet]
public async Task<IActionResult> GetProducts()
{
var products = await _productService.GetAllAsync();
return Ok(products);
}

10、忽略 HTTP 动词实现RESTful 原则

​ ASP.NET Core 中的控制器应遵循 RESTful 架构的原则。避免使用不符合 RESTful 约定的不当 HTTP 谓词或操作。使用适当的 HTTP 动词(GET、POST、PUT、DELETE 等)。

1
2
3
4
5
6
7
8
9
10
11
12
13
// Avoid
public IActionResult DeleteProduct(int productId)
{
// ...
}


// Prefer:
[HttpDelete("/api/products/{id}")]
public IActionResult DeleteProduct(int productId)
{
// ...
}

11、缺乏正确的路由

​ 确保控制器已正确路由以处理传入请求。避免不一致或不明确的路由配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Avoid
[HttpGet]
public IActionResult Get()
{
// ...
}


// Prefer:
[HttpGet("api/products")]
public IActionResult Get()
{
// ...
}

12、缺少日志

​ 日志记录是应用程序开发的一个非常关键的方面,因为它有助于在代码执行期间跟踪重要事件、条件和错误。使用中间件或操作筛选器捕获相关信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// Avoid
[HttpPost]
public async Task<IActionResult> CreateProduct(ProductDto productDto)
{
if (someSimpleCondition)
{
// ...
}

await _productService.CreateAsync(productDto);
return Ok();
}

// Prefer
[HttpPost]
public async Task<IActionResult> CreateProduct(ProductDto productDto)
{
if (someSimpleCondition)
{
// ...
_logger.LogWarning("Warning: Some simple condition is met."); // Log a warning
}

await _productService.CreateAsync(productDto);
return Ok();
}

参考:

  1. 12 Bad Practices to Avoid in ASP.NET Core API Controllers