• 35 minutes to read

25 ASP.NET Core MVC 프레임워크 ASP.NET Core의 기반 기술인 MVC 프레임워크의 주요 특징과 전반적인 내용을 다루어 보겠습니다.

Pages 이름으로 ASP.NET Core MVC 웹 애플리케이션 만들기

25.1 ASP.NET Core MVC
ASP.NET Core는 ASP.NET과는 달리 웹 폼 기술은 포함하지 않고 MVC와 Web API 그리고 SignalR 등이 통합된 형태로 제공됩니다. 앞서 작성한 ASP.NET Core 웹 응용 프로그램 프로젝트인 DotNetNote 프로젝트는 MVC를 사용하는 데 필요한 모든 구성 요소가 갖추어져 있습니다. 이러한 ASP.NET Core MVC는 기본적으로 NuGet을 통해서 Microsoft.AspNetCore.Mvc 패키지(어셈블리)를 추가해야 합니다. 그리고 Startup.cs 파일에 MVC 서비스 추가를 위한 AddMvc() 메서드와 MVC 사용 설정을 위한 UseMvc() 메서드를 호출해야 합니다. 그런 후 경로에 따른 액션 및 뷰 페이지 호출을 위한 MVC 라우트 설정이 완료되면 완벽한 MVC 프로젝트가 설정됩니다. 다음 코드는 기본 생성된 Startup.cs 파일의 내용입니다. ConfigureServices() 메서드에서 AddMvc() 메서드가 실행되고, Configure() 메서드 하단에서 UseMvc() 메서드가 호출됨으로써 MVC 사용을 위한 기본 절차가 이미 구현되어 있습니다.

//기본 생성된 Startup.cs 파일의 내용 살펴보기: ASP.NET Core 1.0
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
 
namespace DotNetNote
{
    public class Startup
    {
        public Startup(IWebHostEnvironment env)
        {
            var builder = new ConfigurationBuilder()
                .SetBasePath(env.ContentRootPath)
                .AddJsonFile(
                    "appsettings.json", optional: true, reloadOnChange: true)
                .AddJsonFile(
                    $"appsettings.{env.EnvironmentName}.json", optional: true)
                .AddEnvironmentVariables();
            Configuration = builder.Build();
        }
 
        public IConfigurationRoot Configuration { get; }
 
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
        }
 
        public void Configure(
            IApplicationBuilder app, 
            IWebHostEnvironment env, 
            ILoggerFactory loggerFactory)
        {
            loggerFactory.AddConsole(Configuration.GetSection("Logging"));
            loggerFactory.AddDebug();
 
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseBrowserLink();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
            }
 
            app.UseStaticFiles();
 
            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });
        }
    }
}

//기본 생성된 Startup.cs 파일의 내용 살펴보기: ASP.NET Core 6.0
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using WebApplication1.Data;

namespace WebApplication1
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<ApplicationDbContext>(options =>
                options.UseSqlServer(
                    Configuration.GetConnectionString("DefaultConnection")));
            services.AddDefaultIdentity<IdentityUser>(
                options => options.SignIn.RequireConfirmedAccount = true)
                .AddEntityFrameworkStores<ApplicationDbContext>();
            services.AddControllersWithViews();
            services.AddRazorPages();
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseDatabaseErrorPage();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
                app.UseHsts();
            }
            app.UseHttpsRedirection();
            app.UseStaticFiles();

            app.UseRouting();

            app.UseAuthentication();
            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{controller=Home}/{action=Index}/{id?}");
                endpoints.MapRazorPages();
            });
        }
    }
}

25.1.1 MVC 관련 패키지
MVC 프레임워크를 사용하기 위해서는 project.json 파일 또는 NuGet 패키지 관리자를 통해서 다음 패키지를 프로젝트에 추가해야 합니다. ASP.NET Core 6.0부터는 project.json 파일은 사용되지 않습니다.

"Microsoft.AspNetCore.Mvc": "1.0.0"

1.0 버전 이상에서는 Visual Studio에서 기본 제공하는 웹 응용프로그램 템플릿을 사용하면 MVC 관련 모든 패키지가 기본으로 설치되어 있습니다. 따로 MVC 관련 피키지 설치에 신경을 쓰지 않아도 됩니다.

25.1.2 MVC 사용 전 미들웨어 추가
MVC 프레임워크를 사용하기 위해서는 Startup.cs 파일의 ConfigureServices() 메서드에 다음 코드를 입력해야 합니다(ASP.NET 웹 응용 프로그램 템플릿을 사용한 DotNetNote 프로젝트에는 MVC 사용을 위한 미들웨어 추가 관련 코드가 기본으로 포함되어 있습니다).


