Skip to content

Instantly share code, notes, and snippets.

@jacking75
Created April 24, 2023 14:47
Show Gist options
  • Save jacking75/344fd3c8c16fc27fe51d4c983a3a6306 to your computer and use it in GitHub Desktop.
Save jacking75/344fd3c8c16fc27fe51d4c983a3a6306 to your computer and use it in GitHub Desktop.
컴투스 서버 캠퍼스 1기 과제1 ASP.NET Core 학습 정리 - 한성재

REST

  • 의미: 자원을 이름으로 그분하여 해당 자원의 상태를 주고받는 모든 것. -> 자원의 표현에 의한 상태 전달
  • REST API URL 네이밍 규칙
    1. 동사보다 명사사용
    2. 대문자보다 소문자 사용
    3. 마지막에 '/' 사용 X
    4. '_' 대신 '-' 사용
    5. 파일 확장자 포함 X
    6. 복수형 사용

HTTP

  • 인터넷에서 데이터를 주고 받을 수 있는 프로토콜

    HTTPS

    • HTTP의 암호화된 버전
    • 443번 포트를 사용하며 암호화(대칭/비대칭 키 암호화)로 보안이 강화됨
    • 대칭 키 암호화
      • 클라이언트와 서버가 동일한 키를 사용해 암호화/복호화함
      • 키가 노출되면 보안에 위험
      • 대신 속도가 빠름
    • 비대칭 키 암호화
      • 1쌍의 public키와 private키로 구성
      • 암호화/복호화를 위해 CPU를 사용해야 하기 때문에 연산속도가 비교적 느림
      • public키가 노출이 되어도 private키로만 복호화할 수 있기 때문에 안전함
    • 대칭키와 비대칭키 암호화 모두 사용하여 연산속도 안정성이 높음
  • 서버-클라이언트 구조

    • 클라이언트가 서버에 요청을 보내고 서버가 요청의 대한 결과를 만들어 응답
    • 서버에서의 데이터를 클라이언트가 독립적으로 처리가 가능
  • stateless(무상태성)

    • 서버에서 클라이언트의 상태를 보존하지 않음
    • 같은 클라이언트에 대해 항상 같은 서버가 응답할 필요가 없음
    • Scale-up(기존 서버를 높은 사양으로 업그레이드)에 유리
    • 요청을 보낼 때 매번 새로운 연결 시도/해제를 위한 추가 데이터를 전송해야함
  • 비연결성

    • 요청 후 응답을 받으면 바로 연결 종료
    • 불필요한 연결을 하지 않아 서버 유지 자원을 줄임

HTTP 통신방식

GET

  • 클라이언트가 서버에게 데이터를 요청하기 위해 사용(idempotent)
  • URL끝에 ?를 붙여 끝임을 표시하고 그 뒤에 쿼리 스트링을 붙힘
  • 쿼리는 key-value쌍으로 입력
  • URL에 쿼리를 포함해서 전송하기 때문에 body는 비어있음
  • URL에 쿼리가 노출되기 때문에 중요한 데이터를 전송할 때 사용X
  • 캐싱이 가능해서 상대적으로 전송 속도가 빠름
  • 하지만 글자 수 제한 때문에 간단한 데이터 요청으로만 사용

POST

  • 클라이언트가 서버로 데이터를 전송하여 리소스 추가/생성(non-idempotent)
  • body의 타입을 설명하는 Content-Type 해더필드가 들어감
  • body에 데이터를 넣어서 전송함
  • 데이터 양에 제한이 없어 대용량 데이터를 보내는데 적합
  • 데이터가 URL에 표시되지 않아 GET보다 보안성이 좋지만 클라이언트에서 표시되기때문에 암호화작업 필요

JSON

  • 인간이 읽을 수 있는 텍스트를 사용하여 데이터 객체를 전달하기 위한 개방형 표준 포맷

구조

  • 데이터는 key-value 쌍으로 구성 ex) {"name": "Jack", "age": 30}
  • key는 문자열, value는 다양한 타입(string/number/bool/object/array/NULL)
  • 각 데이터는 ','로 구분
  • 하나의 객체는 중괄호로 구분, key-value의 쌍들로 구성
  • 배열은 대괄호로 구분, 여러 객체들로 구성

특징

장점

  • 내용이 함축적으로 최소한의 정보만을 가지고 있음 -> XML에 비해 구조가 단순하여 용량이 줄어들고 속도가 빠름
  • 언어에 독립적이기 때문에 서로 다른 시스템 간에 객체 교환이 가능
  • 구조정의의 용이성과 가독성이 뛰어남

단점

  • XML처럼 스키마를 이용한 검증이 불가능
  • 주석처리 불가능

ASP.NET Core

특징

  • ASP.NET의 오픈소스 버전
  • 크로스 플랫폼을 지원하기 위한 Web 프레임워크
  • C#을 이용하여 개발 가능
  • Windows, Linux, Mac os등에서 동작 가능

기능

program.cs

  • ASP.NET Core 6버전에는 main 사용구가 없음

  • startup 클래스가 없어지고 하나의 파일에 모든 코드 작성

  • 앱에서 요구하는 서비스를 구성

  • 앱의 요청 처리 파이프라인이 일련의 미들웨어 구성 요소로 정의

  • 6버전에서의 호스트는 주로 .NET WebApplication 호스트 사용

Create Builder

