5. DTO와 AutoMapper 그리고 Repository

5-7. Plant 번호

Plant API 컨트롤러를 완성하고, 다음 API인 Plant Number에 대해 같은 작업을 수행하는 연습을 할 것입니다.

Plant Number 모델을 생성하고, 이를 기반으로 CRUD 작업을 구현하는 것이 목표입니다. 다음은 강의 내용의 요약 및 번역입니다.

  1. 모델 생성: Plant Number 클래스를 모델 폴더에 추가합니다. 이 클래스는 각 식물의 고유 번호와 특별한 요구 사항(예: 장애인 접근 가능 여부)을 포함합니다.
  2. 속성:
    • PlantNo: 식물 번호 (사용자가 입력, 고유 키로 사용됨)SpecialDetail: 특별한 요구 사항에 대한 설명CreatedUpdated 날짜
  3. 데이터 주석:
    • PlantNo는 기본 키로 지정되며, 데이터베이스에서 자동으로 생성되지 않도록 설정합니다.
  4. DTO 생성: CRUD 작업을 위해 PlantNumberDTO, PlantNumberCreateDTO, PlantNumberUpdateDTO를 생성합니다. 이 DTO들은 초기에는 동일한 속성을 가지지만, 향후 요구 사항 변경에 따라 쉽게 수정할 수 있도록 별도로 관리하는 것이 좋습니다.
// PlantNumber.cs
public class PlantNumber
{
    [Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
    public int PlantNo { get; set; }
    public string SpecialDetails { get; set; }
    public DateTime CreatedDate { get; set; }
    public DateTime UpdatedDate { get; set; }
}
​
// PlantNumberDTO.cs 
public class PlantNumberDTO
{
    [Required]
    public int PlantNo { get; set; }
​
    public string SpecialDetails { get; set; }
}
​
//PlantNumberCreateDTO.cs
public class PlantNumberCreateDTO
{
    [Required]
    public int PlantNo { get; set; }
​
    public string SpecialDetails { get; set; }
}
​
//PlantNumberUpdateDTO.cs
public class PlantNumberUpdateDTO
{
    [Required]
    public int PlantNo { get; set; }
​
    public string SpecialDetails { get; set; }
}

데이터베이스 테이블 생성

  • ApplicationDbContext 수정: PlantNumbers에 대한 DbSet을 추가하여 데이터베이스에 테이블을 생성합니다.

리포지토리 추가

  • 인터페이스 및 구현: IPlantNumberRepository 인터페이스와 PlantNumberRepository 클래스를 생성하여 CRUD 작업을 정의하고 구현합니다.

컨트롤러 추가

  • PlantNumberAPIController 생성: CRUD 작업을 위한 RESTful API 엔드포인트를 구현합니다. 각 메소드는 PlantNumberRepository를 사용하여 데이터베이스 작업을 수행합니다.

AutoMapper 설정

