C#

ASP.NET Core MVC Core

ASP.NET Core에는 MVC Core가 내장되어 있다. 기존의 ASP.NET MVC5 및 그 이하 버젼에서 MVC는 상대적으로 무거운 System.Web.dll에 포함되어 있었는데, MVC6 (혹은 MVC Core 1.x)부터는 별도의 가벼운 패키지로 만들었다. 또한, MVC5에서 MVC UI 부분은 Controller 클래스, WebAPI 부분은 ApiController 클래스로 분리하여 사용하던 것을 통합하여 MVC Core 1.0 (MVC6)부터는 UI든 API든 모두 Microsoft.AspNet.Mvc.Controller 클래스를 사용하게 되었다. MVC6는 ASP.NET Core 1.0 안에 포함된 기능으로 이는 MVC Core 1.x로 불리우며, 이후 MVC Core 2.x, ... 등으로 버젼업하고 있다.

MVC6 Core의 또다른 새 기능으로 Tag Helper 기능을 들 수 있다. 기존 MVC5에서 사용되던 MVC HTML Helper을 대체하는 Tag Helper는 기본 HTML안에 특별한 Attribute를 추가하는 방식이다. MVC6는 기존 MVC5 HTML Helper을 계속 지원한다. Tag Helper를 사용하는 장점은 HTML을 UI 디자이너가 담당하고 개발자가 이를 넘겨받아 통합하는 경우, 디자이너와 개발자 사이에 HTML을 기반으로 쉽게 소통할 수 있다는 점이다.

MVC Core Tag Helper

Tag Helper는 일반 HTML Tag 안에 asp-* 형식의 Attribute를 추가하면 MVC View 엔진에서 이를 인식하여 처리해 주는 것이다. 예를 들어, Html.TextBoxFor(m => m.Id) 와 같은 HTML Helper 표현은 <input asp-for="Id" /> 와 같이 Tag Helper식으로 표현될 수 있다. 여기서 Html.TextBoxFor()는 모델 바인딩을 위해 m => m.Id 라는 람다식을 쓰고 있는데, Tag Helper에서는 이러한 바인딩을 간단히 asp-for="Id" 라는 Attribute로 표현한다. 이렇게 하면, HTML 디자인 도구를 사용하는 UI 디자이너는 asp-for 라는 Custom Attribute를 무시하게 되고, 웹 개발자는 모델 바인딩을 C# 표현이 아닌 HTML Attribute로 표현할 수 있게 된다. 아래 예제는 HTML Helper과 동일한 결과를 리턴하는 Tag Helper의 예를 표현한 것이다.

// MVC5 HTML Helper의 예
@model CoreApp1.Models.Person

<div>
    @using (Html.BeginForm())
    {
        <div>
            @Html.LabelFor(m => m.Id)
            @Html.TextBoxFor(m => m.Id)
        </div>
        <div>
            @Html.LabelFor(m => m.Name)
            @Html.TextBoxFor(m => m.Name)
        </div>
        <input type="submit" value="Save" />
    }
</div>


// MVC6 Tag Helper의 예
@model CoreApp1.Models.Person

<form asp-controller="Home" asp-action="Create" method="post">
    <div>
        <label asp-for="Id">Id:</label>
        <input asp-for="Id" />
    </div>
    <div>
        <label asp-for="Name">Name:</label>
        <input asp-for="Name" />
    </div>

    <input type="submit" value="Save" />
</form>
위의 예제의 form 태그에는 asp-controller 라는 Tag Helper가 있는 이는 Form을 Submit할 Controller를 지정한 것이고, 마찬가지로 asp-action은 해당 컨트롤러의 action 명을 지정한 것이다. Visual Studio는 Tag Helper에 대한 IntelliSense 기능을 제공하므로, 특정 HTML 태그 안에서 asp- 라고 치면, 해당 태그에서 사용할 수 있는 Tag Helper들을 볼 수 있다.

Tag Helper는 Optional 컴포넌트로서 View를 정의할 때 어떤 Tag Helper를 쓸 지 지정해야 주어야 하는데, 보통 모든 View에서 Tag Helper를 사용할 수 있도록 /Views/_ViewImports.cshtml 파일 안에 아래와 같은 코드를 넣어 모든 View에 대해 Tag Helper를 Enable한다. 아래 문자은 Microsoft.AspNetCore.Mvc.TagHelpers 안에 있는 모든 (*) Tag Helper들을 사용한다는 의미이다.

@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

MVC Core Web API

MVC Core는 MVC UI와 Web API에 대해 동일한 Microsoft.AspNet.Mvc.Controller 클래스를 사용한다. 다만, MVC UI가 일반적으로 HTML 웹페이지를 리턴하는 반면, Web API는 Json, XML 등과 같은 데이타를 리턴한다.
MVC Web API 경로를 지정하기 위해서 각 메서드 위에 [HttpGet("api/user/{id}")] 와 같은 Attribute를 지정할 수 있다.
아래 예제는 Web API를 통해 특정 ID의 사용자를 리턴하는 코드이다.