//Startup.cs 파일의 ConfigureServices()
services.AddMvc(); // ASP.NET Core 1.0

services.AddControllersWithViews(); // ASP.NET Core 6.0

그리고 Startup.cs 파일의 Configure() 메서드에 다음 코드를 입력해야 합니다.


//Startup.cs 파일의 Configure()
// ASP.NET Core 1.0
app.UseMvc(routes =>
{
    routes.MapRoute(
        name: "default",
        template: "{controller=Home}/{action=Index}/{id?}");
});

// ASP.NET Core 6.0
app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(
        name: "default",
        pattern: "{controller=Home}/{action=Index}/{id?}");
    endpoints.MapRazorPages();
});

25.1.3 Model, View, Controller
MVC 패턴은 다음의 특징을 가집니다.

  • 아키텍처 패턴(Architectural Pattern)
  • 관심의 분리(Separation of Concerns)
  • 테스트(Testability)와 유지 보수성(Maintainability) 높임

25.1.4 <추가> ContentRootPath 속성
참고로 ASP.NET Core에서 사용되는 IWebHostEnvironment.ContentRootPath 속성은 현재 프로젝트의 루트 경로를 나타냅니다. Path.Combile(_hosting.ContentRootPath, “wwwroot”); 형태로 정적 웹 영역의 루트의 물리적인 경로를 얻을 수 있습니다.

25.2 라우팅(Routing)
<공통> MVC 프레임워크에서 웹 브라우저 URL을 통해서 특정 요청을 수행하는 것을 라우팅(Routing)이라고 합니다. 라우트, 라우팅이라는 단어는 경로라는 의미로 MVC는 모든 요청(Request)을 웹 브라우저의 URL 경로를 통해서 처리합니다. 웹 프로그래밍에서 라우팅(Routing)은 단어 그대로 경로 정하기를 의미합니다. 정적인 파일인 HTML, CSS, JavaScript 등은 /Index.html, /css/site.css, /js/site.js 형태로 특정 경로에 특정 파일이 있는 형태입니다. MVC 프레임워크에서는 /Home/Index/1234 형태로 Home 컨트롤러의 Index 액션 메서드의 1234 값을 갖는 Id 매개 변수로 표현되는 라우팅 매커니즘을 사용합니다. MVC의 라우팅은 요청 URI를 컨트롤러의 액션 메서드에 일치시킵니다. 어려운 말로 다르게 표현해보면, 라우팅은 URL을 종점(Endpoint)에 연결시킵니다. </공통>

ASP.NET Core에서 라우팅은 Startup.cs 파일에서 다음 코드를 통해서 설정이 됩니다.

  • app.UseRouting();

25.2.1 웹 폼과 MVC의 경로(라우트) 차이점
클래식 ASP를 포함한 ASP.NET 웹 폼은 URL 요청 자체가 디스크에 있는 파일을 나타냈습니다. 하지만 ASP.NET Core MVC는 URL 경로가 특정 파일을 나타내지 않고, 특정 컨트롤러에 있는 액션 메서드를 가리키는 방식이 기본값입니다. 정적인 파일 호출이 아닌 컨트롤러의 메서드가 경로(라우트)와 연결된 개념으로 보면 됩니다. 정리해보면 다음과 같습니다.

  • 웹 폼 = 디스크 상의 파일
  • MVC = 컨트롤러 액션(Controller Action)
  • ASP.NET Core Page(Razor Page, Blazor) = 디스크 상의 파일 + 라우팅

25.2.2 URL 라우팅
<공통> URL 라우팅은 특정 요청을 컨트롤러와 액션 메서드에 연결 시켜줍니다. 연결 후 해당되는 뷰 페이지로 전달해주는 역할까지 합니다. 웹 브라우저로 루트 경로를 요청하면 Home 페이지가 실행되는데 설정에 따라서 / 경로 또는 /Home 또는 /Home/Index 경로 모두 똑같이 Home 페이지를 보여줍니다. 이는 MVC 프레임워크의 기본 라우팅 규칙에 의한 것입니다. </공통>

ASP.NET Core MVC의 기본 Home 페이지는 실제로는 ~/Views/Home/Index.cshtml 페이지가 실행되는 것입니다.

그림 25 1 Home 페이지 실행

25.2.3 기본적인 URL 라우팅
<공통> 다음은 MVC에서 기본으로 제공되는 URL 라우팅에 대한 간단한 표현 방법입니다.

