Model validation in ASP.NET Core

Model Validations

https://learn.microsoft.com/en-us/aspnet/core/mvc/models/validation?view=aspnetcore-8.0

ASP.NET Core MVC 또는 Razor Pages 앱에서 사용자 입력의 유효성을 검사하는 방법

Model state

Model state represents errors that come from two subsystems: model binding and model validation.
Model statemodel binding(모델 바인딩)model validation(모델 유효성 검사)라는 두 가지 서브 시스템에서 발생한 오류를 나타냅니다.

Errors that originate from model binding are generally data conversion errors.
model binding에서 발생한 오류는 일반적으로 데이터 변환 오류입니다.

For example, an “x” is entered in an integer field.
예를 들어, 정수(int) 필드에 ‘x'(char)가 입력된 경우입니다.

Model validation occurs after model binding and reports errors where data doesn’t conform to business rules.
Model validation는 모델 바인딩 후에 발생하며, 데이터가 비즈니스 규칙에 부합하지 않는 경우 오류를 보고합니다.

For example, a 0 is entered in a field that expects a rating between 1 and 5.
예를 들어, 1에서 5 사이의 값을 기대하는 필드에 0이 입력된 경우입니다.

Both model binding and model validation occur before the execution of a controller action or a Razor Pages handler method.
model bindingmodel validation컨트롤러 액션이나 Razor Pages 핸들러 메서드가 실행되기 전에 발생합니다.

For web apps, it’s the app’s responsibility to inspect ModelState.IsValid and react appropriately.
웹 애플리케이션에서는 ModelState.IsValid를 검사하고 적절하게 반응하는 것이 애플리케이션의 책임입니다.

Web apps typically redisplay the page with an error message, as shown in the following Razor Pages example:
웹 애플리케이션은 일반적으로 오류 메시지가 포함된 페이지를 다시 표시합니다. 다음은 Razor Pages의 예제입니다:

public async Task<IActionResult> OnPostAsync()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }
    _context.Movies.Add(Movie);
    await _context.SaveChangesAsync();
    return RedirectToPage("./Index");
}

ASP.NET Core MVC 컨트롤러와 뷰를 사용하는 경우, 다음 예제는 컨트롤러 액션 내부에서 ModelState.IsValid를 확인하는 방법을 보여줍니다:

public async Task<IActionResult> Create(Movie movie)
{
    if (!ModelState.IsValid)
    {
        return View(movie);
    }
    _context.Movies.Add(movie);
    await _context.SaveChangesAsync();
    return RedirectToAction(nameof(Index));
}

Web API 컨트롤러는 [ApiController] 특성을 가진 경우 ModelState.IsValid를 확인할 필요가 없습니다.

이 경우, 모델 상태가 유효하지 않으면 오류 세부 정보가 포함된 자동 HTTP 400 응답이 반환됩니다.

자세한 내용은 Automatic HTTP 400 responses에서 확인할 수 있습니다.

Rerun validation

Validation is automatic, but you might want to repeat it manually.
유효성 검사 다시 실행 유효성 검사는 자동으로 이루어지지만, 수동으로 다시 실행하고 싶을 때가 있습니다.

For example, you might compute a value for a property and want to rerun validation after setting the property to the computed value.
예를 들어, 속성의 값을 계산한 후 해당 속성에 계산된 값을 설정한 후에 유효성 검사를 다시 실행하고 싶을 수 있습니다.

To rerun validation, call ModelStateDictionary.ClearValidationState to clear validation specific to the model being validated followed by TryValidateModel:
유효성 검사를 다시 실행하려면, ModelStateDictionary.ClearValidationState를 호출하여 검사를 수행할 모델의 유효성 검사를 지우고, TryValidateModel을 호출합니다:

public async Task<IActionResult> OnPostTryValidateAsync()
{
    var modifiedReleaseDate = DateTime.Now.Date;
    Movie.ReleaseDate = modifiedReleaseDate;
    ModelState.ClearValidationState(nameof(Movie));
    if (!TryValidateModel(Movie, nameof(Movie)))
    {
        return Page();
    }
    _context.Movies.Add(Movie);
    await _context.SaveChangesAsync();
    return RedirectToPage("./Index");
}

Validation attributes

Validation attributes let you specify validation rules for model properties.
Validation attributes(유효성 검사 속성)을 사용하면 모델 속성에 대한 유효성 검사 규칙을 지정할 수 있습니다

The following example from the sample app shows a model class that is annotated with validation attributes.
다음 예제는 sample app에서 Validation attributes으로 주석이 달린 모델 클래스를 보여줍니다.

The [ClassicMovie] attribute is a custom validation attribute and the others are built in.
[ClassicMovie] 속성은 custom validation attribute(사용자 정의 속성)이고, 나머지는 내장된 속성입니다.

Not shown is [ClassicMovieWithClientValidator], which shows an alternative way to implement a custom attribute.
[ClassicMovieWithClientValidator]는 사용자 정의 속성을 구현하는 또 다른 방법을 보여줍니다.

ClassicMovieAttribute.cs (https://github.com/dotnet/AspNetCore.Docs/blob/main/aspnetcore/mvc/models/validation/samples/6.x/ValidationSample/Validation/ClassicMovieAttribute.cs)

using System.ComponentModel.DataAnnotations;
using ValidationSample.Models;

namespace ValidationSample.Validation;

// <snippet_Class>
public class ClassicMovieAttribute : ValidationAttribute
{
    public ClassicMovieAttribute(int year)
        => Year = year;

    public int Year { get; }

    public string GetErrorMessage() =>
        $"Classic movies must have a release year no later than {Year}.";

    protected override ValidationResult? IsValid(
        object? value, ValidationContext validationContext)
    {
        var movie = (Movie)validationContext.ObjectInstance;
        var releaseYear = ((DateTime)value!).Year;

        if (movie.Genre == Genre.Classic && releaseYear > Year)
        {
            return new ValidationResult(GetErrorMessage());
        }

        return ValidationResult.Success;
    }
}
// </snippet_Class>
public class Movie
{
    public int Id { get; set; } // 영화의 고유 식별자

    [Required] // 이 속성은 필수 입력 값임을 나타냄
    [StringLength(100)] // 문자열 최대 길이를 100자로 제한
    public string Title { get; set; } = null!; // 영화 제목

    [ClassicMovie(1960)] // 사용자 정의 유효성 검사 속성, 1960년 이전의 클래식 영화일 경우 특정 조건을 검증
    [DataType(DataType.Date)] // 날짜 형식을 지정
    [Display(Name = "Release Date")] // 표시 이름을 "Release Date"로 지정
    public DateTime ReleaseDate { get; set; } // 영화 개봉일

    [Required] // 이 속성은 필수 입력 값임을 나타냄
    [StringLength(1000)] // 문자열 최대 길이를 1000자로 제한
    public string Description { get; set; } = null!; // 영화 설명

    [Range(0, 999.99)] // 값의 범위를 0에서 999.99로 제한
    public decimal Price { get; set; } // 영화 가격

    public Genre Genre { get; set; } // 영화 장르, Genre 클래스와 연관

    public bool Preorder { get; set; } // 영화 사전 예약 가능 여부
}

Built-in attributes

Here are some of the built-in validation attributes:
다음은 일부 built-in(내장된) validation attributes입니다:

  • [ValidateNever]:
    Indicates that a property or parameter should be excluded from validation.
    속성 또는 매개 변수를 유효성 검사에서 제외합니다.
  • [CreditCard]:
    Validates that the property has a credit card format. Requires jQuery Validation Additional Methods.
    속성이 신용카드 형식인지 유효성 검사합니다. jQuery Validation Additional Methods가 필요합니다.
  • [Compare]:
    Validates that two properties in a model match.
    모델의 두 속성이 일치하는지 유효성 검사합니다.
  • [EmailAddress]:
    Validates that the property has an email format.
    속성이 이메일 형식인지 유효성 검사합니다.
  • [Phone]:
    Validates that the property has a telephone number format.
    속성이 전화번호 형식인지 유효성 검사합니다.
  • [Range]:
    Validates that the property value falls within a specified range.
    속성 값이 지정된 범위 내에 있는지 유효성 검사합니다.
  • [RegularExpression]:
    Validates that the property value matches a specified regular expression.
    속성 값이 지정된 정규 표현식과 일치하는지 유효성 검사합니다.
  • [Required]:
    Validates that the field isn’t null. See [Required] attribute for details about this attribute’s behavior.
    필드가 null이 아닌지 유효성 검사합니다. 이 속성의 동작에 대한 자세한 내용은 [Required] 속성을 참조하세요.
  • [StringLength]:
    Validates that a string property value doesn’t exceed a specified length limit.
    문자열 속성 값이 지정된 길이 제한을 초과하지 않는지 유효성 검사합니다.
  • [Url]:
    Validates that the property has a URL format.
    속성이 URL 형식인지 유효성 검사합니다.
  • [Remote]:
    Validates input on the client by calling an action method on the server.
    서버의 액션 메서드를 호출하여 클라이언트에서 입력을 유효성 검사합니다.
    See [Remote] attribute for details about this attribute’s behavior.
    이 속성의 동작에 대한 자세한 내용은 [Remote] attribute을 참조하세요.

A complete list of validation attributes can be found in the System.ComponentModel.DataAnnotations namespace.
유효성 검사 속성의 전체 목록은 System.ComponentModel.DataAnnotations 네임스페이스에서 확인할 수 있습니다.

using System;
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc;

public class ExampleModel
{
    public int Id { get; set; } // 고유 식별자

    [Required] // 필수 입력 값
    [StringLength(100)] // 문자열 최대 길이 100자
    public string Title { get; set; } = null!; // 제목

    [CreditCard] // 신용카드 형식인지 유효성 검사
    public string CreditCardNumber { get; set; } = null!; // 신용카드 번호

    [Compare("Password", ErrorMessage = "Passwords do not match.")] // 두 속성이 일치하는지 유효성 검사
    public string ConfirmPassword { get; set; } = null!; // 비밀번호 확인

    [EmailAddress] // 이메일 형식인지 유효성 검사
    public string Email { get; set; } = null!; // 이메일 주소

    [Phone] // 전화번호 형식인지 유효성 검사
    public string PhoneNumber { get; set; } = null!; // 전화번호

    [Range(1, 100)] // 값의 범위를 1에서 100으로 제한
    public int Quantity { get; set; } // 수량

    [RegularExpression(@"^[a-zA-Z0-9]*$", ErrorMessage = "Only alphanumeric characters are allowed.")] // 알파벳과 숫자만 허용
    public string Username { get; set; } = null!; // 사용자 이름

    [Url] // URL 형식인지 유효성 검사
    public string Homepage { get; set; } = null!; // 홈페이지 URL

    [Remote(action: "VerifyUsername", controller: "Users")] // 서버의 액션 메서드를 호출하여 유효성 검사
    public string UsernameRemote { get; set; } = null!; // 원격 유효성 검사용 사용자 이름

    [ValidateNever] // 유효성 검사에서 제외
    public string InternalCode { get; set; } = null!; // 내부 코드, 유효성 검사 제외

    [ClassicMovie(1960)] // 사용자 정의 유효성 검사 속성
    [DataType(DataType.Date)] // 날짜 형식을 지정
    [Display(Name = "Release Date")] // 표시 이름을 "Release Date"로 지정
    public DateTime ReleaseDate { get; set; } // 영화 개봉일
}

Error messages

Validation attributes let you specify the error message to be displayed for invalid input. For example:
Validation attributes을 사용하면 잘못된 입력에 대해 표시할 오류 메시지를 지정할 수 있습니다. 예를 들어:

[StringLength(8, ErrorMessage = "Name length can't be more than 8.")]

Internally, the attributes call String.Format with a placeholder for the field name and sometimes additional placeholders. For example:
내부적으로, 속성은 필드 이름과 때때로 추가 자리 표시자를 위한 String.Format을 호출합니다. 예를 들어:

[StringLength(8, ErrorMessage = "{0} length must be between {2} and {1}.", MinimumLength = 6)]

When applied to a Name property, the error message created by the preceding code would be “Name length must be between 6 and 8.”.
이 코드가 Name 속성에 적용되면, 생성된 오류 메시지는 “Name length must be between 6 and 8.”가 됩니다.

To find out which parameters are passed to String.Format for a particular attribute’s error message, see the DataAnnotations source code.
특정 속성의 오류 메시지에 대해 String.Format에 전달되는 매개변수를 알아보려면 DataAnnotations source code 를 참조하세요.

추가 내용
https://learn.microsoft.com/en-us/aspnet/core/mvc/models/validation?view=aspnetcore-8.0#required-validation-on-the-server


using System.ComponentModel.DataAnnotations;

namespace NZWalks.API.Models.DTO
{
    public class AddRegionRequestDto
    {
        [Required]
        [MinLength(3, ErrorMessage = "Code has to be a minimum of 3 characters")]
        [MaxLength(3, ErrorMessage = "Code has to be a maximum of 3 characters")]
        public string Code { get; set; }

        [Required]
        [MaxLength(100, ErrorMessage = "Code has to be a maximum of 100 characters")]
        public string Name { get; set; }
        public string? RegionImageUrl { get; set; } // Nullable

    }

}
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;
using NZWalks.API.Data;
using NZWalks.API.Models.Domain;
using NZWalks.API.Models.DTO;
using static System.Net.WebRequestMethods;
using System;
using Microsoft.EntityFrameworkCore;
using AutoMapper;
using System.Collections.Generic;
using NZWalks.API.CustomActionFilter;

namespace NZWalks.API.Controllers
{

    [Route("api/[controller]")] // 컨트롤러의 기본 Route를 설정. [controller]는 컨트롤러 이름으로 대체됨.
    [ApiController] // API 컨트롤러 특성 적용
    public class RegionsController : ControllerBase
    {

        private readonly NZWalksDbcontext dbcontext;
        private readonly IRegionRepository regionRepository;
        private readonly IMapper mapper;

        public RegionsController(NZWalksDbcontext dbContext, IRegionRepository regionRepository, IMapper mapper)
        {
            this.dbcontext = dbContext;
            this.regionRepository = regionRepository;
            this.mapper = mapper;
        }

        // GET ALL REGIONS
        // GET: https://localhost:1234/api/regions
        [HttpGet]
        public async Task<IActionResult> GetAll()
        {
            // Get Data From Database - Domain models
            // var regionDomain = await dbcontext.Regions.ToListAsync(); // Change Async
            var regionDomain = await regionRepository.GetAllAsync();

            // Map Domain Models to DTOs
            // var regionDto = new List<RegionDto>();
            // foreach (var region in regionDomain) 
            // {
            //     regionDto.Add(new RegionDto() 
            //     {
            //         Id = region.Id,
            //         Code = region.Code,
            //         Name = region.Name,
            //         RegionImageUrl = region.RegionImageUrl
            //     });
            // }

            // Map Domain Models to DTOs
            var regionDto = mapper.Map<List<RegionDto>>(regionDomain);

            return Ok(regionDto);
        }

        // GET SINGLE REGION (Get Region By ID)
        // GET: https://localhost:1234/api/regions/{id}
        [HttpGet]
        [Route("{id:guid}")]
        public async Task<IActionResult> GetById([FromRoute]Guid id) 
        {
            // Get Data From Database - Domain models
            // var region = dbcontext.Regions.Find(id);
            // var regionDomain = await dbcontext.Regions.FirstOrDefaultAsync(x => x.Id == id);
            var regionDomain = await regionRepository.GetByIdAsync(id);
            if (regionDomain == null)
            {
                return NotFound();
            }

            // Map Domain Models to DTOs
            // var regionDto = new RegionDto() 
            // {
            //     Id = regionDomain.Id,
            //     Code = regionDomain.Code,
            //     Name = regionDomain.Name,
            //     RegionImageUrl = regionDomain.RegionImageUrl
            // };

            var regionDto = mapper.Map<RegionDto>(regionDomain);

            return Ok(regionDto);

        }

        // POST To Create New Region
        // POST : https://localhost:1234/api/regions
        [HttpPost]
        [ValidateModel]
        public async Task<IActionResult> Create([FromBody] AddRegionRequestDto addRegionRequestDto)
        {
            /* [ValidateModel] 적용전
                        if (ModelState.IsValid) // [ValidateModel] 
            {
                // [Require]
                // string Code, Name
                // string? RegionImageUrl

                // Map or Convert DTO to Domain Model
                //var regionDomainModel = new Region
                //{
                //    Code = addRegionRequestDto.Code,
                //    Name = addRegionRequestDto.Name,
                //    RegionImageUrl = addRegionRequestDto.RegionImageUrl
                //};

                // Map or Convert DTO to Domain Model
                var regionDomainModel = mapper.Map<Region>(addRegionRequestDto);

                // Use Domain Model to create Region
                // await dbcontext.Regions.AddAsync(regionDomainModel);
                // await dbcontext.SaveChangesAsync();

                await regionRepository.CreateAsync(regionDomainModel);


                // Map Domain model back to DTO
                //var regionDto = new RegionDto 
                //{ 
                //    Id = regionDomainModel.Id,
                //    Code = addRegionRequestDto.Code,
                //    Name = regionDomainModel.Name, 
                //    RegionImageUrl = regionDomainModel.RegionImageUrl

                //};

                var regionDto = mapper.Map<RegionDto>(regionDomainModel);

                // 201 Created 응답으로 생성된 DTO 반환
                return CreatedAtAction(nameof(GetById), new { id = regionDto.Id }, regionDto);
                // CreatedAtAction은 ASP.NET Core에서 HTTP POST 요청 후 201 Created 상태 코드를 반환할 때 사용되는 메서드
                // 새로운 리소스를 생성한 후, 해당 리소스에 접근할 수 있는 URI를 함께 제공하여 클라이언트가 리소스를 쉽게 찾을 수 있음
            }
            else
            {
                return BadRequest(ModelState);
            }
             
            */

            // [Require]
            // string Code, Name
            // string? RegionImageUrl

            // Map or Convert DTO to Domain Model
            //var regionDomainModel = new Region
            //{
            //    Code = addRegionRequestDto.Code,
            //    Name = addRegionRequestDto.Name,
            //    RegionImageUrl = addRegionRequestDto.RegionImageUrl
            //};

            // Map or Convert DTO to Domain Model
            var regionDomainModel = mapper.Map<Region>(addRegionRequestDto);

            // Use Domain Model to create Region
            // await dbcontext.Regions.AddAsync(regionDomainModel);
            // await dbcontext.SaveChangesAsync();

            await regionRepository.CreateAsync(regionDomainModel);


            // Map Domain model back to DTO
            //var regionDto = new RegionDto 
            //{ 
            //    Id = regionDomainModel.Id,
            //    Code = addRegionRequestDto.Code,
            //    Name = regionDomainModel.Name, 
            //    RegionImageUrl = regionDomainModel.RegionImageUrl

            //};

            var regionDto = mapper.Map<RegionDto>(regionDomainModel);

            // 201 Created 응답으로 생성된 DTO 반환
            return CreatedAtAction(nameof(GetById), new { id = regionDto.Id }, regionDto);
            // CreatedAtAction은 ASP.NET Core에서 HTTP POST 요청 후 201 Created 상태 코드를 반환할 때 사용되는 메서드
            // 새로운 리소스를 생성한 후, 해당 리소스에 접근할 수 있는 URI를 함께 제공하여 클라이언트가 리소스를 쉽게 찾을 수 있음
        }



        // Update Region
        // PUT: https://localhost:1234/api/regions/{id}
        [HttpPut]
        [Route("{id:guid}")]
        [ValidateModel]
        public async Task<IActionResult> Update([FromRoute] Guid id, [FromBody] UpdateRegionRequestDto updateRegionRequestDto)
        {
            /* [ValidateModel] 적용 전
            if (ModelState.IsValid)
            {
                // Map 
                //var regionDomainModel = new Region
                //{
                //    Code = updateRegionRequestDto.Code,
                //    Name = updateRegionRequestDto.Name,
                //    RegionImageUrl = updateRegionRequestDto.RegionImageUrl

                //};

                var regionDomainModel = mapper.Map<Region>(updateRegionRequestDto);

                // Check if region exists
                // var regionDomainModel = await dbcontext.Regions.FirstOrDefaultAsync(x => x.Id == id);
                regionDomainModel = await regionRepository.UpdateAsync(id, regionDomainModel);

                if (regionDomainModel == null)
                {
                    return NotFound();
                }

                //// Map DTO to Domain model
                //regionDomainModel.Code = updateRegionRequestDto.Code;
                //regionDomainModel.Name = updateRegionRequestDto.Name;
                //regionDomainModel.RegionImageUrl = updateRegionRequestDto.RegionImageUrl;

                //await dbcontext.SaveChangesAsync();

                // Convert Domain Model to DTO
                //var regionDto = new RegionDto
                //{
                //    Id = regionDomainModel.Id,
                //    Code = regionDomainModel.Code,
                //    Name = regionDomainModel.Name,
                //    RegionImageUrl = regionDomainModel.RegionImageUrl
                //};

                var regionDto = mapper.Map<RegionDto>(regionDomainModel);

                return Ok(regionDto);

            }
            else 
            { 
                return BadRequest(ModelState);
            }
            */
            // Map 
            //var regionDomainModel = new Region
            //{
            //    Code = updateRegionRequestDto.Code,
            //    Name = updateRegionRequestDto.Name,
            //    RegionImageUrl = updateRegionRequestDto.RegionImageUrl

            //};

            var regionDomainModel = mapper.Map<Region>(updateRegionRequestDto);

            // Check if region exists
            // var regionDomainModel = await dbcontext.Regions.FirstOrDefaultAsync(x => x.Id == id);
            regionDomainModel = await regionRepository.UpdateAsync(id, regionDomainModel);

            if (regionDomainModel == null)
            {
                return NotFound();
            }

            //// Map DTO to Domain model
            //regionDomainModel.Code = updateRegionRequestDto.Code;
            //regionDomainModel.Name = updateRegionRequestDto.Name;
            //regionDomainModel.RegionImageUrl = updateRegionRequestDto.RegionImageUrl;

            //await dbcontext.SaveChangesAsync();

            // Convert Domain Model to DTO
            //var regionDto = new RegionDto
            //{
            //    Id = regionDomainModel.Id,
            //    Code = regionDomainModel.Code,
            //    Name = regionDomainModel.Name,
            //    RegionImageUrl = regionDomainModel.RegionImageUrl
            //};

            var regionDto = mapper.Map<RegionDto>(regionDomainModel);

            return Ok(regionDto);
        }


        // Delete Region
        // Delete https://localhost:1234/api/regions/{id}
        [HttpDelete]
        [Route("{id:guid}")]
        public async Task<IActionResult> Update([FromRoute] Guid id)
        {
            //var regionDomainModel = await dbcontext.Regions.FirstOrDefaultAsync(x => x.Id == id);

            var regionDomainModel = await regionRepository.DeleteAsync(id);


            if (regionDomainModel == null)
            {
                return NotFound();
            }

            // dbcontext.Regions.Remove(regionDomainModel);
            // await dbcontext.SaveChangesAsync();

            // return deleted Region back
            // Convert Domain Model to DTO
            //var regionDto = new RegionDto
            //{
            //    Id = regionDomainModel.Id,
            //    Code = regionDomainModel.Code,
            //    Name = regionDomainModel.Name,
            //    RegionImageUrl = regionDomainModel.RegionImageUrl
            //};

            var regionDto = mapper.Map<Region>(regionDomainModel);

            return Ok(regionDto);
        }
    }
}
using AutoMapper;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using NZWalks.API.CustomActionFilter;
using NZWalks.API.Models.Domain;
using NZWalks.API.Models.DTO;
using NZWalks.API.Repositories;

namespace NZWalks.API.Controllers
{
    // /api/walks
    [Route("api/[controller]")]
    [ApiController]
    public class WalksController : ControllerBase
    {
        private readonly IMapper mapper;
        private readonly IWalkRepository walkRepository;

        public WalksController(IMapper mapper, IWalkRepository walkRepository)
        {
            this.mapper = mapper;
            this.walkRepository = walkRepository;
        }


        // CREATE walk
        // POST: /api/walks 
        [HttpPost]
        [ValidateModel]
        public async Task<IActionResult> Create([FromBody] AddWalkRequestDto addWalkRequestDto)
        {
            /* [ValidateModel] 적용전
            if (ModelState.IsValid)
            {
                // Map DTO to Domain Model
                var walkDomaiModel = mapper.Map<Walk>(addWalkRequestDto);

                await walkRepository.CreateAsync(walkDomaiModel);

                // Map Domain model to DTO
                var walkDto = mapper.Map<WalkDto>(walkDomaiModel);

                return Ok(walkDto);
            }
            else 
            {
                return BadRequest(ModelState);
            }


            */

            // Map DTO to Domain Model
            var walkDomaiModel = mapper.Map<Walk>(addWalkRequestDto);

            await walkRepository.CreateAsync(walkDomaiModel);

            // Map Domain model to DTO
            var walkDto = mapper.Map<WalkDto>(walkDomaiModel);

            return Ok(walkDto);
        }

        // GET Walks
        // Get: /api/walks
        [HttpGet]
        public async Task<IActionResult> GetAllAsync()
        {
            var walksDomain = await walkRepository.GetAllAsync();

            // Map Domain Model to DTO
            var walksDto = mapper.Map<List<WalkDto>>(walksDomain);

            return Ok(walksDto);
        }


        // Get Walk By Id
        // GET: /api/Walks/{id}
        [HttpGet]
        [Route("{id:Guid}")]
        public async Task<IActionResult> GetByIdAsync([FromRoute] Guid id)
        {
            var walksDomain = await walkRepository.GetByIdAsync(id);

            if (walksDomain == null)
            {
                return NotFound();
            }

            var WalkDto = mapper.Map<WalkDto>(walksDomain);
            
            return Ok(WalkDto);
        }

        // Update Walk By Id
        // Put: / api/Walks/{id}
        [HttpPut]
        [Route("{id:Guid}")]
        [ValidateModel]
        public async Task<IActionResult> Update([FromRoute] Guid id, UpdateWalkRequestDto updateWalkRequestDto)
        {
            /*  [ValidateModel] 적용전
            if (ModelState.IsValid)
            {
                // Map DTO to Domain Model
                var walksDomain = mapper.Map<Walk>(updateWalkRequestDto);

                walksDomain = await walkRepository.UpdateAsync(id, walksDomain);

                if (walksDomain == null)
                {
                    return NotFound();
                }

                var walksDto = mapper.Map<Walk>(walksDomain);

                // Map Domain Model to DTO
                return Ok(walksDto);
            }
            else
            {
                return BadRequest(ModelState);
            }

            */

            // Map DTO to Domain Model
            var walksDomain = mapper.Map<Walk>(updateWalkRequestDto);

            walksDomain = await walkRepository.UpdateAsync(id, walksDomain);

            if (walksDomain == null)
            {
                return NotFound();
            }

            var walksDto = mapper.Map<Walk>(walksDomain);

            // Map Domain Model to DTO
            return Ok(walksDto);
        }

        // Delete a Walk By Id
        // DELETE: /api/Walks/{id}
        [HttpDelete]
        [Route("{id:Guid}")]
        public async Task<IActionResult> Delete([FromRoute] Guid id)
        { 
            var deleteWalkDomainModel = await walkRepository.DeleteAsync(id);

            if (deleteWalkDomainModel == null)
            {
                return NotFound();
            }

            // Map Domain Model to DTO
            var deleteWalkDto = mapper.Map<WalkDto>(deleteWalkDomainModel);

            return Ok(deleteWalkDto);
        }
    }
}
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;

namespace NZWalks.API.CustomActionFilter
{
    public class ValidateModelAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuting(ActionExecutingContext context)
        {
            if (context.ModelState.IsValid == false)
            { 
                context.Result = new BadRequestResult();
            }
        }
    }
}

댓글 달기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다

위로 스크롤