Skip to content

잡다한 .NET 지식

JaeYeonLee0621 edited this page Oct 4, 2018 · 55 revisions

잡다한 .NET 지식


DI 사용하기

  • Unity 사용하기

Unity, Unity.MVC package Install : MVC5 사용 시 Unity, Unity.WebAPI package Install : WebAPI 사용 시

UnityConfig.cs

public static void RegisterTypes(IUnityContainer container)
{
    HanaMembers.Unity.Container.RegisterTypes(container);
}

Unity\Container.cs

public static void RegisterTypes(IUnityContainer container)
{
    container.RegisterType<IPointService, PointServiceImpl>();
}




MySQL 사용하기

MySQL.Data(6.9.12) package Install




Route 사용하기

routes.MapMvcAttributeRoutes();를 RouteConfig.cs에 정의해야 사용이 가능




Dapper 사용하기

예시를 응용해서 사용합니다. 보통 Procedure를 만들어서 사용하기 때문에 만약 생 쿼리를 사용한다면 다르게 사용해야합니다. 검색을 해봐야겠죠?

public CommonResult<ResGetPartnerList> GetPartnerList (ReqGetPartnerList objReqParam)
{
    //응답 Param
    CommonResult<ResGetPartnerList> objResParam = new CommonResult<ResGetPartnerList>(CommonError.BOQ_API_INTERNAL_ERR);  //Default로 999오류로 시작

    // 응답 Data Param
    ResGetPartnerList objResData = new ResGetPartnerList();

    try
    {
        using (var objDapper = CreateConnection())
        {
            DynamicParameters reqParam = new DynamicParameters();

            reqParam.Add("@pi_strName", objReqParam.StoreName, DbType.String, ParameterDirection.Input);
            reqParam.Add("@pi_intBusinessTypeNo", objReqParam.BusinessTypeNo, DbType.Int32, ParameterDirection.Input);
            reqParam.Add("@pi_intRegionNo", objReqParam.RegionNo, DbType.Int32, ParameterDirection.Input);
            reqParam.Add("@pi_intPageSize", objReqParam.PageSize, DbType.Int32, ParameterDirection.Input);
            reqParam.Add("@pi_intPageNo", objReqParam.PageNo, DbType.Int32, ParameterDirection.Input);
            reqParam.Add("@po_intRecordCnt", dbType: DbType.Int32, direction: ParameterDirection.Output);

            IEnumerable<PartnerInfo> resObjData = objDapper.Query<PartnerInfo>("UP_PARTNER_MINI_AR_LST", reqParam, commandType: CommandType.StoredProcedure);

            // 찾는 Category가 없을 때
            if (resObjData.ToList().Count == 0)
            {
                objResParam.result.code = CommonError.BOQ_API_RESOURCE_NOTFOUND_ERR;
                return objResParam;
            }

            //성공 시 응답 정보 셋팅
            objResParam.result.code = CommonError.BOQ_API_SUCCESS;
            objResData.StoreInfoList = resObjData.ToList();
            objResParam.data = objResData;
        }
    }
    catch (SqlException sqlEx)
    {
        // SQL 에러 발생시
        objResParam.result.code = CommonError.BOQ_API_INTERNAL_ERR;
        objResParam.result.detail = string.Format("[{0}]{1}", sqlEx.Errors[0].Number, sqlEx.Errors[0].Message);
    }
    catch (Exception objEx)
    {
        // Class 내에서 에러 발생시
        objResParam.result.code = CommonError.BOQ_API_INTERNAL_ERR;
        objResParam.result.detail = string.Concat(objEx.Message, objEx.StackTrace);
    }
    finally
    {
        // finally
    }

    return objResParam;
}




c# 에서 지원하는 antiforgery token

ASP.NET Core 2.0 또는 더 최신 버전은 FormTagHelper에서 antiforgery token HTML form을 자동으로 주입하고 있다. 특히 Razor File은 자동으로 생성시켜준다.

또한 IHtmlHelper.BeginForm은 GET이 아닌 POST가 기본 method가 되게 하여 공격을 방어한다.




Authentication, Authorization 구현

  • Authentication

: 시스템 접근 시 등록된 사용자 인지 여부 확인

  • Authorization

: 접근 후 인증된 사용자에게 권한 부여 : 권한에 따라 사용할 수 있는 기능이 제한

  1. 사용하는 방식은 여러가지 인데 C# 에서 지원해주는 역할 관리 방식을 사용할 수 있다. roleManager 사용 방법

  2. 혹은 DB에서 관리자를 저장해놓고 불러와서 사용할 수도있다.

+) shouldLockout 이란? (shouldLockout 참고 자료)

만약 이 flag가 true로 설정되어있다면 몇 번의 로그인을 실패하면 locked out이 된다.

만약 비밀번호가 맞으면 2FA(2단계 인증 솔루션)으로 가게되고 (만약 존재한다면) 실패하면 사용자 레코드의 failed log-ins의 값이 증가한다.

var signInManager = new SignInManager(userManager, authenticationManager);
var signInStatus = await signInManager.PasswordSignInAsync(username, password, isPersistent, shouldLockout);




CallBack 함수

Delegate는 대리자라는 의미로 함수를 대신 호출하는 것이다. Delegate는 그 자체로는 사용에 의미가 없어보이지만, CallBack을 사용할 때 매우 필요한 기능이다.

delegate int myDelegate(int a, int b);

class MainApp {
  public static void Calculator(int a, int b, myDelegate dele){
    Console.WriteLine(dele(a,b));
  }
  public static int plus(int a, int b){ return a+b; }
  public static int minus(int a, int b){ return a-b; }

  static void Main(){
    myDelegate plua = new myDelegate(plus);
    myDelegate minus = new myDelegate(minus);

    Calculator(11,12,plus);
    Calculator(12,13,minus);
  }
}