var builder = WebApplication.CreateBuilder(args); -> 6.0버전부터 사용 가능 -> WebApplication.CreateBuilder는 미리 구성된 기본값을 사용해 클래스의 새 인스턴스를 초기화 -> 초기화 된 builder는 가장 높은 우선 순위 부터 가장 낮은 우선 순위까지 다음 순서로 앱의 기본 구성을 제공

  1. 명령줄 구성 공급자를 사용하는 명령줄 인수
  2. 접두사 없는 환경 변수 구성 공급자를 사용하는 접두사 없는 환경 변수
  3. 사용자 비밀 - 앱이 환경에서 실행되는 경우
  4. JSON 구성 공급자를 사용하는 appsettings.{Environment}.json. 예를 들어 appsettings.Production.json 및 appsettings.Development.json를 지정
  5. JSON 구성 공급자를 사용하는 appsettings.json.
  6. 기본 호스트 구성에 대한 대체(참고: https://learn.microsoft.com/ko-kr/aspnet/core/fundamentals/configuration/?view=aspnetcore-7.0#host)

WebApplicationBuilder

  • ASP.NET Core앱은 시작 시 호스트를 빌드

  • 호스트의 리소스 캡슐화 목록

    • HTTP서버 구현
    • 미들웨어 구성 요소
    • 로깅
    • DI 서비스
    • configureation
  • WebApplicationBuilder.Build 메서드의 호스트 구성

    • Kestrel을 웹서버로 사용하고 IIS통합을 사용하도록 설정
    • appsettings.json, 환경 변수, 명령줄 인수, 기타 구성 소스에서 구성을 로드
    • 콘솔 및 디버그 공급자에게 로깅 출력을 보냄

라우팅

  • 들어오는 HTTP요청을 일치시켜 앱의 실행 가능한 엔드포인트로 연결하는 것

  • 관련 메서드

    • MapGet

      • MapGet메서드를 사용하여 엔드포인트를 정의
    • UseRouting

      • 경로 일치를 미들웨어 파이프라인에 추가 -> 해당 미들웨어는 앱에 정의된 엔드포인트 집합을 확인 후 요청을 기반으로 가장 적합한 항목 선택
    • UseEndpoints

      • 엔드포인트 실행을 미들웨어 파이프라인에 추가 -> 선택한 엔드포인트와 연결된 대리자를 실행
    • 일반적으로는 UseRouting과 UseEndpoints를 호출할 필요가 없으나 호출하여 실행순서를 변경할 수 있음.

(Ex)

app.Use(async (context, next) =>   // 사용자 지정 미들웨어 등록
{
    // ...
    await next(context);
});

app.UseRouting();                   // 일치 미들웨어 구성

app.MapGet("/", () => "Hello World!");  // 여기서 등록된 엔드포인트는 파이프라인 끝에 실행
                                        // Get요청이 루트 url "/"로 전송되면 
                                        // 요청 대리자가 실행되고
                                        // Hello World!가 HTTP응답에 기록됨

-> 임의대로 호출하여 사용자 지정 미들웨어를 등록후 일치 미들웨어를 구성함. UseRouting을 호출하지 않았다면 사용자 지정 미들웨어는 일치 미들웨어 이후 실행

로깅

  • logging = 시스템 동작 시 시스템 상태/작동 정보 등을 시간에 따라 기록하는 것
  • ClearProviders를 호출하여 원하는 로깅 공급자를 추가
  • GetSection을 사용하여 appsettings.json파일에 있는 Logging 설정을 가져올 수 있음

로깅 구성

  • appsettings.{ENVIRONMENT}.json 파일에서 제공 (로그 설정 예시)
{
  "Logging": {
    "LogLevel": {               
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  }
}
  • LogLevel
    • 로그의 심각도를 나타내는 속성
    • Trace = 0, Debug = 1, Information = 2, Warning = 3, Error = 4, Critical =5, None = 6

appsettings.json

  • 프로젝트 환경에 따라 appsettings.json파일에 정의된 설정값을 앱에 제공
  • 코드내의 설정 값들이 appsettings.json파일에 의해 결정되는 것
  • GetSection을 이용해 값들을 가져올 수 있음
{
   "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "MyConfig": {
    "Host": "localhost",
    "Port": 4500
  }
}

-> MyConfig의 Port값을 가져오려면 Configuration.GetSection("MyConfig")["Port"];

launchsetting.json

  • 시스템 환경에 설정된 값을 재정의
  • 프로젝트가 구동하도록 설정하는 것과 관련하여 어떤 환경변수가 사용되어야 하는지를 포함한 모든 프로파일 설정을 가지고 있는 파일
  • 특징
    • 로컬 개발 머신에서만 사용
    • 배포되서는 안됨
    • 프로필 설정을 포함한다.

미들웨어

  • 정의: 요청 및 응답을 처리하는 앱 파이프라인으로 조립되는 소프트웨어
  • 구성요소
    • 파이프라인의 다음 구성요소로 요청을 전달할지 여부를 선택
    • 파이프라인에서 다음 구성 요소 전후에 작업을 수행

요청 대리자(Request Delegates)

  • 요청 파이프라인을 빌드하는데 사용되며 각 HTTP요청을 처리함
  • 요청 대리자는 Run, Map, Use확장 메서드를 사용하여 구성
    • Run: 앱의 요청 파이프라인에 터미널 미들웨어 대리자 추가
    • Map: 지정된 요청 경로의 일치 항목에 따라 요청 파이프라인을 분기함
    • Use: 인라인으로 정의된 미들웨어 대리자를 앱의 요청 파이프라인에 추가
  • 개별 요청 대리자는 무명 메서드로 인라인에서 지정되거나 다시 사용할 수 있는 클래스에서 정의될 수 있음.

미들웨어 순서

  • 사용자는 원하는 대로 기존 미들웨어 순서를 바꾸거나 시나리오의 필요에 따라 사용자 지정 미들웨어를 주입 가능
  1. UseExceptionHandler
    • 예외를 잡고, 기록하고, 대체 파이프라인에서 요청을 다시 실행함
  2. UseHsts
    • Strict-Transport-Security헤더를 추가
    • 신뢰할 수 없거나 잘못된 인증서를 사용하지 못하도록 차단한다.
  3. UseHttpRedirection
    • HTTP요청을 HTTPS로 리디렉션
  4. UseStaticFiles
    • 정적파일을 반환하고 추가 요청 처리를 단락
  5. UseRouting
    • 들어오는 HTTP 요청을 일치시켜 앱의 실행 가능 엔드포인트로 디스패치
    • 호출하지 않으면 라우팅 미들웨어는 파이프라인의 시작 부분에서 실행됨
  6. UseAuthentication
    • 보안 리소스에 대한 액세스가 허용되기 전에 사용자 인증을 시도
  7. UseAuthorization
    • 사용자에게 보안 리소스에 액세스할 수 있는 권한 부여
  8. UseEndpoints
    • 요청 파이프라인에 엔드포인트를 추가
  • 요청 시에는 Program.cs파일에 추가되는 순서대로 호출
  • 응답 시에는 역순으로 호출 됨

DI(의존성 주입)

  • 의존성 역전 원칙을 기반으로 느슨하게 결합된 모듈을 이용해서 응용 프로그램을 구성하는 기법
    • 의존성 역전 원칙: 상위모듈은 변화하기 쉬운 하위모듈에 의존해서는 안되며 둘 다 추상화에 의존해야 함
  • A라는 클래스를 만들 때, A가 의존하는 클래스들을 A로 생성하는 게 아닌 그 클래스들에 A를 주입한다는 개념
  • DI는 느슨한 결합과 높은 응집을 가능하게 만들어 줌
class CurrentOperation(ABC):
	def calculate(self, num1, num2):
		pass

class Add(CurrentOperation):
	def calculate(self, num1, num2):
		ret = num1 + num2
		print(f'{num1} + {num2} = {ret}')
		return ret

class Sub(CurrentOperation):
	def calculate(self, num1, num2):
		ret = num1 - num2
		print(f'{num1} - {num2} = {ret}')
		return ret

class Calculator:
	def __init__ (self, operation: CurrentOperation):
		self.operation = operation
	
	def start(self, num1, num2):
		self.operation.calculate(num1, num2)

if __name__ == '__main__':
	operation = Add()           // 이부분을 수정하면 쉽게 기능을 변경 가능
	cal = Calculator(operation)
	cal.start(1, 2)

-> Calculator클래스는 하위 클래스들과 결합도가 약해서 쉽게 기능을 수정 할 수 있다.

생명주기

  1. AddTransient
    • 각각의 요청, 서비스마다 각각 새로운 객체를 생성하는 의존성 주입
    • 각각 마다 다른 GUID(전역으로 사용되는 고유 식별자)를 부여받음
  2. AddScope
    • 클라이언트가 요청(연결)할 때 생성
    • AddTransient와는 다르게 다른 서비스에도 같은 GUID, 객체가 사용되지만 새로운 요청이 들어오면 갱신됨
  3. AddSingleton
    • 처음 요청할 때 생성되며 프로그램이 종료하는 순간까지 변하지 않음
    • 객체가 다르거나 새로운 요청이 들어와도 GUID가 동일함

Async와 Await

  • 비동기 프로그래밍을 위한 키워드
  • 비동기 메서드의 반환형
    • void: 비동기 메서드가 이벤트 핸들러로 사용될 때
    • Task: 비동기 메서드가 반환하는 데이터가 없을 때
    • Task: 비동기 메서드가 반환하는 데이터가 있을 때 T에 명시

Async

  • 해당 메서드에 Await키워드를 1개 이상 사용할 수 있게 함
  • 반드시 void 또는 Task 또는 Task를 반환해야함(대부분 Task/Task를 반환함)
  • void를 사용하면 비동기 메서드를 호출하는 쪽에서 비동기 제어 불가능 -> void는 비동기 이벤트 핸들러로 사용할 때 사용

Await

  • 비동기 작업의 흐름을 제어하는 키워드
  • Await를 이용해 비동기 작업의 순서를 정해줄 수 있음
  • 메서드 실행 도중 Await를 만나면 호출 메서드를 일시 중단하고 대기 작업이 끝날 때까지 작업 흐름을 호출자에게 넘김
  • Await키워드는 호출자의 스레드내에서 수행되며 별도의 스레드를 생성하지 않음
  • Async 키워드로 수식되지 않은 메서드는 Await키워드를 사용할 수 없음

(ex)

using System;
using System.Threading.Tasks;

namespace AsyncTest
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("main start");

            MyAsync1();
            MyAsync2();

            Console.WriteLine("main end");

            Console.ReadLine();
        }

        async static private void MyAsync1()
        {
            Console.WriteLine("async1 start");

            await Task.Delay(5000);

            Console.WriteLine("async1 end");
        }

        async static private void MyAsync2()
        {
            Console.WriteLine("async2 start");

            await Task.Delay(5000);

            Console.WriteLine("async2 end");
        }
    }
}
  • 결과 main start async1 start async2 start main end async1 end async2 end
  1. Main 메서드 실행
  2. 비동기 메서드인 MyAsync1호출
  3. MyAsync1메서드에서 "async1 start"출력 후 await Task.Delay(5000)을 만나 MyMethodAsync를 호출한 호출자(main)에게 작업의 흐름이 넘어감
  4. 호출자는 MyAsync2호출
  5. 3번째 줄과 똑같이 "async2 start"출력 후 호출자에게 작업의 흐름이 넘어감
  6. 호출자가 "main end"를 출력하여 작업을 마침
  7. MyAsync1와 MyAsync2 각각의 Task.Delay(5000)실행 후 "async1 end"와 "async2 end"출력 (이 때 출력 순서는 실행마다 달라졌음)