표 25 1 MVC URL 라우팅 사용 예 경로 컨트롤러 클래스 액션(Action) 메서드 id 매개변수 https://localhost/Home Home Index https://localhost/Home/Index Home Index https://localhost/Home/About Home About https://localhost/Note/Details/12345 Note Details 1234

표와 같이 MVC의 기본 라우팅 규칙에 의해서 /Home/About 경로를 요청하면 다음과 같이 About 페이지가 실행됩니다. 그림 25 2 About 페이지 실행

</공통> 마찬가지로 /Home/Contact 경로를 요청하면 HomeController.cs 파일의 Contact 액션 메서드에 의해서 ~/Views/Home/Contact.cshtml 페이지가 실행됩니다. 그림 25 3 Contact 페이지 실행

25.2.4 URL 라우팅(컨벤션 기반 라우팅)
ASP.NET Core MVC에서는 Startup.cs 파일에서 라우팅에 대한 정보를 다룹니다. Startup.cs의 Configure 메서드에서 다음 코드에 의해 /Home/Index 형식의 URL이 실행됩니다. 옵션이 설정된 코드는 app.UseMvcWithDefaultRoute() 메서드 호출과 같은 내용을 보여줍니다.

app.UseMvc(routes =>
{
    routes.MapRoute(
        name: "default",
        template: "{controller=Home}/{action=Index}/{id?}");
});

다음과 같이 경로가 요청되면 모두 똑같이 Home 컨트롤러의 Index 액션 메서드를 실행합니다.

  • /
  • /Home
  • /Home/Index
  • /Home/Index/<모든 값>

만약 /Home/Index/100 형식으로 요청되면 Home 컨트롤러의 Index 액션 메서드에 id 매개변수로 값 100이 전달됩니다. /Home/About 형태로 URL이 요청되면 MVC 프로젝트의 Home 컨트롤러에서 About 액션 메서드가 있으면 실행되고, 없으면 에러를 표시합니다. 최종적으로 다음과 같이 기본값으로 URL 요청이 이루어진다고 보면 됩니다.

  • /컨트롤러/액션/매개변수 컨트롤러 안에 있는 각각의 액션 메서드가 호출되면 /Views/ 폴더 중 컨트롤러와 같은 이름의 서브 폴더에서 액션 메서드와 같은 이름의 cshtml 뷰 페이지를 찾아 실행됩니다. /Home/Index/1234 식으로 요청되면 /Views/Home/Index.cshtml 페이지가 실행되고, 매개변수로 id=1234가 전달됩니다. 참고로 AccountController 클래스에 Register와 Login 액션 메서드가 있고, 그에 해당하는 뷰 페이지가 있다고 가정합니다. /Account/Register 경로를 요청하면 AccountController 클래스의 Register 액션 메서드가 실행되고 /Views/Account/Register.cshtml 파일이 나타난다. 그림 25 4 Register 페이지 실행

마찬가지로 /Account/Login 경로를 요청하면 AccountController 클래스의 Login 액션 메서드가 실행되고 /View/Account/Login.cshtml 파일이 실행됩니다. 그림 25 5 Login 페이지 실행

25.2.4.1 라우트 제약조건 다음 코드처럼 라우트에 제약조건을 줄 수 있습니다. {id:int?}

25.2.5 정적 세그먼트(Static Segment)
라우트에 특정 문자열을 삽입하는 걸 말합니다. template: "Board/{action}/{num?}"

25.2.6 참고: ASP.NET 4.6 MVC 5에서의 라우팅
ASP.NET Core MVC와 달리 ASP.NET MVC 5 프로젝트는 Global.asax 파일에 다음 코드와 같이 라우트 설정을 등록하는 코드가 있고, RouteConfig 파일은 따로 ~/App_Start/RouteConfig.cs 파일에 코드가 존재합니다.


//MVC 5 프로젝트의 Global.asax 파일
protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();
    FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
    RouteConfig.RegisterRoutes(RouteTable.Routes);
    BundleConfig.RegisterBundles(BundleTable.Bundles);
}

참고로 다음은 MVC 5 RouteConfig.cs 파일의 코드입니다. MapRoute() 메서드에 기본값으로 /<컨트롤러이름>/<액션이름>/<매개변수값> 형태로 구성되어 있고, 기본값으로 입력이 들어오지 않을 때는 /Home/Index 경로가 호출되도록 설정된 내용입니다.


