[ASP.NET Core] ASP.NET Core 시작해보기

개요

새롭게 발표된 닷넷코어(.NET CORE)는 기존 닷넷프레임웍(.NET Native)에 비해 다음과 같은 장점을 갖고 있습니다.

  1. 다양한 OS와 환경 지원
  2. 동적 컴파일을 통한 간편한 개발

특히 닷넷코어 플랫폼 위에서 웹개발을 하는 경우 닷넷코어를 위해 새롭게 작성된 ASP.NET Core 프레임웍을 사용할 수 있습니다. 기존 ASP.NET에 비해 ASP.NET Core는 여러가지 장점을 갖습니다. 몇 가지 장점을 살펴보죠.

자체 웹서버 지원

더 이상 IIS를 반드시 사용할 필요가 없습니다. NodeJS처럼 독립적으로 실행되는 웹 서버 기능을 자체적으로 제공하기 때문입니다. 이 기술을 위해 몇 가지 살펴봐야 할 것이 있습니다.

1 OWIN Interface

웹서버와 닷넷으로 작성된 웹서비스를 분리하기 위한 인터페이스입니다. 기존에는 IIS상에서 ASP.NET 서비스가 실행되어야만 했습니다. 하지만 이제 ASP.NET Core기반의 웹서비스는 무조건 독립적인 서비스로 운영됩니다.
이를 위해 호스트(Host)라는 용어를 알아야합니다. 이에 대한 정의는 MS의 공식문서에서 다루고 있습니다만 중요한 내용을 인용하면 다음과 같습니다.

What is the difference between a host and a server?
The host is responsible for application startup and lifetime management. The server is responsible for accepting HTTP requests. Part of the host’s responsibility includes ensuring the application’s services and the server are available and properly configured. You can think of the host as being a wrapper around the server. The host is configured to use a particular server; the server is unaware of its host.

즉 호스트는 애플리케이션의 작동에 대한 관심을 두는 반면 서버의 경우 HTTP요청을 처리하는데 주안점을 둡니다. 의존관계로 보자면 호스트는 특정 서버를 사용하지만 서버 입장에서는 호스트는 인식할 수 없는 단 방향 관계입니다.

헌데 이 구조에서 호스트가 서버를 사용하니까 호스트와 서버 사이에 표준적으로 통신할 프로토콜이 필요하게 됩니다. 이러한 프로토콜로 정의한 것이 바로 OWIN입니다. 따라서 OWIN은 구현체가 없는 순수한 프로토콜 정의입니다.
이 프로토콜만 준수한다면 호스트는 어떤 서버와도 통신할 수 있을 것입니다. 따라서 IIS와 ASP.NET Core 호스트 간에도 이러한 통신이 일어납니다. 이제 더 이상 직접 IIS가 실행하는 구조가 아닌 셈입니다.

2 Kestrel Host

앞에서 살펴본 OWIN프로토콜은 결국 닷넷코어측 호스트와 웹서버와의 통신을 정의한 프로토콜입니다. ASP.NET Core에서 OWIN의 호스트측 프로토콜의 실제 구현체가 Kestrel 입니다(오픈소스입니다)
이에 비해 웹서버측의 OWIN구현은 직접 해야 하는데 IIS 8.5 이상이나 최신 IISexpress에는 이 구현이 내장되어있습니다. 하지만 구형 IIS7이라던가 nginX같은 서버들은 직접 프로토콜에 맞게 구현할 수 있습니다. 아래에는 다양한 서버환경에 따른 연동에 도움이 되는 링크가 있습니다.

또한 구형 IIS의 경우 역방향프록시에 대한 기능을 확장해야 합니다. 이에 대한 참고할 수 있는 링크(한글)가 있습니다.

사실 이런 이론적인 얘기가 현장 서비스에 얼마나 통할지는 알 수 없습니다. 실제 IIS의 HTTPS의 인증서 연동까지 부드럽게 처리하는 과정은 이후 시리즈가 진행되면서 실습해 보겠습니다.

DI 내장

ASP.NET Core의 경우 IServiceCollection의 다양한 메소드를 통해 DI처리를 할 수 있게 되어 더 이상 외부의 DI에 의존하지 않아도 됩니다. DI는 워낙 핵심적인 개념이라 이후 포스팅에서 다루게 될 것입니다만 기본적인 사용법은 아래 공식 문서에서 구경할 수 있습니다.

Dependency Injection

CLI제공

