office 문서의 관리 / 편집을 프로그램으로 하기 위해서는 vb나 c#을 이용해야하죠. 특히 다음과 같은 마소의 본연 라이브러리로 하는데요, 기술의 발달로 .Net Framework 가 아닌 .NET core를 넘어 .NET Stardard에서
Microsoft.Office.Interop.Word
Microsoft.Office.Interop.Excel
Microsoft.Office.Interop.Powerpoint
Microsoft.Office.Interop.Outlook
의 com api를 사용하면 다음과 같은 에러메시지를 맞게 되었죠.
일단 이유에 대해서 설명드리겠습니다. 윈도우 전용이었던 .Net Framework 에서 cross platform을 추구하는 .NET core, .NET Standard로 넘어오면서 기본 세팅하에서는 com api를 인식할 수 없게 되었어요. com api는 윈도우에만 존재하기 때문이죠.
구글링했을때는 ".net core에서는 사용할 수 없다." 라고 기술되어 포기했었는데, 직접참조하면 된다고해서 도전을 다시 해봤죠. 역시나 안됐어요.
그렇다면 어떻게 세팅하면 사용할 수 있을까?
방법은 의외로 간단했어요.
platform 세팅을 any 에서 window로 바꾸는 것이죠.
1. 솔루션 바로 하단의 어플리케이션을 우클릭
2. 애플리케이션 -> 일반
3. 대상 OS를 찾아 기본값인 (없음)을 클릭
4.Windows를 클릭
5. 솔루션 내 종속성 우클릭 후 COM 참조 추가
(Nuget으로 microsoft.office.interop 라이브러리를 추가하면 에러가 나던데, 성공하신분 연락 또는 댓글로 방법 남겨주시기 바랍니다!!)
6. 필요한 라이브러리 추가 ( 스크린샷 예제에서는 Microsoft.Office.Interop.Word 를 사용하기 위해 word com object를 추가)
word com 객체를 추가하였고 이제 using Microsoft.Office.Interop.Word를 사용하면 라이브러리 사용이 가능하답니다.
주의할점!!!
1. 플랫폼을 바꾸면 어떻게 되느냐 - 당연히 지정한 플랫폼에서만 사용가능합니다. window10/11 등의 window os가 설치된 PC 또는 window server에서만 사용이 가능한 것이죠.
2. 위 예제에서 Microsoft Word 16.0 Object Library가 안보이시는 분들 특히, 이 com 객체를 추가하여 라이브러리를 사용하기 위해서는 개발된 어플리케이션이 설치될 pc에 office가 설치되어 있어야 합니다.
3. 이 포스팅을 업로드하는 시점까지 확정된 microsoft의 com api 지원기간은 2029년까지로 알려져있습니다.
Get 방식의 http method는 정보의 조회 등인 경우가 많아 대부분 200번 즉 response.StatusCode 가 HttpStatusCode.OK일 거예요. 다만, 구체적으로 반환되는 HTTP Status Code는 API를 제작한 개발사/개발자에 문의 또는 설명문서를 참조하여 정하시는 것이 좋겠습니다.
다만, 200번대가 보통 성공을 나타낸다고 약속되어져 있습니다. (제가 약속한거 아닙니다.)
따라서 다음과 같은 방법으로 200번대면 성공이라고 가정, 아니면 실패를 예외를 던지는 속성과 함수도 존재합니다.
// response.IsSuccessStatusCode: response가 200번 대면 true를 반환하는 bool값
if(response.IsSuccessStatusCode) {
// 성공
}
// if문도 필요 없이 200번대면 다음 레코드를 읽고, 200번대가 아니면 예외를 던지는 함수
response.EnsureSuccessStatusCode();
응답이 성공했으면 원하는 응답을 읽어와야하고, 그 방법에는 여러가지가 있을텐데요. 두가지를 설명드리겠습니다.
1. 응답을 모두 string으로 받아오는 방법
c#의 함수나 속성명은 상당히 직관적입니다.
[응답]의 [내용]을 [문자열]으로 불러오겠습니다.
(response)(content)(string)
var responseContent = await response.Content.ReadAsStringAsync();
그런데 대부분은 string 자체를 사용하진 않을 것입니다. 약속된 API라면 반환값이 json형태의 문자열이기 때문이죠.
따라서 json형태의 문자열을 다음과 같이 클래스로 바꿔줍니다.( System.Text.Json 필요)
var result = JsonSerializer.Deserialize<ResultClass>(responseContent, new JsonSerializerOptions());
JsonSerializerOptions 옵션 등과 같은 System.Text.Json의 고유 기능은 제가 자세히 설명해놓은 글
저의 경우 NewtonSoft.Json은 .Net Framework에서는 많이 사용했지만. Net Standard에 와서는 내장된 System.Text.Json을 적극 사용하고 있습니다. 특히 아래 2번 방법과 JsonSerializerOptions 을 공유하고 있기 때문에 사용하시는 것을 권장드립니다.
2. 응답내용을 불러올때 클래스로 만드는 방법입니다. (using System.Net.Http.Json 필요)
ResultClass result = await response.Content.ReadFromJsonAsync<ResultClass>();
형태로 사용하거나 var로 선언하긴하지만, 여러분에게 명시적으로 보여드리기 위해 클래스로 선언부를 작성하였습니다.
개발 초기에는 응답내용을 전부 로그를 찍기 위해 1번으로 시작할때도 있지만, 주로 2번을 사용하고 있습니다.
ReadFromJsonAsync의 정의를 보면, System.Text.Json의 JsonSerializerOptions 옵션을 공유합니다.
null처리, 기본값 처리 등을 손쉽게 할 수 있고, 그에 따라 api의 상세한 요구조건을 컨트롤 할 수 있으니 반드시 알아두시면 좋겠습니다.
ResultClass가 무엇인가요? 라고 물으실 수 있을 것 같아요. 아무렇게나 작성한 임의의 클래스입니다.
{"number": 1} 이라는 형태의 json이 왔고 약속된 형태라면,
ResultClass은 아래와 같이 정의가 되었을 것입니다.
class ResultClass{
public int Number {get; set;}
}
// 또는
class ResultClass{
[JsonPropertyName("number")]
public int Sequence {get; set;}
}
serialize했을때 json이 되는 클래스를 만들었다라고 보시면 되겠습니다.
이 포스트는 json, json과 클래스의 관계에 대한 설명글이 아니기 때문에 이 정도만 작성하도록하고, 여태까지 설명 내용을 한 번에 보실 수 있도록 담아보겠습니다.
// json문자열을 역직렬화할 Model 클래스
class ResultClass{
[JsonPropertyName("number")]
public int Sequence {get; set;}
}
// HttpClient 선언
HttpClient httpClient = new(){
BaseAddress = new Uri("https://api.example.com/"),
Timeout = TimeSpan.FromSeconds(30) // 굳이 적지 않아도 되나 기본값이 30초
};
// 필요시 헤더 추가
if (!httpClient.DefaultRequestHeaders.Contains("Key"))
httpClient.DefaultRequestHeaders.Add("Key", "Value");
// Get 방식 호출
HttpResponseMessage response = await httpClient.GetAsync("/class/students");
// 성공 여부 검증
response.EnsureSuccessStatusCode();
// 성공시 json을 역직렬화하여 변수에 담는다.
var result = await response.Content.ReadFromJsonAsync<ResultClass>();
폴더 형태의 구조를 담은 DB 또는 그러한 형태를 반환하는 API를 경험할 일이 많아졌다.
[상위폴더 여부 | 상위 폴더 Id | 폴더 Id | 폴더 이름]
간추려 위와 같은 컬럼이 있는 DB가 있거나,
유사형태의 값을 반환하는 API를 만났다.
DB만 있었다면 DB에서 recurrsive 테이블로 재귀로 뽑아 내는 것도 방법이겠지만
API를 사용해야만 하는 시점에서는 위 형태의 반환 값을 폴더구조로 풀어내는 것에 대한 고민이 있었다.
(공부 뒤 블로그나 github에 올리지 못한 내용이 더 많아서 공부를 뜨문뜨문한다고 보실 분들도 있겠지만...
필자는 계속 책을 구매하거나 인강을 듣거나 아주 작은 단위의 프로젝트를 혼자 진행하며
공부하고 찾아보면서 학습중이었다는 점....)
그러다 DFS를 여기 적용할 수 있겠다는 생각이 들었다.
이 포스트에서는 그러한 예제를 풀어가려한다.
재직회사에서 C#을 쓰고 있어 C#으로 개발하였으니 다른언어를 하셨더라도 원리로 가져가면 될 것 같다.
특히,
폴더명으로 검색하기에는
a \ b
a \ c \ b
의 경우 처럼 이름은 같지만 다른 위치에 있는 것을 프로그램은 구분해서 조회할 수 없기 때문에 폴더구조로 뽑아오거나 id를 뽑아오는 것은 매우 필연적이기 떄문에 이 작업이 필요할때가 많다.
먼저 아래와 같은 클래스를 작성했다.
DB에서 4개 컬럼을 읽어다 넣었거나, API 반환값이거나, 혹은 읽거나 받은 정보를 여기에 담았다고 가정하자.
class Folder
{
public int ParentId { get; set; }
public int Id { get; set; }
public bool HasParent { get; set; }
public string Name { get; set; }
public string NameHierarchy { get; set; }
public string IdHierarchy { get; set; }
}
SMS(문자) 발송, 이메일 발송, 우리나라 기준 한정으로 카카오 메시지 발송이 있을 것입니다.
이번 포스트에서는 그중에서도 이메일 발송에 대해 알아보도록 하겠습니다.
대개 email을 발송하기 위해서는
이메일을 발송하는 이메일 서버(SMTP)와 이메일을 발송할 이메일 계정이 필요합니다.
C#에서는
.Net 라이브러리 중 System.Net.Mail 라이브러리 내
SmtpClient 클래스로 SMTP와 연결하고,
MailMessage 클래스로 이메일을 작성할 수 있습니다.
다만 이 포스트에서는 메일서버 구축이 아닌 보내기에 초점을 두었기 때문에 SMTP서버의 구축 관련한 내용은 다루지 않겠습니다.
참고로 무료로 풀려있는 SMTP 서버들이라 하더라도 일정 갯수가 넘어가게 발송하게되면 상업적용도로 간주되어 결제를 요구하기도 하니, 상업적용도로 사용하실 분들은 SMTP서버를 제공하는 각 기업에 절차에 맞게 결제 등을 하여 사용하시면 됩니다. c# 개발자 또는 엔지니어라면 아마도 Microsoft, 한국에서는 역시 naver, 그리고 kakao도 daum을 인수했기 때문에 smtp서버가 있을 것이라 판단됩니다.
SqlClient도 그렇고 호출을 담당하는 클래스들은 ~Client로 명명하는 것이 규칙으로 보입니다.
먼저, SmtpClient를 선언합니다.
using SmtpClient SmtpServer = new(smtpAddress)
{
Port = smtpPort,
EnableSsl = true,
UseDefaultCredentials = false,
DeliveryMethod = SmtpDeliveryMethod.Network,
Credentials = new NetworkCredential(mailSender, mailSenderPassword)
};
smtpAddress 자리에는 SMTP Server의 주소를 입력하면되고
smtpPort에는 SMTP Server의 열려있는 포트를 이용하면 됩니다. 보통 암호화 보안연결을 사용하면 465나 587, 사용하지 않으면 25가 기본값으로 알려져있습니다.(https가 443, http가 80 인 것과 동일하게 생각하시면 되겠습니다.)
SMTP서버를 직접 구축하신 경우에는 네트워크에 지정한 포트번호를 이용하시면 되겠습니다.
SMTP에 연결할 계정은 NetworkCredential() 클래스에 (계정, 비밀번호) 형태의 생성자가 있으므로, SmtpClient 클래스의 인스턴스 필드 중 Credentials로 입력해주면 됩니다.
SmtpClient는 IDisposabled이 구현되어있어서 선언부 앞에 using을 붙여주면 사용하지 않을때 메모리를 해체할 수 있습니다.
Visual Studio 기준으로 ctrl버튼을 누른채로 작성한 SmtpClient를 클릭하면, 해당 클래스를 모두 보실 수 있는데요.
이외에도 여러 필드가 있지만 포스트의 예제에서는 저 정도만 사용하도록 하겠습니다.
SMTP를 연결했으면 이제는 이메일을 작성해야겠죠.
using MailMessage email = new()
{
From = new MailAddress(mailSender),
Subject = emailSubject,
BodyEncoding = Encoding.UTF8,
IsBodyHtml = true,
Body = "본문 내용",
};
만일 받는이가 1명이라면 MailMessge(string from, string to)라는 생성자가 존재하기 때문에 아래와 같이 선언하는 방법도 있습니다.
왜 선언부에 To, Cc, Bcc를 안적으셨냐고 물으실 수 있는데요, 여러명한테 보낼 수 있다보니 세 변수는 컬렉션으로 구성되어있어요. (class MailAddressCollection : Collection ) 한명한테만 보낸다면 모르겠지만, 여러명한테도 보낼 상황이 있는 작업물이라면 선언하고 추가하는 방식이 좋습니다.
먼저 방금 작성된 함수는 숫자를 매번계산해야 합니다. => 따라서 성능을 개선하기 위해서는 변화하는 값이 아니기 때문에 미리 계산하거나 하드코딩으로 클래스로 초성, 시작글자, 마지막글자의 int나 char값을 작성 후 컬렉션에 넣어서 관리해야 첫번째 함수와 유사한 퍼포먼스를 낼 수 있을 것입니다.
개발자 중 누군가 초성을 포함하여 검색하는 방법을 물었는데, 시간이 남아서 개발을 했다가 초안이 맘에 안들어서 퇴근 후 공부를 마친 시간에 보완작업을 해보았습니다.
현재 다니는 회사에서 사용하는 언어가 C#인데, 회사에 적용해도 좋을 것 같아서 C#을 사용했습니다.
일단 전체 검색대상 목록을 List<string>에 담았다고 가정하여,
다음과 같은 List를 선언합니다.
List<string> words = new List<string> { "가나다", "가노", "가난", "가나두", "가나라", "고리", "기체", "깋체", "나다라", "다라마", "가나a", "a가나", "a가나다" };
그리고 검색어를 전체 구현이라면 앞단에서 받아야 겠지만, 콘솔을 통한 입력도 받지 않고, 선언을 하겠습니다.
검색어는
string keyword1 = "ㄱ나ㄷ";
string keyword2 = "aㄱ나";
이렇게 두개로 하겠습니다.
컬렉션에서 어떤 조건을 만족하는 모든 값을 컬렉션으로 반환하는 함수인 FindAll()을 사용하도록 할텐데요,
words.FindAll(w => 어떤함수(w))로 사용할 어떤함수를 작성해야겠죠.
words.FindAll(word => 어떤함수( word )) 는 아래와 같은 기능을 한다고 이해하지면 됩니다. 실제로는 변수에 델리게이트를 사용하는 것이지만, 이해를 돕기 위해 작성한 것입니다.
List<string> 가짜FindAll(List<string> words){
List<string> a = new();
foreach(string word in words)
{
if(어떤함수(word))
a.Add(word)
}
return a;
}
그러면 어떻게 해야 초성에 맞는 한글들을 표현해낼 것인가를 고민해야겠죠.
우리는 char라는 자료형을 알고있죠. 유니코드로 숫자 범위로서 한글에 대한 범위를 지정해볼 수 있습니다.
따라서 검색어 중 어떤 글자가 'ㄱ', 'ㄴ' 등의 자음 초성일때, 가~깋, 나 ~닣가 포함되어있는지를 범위로 지정해볼 생각을 할 수 있습니다.
간단하게 검색어와 실제단어를 비교한다고 생각하고, n번째 글자가 ㄱ이면 원래 글자가 '가'와 '깋' 사이에 있는지 여부를 반환합니다.
if(검색어 중 n번째 글자 == 'ㄱ') {
원래 글자 중 n번째 글자 >= '가' && 원래 글자 중 n번째 글자 <= '깋'
}
받침 등이 자음마다 달라서 아주 일정한 규칙이 있는 것이 아니라서 이 작업을 ㄱ~ㅎ까지 모두 해주어야 합니다.
엑셀을 이용해도 됩니다.
그런데 우리는 switch 변수구문을 작성한 적이 있기 때문에 아래와 같이 작성합니다.
저 같은 경우는 ㄱ~ㅎ, 가~하, 깋~힣은 gpt에게 한글을 작성해달라고 하고, 엑셀에서 목록을 만들어서 다시 vs에 붙여넣기 했답니다. 초성이 아닌 경우에는 검색어와 원래 단어의 n번째 글자가 같으면 되기 때문에 _ => unicode == OriginChar로 작성하였습니다. OriginChar는 원래 목록에 있던 단어의 n번째 글자이고 unicode는 검색어의 n번째 글자입니다. 지금보니 변수명을 잘못지었지만, 이미 개발했으니 그대로 쓰겠습니다.