Delegate Chain이라고 해서 하나의 Delegate에 여러개의 함수를 묶을 수도 있다.

myDelegate dele;
dele = new myDelegate(minus);
dele += plus;
dele -= minus;




partial class

한 곳에서 class를 정의하는 것이 아닌 여러 군데에서 class를 정의할 수 있는 것.




Attribute 실행 순서

BaseController에 3개의 Filter를 걸어보았다.

Authorization Filter
Logging Filter
ExceptionHandler Filter

Authorization Filter에서는 접근 권한을 판단해주고

Logging Filter에서는 로그를 찍으며

ExceptionHandler Filter에서는 예외 상황을 처리해준다.

BaseController.cs

// Action 처리 전 실행부
protected override void OnActionExecuting(ActionExecutingContext objFilterContext) {}

// Action 처리 후 실행부
protected override void OnActionExecuted(ActionExecutedContext objFilterContext) {}

Authorization Filter.cs

public class Authorize : AuthorizeAttribute
{
    // 권한 검사
    public override void OnAuthorization(AuthorizationContext objFilterContext){ }

    // 권한이 없을 때 넘어가는 페이지 (현재는 Authorization에 관한 것이기 때문에 View를 관여하는 ActionResult단을 상속시켜줘야 사용할 수 있다.)
    public class UnAuthentecatedResult : ActionResult{}
}

Logging Filter.cs

public class Logging : ActionFilterAttribute
{
    public override void OnActionExecuted(ActionExecutedContext objFilterContext) {}
}

ExceptionHandler Filter.cs

public class ExceptionHandler : HandleErrorAttribute
{
  // 예외 처리
   public override void OnException(ExceptionContext objFilterContext) {  }
}

위와 같이 구성을 해놓고 Filter를 돌려보았다.

HomeController에서 BaseController를 상속시켜놓은 이후 돌렸을 때

1. BaseController로 이동하여 Authorize Filter를 탄다.
2. BaseController의 OnActionExecuting 함수를 돈다.
3. View 단에서 처리한다.
4. Logging Filter의 OnActionExecuted를 탄다.
5. BaseController의 OnActionExecuted를 탄다.
6. View가 Rendering되고 페이지가 뜬다.

자동으로 같은 OnActionExecuted라도 Logging인지 Authorize인지에 따라 Framework 내부에서 임의로 우선순위를 설정해놓았기 때문에 저렇게 돌아가는 것 같다. 신기방기




Filter와 Interceptor의 차이

Filter와 Interceptor는 사실 기능상으로는 크게 차이가 없다

하지만 기능을 실행하는 데 있어서 실행 순서에 차이가 있다.

아래의 그림은 Spring의 Filter와 Interceptor를 나타낸 것이다.

1. 사용자의 요청이 들어온다.
2. Filter를 통해 거른다.
3. DispatcherServlet울 통한다.
4. DispatcherServlet은 HandlerMapping과 Interceptor를 호출한다.
5. Controller를 호출하여 로직을 처리한 후 Interceptor가 다시 처리된다.
6. 이후 View와 관련된 부분을 처리한다.
7. 모든 작업이 끝나면 DispatcherServlet -> Filter 순으로 응담이 나온다.




Email 관련 Protocol

이메일 전송 기능을 구현할 때 필요한 지식과 코드입니다. C# 기반의 .NET Framework 4.5 Version으로 되어있습니다! :)

SMTP란?

  • Simple Mail Transfer Protocol의 약자로 메일을 송신하기 위한 TCP/IP Protocol
  • 7 bit ASCII Code기반의 텍스트 형식으로 사용되야 하기 때문에 미디어 전송을 위해 MIME를 이용해야함
  • SMTP 사용시 동일한 네트워크에 다른 프로세스로 메일을 전송할 수 있고 양쪽 네트워크에 접근할 수 있는 Relay나 Gateway 프로세스를 경유하면 다른 네트워크에 있는 프로세스에게도 메일이 전달 가능
  • SMTP를 사용하나 수신측에서는 Queue Message 능력 제한으로 인해 POP3나 IMAP 중 하나의 프로토콜을 씀
  • TCP Port 25 (Well - known Port)

SMTP 과정

1. 사용자는 mail client와 같은 프로그램을 통해 mail 작성후 SMTP를 사용하여 mail deamon으로 메시지 전송
2. mail deamon은 client 주소를 분석하고 가장 가까운 mail server로 메시지와 정보 전송
3. 송신자 측의 전자 우편을 관리하는 Mail Server에 전달되면 Mail Server는 전자우편 주소를 분석해서 최단 경로를 찾아 근접한 Mail Server에 편지 전송
4. 최종 수신자 측의 Mail Server에 도달할 때까지 계속해서 반복

POP3란?

  • Post Office Protocol의 약자로 인터넷 서버가 사용자를 위해 Email을 수신하고 그 내용을 보관하기 위해 사용되는 Client/Server Protocol
  • 주기적으로 자신의 메일 수신함을 점검하고 만약 수신된 메일이 있으면 Client 쪽으로 Download되고 서버에 더이상 남아있지 않음

IMAP이란?

  • 로컬 서버에서 전자우편을 엑세스 하기 위한 표준 프로토콜
  • 인터넷 서버를 이용하여 전자 우편을 수신하고 보관하는 Client/Server Protocol
  • 사용자는 편지의 제목과 송신자를 보고 메일을 실제로 다운로드 할 것인지 결정 가능
  • 서버에 폴더나 우편함을 만들어 관리할 수 있으며 메시지를 지우거나 메시지의 일부 또는 전체 내용에 대해 검색을 수행가능

Local에서 Email 전송하기

  1. SMTP 서버 구축하기 Window 에서 SMTP 서버 구축하기 를 읽으면서 Local Window에서 STMP 서버를 구축하시면 됩니다! :)

  2. ASP.NET MVC5 Code

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Net.Configuration;
using System.Net.Mail;
using System.Net.Mime;
using System.Text;
using System.Web;
using System.Web.Mvc;

