뷰: 뷰에 서비스 주입하기

등록일시: 2016-09-09 08:00,  수정일시: 2016-09-21 00:55
조회수: 6,226
이 문서는 ASP.NET Core MVC 기술을 널리 알리고자 하는 개인적인 취지로 제공되는 번역문서입니다. 이 문서에 대한 모든 저작권은 마이크로소프트에 있으며 요청이 있을 경우 언제라도 게시가 중단될 수 있습니다. 번역 내용에 오역이 존재할 수 있고 주석은 번역자 개인의 의견일 뿐이며 마이크로소프트는 이에 관한 어떠한 보장도 하지 않습니다. 번역이 완료된 이후에도 대상 제품 및 기술이 개선되거나 변경됨에 따라 원문의 내용도 변경되거나 보완되었을 수 있으므로 주의하시기 바랍니다.
본문에서는 의존성 주입(Dependency Injection) 기능을 이용해서 뷰의 구성 요소를 구성하기 위한 데이터를 가져오고 활용하는 방법을 살펴봅니다.

ASP.NET Core MVC는 뷰에 대한 의존성 주입(Dependency Injection)을 지원합니다. 이 기능은 지역화(Localization)나 뷰의 요소를 구성하기 위해서 전용 데이터가 필요한 경우 같은 뷰 전용 서비스에 유용하게 활용할 수 있습니다. 그러나 여전히 컨트롤러와 뷰 간에 관심사의 분리(Separation of Concerns)를 유지하기 위해서 최대한 노력해야 하므로, 뷰에서 출력하는 주요 업무 데이터는 그대로 컨트롤러에서 전달하는 것이 바람직합니다.

GitHub에서 샘플 코드 확인 및 다운로드 받기

간단한 예제

뷰에 서비스를 주입하려면 @inject 지시문을 사용하면 됩니다. @inject 지시문으로 뷰에 속성을 추가한 다음, DI를 이용해서 그 속성을 채운다고 생각하면 이해가 빠를 것입니다.

@inject 지시문의 구문은 다음과 같습니다:
@inject <type> <name>

다음은 실제 @inject 지시문의 사용 예입니다:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
@using System.Threading.Tasks
@using ViewInjectSample.Model
@using ViewInjectSample.Model.Services
@model IEnumerable<ToDoItem>
@inject StatisticsService StatsService
<!DOCTYPE html>
<html>
<head>
    <title>To Do Items</title>
</head>
<body>
    <div>
        <h1>To Do Items</h1>
        <ul>
            <li>Total Items: @StatsService.GetCount()</li>
            <li>Completed: @StatsService.GetCompletedCount()</li>
            <li>Avg. Priority: @StatsService.GetAveragePriority()</li>
        </ul>
        <table>
            <tr>
                <th>Name</th>
                <th>Priority</th>
                <th>Is Done?</th>
            </tr>
            @foreach (var item in Model)
            {
                <tr>
                    <td>@item.Name</td>
                    <td>@item.Priority</td>
                    <td>@item.IsDone</td>
                </tr>
            }
        </table>
    </div>
</body>
</html>

이 뷰에는 전반적인 통계를 보여주는 요약 정보와 ToDoItem 인스턴스의 목록이 함께 출력됩니다. 이때, 필요한 요약 정보를 뷰에 주입한 StatisticsService에서 가져오는데, 이 서비스는 Startup.cs 파일의 ConfigureServices 메서드에서 의존성 주입에 등록됩니다:

1
2
3
4
5
6
7
8
// For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();

    services.AddTransient<IToDoItemRepository, ToDoItemRepository>();
    services.AddTransient<StatisticsService>();
    services.AddTransient<ProfileOptionsService>();

그리고 StatisticsService 클래스는 리파지터리를 통해서 접근해서 가져온 ToDoItem 인스턴스 집합을 대상으로 몇 가지 계산을 수행합니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
using System.Linq;
using ViewInjectSample.Interfaces;

namespace ViewInjectSample.Model.Services
{
    public class StatisticsService
    {
        private readonly IToDoItemRepository _toDoItemRepository;

        public StatisticsService(IToDoItemRepository toDoItemRepository)
        {
            _toDoItemRepository = toDoItemRepository;
        }

        public int GetCount()
        {
            return _toDoItemRepository.List().Count();
        }

        public int GetCompletedCount()
        {
            return _toDoItemRepository.List().Count(x => x.IsDone);
        }

        public double GetAveragePriority()
        {
            if (_toDoItemRepository.List().Count() == 0)
            {
                return 0.0;
            }

            return _toDoItemRepository.List().Average(x => x.Priority);
        }
    }
}

본문의 예제 리파지터리는 메모리에 저장된 컬렉션을 사용하고 있습니다. 그러나 이 예제에 사용된 구현 방식은 (모든 데이터를 메모리에 올려 놓고 작업하는 방식은) 원격으로 접근하는 대량의 데이터 집합을 대상으로는 권장되지 않습니다.

이번 예제는 뷰에 바인딩 된 모델의 데이터와 뷰에 삽입된 서비스의 데이터를 함께 출력합니다:

조회 데이터 채우기

뷰 주입은 드롭다운 리스트 같은 UI 요소의 옵션을 채워야 할 때 유용합니다. 가령, 성별과 상태 및 기타 설정을 지정할 수 있는 옵션들을 제공하는 사용자 프로필 폼을 가정해보시기 바랍니다. 일반적인 MVC 접근방식으로 이런 폼을 렌더하기 위해서는 컨트롤러에서 이런 옵션 집합들 각각에 대한 데이터 접근 서비스를 요청한 다음, 바운드 될 각각의 옵션 집합을 모델이나 ViewBag에 채워넣어야 합니다.

다른 접근방식은 뷰에 직접 서비스를 주입해서 옵션들을 가져오는 것입니다. 이 방법은 컨트롤러 구현에 필요한 코드의 양을 최소화시켜주고, 뷰 요소 구성을 위한 로직을 뷰 자체로 이동시켜줍니다. 결과적으로 사용자 프로필 편집 폼을 출력하는 컨트롤러 액션에서는 사용자 프로필의 인스턴스만 뷰에 전달하면 됩니다:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
using Microsoft.AspNetCore.Mvc;
using ViewInjectSample.Model;

namespace ViewInjectSample.Controllers
{
    public class ProfileController : Controller
    {
        [Route("Profile")]
        public IActionResult Index()
        {
            // TODO: look up profile based on logged-in user
            var profile = new Profile()
            {
                Name = "Steve",
                FavColor = "Blue",
                Gender = "Male",
                State = new State("Ohio","OH")
            };
            return View(profile);
        }
    }
}

HTML 폼에서는 이 세 가지 속성들에 대한 드롭다운 리스트 등을 이용해서 프로필 설정을 수정합니다:

그리고 드롭다운 리스트들의 목록은 뷰에 주입된 서비스를 이용해서 채워집니다:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@using System.Threading.Tasks
@using ViewInjectSample.Model.Services
@model ViewInjectSample.Model.Profile
@inject ProfileOptionsService Options
<!DOCTYPE html>
<html>
<head>
    <title>Update Profile</title>
</head>
<body>
<div>
    <h1>Update Profile</h1>
    Name: @Html.TextBoxFor(m => m.Name)
    <br/>
    Gender: @Html.DropDownList("Gender",
           Options.ListGenders().Select(g => 
                new SelectListItem() { Text = g, Value = g }))
    <br/>

    State: @Html.DropDownListFor(m => m.State.Code,
           Options.ListStates().Select(s => 
                new SelectListItem() { Text = s.Name, Value = s.Code}))
    <br />

    Fav. Color: @Html.DropDownList("FavColor",
           Options.ListColors().Select(c => 
                new SelectListItem() { Text = c, Value = c }))
    </div>
</body>
</html>

ProfileOptionsService 클래스는 이 폼에 필요한 데이터만을 제공하기 위해서 전용으로 설계된 UI 수준의 서비스입니다:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
using System.Collections.Generic;

namespace ViewInjectSample.Model.Services
{
    public class ProfileOptionsService
    {
        public List<string> ListGenders()
        {
            // keeping this simple
            return new List<string>() {"Female", "Male"};
        }

        public List<State> ListStates()
        {
            // a few states from USA
            return new List<State>()
            {
                new State("Alabama", "AL"),
                new State("Alaska", "AK"),
                new State("Ohio", "OH")
            };
        }

        public List<string> ListColors()
        {
            return new List<string>() { "Blue","Green","Red","Yellow" };
        }
    }
}

먼저 사용할 형식을 Startup.cs 파일의 ConfigureServices 메서드에서 의존성 주입을 통해서 등록해야 한다는 점을 잊지 마십시오.

서비스 재정의하기

이 기법을 활용하면 새로운 서비스를 주입하는 작업 외에 기존에 페이지에 주입된 서비스를 대체할 수도 있습니다. 다음 그림은 첫 번째 예제에 사용된 페이지에서 사용 가능한 모든 필드들을 보여줍니다:

직접 확인할 수 있는 것처럼 기본 필드로 Html, Component, 그리고 Url이 포함되어 있습니다 (주입된 StatsService 뿐만 아니라). 만약 기본 HTML 헬퍼의 인스턴스를 직접 구현한 버전으로 대체하고 싶다면, 다음과 같이 @inject 지시문을 이용해서 손쉽게 처리할 수 있습니다:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@using System.Threading.Tasks
@using ViewInjectSample.Helpers
@inject MyHtmlHelper Html
<!DOCTYPE html>
<html>
<head>
    <title>My Helper</title>
</head>
<body>
    <div>
        Test: @Html.Value
    </div>
</body>
</html>

기존 서비스를 확장하고 싶은 경우, 기존 구현을 상속받거나 래핑해야 할 때 간단히 이 기법을 활용하면 됩니다.