“dotnet” 이라 불리는 CLI 툴을 제공합니다. 이 툴을 이용하면 비쥬얼스튜디오 등의 도움이 없어도 간편하게 프로젝트는 생성하고 관리하며 실행하는 것이 가능합니다. dotnet의 주요 기능은 다음과 같습니다.

  • dotnet new projectName – 미리 정의된 프로젝트로 스케폴딩함.
  • dotnet restore – 의존성을 파악하여 nuget을 통해 필요한 어셈블리를 내려받음.
  • dotnet run – 즉각적으로 컴파일하여 진입점(Program.Main)을 실행함.

기타 전체 기능에 대한 설명은 아래 링크(한글)를 참고하면 됩니다.

.NET Core 명령줄 인터페이스 도구

동적 컴파일

기존 ASP.NET도 동적컴파일 기능이 없었던 것은 아닙니다. 하지만 코드를 변경하면 반드시 빌드 과정을 거쳐 웹 서비스에 적용되는 구조였죠. 새롭게 작성된 Roslyn컴파일러 기반으로 작동하여 코드를 수정면 곧장 웹서비스에 반영됩니다. Roslyn에 대한 내용은 이 시리즈의 범위를 크게 벗어나기 때문에 다루지 않습니다 ^^;

자바스크립트처럼 곧장 실행된다는 거야?
컴파일이 없는 것처럼 편안한 느낌 일거야. 그러고 보니 NodeJS랑 비슷한 인상.
NodeJS(..뭔지 모르는데..)

설치하기

닷넷코어를 사용하기 위해 설치해야 할 프로그램은 오직 SDK뿐입니다. 다음의 링크에서 플랫폼에 맞는 버전을 설치하면 됩니다(본 포스팅은 윈도우를 기준으로 설명해가게 됩니다)

https://www.microsoft.com/net/download/core

설치가 끝나면 cmd를 열어 간단히 dotnet 이라고 쳐보면 다음과 같은 화면을 볼 수 있습니다.

MVC프로젝트 생성 및 실행

이제 CLI툴을 사용할 수 있게 되었으므로 초 간단히 MVC프로젝트를 생성하여 실행해보죠.

  1. 우선 하나의 웹사이트를 운영할 프로젝트 폴더를 생성합니다.
  2. 그 폴더로 이동한 뒤
  3. dotnet new mvc 를 실행합니다.

예를 들어 c:\test 라는 폴더에서 dotnet new mvc를 실행하면 다음과 같은 화면을 보게 됩니다.

실제 탐색기에서 test폴더를 열어보면 필요한 파일과 폴더가 전부 생성되어있음을 알 수 있습니다.

new 명령을 통해 기본적인 파일은 생성 되었지만, 이 프로젝트가 의존해야 하는 여러 어셈블리가 없는 상태입니다.

현재 프로젝트에 대한 의존성 패키지 정의는 test.csproj에 들어있습니다.
이 전 RC에서는 project.json에 정의되어있었으나 버전업하면서 다시 xml기반의 .csproj로 퇴화 변경되었습니다. 이러한 이유로 시중에 출간된 책과 많은 번역 글에는 project.json 기반의 설명이 많습니다 ^^;

현재 프로젝트 설정에 맞게 필요한 어셈블리를 다운로드 하려면 다음의 명령을 사용합니다.

dotnet restore

필요한 어셈블리를 내려받았으므로 이제 실행할 수 있습니다. 곧장 run을 이용해 실행해보죠.

dotnet run

보시다시피 5000번에서 대기하고 있습니다. localhost:5000을 브라우저로 들어가면 다음과 같은 화면을 보게 될 것입니다.

이상의 과정을 요약하면 다음과 같습니다.

  1. c:\test 폴더 생성
  2. dotnet new mvc
  3. dotnet restore
  4. dotnet run

고작 이 정도입니다. 컴파일 과정도 없고 기본 셋팅도 전부 잡혀있으며 new mvc시점에 이미 mvc 샘플 프로젝트의 스케폴딩 및 기초 코드 샘플이 전부 들어가 있습니다.

NPM과 yo로 손쉽게 express 웹 샘플 띄운 기분을 닷넷코어에서 느낄 수 있어!
…(NPM, yo, express도 다 모른다고 ^^;;)

프로젝트가 생성한 파일들

이 번에는 빈 웹프로젝트를 만들어볼 것입니다. 이미 해봐서 쉬운데, 다른 점은 오직 dotnet new mvc 에서 dotnet new web 으로 바꾸는 것 밖에 없습니다. 다음의 순서대로 차근차근 진행해보죠.

  1. 우선 앞에 열려있던 명령창을 닫아 5000번에서 작동하는 서버를 멈춰줍니다(포트충돌을 피하기 위해)
  2. c:\test1 폴더를 만들고
  3. dotnet new web
  4. dotnet resotre
  5. dotnet run

이제 브라우저에서 localhost:5000 을 하면 간단히 “hello world!”만 출력되는 화면을 만나게 될 것입니다.

