상세 컨텐츠

본문 제목

[ASP.NET Core] 의존성 주입(Dependency Injection)

.NET/ASP.NET Core

by 클리엘 2021. 11. 1. 11:01

본문

728x90

흔히 '관심사의 분리'라 하는 것으로 MVC pattern의 가장 중요한 특징 중 하나에 해당합니다. program을 이루는 각 부분의 밀접도를 최소화하여 독립성을 유지하고자 하는 개념으로 new를 통해 곧바로 instance를 생성하는 것이 아니라 interface를 이용해 의존성 주입을 구현하고 이로 인해 program을 이루는 각 구성요소의 관심사를 최대한 분리하고자 하는 것이 핵심입니다.

 

응용 program의 상호의존성이 너무 강하면 하나의 변경사항으로 다른 쪽 수정사항을 유발하게 되고 또 그것으로 인해 생각지 못한 전혀 다른 부분에서의 수정 작업을 거쳐야 하는 등 비효휼적인 과정이 너무 많이 발생할 수 있습니다. 따라서 의존성을 줄이면 서로 간의 변경사항에 대한 영향력을 최소화할 수 있게 됩니다.

 

의존성 주입을 시도해보기 전에 아래 방법으로 간단한 login project를 먼저 생성합니다. 이 project는

 

[.NET/ASP.NET Core] - [ASP.NET Core] MVC Pattern

 

[ASP.NET Core] MVC Pattern

MVC는 Model, View, Controller를 의미하는 것으로 이 3개의 구조를 통해 web project가 완성되는 것을 말합니다. 해당 구조의 간단한 예를 만들어 보기 위해 가상의 website에서 login 하는 과정으로 mvc pattern..

lab.cliel.com

 

위의 글과 동일한 project이지만 좀 더 ASP.NET Core의 변경사항에 맞게 수정한 것이며 project도 empty project가 아닌 ASP.NET Core Web App (Model - View - Controller) Template을 그대로 활용한 것입니다. MVC Pattern에서 설명은 크게 다르지 않으니 이 부분은 빠르게 진행하도록 하겠습니다.

 

Visual Studio 2019를 실행하고 ASP.NET Core Web App (Model - View - Controller) Template을 사용한 project를 생성합니다.

그럼 위와 같이 MVC의 기본구조가 완성된 project가 만들어집니다. 이 상태에서 Controller에 UserController를 새롭게 생성하고

using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace WebApplication1.Controllers
{
    public class UserController : Controller
    {
        public IActionResult Index()
        {
            return View();
        }
    }
}

Login처리를 위한 Action Method를 추가합니다.