///App_Start/Route.Config.cs 파일
public class RouteConfig
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

        routes.MapRoute(
            name: "Default",
            url: "{controller}/{action}/{id}",
            defaults: new { 
controller = "Home", action = "Index", id = UrlParameter.Optional }
        );
    }
}

이와 같은 코드에 의해서 /Home/Index/ 형식으로 호출될 때 HomeController에 있는 Index 액션 메서드가 실행됩니다.

25.2.7 참고: asp-controller, asp-action, asp-route-id, asp-page asp-page 특성은 Pages 폴더에 있는 Razor 페이지로 이동할 때 사용됩니다.

25.2.8 여러 기술이 사용하는 라우팅 예

  • MVC: /Home/Index
  • Razor Pages: /MyPages
  • SignalR: /Hub/Chat

25.2.9 endpoints.MapHealthChaecks(“/healthz”);
25.2.9.1 app.UseHealthChecks("/healthz"); 상태 모니터링을 위한 경로를 추가할 수 있습니다.

25.3 [Route()] 특성을 사용한 특성 라우팅 MVC 프레임워크는 Route 특성을 제공하여 [Route(“/Abcd”)] 식으로 특정 컨트롤러 클래스에 붙이면 해당 컨트롤러 요청 시 /Abcd로 바로 실행할 수 있습니다. 이를 특성 라우팅이라고 합니다. 특성 라우팅은 기본적으로 제공하는 규칙 기반 라우팅 이외의 형태로 직접 라우팅을 설정해서 사용하고자 할 때 유용한 기능입니다. 특정 액션 메서드에도 [Route("ABC")] 특성을 붙일 수 있습니다. 이렇게 웹 페이지 요청 시 /컨트롤러/ABC 경로를 요청하면 [Route(“ABC”)]가 설정된 해당 액션 메서드가 수행됩니다. 액션 메서드 하나에 Route 특성을 하나 이상 적용할 수 있습니다. [Route("Foo/Bar/{MyParam}")] 식 요청으로 액션 메서드의 MyParam 매개변수를 받는 형태로 라우팅을 설정할 수 있습니다. ASP.NET Core MVC 프로젝트인 DotNetNote 웹 프로젝트에 다음과 같이 컨트롤러 클래스를 만들고 Route 특성을 부여합니다. 단순하게 경로만 테스트하기에 Controller로부터 상속하지 않고, 일반 클래스에 Route 특성만 적용하였습니다.


///Controllers/RouteDemoController.cs
using Microsoft.AspNetCore.Mvc;

namespace DotNetNote.Controllers
{
    // [Route()] 특성을 사용한 특성 라우팅
    [Route("RouteDemo")]
    public class RouteDemoController
    {
        [Route(""), Route("Index")]
        public string Index()
        {
            return "특성 라우팅";
        }
    }
}

프로젝트를 실행 후 웹 브라우저에서 /RouteDemo 또는 /RouteDemo/Index 경로를 요청해서 Index 액션 메서드를 실행할 수 있습니다. 그림 25 6 특성 라우팅

25.3.1 ASP.NET Core 라우팅 방식: 템플릿 라우팅과 특성 라우팅

  • 템플릿 라우팅: Startup.cs 파일에서 정의된 컨트롤러/액션/매개 변수 형태의 라우팅 방식
  • 특성(Attribute) 라우팅: 특정 컨트롤러 또는 메서드에 [Route] 특성을 사용하여 HTTP 요청을 처리하는 방식

25.4 [실습] ASP.NET Core MVC의 주요 특징 미리보기 25.4.1 소개 Visual Studio 2019로 만든 ASP.NET Core 프로젝트에서 MVC 프레임워크의 주요 특징을 살펴보겠습니다. 예제를 통해 모델, 컨트롤러, 뷰에 해당하는 각각의 기능을 하나씩 따라해보겠습니다.

25.4.2 따라하기 1: 기본 생성 프로젝트 둘러보기 (1) Visual Studio를 사용하여 C:\ASP.NET\DotNetNote 프로젝트를 실행합니다.

(2) DotNetNote 프로젝트에 Models 폴더를 생성합니다. MVC 패턴의 기본 구조인 Models, Views, Controllers 폴더를 두고 이곳에 각각의 기능을 구현할 것입니다.

(3) 프로젝트 루트에 있는 project.json 파일을 열어 보면 다음과 같이 기본 구조가 만들어져 있다(버전에 따라 책과 다르게 표현될 수 있습니다). “dependencies” 섹션에 “Microsoft.AspNetCore.Mvc” 항목이 추가되어 있음을 살펴보기 바랍니다. 이 패키지가 MVC 프레임워크를 사용하도록 합니다. 그림 25 7 project.json 파일의 내용

