반응형

안녕하세요. 

예전에 NewtonSoft.Json 라이브러리를 설명드렸었는데요.

이번엔 System안에 속한 Json 컨버팅 라이브러리를 설명드리도록 하겠습니다.

 

NewtonSoft.Json( [c# dotnet nuget] Newtonsoft.Json (tistory.com) ) 글에 설명을 자세히 적어놨기 때문에

기본적인 시리얼라이즈, 디시시리얼라이즈 원리 자체는 동일하기때문에 여기서는 설명은 최소화 합니다.

 

[c# dotnet nuget] Newtonsoft.Json

이번에는 Newtonsoft.Json에 대해서 소개하도록 하겠습니다. System.Text.Json으로도 json데이터를 처리할 수 있지만 너무나 편리하고 기존에 만들어진 소스에서 많이 사용하고 있어서 사용하게 되었고

pichen.tistory.com

 

 - 인스턴스를 json형태의 문자열로 바꾸는 메소드(직렬화)

// 그냥
string jsonString = JsonSerializer.Serialize(어떤 인스턴스);

// 제네릭을 사용
string jsonString = JsonSerializer.Serialize<어떤 클래스>(어떤 클래스의 인스턴스);

 

 

- System.Text.Json에도 컨버팅 옵션이 존재합니다!

// 명확하게 써놓은것
var options = new JsonSerializerOptions { WriteIndented = true };
string jsonString = JsonSerializer.Serialize(인스턴스, options);

// 한줄로
string jsonString = JsonSerializer.Serialize(
	인스턴스, 
    new JsonSerializerOptions { WriteIndented = true }
);

    JsonSerializerOptions에 쓰일 수 있는 변수를 설명드리겠습니다. Newtonsoft.Json을 보고 오신분이라면 WriteIndented가 뭔지 감이 오실텐데요.

 

    WriteIndented =true :  를 설정하면 엔터등 공백을 포함하여 json문자열을 만듭니다.

 

    PropertyNameCaseInsensitive=true :  대소문자를 구분하여 비교를 사용합니다.

    Newtonsoft.Json에서는 대소문자를 구분하지 않아 false와 동일하며 System.Text.Json에서는 구분이 가능하다는 장점이 있는겁니다.

    

  IgnoreReadOnlyFields=true : 프로퍼티가 readonly인 경우 이를 무시하고 serialize합니다.

 

  DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull : null값을 무시합니다.

 DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault : 기본값을 무시합니다.

 

 

   - json형태의 문자열을 Oject또는 특정 클래스로 바꾸는 메소드(역직렬화)

var oject = JsonSerializer.Deserialize(json);

클래스 인스턴스 = JsonSerializer.Deserialize<클래스>(json);

 

 

 - 인스턴스를 db 등이 아닌 파일에 기록할때 사용하는 메소드

 string fileName = "아무파일명.json";
using FileStream createStream = File.Create(fileName);
await JsonSerializer.SerializeAsync(createStream, 파일화하고싶은 인스턴스);
await createStream.DisposeAsync();

 

json파일로 만든 것은 ReadAllText로 읽어와서 Desirialize하여 사용하시면 됩니다.

 

 

제가 사용하는 기능 위주로 작성했기 때문에 어떤 특정한 내용을 찾고자 오셨다면 내용이 부족하다 느낄 수 있습니다.

 

Newtonsoft.Json에서 System.Text.Json으로 마이그레이션 - .NET | Microsoft Learn

 

Newtonsoft.Json에서 System.Text.Json으로 마이그레이션 - .NET

로 마이그레이션하는 Newtonsoft.JsonSystem.Text.Json 방법과 차이점에 대해 알아봅니다 System.Text.Json.

learn.microsoft.com

 

microsoft 공식 링크를 통해 자세한 부분을 찾아보시기 바립니다.

반응형
반응형

Sicos1977/MSGReader: C# Outlook MSG file reader without the need for Outlook (github.com)

 

Msgreader는 이메일 파일인 .msg와 .eml확장자 파일을 읽을 수 있도록하는 라이브러리입니다.

원본 github주소는 위 그림을 클릭하면 이동하실 수 있습니다.

 

이메일 파일에 담긴 이메일 정보(보낸이, 받는이, 보낸시각, 받은시각, cc, bcc, 메일제목, 메일내용, 첨부파일 등)의 정보를 읽을 수 있도록합니다.

 

상세한 예외처리는 제외하고 용법을 보겠습니다.

 

먼저, 메일정보를 쉽게 주고받을 수 있도록 프로퍼티를 충분히 가진 클래스를 선언하겠습니다.

 

class MailFile {
    public string FileDirectory { get; set; }          // 파일 경로
    public string FileName { get; set; }               // 파일명
    public string FilePath { get; set; }               // 파일경로 + 파일명
    public string Originator { get; set; }             // 보낸사람
    public string Addressee { get; set; }              // 받는사람
    public string CC { get; set; }                     // CC
    public string BCC { get; set; }                    // BCC
    public DateTime SentDate { get; set; }             //  발신 시간
    public DateTime ReceivedDate { get; set; }         // 수신 시간
    
    public MailFile(string fileDirectory, string fileName)
    {
        FileDirectory = fileDirectory;
        FileName = fileName;
        FilePath = Path.Combine(fileDirectory, fileName);  
        // 그냥 string으로 더하지 않고 Path.Combine으로 쓴이유를 모른다면 독자는 꼭 찾아봐야한다!
    }
}

 

 

예제이기 때문에 msg인지 eml파일인지 여부를 비교할 대상은 그냥 선언하겠습니다.

(여기서는 확장자로만 구분합니다.)

// 메일 파일을 읽기전에 있어야할 것들을 기입한 부분입니다.
// msg인지 eml인지 비교할 확장자,
// MailFile 클래스의 선언
// 생성된 첨부파일을 다운받을 경로 등

string msgExtention = ".MSG";
string emlExtention = ".EML";

string mailFileDirectory = @"파일경로, \가 보통들어가서 @를 붙임";
string mailFileName = @"파일명";

var mailFile = new MailFile(mailFileDirectory, mailFileName);
string downloadDirectory = @"첨부파일이있다면 다운받을 경로이다.";

 

다음은 메일파일의 정보가 있는지 여부를 받아서 받아오는 것이다.

FileInfo mailFileInfo = new FileInfo(mailFile.FilePath);

if (fileInfo.Exists && fileInfo.Length > 0)	
// fileInfo.Exists는 파일이 존재하는지 여부를 리턴
// fileInfo.Length는 파일의 크기를 byte단위로 리턴
{
    if (mailFileInfo.Extension.ToUpper().Equals(msgExtention))
    // 파일 경로 비교 ToUpper()를 해도되고 StringComparison.OrdinalIgnoreCase 를 사용해도된다.
    {
        using var msgFile = new MsgReader.Outlook.Storage.Message(mailFilePath);
        // 메일 파일의 경로에 따라 여기서는 확장자값이 .MSG이면 msg인스턴스(Storage.Message)를 생성한다.
        
        // 아래는 읽으면 감이 올 것이다. 각 정보를 받아오는 것이고 
        // 여기에 매칭되도록 초반에 MailFile을 선언했다.
        // 실제 사용시에는 MailFile에 첨부파일 존재여부도 넣어주면 좋다. 
        mailFile.Originator = msg.Sender?.DisplayName;                                    // 보낸이
        mailFile.Addressee = msg.GetEmailRecipients(RecipientType.To, false, false);      // 받는이
        mailFile.CC = msg.GetEmailRecipients(RecipientType.Cc, false, false);             // CC
        mailFile.BCC = msg.GetEmailRecipients(RecipientType.Bcc, false, false);           // BCC
        mailFile.SentDate = msg.SentOn ?? DateTime.MinValue;                              // 보낸시각
        mailFile.ReceivedDate = msg.ReceivedOn ?? DateTime.MinValue;                      // 받는시각
    	
        // 첨부파일은 아래와 같은방법으로 가져올 수 있다.  
        // #### 첨부파일류들은 data[]로 파일데이터를 들고있기때문에 아주 무겁다. ####
        // ## 그러나 IDisposable을 구현하고 있어서 dispose()나 using을 사용할 수 있다.
        // ## 해당부분은 여기에서는 포함하고 있지 않아서 참고하여 개발시 이 부분을 반드시 확인하기바란다.
        // ## 아니면 메모리가 탈탈 털리는 현상을 볼 지도...
        // ## 또한 파일데이터를 들고있기때문에 너무 용량이 큰 친구들은 이것으로 첨부파일 뽑으려면
        // ## 메모리가 아주커야할 것이다... 파일 용량에 꼭 제한을 두자.
        // ## 도입부에 FileInfo 선언했는데 거기서 처리할 수 있다.
        var attachmentsObject = msgFile.Attachments;

        attachmentsObject.ForEach(attachment =>
        {
            try
            {
                if (!Directory.Exists(downloadDirectory)){    // 첨부파일을 다운받을 경로가 없으면 만든다.
                    Directory.CreateDirectory(downloadDirectory);
                }
                
                if (attachment != null && attachment is Storage.Attachment)  // 일반적인 첨부파일이면
                {
                    var attachmentCast = attachment as Storage.Attachment ?? throw new Exception($"형변환했는데 null이면 던질 예외");
                    string attachmentPath = Path.Combine(downloadDirectory, attachmentCast.FileName);             // 내려받을 첨부파일 path
                    File.WriteAllBytes(attachmentPath, attachmentCast.Data);                                      // 다운로드           
                }
                else if (attachment != null && attachment is Storage.Message)  // 앞에 using var msgFile = new MsgReader.Outlook.Storage.Message(mailFilePath); 부분을 보면 눈치 챘을 수도 있지만 이는 첨부파일이 MSG파일인 경우이다.
                {
                  var attachmentCast = attachment as Storage.Message ?? throw new Exception($"형변환했는데 null이면 던질 예외");
                  string attachmentPath = Path.Combine(downloadDirectory, attachmentCast.FileName);
                  attachmentCast.Save(attachmentPath);                       // Storage.Message는 Save함수를 사용하여 파일을 내려받아야 한다.
                }
                else
                {
                  // 둘다 아닌경우에 취할 조치를 하면 된다. 예외처리를 해도 좋다.
                }
            }
            catch (Exception ex)
            {
              Log.Error(ex.ToString());
            }
      });
    }
    else if (mailFileInfo.Extension.ToUpper().Equals(emlExtention))
    {
        var emlFile = MsgReader.Mime.Message.Load(mailFileInfo);    // eml 파일을 읽는 방법이다.

        if (emlFile.Headers != null)    // eml 파일의 메일 파일정보 불러오기는 복잡하다... msgReader 공식 문서를 참조하면 이렇게 해야한다.
        {
            if (emlFile.Headers.From != null && !string.IsNullOrEmpty(eml.Headers.From.DisplayName))
            { mailFile.Originator = emlFile.Headers.From.DisplayName.ToString().Replace("\"", string.Empty); }
            else if (eml.Headers.From != null && !string.IsNullOrEmpty(eml.Headers.From.Address))
            { mailFile.Originator = emlFile.Headers.From.Address.ToString().Replace("\"", string.Empty); }
            else { mailFile.Originator = ""; }
        
            emlFile.Headers.To.ForEach(to => mailInfo.Addressee += "," + to.ToString());
            if (mailFile.Addressee == null) { mailFile.Addressee = ""; }
            else { mailFile.Addressee = mailFile.Addressee.Substring(1).Replace("\"", string.Empty); }
        
            emlFile.Headers.Cc.ForEach(cc => mailFile.CC += "," + cc.ToString());
            if (mailFile.CC == null) { mailFile.CC = ""; }
            else { mailFile.CC = mailFile.CC.Substring(1).Replace("\"", string.Empty); }
        
            emlFile.Headers.Bcc.ForEach(bcc => mailFile.BCC += "," + bcc.ToString());
            if (mailFile.BCC == null) { mailFile.BCC = ""; }
            else { mailFile.BCC = mailInfo.BCC.Substring(1).Replace("\"", string.Empty); }
        
            if (emlFile.Headers.DateSent != null) { mailFile.SentDate = emlFile.Headers.DateSent; }
            else { mailFile.SentDate = DateTime.MinValue; }
        
            if (emlFile.Headers.Received != null && emlFile.Headers.Received.Count > 0)
            {
                mailFile.ReceivedDate = emlFile.Headers.Received.Last().Date;
            }
            else
            {
                mailFile.ReceivedDate = DateTime.MinValue;
            }
        }    // 여기까지가 메일정보 읽기
        
        ObservableCollection<MessagePart> attachments = emlFile.Attachments;    
        var attachmentsCount = attachments.Count;       
    
        foreach (var attachment in attachments) // eml파일은 정보읽기는 복잡하지만 첨부파일 다운로드는 간단하다.
        { 
            try
            {
                if (!Directory.Exists(downloadDirectory)) 
                    Directory.CreateDirectory(downloadDirectory);
    
                if (attachment.IsAttachment)
                    attachment.Save(new FileInfo(Path.Combine(downloadDirectory, attachment.FileName)));             
                
            }
            catch (Exception ex)
            {
                Log.Error(ex.ToString());
            }
        }
    }
}

 

생각보다 코드가 길기 때문에 설명도 코드블럭내에 같이 적어놨습니다. 특히 ####, ## 붙은 부분을 반드시 읽어서 참고시에 메모리 등 관리문제를 최대한 방지 하시기 바랍니다.

 

반응형
반응형

이번에는 Newtonsoft.Json에 대해서 소개하도록 하겠습니다.

 

 

System.Text.Json으로도 json데이터를 처리할 수 있지만

너무나 편리하고 기존에 만들어진 소스에서 많이 사용하고 있어서 사용하게 되었고 설명드리고자 합니다.

 

 

요즘에는 xml을 거의 사용하지 않고 json으로 데이터를 주고받으면서

 

많이 사용하고 있죠

 

using Newtonsoft.Json;

으로 설치한 nuget 패키지를 사용할 파일에서 import하시면 사용가능합니다.

 

Newtonsoft.Json의 주요기능

 

문자열로 변환 가능한 클래스를 json 데이터로 변환하는 것

json 형태의 문자열을 클래스로 변환하는 것입니다.

 

 

 

먼저 예시로 사용할 클래스를 만들어보도록 하겠습니다.

( 참고로 github에 올려두었습니다. 원본 소스가 궁금하신분들은 여기서 내려받거나 복사하시면 되겠습니다. - msab2170/tistory50_Newtonsoft.Json_Example: 티스토리 블로그 50번 글 소스파일 (github.com)

 

GitHub - msab2170/tistory50_Newtonsoft.Json_Example: 티스토리 블로그 50번 글 소스파일

티스토리 블로그 50번 글 소스파일. Contribute to msab2170/tistory50_Newtonsoft.Json_Example development by creating an account on GitHub.

github.com

 

class Example
{
    public List<Fruit> Fruits;
    public string Str;
    public int Number = 0;
    public int NumberDefault0 = 0;
    public bool IsOk;
    public string? NullableStr;
    public int? NullableInteger;
}

class Fruit
{
    public string Name;
    public int price;
}

 

 

그리고 아래는 실행할 소스 입니다.

 

using Newtonsoft.Json;

Example example = new()
{
    Str = "문자열",
    Number = 10,
    NumberDefault0 = 0,
    IsOk = true,
    NullableStr = null,
    NullableInteger = null
};

var exampleJson0 = JsonConvert.SerializeObject(example);
var exampleJson1 = JsonConvert.SerializeObject(example, Formatting.None);
var exampleJson2 = JsonConvert.SerializeObject(example, Formatting.Indented);
var exampleJson3 = JsonConvert.SerializeObject(example, Formatting.None, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
var exampleJson4 = JsonConvert.SerializeObject(example, Formatting.None, new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Ignore });
var exampleJson5 = JsonConvert.SerializeObject(example, Formatting.Indented, new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Ignore });
var exampleJson6 = JsonConvert.SerializeObject(example, Formatting.Indented, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });

Console.WriteLine($"exampleJson0: {exampleJson0}");
Console.WriteLine($"exampleJson1: {exampleJson1}");
Console.WriteLine($"exampleJson2: {exampleJson2}");
Console.WriteLine($"exampleJson3: {exampleJson3}");
Console.WriteLine($"exampleJson4: {exampleJson4}");
Console.WriteLine($"exampleJson5: {exampleJson5}");
Console.WriteLine($"exampleJson5: {exampleJson6}");

 

아래는 실행 결과값입니다.

exampleJson0: {"Fruits":null,"Str":"문자열","Number":10,"NumberDefault0":0,"IsOk":true,"NullableStr":null,"NullableInteger":null}
exampleJson1: {"Fruits":null,"Str":"문자열","Number":10,"NumberDefault0":0,"IsOk":true,"NullableStr":null,"NullableInteger":null}
exampleJson2: {
  "Fruits": null,
  "Str": "문자열",
  "Number": 10,
  "NumberDefault0": 0,
  "IsOk": true,
  "NullableStr": null,
  "NullableInteger": null
}
exampleJson3: {"Str":"문자열","Number":10,"NumberDefault0":0,"IsOk":true}
exampleJson4: {"Str":"문자열","Number":10,"IsOk":true}
exampleJson5: {
  "Str": "문자열",
  "Number": 10,
  "IsOk": true
}
exampleJson5: {
  "Str": "문자열",
  "Number": 10,
  "NumberDefault0": 0,
  "IsOk": true
}

JsonConvert.SerializeObject(example); 했을때 default로 들어가는 설정들이 보이시나요?

 

위에 적은 예시에서 exampleJson 뒤에 붙은 숫자대로 설명하겠습니다.

 

0. 기본적으로 JsonConver.SerializeObject() 내에 별다른 설정 값이 없다면

 

Example 클래스를 토대로 json형태로 변환하는데

null값이 있으면 "변수명":null로 입력이 된다는 것을 알 수 있고,

클래스의 모양을 무시하고 한 줄에 출력된다는 것을 알 수 있습니다.

 

 

1. 을 보면 JsonConvert.SerializeObject(example, Formatting.None); 가 0의 예시와 동일하다는 것을 알 수 있죠,

이를 토대로 우리는

 

클래스 모양을 무시하고 한 줄로 치는 값은 Formatting형 값인 Formatting.None이라는 것을 알 수 있습니다.

 

 

2.에서는 Formatting형 값을 Formatting.Indented로 주자 클래스 모양처럼 보기 쉽게 문자열이 변형됨을 알 수 있습니다.

가독성을 높인 대신에 쓸데없이 라인을 많이 잡아먹는다는 단점이 있습니다.

 

제 경험상 주고받을때는 굳이 공간을 더 쓸 필요없기 때문에 한줄로 주로 사용하는 경우가 많았고

로그로 남길때는 라인수냐 가독성이냐 일부 논의를 할 수 있지만 역시 한 줄로 사용하는 경우가 많았습니다.

 

 

3번과 4번은 설계에 따라서 많이 사용하기도 합니다.

JsonConvert.SerializeObject(example, Formatting.None, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });

 

JsonConvert.SerializeObject 함수의 세번째 변수자리에는 JsonSerializerSettings 클래스 변수를 사용하는데 이는 변환시 필터작업?을 포함합니다.

 

JsonSerializerSettings  내에는 대표적으로 3번과 4번에 쓰인 NullValueHandlingDefaultValueHandling 가 있습니다.

사용 형식은 위 예제대로

new JsonSerializerSettings {

    NullValueHandling = NullValueHandling.Ignore

}

의 방식이고 쉼표로 구분해 둘 또는 다른 설정과 같이 쓸 수도 있으며,

 

 

NullValueHandling.Ignore은 변환하고자하는 인스턴스 내 변수 값이 null 이면 이를 없는 셈치고 json으로 변환합니다.

3번과 6번을 보면 null 값이 담겨있는 Fruits와 NullableStr, NullableInteger 가 사라졌다는 것을 알 수 있습니다.

반대 값은 NullValueHandling.Include 이며 이는 디폴트값이라 따로 지정할 필요는 없습니다.

 

 

DefaultValueHandling도 마찬가지 기능입니다. Ignore와 Include가 있으며 비교대상이 null에서 지정한 기본값으로 변했습니다. int는 보통 기본값이 0이고 그외는 null 이기 때문에 지정해놓은 기본값이 없다면 null인 경우에 무시됩니다.

 

4번과 5번을 보면 null값과 기본값으로 지정한 값 모두 사라졌다는 것을 알 수 있습니다.

 

 

JsonSerializerSettings 에는 그 외에도 StringEscapeHandling, TypeNameHandling, Converters 등이 있는데 제가 사용해본적은 없기 때문에 이번 설명에서는 제외하였습니다.

 

 

JsonConvert.SerializeObject() 에 대한 설명이었습니다.

-----------------------------------------------------------------------------------------------------------------

인스턴스를 json형태로 변환했는데 json형태의 문자열을 클래스의 인스턴스로 변환하는 것도 있어야겠죠?

 

우선 이해를 돕기 위한 코드를 작성하겠습니다.

 

class Example
{
    public List<Fruit> Fruits;
    public string Str;
    public int Number = 0;
    public int NumberDefault0 = 0;
    public bool IsOk;
    public string? NullableStr;
    public int? NullableInteger;

    public override string? ToString() => JsonConvert.SerializeObject(this);
    public string? ToStringJson() => JsonConvert.SerializeObject(this, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore});
    public string? ToString2() =>
        "Fruits=" + (Fruits == null ? "null" : Fruits.ToString()) +
        "\n&Number=" + Number +
        "\n&NumberDefault0=" + NumberDefault0 +
        "\n&IsOk=" + IsOk +
        "\n&NullableStr=" + (NullableStr == null ? "null" : NullableStr.ToString()) +
        "\n&NullableInteger=" + (NullableInteger == null ? "null" : NullableInteger.ToString());
}

