설명적인걸 우선시하기보다는 간단한 예제 project를 직접 만들어 봄으로서 전체적인 맥락을 우선 살펴보고자 합니다. 그 이후부터 각각에 대한 부분을 좀 더 자세하게 살펴보면 훨씬 이해하기 쉬울 것입니다.
1. 상황 설정
예제로 만들어볼 application은 대략 아래 기능을 가진 식당 예약 program이 될 것입니다.
- 식당소개 화면
- 예약접수
- 예약 현황 보기
2. Project 생성
PowerShell을 열고 Project를 생성할 위치(folder)로 이동한 다음 아래 명령으로 새로운 Project를 생성합니다.
dotnet new globaljson --sdk-version 8.0.202 --output RestaurantReservations dotnet new mvc --no-https --output RestaurantReservations --framework net8.0 dotnet new sln -o RestaurantReservations dotnet sln RestaurantReservations add RestaurantReservations |
(1) Project 준비하기
Visual Studio 혹은 Visual Studio Code를 사용해 Project를 열고 HomeController.cs의 내용을 아래와 같이 변경합니다. 처음 시작할 때 code는 그리 복잡할 필요가 없습니다.
using System.Diagnostics;
using Microsoft.AspNetCore.Mvc;
using RestaurantReservations.Models;
namespace RestaurantReservations.Controllers;
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
}
그런 다음 Views/Home에서 Index.cshtml file을 열고 아래와 같이 내용을 변경합니다.
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Party!</title>
</head>
<body>
<div>
<div>
Our restaurant reservations.
</div>
</div>
</body>
</html>
여기까지 완성한 뒤 PowerShell에서 아래 명령으로 project를 실행합니다.
dotnet watch |
명령이 실행되면 Project가 compile 되고 실행된 뒤 Web browser가 자동으로 열려 아래와 같이 표시할 것입니다.
위와 같이 Project를 실행하면 Project의 code를 변경할 때 자동적으로 변경된 code가 다시 compile 되고 그 결과가 browser반영됨을 알 수 있습니다.
만약 변경된 code에 오류가 있는 경우 dotnet watch는 이를 반영할 수 없음을 표시할 것이며 이때는 code를 정상적으로 수정한 후 application을 재시작하여 변경된 사항이 반영되도록 할 수 있습니다.
(2) Data Model 추가하기
ASP.NET Core application에서 Data Model은 중요한 부분에 해당합니다. Model은 실제 개체, Process, 그리고 Application의 Domain으로 알려진 대상을 정의하는 규칙을 나타냅니다. 때문에 Domain model이라고도 하는 Model은 Application의 개념을 구성하는 C#개체(Domain Objects)와 이를 조작하는 Method를 포함합니다. 대부분의 Project에서 ASP.NET Core Application의 역할은 사용자에게 Data Model과 상호작용할 수 있는 기능을 제공함으로써 Data Model로의 접근을 가능하게 하는 것입니다.
특별한 경우가 아니면 대게 Data Model Class는 Models folder에 정의되며 이는 ASP.NET Core application의 기본적인 규칙에 해당하고 Template에 의해 Project가 생성될때 자동으로 만들어지는 folder이기도 합니다. 예제는 최대한 단순하게 유지할 것이므로 그리 복잡한 Model은 필요하지 않습니다. 추가할 Model명은 ReservationResponse이라고 하고 해당 Model을 사용자로부터 예약정보를 받는 데 사용할 것입니다.
Vsiaul Studio를 사용한다면 Solition Explorer에서 Models folder에 Mouse오른쪽 button을 click한 뒤 Pop-up에서 Add > Class를 선택합니다. 그리고 file이름이 ReservationResponse.cs를 입력 후 'Add'를 눌러줍니다.
Visual Studio Code의 경우 Models folder에서 Mouse 오른쪽 button을 눌러 ' New File'을 선택하고 ' ReservationResponse.cs'로 file이름을 입력해 file을 추가합니다. file이 추가되면 다음과 같이 Model을 정의합니다.
public class ReservationResponse
{
public string? Name { get; set; }
public DateTime? VisitDate { get; set; }
public string? Phone { get; set; }
public bool? IsNotify { get; set; }
}
ReservationResponse Model에 정의된 모든 속성이 null가능한 type임에 주목합니다. 이 속성은 잠시 후 살펴볼 유효성 검증 부분에서 중요하게 작용하는 부분입니다.
(3) Action과 View 추가하기
예제 Application의 가장 주요한 기능은 예약을 받는 것입니다. 따라서 Action method에서는 사용자의 예약요청을 받을 수 있도록 정의되어야 합니다. 하나의 Controller에는 다수의 Action method가 정의될 수 있는데 이를 통해 관련된 Action method를 하나로 모아놓을 수 있습니다. 아래 예제는 HomeController에 새로운 Action method를 추가한 것입니다.
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
public ViewResult ReservationForm()
{
return View();
}
}
위 두개의 Action method는 모두 추가적인 매개변수 없이 View method를 호출하고 있습니다. Razor view engine은 기본적으로 Action Method의 이름을 통해 View file을 찾아가게 되므로 Index Action method는 Razor가 Index.cshtml을 찾게 되며 ReservationForm Action method는 Razor가 ReservationForm.cshtml을 찾게 합니다.
Visual Studio에서는 Views > Home folder에서 Mouse오른쪽을 눌러 Add > New Item을 선택합니다. 그 후에 Razor View – Empty를 선택하고 file명을 ReservationForm.cshtml을 지정한 뒤 Add를 눌러 file을 추가합니다. Visual Studio Code에서는 Views > Home folder에서 Mouse오른쪽을 눌러 New File을 선택하고 file명을 ReservationForm.cshtml로 지정합니다.
ReservationForm.cshtml file이 추가되면 file내용을 아래와 같이 변경합니다.
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>RsvpForm</title>
</head>
<body>
<div>
Reservation Page.
</div>
</body>
</html>
file변경을 완료하고 Browser에서 /home/reservationform으로 URL을 요청하면 Razor view engine은 ReservationForm.cshtml file을 찾아 아래와 같은 응답을 생성할 것입니다.
(4) Action method 연결하기
이제 Index view에서 손님이 식당을 예약할 수 있는 화면을 볼 수 있도록 하기 위해 아래와 같이 link를 연결을 추가합니다.
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Reservation</title>
</head>
<body>
<div>
<div>
Our restaurant reservations.
</div>
<a asp-action="ReservationForm">Reservation</a>
</div>
</body>
</html>
예제에서 추가한 a요소는 asp-action이라는 속성을 갖고 있는데 이 속성은 tag helper 중 하나로서 View가 render 될 때 Razor가 처리하게 되는 부분입니다. 예제에서는 asp-action가 Razor에 의해 처리되면 href속성을 a요소에 추가하게 되며 이때 지정한 Action method로의 URL을 가지게 됩니다. tag helper에 관해서는 나중에 자세히 알아볼 테지만 예제에서의 tag helper는 Razor에게 현재 View를 render 하고 있는 동일한 Controller에서 정의된 Action method의 URL을 추가하도록 하는 것입니다.
따라서 예제를 실행하게 되면 Web Browser는 다음과 같이 결과를 표시하게 될 것입니다.
URL에 mouse를 올려보면 해당 link가 지정하는 URL을 확인해 볼 수 있습니다.
사실 link를 생성할 때는 본래 HTML처럼 href속성을 사용해 바로 URL을 지정해 줄 수도 있지만 ASP.NET Core에서는 가능하면 tag helper를 사용해 주는 것이 좋습니다. 예제에서 tag helper가 a 요소에 href속성을 추가할 때는 Application의 구성을 확인하여 어떤 URL이 지정되어야 하는지를 확인합니다. 이를 통해 Application의 구성이 변경되는 경우에도 그에 따른 URL변경이 바로 적용될 수 있도록 함으로써 모든 View의 URL을 일일이 변경할 필요가 없도록 해줍니다.
(5) Form 만들기
Index view에서 link를 만들어 ReservationForm으로 연결되도록 했다면 실제 예약이 가능하도록 ReservationForm.cshtml을 아래와 같이 변경합니다.
@model ReservationResponse
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Reservation</title>
</head>
<body>
<form asp-action="ReservationForm" method="post">
<div>
<label asp-for="Name">Name:</label>
<input asp-for="Name" />
</div>
<div>
<label asp-for="VisitDate">Date(Visit):</label>
<input asp-for="VisitDate" />
</div>
<div>
<label asp-for="Phone">Phone:</label>
<input asp-for="Phone" />
</div>
<div>
<label asp-for="IsNotify">Notification settings</label>
<select asp-for="IsNotify">
<option value="">Choose an option</option>
<option value="true">Yes, Let me know.</option>
<option value="false">No, Don't tell me.</option>
</select>
</div>
<button type="submit">Submit</button>
</form>
</body>
</html>
예제에서 @model은 View의 Model로서 ReservationResponse가 사용됨을 알려주고 있으며 HTML에서 input과 label에 각각의 ReservationResponse속성을 정의하고 있습니다. 이때 각 HTML요소는 asp-for라는 tag helper를 사용하여 Model의 속성과 연결합니다. 이러한 방법으로 생성된 요소는 HTML로 아래와 같이 생성될 것입니다.
<div>
<label for="Name">Name:</label>
<input type="text" id="Name" name="Name" value="">
</div>
보시는 바와 같이 label에서 asp-for attribute는 for attribute의 값을 설정하며 input에서 asp-for는 id와 name의 값을 설정합니다. 지금은 이러한 방법으로 요소를 Model의 속성과 연결하는 것이 별것 아닌 것처럼 보일 수 있지만 Application을 작성할 때 많은 이점을 얻을 수 있습니다. 특히 form요소에 적용된 asp-action은 Application의 URL routing configuration을 사용하여 특정 action method를 대상으로 하는 URL을 설정하게 됩니다.
<form method="post" action="/Home/ReservationForm">
a 요소에 tag helper를 적용한 것과 마찬가지로 이러한 접근 방식은 Application에서 사용하는 URL system이 변경된다고 하더라도 tag helper는 content를 생성할 때 변경사항이 자동으로 반영될 수 있도록 합니다. 결과적으로 위 예제는 다음과 같은 결과를 생성할 것입니다.
(6) Form data 받기
위 예제에서 예약에 필요한 내용을 입력하고 Submit button을 click 하면 입력된 내용이 다시 초기화되는 것 말고는 별다른 작업을 수행하는 것이 없습니다. 현재 ReservationForm action method는 View를 다시 render 하는 것 이외에 어떤 것도 구현되지 않았기 때문입니다. 따라서 Form의 data를 받아 이를 처리하기 위해 다음과 같이 ReservationForm의 두 번째 Action method를 추가합니다.
using System.Diagnostics;
using Microsoft.AspNetCore.Mvc;
using RestaurantReservations.Models;
namespace RestaurantReservations.Controllers;
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
[HttpGet]
public ViewResult ReservationForm()
{
return View();
}
[HttpPost]
public ViewResult ReservationForm(ReservationResponse reservationResponse) {
return View();
}
}
예제에서 첫 번째 Method는 기존에 Method에 HttpGet attribute를 적용한 것으로 Get 요청은 browser에서 link를 click 하면 발생하는 일반적인 요청입니다. 두 번째 Action method는 새롭게 추가한 것으로 HttpPost attribute를 적용하였으며 form의 data가 POST요청을 통해 들어올때 호출됩니다.
위 2개의 ReservationForm Action method는 같은 URL로 호출되지만 ASP.NET Core는 상술하였듯 GET과 POST요청에 따라 그에 맞는 Action method를 호출할 것입니다.
● Model binding
특히 두번째 ReservationForm Action method는 ReservationResponse개체를 매개변수로 갖고 있는데 이를 통해 form으로 입력된 data를 받을 수 있게 됩니다. 이를 Model binding이라고 하는데 ASP.NET Core의 가장 핵심적인 기능 중에 하나로 HTTP요청을 통해 들어오는 form data를 분석하여 이를 key와 값의 쌍으로 구성한 뒤 그것을 Domain model의 속성으로 할당하는 동작을 수행합니다.
Model binding은 강력하며 HTTP요청을 직접적으로 처리해야 하는 번거로운 상황을 피하고 C# 개체를 통해서 동일한 처리를 구현할 수 있도록 해줍니다. 따라서 예제에서 Action method의 매개변수로 분석된 ReservationResponse개체는 form field로부터 자동적으로 속성에 값이 할당될 것입니다.
보통 사용자로부터 입력된 data는 외부 Database를 통해 저장되는 것이 일반적이지만 이는 번거로운 사전작업을 필요로 하며 Model binding처리에 집중하기 위해 개체의 Collection을 대신 사용하여 data의 저장을 대신하고자 합니다. 이를 위해 Models folder에서 Repository.cs file을 아래와 같이 추가합니다.
public static class Repository
{
private static List<ReservationResponse> responses = new();
public static IEnumerable<ReservationResponse> Responses => responses;
public static void AddResponse(ReservationResponse response)
{
responses.Add(response);
}
}
● Form data 저장하기
위에서 만든 Repository에 따라 HomeController에 있는 POST요청에 대한 ReservationForm Action method를 아래와 같이 변경합니다.
[HttpPost]
public ViewResult ReservationForm(ReservationResponse reservationResponse) {
Repository.AddResponse(reservationResponse);
return View("Thanks", reservationResponse);
}
ASP.NET Core는 위 method를 호출하기 전 Model binding을 통해 form data로부터 값을 추출하고 이 값을 ReservationResponse개체의 속성으로 할당할 것입니다. 그리고 그 개체를 Repository의 AddResponse method의 인수로 전달함으로써 Collection으로 저장합니다.
(7) 결과 View 추가하기
위 예제에서 View를 호출할 때는 Thanks라는 이름의 View를 호출하도록 하고 있으며 매개변수로 받은 reservationResponse개체를 View의 두 번째 인수로 전달하여 이를 View model로 사용하도록 하고 있습니다. 따라서 Thanks.cshtml file을 Views > Home folder에 아래와 같이 추가해 줍니다.
@model ReservationResponse
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Thanks</title>
</head>
<body>
<div>
<h1>Thank you, @Model?.Name!</h1>
@if (Model?.IsNotify == true) {
@:We'll let you know when your scheduled date arrives.
} else {
@:Make sure you're well-acquainted with the dates you've scheduled.
}
</div>
Click <a asp-action="ListResponses">here</a> to view all reservation status.
</body>
</html>
Thanks view에서 HTML content를 생성할 때는 ReservationForm Action method에서 제공되는 ReservationResponse View model을 사용하고 있습니다. View model 개체의 속성으로 접근할 때는 '@Model.<속성명>'형식의 표현식을 사용할 수 있으므로 예제에서 Name속성에 접근하기 위해 @Model.Name과 같은 표현식이 사용되었습니다. 여기까지 하면 입력과 저장, 출력이 진행되는 일련의 과정을 대략적으로나마 완성된 것입니다. Application을 실행하고 Home에서 Reservation link를 click한뒤 form에 필요한 data를 입력하고
Submit button을 눌러 결과를 확인합니다.
(8) 예약내역 확인 추가하기
Thanks.cshtml에서는 예약자를 확인하기 위한 별도의 a 요소를 추가하였습니다. 이때 asp-action Tag helper를 사용하여 ListResponses라는 Action method를 대상으로 하는 URL을 생성하고 있습니다. 따라서 실제 Browser에 표시된 Link에 Mouse를 올려보면 다음과 같이 URL이 표시됨을 알 수 있습니다.
하지만 아직 HomeController에는 ListResponses라는 Action method가 존재하지 않으므로 HomeController에 아래와 같이 Action method를 추가해 줍니다.
public ViewResult ListResponses()
{
return View(Repository.Responses);
}
해당 Method에서 View를 호출할 때는 Repository의 Responses를 인수로 전달하고 있습니다. 다만 별도의 View이름은 사용하지 않았기에 Action method와 동일한 View를 호출하게 되며 이때 Repository.Responses가 Model view로서 사용됩니다.
이어서 View > Home foler에 ListResponses.cshtml을 다음과 같이 추가합니다.
@model IEnumerable<ReservationResponse>
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Reservations list</title>
</head>
<body>
<h2>Reservations list</h2>
<table>
<thead>
<tr><th>Name</th><th>VisitDate</th><th>Phone</th></tr>
</thead>
<tbody>
@foreach (ReservationResponse r in Model!) {
<tr>
<td>@r.Name</td>
<td>@r.VisitDate</td>
<td>@r.Phone</td>
</tr>
}
</tbody>
</table>
</body>
</html>
View file의 확장자가 cshtml인건 해당 file이 HTML과 C# code가 한 곳에 섞일 수 있다는 것을 의미하는 것입니다. 실제 이와 같은 구현을 위 예제에서 볼 수 있는데 foreach문을 통해 View로 전달된 ReservationResponse Collection의 각각의 개체를 통해 그 속성을 표시하도록 하고 있습니다. 이때 foreach문은 @foreach표현식으로 사용됩니다.
@foreach표현식 내부에서는 각 ReservationResponse의 속성을 통해 <tr><td> HTML문을 생성하며 그 결과를 Browser로 보낼 것입니다.
Application을 다시 시작하고 예약 form에 필요한 내용을 입력한 뒤 Thank Page에서 here link를 click 하면 다음과 같은 결과를 표시할 것입니다.
(9) 유효성 검증하기
사용자가 예약 form에서 data를 입력할 때의 문제점은 일부 혹은 전체를 아예 공란으로 남겨둔 채 submit을 click 할 수 있다는 것입니다. 이를 위해 ASP.NET Core는 Model class에 아래와 같이 Attribute를 적용함으로써 유효성검증 규칙을 적용할 수 있습니다.
using System.ComponentModel.DataAnnotations;
public class ReservationResponse
{
[Required(ErrorMessage = "Please enter your name")]
public string? Name { get; set; }
[Required(ErrorMessage = "Please enter your visit date")]
public DateTime? VisitDate { get; set; }
[Required(ErrorMessage = "Please enter your phone number")]
[Phone]
public string? Phone { get; set; }
[Required(ErrorMessage = "Please specify whether you'll notice")]
public bool? IsNotify { get; set; }
}
ASP.NET Core가 Model에서 유효성검증에 관한 attribute를 확인하면 Model binding이 처리될 때 이를 유효성검증에 사용하게 됩니다.
ReservationResponse class에서 각 속성을 정의할때는 ?를 사용해 해당 null가능한 속성으로 정의하였습니다. 이는 해당 속성에 값이 할당되지 않을 수 있음을 의미하기도 합니다. 특히 IsNotify 속성에서 만약 ?를 사용하지 않게 되면 Model binding을 통해 전달받을 수 있는 값이 단지 true와 false만이 가능하게 되지만 nullable bool이 되면 true, false 그리고 null상태도 가질 수 있게 됩니다. 따라서 form을 입력할 때 어떠한 것도 선택하지 않은 채 'Choose an option'을 그대로 놔두게 되면 null이 될 수 있으며 이때 Required attribute로 인해 validation error가 발생할 수 있습니다.
유효성확인에 관한 문제가 있는지를 확인하는 방법은 form data를 받는 Action method에서 ModelState.IsVaild속성을 사용하는 것입니다.
[HttpPost]
public ViewResult ReservationForm(ReservationResponse reservationResponse) {
if (ModelState.IsValid) {
Repository.AddResponse(reservationResponse);
return View("Thanks", reservationResponse);
}
else {
return View();
}
}
Controller base class에서는 ModelState를 통해 Model binding이 처리된 결과에 관한 상세정보를 제공합니다. ModelState.IsValid속성이 true라면 ReservationResponse class에서 attribute를 통해 적용된 유효성검증조건을 만족한다는 것을 의미하는 것이며 예제의 경우라면 이전과 마찬가지로 Thanks View를 표시하게 됩니다.
하지만 ModelState.IsValid속성이 false라면 유효성검증에 관한 오류가 존재함을 의미하는 것입니다. ModelState를 통해서 발생한 오류의 상세정보를 확인할 수 있지만 현재 예제에서는 매개변수 없이 View method를 호출하여 사용자로 하여금 발생한 문제를 해결하도록 요청하는 방법을 사용할 것입니다.
View가 render 될 때 Razor는 요청과 관련된 유효성 오류에 대한 상세정보에 접근할 수 있기 때문에 tag helper에서는 유효성과 관련된 오류정보를 통해 사용자에게 무엇이 잘못되었는지를 알려줄 수 있습니다. 이를 위해 ReservationForm view에서 아래와 같이 tag helper를 추가합니다.
<form asp-action="ReservationForm" method="post">
<div asp-validation-summary="All"></div>
<div>
<label asp-for="Name">Name:</label>
<input asp-for="Name" />
</div>
<div>
<label asp-for="VisitDate">Date(Visit):</label>
<input asp-for="VisitDate" />
</div>
<div>
<label asp-for="Phone">Phone:</label>
<input asp-for="Phone" />
</div>
<div>
<label asp-for="IsNotify">Notification settings</label>
<select asp-for="IsNotify">
<option value="">Choose an option</option>
<option value="true">Yes, Let me know.</option>
<option value="false">No, Don't tell me.</option>
</select>
</div>
<button type="submit">Submit</button>
</form>
asp-validation-summary attribute를 div요소에 추가하였는데 View를 render 할 때 여기에 유효성검증오류의 내용을 표시할 것입니다. asp-validation-summary attribute의 값은 ValidationSummary라는 열거형의 값으로서 여기서 summary가 포함할 유효성검증 오류의 유형을 지정합니다. 예제에서는 이를 All로 설정하고 있으며 이와 관련되서는 추후에 좀 더 자세히 다룰 것입니다.
Validation summary가 실제 작동하는걸 보기 위해 Application을 다시 시작하고 예약 form안에서 일부 field를 빈칸으로 남겨 Submit을 시도합니다. 그러면 아마도 browser는 다음과 같이 표시될 것입니다.
ReservationForm action method에서는 사용자가 필요한 내용을 모두 입력하기 전까지는 Thanks view를 표시하지 않을 것입니다. 이때 위 예제에서 Razor가 Validation summary와 함께 View를 render 할 때 Name field에 입력된 내용은 그대로 유지되면서 그 값이 다시 표시되고 있음에 주목하시기 바랍니다. 이러한 동작은 Model binding을 사용함으로써 얻을 수 있는 또 다른 이점인데 이를 통해 form data에 대한 작업에 좀 더 집중할 수 있습니다.
● 잘못된 field 강조하기
요소에서 Model 속성과 연결된 Tag helper는 Model binding과 함께 사용되는 유용한 기능이 있는데 Model 속성이 유효성검사과정을 통과하지 못하는 경우 Tag helper는 약간 다른 HTML을 생성하게 됩니다. 예를 들어 위 예제에서 Name field는 유효성검사 오류가 존재하지 않는데 이때는 다음과 같은 HTML을 생성합니다.
<input type="text" id="Name" name="Name" value="HongGilDong">
그런데 Phone의 경우에는 아무런 내용을 입력하지 않았고 때문에 유효성검증 오류가 존재하는데 이때는 다음의 HTML을 생성합니다.
<input type="tel" class="input-validation-error" id="Phone" name="Phone" value="">
Name의 HTML과 비교하여 Phone에서는 Tag helper가 input-validation-error class가 추가되었습니다. 이러한 동작은 해당 class에 맞는 CSS style을 포함하는 stylesheet를 생성하여 활용할 수 있습니다.
일반적으로 ASP.NET Core project에서 정적 content는 wwwroot folder안에 들어가며 content의 유형에 따라 이 folder안에서 분류됩니다. 따라서 CSS stylesheet는 wwwroot > css안에 JavaScript file은 wwwroot > js안에 들어갑니다.
Project를 처음 생성할 때 사용한 template은 자동으로 site.css file을 만들어 이를 wwwroot > css안에 넣어둡니다. 예제에서는 이 file을 사용해 보고자 합니다. wwwroot > css에서 site.css file을 찾아 다음과 같이 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;
}
위 내용을 추가해 StyleSheet file을 저장하고 나면 이를 ReservationForm view에 적용하기 위해 head에 다음과 같이 link요소를 추가합니다.
<head>
<meta name="viewport" content="width=device-width" />
<title>Reservation</title>
<link rel="stylesheet" href="/css/site.css" />
</head>
link요소에서는 href를 사용해 css file의 위치를 지정합니다. 이때 경로를 지정하는 URL에서 wwwroot가 포함되어 있지 않음에 주목하시기 바랍니다. ASP.NET은 기본적으로 image나 css, javascript file과 같은 정적 요소에 대한 요청이 발생할 때 wwwroot folder에 대한 연결을 자동으로 수행하므로 URL과 같은 경로지정 시 wwwroot는 생략할 수 있습니다.
Application을 다시 실행하고 이전과 동일한 방법으로 유효성검증 오류를 유발하면 다음과 같이 표시될 것입니다.
'.NET > ASP.NET' 카테고리의 다른 글
[ASP.NET Core] - 5. 단위 Test (2nd) (0) | 2024.03.29 |
---|---|
[ASP.NET Core] - 4. 개발도구 사용하기 (2nd) (0) | 2024.03.22 |
[ASP.NET Core] - 2. 시작하기 (2nd) (0) | 2024.03.15 |
[ASP.NET Core] - 1. 개요 (2nd) (0) | 2024.03.12 |
NET::ERR_CERT_INVALID 문제 (0) | 2023.11.27 |