Attribute

  • 코드에 대한 부가정보(주석과 다르게 컴퓨터도 읽을 수 있음)
  • 클래스에 메타데이터를 추가할 수 있도록 제공되는 것
  • 클래스, 메소드, 구조체, 생성자, 프로퍼티, 필드, 이벤트, 인터페이스 등 여러가지 요소에 사용 가능
  • 대괄호에 둘러싸서 사용 -> [attribute명(positional_parameter, name_parameter = value, ...)]

종류

  1. Conditional
  • 사용자가 설정한 조건에 맞춰 해당 메소드를 실행할지 말지 결정

  • 사용법

[Conditional("DEBUG")]
public void Test() => Console.WriteLine("test");
-> Debug모드일 때만 Test함수가 실행 되도록 함
  1. Obsolete
  • 사용하지 않는 클래스, 함수, 필드를 실행되지 않도록 함 -> 코드를 없애기 보다는 남겨놓고 쓰지 않도록 하기 위함 -> 코드를 그냥 없앰으로써 생기는 문제를 방지

  • 사용법

[Obsolete]  // 아래의 클래스, 메소드, 필드가 더이상 사용되지 않는다는 경고 메시지가 뜸
            // 경고만 뜨고 실행은 정상적

[Obsolete("New Method를 사용하시오")]  // 위와 똑같이 작동
                                      // 인자로 들어간 string이 경고 메시지에 부가 정보로 뜸

[Obsolete("New Method를 사용하시오", true)] // 두번째 인자로 true를 넣으면
                                           // 경고만 하는 것이 아닌 컴파일 에러를 내면서 실행 자체를 막음
  1. Dll Import
  • 관리되지 않는(.net 환경 밖에서 개발된 코드)DLL을 사용할 수 있게 함
  • 사용법