project.json 파일은 새로운 형태의 프로젝트 파일입니다. project.json 파일 안에서 편집할 때는 인텔리센스의 도움을 받을 수 있습니다. 편집 시 관련된 키워드에 해당하는 패키지들을 자동으로 NuGet으로 읽어와 프로젝트에 포함시켜 줍니다.

<참고> MVC 관련 패키지 추가 ASP.NET Core 프로젝트 생성시 Empty 템플릿을 사용하여 생성한 프로젝트에서는 MVC와 관련해서 아무런 패키지도 포함되어 있지 않습니다. MVC를 사용하려면 project.json 파일의 dependencies 영역에 "Microsoft.AspNetCore.Mvc"를 직접 포함해주어야 합니다. </참고>

(4) DotNetNote 프로젝트의 참조를 확장하면 다음과 같이 Mvc 패키지(어셈블리)를 추가한 후 참조 영역에 관련 어셈블리가 등록된 모습을 확인할 수 있습니다.

그림 25 8 MVC 패키지가 포함된 후의 참조(References)

(5) 이번에는 응용 프로그램 파이프라인(pipeline)을 확인해보겠습니다. MVC 관련 참조를 프로젝트에 추가했다면 다음으로 응용 프로그램 파이프라인에 MVC 프레임워크를 추가해야 합니다. Startup.cs 파일을 열고 Configure 메서드 코드를 다음과 같이 확인합니다.


//Startup.cs Configure() 메서드 중 MVC 설정 코드
public void Configure(IApplicationBuilder app)
{
    app.UseMvc(routes =>
    {
        routes.MapRoute(
            name: "default",
            template: "{controller=Home}/{action=Index}/{id?}");
    });
}

(6) 다음으로 Startup.cs 파일의 ConfigureServices 메서드에 AddMvc() 메서드를 호출하여 현재 프로젝트에 MVC를 사용할 수 있게 해야 합니다. 여기까지 설정되어 있어야 웹 프로젝트에서 MVC를 사용할 수 있습니다. ASP.NET Core 프로젝트에서 MVC 사용을 위한 최소한의 단계는 AddMvc()와 UseMvc() 메서드의 호출입니다. ConfigureServices() 메서드에 다음 코드와 같이 AddMvc() 메서드가 추가되었는지 확인합니다.


//Startup.cs 파일의 ConfigureServices() 메서드
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc(); 
}

(7) AddMvc() 확장 메서드를 호출함으로써 MVC 관련 의존성을 모두 추가할 수 있습니다. App.UseMvc()로 라우팅을 설정하는 코드는 app.UseMvcWithDefaultRoute() 메서드로 대체해서 사용해도 무관합니다. 당연한 얘기겠지만 MVC를 포함한 정적 파일들을 서비스하려면 app.UseStaticFiles() 메서드도 미리 함께 추가해야 한다

<참고> 상태 코드 표시 관련 미들웨어 프로젝트에 대한 예외 처리 시 예외에 대한 자세한 상태 코드를 표시하려면 다음 두 미들웨어 중 하나를 ConfigureServices 메서드에 추가하면 됩니다.

  • app.UseStatusCodePages();
  • app.UseStatusCodePagesWithRedirects("~/Error/Code{0}"); </참고> 25.4.3 따라하기 2: MVC의 주요 기능 미리 살펴보기 (1) 따라하기 1에서는 기본 생성 코드를 살펴보았습니다. 계속해서 Models 폴더에 샘플로 모델 클래스를 하나 만들어보겠습니다. 솔루션 탐색기의 Models 폴더에 마우스 오른쪽 버튼을 클릭해서 새 항목 중 클래스를 선택하고, 클래스 이름은 Data.cs로 설정합니다. 기본 제공 코드를 모두 제거하고 3개의 클래스 파일을 다음과 같이 작성합니다. 데모용 소스가 아닌 실제 사용 가능한 환경이라면 _data 필드에 저장되는 데이터는 SQL Server 데이터베이스의 데이터가 사용됩니다. 하지만 이 예제에서는 인메모리 데이터베이스 형태를 사용할 것입니다. 추가적으로 뷰 페이지에 코드를 주입시켜서 사용할 목적으로 DataFinder 클래스를 만들고 비동기 메서드로 특정 Id에 해당하는 데이터를 찾아주는 메서드를 구현하였습니다. 다음 코드는 Data와 DataService, DataFinder 클래스 전체를 나타냅니다.

