이전 글에서 ASP.NET Core를 통해 Application을 개발하기 위한 준비를 마쳤으므로 간단한 데이터 입력 응용프로그램을 생성해 보고자 합니다. 다소 빠르게 진행하는 대신 일부 설명이 생략될 수 있지만 추후에 모두 상세하게 다시 다룰 것입니다.
1. 식당 예약 프로그램
손님이 인터넷을 통해 언제, 몇 명의 인원이 방문할지를 결정할 수 있는 식당 예약 프로그램을 간단히 만들어볼 것입니다.
2. 프로젝트 생성
(1) 프로젝트 준비
ASP.NET Core MVC 템플릿을 통해 'RestaurantReservation'이라는 새로운 프로젝트를 생성합니다.
프로젝트가 생성되고 나면 필수적인 부분에만 집중하기 위해 HomeController.cs의 내용을 아래와 같이 간략하게 수정합니다.
using Microsoft.AspNetCore.Mvc;
namespace RestaurantReservation.Controllers
{
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
}
}
그리고 Views->Home폴더 아래에 Index.cshtml도 아래와 같이 변경합니다.
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<title>맛나식당</title>
</head>
<body>
<div>
맛나 식당에 오신걸 환영합니다.!
</div>
</body>
</html>
지금까지 모든 사항이 완료되었으면 Visual Studio의 'Start Without Debugging'을 눌러 프로젝트를 실행합니다.
잠시 후 새로운 웹브라우저가 열리고 위에서 변경한 사항이 잘 반영되는지를 확인합니다.
그리고 Index.cshtml파일을 임의로 수정하고 다시 저장해 보시기 바랍니다. 그러면 위에서 열린 브라우저는 변경된 사항을 즉시 반영할 것입니다.
(2) Data Model 추가
Data Model은 ASP.NET Core Application에서는 가장 중요한 부분에 해당합니다. Model은 Application의 도메인으로 알려진 대상을 정의하는 실제 세계의 객체, 프로세스, 규칙을 나타냅니다. 도메인 모델로서 참조되는 모델은 Application의 세계관을 구성하는 C#객체(도메인 개체라고 표현)와 이들을 조작하는 메서드를 포함합니다. 대부분의 프로젝트에서 ASP.NET Core Application작업은 사용자에게 Data Model에 대한 접근을 제공하며 사용자가 이들과 상호작용할 수 있도록 하는 기능을 제공하는 것입니다.
Data Model 클래스는 ASP.NET Core application에서 관례적으로 Models라는 이름의 폴더에 생성되며 템플릿에 의해 자동적으로 프로젝트에 추가됩니다. 참고로 이번 프로젝트에서는 복잡한 Model은 필요로 하지 않으므로 'ReservationResponse'이라는 Model 클래스 하나만 사용할 것입니다.
Visual Studio의 Solution Explorer에서 Models폴더에 마우스 오른쪽 버튼을 눌러 Add -> Class항목을 선택해
아래와 같은 Model 클래스 하나를 생성합니다.
namespace RestaurantReservation.Models
{
public class ReservationResponse
{
public string? Name { get; set; }
public string? Phone { get; set; }
public DateTime ReservationTime { get; set; }
public int Attendance { get; set; }
}
}
클래스를 추가하고 저장하게 되면 아마도 Hot Reload가 적용될 수 없다는 메시지를 보게 될지도 모릅니다. 이는 Visual Studio안에서 새로운 클래스 파일을 추가하고 정의하는 등의 모든 변경사항에 대해 Hot Reload가 대처할 수 없다는 것입니다. 깊게 생각할 필요 없이
'Always rebuild when updates can't be applied'를 체크한 다음 Rebuild and Apply Changes버튼을 눌러줍니다.
(3) 두 번째 Action과 View생성
HomeController.cs의 Index 메서드 밑에는 아래와 같은 새로운 메서드를 추가합니다.
public ViewResult ReservationForm()
{
return View();
}
보시면 Index()와 ReservationForm()둘다 매개변수 없이 View() 메서드를 호출하고 있는데 Razor 뷰 엔진은 View파일을 찾는데 Action Method이름을 사용합니다. 즉, ReservationForm() 메서드는 Razor에게 ReservationForm.cshtml이라는 파일을 View에서 찾아보라고 하는 것입니다.
따라서 Solution Expreror에서 Views -> Home폴더에 ReservationForm.cshtml파일을 추가하도록 합니다.
그리고 파일의 내용은 아래와 같이 변경합니다.
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<title>예약하기</title>
</head>
<body>
<div>
This is the RsvpForm.cshtml View
</div>
</body>
</html>
위와 같이 수정 후 웹브라우저에서 URL을 /Home/ReservationForm으로 지정하면 Razor 뷰 엔진은 위에서 추가한 ReservationForm.cshtml뷰를 찾아 응답하게 될 것입니다.
(4) Action Method 연결
위에서는 추가된 Action과 View를 위해 /Home/ReservationForm으로 URL을 직접 지정했지만 일반적인 경우 대부분은 사용자가 쉽게 해당 주소를 방문할 수 있도록 Link를 연결해 둡니다. 따라서 Index.cshtml을 수정해 아래와 같이 ReservationForm으로 이동하는 Link를 추가합니다.
<div>
맛나 식당에 오신걸 환영합니다.!!<br />
<a asp-action="ReservationForm">예약하기</a>
</div>
추가한 Element는 asp-action이라는 속성을 가진 것으로 Tag Helper라는 것을 사용한 하나의 예이며 View가 렌더링 될 때 수행되는 Razor를 위한 지시 방법에 해당합니다.
asp-action속성은 Element에 Action 메서드를 위한 URL을 포함하는 href속성을 a Elment에 추가하도록 합니다. Tag Helper들이 동작하는 방식에 대해서는 추후에 알아보도록 하고 일단은 예제에서 사용된 Tag Helper는 Razor에게 현재 View가 렌더링 된 같은 Controller에 존재하는 Action메서드의 URL을 추가하도록 한다는 것만 알아두면 충분합니다.
웹브라우저에서 다시 루트(/) 경로로 이동하여 위에서 생성한 링크를 확인한 뒤 해당 링크를 클릭해 페이지가 잘 이동하는지 확인합니다.
중요한 점은 URL을 생성하기 위해 View에서 직접 링크를 생성하는 대신 ASP.NET Core에서 제공하는 기능을 사용했다는 것입니다. Tag Helper가 Element를 위해 href속성을 생성할 때 Application의 설정을 확인하여 어떤 URL이 들어가야 할지를 알아내는데 이는 경로가 변경된 다른 URL형식을 제공할 때 모든 View에 변경사항을 적용해 줄 필요 없이 Application의 설정만 변경하는 것으로 끝낼 수 있도록 도와줍니다.
(5) Form 만들기
이제 위에서 만든 ReservationForm View는 Index View에서 링크를 통해 들어올 수 있게 되었습니다. 하지만 ReservationForm View는 아직 미완의 상태이므로 아래와 같이 ReservationResponse모델 편집을 위한 HTML Form으로 렌더링 될 수 있도록 수정합니다.
@model RestaurantReservation.Models.ReservationResponse
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<title>예약하기</title>
</head>
<body>
<form asp-action="ReservationForm" method="post">
<div>
<label asp-for="Name">예약자 이름 : </label>
<input asp-for="Name" />
</div>
<div>
<label asp-for="Phone">전화번호 : </label>
<input asp-for="Phone" />
</div>
<div>
<label asp-for="ReservationTime">예약일자 : </label>
<input asp-for="ReservationTime" />
</div>
<div>
<label asp-for="Attendance">방문자수 : </label>
<input asp-for="Attendance" />
</div>
<button type="submit">등록</button>
</form>
</body>
</html>
예제에서 @model은 View가 View Model로서 ReservationResponse의 객체가 사용됨을 지정하는 것입니다. 따라서 label과 input을 ReservationResponse Model클래스의 각 속성과 대응되도록 정의했으며 asp-for 어트리뷰트를 통해 Model 속성을 할당하였습니다. asp-for 또한 tag helper에 해당하며 tag helper는 각 Element를 View Model객체와 연결하도록 설정합니다.
label에 사용된 asp-for는 value값을 설정하며 input에서 사용된 asp-for는 id와 name을 설정합니다. 여기까지만 보면 굳이 asp-for를 사용할 필요 없이 직접 value와 id, name을 설정하는 편이 나을 것 같아 보이겠지만 앞으로 계속 진행해 가면서 Model 속성이 기능적으로 제공하는 이점이 더 크다는 것을 알게 될 것이므로 이러한 벙법을 따르기를 강력히 권장합니다.
예제에서 Form에 적용된 asp-action은 action 어트리뷰트의 URL적용을 위해 Application URL 라우팅 설정을 보다 더 즉각적으로 사용한 것이라 할 수 있는데 특정 Action 메서드를 목표로 한 URL을 아래와 같이 정의하게 됩니다.
<form method="post" action="/Home/ReservationForm">
앞서 언급했듯 URL을 직접 작성하지 않고 tag helper를 사용하는 이유는 만약 어떠한 이유로 Application에서 사용하는 URL 시스템을 바꾸게 되더라도 tag helper는 이를 자동으로 반영하여 content를 생성할 수 있기 때문입니다.
프로젝트를 실행해 '예약하기'를 클릭하여 신청 폼 화면으로 이동합니다.
(6) Form 데이터 수신하기
사용자가 신청폼(ReservationForm)에서 필요한 내용을 작성한 후 '등록'버튼을 누르게 되면 데이터는 asp-action에 지정된 값에 따라 HomeController의 ReservationForm이라는 Action() 메서드로 전달될 것입니다. 하지만 HomeController의 ReservationForm는 이미 ReservationForm.cshtml이라는 View를 렌더링하고 있습니다. 따라서 이에 대한 처리가 필요한데 그전에 Post와 Get에 대한 이해가 우선되어야 합니다.
- GET : GET 요청은 웹브라우저에서 누군가가 link를 클릭할 때마다 일반적으로 발생되는 요청입니다. 예제에서 ReservationForm은 Index에서 Link를 통해 방문한다면 GET 요청이 발생되고 초기 입력 가능한 빈 Form을 표시하게 될 것입니다.
- POST : 위 예제에서처럼 post Method가 설정된 Form요소는 Post요청을 통해 Server로 입력한 Form의 데이터를 전달할 것입니다. 데이터를 전달할 Action 메서드는 ReservationForm으로 지정하였으므로 이곳으로 입력값이 전달될 것입니다.
위의 설명에 따라 ReservationForm Action 메서드는 POST 요청을 처리하기 위한 또 다른 메서드를 필요로 한다는 것을 알 수 있습니다. C# 메서드를 아래와 같이 분리하여 POST와 GET 요청을 각각 처리하도록 함으로써 2개의 메서드가 서로 다른 응답을 하도록 하는 것은 Controller의 코드를 깔끔하게 유지하는 데에도 도움이 됩니다. 2개의 Action 메서드는 같은 URL에 의해 호출되지만 ASP.NET Core는 요청이 GET인가 POST인가에 따라 적절한 메서드가 호출될 수 있도록 만들어 줄 것입니다.
using Microsoft.AspNetCore.Mvc;
using RestaurantReservation.Models;
namespace RestaurantReservation.Controllers
{
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
[HttpGet]
public ViewResult ReservationForm()
{
return View();
}
[HttpPost]
public ViewResult ReservationForm(ReservationResponse reservationResponse)
{
return View();
}
}
}
참고로 이전에 존재하던 ReservationForm() 메서드에는 [HttpGet]이라는 Attribute를 추가하여 해당 Method는 오로지 GET 요청에만 사용될 수 있도록 정의하였습니다. 그다음 같은 이름의 ReservationForm() 메서드를 하나 더 만들었는데 매개변수로 ReservationResponse의 객체를 전달받는 메서드이며 [HttpPost] Attribute를 적용함으로써 이 메서드는 POST 요청에만 사용될 수 있음을 정의하고 있습니다.
● Model 바인딩의 이해
첫 번째 ReservationForm() 메서드는 ReservationForm.cshtml파일을 View로서 렌더링 할 뿐이지만 두 번째 ReservationForm() 메서드는 매개변수를 가진 것으로 POST 요청에 의해 응답으로서 호출되는 Action 메서드와 ReservationResponse형식의 C#클래스가 주어지게 됩니다.
메서드와 클래스가 서로 연결이 가능한 이유는 모델 바인딩 때문입니다. 모델 바인딩은 들어오는 데이터를 분석하고 HTTP 요청에 있는 키-값 쌍의 데이터를 사용하여 Domain Model Type의 속성을 채우는 데 사용됩니다.
모델 바인딩은 강력하며 웹브라우저에 의해 전달된 값을 개별적으로 처리하기보다 C#객체를 통해 데이터를 처리하도록 함으로써 HTTP 요청을 직접적으로 처리하는 따분함을 제거할 수 있도록 합니다. 매개변수로서 Action 메서드에 전달된 ReservationResponse객체는 자동적으로 Form 필드로부터 데이터를 채우게 됩니다.
모델 바인딩이 어떻게 작동하는지를 나타내기 위해 몇 가지 사전 작업을 해줄 텐데 예제로 만들고 있는 Application의 목표 중 하나는 예약을 신청한 사람의 상세정보를 볼 수 있는 Summary 페이지를 표시하는 것이므로 이를 위해 객체를 위한 in-memory 컬렉션을 생성할 것입니다. 이는 Application이 중단되거나 재실행되면 가지고 있던 데이터를 모두 잃어버리게 되기 때문에 실제 Application에서는 유용하지 않지만 일단 ASP.NET Core자체에 집중하고자 이러한 데이터 저장 방식을 사용할 것입니다.
프로젝트의 Models폴더 안에서 Repository.cs이름의 새로운 파일을 추가하고 아래와 같이 Code를 작성합니다.
namespace RestaurantReservation.Models
{
public static class Repository
{
private static List<ReservationResponse> _responses = new();
public static IEnumerable<ReservationResponse> Responses => _responses;
public static void AddReservation(ReservationResponse response)
{
Console.WriteLine(response);
_responses.Add(response);
}
}
}
Application에서 필요한 곳에 데이터를 쉽게 저장하고 검색할 수 있도록 하기 위해 Repository와 멤버는 모두 static으로 정의하였습니다. 물론 ASP.NET Core는 공통적인 기능을 정의하기 위한 의존성 주입(Dependency Injection)이라는 세련된 접근방법을 제공하고 있지만 정적(static) 클래스는 지금 예제와 같은 단순한 Application을 위해서만 사용될 뿐입니다.
● 데이터 저장
위에서 생성한 Repository로 데이터를 저장할 곳을 만들었으므로 HTTP POST 요청을 수신할 ReservationForm() Action 메서드를 아래와 같이 수정합니다.
[HttpPost]
public ViewResult ReservationForm(ReservationResponse reservationResponse)
{
Repository.AddReservation(reservationResponse);
return View("ReservationComplete", reservationResponse);
}
위의 ReservationForm() 메서드가 호출되기 전에 ASP.NET Core는 HTML Form으로부터 값을 추출하여 이를 ReservationResponse객체의 속성에 값을 할당합니다. 결과는 메서드가 HTTP 요청을 처리하기 위해 호출될 때 매개변수로 사용되며 요청으로 전송된 Form 데이터는 Action 메서드로 전달된 ReservationResponse객체를 통해 처리해야 합니다. 이 경우 매개변수로서 전달된 객체는 Repository.AddReservation() 메서드로 전달되어 컬렉션에 저장될 것입니다.
(7) 완료 View 만들기
ReservationForm() 메서드에서는 View() 메서드가 호출되면 ReservationComplete라는 뷰를 지정하고 Model View로서 Model Binder에 의해 만들어진 ReservationResponse객체를 사용해 ViewResult를 생성하게 됩니다. 따라서 ReservationComplete.cshtml이라는 뷰를 아래 내용으로 Views -> Home폴더에 추가하여 사용자에게 예약 결과를 표시하도록 합니다.
@model RestaurantReservation.Models.ReservationResponse
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>감사합니다!</head>
<body>
<div>
<h1>[@Model?.Name]님 예약이 완료되었습니다.</h1>
연락처 : @Model?.Phone<br />
예약일정 : @Model?.ReservationTime<br />
전체인원 : @Model?.Attendance
</div>
예약 현황 <a asp-action="ListReservation">보기</a>
</body>
</html>
ReservationComplete.cshtml View에 의해 만들어진 HTML은 ReservationForm() 메서드를 통해 제공된 ReservationResponse라는 Model View에 의존하여 생성됩니다. 또한 Domain객체의 속성에 있는 값에 접근하기 위해서 @Model. [속성 값] 표현식을 사용하였습니다. 따라서 @Model.Name표현은 Name속성의 값을 가져오게 됩니다.
위의 뷰까지 생성되었으면 프로젝트를 실행하여 '예약하기'링크를 누른 뒤 필요한 Form을 작성하고 '등록'버튼을 눌러 실행결과를 확인합니다.
(8) 예약 현황 보기
ReservationComplete.cshtml뷰의 마지막에는 예약 현황을 보기 위한 ListReservation의 Link를 추가하였습니다. 실제 Link로 연결된 주소는 /Home/ListReservation이 될 것이므로 HomeController에 ListReservation이라는 Action 메서드를 추가합니다.
public ViewResult ListReservation()
{
return View(Repository.Responses);
}
위의 Action메서드의 View()는 Repository.Responses만을 매개변수로 사용하고 있습니다. 이는 Action Method의 이름을 View파일의 이름으로 사용하는 기본 View를 Razor가 Render 하도록 하며 동시에 View Model로 Repository.Responses를 사용하도록 하는 것입니다.
ListReservation.cshtml View를 Views -> Home폴더 아래에 다음과 같은 내용으로 추가합니다.
@model IEnumerable<RestaurantReservation.Models.ReservationResponse>
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>예약현황</head>
<body>
<div>
<table>
<thead>
<tr>
<th>이름</th>
<th>예약일정</th>
<th>인원</th>
</tr>
</thead>
<tbody>
@foreach(var item in Model)
{
<tr>
<td>@item.Name</td>
<td>@item.ReservationTime</td>
<td>@item.Attendance</td>
</tr>
}
</tbody>
</table>
</div>
</body>
</html>
Razor는 C#코드와 HTML의 혼합사용을 위해 .cshtml이라는 확장자의 View파일을 사용합니다. 예제에서는 Action 메서드가 View() 메서드를 통해서 전달한 ReservationResponse 객체를 사용하기 위해 @foreach 표현식을 사용했는데 일반적인 C#의 foreach와는 달리 내부에 브라우저로 전달할 HTML요소를 포함하고 있습니다. 위의 예제 뷰에서는 각 ReservationResponse객체가 객체의 속성 값을 통해 채워진 td 요소를 포함한 tr 요소를 생성하고 있습니다.
프로젝트를 다시 실행해 '예약하기'로 들어가 필요한 Form요소를 채운 뒤 해당 내용을 등록하고 Link를 통해 예약 현황으로 들어가 아래와 같이 표시되는지를 확인합니다.
(9) 유효성 검증(Validation) 하기
예약하기에서는 사용자가 필요한 모든 Form을 입력하지 않거나 아예 빈 화면상태에서 '등록'버튼을 눌러버릴 수도 있습니다. ASP.NET Core Application에서 유효성 검증 규칙은 Model 클래스에 Attribute를 정의함으로써 적용될 수 있는데 이 것은 해당 Model 클래스를 사용하는 모든 Form에 동일한 유효성 검증 규칙을 적용할 수 있다는 것을 의미합니다.
Attribute는 System.ComponentModel.DataAnnotations 네임스페이스에 있으며 예제의 ReservationResponse 모델 클래스에는 아래와 같은 Attribute를 적용하였습니다.
using System.ComponentModel.DataAnnotations;
namespace RestaurantReservation.Models
{
public class ReservationResponse
{
[Required(ErrorMessage = "이름이 입력되지 않았습니다.", AllowEmptyStrings = false)]
public string? Name { get; set; }
[Required(ErrorMessage = "전화번호가 입력되지 않았습니다.", AllowEmptyStrings = false)]
public string? Phone { get; set; }
[Required(ErrorMessage = "예약일자가 입력되지 않았습니다.", AllowEmptyStrings = false)]
public DateTime? ReservationTime { get; set; }
[Required(ErrorMessage = "인원수가 지정되지 않았습니다.", AllowEmptyStrings = false)]
public int? Attendance { get; set; }
}
}
ASP.NET Core는 위와 같은 Attribute를 감지하여 Model Binding이 처리되는 동안 유효성 검증을 위해 사용합니다. 예제를 보면 string?이나 DateTime?처럼 nullable유형이 지정되었는데 이는 Model에서 null형태의 값이 들어오기를 허용한 다음 Required Attribute를 통해 값의 입력 여부를 판단할 수 있도록 하기 위함입니다. Model에 유효성 Attribute를 사용하는 이러한 방법은 나중에 ASP.NET Core가 어떻게 C#기능을 우아하게 HTML과 HTTP에 섞어서 처리하는지를 보여줄 것입니다.
위의 Model Validation은 Form 데이터를 전달받는 Action 메서드에서 ModelState의 IsValid속성을 통해 실제 값의 유효성을 판단할 수 있습니다.
[HttpPost]
public ViewResult ReservationForm(ReservationResponse reservationResponse)
{
if (ModelState.IsValid)
{
Repository.AddReservation(reservationResponse);
return View("ReservationComplete", reservationResponse);
}
else
return View();
}
Controller의 기반 클래스는 ModelState속성을 통해 Model Binding을 처리한 상세한 결과를 제공하는데 위 예제에서 ModelState의 IsVaild속성이 true라면 Model Binder가 ReservationResponse에 지정한 Attribute의 제약조건을 만족한다는 것을 의미하므로 Form 데이터를 저장하고 ReservationComplete View를 보여주게 됩니다.
하지만 ModelState의 IsVaild속성이 false를 나타낸다면 유효성 검증과정에서 에러가 발생했다는 것을 의미하므로 ModelState속성에 각 에러와 관련된 상세정보를 제공하는 객체를 반환받게 됩니다. 여기서 우리는 매개변수 없는 View() 메서드를 호출함으로써 사용자에게 문제점을 설명하고 적절한 처리를 요청하는 자동화된 기능을 활용할 수 있습니다.
View가 Render 될 때 Razor는 요청을 통해 할당된 유효성 검증 오류의 상세정보로 접근할 수 있고 Tag Helper는 이러한 상세정보로 접근하여 사용자에게 직접 에러를 표시할 수 있게 됩니다. 따라서 ReservationForm.cshtml View파일에 아래와 같이 Validation Summary를 추가하여 관련 오류가 표시될 수 있도록 수정합니다.
<form asp-action="ReservationForm" method="post">
<div asp-validation-summary="All"></div>
<div>
<label asp-for="Name">예약자 이름 : </label>
<input asp-for="Name" />
</div>
<div>
<label asp-for="Phone">전화번호 : </label>
<input asp-for="Phone" />
</div>
<div>
<label asp-for="ReservationTime">예약일자 : </label>
<input asp-for="ReservationTime" />
</div>
<div>
<label asp-for="Attendance">방문자수 : </label>
<input asp-for="Attendance" />
</div>
<button type="submit">등록</button>
</form>
예제는 VIew가 Render될때 모든 유효성 검증 관련 오류를 표시하기 위해 asp-validation-summary를 div에 적용하였습니다.
asp-validation-summary는 ValidationSummary라는 열거 값인데 summary가 포함할 유효성 검증 에러의 유형을 명시합니다.
예제는 All이라고 지정하였고 이는 대부분의 Application에서 사용하는 값입니다. Validation Summary가 실제 동작하는 것을 보려면 Form에서 값을 입력하지 않고 '등록'버튼을 눌러보면 됩니다.
ReservationForm 메서드는 ReservationResponse 모델이 걸린 모든 유효성 제한조건이 만족될 때까지 ReservationComplete View를 표시하지 않을 것입니다. Form Field 중 하나라도 입력하면 다른 비어있는 Field로 인해 여전히 유효성 검증 오류가 발생할 텐데 이때 입력된 값은 계속 유지되고 오류가 발생하는 와중에도 계속 화면에 표시될 수 있습니다. 이것은 Model Binding의 또 다른 장점으로 Form 데이터를 통한 작업을 단순화할 수 있습니다.
(10) 입력 필드 강조
Model 속성을 HTML요소에 할당하는 Tag Helper는 Model Binding과 관련된 유용한 기능을 갖고 있는데 그것은 Model 클래스 속성이 유효성 검증에 실패할 때 또 다른 약간의 HTML을 생성하는 것입니다. 우선 아래는 아무런 유효성 오류가 발생하지 않은 상태의 Phone이라는 Input요소를 표시하고 있습니다.
<input type="text" data-val="true" data-val-required="전화번호가 입력되지 않았습니다." id="Phone" name="Phone" value="">
이 상태에서 유효성 검증 오류가 발생하면 위의 HTML은 아래와 같이 바뀌게 될 것입니다.
<input type="text" class="input-validation-error" data-val="true" data-val-required="전화번호가 입력되지 않았습니다." id="Phone" name="Phone" value="">
차이를 보면 Tag Helper는 input요소에 input-validation-error라는 class를 추가한 것이 있습니다. 이 기능의 이점을 활용하여 input-validation-error라는 이름에 맞는 새로운 CSS요소를 추가하면 시각적인 강조 효과를 얻을 수 있을 것입니다.
일반적으로 ASP.NET Core 프로젝트의 정적 요소는 wwwroot 폴더에 위치합니다. 이때 CSS 관련 파일은 wwwroot -> css폴더에 들어가고 javascript파일은 wwwroot -> js폴더에 들어가는 등 Content Type 별로 정리될 수 있습니다. 참고로 서드파티 CSS나 Javascript 패키지는 wwwroot -> lib폴더에 들어갑니다.
ASP.NET Core의 프로젝트는 wwwroot -> css폴더에 site.css이라는 이름의 파일을 기본적으로 생성해 주는데 이 파일을 찾아 기존의 내용을 유지하고 맨 아래에 다음과 같은 내용을 추가해 줍니다.
.field-validation-error {color: #f00;}
.field-validation-valid { display: none;}
.input-validation-error { border: 1px solid #f00; background-color: #fee; }
.validation-summary-errors { font-weight: bold; color: #f00;}
.validation-summary-valid { display: none;}
위의 내용을 추가하고 나면 해당 CSS 스타일이 필요한 파일의 head에 site.css파일의 경로를 추가해야 합니다. 따라서 예제에서는 ReservationForm.cshtml의 head에 아래와 같은 link요소를 추가합니다.
<head>
<title>예약하기</title>
<link rel="stylesheet" href="/css/site.css" />
</head>
link 요소는 href속성을 사용하여 실제 css파일의 경로를 지정합니다. 이때 URL에는 wwwroot폴더가 생략됨에 주의하십시오. ASP.NET의 기본 설정은 image나 css, javascript파일 등의 정적 요소들의 요청에 자동적으로 wwwroot폴더가 매핑되는 것을 포함합니다. 위에서 추가된 css로 인해 유효성 에러를 일으키는 Form의 Submit이 발생하면 더욱 시각적인 에러가 화면에 표시될 것입니다.
'.NET > ASP.NET' 카테고리의 다른 글
[ASP.NET Core] Shopping mall project - Category탐색과 장바구니 구현 (0) | 2022.08.22 |
---|---|
[ASP.NET Core] Shopping mall project 시작하기 (0) | 2022.08.16 |
[ASP.NET Core] 시작하기 (0) | 2022.07.29 |
[ASP.NET Core] ASP.NET Core 개요 (0) | 2022.07.27 |
[ASP.NET Core] Blazor 웹 프로젝트 시작하기 (0) | 2022.04.01 |