C#

Core Startup 설정

ASP.NET Core WebHost 구성과 실행

ASP.NET Core 프로그램의 시작은 Program.cs 의 Main() 메서드로서 프로젝트 템플릿에서 생성된 코드는 다음과 같다.

using System.IO;
using Microsoft.AspNetCore.Hosting;

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

            host.Run();
        }
    }
}

ASP.NET Core 프로그램을 실행하기 위해서는 WebHostBuilder 클래스를 사용하여 웹 Host를 셋업하고 구동시켜야한다. 웹 프로그램은 IWebHost 을 구현한 Host 안에서 실행되는데, 동일한 웹 프로그램은 여러 Host 안에서 실행될 수 있다. WebHostBuilder는 어떤 Host를 사용할 지, 어떤 Startup 클래스를 사용하지 등과 같은 옵션들을 지정한 후 마지막에 Build() 메서드를 호출하여 IWebHost를 리턴한다.
Microsoft.AspNetCore.Hosting.IWebHost 인스턴스를 얻은 후 Run()을 실행하여 Host를 실행하게 된다.

UseKestrel() 메서드는 Kestrel 웹서버를 사용할 것을 지정하는 것인데, ASP.NET Core 웹 프로그램은 더 이상 IIS에서 호스팅되지 않고, (일반적으로) Kestrel 이라 불리우는 Full Web Server를 사용하여 구동된다. IIS가 Windows 전용 웹 서버인 반면, Kestrel 서버는 Windows, Linux, OSX 에서 사용할 수 있는 Cross Platform 웹 서버이다.

ASP.NET Core 웹 프로그램을 IIS 에서 사용하기 위해서는 UseKestrel()과 UseIISIntegration()를 모두 지정해야 하는데, 여기서 UseIISIntegration()는 IIS 서버가 외부와의 인터넷 통신을 담당한다는 것을 의미한다 (주: VS 2017 에서는 UseIISIntegration 기능이 기본으로 포함되어 있으며, UseIISIntegration() 메서드를 제거하였기 때문에 호출할 필요가 없다) . 실제 웹 사이트를 호스팅하는 것은 Kestrel 서버이고, IIS는 인터넷 HTTP 호출을 받아 백엔드의 Kestrel 서버에 전달하고 다시 결과를 리턴하는 역활을 한다 (이러한 IIS 기능을 Reverse Proxy Server 라 한다).

WebHostBuilder는 웹 호스트를 지정할 뿐만 아니라 다양한 호스트 Configuration을 설정할 수 있는 메서드들을 제공한다. 예를 들어 위의 예제에서 UseContentRoot() 메서드는 Web Root 폴더를 지정하는 것이다. ASP.NET Core 호스트는 호스트에서 사용할 서비스와 Request Pipeline 등을 지정하기 위해 Framework에서 Startup 어셈블리를 호출하는데, 어떤 Startup 어셈블리를 호출해야 하는지를 설정하기 위해 UseStartup() 메서드를 사용한다. 위의 예제에서 UseStartup<Startup>() 메서드는 "Startup" 이라는 클래스를 사용할 것을 지정한 것으로, 프로젝트 템플릿에서 생성한 Startup.cs 파일이 이 클래스를 포함하고 있다. 개발자는 필요에 따라 개발 환경에 따라 다른 Startup 을 지정할 수도 있다.

Startup - Request Pipeline 구성

Startup는 반드시 Configure() 메서드를 가져야 하며, Optional로 ConfigureServices() 메서드를 가질 수 있다. ConfigureServices()와 Configure() 메서드는 모두 웹 프로그램이 구동될 때 호출되는데, 만약 ConfigureServices가 존재한다면, ConfigureServices() 메서드가 Configure() 보다 먼저 호출된다.

Configure() 메서드는 HTTP Request가 들어오면 ASP.NET이 어떻게 반응해야 하는지를 지정하는 것으로 여러 Request Pipeline을 설정하는 역활을 한다. Request Pipeline은 소위 Middleware 들을 계속 추가해 나감으로써 구성하는데, 처음 미들웨어가 실행된 후 다음 미들웨어로 계속 Pipeline 처럼 이동한다. 예를 들어, 아래와 같이 간단한 Pipeline을 구성할 수 있다.

public void Configure(IApplicationBuilder app)
{
    // 미들웨어1
    app.UseExceptionHandler("/Home/Error"); 

    // 미들웨어2
    app.UseStaticFiles();                   

    // 미들웨어3
    app.UseMvcWithDefaultRoute();    
}
위의 예제에서 [미들웨어1]은 어떤 Error가 발생하면 다음 [미들웨어2]로 이동하고, [미들웨어2]에서 해당 Request가 Static 파일이면 이를 처리하고 아니면 다시 [미들웨어3]로 넘어가 MVC 라우팅을 타게 된다. Configure() 메서드에서의 미들웨어 순서가 곧 Request Pipeline의 순서이므로 미들웨어 순서를 바꾸면 다른 결과를 초래할 수 있다. ASP.NET Core는 여러 미들웨어들을 내장하고 있으며, 필요한 경우가 개발자가 자신의 미들웨어를 추가할 수 있다.

Configure() 메서드는 Request Pipeline 설정외에 웹 어플리케이션과 관련한 여러 셋팅을 설정하는데 사용되기도 한다. 예를 들어, 아래 예제는 기본 프로젝트 템플릿으로부터 생성된 Configure() 메서드인데, 메서드 처음부분에 loggerFactory가 어떤 로깅 옵션을 사용는지 지정하고 있다.

public void Configure(IApplicationBuilder app, IHostingEnvironment 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?}");
    });
}
위의 Configure() 메서드의 입력 파라미터를 보면 IApplicationBuilder, IHostingEnvironment 등과 같이 모두 인터페이스들인데, 이들 파라미터는 ASP.NET Core에 내장되어 있는 Dependancy Injection 프레임워크에 의해 처리된다.

Startup - 서비스 구성과 Dependancy Injection

Startup에서 ConfigureServices() 메서드는 웹 프로그램에서 어떤 Framework Service를 사용할 지를 지정하거나 개발자의 Custom Type에 대한 Dependancy Injection을 정의하는 일을 한다.

ConfigureServices 메서드는 IServiceCollection 을 입력파라미터로 갖는데, IServiceCollection 는 여러 Add*() 메서드들을 갖고 있다. 예를 들면, Framework Service로서 MVC 를 사용하기 위해 쓰이는 AddMvc() 메서드, Anti Forgery 기능을 추가하는 AddAntiforgery(), 인증서비스를 위해 쓰이는 AddIdentity() 메서드 등등이 있다. 이러한 다양한 서비드들을 IServiceCollection 컬렉션에 추가함으로써 웹 프로그램에서 그 기능을 사용할 수 있게 된다.
아래 예제는 Framework Service 중 MVC와 Anti Forgery, CORS 기능을 추가한 예이다.

public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services.AddMvc();
    services.AddCors();
    services.AddAntiforgery();   
}

ASP.NET Core는 자체적으로 Dependancy Injection 기능을 내장하고 있다. Dependancy Injection은 어떤 타입의 객체가 필요할 때 Framework이 알아서 해당 타입의 객체를 생성해서 제공해 주는 Framework 서비스이다. 예를 들어, 로깅을 위해 ILoggerFactory라는 인터페이스를 갖는 객체가 필요할 때, 개발자가 자신의 클래스의 생성자에 ILoggerFactory 입력파라미터를 지정하면, Framework은 자동으로 해당 인터페이스에 해당하는 객체를 생성하여 제공해 주게 된다. (주: Dependancy Injection은 보통 Constructor 또는 Property을 통해 Inject 되는데, ASP.NET Core는 Constructor Injection을 사용한다)

개발자가 작성한 Custom Type에 대해 Dependancy Injection을 지정하기 위해서 IServiceCollection의 AddTransient(), AddScoped(), AddSingleton() 등의 메서드를 사용한다. Dependancy Injection 설정은 위 메서드를 통해 보통 인터페이스명과 구체적인 클래스 타입을 지정하는데, 이는 특정 인터페이스가 요청되면 그에 상응하는 클래스 타입을 생성해서 리턴한다는 것을 의미한다.
아래 예제는 IMail 이라는 인터페이스가 요청되면 MyMail 이라는 클래스 객체를 제공받아 사용하는 예이다.

// Startup.cs 에 정의된 Dependancy Injection 설정
public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services.AddMvc();

    // Add application services
    services.AddTransient<IMail, MyMail>();  // DI 설정
}


// MyMail.cs : IMail 인터페이스와 이를 구현한 MyMail 클래스
public interface IMail
{
    void SendMail(string msg);
}

public class MyMail : IMail
{
    public void SendMail(string msg)
    {
        // 생략
    }
}


// Controller에서 사용하는 예
public class UserController : Controller
{
    private IMail _mail;

    public UserController(IMail mail)  // DI 사용
    {
        this._mail = mail;
    }

    public void Notify()
    {
       _mail.SendMail("Notify");            
    }

    // 생략
}
먼저 Startup의 ConfigureServices() 안에 services.AddTransient()를 사용하여 IMail 인터페이스가 요청되면 MyMail 객체를 리턴할 것을 지정하였다. 다음 UserController 컨트롤러의 생성자에서 IMail 인터페이스가 요청하여 Framework이 MyMail 객체를 제공하면 이를 private 필드에 저장하고 필요한 메서드에서 이를 사용하고 있다. 여기서 한가지 주의할 점은 Dependancy Injection을 사용하는 인터페이스와 클래스는 항상 public으로 설정해야 한다는 점이다.

Dependancy Injection (DI)에서 또하나 중요한 옵션으로 객체를 매번 새로 생성하느냐, 이미 있는 객체를 제공하느냐를 결정하는 것이 있는데, DI는 이를 통해 자신의 내부 컨테이너 안의 객체들에 대한 Lifetime을 관리하게 된다.
Dependancy Injection를 설정하기 위해 사용되는 AddTransient() 메서드는 매번 인터페이스가 요청될 때마다 새로운 객체를 생성해서 돌려준다. AddScoped() 메서드는 하나의 HTTP Request 당 하나의 객체를 사용한다. 즉 Web Request가 하나 들어왔을 때, 여러 클래스에서 동일한 인터페이스를 요청하더라도 이미 생성된 동일한 객체를 돌려준다. AddSingleton() 메서드는 웹 프로그램 전체를 통해 오직 하나의 객체만을 사용한다. 즉, 처음 그 인터페이스가 요청되면 새로 생성하고, 나머지 모든 요청에 대해서는 항상 이미 생성된 객체를 리턴한다.
개발자는 해당 객체를 매번 생성할지, 하나만 생성할 지 등을 결정해서 다른 메서드를 호출해야 한다.

public void ConfigureServices(IServiceCollection services)
{
    services.AddTransient<IMail, MyMail>();
    //services.AddScoped<IMail, MyMail>();
    //services.AddSingleton<IMail, MyMail>();
}

ASP.NET Core는 Dependancy Injection과 더불어 Service Container 서비스를 제공하고 있다. 웹 프로젝트 내의 클래스에서 만약 HttpContext 객체를 엑세스할 수 있다면, HttpContext.RequestServices.GetService(요청타입) 메서드를 호출하여 요청하는 타입의 객체를 얻어 올 수 있다. 예를 들어, 아래는 IMail 인터페이스를 요청하여 MyMail 객체를 얻어오는 예이다.

// Controller 안의 메서드

[HttpGet("api/user/send")]
public IActionResult Send(string msg)
{
    var mail = (IMail)HttpContext.RequestServices.GetService(typeof(IMail));
    mail.SendMail(msg);

    return Ok();
}

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

Previous Next Print