//Models/Data.cs 
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
 
namespace DotNetNote.Models
{
    public class Data
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Title { get; set; }
    }
 
    public class DataService
    {
        private readonly List<Data> _data = new List<Data>()
        {
            new Data { Id = 1, Name = "김태영", Title = "안녕하세요." },
            new Data { Id = 2, Name = "박용준", Title = "반갑습니다." },
            new Data { Id = 3, Name = "한상훈", Title = "또 만나요." },
        };
 
        public List<Data> GetAll()
        {
            return _data;
        }
 
        public Data GetDataById(int id)
        {
            return _data.Where(n => n.Id == id).SingleOrDefault();
        }
 
        public List<Data> GetDataByName(string name)
        {
            return _data.Where(
                n => n.Name.ToLower().Equals(name.ToLower())).ToList();
        }
    }
 
    public class DataFinder
    {
        private DataService _service = new DataService();
 
        public async Task<Data> GetDataById(int id)
        {
            return await Task.FromResult(_service.GetDataById(id));
        }
    }
}

(2) 모델 클래스도 작성했으니 이제 이를 사용할 컨트롤러 클래스를 만들어 보겠습니다. Controllers 폴더에 마우스 오른쪽 버튼을 클릭하여 새 컨트롤러인 DataController.cs 파일을 생성합니다. DataController에 IActionResult를 반환시켜 주는 Index 메서드에 DataService 클래스를 사용하여 모든 데이터 리스트를 View() 메서드의 매개변수로 전달하여 앞으로 생성할 뷰 페이지로 전송하는 코드를 입력합니다. 컨트롤러는 모델의 데이터를 받아서 그 값을 가공 등의 과정을 거쳐 뷰 페이지로 넘겨주는 역할을 합니다.


//Controllers/DataController.cs 
using DotNetNote.Models;
using Microsoft.AspNetCore.Mvc;
 
namespace DotNetNote.Controllers
{
    public class DataController : Controller
    {
        public IActionResult Index()
        {
            // 모든 데이터를 읽어서 View 페이지에 전달
            DataService demoService = new DataService();
            var data = demoService.GetAll();
            return View(data);
        }
    }
}

(3) 이번에는 뷰 페이지를 만들어 보겠습니다. Views 폴더에 마우스 오른쪽 버튼을 클릭하여 ‘Data’라는 새 폴더를 만듭니다. Data 폴더에 마우스 오른쪽 버튼을 클릭하여 <추가 > 새 항목>에서 <MVC 뷰 페이지>를 Index.cshtml라는 이름으로 생성합니다. Index.cshtml 페이지에 다음과 같이 코드를 입력합니다.


//Views/Data/Index.cshtml 
@using DotNetNote.Models
@model IEnumerable<Data>
@inject DataFinder Finder
 
<h2>데이터 리스트</h2>
<h3>전체 데이터 출력</h3>
<ul>
    @foreach (var data in Model)
    {
        <li>@data.Id: @data.Name, @data.Title</li>
    }
</ul>
<hr />
 
<h3>단일 데이터 출력</h3>
@{
    var first = await Finder.GetDataById(1);
}
<div>
    1번 데이터: @first.Id, @first.Name, @first.Title
</div>

첫째 줄의 @using 지시문은 C#의 using 구문과 같은 역할로 현재 뷰 페이지에서 Models 네임스페이스를 사용하겠다고 지정한 것입니다. @model 지시문에 의해 컨트롤러에서 뷰 페이지로 전송된 데이터를 List<Data> 또는 IEnumerable<Data> 형태로 받습니다. 그런 후 Razor 문법에 의해 @foreach 문으로 넘겨온 전체 값을 Model 개체에 담고, 이를 반복하면서 data 개체로 받아서 data 개체의 Id, Name, Title 속성을 출력해서 웹 페이지에 반복해서 출력하는 코드를 구현했습니다. Razor 표현식으로 불리는 @foreach 구문을 사용하는 방식은 뒤에서 한 번 더 자세히 설명합니다. @inject 지시문은 DataFinder 클래스를 직접 컨트롤러에서 호출해서 뷰 페이지로 전달하는 게 아니라 뷰 페이지에 직접 주입(Inject)해서 사용할 수 있게 하는 의존성 주입 방식입니다. 단, 이 코드를 사용하려면 DataFinder 클래스를 Starup.cs 파일에서 서비스 등록을 해주어야 합니다. 이 절차는 잠시 후에 살펴보도록 합니다.