using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace WebApplication1.Controllers
{
    public class UserController : Controller
    {
        public IActionResult Index()
        {
            return View();
        }

        [HttpGet]
        public IActionResult Login()
        {
            return View();
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Login(UserLogin user)
        {
            return View();
        }
    }
}

Models folder에는 UserLogin Class를 만들어 View Model을 생성합니다.

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;

namespace WebApplication1.Models
{
    public class UserLogin
    {
        [Required(AllowEmptyStrings = false, ErrorMessage = "아이디가 입력되지 않았습니다.")]
        [MinLength(length: 5, ErrorMessage = "아이디는 최소 5자 이상 입력되어야 합니다.")]
        [Display(Name = "아이디")]
        public string UserID { get; set; }

        [Required(AllowEmptyStrings = false, ErrorMessage = "비밀번호가 입력되지 않았습니다.")]
        [Display(Name = "비밀번호")]
        public string UserPW { get; set; }
    }
}

다시 UserController로 돌아와 HttpPost가 설정된 Login()을 다음과 같이 수정합니다.

[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Login(UserLogin user)
{
    if (ModelState.IsValid)
    {
        string userid = "abcdef";
        string userpw = "1234";

        if (user.UserID.Trim().Equals(userid) && user.UserPW.Trim().Equals(userpw))
        {
            TempData["message"] = "로그인 되었습니다.";
        }
        else
        {
            TempData["message"] = "로그인에 실패하였습니다.";
        }

        return RedirectToAction("Index", "Home");
    }
    else
    {
        ModelState.AddModelError(string.Empty, "입력값 오류");
        return RedirectToAction("Login");
    }
}

Index.cshtml에는 TempData를 표시할 수 있도록 하고

@{
    ViewData["Title"] = "Home Page";
}

<div class="text-center">
    <h1 class="display-4">Welcome</h1>
    <p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
</div>

<div>@TempData["message"]</div>

Login.cshtml 새롭게 추가해 다음과 같이 UI를 완성합니다.

@model UserLogin
@*
    For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
*@
@{
}

<form method="post" asp-controller="User" asp-action="Login" onsubmit="btnLogin.disabled = false; return true;">
    <div asp-validation-summary="All"></div>
    @Html.DisplayNameFor(Model => Model.UserID)&nbsp;
    <input type="text" asp-for="UserID" placeholder="아이디" autofocus />&nbsp;<span asp-validation-for="UserID"></span>
    <br />
    @Html.DisplayNameFor(Model => Model.UserPW)&nbsp;
    <input type="password" asp-for="UserPW" placeholder="비밀번호" />&nbsp;<span asp-validation-for="UserPW"></span>
    <br />
    <button type="submit" name="btnLogin">로그인</button>
</form>

@section Scripts {
    @{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
}

이와 같은 상태에서 의존성 주입을 적용하기 위해, 우선 data로서의 Model과 View에서 사용될 Model을 정의하는 별도의 project를 생성하는 것으로 시작합니다.

project는 class library를 선택하고 진행하며 project가 생성되면 Data와 View folder를 추가해 놓습니다.

View folder에는 View Page에 사용할 Model을 저장할 곳인데 이미 위에서 만든 UserLogin class가 view model에 해당하므로 해당 file을 View folder에 끌어다 놓습니다. 다만 namespace가 달라지므로 project에 맞게 수정해 줘야 합니다.

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;

namespace WebModel.View
{
    public class UserLogin
    {
        [Required(AllowEmptyStrings = false, ErrorMessage = "아이디가 입력되지 않았습니다.")]
        [MinLength(length: 5, ErrorMessage = "아이디는 최소 5자 이상 입력되어야 합니다.")]
        [Display(Name = "아이디")]
        public string UserID { get; set; }

        [Required(AllowEmptyStrings = false, ErrorMessage = "비밀번호가 입력되지 않았습니다.")]
        [Display(Name = "비밀번호")]
        public string UserPW { get; set; }
    }
}

그리고 본래 있던 UserLogin파일은 삭제해야 하는데 그러면 기존 project에서는 당연히 오류가 발생할 것이므로 새롭게 만든 project(예제에서는 WebModel)를 기존 project(예제에서는 WebApplication1)에서 참조하도록 설정합니다.

그런 후에 해당 소스에서 using을 통해 namespace를 지정하여 오류를 제거하도록 합니다. 이제 WebModel의 Data folder에도 UserLogin class를 추가합니다.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace WebModel.Data
{
    public class UserLogin
    {
        public string UserID { get; set; }
        public string UserPW { get; set; }
    }
}

WebService라는 project를 새로 생성하고 WebService에서도 WebData를 참조 추가합니다. 그다음 WebService project에 UserService.cs file을 추가합니다.

추가한 UserService.cs file은 다음과 같이 수정합니다. source를 보면 UserSerivce가 IUserService로부터 상속받도록 되어 있는데 IUserSerivce는

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WebModel.Data;

namespace WebService
{
    public class UserService : IUserService
    {
        private List<UserLogin> Users = new List<UserLogin> {
            new UserLogin { UserID = "abcdefg", UserPW = "1234" }
        };

        public bool UserExists(string userID, string userPW)
        {
            return Users.Where(x => x.UserID == userID && x.UserPW == userPW).Any();
        }
    }
}

IUserService.cs라는 interface file도 다음과 같은 내용으로 새롭게 추가합니다.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace WebService
{
    public interface IUserService
    {
        bool UserExists(string userID, string userPW);
    }
}

의존성 주입에서는 이 interface가 핵심입니다. UserService를 통해 곧바로 instance를 생성하지 않고 중간에 interface를 통해서 필요한 method를 호출해야 합니다.

 

이제 WebApplication1 project에서 WebService project를 참조 추가합니다.(기존의 참조했던 WebModel은 삭제합니다.) WebApplication1 project의 UserController에서는 생성자 주입을 통해 UserService의 instance를 받아올 것이므로 생성자를 만들고 login을 처리하는 method에서도 UserService에서 정의한 UserExists() method를 사용할 수 있도록 수정합니다.

public class UserController : Controller
{
    private IUserService _IUser;

    public UserController(IUserService IUser)
    {
        _IUser = IUser;
    }

    public IActionResult Index()
    {
        return View();
    }

    [HttpGet]
    public IActionResult Login()
    {
        return View();
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    public IActionResult Login(UserLogin user)
    {
        if (ModelState.IsValid)
        {
            if (_IUser.UserExists(user.UserID, user.UserPW))
            {
                TempData["message"] = "로그인 되었습니다.";
            }
            else
            {
                TempData["message"] = "로그인에 실패하였습니다.";
            }

            return RedirectToAction("Index", "Home");
        }
        else
        {
            ModelState.AddModelError(string.Empty, "입력값 오류");
            return RedirectToAction("Login");
        }
    }
}

그리고 Startup.cs file의 ConfigureServices method에서도 AddScoped() 메서드를 사용해 UserService의 instance가 IUserService로 들어갈 수 있도록 Serivce를 추가합니다. 이렇게 하면 자동으로 IUserService를 사용할 때마다 UserService의 instance가 주입됩니다.

public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped<IUserService, UserService>();
    services.AddControllersWithViews();
}

마지막으로 Login.cshtml에서 using을 통해 변경된 model의 namespace를 지정해주면 전체적인 project가 완성됩니다.

@using WebModel.View
@model UserLogin
@*
    For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
*@
@{
}

 

728x90

태그

관련글 더보기

댓글 영역