class Fruit
{
    public string Name;
    public int price;
}

Example 클래스를 출력 가능하게 바꾸는 클래스를 기본 ToString()을 override 한것 외에도 두개를 더 만들었고

 

 

다음은 실행 코드 입니다.

Example example = new()
{
    Str = "문자열",
    Number = 10,
    NumberDefault0 = 0,
    IsOk = true,
    NullableStr = null,
    NullableInteger = null
};

// 1.------------------------------------------------
var exampleJson0 = JsonConvert.SerializeObject(example);
Console.WriteLine($"example: {example}");
Console.WriteLine($"example.ToStringJson(): {example.ToStringJson()}");
Console.WriteLine($"example.ToString2(): {example.ToString2()}");

// 2.------------------------------------------------
var ex0 = JsonConvert.DeserializeObject(exampleJson0);
Console.WriteLine($"{ex0.GetType().Name}: {ex0}");
//Console.WriteLine($"ex0.ToStringJson(): {ex0.ToStringJson()}");
//Console.WriteLine($"ex0.ToString2(): {ex0.ToString2()}");

// 3.------------------------------------------------
var ex1 = JsonConvert.DeserializeObject<Example>(exampleJson0);
Console.WriteLine($"{ex1.GetType().Name}: {ex1}");
Console.WriteLine($"ex1.ToStringJson(): {ex1.ToStringJson()}");
Console.WriteLine($"ex1.ToString2(): {ex1.ToString2()}");