이제 앞 서 생성했던 test의 mvc프로젝트와 현재 test1의 빈 web프로젝트의 폴더 안에 파일을 비교해보죠.

결국 핵심적으로는 Program.cs와 Startup.cs만 필요할 뿐 나머지는 mvc프로젝트에서 필요에 따라 더 추가한 것을 알 수 있습니다.

닷넷코어의 최초 실행과정

이번 장은 복잡하니 어려우면 민이는 건너뛰어도 괜찮아
실습은 쉬웠는데 말이지.

Program.cs

dotnet run 을 실행하면 진입점이 되는 클래스를 찾게 되는데, 그게 바로 Program 클래스입니다.

Main함수를 진입점으로 호출하고 모든 것은 거기서부터 시작됩니다.실제 파일을 열어 Main함수의 내용을 보면 다음과 같이 되어있습니다.

public static void Main(string[] args)
{
  var host = new WebHostBuilder()
      .UseKestrel()
      .UseContentRoot(
         Directory.GetCurrentDirectory()
      )
      .UseIISIntegration()
      .UseStartup<Startup>()
      .Build();
  host.Run();
}

코드는 매우 간단한데 그저 host를 빌더 패턴으로 생성한 뒤 Run() 메소드를 통해 실행하는 것이 전부입니다. 빌더에게 설정하고 있는 체이닝 메소드를 살펴보면 되겠죠.
닷넷코어의 웹개발과 관련된 공식 API의 주소는 다음과 같습니다.

https://docs.microsoft.com/en-us/aspnet/core/api/

WebHostBuilder는 Microsoft.AspNetCore.Hosting 네임스페이스 소속입니다. 다음의 문서에서 자세한 설명을 볼 수 있습니다.

WebHostBuilder

하지만 이 문서를 열심히 봐도 빌더가 사용하고 있는 메소드들의 선언이 보이지 않습니다. 전부 익스텐션으로 선언되어있기 때문입니다. 따라서 귀찮지만 각 메소드에 대해서는 각각의 익스텐션 선언을 참조해야 합니다.

각각의 기능을 host에 셋팅한 뒤 최종적으로 Run()을 하게 되는데 Run 또한 익스텐션으로 정의되어있습니다.

하지만 이상의 개별 API를 아는 정도로는 어떻게 작동하여 웹애플리케이션이 실행되는 것인지 이해하기 힘듭니다. 사실 이 글을 쓰는 이유가 API문서에 제대로 된 샘플코드가 들어있지 않고 설명도 단문형으로 불친절하기 때문이라고 할 수 있습니다. 대신 MS는 개요와 같은 설명서형 문서를 제공합니다(하지만 그래봐야 개요와 특정 케이스에 대한 샘플일 뿐이라..) 쨌든 최초 빌더를 셋팅하고 호스트가 시작되는 과정에 대한 공식 설명 문서가 있습니다.

hosting

Run()메소드가 하는 일은 시작 쓰레드를 블록한 채 웹애플리케이션을 실행하는 것입니다. 좀 더 기술적으로는 StartupLoader를 통해 웹애플리케이션의 시작점이 되는 Startup 클래스를 로딩하여 시작하게 됩니다. 문제는 정확하게 이게 어떻게 일어나는가 입니다. 이에 대한 자세한 문서가 다음에 있습니다.

startup

간단히 요약하자면 빌더에서 .UseStartup()을 통해 Startup 클래스를 바인딩 해뒀으므로 Run()시점에 Startup 클래스를 인스턴스화하여 사용하게 됩니다.

Startup.cs

Program에서 ASP.NET Core 프레임웍을 발동시켜 바통을 넘겨받게 되는 것은 바로 Startup 클래스입니다. 우선 test1에 있는 간략한 Startup 클래스의 내용은 다음과 같습니다.

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
    }
    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        loggerFactory.AddConsole();
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.Run(async (context) =>
        {
            await context.Response.WriteAsync("Hello World!");
        });
    }
}

ConfigureServices() 메소드는 DI를 위해 사용됩니다. 현재는 복잡한 DI설정이 없으니 아무 일도 안하고 있습니다.

Configure() 메소드의 내부를 보면 로그를 콘솔에 출력하고 개발 환경인 경우 예외를 출력하게 하는 기본 셋팅을 하고 있습니다. 이어서 실제 app을 실행하면서 요청에 대해 정적으로 무조건 Hello World! 를 출력하게 하는 간단한 구성입니다.

하지만 Startup은 POCO(자바식으로 표현하자면 POJO)객체로 아무런 상속이나 인터페이스 구현이 없습니다. StartupLoader는 어떻게 Startup의 Configure를 app, env, loggerFactory인자와 함께 호출하는 걸까요?