namespace Email.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            sendGmail2();
            return View();
        }

        public ActionResult About()
        {
            ViewBag.Message = "Your application description page.";

            return View();
        }

        public ActionResult Contact()
        {
            ViewBag.Message = "Your contact page.";

            return View();
        }

        /*
        Google에 SMTP를 사용하려면 이곳으로 보내야합니다! :)

        < Google SMTP Email Configuration >
        - Gmail Address : smtp.gmail.com
        - Port Number   : 587
        */

        // Google에 매일 보내기 Version 1
        // Web.config에 설정해놓기
        /*
        <!-- 이메일 전송 관련 Configuration -->
         <system.net>
           <mailSettings>
             <smtp from="ljyeon9466@gmail.com">
               <network host="smtp.gmail.com" port="587" userName="사용자 이름" password="비밀번호 defaultCredentials="false"/>
             </smtp>
           </mailSettings>
         </system.net>
        */
        public void sendGmail_WebConfig()
        {
            // Web.config에서 SMTP 설정 읽음
            SmtpSection smtpConfig = (SmtpSection)ConfigurationManager.GetSection("System.net/mailSettings/smtp");

            // 발신자 및 수신자 메일 설정
            string senderName = "이재연 테스트";
            string senderId = "wkddnjset@gmail.com";
            string receiveId = "ljyeon9466@gmail.com";
            string msgTitle = "Test Mail Title";
            string msgContent = "<div> <h1> Test Mail </h1> </div>";

            // 메일 컨텐츠 설정
            MailMessage message = new MailMessage();
            message.From = new MailAddress(senderId, senderName);
            message.To.Add(new MailAddress(receiveId));
            message.Subject = msgTitle;
            message.Body = msgContent;
            message.SubjectEncoding = System.Text.Encoding.UTF8;
            message.BodyEncoding = System.Text.Encoding.UTF8;
            message.IsBodyHtml = true;

            // 첨부 파일 설정
            Attachment at = new Attachment(@"C:\Test_image.png", MediaTypeNames.Image.Jpeg);
            at.ContentId = "ContentIDO"; // Attachment의 ContentID를 통해 메일 내용의 Img Tag에서 접근 가능함
            message.Attachments.Add(at);

            // SMTP 설정
            SmtpClient smtpClient = new SmtpClient(smtpConfig.Network.Host, smtpConfig.Network.Port);
            smtpClient.UseDefaultCredentials = false;

            // SMTP서버로부터 인증을 받기 위한 Credentials 생성
            NetworkCredential networkCred = new NetworkCredential(smtpConfig.From, smtpConfig.Network.Password);
            smtpClient.Credentials = networkCred;

            // SSL 접속을 할 수 있도록 EnableSsl을 True로 설정 (구글은 필수, 미 설정시 메일이 발송되지 않음)
            smtpClient.EnableSsl = true;
            smtpClient.DeliveryMethod = SmtpDeliveryMethod.Network;

            // send Method를 이용하여 전송
            smtpClient.Send(message);
        }

        // Google에 Mail 보내기 Version 2
        // Web.config 파일 설정 하지 않음
        public void sendGmail2()
        {
            SmtpClient client = new SmtpClient("smtp.gmail.com", 587);
            client.UseDefaultCredentials = false;                      // 시스템에 설정된 인증 정보를 사용하지 않는다.
            client.EnableSsl = true;                       // SSL(Secure Socket Layer)-전송 보안 계층 을 사용한다.
            client.DeliveryMethod = SmtpDeliveryMethod.Network; // 이걸 하지 않으면 Gmail에 인증을 받지 못한다.
            client.Credentials = new System.Net.NetworkCredential("ljyeon9466@gmail.com", "비밀번호");

            MailAddress from = new MailAddress("ljyeon9466@gmail.com", "이재연", System.Text.Encoding.UTF8);
            MailAddress to = new MailAddress("ljyeon9466@gmail.com");

            MailMessage message = new MailMessage(from, to);

            message.Body = "This is a test e-mail message sent by an application. ";
            message.Body += Environment.NewLine;
            message.BodyEncoding = System.Text.Encoding.UTF8;
            //message.Subject                     = "test message 2";
            //message.SubjectEncoding             = System.Text.Encoding.UTF8;

            try
            {
                // 동기로 메일을 보낸다.
                client.Send(message);

                // Clean up.
                message.Dispose();
            }
            catch (Exception Ex)
            {
                Debug.Write(Ex.Message);
            }
        }

        // daum으로 메일 보내기
        public void SendDaumEmail()
        {
            // pop.daum.net 995
            SmtpClient client = new SmtpClient("smtp.daum.net", 465);
            client.UseDefaultCredentials = false;
            client.EnableSsl = true;
            client.DeliveryMethod = SmtpDeliveryMethod.Network;
            client.Credentials = new System.Net.NetworkCredential("ljyeon9466@daum.net", "비밀번호");

            MailAddress from = new MailAddress("ljyeon9466@daum.net", "이재연", System.Text.Encoding.UTF8);
            MailAddress to = new MailAddress("ljyeon9466@daum.net");

            MailMessage message = new MailMessage(from, to);

            message.Body = "This is a test e-mail message sent by an application. ";
            message.Body += Environment.NewLine;
            message.BodyEncoding = System.Text.Encoding.UTF8;

            try
            {
                // 동기로 메일을 보낸다.
                client.Send(message);

                // Clean up.
                message.Dispose();
            }
            catch (Exception Ex)
            {
                Debug.Write(Ex.Message);
                Debug.Write(Ex.StackTrace);
                Debug.Write(Ex.InnerException);
            }
        }

        // Naver로 메일 보내기
        public void sendNaverEmail()
        {
            const string SMTP_SERVER = "smtp.naver.com"; // SMTP 서버 주소
            const int SMTP_PORT = 587; // SMTP 포트
            const string MAIL_ID = "ljyeon9466@naver.com"; // 보내는 사람의 이메일
            const string MAIL_ID_NAME = "ljyeon9466"; // 보내는사람 계정 ( 네이버 로그인 아이디 )
            const string MAIL_PW = "비밀번호";  // 보내는사람 패스워드 ( 네이버 로그인 패스워드 )

            try
            {
                MailAddress mailFrom = new MailAddress(MAIL_ID, MAIL_ID_NAME, Encoding.UTF8); // 보내는사람의 정보를 생성
                MailAddress mailTo = new MailAddress("ljyeon9466@daum.net"); // 받는사람의 정보를 생성
                SmtpClient client = new SmtpClient(SMTP_SERVER, SMTP_PORT); // smtp 서버 정보를 생성
                MailMessage message = new MailMessage(mailFrom, mailTo);

                message.Subject = "CSDP000 테스트"; // 메일 제목 프로퍼티
                message.Body = "반갑습니다"; // 메일의 몸체 메세지 프로퍼티
                message.BodyEncoding = Encoding.UTF8; // 메세지 인코딩 형식
                message.SubjectEncoding = Encoding.UTF8; // 제목 인코딩 형식
                client.EnableSsl = true; // SSL 사용 유무 (네이버는 SSL을 사용합니다. )
                client.DeliveryMethod = SmtpDeliveryMethod.Network;
                client.Credentials = new System.Net.NetworkCredential(MAIL_ID, MAIL_PW); // 보안인증 ( 로그인 )
                client.Send(message);  //메일 전송
            }

            catch (Exception ex)
            {
                Debug.Write(ex.Message);
            }
        }
    }
}