실행 코드에서

 

우선 설명드릴 내용은 json 형태의 string문자열을 객체로 변환하는 함수는 

 

JsonConvert.DeserializeObject(json문자열)

JsonConvert.DeserializeObject<T>(json문자열); 

 

 

1. 단락은 example 인스턴스를 json으로 변환하여 exampleJson0 인스턴스에 string 문자열로 담았던 내용이구요

2. 는 JsonConvert.DeserializeObject() 를 이용해서 다시 객체로 만든 것입니다.

2.에 하단부는 왜 주석처리를 해놨냐, JsonConvert.DeserializeObject()로 리턴 받은 ex0의 객체는 Example클래스의 인스턴스가 아니기 때문입니다.

 

더 정확히 말하면 타입을 지정하지 않아 JObject형태가 되버린 ex0은

Object클래스의 메소드인 .ToString()은 쓸 수 있으나

Example클래스의 메소드인 ToStringJson()와 ToString2()를 사용할 수 없기 때문입니다.

 

이를 보정하기 위한 3.은 json문자열을 원하는 클래스타입을 제네릭으로 작성하면 해당 클래스타입으로 받아옵니다.

따라서 Example? 형태이며, Example클래스의 메소드를 모두 사용할 수 있게 됩니다.

 

받아오는 형태를 모르는 것이 아니라면 꼭 제네릭으로 사용하시는 것을 권장드립니다.

 

 