using System;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using CoreApp1.Models;

//[Route("api/user")]
public class UserController : Controller
{
    [HttpGet("api/user/{id}")]
    public JsonResult GetUser(int id)
    {
        MyDbContext db = new MyDbContext();
        var user = db.Users.SingleOrDefault(p => p.Id == id);

        // 결과를 JSON 포맷으로 리턴
        return Json(user);            
    }

    [HttpGet("api/user/v2/{id}")]
    public IActionResult GetUser2(int id)
    {
        MyDbContext db = new MyDbContext();
        var user = db.Users.SingleOrDefault(p => p.Id == id);
        if (user != null)
        {
            // HTTP Status OK (200)와 함께 결과를 리턴
            return Ok(user);
        }
        else
        {
            // HTTP Status 400와 에러메시지 리턴
            return BadRequest("No data found");
        }
    }
}

위 예제의 GetUser() 메서드는 JSON을 리턴하고 있는데, Controller.Json() 메소드 안에 C# 객체를 넣으면 자동으로 JSON 포맷을 변환하여 리턴하게 된다. GetUser2()는 GetUser()와 비슷하지만, 해당 User를 발견했을 때는 HTTP 상태 코드 OK (200)와 User 정보를 JSON 포맷으로 리턴하고, 해당 User가 없는 경우는 Bad Request 상태 코드와 에러메서지를 리턴하는 예이다.

모든 메서드의 API 경로가 중복된다면 아래 예제처럼 클래스위에 [Route()] Attribute를 지정할 수 있다. 만약 Route와 메서드에 동시에 지정하면 둘을 합한 경로가 된다. 즉, 아래 예제에서 GetUser2() 메서드는 [Route("api/user")]와 [HttpGet("v2/{id}")]을 합쳐 api/user/v2/{id} 라는 API 경로가 된다.


[Route("api/user")]
public class UserController : Controller
{        
    [HttpGet("{id}")]   
    public JsonResult GetUser(int id)
    {     
        // 생략    
    }

    [HttpGet("v2/{id}")]
    public IActionResult GetUser2(int id)
    {
        // 생략    
    }
}

Web API에서 POST를 하기 위해서는 메서드 앞에 [HttpPost] 를 지정한다. 마찬가지로 수정을 위해서는 HTTP PUT 명령(verb)에 해당하는 [HttpPut]을, 삭제를 위해서는 [HttpDelete]를 지정한다.
아래 예제는 /api/user API 경로로 데이타를 POST할 때 이를 서버에서 처리하는 코드로서, 전달된 데이타를 DB에서 저장하고 true를 리턴한다.

using System;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using CoreApp1.Models;
using System.Diagnostics;

namespace CoreApp1.Controllers
{
    // 동일한 표현: [Route("api/[controller]")]
    [Route("api/user")]   
    public class UserController : Controller
    {       
        [HttpPost]
        public JsonResult Save([FromBody]User user)
        {
            var db = new MyDbContext();

            try
            {
                if (ModelState.IsValid)
                {
                    db.Users.Add(user);
                    db.SaveChanges();
                    return Json(true);
                }
            }
            catch (Exception ex)
            {
                //Logging error                
                Debug.WriteLine(ex);
            }
            return Json(false);
        }
    }
}

여기서 한가지 주목할 점은 POST 메서드의 입력 파라미터가 User 라는 클래스의 객체이고, MVC Framework이 자동으로 전달된 데이타를 User 파라미터에 바인딩한다는 점이다. 또한, HTTP POST가 Request Body로 데이타를 보낼 경우, 이를 Request Body로부터 데이타를 읽어 오기 위해 [FromBody] 라는 Attribute를 입력파라미터 앞에 지정한다. 만약 이를 지정하지 않으면 디폴트로 Query String에서 데이타를 가져온다. Request Body로 데이타를 보냈는데, [FromBody]를 지정하지 않으면 입력 파라미터가 빈 객체로 할당된다.

위 예제에서 데이타 저장에 앞서 ModelState.IsValid 를 체크하는데 이는 모델 객체가 타당한지를 체크하는 것이다. 예를 들어, 아래와 같이 User 클래스가 정의되어 있다고 할 때, 이는 Id는 반드시 있어야 하고 Name은 최소 2자 최대 100자이어야 한다는 제약조건을 지정한 것이다. 이러한 제약조건에 모든 데이타가 타당한 지를 ModelState.IsValid을 통해 체크할 수 있다.

using System.ComponentModel.DataAnnotations;

public class User
{
    [Required]
    public int Id { get; set; }

    [StringLength(100, MinimumLength = 2)]
    public string Name { get; set; }

    public string Phone { get; set; }
    public string Address { get; set; }
}

본 웹사이트는 광고를 포함하고 있습니다. 광고 클릭에서 발생하는 수익금은 모두 웹사이트 서버의 유지 및 관리, 그리고 기술 콘텐츠 향상을 위해 쓰여집니다.

Previous Next Print