주의!!!!) Gmail에서 2단계 인증 사용을 풀어야 합니다

+) 보안 수준이 낮은 앱에서 계정에 액세스하도록 허용 을 사용하지 않음에서 사용함으로 변경하세요. 아니면

The SMTP server requires a secure connection or the client was not authenticated

이런 에러가 떠서 저처럼 2시간을 낭비할 수 있습니다 :)

+) Gmail 관련된 글이 당연하게도 가장 많습니다.


실무에서는?

안정된 의존관계를 읽다가 Email Service를 이후에 이렇게 발전시키면 좋을 것 같아서 적어놓습니다! :)

대체로 우리가 웹서비스를 만들 때 Email 발송은 매우 많이 하게 됩니다. 최초의 시작은 SMTP 서버에 의존하겠지만 나중에는 DB에 메일 내용을 넣고 배치작업으로 메일을 보내거나 아니면 MQ를 통해 메일 내용을 전달하고 다른 프로그램에서 실제 메일 발송을 하게 하는 경우도 있는등 변동성이 매우 큰 것이 Email 발송입니다.




log Print

log는 보통 log4net 이라는 원래 java에서 유명한 log library가 .net을 위해서 만든 library를 가장 많이 쓴다. nuget package 에서 다운로드 받으면 된다.

쓰는 방법에는 2가지가 있는데

  1. app.config, log4net.config 등 Configuration 파일을 이용
  2. code로 custom 해서 사용

1번을 이용하려면 log4net Configuration파일을 이용해서 개발하기 을 보면 아주 자세하기 나와있다.

2번을 이용하려면 청소년 프로젝트 DoungJi Backend을 보면 나와있다.

너무 많아서 잘 설명할 수는 없지만

크게

- Contracts
  - ILogHandle : LogHandle Interface

- Implementation
  - Log        : log4net configuration 정의
  - LogHandle  : WriteLog 사용하기

이다.


Json 사용하기

json을 예쁘게 보여주기 위해서 다양한 노력을 했는데 (아으) 그냥 Newtonsoft를 이용하면 가능하다.

string jsonModel = JsonConvert.SerializeObject(objModel, Formatting.Indented);

file Path 설정하기

주의해야할 점은

  1. file path를 나타낼때 ~~ 가 아닌 /로 해야한다.
  2. file path는 기존의 프로젝트가 시작이다.

나의 경우에는 DoungJi안에 Doungi.Infrasturcture를 생성했는데 root 경로가 DoungJi.Infrasturcture 였다.

즉 DoungJi/Log/DevLog에 파일을 넣고 싶으면 Doungi.Infrasturcture 위의 Doungi에서 파일에 들아가야하기 때문에

../Logs/DebLog 로 설정해야한다.




Visual Studio Git clone error가 발생했을 때

나의 경우에는 visual studio에서는 file로 읽었는데 실제로는 directory라고 읽었기 때문에 나타나는 증상이었다. 이 경우는 내가 git에 올릴 때 제목이 없는 빈 파일을 올려 사실은 / 뒤에 파일 이름이 있었지만 그 파일 이름이 사실상 빈 값이었기 때문에 visual studio에서는 directory라고 읽은 것이다. 그 파일을 삭제해주니 git clone이 성공적으로 되었다! 이제 version오류를 해결하러..




Owin이란

.NET Web Application과 Web Server가 대화하는 방식을 표준화한 규격




nested class란

중첩 클래스 즉 클래스 내에 클래스가 또 정의된 클래스

이러한 클래스의 경우 static한 함수는 top level에 있어야함

Extension methods must be defined in a top level static class

이러한 에러가 난 일이 있는데 밖을 partial class로 묶고 안에 public class가 있었으며, 그 안에 public function(this type parameter)이 존재했다.

  • public static function에서 top level로 빼라는 에러가 나와서

  • public class 위로 뺐더니 partial class에서는 non-generic static class를 사용해야한다고 한다.

확장 메서드 (parameter에 this가 들어가는 메서드) 들은 generic이 아닌 static class에 정의해야한다고 한다.

  • 따라서 this를 빼는 형식으로 변경하였더니 해결되었다.




404 Error