  • 매핑 구성: PlantNumber와 관련 DTO 간의 매핑을 MappingConfig 파일에 추가하여 객체 변환을 자동화합니다.
// PlantNumberAPIController.cs 추가
[Route("api/PlantNumberAPI")]
[ApiController]
public class PlantNumberAPIController : ControllerBase
{
    protected APIResponse _response;
    private readonly IPlantNumberRepository _dbPlantNumber;
    private readonly IMapper _mapper;
​
    public PlantNumberAPIController(IPlantNumberRepository dbPlantNumber, IMapper mapper)
    {
        _dbPlantNumber = dbPlantNumber;
        _mapper = mapper;
        this._response = new();
    }
​
​
    [HttpGet]
    [ProducesResponseType(StatusCodes.Status200OK)]
    public async Task<ActionResult<APIResponse>> GetPlantNumbers()
    {
        try
        {
​
            IEnumerable<PlantNumber> plantNumberList = await _dbPlantNumber.GetAllAsync();
            _response.Result = _mapper.Map<List<PlantNumberDTO>>(plantNumberList);
            _response.StatusCode = HttpStatusCode.OK;
            return Ok(_response);
​
        }
        catch (Exception ex)
        {
            _response.IsSuccess = false;
            _response.ErrorMessages
                 = new List<string>() { ex.ToString() };
        }
        return _response;
​
    }
​
    [HttpGet("{id:int}", Name = "GetPlantNumber")]
    [ProducesResponseType(StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status400BadRequest)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    public async Task<ActionResult<APIResponse>> GetPlantNumber(int id)
    {
        try
        {
            if (id == 0)
            {
                _response.StatusCode = HttpStatusCode.BadRequest;
                return BadRequest(_response);
            }
            var plantNumber = await _dbPlantNumber.GetAsync(u => u.PlantNo == id);
            if (plantNumber == null)
            {
                _response.StatusCode = HttpStatusCode.NotFound;
                return NotFound(_response);
            }
            _response.Result = _mapper.Map<PlantNumberDTO>(plantNumber);
            _response.StatusCode = HttpStatusCode.OK;
            return Ok(_response);
        }
        catch (Exception ex)
        {
            _response.IsSuccess = false;
            _response.ErrorMessages
                 = new List<string>() { ex.ToString() };
        }
        return _response;
    }
​
    [HttpPost]
    [ProducesResponseType(StatusCodes.Status201Created)]
    [ProducesResponseType(StatusCodes.Status400BadRequest)]
    [ProducesResponseType(StatusCodes.Status500InternalServerError)]
    public async Task<ActionResult<APIResponse>> CreatePlantNumber([FromBody] PlantNumberCreateDTO createDTO)
    {
        try
        {
            if (await _dbPlantNumber.GetAsync(u => u.PlantNo == createDTO.PlantNo) != null)
            {
                ModelState.AddModelError("CustomError", "Plant Number already Exists!");
                return BadRequest(ModelState);
            }
​
            if (createDTO == null)
            {
                return BadRequest(createDTO);
            }
​
            PlantNumber plantNumber = _mapper.Map<PlantNumber>(createDTO);
​
            await _dbPlantNumber.CreateAsync(plantNumber);
            _response.Result = _mapper.Map<PlantNumberDTO>(plantNumber);
            _response.StatusCode = HttpStatusCode.Created;
            return CreatedAtRoute("GetPlant", new { id = plantNumber.PlantNo }, _response);
        }
        catch (Exception ex)
        {
            _response.IsSuccess = false;
            _response.ErrorMessages
                 = new List<string>() { ex.ToString() };
        }
        return _response;
    }
​
    [ProducesResponseType(StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    [ProducesResponseType(StatusCodes.Status400BadRequest)]
    [HttpDelete("{id:int}", Name = "DeletePlantNumber")]
    public async Task<ActionResult<APIResponse>> DeletePlantNumber(int id)
    {
        try
        {
            if (id == 0)
            {
                return BadRequest();
            }
            var plantNumber = await _dbPlantNumber.GetAsync(u => u.PlantNo == id);
            if (plantNumber == null)
            {
                return NotFound();
            }
            await _dbPlantNumber.RemoveAsync(plantNumber);
            _response.StatusCode = HttpStatusCode.NoContent;
            _response.IsSuccess = true;
            return Ok(_response);
        }
        catch (Exception ex)
        {
            _response.IsSuccess = false;
            _response.ErrorMessages
                 = new List<string>() { ex.ToString() };
        }
        return _response;
    }
​
    [HttpPut("{id:int}", Name = "UpdatePlantNumber")]
    [ProducesResponseType(StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status400BadRequest)]
    public async Task<ActionResult<APIResponse>> UpdatePlantNumber(int id, [FromBody] PlantNumberUpdateDTO updateDTO)
    {
        try
        {
            if (updateDTO == null || id != updateDTO.PlantNo)
            {
                return BadRequest();
            }
​
            PlantNumber model = _mapper.Map<PlantNumber>(updateDTO);
​
            await _dbPlantNumber.UpdateAsync(model);
            _response.StatusCode = HttpStatusCode.NoContent;
            _response.IsSuccess = true;
            return Ok(_response);
        }
        catch (Exception ex)
        {
            _response.IsSuccess = false;
            _response.ErrorMessages
                 = new List<string>() { ex.ToString() };
        }
        return _response;
    }
}
​
// MappingConfig.cs 수정 
CreateMap<PlantNumber, PlantNumberDTO>().ReverseMap();
CreateMap<PlantNumber, PlantNumberCreateDTO>().ReverseMap();
CreateMap<PlantNumber, PlantNumberUpdateDTO>().ReverseMap();
​
// Program.cs 수정 
builder.Services.AddScoped<IPlantNumberRepository, PlantNumberRepository>();
​
// IPlantNumberRepository.cs 추가 
public interface IPlantNumberRepository : IRepository<PlantNumber>
{
    Task<PlantNumber> UpdateAsync(PlantNumber entity);
}
​
// PlantNumberRepository.cs 추가 
public class PlantNumberRepository : Repository<PlantNumber>, IPlantNumberRepository
{
    private readonly ApplicationDbContext _db;
    public PlantNumberRepository(ApplicationDbContext db) : base(db)
    {
        _db = db;
    }
​
    public async Task<PlantNumber> UpdateAsync(PlantNumber entity)
    {
        entity.UpdatedDate = DateTime.Now;
        _db.PlantNumbers.Update(entity);
        await _db.SaveChangesAsync();
        return entity;
    }
}

패키지 콘솔 실행

PM> add-migration AddPlantNumberTable
PM> update-database

이를 통해 는 .Net Core Web API를 사용하여 데이터베이스와 상호작용하는 방법, 리포지토리 패턴의 적용, AutoMapper를 사용한 객체 매핑, 그리고 RESTful API 엔드포인트의 구현 방법을 실습합니다.