using System;
using System.Runtime.InteropServices;

class DLLIport{
    [DllImport("User32.Dll")]   // 인자로 사용할 DLL파일을 넣는다

    public static extern int MessageBox(int h, string m, string c, int type);
                                // User32.Dll에 있는 MessageBox함수를 사용하는 것이기 때문에
                                // extern키워드로 외부 메소드임을 알려준다
    static void Main()
    {
        MessageBox(0, "Hello!", "In C#", 0);
    }
}
  1. 사용자 정의 Attribute
  • System.Attribute 클래스를 상속받아 만든 클래스로 사용자가 임의대로 새로운 어트리뷰트를 만들 수 있음
  • 사용자 정의 Attribute는 한번 밖에 사용할 수 없다 -> System.AttributeUsage Attribute 선언부에 사용하면 여러번 사용 가능
  • 사용법
[System.AttributeUsage(System.AttributeTargets.Class, AllowMultiple=true)]
                                        // 아래 attribute를 여러번 사용할 수 있게 함
public class Author : System.Attribute // System.Attribute 상속하여 새로운 attribute 생성
{                                      
    private string Name;
    public double Version;

    public Author(string name)        // Attribute의 기능 구현
    {
        Name = name;
        Version = 1.0;
    }
}

[Author("P. Ackerman", Version = 1.1)] // 다른 attribute 처럼 사용하면 됨
class SampleClass
{
    // P. Ackerman's code goes here...
}
  1. ApiController
  • 형식 및 모든 파생 형식이 HTTP API응답을 제공하는데 사용됨을 나타냄

  • 이 attribute가 선언된 부분은 API를 빌드하기 위해 개발자 환경을 개선하는 기능/동작으로 구성

  • 특성

    • Route attribute 요구
      • [Route("[controller]")] 같은 Route attribute를 요구함
    • 자동 HTTP 400 응답
      • 자동으로 HTTP 400 응답을 자동으로 트리거하는 모델 유효성 검사 오류를 만듬
    • 바인딩 소스 매개 변수 유추
    • 다중 파트/폼 데이터 요청 정보 유추
    • 오류 상태 코드에 대한 문제 세부 정보
      • 오류 결과(코드 400 이상의 결과)를 ProblemDetails가 있는 결과로 변환
      • ProblemDetails형식은 HTTP 응답에 머신에서 읽을 수 있는 오류 세부 정보를 제공하기 위해 RFC 7807 사양을 기반으로 함
  • 사용법

[ApiController]
[Route("[controller]")] // ApiController를 사용하기 위해 선언 해야함
public class WeatherForecastController : ControllerBase
  1. Route
  • URL에 대한 경로를 정의 하기 위한 attribute

  • 웹 API의 URL을 세세하게 제어할 수 있음

  • 리소스의 계층 구조를 설명하는 URI를 쉽게 만들 수 있음

  • 사용법

[Route("customers/{customerId}/orders")]    // 변수로 URL의 경로를 정의
public IEnumerable<Order> GetOrdersByCustomer(int customerId) { ... }
  1. HTTP
  • GET, POST 등의 작업을 하는 메서드로 만들어 주는 역할
  • 종류 [HttpDelete] [HttpGet] [HttpHead] [HttpOptions] [HttpPatch] [HttpPost] [HttpPut]
  1. Require
  • 데이터 필드에 데이터가 반드시 입력되어야 한다는 것을 나타내는 attribute

  • 문자열 크기 제한을 두기 위해 MinLength/StringLength attribute와 함께 사용되기도 함

  • RegularExpression attribute를 사용하여 데이터의 값이 지정된 규칙과 일치하는지 검사함

  • 사용법

public class Movie
{
    public int Id { get; set; }

    [Required]          // 아래 데이터는 반드시 입력되어야 함
    [StringLength(100, MininumLength = 1
            , ErrorMessage = "Title should be between 1 and 100 characters")]
    public string Title { get; set; } = null!;


    [Required]
    [StringLength(1000, MininumLength = 100
            , ErrorMessage = "Description should be between 100 and 1000 characters")]
                    // Title의 길이는 1 ~ 100이어야 하며 잘못된 입력 시 ErrorMessage가 나옴
    public string Description { get; set; } = null!;
}

public IActionResult VerifyPhone(
    [RegularExpression(@"^\d{3}-\d{3}-\d{4}$")] string phone) 
    // RegularExpression attribute를 사용하여 전화번호 규칙과 일치하는지 검사
{
    if (!ModelState.IsValid)
    {
        return Json($"Phone {phone} has an invalid format. Format: ###-###-####");
    }

    return Json(true);
}

Dapper

  • ORM(개체 관계형 매퍼)

  • 객체지향 모델에서 기존 관계형 데이터베이스에 매핑하기 위한 프레임워크를 제공 -> 기존 데이터 타입을 객체지향 모델에서 이해할 수 있는 데이터로 변환해줌

  • 장점

    • 간단하고 속도가 빠름
    • SQL문을 입력하지 않고 파라미터와 쿼리만 입력하면 됨
    • 쿼리 결과를 buffer에 담아서 반환하기 때문에 DB에 락을 적게 발생시킨다
    • 패키지가 지원해주는 모든 DBMS를 연결할 수 있음
  • 단점

    • 쿼리를 직접 짜야하며 도메인 구조가 바뀌면 쿼리도 바꿔야함
    • 복잡한 매핑은 지원하지 않음
    • Join해서 오브젝트 내부에 리스트를 넣는 등의 작업은 매핑 메소드를 따로 만들어야 함

쿼리

입력 데이터 매개 변수화

  • 입력데이터를 그대로 쿼리에 쓸 경우 SQL Injection 공격에 취약함
    • SQL Injection: 악의적인 사용자가 임의의 SQL구문을 주입하여 의도하지 않은 명령을 수행하게 하는 것
  • 그래서 데이터 그대로 쿼리문을 쓰는 것보다 객체나 class로 매핑하여 사용 -> 데이터가 노출되지 않음
// 1
userInfo = await connection.QuerySingleOrDefaultAsync<DBUserInfo>(
    sql: "select Email, HashedPassword, SaltValue from account where Email = @Email;", param: new{ Email = email}   
    // 객체를 생성하여 인자로 넘겨주는 방식
);