404 Error가 계속 뜨는 즉 라우팅을 먹였는데도 페이지가 뜨지 않는 에러가 계속 발생했다.

어떻게 찾아봐도 계속 안됬는데 안됬던 이유는

Release Mode로 계속 돌렸기 때문에 에러가 발생했는데도 불구하고 에러가 뜨지 않아서

에러대신 404 Page가 뜬 것이었다.

앞으로 꼭 Debug모드 로 돌려놓자!!!!!!!




Host 등록하기

C:\Windows\System32\drivers\etc\hosts

에 들어가면 host 파일로 들어가게된다.

host파일에서 정의하는 것은 특정 ip를 특정 dns로 입력하는 것이다.

즉 url을 입력하면 DNS Server에 먼저 가서 ip를 얻어오는데 만약 내 컴퓨터의 host 파일에 해당 내용이 있다면 먼저 적용한다.

예시)

85.45.15.42 tirrilee.local.co.kr

를 해당 파일에 추가하면 된다.




롱폴링(long polling)


클라이언트가 서버에 있는 정보를 요청하면 지정한 시간동안 연결을 열어둔다. 서버에 정보가 없으면 클라이언트가 요청한 정보가 생길 때 까지 또는 지정한 시간이 끝날 때까지 해당 요청을 열어둔다. 지정 시간이 끝나면 클라이언트는 서버에 있는 정보를 다시 요청한다.

롱 폴링은 코멧 또는 리버스 에이작스(Riverse ajax) 라고도 한다. 메시지 양이 많으면 롱 폴링을 사용해도 기존의 폴링에 비해 특별히 성능이 개선되지 않는 것을 염두에 두어야 한다. 클라이언트가 새 정보를 가져오기 위해 끊임 없이 서버로 재연결을 해야 하는데, 그로인해 네트워크 반응이 빠른 폴링과 같아지기 때문이다. 롱 폴링의 또다른 단점은 표준 구현이 없다는 점이다.




폴링 (poling)


주기적인 시간마다 클라이언트가 서버로 요청을 동기적으로 호출해서 사용 가능한 정보가 있는지 알아낸다. 요청을 주기적인 간격으로 이뤄 지며 클라이언트는 정보가 있든 없든 응답을 받는다.

폴링은 메시지가 전달되는 간격을 정확히 알고 있을 때 적절한 해결책이다. 왜냐하면 서버에 정보가 있음을 알고 있어야만 클라이언트를 동기화해서 요청을 전송할 수 있기때문이다. 다만, 실시간 데이터는 대체로 그렇게 예측 가능하지도 않고 불필요한 요청을 보낼 수도 있어서 과다한 연결이 불가피하다. 그래서 결국 메시지 비율은 적은데도 수많은 연결을 불필요하게 여 닫아야 할 수도 있다.




RPC


Remote procedure call의 약자로, 말 그대로 원격 프로시저 호출을 뜻한다. 즉, 외부 사용자가 서버 내의 프로시저를 호출 시키는 것이다.

컴퓨터 프로그램이 다른 주소공간에서 원격제어를 위해 함수나 프로시져의 실행을 허용하는 기술이다.(프로토콜) 저수준의 프로세스간 통신방법으로 네트워크 프로그래밍을 함수호출 레벨 수준으로 작성가능케하는 API이다. 객체지향 개발개념이 더해진 것은 원격호출 또는 원격방식호출(RMI:Remote Method Invocation). 클라이언트/서버 구조이며 RPC의 대리인격인 Stub을 이용하기 때문이다.

Stub

원격지에 위치해있는 프로그램을 대리하는 작은 루틴. RPC(혹은 RMI)를 사용하는 프로그램이 컴파일되면 요청된 절차를 제공하는 프로그램의 대역을 한다.

클라이언트에서 작업을 요청하는 데이터를 Marshaling하고 작업이 완료된 데이터를 다시 UnMarshaling하는 역할 클라이언트 보조 객체가 Stub이고 서버 보조객체가 Skeleton이다.




Comet

웹 페이지가 다시 로드되지 않고도 실시간으로 변동사항을 반영해 변화하게 하려면 어떻게 해야할까요. 만약에 서버가 모니터링하고 있다가 그 상태를 수시로 클라이언트 측에 전달하는 서버 푸시(Server Push)방식을 사용하면 가능할 것입니다. 그러나 HTTP는 기본적으로 클라이언트(웹 브라우저)가 서버에 요청(Request)을 하면, 서버가 그에 대해 응답(Response)을 한 후에 연결을 끊습니다. HTTP에서는 클라이언트가 먼저 요청을 하지 않는 이상, 서버는 결코 먼저 움직일 수 없는 것이지요.

하지만 이 문제를 해결하기 위해서 전세계의 많은 개발자들이 만들어놓은, 약간은 편법처럼 느껴지는 방법들이 있습니다. 이러한 방법을 묶어서 코멧(Comet)이라고 합니다.


Comet 클라이언트


Ajax 폴링(Ajax Polling)


이것은 Ajax로 상태를 가져와 화면을 업데이트하는 자바스크립트 함수를 작성하고, setInterval()로 일정 시간마다 이 함수를 호출하는 방법입니다. 설명 만으로도 감잡으신 분들이 있을 정도로 간단한 방법입니다.


아이프레임(Hidden iframe)


서버가 클라이언트의 요청에 응답할 때, Content-Length 헤더를 출력하지 않으면 클라이언트는 서버측으로부터 접속이 종료될 때까지 계속 데이터를 받아들입니다. 이러한 특성을 이용하여 서버측에서 접속을 끊지 않고 계속 유지한다면 숨겨진 <iframe> 에서 아주 긴 시간 동안 서버 푸시(server-push)가 가능해집니다. 접속을 유지하는 동안 서버는 이벤트가 발생할 때 <script> 태그를 출력해서 <iframe>문서의 부모 문서에 데이터를 전달합니다.