가장 올바른 해답은 깃헙에 오픈된 코드를 보면 알 수 있습니다만, 아래 공식 문서에서도 설명하고 있습니다.

Startup

결국 StartupLoader는 Startup클래스를 어셈블리를 직접 로딩하여 리플렉션으로 통제하는 방식인데, 간략히 로직을 요약하자면 다음과 같습니다.

  1. 어셈블리를 통해 해당 Startup클래스를 얻어 인스턴스를 생성합니다.
  2. 생성자 중 IHostingEnvironment를 받는 생성자가 있다면 그쪽을 사용하여 생성합니다.
  3. Startup클래스는 반드시 Configure메소드를 포함해야 하고 선택적으로 DI를 위해 ConfigureServices메소드를 포함할 수 있습니다.
  4. Startup으로부터 생성된 인스턴스의 Configure를 호출한 뒤 실질적인 서비스가 활성화 됩니다.

이 중 필수적이면서도 가장 중요한 건 Configure메소드를 스펙대로 구상하는 것입니다.
이 메소드에서 인자로 넘어온 app, env, loggerFactory에 다양한 설정을 할 찬스를 갖게 됩니다.

ASP.NET Core의 전체적인 구조는 모던 웹서비스 프레임웍이 그러하듯 미들웨어가 체인으로 구성되는 파이프라인 시스템입니다. 따라서 이 시점에 충분히 필요한 미들웨어를 추가하고 기타 설정을 해두면 그것을 기반으로 웹서비스가 실행될 것입니다. 이미 위의 코드에는 다음과 같은 미들웨어가 하나 포함되어있습니다.

if (env.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}

이 의미는 개발환경인 경우 보다 자세한 예외 메세지를 응답으로 출력하는 미들웨어를 추가하라는 것입니다. 이제 직접 필요한 미들웨어를 추가해보죠.

..길었다. 이제 다시 실습인가!
응. 실습은 쉬울거야 ^^

미들웨어 추가 실습

정적 파일을 호스팅할 수 있도록 StaticFileExtensions 클래스의 UseStaticFiles 메소드를 사용하면 손쉽게 jpg같은 정적 파일을 호스팅 할 수 있습니다. 우선 API 문서는 다음과 같습니다.

StaticFileExtensions

브라우저에서 jpg 이미지를 볼 수 있게 test1 을 점진적으로 개선해보죠.

1 의존성 처리

우선 StaticFileExtensions 은 네임스페이스 상으로는 Microsoft.AspNetCore.Builder 지만 어셈블리 상으로는 Microsoft.AspNetCore.StaticFiles.dll 입니다(API문서 가장 하단에 소속된 어셈블리가 나옵니다)
의존성 추가는 test1.csproj 의 xml 를 수정해야 합니다. 파일을 열어 ItemGroup항목에 “Microsoft.AspNetCore.StaticFiles” 를 추가하면 다음과 같은 코드가 될 것입니다.

<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>netcoreapp1.1</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <Folder Include="wwwroot\" />
  </ItemGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore" Version="1.1.1" />
    <PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="1.1.1" />
  </ItemGroup>
</Project>

의존성을 추가했으니 다시 dotnet restore 를 실행합니다. 이제 Microsoft.AspNetCore.StaticFiles.dll 이 프로젝트에 추가된 상태가 되었습니다.

2 미들웨어 사용

Startup 의 Configure 에 app.Run 앞쪽에 다음의 코드를 삽입합니다.

app.UseStaticFiles();

전체적인 Configure의 모양은 다음과 같을 것입니다.

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    loggerFactory.AddConsole();
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseStaticFiles(); //여기!

    app.Run(async (context) =>
    {
        await context.Response.WriteAsync("Hello World!");
    });
}

3 이미지 준비, 실행

ASP.NET Core는 wwwroot 라는 폴더를 별도로 생성하여 정적 파일을 관리하기 쉽도록 스케폴딩 되어있습니다. 이제 c:\test1\wwwroot 폴더에 적당한 이미지를 a.jpg 라는 이름으로 넣어둡니다.

이미지도 준비되었으니 dotnet run 을 통해 다시 웹서버를 실행합니다.

마지막으로 브라우저에서 http://localhost:5000/a.jpg 를 입력하여 들어가면 이미지가 브라우저에서 표시될 것입니다.

결론

이번 포스팅에서는 닷넷코어의 기본적인 구조와 간단한 실습을 해봤습니다.

어려운 내용은 모르겠지만 따라하긴 쉽네!
그게 젤 큰 장점인 거 같아.
%d 블로거가 이것을 좋아합니다: