Framework/.NET

[ASP.NET Core로 Web API 만들기] 1. ASP.NET Core에 대해

kkumta 2023. 4. 22. 00:01

이 게시물은 .NET 7에 관해 설명합니다. 다른 버전을 사용할 경우 게시글의 설명과 다를 수 있습니다.

이 글은 마이크로소프트 설명서를 참고하여 작성되었습니다.

목차

1. ASP.NET Core의 구성

2. 의존성 주입(DI)

3. 서비스의 생명 주기

4. 미들웨어

5. Attribute


1. ASP.NET Core의 구성

Program.cs

  • 애플리케이션의 진입점입니다.
  • 앱에서 요구하는 서비스가 구성됩니다.
  • 앱의 요청 처리 파이프라인이 미들웨어 구성 요소로 정의됩니다.

 

WebApplication.CreateBuilder

var builder = WebApplication.CreateBuilder(args);
  • ASP.NET Core 웹 앱은 위 코드를 생성합니다.
  • 이 코드에서 WebApplication.CreateBuilder는 미리 구성된 기본값을 사용하여 클래스의 새 인스턴스를 초기화합니다.
  • 초기화된 WebApplicationBuilder는 우선순위 순서대로 앱의 기본 구성을 제공합니다. 그 우선순위는 다음과 같습니다.
    1. 명령줄 인수
    2. 접두사 없는 환경 변수
    3. 사용자 비밀
      - 앱이 환경에서 실행되는 경우 해당됩니다. 비밀 정보를 프로젝트와 함께 배포하지 않고, 프로젝트 내부의 환경 변수를 통해 따로 비밀 정보에 접근하는 경우입니다. 
    4. JSON 구성 공급자를 사용하는 appsettings.{Environment}.json
      - appsettings.Production.json, appsettings.Development.json 등의 예가 있습니다.
    5. JSON 구성 공급자를 사용하는 appsettings.json

 

라우팅

  • 들어오는 HTTP 요청을 일치시켜 앱의 실행 가능 엔드포인트로 디스패치하는 역할을 담당합니다.
    • 엔드포인트는 앱의 실행 가능 요청 처리 코드 단위입니다. 엔드포인트는 앱에서 정의되고, 앱 시작 시 구성됩니다. 엔드포인트 일치 프로세스는 요청의 URL에서 값을 추출하고, 요청 처리를 위해 이 값을 제공할 수 있습니다.
  • 앱의 엔드포인트 정보를 사용하여 엔드포인트에 매핑되는 URL을 생성할 수 있습니다.
  • Controllers, 상태 검사와 같은 엔드포인트 지원 미들웨어, 라우팅에 등록된 대리자 및 람다 등을 사용하여 구성할 수 있습니다.
  • WebApplicationBuilder는 UseRouting 및 UseEndpoints를 통해 Program.cs에 추가된 미들웨어를 래핑하는 미들웨어 파이프라인을 구성하나, 앱은 UseRouting 및 UseEndpoints 메서드를 명시적으로 호출하여 실행 순서를 변경할 수 있습니다.
  • 앱에서 일치시키고 실행할 수 있는 엔드포인트는 UseEndpoints에서 구성됩니다.

 

ConfigureLogging

참고: https://github.com/Cysharp/ZLogger

로깅을 설정하기 위해서는 Program.cs에 로깅을 추가하는 코드를 작성해야 합니다. Zlogger로 콘솔 로그를 출력하는 설정을 하기 위해서는 다음과 같은 코드를 추가하면 됩니다.

Host.CreateDefaultBuilder()
    .ConfigureLogging(logging =>
    {
        logging.ClearProviders();

        logging.AddZLoggerConsole(options =>
        {
            options.EnableStructuredLogging = true;
        });
    });

 

더 자세한 설정 방법은 https://github.com/Cysharp/ZLogger를 참고하세요. 다양한 형식의 로그를 출력할 수 있습니다.

 

appsettings.json

{
  "AllowedHosts": "*",
  "ServerAddress": "http://0.0.0.0:포트 번호",
  "logdir": "./log/", 
  "DbConfig": {
    "Redis": "127.0.0.1",
    "MySQL": "Server=127.0.0.1;user=root;",
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  }  
}
  • 애플리케이션 설정 파일입니다.
  • JsonConfigurationProvider에서 로드합니다.
  • appsettings. {Environment}. json값이 appsettings.json 값을 재정의합니다. 개발 환경에서는 appsettings.Development.json, 프로덕션 환경에서는 appsettings.Production.json의 값을 덮어씁니다.

 


2. 의존성 주입(DI)

참고: https://ko.wikipedia.org/wiki/%EC%9D%98%EC%A1%B4%EC%84%B1_%EC%A3%BC%EC%9E%85

DI를 통해 다른 코드의 변경 없이 Program.cs의 변경만으로 구체적인 구현 형식을 변경할 수 있습니다. 이는 클래스 간 느슨한 결합도를 바탕으로 코드 재사용성 향상에 큰 도움이 됩니다.

쉽게 비유해 보자. 데스노트라는 뮤지컬이 있고, 이 뮤지컬에는 엘과 라이토가 등장합니다. 그리고 각 역할당 2명의 배우가 돌아가며 공연을 합니다. 엘 역할을 맡은 엘 A, 엘 B 배우와 라이토 역할을 맡은 라이토 A, 라이토 B 배우가 있습니다. 엘을 연기하는 두 배우는 라이토가 어떤 배우이든 관계없이 라이토를 연기할 수 있는 배우이기만 하면 공연을 진행할 수 있습니다. 라이토를 연기하는 두 배우도 마찬가지입니다. 뮤지컬 감독은 어떤 배우를 어떤 일정에 무대에 세울지 변경할 수 있습니다.
DI 세계에서 각 역할은 다음과 같습니다.
엘과 라이토: 인터페이스
엘 A, 엘 B, 라이토 A, 라이토 B: 서비스
뮤지컬 감독: 주입자

.NET Core API에서의 의존성 주입은 Program.cs 파일에서 이루어집니다.

참고: ASP.NET Core에서 종속성 주입

 

Program.cs 파일에 다음과 같은 코드를 작성하는 것으로 의존성 주입을 할 수 있습니다.

builder.Services.AddControllers();
services.AddSingleton<IMyDependency, MyDependency>();

3. 서비스의 생명 주기

참고: https://holjjack.tistory.com/71

Transient

하나의 요청 안에서도 인스턴스가 여러 번 생성됩니다. 상태를 저장하지 않는 인스턴스에 적합합니다.

 

Scoped

하나의 요청 안에서는 인스턴스가 한 번만 생성됩니다. 요청 내에서 상태를 유지하려는 경우 사용합니다.

 

 

Singleton

한 번의 요청으로 인스턴스가 생성되면 앱이 종료될 때까지 유지됩니다. 즉, 해당 서비스를 사용하는 후속 요청은 모두 같은 인스턴스를 재사용합니다. 

 

 

 

서비스의 생명 주기는 Program.cs에서 다음과 같이 설정합니다.

builder.Services.AddTransient<IMyDependency, MyDependency1>();
builder.Services.AddScoped<IMyDependency, MyDependency2>();
builder.Services.AddSingleton<IMyDependency, MyDependency3>();

 


4. 미들웨어

참고: ASP.NET Core 미들웨어

  • 요청 및 응답을 처리하는 앱 파이프라인으로 조립되는 소프트웨어입니다.
  • 미들웨어에서는 요청을 파이프라인의 다음 구성 요소로 전달할지 여부를 선택합니다. 이 작업은 파이프라인의 다음 구성 요소 전과 후에 수행할 수 있습니다.
  • 요청 파이프라인을 빌드하는 데 요청 대리자가 사용됩니다.
    • 요청 대리자는 각 HTTP 요청을 처리합니다.
    • 요청 대리자는 Run, Map, Use 확장 메서드를 사용하여 구성됩니다.
    • 개별 요청 대리자는 무명 메서드로 인라인에서 지정되거나, 재사용 가능한 클래스에서 정의될 수 있습니다. 이러한 개별 요청 대리자를 미들웨어라고 하며, 미들웨어 구성 요소라고 부르기도 합니다.
    • 각 미들웨어 구성 요소는 파이프라인의 그다음 구성 요소를 호출하거나 파이프라인을 Short-circuiting하는 역할을 담당합니다. 미들웨어가 Short-circuiting되는 경우 미들웨어에서 더는 요청을 처리하지 못하기 때문에 이를 터미널 미들웨어라고 합니다.

 

미들웨어 작동 순서

미들웨어 작동 순서도

  • 미들웨어 구성 요소는 Program.cs에 추가하여 사용합니다.
  • 미들웨어 작동 순서는 Program.cs에 추가한 순서와 요청에서 호출되는 순서가 같고, 응답에서 호출되는 순서는 그 역순입니다.
  • 미들웨어 작동 순서는 보안, 성능 및 기능에 중요합니다. 또한 특정 미들웨어는 정해진 작동 순서를 지켜야 할 수도 있습니다. 

 

다음은 Program.cs에 미들웨어를 추가한 코드입니다.

if (env.IsDevelopment()) // 앱이 개발 환경에서 실행되는 경우
{
    app.UseDeveloperExceptionPage(); // 개발자 예외 페이지 미들웨어가 앱 런타임 오류를 보고합니다
    app.UseDatabaseErrorPage(); // 데이터베이스 런타임 오류를 보고합니다
}
else // 앱이 프로덕션 환경에서 실행되는 경우
{
    app.UseExceptionHandler("/Error"); // 다음에 실행되는 미들웨어에서 던져진 예외를 잡습니다
    app.UseHsts(); // Strict-Transport-Security 헤더를 추가합니다
}
app.UseHttpsRedirection(); // HTTP 요청을 HTTPS로 리다이렉션합니다
app.UseStaticFiles(); // 정적 파일을 반환하고 추가 요청 처리를 단락합니다
app.UseCookiePolicy(); // EU GDPR(일반 데이터 보호 규정)을 준수하게 만듭니다
app.UseRouting(); // 미들웨어가 요청을 라우팅합니다
app.UseAuthentication(); // 보안 리소스에 대한 접근이 허용되기 전에 사용자 인증을 시도합니다
app.UseAuthorization(); // 사용자에게 보안 리소스에 접근할 수 있는 권한을 부여합니다
app.UseSession(); // 세션 상태를 설정 및 유지합니다

 


5. Attribute

참고: 특성을 사용하여 메타데이터 확장

  • 특성은 assembly, module, class, struct, enum, constructor, method, property, field, event, interface, parameter, delegate, returnvalue 등에 사용할 수 있습니다.
  • 특성을 사용하여 런타임 리플렉션 서비스를 통해 추출할 수 있는 메타데이터에 추가 설명 정보를 배치할 수 있습니다.
  • System.Attribute에서 파생되는 특수 클래스 인스턴스를 선언하면 컴파일러에서 특성을 만듭니다.
  • 특성은 코드를 컴파일할 때 메타데이터로 내보내지며, 공용 언어 런타임과 사용자 지정 도구 또는 애플리케이션에서 런타임 리플렉션 서비스를 통해 사용할 수 있습니다.
  • 모든 특성 이름은 규칙에 따라 'Attribute'로 끝나지만, 런타임을 목적으로 하는 C#과 같은 언어에서는 특성의 전체 이름을 지정할 필요가 없습니다. 예를 들어, System.ObsoleteAttribute를 초기화하려는 경우 Obsolete로만 참조해야 합니다.

 

특성 적용 프로세스

참고: https://learn.microsoft.com/ko-kr/dotnet/standard/attributes/applying-attributes

  1. 새 특성을 정의하거나, 기존에 있는 .NET 특성을 사용합니다.
  2. 코드 요소 바로 앞에 특성을 배치하여, 해당 요소에 맞는 특성을 적용합니다. C#의 경우, 특성은 대괄호('[', ']')로 묶고, 공백으로 요소와 구분합니다. 또한, 줄 바꿈 문자를 사용할 수도 있습니다.
  3. 특성에 대한 위치 매개 변수와 명명된 매개 변수를 지정합니다.

 

자주 쓰이는 특성

참고: https://learn.microsoft.com/ko-kr/dotnet/csharp/advanced-topics/reflection-and-attributes/

https://www.geeksforgeeks.org/attributes-in-c-sharp/

 

Obsolete Attribute

참고: ObsoleteAttribute 클래스

public class Example
{
   [ObsoleteAttribute("더 이상 사용하지 않는 프로그램 요소입니다.", true)]
   public void CallOldMethod()
   {
      // 메서드 내용
   }
}

더 이상 사용하지 않는 프로그램 요소를 표시하는 특성입니다.

 

ConditionalAttribute

참고: ConditionalAttribute 클래스

#define DEBUG  

public class Example
{
    [Conditional("DEBUG")]
    public static void Method1(int x)
    {
        Console.WriteLine("DEBUG is defined");
    }
}
  • 메서드 및 클래스에 ConditionalAttribute 특성을 적용할 수 있습니다.
  • 지정된 조건부 컴파일 기호가 정의되어 있지 않으면 메서드 호출 또는 특성이 무시되어야 함을 컴파일러에 알립니다.
  • void를 반환하지 않는 메서드에 이 특성을 적용하면 Visual Studio 컴파일 오류가 발생합니다.

 

DllImport Attribute

[System.Runtime.InteropServices.DllImport("user32.dll")]
extern static void SampleMethod();

이 속성은 메서드가 관리되지 않는 DLL에 표시된 정적 진입점임을 나타냅니다.

 

ApiControllerAttribute

참고: ApiControllerAttribute 클래스

[ApiController]
public class MyControllerBase : ControllerBase
{
}

ApiController 특성을 컨트롤러 클래스에 적용하여 다음과 같은 API 관련 동작을 사용할 수 있습니다.

  • 특성 라우팅 요구 사항
  • 자동 HTTP 400 응답
  • 바인딩 소스 매개 변수 유추
  • 다중 파트/폼 데이터 요청 유추
  • 오류 상태 코드에 대한 문제 세부 정보

 

RouteAttribute

참고: ASP.NET Web API 2의 특성 라우팅

 

특성 라우팅은 규칙 기반 라우팅에 비해 Restful API에서 공통적인 특정 URI 패턴을 지원하기 쉽습니다. 이러한 유형의 URI의 예로는 '/customers/1/orders'가 있습니다.

 

특성 라우팅을 사용한 예제 코드는 아래와 같습니다.

[Route("customers/{customerId}/orders")]
public IEnumerable<Order> GetOrdersByCustomer(int customerId) {}

 

경로 접두사

참고: ASP.NET Web API 2의 특성 라우팅

[RoutePrefix("api/books")]
public class BooksController : ApiController
{
    // GET api/books
    [Route("")]
    public IEnumerable<Book> Get() { }

    // GET api/books/5
    [Route("{id:int}")]
    public Book Get(int id) { }

    // POST api/books
    [Route("")]
    public HttpResponseMessage Post(Book book) { }
}
  • 컨트롤러의 경로가 모두 동일한 접두사로 시작할 경우에 사용합니다.
  • [RoutePrefix] 특성을 사용합니다.

 

아래는 경로 접두사를 재정의한 코드 예시입니다.

[RoutePrefix("api/books")]
public class BooksController : ApiController
{
    // GET /api/authors/1/books
    [Route("~/api/authors/{authorId:int}/books")]
    public IEnumerable<Book> GetByAuthor(int authorId) { }
}

 

HTTPAttribute

  • Web API는 요청의 HTTP 메서드에 따라 작업을 선택합니다.
  • 기본적으로 요청의 HTTP 메서드 이름의 시작 부분과 일치하는 항목을 찾습니다. 이때, 대/소문자는 구분하지 않습니다. 즉, Post 메서드 요청과 PostUser 컨트롤러 메서드는 일치한다고 볼 수 있습니다.
  • 아래의 특성을 사용하면 이 규칙을 재정의할 수 있습니다.
    • [HttpPost]
    • [HttpGet]
    • [HttpPut]
    • [HttpPatch]
    • [HttpDelete]
    • [HttpHead]
    • [HttpOptions]

 

다음은 HTTPAttribute를 사용한 예시 코드입니다.

[Route("api/users")]
[HttpPost]
public HttpResponseMessage CreateUser(User user) { }

[Route("api/users/{id:long}")]
[HttpDelete]
public HttpResponseMessage RemoveUser(long id) { }

 

RequireAttribute

참고: RequiredAttribute 생성자

[Required()]
public object MiddleName;

위 코드의 경우, MiddleName이 null이면 유효성 검사 오류 메시지가 표시됩니다.

 

사용자 정의 Attribute

참고: 사용자 지정 특성 작성

public class MyClass
{
    [MyAttribute]
    public void MyMethod()
    {
        // 메서드 내용
    }
}
  • 사용자 지정 특성은 System.Attribute를 상속받은 클래스입니다.
  • 특성 적용 시 대괄호로 특성 클래스를 인스턴스화합니다.