이 방식의 장점은 폭넓은 호환성에 있습니다. 어지간히 오래된 브라우저에서도 <iframe>태그는 지원해주니까요. 다만, 에러를 제대로 다룰 수 없는 점, HTTP의 상태를 다룰 수 없는 점, 모바일 브라우저에서는 사용할 수 없는 점 등은 단점입니다. 또한, 웹 브라우저의 기본 보안 정책인 동일 출처 정책(Same-Origin Policy)의 영향을 받으므로 도메인이나 스키마, 포트가 다른 문서간에는 사용할 수 없습니다.

Safari 브라우저에서는 display 스타일이 none으로 설정된 iframe을 폼 전송의 target으로 설정할 수 없으므로, 크기를 매우 작게 만들어 안보이는 영역에 두는 방식을 사용하는 것이 좋습니다.


Ajax를 이용한 롱 폴링(Long Polling by Ajax)


Ajax로 호출하는 파일이 반복문을 돌며 대기하고 있다가, 화면을 업데이트 해야할 상황이 되면 비로소 화면을 업데이트 할 수 있도록 데이터를 출력하고 Ajax를 마치는 방법입니다. 화면이 업데이트되고 나면 다시 동일한 파일을 Ajax로 호출하여 같은 과정을 반복하게 됩니다.


스크립트 태그를 이용한 롱 폴링(Long Polling by Script Tag)


<script> 태그의 src 속성에 서버측에서 응답할 주소를 입력하고, 서버측에서는 응답할 내용이 있을 때까지 이 접속을 계속 유지합니다.

접속을 유지하다가 응답할 내용이 발생하면 서버측에서는 이름을 전달받은 콜백 함수에 데이터를 전송합니다. 콜백 함수에 전달하는 데이터의 형태는 서버와 클라이언트가 하나의 규약에 따르기만 하면 뭐든 상관없지만, JSON 데이터를 사용하는 것이 일반적이며, 확장성 측면에서도 더 유리합니다. 참고로, 이와 같이 JSON 데이터를 콜백 함수로 감싸서 응답하는 방식을 JSONP 방식이라고 합니다.

콜백 함수가 실행되면 전달받은 데이터를 처리함과 동시에 방금 끊어진 접속을 대체할 수 있는 새로운 <script> 태그를 생성합니다. 이런 식으로 계속 대기 + 계속 생성하는 것이 바로 <script> long-polling 방식의 핵심입니다.

이 방식은 여러 브라우저에서 사용할 수도 있으면서 포트, 도메인 등의 제약도 받지 않는 장점이 있는 반면, 에러나 HTTP의 상태를 다룰 수 없고, iframe 처럼 스트리밍 방식으로 사용할 수 없다는 단점이 있습니다. 스트리밍으로 사용할 수 없는 이유는 <script> 태그의 특성 때문입니다. <script> 태그는 src에서 지정한 리소스를 모두 읽어온 후에 해석하고 실행하기 때문에, 서버측에서 데이터를 반환했다면 반드시 접속을 종료해야 합니다. 그래야, 콜백 함수가 실행되니까요.


Comet 서버


1, 2년쯤 전에 국내에서 다섯손가락 안에 드는 대형 서비스를 운영하고 있는 모기업에 계신 개발자는 자신들이 구현한 채팅 서비스가 부하가 너무 심했는데, 비동기 서버로 바꾼 이후로는 동접 500명도 힘들어하던 서버가 3,000명도 거뜬히 견디게 되었다고 하더군요. 저도 실제로 그 정도 부하를 겪어볼 일은 많지 않아서 측정하기 어려웠는데, 그 분 말씀이 꽤 좋은 경험이 됐습니다.

Comet 전용 서버들은 대부분 이러한 비동기 접속을 구현하고 있습니다. Comet 전용 서버라고 해서 특별한 기능이 있는 것은 아니고, 조금 더 다듬어진 프레임웍과 Comet을 위한 간편한 설정, 사용법 등을 제공합니다. 참고로, 최근에는 일반적인 웹 서버들도 성능을 위해 epoll 이나 kqueue 등을 사용한 비동기 접속을 제공하고 있습니다.




SignalR


SignalR Document 번역본

ASP.NET 개발자를 위한 라이브러리로 실시간 웹 기능 구현을 위해 사용되는 과정을 단순하게 만들어주는 라이브러리 입니다. 또한 SignalR은 서버측 .NET 코드에서 클라이언트 브라우저의 자바스크립트 함수를 호출하기 위한 Server-To-Client 원격 프로시저 호출을 생성할 수 있는 간결한 API를 제공해줍니다. 연결 관리(연결 및 연결 해제 이벤트 등)나 연결 그루핑을 위한 API도 포함하고 있습니다.

SignalR은 클라이언트와 서버 간의 실시간 작업을 수행하기 위해 필요한 몇 가지 전송방식을 추상화시킨 것입니다. 즉, SignalR의 연결은 먼저 HTTP 형태로 시작된 다음, 만약 가능하다면 WebSocket 연결로 승격됩니다.

참고로 SignalR 1.x는 .NET 프레임워크 4.0부터 지원됩니다.


HTML 5 전송방식


이 유형의 전송방식들은 HTML 5에 대한 지원 정도에 따라 영향을 받습니다. 만약, 클라이언트 브라우저가 HTML 5 표준을 지원하지 않는다면 오래된 전송방식이 사용될 것입니다.

WebSocket 은 엄격한 요구 사항을 갖고 있기 때문, 이를 만족하는 브라우저들은 오직 최신 버전의 마이크로소프트 인터넷 익스플로러, 구글 크롬, 그리고 모질라 파이어폭스뿐이며, 오페라나 사파리 같은 다른 브라우저들은 일부분만 구현되어 있습니다.