이것으로 Newtonsoft.Json 에대한 설명을 마칩니다. 

부족한 부분이나 문의는 댓글로 남겨 주시면 반영하도록 노력하겠습니다. 

반응형
반응형

안녕하세요. 이 카테고리의 지난 글에서 Serilog를 설명드렸는데요.

(이전 글 링크 : https://pichen.tistory.com/48)

 

[c# - dotnet nuget] serilog

블로그에는 글을 너무 안쓴 모양이다... 아주 초반부터 사용하던 이런 것조차 없어서 작성한다. Serilog는 로그를 아주 손쉽게 콘솔 또는 파일로 작성가능하도록 하는 너겟? 누겟? 패키지이다. 참

pichen.tistory.com

 

해당 내용은 Console app, Winform, Wpf 등에서 사용할 수 있는 방법이었구요.

 

web을 할때는 로그를 어떻게 남기냐? -> asp.net core 버전인 Serilog.AspNetCore가 있습니다.

 

 

여기서는 asp.net core mvc 혹은 wep api 프로젝트에서의 사용법을 설명드리겠습니다.

 

 

먼저 visual studio 내 도구 탭에 nuget 패키지 관리자를 통해서 다음과 같이 검색하면

 

Serilog.AspNetCore가 있습니다. 사용하시는 버전에 맞게 설치하시기 바랍니다.

 

다만 이번 예제는 .net7.0버전을 사용했다는 점을 알려드립니다.

 

program.cs에서 

using Serilog;를 적어주시고

var builder = WebApplication.CreateBuilder(args);의 아래에 다음과 같이 입력하시면 됩니다.

Log.Logger = new LoggerConfiguration()
    .ReadFrom.Configuration(builder.Configuration)
    .CreateLogger();

builder.Host.UseSerilog();

var configuration = new ConfigurationBuilder()
            .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
            .Build();

이전 글의 세팅과는 사뭇 다르죠,

 

이전 글의 방법대로도 asp.net core에서 사용할 수도 있으나

 

 

asp.net core에서는 appsettings.json파일에서 설정정보를 받아오기 때문에 그걸 가능하게 하기 위한 방법입니다.

이전 글의 세팅도 사용가능하나 추천하지 않기 때문에 따로 적지는 않겠습니다.

 

만일 개발자버전에서는 다르게 지정하고 싶다면 IsDevelopment() 블록 내에 다른 .json 파일을 적어주시면 됩니다.

여기서는, 개발버전의 디폴트 .json파일인 appsettings.Development.json을 이용하도록 적었습니다.

 

if (app.Environment.IsDevelopment())
{
	// 여기 부분
	  var configuration = new ConfigurationBuilder()
              .AddJsonFile("appsettings.Development.json", optional: false, reloadOnChange: true)
              .Build();

}

이렇게 입력한 후 로그를 남길 컨트롤러로 돌아갑니다.

 

이후 부터는 사실 이미 설치되어있는 ILogger를 이용하면 되는데요. 그래도 혹시 처음 접하시는분들을 위해 방법을 남깁니다.

[Route("[controller]")]
public class HomeController : ControllerBase
{
    private readonly IConfiguration _configuration;
    private readonly ILogger<HomeController> _logger;

    public HomeController(ILogger<HomeController> logger, IConfiguration configuration)
    {
        _logger = logger;
        _configuration = configuration;
    }
}

이와 같이 private readonly + 생성자의 변수를 입력하면 ILogger에 대한 의존성 주입이 완료되며,

ILogger로 작성한 로그가 Serilog 라이브러리에서 파일이나 콘솔에 남길 수 있게 됩니다.

 

각각 메소드에

_logger.LogVerbose();

_logger.LogDebug();

_logger.LogInformation();

_logger.LogWarning();

_logger.LogFatal();

_logger.LogError();

 

등과 같이 사용하실 수 있습니다. 괄호 내에는 string 변수를 입력하면 되며, 서식 문자열 등은 앞에 Serilog 게시글을 확인하시기 바랍니다.

 

마지막으로 appsettings.json에 어떻게 입력하면 되는지 설명드리겠습니다.

 

"Serilog": {
  "Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.File" ],
  "MinimumLevel": {
    "Default": "Information",
    "Override": {
      "Microsoft": "Information",
      "System": "Error"
    }
  },
  "WriteTo": [
    {
      "Name": "Console"
    },
    {
      "Name": "File",
      "Args": {
        "path": "로그파일을 저장할 경로이며, .txt 바로앞에년월일이 찍힙니다. log-.txt",
        "rollingInterval": "Day",
        "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}"
      }
    }
  ]
}

앞 글의 Serilog를 입력할때 넣었던 설정정보가 모두 들어있죠.

Using에는 Serilog.AspNetCore를 설치하면 이미 담겨있는 라이브러리들입니다.

 

using

   "Serilog.Sinks.Console" - console창에도 찍으려면 기입

   "Serilog.Sinks.File" - file을 남기려면 기입

MinimumLevel - 최저 로그레벨, 최저 로그레벨 자체에 대한 설명은 이전 Serilog 설명글에 남겨두었기 때문에 생략합니다. (이전 글을 보고 싶으시다면 글의 최상단으로 이동하시면 링크가 있습니다.)

 

위에 using은 라이브러리 참조를 나타냈다면, 실제로 쓰기역할을 하는 부분이

WriteTo 입니다. 

"Name": "Console" 기입시 콘솔에 찍히게 되고

"Name": "File"  기입시 로그파일이 남게 됩니다.

 

위에 using 부분이랑 console끼리, File끼리 짝이라고 생각하시면 됩니다.

 

WriteTo 내 Args 에는 파일에 대한 설정정보를 기입할 수 있으며

"path"에는 로그파일이 작성될 경로와 파일명을 입력하는데, 확장자 바로 앞에는 날짜가 자동으로 붙습니다.

가령 "log-.txt"로 입력하게되면 프로젝트 경로 내 log-20231027.txt 파일로 로그파일이 생성됩니다.

그러나 서버에 사이트를 올릴때에는 절대경로로 적어두는 것이 좋다고 생각하고, 이 형식을 감안해서 파일이름도 별도로 정해두어 알아보기 쉽게하는 것이 좋다고 생각합니다.

"rollingInterval" 은 파일이 새로 생성되는 주기이고 "Day"로 적으면 하루가 지나면 새로운 로그파일이 생성됩니다.

"outputTemplate"은 로그가 찍히는 line의 형식을 나타냅니다. 기본 세팅을 변경하고싶다면 여기서 변경하면 됩니다.

 

 

이상으로 Serilog.AspNetCore에 대한 기본적인 설명을 마칩니다.

사용하면서 궁금한 점이나 추가했으면 좋겠는 점은 댓글로 남겨주시면 업데이트하도록 하겠습니다.

 

감사합니다.

 

반응형
반응형

블로그에는 글을 너무 안쓴 모양이다... 아주 초반부터 사용하던 이런 것조차 없어서 작성한다.

필자의 github

 
Serilog는 로그를 아주 손쉽게 콘솔 또는 파일로 작성가능하도록 하는 너겟? 누겟? 패키지이다.
 
참고로 콘솔에도 로그를 찍고 싶다면 : Serilog.Sinks.Console을,
로그파일로도 작성하고 싶다면 : Serilog.SInks.File을 같이 설치 해주어야 한다.
 
설치과정만 세분화 되어있을 뿐 각 용량이나 설치시간은 매우 짧고 using Serilog만 치면 나머지 둘은 알아서 따라오기 때문에 아주 간편하다.
 
필자같은 경우는
로그찍을때 파일 경로 등 때문에
LogHandler 라는 이름으로 클래스를 하나 작성하는데 그 방법대로 설명하려고 한다.
 

class LogHandler
{
    public string today;
    public string[] todayYMD;
    public string logFilePath;

    public LogHandler()
    {
        today = DateTime.Now.ToString("yyyy-MM-dd");
        todayYMD = today.Split('-');
        logFilePath = $"프로젝트명-logs/{todayYMD[0]}/{todayYMD[0]}-{todayYMD[1]}/프로젝트명-log-.txt";
    }
}

사실 이 방법은 바보 같을 수도 있다. 결국에는 logFilePath 하나만을 사용하기 때문이다.
하지만 한번 만들어두니 알아보기도 쉬워서 매번 복사해다 쓰고 있다.
 
------------------------------------------------------
 
 
다음은 main() 또는 program.cs에 작성해야하는 부분이다.
using Serilog; 하나면 Serilog.Sinks~류 들을 별도로 설치했다고해서 using도 또 써줘야하는건 아니다.
 
아래 소스는 위에서 만든 클래스를 선언하여 logFIlePath를 담을뿐
실질적으로Log.Logger 부분만 봐도 무방하다.

using Serilog;

LogHandler logHandler = new();   

Log.Logger = new LoggerConfiguration()
    //.WriteTo.Console()                          // Serilog.Sinks.Console를 설치한 경우 사용가능
    .WriteTo.File(                                // Serilog.Sinks.File를 설치한 경우 사용가능
        logHandler.logFilePath,                   // 로그를 지정할 파일 경로 
        rollingInterval: RollingInterval.Day,
        retainedFileCountLimit: null,
        fileSizeLimitBytes: 50 * 1024 * 1024,
        rollOnFileSizeLimit: true,
        outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [{Level}] {Message}{NewLine}{Exception}")   
    .MinimumLevel.Information()    // 로그 최저레벨을 설정한다.                
    // .MinimumLevel.Is(Serilog.Events.LogEventLevel.Information) // 바로 위 .MinimumLevel.Information()를 제거하고 이 방법을 사용하면 설정 등으로 가져와서 최저레벨을 지정할 수도 있다.
    .CreateLogger();

WriteTo.Console() 소스에 주석으로 적혀있기 때문에 패스하고
 
WriteTo.File()에는 여러가지 변수가 올 수 있는데
 
1. rollingInterval은 새로 파일을 만드는 기간 또는 주기를 말하는 것이다.
위 소스에는 RollingInterval.Day이기 때문에 일이 바뀔때마다 새로운 날짜의 파일이 생성된다.
 
2. retainedFileCountLimit은 로그파일 갯수 상한을 정하는 것이다.
 
필자의 경우는 서버 관리자가 직접 지울만큼 쌓이기 전까지는 보관하는 것을 선호하기 때문에 null을 입력했다.
그러나 하드용량이 매우적은 컴퓨터나 서버에서 사용한다면 제한을 두는 것이 좋다.
 
3. fileSizeLimitBytes는 로그파일의 최대크기를 정해놓는 것으로 byte단위로 입력하면 된다. 필자의 짧은 경험상으로는 50mb가 넘어가면 키는데 좀 딜레이가 생기는 것 같아서 저렇게 작성했다.
 
4. rollOnFileSizeLimit: 은 3번에서 지정한 로그파일의 용량 한계가 넘어가면 어떻게 할지를 결정하는 것이다. true면 다음 파일을 생성하고 false면 로그파일의 용량이 한계에 다다르면 더이상 파일을 작성하지 않는다. 따라서 근방의 로그가 작성되지 않길 바랄리가 없기 떄문에 true를 해야만 한다.
 
5. outputTemplate: 은 찍히는 로그의 모양을 나타낸 것이다. 미세한 시간차이를 확인하고 싶은게 아니라면 fff를 빼도 좋다.
시간 그 부분 제외하고는 딱히 수정해본적이 없기 때문에 필요하다면 Template작성방식을 검색해보아야 할 것이다.
 
그리고 
 
.MinimumLevel. 은 찍히는 최저로그레벨을 정하는 것이다.
필자가 기억하기로 Verbose, Debug, Information, Waring, Error, Fatal  정도가 있는데
우측으로 갈수록 높은 레벨이다. 따라서 .MinimumLevel.Information()으로 작성하면 Information, Waring, Error, Fatal 는 찍히고 Verbose, Debug는 무시된다.
( .MinimumLevel.Information() - Information()자리에 Verbose()부터 Fatal()까지 모두 올 수 있다)
 
.MimimumLevel은 Is()라는 함수도 갖고있는데 변수로 Serilog.Events.LogEventLevel 안에있는 프로퍼티들을 사용할 수 있다. 위의 .MinimumLevel.과 동일한 기능이다. 그러나 Is()가 매력적인 것은 개발시 미리 지정이 아니라 실행시 어떤 로직에 따라서 로그 최저레벨을 정할 수 있다는 점이다.
 
예를 들면 .MinimumLevel.Is(WhatIWant()) 로 두고
public WhatIWant()의 리턴값으로 Serilog.Events.LogEventLevel.Warning 등을 사용하여 결정할 수있다.
 
 
------------------------------------------------------
 
마지막으로 예제 및 serilog의 서식문자열 사용법을 설명하고 포스팅을 마치도록 하겠다.

// 서식문자열의 사용방법
// Console.WriteLine()에서는 {순서,글자너비 + 좌우 정렬여부} 
// -> Console.WriteLine("{0,-10} | {1,-20}", "asdfsadfs", "test1string");
// 방식으로 사용하지만 Serilog의 서식문자열 형식은 별도로 정해져있음
// 순서가 아닌 아무런? 단어를 사용하고 쉼표(,) 앞뒤에 공백이 없어야만 함 
// (필자는 버릇처럼 띄어쓰기를 넣다가 꽤많은 출력실패를 경험하였음)

Log.Information("| {Status,-10} | {Address,-30} | {RoundtripTime,-15} | {BufferLength,-20} |",
	"Status", "Address", "RoundTrip time", "Buffer size");
    
// 다양한 로그레벨로
string a = "아무 문자열";
Log.Verbose($"아무아무아무아무 {a}");
Log.Debug("껄껄껄");
Log.Information(@"C:\directory\ddong");
Log.Waring("위험!! 위험!!");
Log.Error("에러가 나부러쓰");
Log.Fatal("치명적~");

 
마지막으로,  

throw new Exception("여기서 적는말은 에러다");

throw new Exception으로 string 문자열을 던지면 익셉션이 던져진 것과 별개로
Log.Error에 적은 것처럼 로그가 남는다.
 
 
 
 
asp.net core 프로젝트에서도 appsettings.json으로 설정정보를 담은 serilog를 사용할 수 있다.
이는 차후에 적도록하겠다. 

23년에 이 글과 비슷하게 작성했는데 링크를 깜빡하여 25년 초에 붙입니다.
link:
https://pichen.tistory.com/m/49

[c# dotnet nuget] Serilog.AspNetCore

안녕하세요. 이 카테고리의 지난 글에서 Serilog를 설명드렸는데요. (이전 글 링크 : https://pichen.tistory.com/48) [c# - dotnet nuget] serilog 블로그에는 글을 너무 안쓴 모양이다... 아주 초반부터 사용하던

pichen.tistory.com

반응형
반응형

안녕하세요.

 

플러터 공식홈페이지 -> getstart

오늘은 flutter 설치방법을 알려드릴텐데, 그전에

 

mac OS, 맥킨토시가 운영체제인 맥북을 사용하는 개발자라면

 

반드시 알아야하는 homebrew에 대해 알아보도록 하겠습니다.

 

왜냐하면 플러터를 homebrew를통해서 설치할 것이기 떄문이죠!

 

 

 

 

참고로 homebrew를 사용하지 않으면 flutter 공식 홈페이지의 macOS install을 따라...

뭐를 치고, 다운받고, 경로를 설정하고.... 아주 복잡합니다.

 

 

 

 

그러니까 homebrew를 깔아봅시다!

 

참고로 저는 맥북 m2 사용자 입니다.

--------------------------------------------------------------------------

1. homebrew 설치법

homebrew의 사이트를 가면

 

https://brew.sh/

 

Homebrew

The Missing Package Manager for macOS (or Linux).

brew.sh

이런 화면이 나옵니다.

 

하단에 주소를 포함한 명령어를 복사하거나

하단우측에 문서아이콘을 누르면 복사되는데

 

이를 터미널붙여넣기 해주면 homebrew가 설치 됩니다!

버전이나 다른 사유로 해당 명령어가 미세하게 바뀔 수 있기 때문에 적어드리진 않고

사이트에 접속해서 확인하시기 바랍니다.

 

homebrew는 간단한 명령어로 다양한 프로그램을 설치하거나 정보확인을 할 수 있기 때문에 관련해서 지속적으로 관심을 가져도 좋을 것 같아요.

 

---------------------------------------------------------------------------------

 

2. flutter 설치법

homebrew 상단에 flutter를 검색해보면 오늘 기준으로는 이런 화면이 나옵니다.

 

터미널  brew install --cask flutter 라는 명령어를 치면 설치가 됩니다!

그러나 완전히 설치 됐다고 볼 수 없어요. 왜냐면 그럼 어디서 개발하고 개발된 내용은 어디서....????!?!?!?

 

제 기준 맥북에서 vs code를 통해서 flutter 프로젝트를 빌드했는데 거기까지 알려드려야겠죠!

 

---------------------------------------------------------------------------------

 

3. VS code

 

https://code.visualstudio.com/ 에 접속하여 macOS-Stable로 설치하시면 됩니다.

 

 

설치 후에는 vs code를 실행하고 extensions tab에서 flutter가 install되어있지 않다면 설치하도록하자. 아까는 sdk를 설치한거고 지금은 flutter 개발을 vs code로 편리하게 할 수 있도록하는 것을 설치하는 것이다..

flutter는 dart라는 언어를 사용하므로 Dart도 검색해서 설치하도록 하자.

 

또한 상단 view 탭에서 command palette를 클릭 후 shell command 또는 code 라고 치면

Shell Command: Install 'code' command in PATH 가 나오는데 이것을 클릭하면 code 명령어를 칠 수 있게 된다. 반드시 설치하도록 하자.

---------------------------------------------------------------------------------

4. 시뮬레이터 설치 - Xcode(참고로 android 시뮬레이터 설치는 window에서 flutter 설치시에 설명할 예정입니다.)

 

flutter에서 디버깅을 하려면 아이폰에 적용했을때 어떤 화면이 나타났을까를 볼 수 있어야겠죠

이러한 기능을 하려면 mac os의 개발자 도구인 Xcode를 설치하여야 합니다.

 

 

공식 홈페이지에 현재 나와있는 방법을 설명 드리겠습니다.

 

영어를 잘하시거나 업데이트 등을 거쳐 제가 적은 방법이 더이상 소용 없는 경우에는 아래 링크를 타고 들어가서 확인하세요!

 

https://docs.flutter.dev/get-started/install/macos#install-xcode

 

macOS install

How to install on macOS.

docs.flutter.dev

1. App Store에서 XCode를 검색 후 설치

2.  터미널에

 sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
 sudo xcodebuild -runFirstLaunch

를 치고 엔터 후 다시 아래를 입력 후 엔터

 sudo xcodebuild -license

---------------------------------------------------------------------------------

5. 터미널에 flutter doctor를 쳐보자!

android studio, Xcode, vscode, chrome browser 등등 플러터를 사용할때

 

쓸 수 있는 모든 것들이 제대로 세팅되어있는지 확인해 볼 수 있다.

 

브라우저는 설치가 당연히 되어있을 것이라 생각하고 따로 설명 안했는데 혹시 설치가 되어있지 않다면 구글에 크롬을 쳐보도록 하자

 

잘 따라 왔다면 저기에서 Xcode, vscode, chrome browser는 초록불이 들어와있을 것이다.

 

---------------------------------------------------------------------------------

 

6. 마지막이다. 프로젝트를 생성해보자!

 

1. 터미널에서 프로젝트를 생성할 경로를 들어가자

cd 폴더명 으로 경로를 이동할 수있다. ex) a폴더에 들어와있는데 a폴더 내 b폴더로 들어가려면 - cd b 를치면 a\b로 이동

 

2. 터미널에 flutter create 프로젝트명

입력한 프로젝트명으로 해당경로에 flutter 프로젝트가 생성된다.

 

3. cd 프로젝트명

생성한 프로젝트 내로 들어간다.

 

4. 터미널에 code .

('.' 의도해서 친거다.)

이러면 생성한 flutter 프로젝트를 vs code로 개발가능하게 실행된다. 참고로 이 4번을 위해서 vs code에서 명령어 팔레트를 통해서 code를 설치했다. 다른 기능도 많으니 다시 한번 무조건 설치를 추천한다.

 

 

android를 설치하러 오신분들도 많겠지만.... 필자는 지금 맥북과 윈도우 모두 설치 완료했는데 조금 귀찮아서 뺐다.

윈도우에서 flutter 설치하는 포스팅에서 맥북의 homebrew같은 chocolatey설치 방법과 android studio 설치 후 에뮬레이터 설치 방법을 다루도록 하겠다.. 언제 쓸지는 ...

 

 

 

반응형
반응형

vs code가 제일 만들기 편하지만 .txt로 만든 뒤 확장자를 .bat로 바꿔 배치파일로 바꿔줘도 무방하다.

 

 

개발한 파일 소스를 한줄한줄 설명을 하는 방식으로 진행한다.

모든 보라색 글씨를 지우고 만들면 배치파일로써 작동할 것이다.

 

 

그러나 이 레지스트리는 백업 즉,

레지스트리를 보관할 파일로 복사 후 존재하는 레지스트리를 삭제하기 때문에

 

"지워도 되는 레지스트리"를 확인하고 실행하거나

"레지스트리 삭제" 부분을 설명을 이해한 후 제거하고 실행하기를 권장한다.

 

잘못된 실행으로 인한 피해는 글쓴이가 보상해줄 수 없다.

 

설명을 시작하겠다.

 

-----------------------------------------------------------------------------------------------------------------

@echo off 를 적어주고

 

set BackupDirectory=C:\backupFolder 레지스트리를 백업할 경로를 변수에 담아 준다(set)

set BackupFile=%BackupDirectory%\backupregistrylist.reg 해당 경로에 저장할 파일명과 확장자를 적어준다(set)

 

(%변수%은 shell script에서 기본적으로 변수를 사용하는 방법이다.)

 

set RegistryPath=HKEY_CURRENT_USER\할튼\백업할레지스트리경로 

ㄴ 특정 레지스트리를 찍기 뭐해서 백업하고자 하는 레지스트리를 각자 입력해서 set으로 변수에 담아준다.

 

reg query "%RegistryPath%" >nul 2>&1

if %errorlevel% equ 0 (

ㄴ 레지스트리가 있으면 이라는 뜻이다.

     정확히는 레지스트리를 체크하고 에러가 나지 않았으면 이라는 뜻이다.
    echo "%RegistryPath%" exists. 

    ㄴecho는 파이썬에서 print() 또는 c#에서 Console.Write() 정도로 생각하면 된다. 
    rem 

    ㄴ rem은 대략적으로 "다음을 출력한다" 라고 생각하면 된다. 같은 줄에 쓴 글은 주석으로 처리된다.
        if not exist "%BackupDirectory%" (

        ㄴ BackupDirectory로 설정한 경로가 없으면
            mkdir "%BackupDirectory%"

            ㄴ make directory의 약자이다. 해당 경로를 만든다.
            echo %BackupDirectory% not exist. make directory.

            ㄴ 위에도 설명했다. 화면에 출력하는 용도이다.
        )
        reg export "%RegistryPath%" "%BackupFile%" /y

        ㄴ RegistryPath 경로의 레지스트리를 BackupFile위치로 write한다. /y는 덮어쓰기 허용을 말한다.
        echo registry backup success.
        ㄴ echo 생략


        reg delete "%RegistryPath%" /f

        ㄴ 레지스트리를 삭제한다 /f는 뭔가 사용중이어도 강제로 진행한다는 뜻이다.
        echo %RegistryPath% is deleted.

        ㄴ echo 생략
) else (
    echo %RegistryPath% not exists.

    ㄴ if문이 레지스트리가 있으면 이었기 때문에 이자리에는 없는 경우에 실행할 부분이 나온다.

    ㄴ 이 예제에서는 레지스트리 경로가 존재하지 않는다라고 출력했다.
)

 

-----------------------------------------------------------------------------------------------------------------

 

이 파일 실행 후 생성된 레지스트리 파일 backupregistrylist.reg를 실행하면 백업했던 레지스트리가 다시 설치된다.

 

그대로 따라 하는 과정에서 shell script 언어가 어떻게 쓰이는지 아주 간단하게 나마 받아들일 수 있기를 바란다.

 

반응형
반응형

안녕하세요. 먼저 테이블과 프로시저를 적고 하단에 설명을 하겠습니다.

ASP.NET CORE 7.0 버전의 MVC 모델에 sql server를 사용하여 만든 토이프로젝트 내에 테이블과 프로시저입니다.

 

각 테이블은 ENTITY FRAMEWORK CORE의 모델을 이용해 프로그램이 생성해준 테이블입니다.

 

CREATE TABLE [dbo].[Worker] (
    [Id]        INT           IDENTITY (1, 1) NOT NULL,
    [CompanyId] INT           NOT NULL,
    [Name]      NVARCHAR (20) NOT NULL,
    [Email]     NVARCHAR (20) NOT NULL,
    [Phone]     NVARCHAR (12) NOT NULL,
    [IsDelete]  NVARCHAR (1)  DEFAULT ('N') NOT NULL,
    CONSTRAINT [PK_Worker] PRIMARY KEY CLUSTERED ([Id] ASC),
    CONSTRAINT [FK_Worker_Company_CompanyId] FOREIGN KEY ([CompanyId]) REFERENCES [dbo].[Company] ([Id]) ON DELETE CASCADE
);

CREATE TABLE [dbo].[User] (
    [Id]       INT           IDENTITY (1, 1) NOT NULL,
    [LoginId]  NVARCHAR (20) NOT NULL,
    [Password] NVARCHAR (20) NOT NULL,
    CONSTRAINT [PK_User] PRIMARY KEY CLUSTERED ([Id] ASC)
);

CREATE TABLE [dbo].[Company] (
    [Id]       INT           IDENTITY (1, 1) NOT NULL,
    [Name]     NVARCHAR (20) NOT NULL,
    [Address]  NVARCHAR (50) NOT NULL,
    [Contact]  NVARCHAR (12) NOT NULL,
    [IsDelete] NVARCHAR (1)  DEFAULT ('N') NOT NULL,
    CONSTRAINT [PK_Company] PRIMARY KEY CLUSTERED ([Id] ASC)
);

CREATE TABLE [dbo].[ChangeHistory] (
    [Id]          INT            IDENTITY (1, 1) NOT NULL,
    [UserId]      INT            NOT NULL,
    [LoginId]     NVARCHAR (MAX) NOT NULL,
    [CompanyId]   INT            NULL,
    [CompanyName] NVARCHAR (MAX) NULL,
    [WorkerId]    INT            NULL,
    [WorkerName]  NVARCHAR (MAX) NULL,
    [ActIP]       NVARCHAR (MAX) NOT NULL,
    [Act]         NVARCHAR (MAX) NOT NULL,
    [ActDate]     DATETIME2 (7)  DEFAULT (sysdatetime()) NOT NULL,
    CONSTRAINT [PK_ChangeHistory] PRIMARY KEY CLUSTERED ([Id] ASC)
);

 

 


 다음은 sql server내에서 사용한 sql 파일을 그대로 가져왔습니다.
  한 번 설명한 내용은 그 밑에선 생략하겠습니다!



drop procedure if exists CreateCompany 

-- drop proceduce 는 말그대로 프로시저를 db에서 drop 시킨다(삭제한다) 이며
-- if exists를 붙이므로서 존재하면 삭제하는 기능입니다.
-- 보통 spl 파일 전체를 실행 시키므로 프로시저나 테이블생성문 앞에 붙여 사용합니다.

go	-- go는 앞문장과 뒷문장을 구분지어주는 역할을 합니다.



-- CreateCompany 이라는 stored procedure를 생성합니다.
-- 변수는 @Name ~ @Act 까지 총 9개를 받아서 처리하며
-- 이 프로시저를 실행시키면
-- Company 테이블에는 @Name ~ @Address 변수를 이용하여 한개의 레코드를 insert하고
-- ChangeHistory 테이블에는 나머지 6개의 변수를 이용하여 한개의 레코드를 insert 합니다.

create procedure CreateCompany
	@Name  NVARCHAR (20),
	@Contact  NVARCHAR (12),
	@Address  NVARCHAR (50),

	@UserId int,
	@LoginId  nvarchar(MAX),
	@CompanyId int ,
	@CompanyName nvarchar(MAX),
	@ActIP nvarchar(MAX),
	@Act nvarchar(1)
AS
BEGIN
	insert into Company (Name, Contact, Address)
	values (@Name, @Contact, @Address)

	insert into ChangeHistory (UserId, LoginId, CompanyId, CompanyName, ActIP, Act)
	values (@UserId, @LoginId, @CompanyId, @CompanyName, @ActIP, @Act)
END
go


-- declare는 프로시저 내에서 변수를 선언하는 문법으로 해석자체가 '선언하다'입니다.
-- @CompanyName을 선언하여 
-- 바로 아래 문장인
-- select @CompanyName =Name from Company where Id = @id;
-- Company 테이블에서 Id가 @id인 레코드(Id는 Primary key 이므로 단일 레코드가 셀렉됩니다.)
-- 중 Name 컬럼의 값을 @CompanyName이라는 변수에 집어 넣는다.
-- @DorR은 Delete or Restore의 줄임말로 
-- 		Y인 경우 Delete를(IsDelete = 'Y'),
-- 		N인 경우 Restore를(IsDelete = 'N')합니다.

-- 회사를 삭제하는데 회사에 worker가 있으면 worker들도 모두 IsDelete를 Y로 만드는 프로시저입니다.
-- 복구시에는 회사만 복구됩니다.

drop procedure if exists DorRCompany 
go
create procedure DorRCompany
	@id int,
	@DorR nvarchar(1),
	@UserId int,
	@LoginId  nvarchar(MAX),
	@ActIP nvarchar(MAX),
	@Act nvarchar(1)
as
begin
	declare @CompanyName NVARCHAR(MAX);
	select @CompanyName =Name from Company where Id = @id;

	update Company set IsDelete = @DorR where Id = @id;
	insert into ChangeHistory (UserId, LoginId, CompanyId, CompanyName, ActIP, Act)
	values (@UserId, @LoginId, @id, @CompanyName, @ActIP, @Act)

	declare @WorkerCountInCompany int;
	select @WorkerCountInCompany=count(CompanyId)  from Worker group by CompanyId having CompanyId = @id
	if @WorkerCountInCompany > 0 and @DorR = 'Y'
	begin
		update Worker set IsDelete = 'Y' where CompanyId = @id
	end
end
go

drop procedure if exists CreateWorker 
go
create procedure CreateWorker
	@CompanyId int,
	@Name nvarchar(MAX),
	@Email nvarchar(MAX),
	@Phone nvarchar(MAX),

	@UserId int,
	@LoginId nvarchar(MAX),
	@ActIP nvarchar(MAX)
AS
begin
	declare @WorkerId int;
	insert into Worker (CompanyId, Name, Email, Phone)
	values (@CompanyId, @Name, @Email, @Phone);

	select @WorkerId =Max(Id) from Worker;
	insert into ChangeHistory (UserId, LoginId, WorkerId, WorkerName, ActIP, Act)
	values (@UserId, @LoginId, @WorkerId, @Name, @ActIP, 'C')
end
go

drop procedure if exists DorRWorker
go
create procedure DorRWorker
	@id int,
	@DorR nvarchar(1),
	@UserId int,
	@LoginId  nvarchar(MAX),
	@ActIP nvarchar(MAX),
	@Act nvarchar(1)
as
begin
	declare @WorkerName NVARCHAR(MAX);
	select @WorkerName =Name from Worker where Id = @id;

	update Worker set IsDelete = @DorR where Id = @id;
	insert into ChangeHistory (UserId, LoginId, WorkerId, WorkerName, ActIP, Act)
	values (@UserId, @LoginId, @id, @WorkerName, @ActIP, @Act)
end

 

 

 

다음은 실행방법 예제입니다.

본문 제일 상단에서 설명한 실행환경에서 사용했고 Controller 중 메소드 하나를 통째로 가져왔습니다.

 

var 선언할변수명 = new SqlParameter(쿼리에서쓰이는변수명, 값) 으로 선언한 값을

_context.Database.ExecuteSqlRawAsync(쿼리문, 선언한변수1, 선언한변수2, ... )

으로 실행시킬 수 있습니다.

 

여기서 Database는 정말로 데이터 베이스를 가리키는 말로 보통의 Context뒤에 오는 테이블명과는 다릅니다.

프로시저 실행 쿼리문은 하단 예제에 query를 참고하시면 됩니다.

 

SqlParameter()로 선언된 변수들은 변수명.SqlValue로 해당변수의 값을 뽑아내서 사용하는 것이 가능합니다.

 

참고 ) SqlParameter() 대신 일반 변수에 담아 $"{변수명}"으로 사용하는 것이 불가능 하지않으나 동적인 부분에서는 안정성이 떨어진다는 경고 메시지가 나옵니다. 처음 접하시는분들은 이 방법으로도 한번 해보시고 메세지를 직접 보고 어떨때 예상과 결과값이 다르게 나오는 것인지 확인해보는 것도 좋을 것 같습니다.

 

 

 

[HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> Create([Bind("Id,CompanyId,Name,Email,Phone, Company")] Worker worker)
        {
            int userId = HttpContext.Session.GetInt32("userId") ?? 0;
            string? userLoginId = HttpContext.Session.GetString("userLoginId");

            if (userId != 0 && userLoginId != null)
            {
                var p1 = new SqlParameter("@CompanyId", worker.CompanyId);
                var p2 = new SqlParameter("@Name", worker.Name);
                var p3 = new SqlParameter("@Email", worker.Email);
                var p4 = new SqlParameter("@Phone", worker.Phone);
                var p5 = new SqlParameter("@UserId", userId);
                var p6 = new SqlParameter("@LoginId", userLoginId);
                var p7 = new SqlParameter("@ActIP", HttpContext.Connection.RemoteIpAddress?.ToString() ?? string.Empty);

                string query = @"EXEC CreateWorker @CompanyId, @Name, @Email, @Phone, @UserId, @LoginId, @ActIP";
                await _context.Database.ExecuteSqlRawAsync(query, p1, p2, p3, p4, p5, p6, p7);

                _logger.Log(LogLevel.Information,
                    $"delete company => UserId = {p5.SqlValue}, UserLoginId = {p6.SqlValue}, " +
                    $"target = Worker, CompanyId ={p1.SqlValue}, WorkerName = {p2.SqlValue} IP = {p7.SqlValue}, time = {DateTime.Now}");

                await _context.SaveChangesAsync();
                return RedirectToAction(nameof(Index));
            }
            ViewData["CompanyId"] = new SelectList(_context.Company, "Id", "Address", worker.CompanyId);
            return View(worker);
        }

 

 

반응형

+ Recent posts