유효성 검사는 보통은 JavaScript를 통해 클라이언트에서 미리 체크하거나, 또는 보안상의 이유로 서버에서 다시 한 번 검사한다.

그런데 만약 사용자의 입력이 여러 필드에 걸쳐 동시에 잘못되었고, 
한꺼번에 모든 에러를 사용자에게 보여주는 구조가 필요하다면 IValidatableObject 를 통해 유효성 검사가 가능하다.

 

1. 모델 정의

using System.ComponentModel.DataAnnotations;

public class SaveModel : IValidatableObject
{
    public string ID { get; set; } = null!;   
    public string Name { get; set; } = null!;

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
     	if (string.IsNullOrWhiteSpace(ID))
        {
             yield return new ValidationResult(
                 "ID 값이 필요합니다.",
                 new[] { nameof(ID) }
             );
        }
        if (string.IsNullOrWhiteSpace(Name))
        {
             yield return new ValidationResult(
                 "Name 값이 필요합니다.",
                 new[] { nameof(Name) }
             );
        }
    }
}

 

2. Razor Page 또는 Controller (POST)

public class IndexModel : PageModel
{
    public async Task<JsonResult> OnPostSave([FromForm] SaveModel data)
    {
    	//이 부분은 테스트 용
        if (!ModelState.IsValid)
        {
            var errors = ModelState
                .Where(kvp => kvp.Value.Errors.Count > 0)
                .SelectMany(kvp => kvp.Value.Errors.Select(e => new
                {
                    ErrorMessage = e.ErrorMessage,
                    MemberNames = new[] { kvp.Key }
                }));

            return new JsonResult(new
            {
                Success = false,
                Errors = errors
            });
        }

        return new JsonResult(new { Success = true });
    }
}

 

3. 유효성 처리 JavaScript

    <form id="saveForm">
        <label>ID: <input type="text" name="ID" id="ID" /></label><br />
        <label>Name: <input type="text" name="Name" id="Name" /></label><br />
        <button type="submit">저장</button>
    </form>

    <div id="errorArea" style="color:red; margin-top: 10px;"></div>
        const formElement = document.getElementById("saveForm");

        function markError(field) {
            field.classList.add("error-field");
        }

        function clearErrorMarks() {
            const errorFields = formElement.querySelectorAll(".error-field");
            errorFields.forEach(field => field.classList.remove("error-field"));
        }

        formElement.addEventListener("submit", async function (e) {
            e.preventDefault();
            clearErrorMarks();
            document.getElementById("errorArea").innerHTML = "";

            const formData = new FormData(this);

            const response = await fetch("/Index?handler=Save", {
                method: "POST",
                body: formData
            });

            const data = await response.json();

            if (data.Errors) {
                const errors = data.Errors;

                const errorMessages = `<div>${errors.map(error => `• ${error.ErrorMessage}`).join("<br />")}</div>`;
                document.getElementById("errorArea").innerHTML = errorMessages;

                errors.forEach(error => {
                    if (error.MemberNames && error.MemberNames.length > 0) {
                        error.MemberNames.forEach(fieldName => {
                            const field = formElement.querySelector(`[name="${fieldName}"]`);
                            if (field) {
                                markError(field);
                            }
                        });
                    }
                });
            } else if (!data.Success) {
                document.getElementById("errorArea").innerHTML = "알 수 없는 오류가 발생했습니다.";
            } else {
                console.log("저장 완료!");
                this.reset();
            }
        });

 

IValidatableObject를 이용한 유효성 검사는 자주 쓰이진 않지만 검증 로직이 여러 필드에 걸쳐 복잡하게 얽혀 있을 때
또는 여러 에러를 한 번에 수집해서 처리해야 할 때 유용하다.

보통은 각 필드에 어노테이션을 붙이거나 하나씩 수동으로 추가하는 방식이 많지만
이렇게 Validate() 안에서 조건별로 yield return만 추가해 주면
복잡한 유효성 체크도 훨씬 깔끔하게 정리할 수 있다.

+ Recent posts