Server Sent Events, EventSource 라고 부르기도 합니다. (브라우저가 Server Sent Events를 지원하는 경우에 선택되며, 인터넷 익스플로러를 제외한 모든 브라우저가 기본적으로 지원합니다.)


전송방식 선택 절차


  1. 브라우저가 인터넷 익스플로러 8이나 그 이하 버전이라면 롱 폴링이 사용됩니다.

  2. JSONP가 구성되어 있다면 (연결이 시작될 때, jsonp 매개변수가 true로 설정된 경우), 롱 폴링이 사용됩니다.

  3. 크로스-도메인 연결이 만들어졌고 (SignalR의 종점이 호스팅 페이지와 다른 경우), 다음 조건들을 모두 만족하면 WebSocket이 사용됩니다:

  • 클라이언트가 CORS(Cross-Origin Resource Sharing)를 지원합니다.

  • 클라이언트가 WebSocket을 지원합니다.

  • 서버가 WebSocket을 지원합니다.

이 조건들 중 한 가지라도 만족하지 않으면 롱 폴링이 사용됩니다.

  1. JSONP가 구성되지 않았고, 크로스-도메인 연결도 아니며, 클라이언트와 서버가 모두 WebSocket을 지원하면 WebSocket이 사용됩니다.

  2. 클라이언트나 서버 중 하나라도 WebSocket을 지원하지 않으면, 가능한 경우 Server Sent Events가 사용됩니다.

  3. 그러나, Server Sent Events을 사용할 수 없다면 영구 프레임이 시도됩니다.

  4. 영구 프레임이 실패하면 롱 폴링이 사용됩니다.


연결 및 허브


SignalR의 API의 Client, Server 통신을 위한 2가지 방법 지속적인 연결 (Persistent Connections)와 허브(Hubs)가 존재한다.

연결은 단일 수신자, 그룹화된 수신자, 브로드캐스트 메시지 전송을 위한 단순한 종점을 나타냅니다.

허브는 연결 API의 상위에 자리한 보다 고 수준의 파이프라인으로 클라이언트나 서버가 서로 상대방의 메서드들을 직접 호출할 수 있게 만들어줍니다.


허브 동작 방식


서버 측 코드에서 클라이언트의 메서드를 호출하면,

활성화된 전송방식을 통해서 호출된 메서드의 이름과 매개변수들이 포함된 패킷이 전송됩니다 (개체가 메서드 매개변수로 전송되면, 이는 JSON으로 직렬화됩니다).

클라이언트가 클라이언트 측의 코드에 정의된 메서드들 중에서 이름이 일치하는 메서드를 찾습니다.

만약 일치하는 메서드가 존재하면 해당 클라이언트 메서드가 역직렬화된 매개변수 데이터를 사용해서 실행됩니다.




Linq


배열을 select where 구문으로 처리할 수 있는 방식이다.

모르면 모르지만 알면 겁나 편하고 간지나는 방법이다.

public class Woman
{
    public int age { get; set; }
    public string name { get; set; }

    public Woman(int age, string name)
    {
        this.age = age;
        this.name = name;
    }
}

위와 같이 class를 만들고 class list를 생성해서 원하는 조건으로 결과값을 구해보았다.

List<Woman> womanList = new List<Woman>();
womanList.Add(new Woman(20, "person1"));
womanList.Add(new Woman(21, "person2"));

// List 형식
var result = (from girl in womanList  where girl.age < 22 select girl.name).ToList();

// IEnumerable 형식
IEnumerable<Woman> temp = (from girl in womanList where girl.age < 22  select girl);

List와 IEnumerable 의 차이
가장 큰 차이점은 IEnumerable은 read-only 이며 List는 수정이 가능하다는 것이다.
즉 수정, 삽입, 삭제가 많은 경우에는 List 읽기만 하는 경우에는 IEnumerable를 사용해야한다.


위의 조건 쿼리를 조금 다른 방식으로 작성하면 아래와 같다.

 var queryResult = ((IEnumerable<Woman>)womanList).Where(x => x.age >= 20 && x.name == "person1").Select(c => c.name);

 var queryResult2 = womanList.Where(x => x.age >= 20)
                                .Select(m => new
                                {
                                    age = m.age*2,
                                    name = m.name
                                });

조건문 2가지를 작성해보았다.

아래의 조건문의 경우에는 나온 값을 변경하여 저장할 수 있다. 위의 예제를 보면 나이의 2배를 저장하고 있다.


새로운 List month 라는 것을 만들고, IEnumerable, IEnumerator 두가지 방법으로 예제를 만들어보았다.

// IEnumerable
IEnumerable<string> iEnumerableOfString = (IEnumerable<string>)Month;
foreach (string AllMonths in iEnumerableOfString){
    Console.WriteLine(AllMonths);
}

// IEnumerator
IEnumerator<string> iEnumeratorOfString = Month.GetEnumerator();
while (iEnumeratorOfString.MoveNext()){
    Console.WriteLine(iEnumeratorOfString.Current);
}

IEnumerable, IEnumerator 차이
IEnumerable은 순서대로 처리를 해야하며 중간에 다른 곳으로 넘기더라도 해당 위치부터 시작할 수 없다.
IEnumerator는 순서대로 처리하는 것은 같지만 원하는 위치부터 시작할 수 있다.

예를 들어 아래의 예제를 보자.

List<string> Month = new List<string>();
Month.Add("January");
 Month.Add("February");
 Month.Add("March");
 Month.Add("April");

static void iEnumeratorMethodOne(IEnumerator<string> i) {
    while (i.MoveNext()) {
        if (i.Current == "February") {
            iEnumeratorMethodTwo(i);
        }
    }
}

static void iEnumeratorMethodTwo(IEnumerator<string> i) {
    while (i.MoveNext()) {
        Console.WriteLine(i.Current);
    }
}

위의 예제를 알맞은 List를 넣어서 돌려보면 iEnumeratorMethodTwo로 February 이후부터 들어갔지만, 이후 March, April 순서대로 나오게 된다.




string.format

string header = String.Format("{0,-12}{1,8}{2,12}{1,8}{2,12}{3,14}","City", "Year", "Population", "Change (%)");

//    City            Year  Population    Year  Population    Change (%)

위의 예제(ex){0,-12})를 보면 괄호의 첫번째 자리 숫자(ex)0)가 뒤에 나오는 변수의 몇번째 문자(ex)City)를 가져올 지를 의미하고,

뒤의 숫자(ex)-12)가 해당 문자를 포함해서 총 몇 글자가 들어갈 수 있는지 명세하는 것이다.

글자가 들어갈 때 - 이면 뒤로 padding , + 이면 앞으로 padding 을 주는 것을 알 수 있다.

string.Format 예제를 보면서 Tuple도 공부할 수 있는 예제가 Microsoft에 있다.

나는 c#을 안다고 생각하면서 사실 아는게 없던 것이다..ㅎㅎ

Tuple<string, DateTime, int, DateTime, int>[] cities =
          { Tuple.Create("Los Angeles", new DateTime(1940, 1, 1), 1504277,
                         new DateTime(1950, 1, 1), 1970358),
            Tuple.Create("New York", new DateTime(1940, 1, 1), 7454995,
                         new DateTime(1950, 1, 1), 7891957),  
            Tuple.Create("Chicago", new DateTime(1940, 1, 1), 3396808,
                         new DateTime(1950, 1, 1), 3620962),  
            Tuple.Create("Detroit", new DateTime(1940, 1, 1), 1623452,
                         new DateTime(1950, 1, 1), 1849568) };

foreach (var city in cities) {
          String.Format("{0,-12}{1,8:yyyy}{2,12:N0}{3,8:yyyy}{4,12:N0}{5,14:P1}",
                                          city.Item1, city.Item2, city.Item3, city.Item4, city.Item5,
                                          (city.Item5 - city.Item3)/ (double)city.Item3);
}
//    Los Angeles     1940   1,504,277    1950   1,970,358        31.0 %

Tuple을 위와같이 생성할 수 있고, 그 Tuple을 어떻게 사용하는지를 공부할 수 있다.

foreach문을 써서 모든 tuple을 돌고 있지만, 비슷한 예제이니 맨 첫번째 값만 복붙하였다.

만약 더 알고싶다면 string.format 을 참고하면 된다.

위의 예제에서 알고싶은 것은 -, + 숫자로 padding을 채우는 것 뿐만 아니라 다른 설정을 할 수 있다는 것이다.

yyyy : 년도 / NO : 금액을 나타내는 것처럼 comma를 표시 / P1 : %값으로 소숫점아래 1자리

이 이외에도 많은 경우의 수가 있으니 이것은 따로 공부해야한다.




Attribute Usage

Attribute 사용을 제한해주는 class

[AttributeUsage(AttributeTargets.Property)]
public class ExampleAttribute : Attribute
{
    public bool Test { get; set; } = false;
}

위처럼 AttributeUsage를 설정해주었으면, Property에 사용하는 Attribute라는 뜻이 된다.

만약 AttributeTargets에서 class를 설정해 주었으면, class에 붙여야하는 Attribute이다. 만약 Class에 붙이지 않으면 에러가 난다.




Swagger (swashbuckler library)


IgnoreObsoleteActions

[Obsolete] 라는 attribute가 붙은 api가 있을 때 그 api는 제외하고 보여준다.

또 다른 방법으로는 해당 api (Controller 쪽 함수) [ApiExplorerSettings(IgnoreApi = true)]을 붙이면 된다.


OperationFilter

IOperationFilter 라는 Interface를 상속하면 Apply 한수를 생성할 수 있다.

이 함수에서는 status code와 관련된 설명을 쓸 수 있다.

관련 코드는 Swashbuckle 이곳에서 예제를 볼 수 있다.




IIS

IIS를 localhost에서 실행할때에는 많은 절차를 거친다.

그냥 그 중에서 신기했던거 하나

C:\Windows\System32\drivers\etc\hosts 에서 IP Domain name 을 순서대로 적어주면 domain name이 DNS없이도 IP에 딱! 붙는다.

그리고 localhost에서는 같은 포트로 여러개의 web을 돌릴 수 있는데, 사이트 host 이름을 다르게 설정해주면 된다.

더 자세한 이야기는 나중에..




프로젝트 빌드 시작점 설정

Visual Studio에서 Solution project 오른쪽 클릭 > properties > Common Properties > Startup Project > Single startup project > 자신이 원하는 프로젝트 설정




Indexer

class를 array 처럼 쓸수있다. class[index]를 하면 class내에서 지정된 array값에서 index에 해당되는 값을 내보낸다.

class DayCollection {
    string[] days = { "Sun", "Mon", "Tues", "Wed", "Thurs", "Fri", "Sat" };

    // This method finds the day or returns -1
    private int GetDay(string testDay) {
        for (int j = 0; j < days.Length; j++) {
            if (days[j] == testDay)    
                return j;
        }

        throw new System.ArgumentOutOfRangeException(testDay, "testDay must be in the form \"Sun\", \"Mon\", etc");
    }

    // The get accessor returns an integer for a given string
    public int this[string day] {
        get { return (GetDay(day)); }
    }

}

class Program {
    static void Main(string[] args) {
        DayCollection week = new DayCollection();
        System.Console.WriteLine(week["Fri"]);
        System.Console.ReadKey();
    }
}




static class

정적클래스 는 한번 생성자가 실행되어 데이터가 세팅되면 변경되지 않는다.

Configuration을 정의할 때 정적클래스를 사용하여 Application_Start 에서 정적 class를 시작하면 configuration 파일을 읽어서 데이터를 세팅한다.

React

Aiden

Zoe

Gini

Clone this wiki locally