(4) 일단 현재 프로젝트를 시작 프로젝트로 설정 후 [Ctrl]+[F5]를 클릭하여 프로젝트를 실행하고, 웹 브라우저 주소 창에 /Data 경로를 요청하면 다음과 같이 에러 메시지가 출력됩니다. 이는 @inject로 주입해서 사용되는 DataFinder 클래스를 Startup.cs 파일에 등록하지 않아서 나타나는 에러 메시지다.

그림 25 9 의존성 주입 이전에 발생하는 에러 메시지

(5) 프로젝트 루트에 있는 Startup.cs 파일의 ConfigureServices() 메서드의 제일 하단에 아래 코드를 추가합니다. services.AddTransient() 메서드를 통해서 DataFinder 클래스를 서비스로 등록하는 코드입니다. services 변수에 AddTransient, AddInstance 등의 메서드를 사용하는 방식이 MVC에 기본 내장된 의존성 주입(Dependency Injection) 기능인데 이에 대한 내용도 뒤에서 좀 더 자세히 설명합니다. 간단히 설명하면 현재 프로젝트에서 DataFinder를 서비스로 등록해놓고, 원하는 컨트롤러 또는 뷰 페이지에서 바로 사용 가능하도록 하는 것입니다.


//Startup.cs 파일의 ConfigureServices 메서드의 코드 일부
// [Demo] DemoFinder 의존성 주입
services.AddTransient<DotNetNote.Models.DataFinder>();

다음은 코드를 입력한 후 Startup.cs 파일의 모습입니다. 여기서 DotNetNote.Models 네임스페이스를 using 구문으로 상단에 등록하여 사용할 수 있습니다. 그림 25 10 의존성 주입

(6) 다시 웹 브라우저를 실행하면 이제 정상적으로 출력됩니다. 그림 25 11 의존성 주입 후 정상적으로 데이터 출력

이처럼 모델(Model)에서 발생된 데이터를 컨트롤러(Controller)가 가져다가 뷰(View) 페이지에서 출력하는 형태가 데이터를 출력하는 가장 일반적인 방식입니다. 여기까지가 일반적인 MVC 프레임워크에서 가장 많이 사용하는 모델, 뷰, 컨트롤러 폴더에 대한 내용입니다. ASP.NET Core MVC에서는 모델의 데이터를 컨트롤러가 아닌 직접 뷰에서 가져다 사용할 수 있는 방법을 제공합니다. 이를 사용하려면 뷰 페이지에서 @inject 지시문으로 직접 특정 클래스를 호출해야 합니다.

(7) 이번에는 뷰 컴포넌트(View Component)라는 개념을 미리보기로 살펴보겠습니다. 프로젝트에 ViewComponents라는 폴더를 생성하고, 이곳에 DataListViewComponent.cs라는 클래스 파일을 만듭니다. 이 클래스 파일에 ViewComponent 클래스로부터 상속 받는 코드를 작성합니다. 조금 복잡해 보일 수 있으나, 특정 이름에 해당하는 데이터를 받아 뷰 컴포넌트 전용 뷰 페이지에 전송해서 사용하게 하는 코드입니다. 뷰 컴포넌트는 이번 실습에서 미리보기로 한 번 만들어보고, 뒤에서 더 자세히 다루겠습니다.


//ViewComponents/DataListViewComponent.cs
using DotNetNote.Models;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Threading.Tasks;
 
namespace DotNetNote.ViewComponents
{
    public class DataListViewComponent : ViewComponent
    {
        public async Task<IViewComponentResult> InvokeAsync(string name)
        {
            var data = await GetByNameAsync(name);
            return View(data);
        }
 
        private Task<IEnumerable<Data>> GetByNameAsync(string name)
        {
            return Task.FromResult(GetByName(name));
        }
 
        private IEnumerable<Data> GetByName(string name)
        {
            DataService service = new DataService();
            return service.GetDataByName(name);
        }
    }
}

(8) ViewComponent 클래스의 기능을 가져다 사용할 페이지를 만들겠습니다. Views 폴더의 Shared 폴더에 Components라는 이름의 폴더를 만듭니다. 이 폴더에 위에서 작성한 DataListViewComponent 클래스의 이름 중 접미사 ViewComponent 부분을 빼고 DataList란 이름으로 폴더를 작성합니다. 이는 MVC 프레임워크에서 사용하는 규칙이므로 ViewComponent를 사용하려면 이 방식을 사용해야 합니다. 마지막으로 DataList 폴더에 MVC 뷰 페이지인 Default.cshtml 파일을 생성합니다. 이 뷰 페이지 이름도 기본값은 Default로 설정해야 합니다. Default.cshtml 파일을 다음과 같이 작성합니다. Razor 구문에 의해서 넘어온 Data의 컬렉션을 출력하는 예제입니다.


//Views/Shared/Components/DataList/Default.cshtml 
@using DotNetNote.Models
@model IEnumerable<Data>

<ul>
    @foreach (var data in Model)
    {
        <li>@data.Id, @data.Name, @data.Title</li>
    }
</ul>

(9) 마지막으로 Views/Data/Index.cshtml 페이지 하단에 다음 코드를 추가 입력하여 생성된 ViewComponent를 Index 페이지에 적용할 수 있습니다. 매개변수로 “박용준”을 넘겨주어 해당 레코드만 찾아서 출력하는 예제입니다.


//Views/Data/Index.cshtml 
@using DotNetNote.Models
@model IEnumerable<Data>
@inject DataFinder Finder
 
<h2>데이터 리스트</h2>
<h3>전체 데이터 출력</h3>
<ul>
    @foreach (var data in Model)
    {
        <li>@data.Id: @data.Name, @data.Title</li>
    }
</ul>
<hr />
 
<h3>단일 데이터 출력</h3>
@{
    var first = await Finder.GetDataById(1);
}
<div>
    1번 데이터: @first.Id, @first.Name, @first.Title
</div>
<hr />
 
 
<h3>뷰 컴포넌트 호출</h3>
<div>
    @await Component.InvokeAsync("DataList", new { name = "박용준" })
</div>

이 코드의 Component.InvokeAsync() 메서드는 첫 번째 매개변수로 DataList 뷰 컴포넌트 이름을 지정하고, 두 번째 매개변수로 DataList 뷰컴포넌트에 name 매개변수로 값을 전달하여 현재 위치에 출력하도록 합니다.

(10) 웹 브라우저로 다시 실행하면 다음과 같이 하단에 데이터가 출력됨을 확인할 수 있습니다. 그림 25 12 최종 실행 결과

25.4.4 마무리 이번 실습을 통해 ASP.NET Core 프로젝트에 모델, 컨트롤러, 뷰 페이지를 최소한의 코드로 만들어서 실행해보았습니다. ASP.NET MVC의 이전 버전과 달리 MVC의 새로운 특징 중 하나인 기본 내장된 의존성 주입(Dependency Injection) 기능과 뷰 컴포넌트(ViewComponent)를 사용하는 방법까지 순서대로 살펴보았습니다. MVC의 세부 기능은 이어지는 내용을 통해서 계속 학습하도록 합니다.

<추가> 25.5 Request 개체

25.5.1 Request 개체

  • Request.IsHttps  HTTPS 통신인지 확인 <추가>

25.6 [동영상 강의] ASP.NET Core 6.0 첫 번째 응용 프로그램 만들기(VideoApp) 3/13 25.6.1 기본 템플릿의 MVC 프레임워크 구조 살펴보기 25.6.2 기본 뷰 페이지 생성 및 실행

  • VideoApp  Index  Details  Create

25.7 GetEndpoint() 메서드 app.Use(next => context => { Console.WriteLine($"Found: {context.GetEndpoint()?.DisplayName}"); return next(context); });

현재 실행중인 라우팅의 경로를 제공

25.8 RequireAuthorization() 메서드 특정 라우팅에 대해서 [Authorize] 특성을 부여하는 것과 동일한 기능을 제공합니다.

  • endpoints.MapHealthChecks(“/healthz”).RequireAuthorization();

25.9 [동영상 강의] 25_99_DotNetNote 솔루션에 클래스 라이브러리와 데이터베이스 프로젝트 추가 후 GitHub에 게시

25.10 SponsorApp_프로젝트를 MVC 프로젝트로 설정하기 25.10.1 SponsorsTypes 테이블 VIP Sponsor Champion

25.10.2 Sponsors 테이블 Id Nickname SponsorType

VisualAcademy Docs의 모든 콘텐츠, 이미지, 동영상의 저작권은 박용준에게 있습니다. 저작권법에 의해 보호를 받는 저작물이므로 무단 전재와 복제를 금합니다. 사이트의 콘텐츠를 복제하여 블로그, 웹사이트 등에 게시할 수 없습니다. 단, 링크와 SNS 공유, Youtube 동영상 공유는 허용합니다. www.VisualAcademy.com
박용준 강사의 모든 동영상 강의는 데브렉에서 독점으로 제공됩니다. www.devlec.com