//////////////////////////////////////////////////////////////////////////////////
// 2
// DBUserInfo라는 쿼리로 받을 정보를 저장할 class를 생성후
DBUserInfo userInfo = new DBUserInfo();     
userInfo.Email = request.Email;
userInfo.HashedPassword = hashingPassword;
userInfo.SaltValue  = saltvalue ;

// 생성한 class를 넘겨서 데이터를 받는 방식
var affectedRow:int = await connection.ExecuteAsync(sql: 
"INSERT INTO Account(Email, SaltValue, HashedPassword) 
Values(@Email, @SaltValue, @HashedPassword);", userInfo); 

Execute / ExecuteAsync

  • SQL명령을 실행시키는 메서드
  • 인자로 SQL문과 필요에 따라 매개변수를 넣어준다
  • Execute / ExecuteAsync 메서드는 해당 SQL문을 실행하고 그 작업에 영향을 받은 행의 갯수를 리턴
string sql = "INSERT INTO Customers (Name, Email) VALUES (@Name, @Email);" 
object[] parameters = { new { Name = "John Doe", Email = "jdoe@example.com" } };
	
using (var connection = new SqlConnection(connectionString))
{
    // 쿼리문과 Insert할 정보가 들어있는 객체를 넣어서 명령 실행
    conn.Execute(sql, parameters);
}

ExecuteScalar / ExecuteScalarAsync

  • SQL명령을 실행한 후의 결과의 첫번째 행의 첫번째 열의 값을 리턴하는 메서드
  • 보통 Scalar함수의 결과를 가져오는 경우 사용(count, datetime, sum, avg 등...)
  • 반환값이 객체임

단일 행 선택 쿼리

  • 여러 개의 행 중 첫 번째의 행만 반환하는 메소드
  • 종류
    • querySingle: 단일 행의 결과를 리턴
    • querySingleOrDefault: 단일 행의 결과를 리턴하거나 없는 경우 null리턴
    • queryFirst: 하나 이상의 행 중 첫 번째 행을 리턴
    • queryFirstOrDefault: 하나 이상의 행 중 첫 번째 행을 리턴하거나 없는 경우 null리턴

각 메소드는 querySingle형식으로 실행 결과를 특정 자료형이나 객체로 매핑할 수 있다 (반환 값은 T type으로 반환 됨)

여러 행 선택 쿼리

  • 여러 행의 데이터를 가져올 수 있는 메소드
  • 종류
    • Query
    • QueryAsync: 결과들을 비동기적으로 리턴 마찬가지로 Query로 사용하여 결과를 매핑하여 사용 가능

SQL Injection

  • 악의적인 사용자가 보안상의 취약점을 이용하여 임의의 SQL문을 주입하고 실행시켜서 데이터베이스가 비정상적인 작업을 하도록 조작하는 행위
  • 발생 조건
    • 어플리케이션이 DB와 연동되어 있음
    • 사용자가 입력한 외부 입력값이 SQL구문의 일부로 사용됨

예시

  1. 의도적인 주석 삽입
  • 사용자가 admin이라는 ID를 사용하고 있는 것을 알고 있다고 가정했을 때 로그인 화면에서 아래처럼 입력함

ID: admin'-- password: 아무거나

  • 위 입력값을 받아 아래 쿼리를 동적으로 생성함
SELECT * FROM USER WHERE ID = 'admin'--' AND Password = '아무거나'
  • 위 쿼리에서 입력한 ID에서 WHERE절의 ID 조건을 강제로 닫아버리고 이하의 내용을 주석처리함 -> admin이 아이디인 계정에 비밀번호 검증을 하지 않고 바로 로그인을 시켜버리게됨
  1. 직접 데이터 베이스 조작
  • 게시글을 올릴 때 아래 쿼리가 생성되어 실행하는 게시판이 있다고 가정
INSERT INTO BOARD(id, title, contents) VALUE('user', '제목', '내용')
  • 사용자가 게시글을 작성할 때 아래의 내용을 입력

제목: SI 내용: '); DELETE FROM BOARD--

  • 위 입력값으로 인해 아래 쿼리가 동적으로 생성됨
INSERT INTO BOARD(id, title, contents) VALUE('user', 'SI', ''); DELETE FROM BOARD--')
  • 위 쿼리가 실행 되면 내용이 비어있는 게시글이 추가되고 BOARD테이블의 모든 데이터를 삭제함

해결 방안

  • Dapper에서는 매개변수화 된 쿼리를 만들어서 SQL Injection 공격에 대응할 수 있음
    • 쿼리에 필요한 데이터들이 직접적으로 들어가지 않고 자리 표시자(이름 앞에 @문자가 붙은 매개변수)를 통해 전달이 됨
    • 자리 표시자를 통해 전달 된 데이터는 문자열 치환이 아니라 별도의 명령문으로 전달되어 주입된 쿼리를 실행할 수 없게 함

Redis

  • Key - Value 구조의 비정형 데이터를 관리하기 위한 오픈 소스 기반의 비관계형 데이터베이스 관리 시스템
  • 인메모리 기반의 NoSQL DBMS

특징

  • 인메모리 데이터 구조 저장소

    • 데이터를 메모리에 저장
    • 속도가 빠르지만 서버가 꺼지면 데이터가 사라진다는 단점이 존재 -> 이 특성을 이용하여 디스크 저장 방식과 함께 보조적으로 사용되는 것이 보편적
  • 다양한 데이터와 자료구조를 지원

    • 단순한 Key - Value쌍 뿐만아니라 다양한 데이터 자료구조를 지원
    • string, bitmap, bit field, hash, list, set, bit array, hyperloglog, stream...
  • 서버 복제 지원

    • Master - slave 구조로 slave를 여러개로 복사하여 만들 수 있음
  • 영속성

    • Redis는 인메모리의 단점인 휘발성을 보완하기 위해 데이터를 디스크에도 따로 저장하여 유지함
  • 싱글스레드

    • 1번에 1개의 명령어만 실행 가능
    • 요청이 병목 되면 다음 요청들이 계속 밀리기 때문에 O(N)관련 명령어(key, flushall,FLUSHDB, Delete COlLECTIONS, Get All Collections 등)을 사용하는데 주의 해야함
    • RDB작업(특정 간격마다 모든 데이터를 디스크에 저장하는 작업)이 매우 오래걸림 -> Redis 장애 원인의 대부분이 해당 기능 때문에 발생

Redis와 Memcached와의 차이점

  1. Redis

    • 처리속도: Memcached와 성능차이가 거의 없음
    • 데이터 저장 방식: 디스크와 메모리에 저장되기 때문에 복구가능
    • 만료일 지정: 만료일을 지정하여 만료시 데이터가 캐시처럼 영원히 삭제
    • 메모리 재사용: 메모리를 재사용 하지 않으며 명시적으로만 데이터를 제거 가능
    • 데이터 타입: 다양한 데이터 타입 지원
  2. Memcached

    • 처리속도: 데이터가 메모리에만 저장되고 디스크를 거치지 않아 빠름
    • 데이터 저장 방식: 데이터가 메모리에만 저장, 서버가 다운되거나 장애 발생시 데이터가 사라짐
    • 만료일 지정: Redis와 동일
    • 메모리 재사용: 저장소 메모리를 재사용. 만료전에 더 이상 데이터를 넣을 메모리가 없으면 LRU알고리즘에 따라 데이터 삭제
      • LRU알고리즘: 가장 오랫동안 참조되지 않은 데이터를 교체
    • 데이터 타입: 문자열만 지원

자료형

  1. string
  • Key - Value 형태의 데이터를 저장하는 기본적인 자료형
  • value에 문자, 숫자 등을 저장
  • 명령어
    1. SET

      • 키와 값을 입력하는 명령어
      • 시간복잡도: O(1)
      • (EX) SET key value EX 100 NX
      • Option
        • EX seconds: 초 단위 만료시간
        • PX milliseconds: 밀리초 단위 만료시간
        • EXAT timestamp-seconds: 초 단위 만료시간을 Unix시간으로
        • EXAT timestamp-seconds: 밀리초 단위 만료시간을 Unix 시간으로
        • NX: 값이 존재 하지 않을 경우에만 키 값에 값을 저장
        • XX: 값이 존재할 경우에만 키 값에 값을 덮어씀
    2. GET

      • 키에 값이 있으면 데이터 출력
      • 없으면 nil 반환하고 문자열이 아니라면 오류 반환
      • 시간복잡도: O(1)
      • (EX) GET key
    3. SETEX

      • 문자열 값을 보관할 키를 설정하고 초 단위로 시간을 설정
      • 시간복잡도: O(1)
      • (EX) SETEX key 100 value
    4. SETNX

      • 키가 존재하지 않을 경우에만 값을 지정
      • 시간복잡도: O(1)
      • (EX) SETEX key value
    5. INCR/DECR

      • 키에 저장된 수를 1씩 증가/감소 시킴
      • 값이 없으면 0부터 시작
      • 시간복잡도: O(1)
      • (EX) INCR key / DECR key
    6. MGET

      • 지정된 모든 키의 값을 반환함
      • 키의 값이 없거나 문자열이 아니면 nil을 반환 -> 실패하는 경우가 없음
      • 시간복잡도: O(N)
      • (EX) MGET key1 key2 key3
    7. MSET

      • 지정된 키의 값을 각각의 값으로 설정
      • SET과 마찬가지로 키에 이미 값이 있으면 기존 값을 덮어 씌운다
      • 시간복잡도: O(N)
      • (EX) MGET key1 value1 key2 value2 key3 value3
  1. Set
  • value들을 유니크하게 담고있는 자료구조. 순서가 지정되어있지 않음
  • 집합형 자료형이기 때문에 합집합, 교집합, 차집합과 같은 연산도 가능
  • 명령어
    1. SADD
      • 집합에 값을 추가
      • (EX) SADD key value
      • (EX) SADD key value1 value2 value3
    2. SREM
      • 집합에 있는 값 삭제
      • (EX) SREM key vaule
    3. SMEMBERS
      • 집합에 있는 모든 멤버 출력
      • (EX) SMEMBERS key
    4. SUNION
      • 집합들의 합집합을 구함
      • (EX) SUNION key1 key2
    5. SINTER
      • 집합들의 교집합을 구함
      • (EX) SINTER key1 key2
    6. SDIFF
      • 집합들의 차집합을 구함
      • (EX) SDIFF key1 key2
  1. List
  • Set과는 다르게 중복된 데이터를 저장할 수 있는 자료구조
  • Linked List의 형태
  • 명령어
    1. LPUSH
      • 왼쪽에서 리스트의 오른쪽에 데이터를 저장
      • (EX) LPUSH key value1 value2...
    2. RPUSH
      • 오른쪽에서 리스트의 왼쪽에 데이터를 저장
      • (EX) RPUSH key value1 value2...
    3. LPOP
      • 리스트 왼쪽에서 데이터를 POP(꺼내고 리스트에서는 삭제됨)
      • (EX) LPUSH key
    4. RPOP
      • 리스트 오른쪽에서 데이터를 POP
      • (EX) RPUSH key
    5. LRANGE
      • 인덱스로 범위를 지정하여 조회
      • (EX) LRANGE 0 10
    6. LINDEX
      • 인덱스로 특정 위치의 데이터 조회
      • (EX) LINDEX index
    7. LPUSHX / RPUSHX
      • 이미 존재하는 리스트에 LPUSH/RPUSH 를 실행(해당 key의 리스트가 없다면 저장 불가능)
    8. BLPOP / BRPOP
      • 리스트의 값이 없을 경우 지정한 시간 만큼 기다려서 값이 들어오면 LPOP/RPOP 실행
      • (EX) BLPOP key1 1000
  • Redis Message Queue
    • List를 이용하여 Queue를 구현한 것
    • LPUSH와 RPOP(또는 RPUSH와 LPOP)을 사용하여 FIFO(First In First Out)구조인 Queue처럼 사용한다
    • BLPOP(BRPOP)를 사용하면 Blocking Pop을 수행할 수 있는데 이를 이용해 메세지 Queue로 사용이 가능
  1. Sorted Set
  • value값을 score 기준으로 정렬하는 Set
  • RDBMS사용하여 랭킹 시스템을 구축하면 최대 O(n)시간이 걸릴수 있지만 Sorted Set은 기본적으로 score따라 정렬된 상태로 저장하기 때문에 O(log n)시간으로 랭킹을 계산할 수 있다.
  • 명령어
    1. ZADD
      • 집합에 값을 추가
      • (EX) ZADD key score value
      • (EX) ZADD key1 score1 value1 key2 score2 value2 key3 score3 value3
    2. ZSCORE
      • 특정 값의 score 조회
      • (EX) ZSCORE key value
    3. ZRANGE/ZREVRANGE
      • 특정 범위의 score를 오름차순/내림차순으로 조회
      • (EX) ZRANGE key 5 10
    4. ZRANK
      • 특정 값의 랭킹을 구함
      • ZRANK key value
  1. Hash
  • key field value 구조로 저장 되는 자료구조
  • 하나의 key에 여러개의 field와 value가 존재
  • 명령어
    1. HSET
      • field와 value를 저장
      • (EX) HSET key field value
    2. HGET
      • field로 value를 조회
      • (EX) HGET key field

트랜잭션

  • 나누어지지 않는 최소한의 단위로 만들어서 all or nothing 전략을 취할 수 있도록 함

  • 즉 어떤 명령이 실패를 하게 되면 명령들을 모두 취소시키고 아니면 모두 실행함

  • Redis는 싱글스레드 기반이기 때문에 동시성 문제를 처리해야함 -> 트랜잭션으로 처리

  • Redis에서 트랜잭션을 유지하려면 순차성을 가져야하고 도중에 명령어가 들어오지 못하게 Lock을 걸어야함

  • 명령들을 Queue에 쌓아두고 한번에 처리함

  • MULTI 명령어 이후 잘못된 명령어를 사용 시 자동으로 DISCARD실행

  • 단 잘못된 자료구조의 명령어일 경우 DISCARD되지 않고 그대로 실행 -> 대부분 개발 과정에서 일어날 수 있는 에러이기 때문

  • 명령어

    1. MULTI
      • 트랜잭션을 준비하는 명령어. 이 명령어 이후 명령은 바로 실행되지 않고 queue에 저장
    2. EXEC
      • 트랜잭션을 시작하는 명령어. MULTI이후 Queue에 쌓인 명령들을 차례대로 실행시킴
    3. DISCARD
      • Queue에 쌓인 명령들을 지워버림
    4. WATCH, UNWATCH
      • Lock을 담당하는 명령어
      • 이 명령어를 사용한 이후 UNWATCH가 되기전엔 한번의 EXEC또는 트랜잭션이 아닌 명령어만 허용
      • MULTI, EXEC만으로는 동시성 문제의 트랙잭션의 고립성을 보장할 수 없으므로 사용
      • WATCH로 인해 예외 발생 시 DISCARD를 사용하여 문제 처리
      • EXEC가 호출되면 묵시적으로 UNWATCH가 호출됨

Pub Sub

  • 특정한 주제에 대해 해당 주제를 구독한 모두에게 메시지를 발행하는 통신 방법

  • 하나의 client가 메세지를 pub하면 이 주제에 연결되어있는 다수의 client가 메시지를 받음 (youtube의 채널 구독과 비슷함. 채널에서 새로운 영상이 올라오면 구독자한테 알림이 가는 것)

  • 이 시스템은 매우 단순한 구조로 되어있음

    • 메시지를 던지기만 하고 따로 보관하지 않음 -> 수신자가 메시지를 받는 것을 보장하지 않고 일반 메시지queue처럼 수신 확인을 하지 않음
  • Redis는 인메모리기반이라 매우 빠르게 메시지를 보낼 수가 있음

  • 명령어

    1. SUBSCRIBE
      • 해당 채널을 구독하여 메시지를 수신받도록 대기
      • (EX) SUBSCRIBE channel
    2. PSUBSCRIBE
      • 나열한 패턴에 일치하는 채널을 구독
      • (EX) PSUBSCRIBE pattern
    3. PUBLISH
      • 메시지를 해당 채널로 송신. 해당 채널에서 대기중인 클라이언트들에게 메시지가 전달됨
      • (EX) PUBLISH channel message
    4. UNSUBSCRIBE
      • 해당 채널의 구독 취소
      • (EX) UNSUBSCRIBE channel1 channel2
    5. PUNSUBSCRIBE
      • 해당 패턴에 일치하는 채널의 구독 취소
      • (EX) PUNSUBSCRIBE pattern1 pattern2
    6. PUBSUB
      • 서버에 등록된 채널이나 패턴 조회

ZLogger

  • Microsoft.Extensions.Logging 위에 구축된 .NET Core 및 Unity용 제로 할당 텍스트/구조적 로거

특징

  • ZString에 의해 UTF8로 직접 버퍼영역에 쓰게되고 ConsoleStream에 정리해서 보내주기 때문에 Boxing이 일어나지 않고 비동기적으로 단번에 쓰기 때문에 부하를 주지 않음

    • Boxing: value형식을 reference(object)형식으로 변환해주는 것
  • 철저한 제네릭, 구조체, 캐시를 활용하여 최대의 성능을 구현

  • ConsoleLogging외에도 FileLogger, RollingFileLogger, StreamLogger를 제공

ZString

  • .NET Core 및 Unity용 메모리 할당을 0으로 설정하는 string 빌더
  • 사용이유
    • 대부분의 경우에 기존의 String보다 메모리 할당량과 속도가 적음

사용법

using ZLogger; // namespace

Host.CreateDefaultBuilder()
    .ConfigureLogging(logging =>
    {
        // 기본 제공자 정의
        logging.ClearProviders();
        
        // optional(MS.E.Logging): default값은 Info이며 option을 바꿔 최소 로그레벨을 지정해 줄 수 있다
        logging.SetMinimumLevel(LogLevel.Debug);
        
        // 콘솔에 로그 출력
        logging.AddZLoggerConsole();

        // 파일에 로그 출력
        logging.AddZLoggerFile("fileName.log");

        // 날짜/시간 또는 파일 크기에 따라 출력 파일 경로가 변경된 파일로 출력
        logging.AddZLoggerRollingFile((dt, x) => $"logs/{dt.ToLocalTime():yyyy-MM-dd}_{x:000}.log", x => x.ToLocalTime().Date, 1024);
       
        // 구조화된 로깅 사용
        logging.AddZLoggerConsole(options =>
        {
            options.EnableStructuredLogging = true;
        });
    })
  • 로그 출력

    1. AddZLoggerConsole

      • 콘솔에 로그를 출력함
      • ConsoleApplication 및 클라우드의 컨테이너화된 Application에 유용
    2. AddZLoggerFile

      • string 인자로 넣은 파일로 로그를 출력함
  • RollingFile

    • 날짜/시간 또는 파일 크기에 따라 출력 파일 경로가 변경된 파일로 출력
logging.AddZLoggerRollingFile(
    // Func<DateTimeOffset, int, string> fileNameSelector
    // 생성된 로그파일의 경로 선택기 
    fileNameSelector: (dt, x) => $"logs/{dt.ToLocalTime():yyyy-MM-dd}_{x:000}.log", 

    // Func<DateTimeOffset, DateTimeOffset> timestampPattern
    // 로그가 작성되는 현재시간을 인수로 받으며
    // 반환 값이 마지막으로 작성된 시간과 다른 경우 
    // fileNameSelector를 호출하여 새파일에 작성
    timestampPattern: x => x.ToLocalTime().Date, 

    // int rollSizeKB
    // 파일크기 제한
    // 크기가 넘어가면 fileNameSelector를 호출하여 새파일에 작성
    rollSizeKB: 1024);
  • 구조화된 로깅
    • 클라우드(Amazon Ahtena, Google BigQuery, Azure Data Lake.. 등) 로깅을 하는데 있어 중요한 역할
    • System.Text.Json.JsonSerializer를 사용하여 문자열로 변환하지 않고 파이프라인에서 완전한 제로 할당이 가능
    • JSON으로 로그를 작성하고 추가 정보(카테고리 이름, loglevel, 시간, 사용자 정의 메타데이터) + 메시지, 정보 + 메시지 + 사용자 정의 페이로드를 지원

SqlKata

  • C#으로 작성된 오픈소스 SQL 쿼리 빌더 라이브러리

  • 특징

    • 코드로 쿼리 문을 만들 수 있게 함
    • 중첩 조건, 서브쿼리에서 선택, 서브쿼리 필터링, 조건문 등과 같은 복잡한 쿼리를 지원
    • 대부분의 RDBMS를 지원
    • SQL엔진이 캐시되도록 하여 쿼리 실행 속도를 높힘
    • 내부적으로 Dapper를 사용하여 쿼리를 데이터베이스에 제출하는 기능을 제공
    • 매개변수 바인딩 기술을 사용하여 SQL Injection 공격을 예방할 수 있음
IEnumerable<Post> posts = await db.Query("Posts")
    .Where("Likes", ">", 10)
    .WhereIn("Lang", new [] {"en", "fr"})
    .WhereNotNull("AuthorId")
    .OrderByDesc("Date")
    .Select("Id", "Title")
    .GetAsync<Post>();

-> 쿼리에 들어가는 데이터를 매개변수화

사용법

연결 설정

using SqlKata;
using SqlKata.Execution;
using System.Data.SqlClient; // Sql Server Connection Namespace

// 연결과 컴파일러 설정
var connection = new SqlConnection("Data Source=MyDb;User Id=User;Password=TopSecret");
var compiler = new SqlServerCompiler();

var db = new QueryFactory(connection, compiler);

쿼리 작성

  1. FROM
new Query("Posts");
new Query().From("Posts");
  • = SELECT * FROM [Posts]
  • Query 메서드를 사용하여 접근할 테이블을 선택
  • From은 생략해도 됨
  1. WHERE
new Query("Posts").Where("Score", ">", 10);
  • = SELECT * FROM [Posts] WHERE [Score] > 10
  • Where 메서드를 사용하여 조건을 간단하게 작성 가능
new Query("Posts").Where("Id", 10);

new Query("Posts").Where("Id", "=", 10);
  • 두 쿼리문은 같은 결과를 수행함 -> Where 메서드는 "="가 기본 값
  1. WhereNull/WhereTrue/WhereFalse
db.Query("Users").WhereFalse("IsActive").OrWhereNull("LastActivityDate");
  • = SELECT * FROM [Users] WHERE [IsActive] = cast(0 as bit) OR [LastActivityDate] IS NULL
  • NULL/Bool값에 대해 필터링하는 Where메서드
  • 해당 방법은 매개변수를 사용하지 않고 SQL문에 값을 그대로 입력함
  1. SELECT
var query = new Query("Posts").Select("Id", "Title", "CreatedAt as Date");
  • = SELECT [Id], [Title], [CreatedAt] AS [Date] FROM [Posts]
  • Posts 테이블에서 Id, Title 그리고 CreateAt을 Data로 멸칭을 붙여서 데이터 조회
  • Select메서드로 조회할 열 입력
  1. INSERT
var query = new Query("Books").AsInsert(new {
    Title = "Toyota Kata",
    CreatedAt = new DateTime(2009, 8, 4),
    Author = "Mike Rother"
});
  • = INSERT INTO [Books] ([Title], [CreatedAt], [Author]) VALUES ('Toyota Kata', '2009-08-04 00:00:00', 'Mike Rother')
  • AsInsert 메서드를 사용하여 데이터 삽입 수행
var cols = new [] {"Name", "Price"};

var data = new [] {
    new object[] { "A", 1000 },
    new object[] { "B", 2000 },
    new object[] { "C", 3000 },
};

var query = new Query("Products")
    .AsInsert(cols, data);
  • = INSERT INTO [Products] ([Name], [Price]) VALUES ("A", 1000), ("B", 2000), ("C", 3000)
  • 동적 매개변수을 사용하여 한번에 여러 데이터를 삽입 할 수 있다
  1. UPDATE
var query = new Query("Posts").WhereNull("AuthorId").AsUpdate(new {
    AuthorId = 10
});
  • = UPDATE [Posts] SET [AuthorId] = 10 WHERE [AuthorId] IS NULL
  • AsUpdate 메서드를 사용하여 데이터 수정
  1. DELETE
var query = new Query("Posts").Where("Date", ">", DateTime.UtcNow.AddDays(-30)).AsDelete();
  • = DELETE FROM [Posts] WHERE [Date] > NOW() - INTERVAL 30 DAY
  • AsDelete 메서드를 사용하여 데이터 삭제
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment