ASP.NET Core API 컨트롤러에서 피해야 할 12가지 나쁜 습관(Practices )

 




 

ASP.NET Core는 웹 애플리케이션 및 웹 API를 빌드하는 데 사용되는 Microsoft의 프레임워크입니다. 이 프레임워크를 사용하면 강력하고 안정적인 웹 애플리케이션을 개발할 수 있습니다.

해당 사이트의 글은 ASP.NET Core API 컨트롤러를 작성할 때 주의해야 할 12가지 잘못된 방법에 대해 설명하고 있습니다. 이러한 나쁜 실천 방법을 피하는 것은 코드의 품질과 성능을 향상시키는 데 도움이 됩니다.

예를 들어, 글에는 API 컨트롤러에서 비동기 코드를 적절하게 사용하는 방법, 예외 처리 및 오류 관리, 보안에 대한 지침 등이 포함되어 있습니다. 이러한 규칙을 따르면 애플리케이션의 안정성과 유지 보수성을 높일 수 있습니다.

여러분이 ASP.NET Core를 사용하여 웹 API를 개발하고 있다면, 해당 사이트의 글을 읽어보고 나쁜 실천 방법을 피해 최고의 코드를 작성하는 데 도움을 받을 수 있을 것입니다. 코드의 품질과 유지 보수성을 향상시켜 안정적이고 성능이 우수한 웹 애플리케이션을 개발하시기를 바랍니다.

 

 

ASP.NET Core에서 컨트롤러를 개발할 때 유지 관리 가능성, 성능 및 모범 사례 준수를 보장하기 위해 피해야 할 특정 관행이 있습니다. 다음은 컨트롤러에서 피해야 할 12가지 사항입니다.

 

 

1. 긴밀한 결합 (Tight coupling)


특정 종속성이나 프레임워크가 있는 컨트롤러에서 긴밀한 결합을 피하세요. 대신 의존성 주입을 사용하여 컨트롤러에 의존성을 주입하세요. 이렇게 하면 느슨한 결합이 촉진되고 코드의 테스트 및 유지 관리가 더 쉬워집니다.

 

// 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 요청을 처리하고 응답을 생성하는 데 집중해야 합니다. 데이터 액세스, 인증 또는 권한 부여와 같은 문제를 컨트롤러에서 직접 처리하지 마세요. 이러한 책임은 별도의 클래스나 미들웨어에 위임하세요.

 

// 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. 예외 처리 부족


컨트롤러는 예외를 정상적으로 처리하고 적절한 오류 응답을 반환해야 합니다. 적절한 처리 없이 예외가 클라이언트에 전파되지 않도록 하세요. 예외 필터 또는 미들웨어와 같은 글로벌 예외 처리 메커니즘을 구현하여 오류 처리를 중앙 집중화하세요.

 

// 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);
}

 

예를 들어, global exception middleware 를 예시로 든다면,

 

public class ExceptionMiddleware
{
    private readonly RequestDelegate _next;

    public ExceptionMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        try
        {
            await _next(context);
        }
        catch (Exception ex)
        {
            // Log the error
            // ...

            await HandleExceptionAsync(context, ex);
        }
    }

    private Task HandleExceptionAsync(HttpContext context, Exception exception)
    {
        context.Response.ContentType = "application/json";
        context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;

        var response = new
        {
            StatusCode = context.Response.StatusCode,
            Message = "Internal Server Error"
        };

        return context.Response.WriteAsync(JsonConvert.SerializeObject(response));
    }
}

 

이 예외 미들웨어를 사용하면 HTTP 요청을 처리하는 동안 발생하는 처리되지 않은 예외가 모두 포착되고 일반 오류 메시지와 상태 코드 500(내부 서버 오류)이 포함된 JSON 응답이 클라이언트에 반환됩니다.
 
보다 자세한 오류 정보를 포함하거나 요구 사항에 따라 응답 구조를 수정하도록 HandleExceptionAsync 메서드를 사용자 지정할 수 있습니다.
 
 
이 미들웨어를 ASP.NET Core 웹 API 애플리케이션에서 사용하려면 Startup.cs 파일 또는 Program.cs에 다음 코드를 추가하면 됩니다. 
 
app.UseMiddleware<ExceptionMiddleware>();
 
 

자세히 알아보기 : Exception filters | Middleware

 

 

4. 장시간 실행 작업 (Long-running operations)


컨트롤러에서 오래 실행되는 작업을 수행하지 마십시오. 작업을 즉시 실행하는 대신 백그라운드에서 처리할 수 있도록 대기열에 넣으십시오.

 

// 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);
}

 

자세히 알아보기 :  Queue service | Background tasks | Hangfire scheduler

 

좀 더 코드를 상세히 알아봤습니다. 

// Import the necessary namespaces
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;

// Assuming Report is a class representing the report data
public class Report 
{
    // Properties of the report class
    // ...
}

// Controller class
public class ReportController : ControllerBase
{
    private readonly IMessageQueueService _messageQueueService;

    public ReportController(IMessageQueueService messageQueueService)
    {
        _messageQueueService = messageQueueService;
    }

    // POST: api/report/generate
    [HttpPost("generate")]
    public async Task<IActionResult> GenerateReport([FromBody] Report report)
    {
        // Enqueue the report for background processing
        var taskIdentifier = await _messageQueueService.EnqueueAsync(report);

        // Return the task identifier to the client
        return StatusCode(StatusCodes.Status202Accepted, taskIdentifier);
    }
}

// IMessageQueueService interface to represent the background task queue service
public interface IMessageQueueService
{
    Task<string> EnqueueAsync(Report report);
}

// Implementation of IMessageQueueService
public class MessageQueueService : IMessageQueueService
{
    // Implement the EnqueueAsync method to enqueue the task for background processing
    public async Task<string> EnqueueAsync(Report report)
    {
        // Perform any necessary background processing here
        // ...
        // ...

        // For demonstration purposes, we can return a simple task identifier as a string
        return Guid.NewGuid().ToString();
    }
}

이 코드에서는 백그라운드 작업 큐 서비스에 대한 컨트랙트를 정의하는 IMessageQueueService 인터페이스가 있다고 가정했습니다. MessageQueueService 클래스는 이 인터페이스를 구현하고, ReportController 클래스는 종속성 주입을 사용하여 생성자를 통해 IMessageQueueService 인스턴스를 주입합니다.

이제 컨트롤러의 GenerateReport 메서드는 요청 본문에서 Report 개체를 수신하고 _messageQueueService를 사용하여 백그라운드 처리를 위해 큐에 넣은 다음 즉시 202 수락됨 상태 코드와 작업 식별자가 포함된 응답을 클라이언트에 반환합니다.

이렇게 하면 클라이언트는 장기 실행 작업이 완료될 때까지 기다릴 필요 없이 자체 처리를 계속할 수 있습니다. 백그라운드 작업은 비동기식으로 처리되므로 서버의 응답성이 향상되고 컨트롤러에서 장기 실행 작업과 관련된 잠재적인 문제를 방지할 수 있습니다.

 

 

 

 

5. 유효성 검사 부족 (Lack of validation)


입력 유효성 검사는 애플리케이션의 무결성과 보안을 보장하는 데 매우 중요합니다. 컨트롤러에서 입력 유효성 검사를 소홀히 하지 마세요. [Required], [MaxLength], custom validation 로직과 같은 모델 유효성 검사 속성을 활용하여 들어오는 데이터를 유효성 검사하세요.

 

 

// 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);
}

 

자세히 알아보기 :  Model Validation | FluentValidation

 

6. 데이터베이스 직접 액세스


컨트롤러에서 데이터베이스에 직접 액세스하지 마세요. 대신 리포지토리 또는 데이터 액세스 서비스와 같은 추상화 계층을 사용하여 컨트롤러를 특정 데이터 액세스 기술로부터 분리하세요. 이렇게 하면 테스트 가능성과 유지 관리 용이성이 향상됩니다.

 

// 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);
}

자세히 알아보기 :  Repository Pattern or Data Layer

 

8. 인증 및 권한 부여 부족 (Lack of authentication and authorization)


민감한 작업에 대한 인증 및 권한 부여를 구현하세요. 그에 따라 컨트롤러를 보호하세요.

 

// Avoid
[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);
}

자세히 알아보기 :  Authentication and Authorization

 

 

9. 과도한 논리 (Excessive logic)


컨트롤러는 주로 들어오는 요청을 처리하고 응답을 반환하는 역할을 담당해야 합니다. 복잡한 비즈니스 로직을 포함해서는 안 됩니다. 대신 비즈니스 로직은 전용 서비스 클래스나 다른 컴포넌트에 위임하세요.

 

// 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);
}

자세히 알아보기 : Common web application architectures

 

 

10. HTTP verbs  및 RESTful principles 무시


ASP.NET Core의 컨트롤러는 RESTful 아키텍처의 원칙을 준수해야 합니다. 부적절한 HTTP 동사 또는 RESTful 규칙에 맞지 않는 동작을 사용하지 마세요. 리소스에 대해 해당 작업을 수행하려면 적절한 HTTP 동사(GET, POST, PUT, DELETE 등) 를 사용하세요.

 

// Avoid:
public IActionResult DeleteProduct(int productId)
{
    // ...
}


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

 

자세히 알아보기 : RESTful web API design

 

 

11. 적절한 라우팅 부족 (Lack of proper routing)


컨트롤러가 수신 요청을 처리할 수 있도록 라우팅이 올바르게 설정되어 있는지 확인하세요. 일관되지 않거나 모호한 라우팅 구성을 피하세요. 충돌을 방지하고 라우팅 로직의 명확성을 향상시키기 위해 '[Route]`와 같은 속성을 사용하여 명확하고 명시적인 라우팅 템플릿을 정의하세요.

 

// Avoid:
[HttpGet]
public IActionResult Get()
{
    // ...
}


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

자세히 알아보기: Routing

 

 

12. 로깅 부족 (Lack of logging)


로깅은 코드 실행 중 중요한 이벤트, 조건 및 오류를 추적하는 데 도움이 되므로 애플리케이션 개발에서 매우 중요한 요소입니다.

 

// 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();
}

 

가능한 경우 middleware 또는 action filter 를 사용하여 관련 정보를 캡처하세요.

 

자세히 알아보기 :  Logging | Middleware | Exception filters

 

이러한 일반적인 함정(pitfalls) 을 피함으로써 유지 관리가 가능하고 테스트가 가능하며 API web development 모범 사례를 따르는 컨트롤러를 ASP.NET Core에서 개발할 수 있습니다.

 

읽어주셔서 감사합니다
저는 미디엄 기사를 통해 웹 개발, 커리어 팁, 최신 기술 트렌드에 대한 인사이트를 공유합니다. 저와 함께 흥미로운 주제를 함께 탐구해 보세요. 함께 배우고, 성장하고, 창조합시다!

 


 

번역

 

https://levelup.gitconnected.com/12-bad-practices-to-avoid-in-asp-net-core-api-controllers-3ba52a10954e

 

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

Expert Tips for Effective ASP.NET Core API Controller Development

levelup.gitconnected.com

 

 

 

댓글

가장 많이 본 글

인천국제공항 제1여객터미널에서 일본으로 가는 출국 절차 안내 ✈️

빅데이터 시대의 도구, LUCY 2.0

Playwright MCP 설정 및 활용 방법