개발팁2016. 8. 31. 10:00

기존에 ASP.NET 에서 크리스탈레포트 출력기능을 만들어 보셨다면,
CrystalReportViewer 라는 서버컨트롤을 통해서 출력 화면을 만들었지만,
ASP.NET MVC 에서는 PDF 파일을 통해 출력하게 된다.

 

우선, Visual Studio 크리스탈레포트 개발환경을 만들어야 한다.
Crystal Reports for Visual Studio .NET 2010 또는 2012 를 검색해서 다운받아 설치한다.
이를 설치해야, Visual Studio 에서 API 와 Report Designer 를 사용할 수 있다.

 

ASP.NET MVC 프로젝트를 새로 만들고,

 

참조에 아래 4개를 추가한다.

 

CrystalDecisions.CrystalReports.Engine
CrystalDecisions.ReportSource
CrystalDecisions.Shared
CrystalDecisions.Web

 


솔루션 탐색기에서, 상위에 Reports 라는 폴더를 만든다.

 

 

 

Reports 를 선택하고, 마우스 오른쪽을 눌러, 추가 -> 새항목을 클릭한다.

 

 

Reporting 항목을 선택하고, Crystal Reports 를 선택하여, 생성할 rpt 파일 이름을 적어주고, 추가 버튼을 누른다.


 

 

 

새 Crystal Report 문서 만들기 창이 뜨면,
보고서 마법사를 사용할수도 있지만, 일단 빈 보고서 사용을 선택하면,
Report Designer 가 나온다.

 

 

 

머리글, 본문, 바닥글을 적당히 작성해보자.
아직까지는, DB 데이터 연결이나, 수식 등은 사용하지 않고, 그냥, 내용만 작성해보자.


 

 

이제, 생성된 레포트 파일을 Controller 에 연결할 차례다.

 

public ActionResult ShowReport()
{
     using (ReportClass rpt = new ReportClass())
     {
          rpt.FileName = Server.MapPath("~/") + "/Reports/Test1.rpt";
          rpt.Load();
          Stream stream = rpt.ExportToStream(CrystalDecisions.Shared.ExportFormatType.PortableDocFormat);

          return File(stream, "application/pdf");
     }
}

 

ReportClass 를 이용하여,  rpt 파일을 Load 하여, PDF (Portable Doc Format) 로 변환하여,
리턴한다.
아직, DB연결은 들어가지 않았기 때문에, 소스파일은 아주 간단하다.

실제로, PDF 로 리턴하게 되면, 웹브라우저에서는 Acrobat Reader 를 통해, 화면에 출력이 되며, 인쇄가 가능하다.

ExportFormatType 에는 Excel 이나 Word, html 등 여러가지가 있으니, 변경하면서, 테스트 해보길 바란다.

 

인터넷익스플로러에서 보여지는 화면이다.

 

 

크롬에서 통해 보여지는 화면이다.

 

실제 인터넷익스플로러 에서는 Object 태그로 구성이 되고, 크롬에서는 Embed 태그를 통해 구성이 된다.

Posted by 헝개
개발팁2016. 8. 30. 09:39

오라클의 Sequence 와 비슷한 기능으로
MS-SQL 에는 Identity 라는것이 있다.
Identity 는 오라클의 Sequence 와는 다르게, 테이블 컬럼의 속성으로 되어 있다.

 

CREATE TABLE test1
(
    seq int identity(1,1),
    val varchar(50) NULL
)

 

identity(1,1) 에서 앞에 1은 시작값, 뒤에 1은 증감값이다.
즉, 최초값을 1로 할수도 있고, 1000000 으로 할수도 있으며,
증감을 1씩 증가하게 할수도 있고, 100씩 증가하게 할수도 있으며,
반대로 -1 씩 감소하게 할수도 있다는 뜻이다.

 

insert into test1(val)
vals ( '1' );
insert into test1(val)
vals ( '2' );

 

테이블에 인서트 할때는 identity 컬럼을 지정할수 없다.
identity 컬럼을 제외한 컬럼에만 값을 지정하면, identity 컬럼은 자동으로 값이 할당된다.

 

seq val
1 1
2 2

 

DBCC CHECKIDENT 를 이용하여, 특정 테이블의 identity 값을 확인해볼 수 있다.

 

현재 identity 컬럼의 값이 얼마인지 확인하는 방법

DBCC CHECKIDENT('test1', NORESEED)

 

identity 값을 변경하는 방법

DBCC CHECKIDENT('test1', RESEED, 1000)

 

이 값은 문자열 형태로 출력이 된다.

 

 

BTW, 프로시저에서 identity 컬럼이 있는 마스터 테이블에 insert 를 한 후에,
방금 insert 하면서 생성된 identity 컬럼의 값이 필요한 경우가 있다.

 

identity 값을 실제 값으로 가져오는 방법은 3가지가 있다.

 

SELECT @@IDENTITY
SELECT IDENT_CURRENT('test1')
SELECT SCOPE_IDENTITY() 

 

1. @@IDENTITY

최종 입력된 identity 값을 가져오지만, 그 값이 a라는 테이블이든지 b라는 테이블이든지, 상관이 없이,
현재 세션내의 모든 범위에서 가져온다.

 

2. IDENT_CURRENT

지정한 테이블의 마지막 identity 값을 가져온다. 그러나, 동시에 테이블에 인서트 되는 경우에도,
다른 사람이 인서트한 마지막 값을 가져올수가 있다.

 

3. SCOPE_IDENTITY

현재 세션의 범위 내에서만 가져온다. 즉, insert 하고 나서, 생성된 identity 값만 가져온다.

 

일반적으로, @@IDENTITY 를 사용하는 경우가 있는데, 이런경우, 논리적 오류가 나올수 있다.
프로시저 안에서 insert 후에 identity 값을 가져올때는 항상 SCOPE_IDENTITY 를 사용하도록 하자.

 

추가적으로, 중간에 데이터가 삭제되면서, identity 컬럼의 값이 중간에 비어 있을때,
중간 값을 채워주고 싶다면 아래와 같이 하면 된다.

 

SET IDENTITY_INSERT test1 ON;

 

insert into test1(seq, val)
vals(9, 'inserted');

 

SET IDENTITY_INSERT test1 OFF;

 


IDENTITY_INSERT ON 으로 하면, identity 컬럼에 수동으로 값을 넣겠다는 뜻이다.

 

 

Posted by 헝개
개발팁2016. 8. 26. 10:46

 

MS-SQL 에 보면, COALESCE 라는 함수가 있다.

(근데, 이거 어떻게 발음하는건가요? 단어가 굉장히 어려워 보이는데..;;)

 

SELECT COALESCE(@a, @b, @c, 'else');

 

앞에서 순서대로 null 이 아닌 값을 찾아오는 함수다.

 

C# 에서도 비슷한 기능을 하는 연산자가 있다.

 

? 라는 연산자는 3항 연산자로 쓰이는데,

 

a == b ? 'same' : 'not same';

 

물음표를 2개 써서,   value1 ?? value2

 

앞에서 순서대로 null 이 아닌 값을 찾는다.

 

물론, 2개 이상의 값에서 찾는다면 , 순서대로 뒤에 ?? 를 나열하면 된다.

 

int? a = null;
int? b = null;
int? c = 100;

int? k = a ?? b ?? c;

int? 는 int 가 primitive type 이기 때문에 null 값이 들어갈 수 없다. int? 는 null 값을 허용한다는 뜻이다.

 

a, b, c 중에 앞에서 순서대로 null 이 아닌 값이 k 에 할당된다.

 

 

if(strV1 != null)

{

    System.out.println("the value is " + strV1);

}

else if(strV2 != null)

{

    System.out.println("the value is " + strV2);

}

else

{

    System.out.println("the value is else");

}

 

if / else 로 구성된 문장은 3항 연산자로 아래와 같이 쓸 수 있다.

 

System.out.println("the value is " + ( strV1 != null ? strV1 : strV2 != null ? strV2 : 'else' ) );

 

이는 다시 ?? 연산자로 아래와 같이 사용이 가능하다.

 

System.out.println("the value is " + ( strV1 ?? strV2 ?? 'else' ) );

 

 

Posted by 헝개
개발팁2016. 8. 25. 10:51

안드로이드 앱을 만들어보신분이라면 토스트 메세지가 어떤건지 바로 이해하실것이다.
순서대로 올라와서, 자동으로 사라지는 메세지 박스이다.

구조가 간단해서 직접 만들어보셔도 좋을것이다.
아래 링크에서 기존에 만들어진 jquery 플러그인을 다운받아보자.

 

https://github.com/Soldier-B/jquery.toast

 

 

html 파일에 css 와 js 파일을 넣고, 토스트메세지를 호출하면 된다.

 

<link rel="stylesheet" type="text/css" href="jquery.toast.min.css" />
<script type="text/javascript" src="jquery.js"></script>
<script type="text/javascript" src="jquery.toast.min.js"></script> 

 

 

$.toast('메세지', 옵션);

 

메세지는 html 코드로, 문자열에 html 태그를 포함할 수도 있다.
옵션은 json 방식으로 넣어주면된다.
json 은 javascript object notation 으로, { } 로 둘러싸인 객체 표현법이다.


 

$.toast('일반 메세지입니다.');

기본 메세지에, 기존옵션을 사용하여, 5초후에 사라진다.

 

$.toast('<h4>위험</h4> 이것은 위험 메세지입니다.', { type: 'danger', duration : 2000 } );

경고 메세지로, duration 값을 2000 으로 주었기 때문에, 2초후에 사라진다.

 

$.toast('<h4>정보</h4> 이것은 정보 메세지입니다.', { type: 'info', sticky : true } );

정보 메세지이고, sticky 에 true라고 주었기 때문에, x 버튼을 눌러 닫기 전까지는 계속 표시된다.

 

$.toast('<h4>성공</h4> 이것은 성공 메세지입니다.', { type: 'success', duration: 3000 } );

성공 메세지이고, 3초후에 자동으로 사라진다.

 

그외에도 toast 의 config 를 설정할 수 도 있다.

이 config 값은 최초에 1회만 설정하면, 이후 toast 메세지는 모두 설정값에 따라 나타난다.

 

기존으로 toast 메세지는 화면의 center 에 나오며, 메세지박스 사지는 500 이다.

 

$.toast.config.align = 'right';
$.toast.config.width = 300;

 

 

 

 

Posted by 헝개
개발팁2016. 8. 16. 10:02

숫자를 한글로 표시하는 방법이다.

은행사이트에서 이체 할때 보면, 숫자로 입력하면, 그 옆에 한글로 표시되는걸 봤을것이다.

 

123,000 => 일십이만삼천 원

 

예제는 자바스크립트 코드로 작성하였지만, 소스가 단순해서, 사용하는 언어로 포팅하여 사용하면 된다.

 

우선, 0 ~ 9 까지의 숫자를 한글로 정의해보자.

 

var arrNumberWord = new Array("","일","이","삼","사","오","육","칠","팔","구");


 

0은 블랭크로 두고, 각각의 숫자에 대해서, 배열로 정의하면, 배열인덱스로 바로 한글명으로 치환이 가능하다.

 

arrNumberWord[9] => 구

arrNumberWord[5] => 오

 

그다음, 4자리마다 끊어지는, 단위를 만들어보자.

영어에서는 3자리로 끊어지기 때문에, 콤마위치가 3자리에 와서, 한눈에 숫자를 파악하기 편한데,

우리나라는 4자리마다 끊어지는데, 콤마위치를 3자리에 뒤서, 영~ 좋지 않다.

 

var arrDigitWord = new  Array("","십","백","천");

 

1, 10, 100, 1000

1의자리는 치환이 없고, 각각, 십, 백, 천 이 된다.

7000 => 위의 arrNumberWord 배열로 칠로 바꾸고, 뒤에 000 => 길이가 3이기 때문에, 배열 인덱스 3으로 가져오면, 천으로 바뀌어, 칠천 이 된다.

 

이렇게, 각 자리수를 4로 나눈 나머지 를 처리하면된다.

 

 

마지막으로는, 4자리마다 세는 단위를 넣어보자.

만, 억, 조 가 해당된다.

10,000 => 만, 100,000,000 => 억

 

var arrManWord = new  Array("","만","억", "조");

 

이 arrManWord 는 자리수를 4로 나눈 몫으로 처리하면 된다.

10,000 의 경우 1을 제외하고, 0000 자리수가 4개이기 때문에, 4로 나누면 1이 나오고, 나머지가 0 인경우,

arrManWord[4로 나눈 정수값] = 억 이 된다.

 

 

<html>
<head>
<script>

      // 1 ~ 9 한글 표시
      var arrNumberWord = new Array("","일","이","삼","사","오","육","칠","팔","구");
      // 10, 100, 100 자리수 한글 표시
      var arrDigitWord = new  Array("","십","백","천");
      // 만단위 한글 표시
      var arrManWord = new  Array("","만","억", "조");



      // Copyright 헝그리개발자(https://bemeal2.tistory.com)
      // 소스는 자유롭게 사용가능합니다. Copyright 는 삭제하지 마세요.
      function fn_change_hangul_money(txt_id)
      {
            var num_value = txt_id.value;
            var num_length = num_value.length;



            if(isNaN(num_value) == true)
                  return;



            var han_value = "";
            var man_count = 0;      // 만단위 0이 아닌 금액 카운트.



            for(i=0; i < num_value.length; i++)
            {
                  // 1단위의 문자로 표시.. (0은 제외)
                  var strTextWord = arrNumberWord[num_value.charAt(i)];



                  // 0이 아닌경우만, 십/백/천 표시
                  if(strTextWord != "")
                  {
                        man_count++;
                        strTextWord += arrDigitWord[(num_length - (i+1)) % 4];
                  }



                  // 만단위마다 표시 (0인경우에도 만단위는 표시한다)
                  if(man_count != 0 && (num_length - (i+1)) % 4 == 0)
                  {
                        man_count = 0;
                        strTextWord = strTextWord + arrManWord[(num_length - (i+1)) / 4];
                  }



                  han_value += strTextWord;
            }

            if(num_value != 0)
                  han_value = "금 " + han_value + " 원";



            document.all.han_money.innerText = han_value;
      }


</script>
</head>

<body>
<form>
<span class="han_money" id="han_money">금액표시</span>
<br>
<input type="text" id="txt_money" maxlength="12" onkeyup="fn_change_hangul_money(this);">

</form>

</body>

</html>

 

 

간단하게 구현해본 자바스크립트 소스이다.

textbox 에 입력된 숫자를 받아서, 한글로 표시해준다.

 

 

 

Posted by 헝개
개발팁2016. 8. 10. 14:55

C# 에서 string 타입으로 숫자가 저장되어 있을때 int 타입으로 변경하는 몇가지 방법이 있다.

 

string n = "1234";

 

라고 할때,

 

 

Method.1

 

int outValue = Convert.ToInt32(n);

 

 

Method.2

 

int outValue = int.Parse(n);

 

 

Method.3

 

int outValue;

int.TryParse(n, out outValue);

 

 

그리고, 여기서 소개할 방법은, C# 의 기본 메서드를 이용하지 않고, 자체적으로 제작을 해보겠다.

 

문자로 저장된 '0' 이라는 값은 실제 메모리상에는 코드값으로 저장이 되어 있다.

우리가 ASCII 코드라고 부르는 값이다.

실제로 '0' 은 48 이라는 값으로 저장이 되며, '9' 는 57 이라는 값으로 저장이 된다.

 

하지만, '0' ~ '9' 가 어떤 값으로 저장이 되는지 알필요도 없다.

 

'0' - '0' 은 0 이 되며,

'1' - '0' = 1 이 되고,

'9' - '0' = 9 가 된다.

 

 

string n = '1234';

int outValue = 0;

 

for (int i = 0; i < n.Length; i++)
{

    outValue = outValue * 10 + (n[i] - '0');

}

 

outValue 를 확인해보면, 1234 라는 값이 들어있는것을 확인할 수 있다.

 

이를 Method.4 라고 부르고,

 

 

Method.1 ~ Method.4 까지 퍼포먼스 테스트를 해봤다.

 

1,000,000 개의 4자리 문자열로 된 배열을 만들고,

 

이를 int 형으로 변환하는 4가지 방법을, 각각 20회씩 실행해 봤다.

 

 

 

 

Convert.ToInt32
int.Parse
int.TryParse
3가지 방법 모두, 비슷한 결과가 나왔다.

4자리 문자열 100만개를 변환하는데, 0.13 ~ 0.14 초가 걸렸다.

 

반면, 직접 만들어서 int로 변환한 경우는, 0.03, 0.04 초가 걸렸다. 속도면에서, 기본 메서드 보다, 4배이상 빨랐다.

 

Posted by 헝개
개발팁2016. 8. 9. 08:29
Erwin Data Modeler R7 이후부터는 Physical Model 과 Logical Model 간에 컬럼 순서가 별도로 관리가 된다.
한군데서 컬럼순서를 바꾸면, 다른곳에서는 순서가 다르게 나오게 된다.

 

이를 자동적으로 동기화 해주는 방법은 없고, 수동으로 동기화를 해야 한다.

 

 

1. 테이블명을 더블클릭하면, Columns / Attributes 창이 뜬다.

 

 

 

Physical 을 기준으로 Logical 을 변경하는경우, Logical 에서 클릭한다.
Logical 을 기준으로 Physical 을 변경하는경우, Physical 에서 클릭한다.

 

 

2. Reset 버튼 클릭

 

 

 

 

3. Reset Order 클릭

 

 

 

4. 전체테이블 / 클릭한테이블 만 바꿀지 선택

 

 

 

Reset attribute order from column order for entity ( ) <= 1개의 테이블만 변경할때
Reset attribute order from column order for for all entities of the model <= 모든 테이블을 변경할때

 

 

 

Posted by 헝개
개발팁2016. 8. 8. 08:21

C# 의 데이터에는 Value Type 와 Reference Type 이 존재한다.

 

Value Type 은 그 자체가 값인 경우이다.
Reference Type 은 별도의 메모리 공간에 인스탄스가 생성이 되고,
메모리의 위치만을 값으로 가지는 경우다.

 

배열이나 클래스의 객체는 Reference Type 으로, 실제값은 별도로 저장이된다.

 

Class 안에 멤버가 모두 Value Type 이라면, 단순히 Clone 을 통해서, 멤버 값이 전부 새로운 값으로 복사가 된다.
그러나, 한 Class 안에 Value Type, 배열, 클래스 객체가 공존한다면,
이 객체를 Clone 을 하게 되면, Value Type 은 정상적으로 복사가 되지만, 나머지 값은 원래 객체의 주소값이 복사가 된다.

 

원래 객체 / 클론 객체가 모두 동일한 메모리 위치를 가리키기 때문에, 한쪽에서 멤버객체의 값을 변경한다면,
다른곳에서는 의도치 않게 값이 변경되는 결과를 보게 된다.

 

Deep Clone 이라는 것은 Value / Reference Type 모두 새로운 값으로 복사를 하는 것이다.

 

 

2가지 방법이 있는데, 복사하려는 객체가 Serializable 객체라면,
// 2. Serializable 객체에 대한  Deep Clone 을 이용하여,
MemoryStream 을 통해 Serialize 하여 복사가 된다.

 

그렇지 않은 Class 는,
// 1. Deep Clone 구현 을 이용하여, 복사를 하면 된다.

Class 안의 멤버는 Value Type 과 Reference Type 이 있고,
Reference Type 객체 안에는 또다시, Value Type 과 Reference Type 의 멤버가 있고,
그 안에 있는 Reference 에는 또다시 Value Type 과 Reference Type 있다.
이런식으로 계층간에 여러 단계의 객체가 존재할 수가 있다.
이를 모두 복사해야 하기 때문에, Circular 또는 Recursive 를 통해서 값을 복사를 해야 한다.

 

using System;
using System.Reflection;
using System.Collections.Generic;
using System.Runtime.Serialization.bformatters.Binary;
using System.IO;

namespace Hungry.Developer
{



    // Copyright 헝그리개발자(https://bemeal2.tistory.com)
    // 소스는 자유롭게 사용가능합니다. Copyright 는 삭제하지 마세요.

    // C# 객체를 복사해주는 클래스
    public class ObjectCopy
    {
        // 1. Deep Clone 구현
        public static T DeepClone<T>(T obj)
        {
            if (obj == null)
                throw new ArgumentNullException("Object cannot be null.");


            return (T)Process(obj, new Dictionary<object, object>() { });
        }



        private static object Process(object obj, Dictionary<object, object> circular)
        {
            if (obj == null)
                return null;



            Type type = obj.GetType();


            if (type.IsValueType || type == typeof(string))
            {
                return obj;
            }



            if (type.IsArray)
            {
                if (circular.ContainsKey(obj))
                    return circular[obj];



                string typeNoArray = type.FullName.Replace("[]", string.Empty);
                Type elementType = Type.GetType(typeNoArray + ", " + type.Assembly.FullName);
                var array = obj as Array;
                Array arrCopied = Array.CreateInstance(elementType, array.Length);

                circular[obj] = arrCopied;


                for (int i = 0; i < array.Length; i++)
                {
                    object element = array.GetValue(i);
                    object objCopy = null;



                    if (element != null && circular.ContainsKey(element))
                        objCopy = circular[element];
                    else
                        objCopy = Process(element, circular);



                    arrCopied.SetValue(objCopy, i);
                }



                return Convert.ChangeType(arrCopied, obj.GetType());
            }



            if (type.IsClass)
            {
                if (circular.ContainsKey(obj))
                    return circular[obj];



                object objValue = Activator.CreateInstance(obj.GetType());
                circular[obj] = objValue;
                FieldInfo[] fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);



                foreach (FieldInfo field in fields)
                {
                    object fieldValue = field.GetValue(obj);

                    if (fieldValue == null)
                        continue;



                    object objCopy = circular.ContainsKey(fieldValue) ? circular[fieldValue] : Process(fieldValue, circular);
                    field.SetValue(objValue, objCopy);
                }

                return objValue;
            }
            else
                throw new ArgumentException("Unknown type");
        }
 

        // 2. Serializable 객체에 대한  Deep Clone
        public static T SerializableDeepClone<T>(T obj)
        {
            using (var ms = new MemoryStream())
            {
                var bformatter = new Binarybformatter();
                bformatter.Serialize(ms, obj);
                ms.Position = 0;

                return (T) bformatter.Deserialize(ms);
            }
        }



    }

}


 

 

 

Posted by 헝개
개발팁2016. 8. 2. 07:59

OOP 디자인 패턴중에 Singleton 이라는 것이 있다.

 

일반적으로 클래스는 new 키워드를 통해서, 인스턴스를 생성하지만,
Singleton 클래스는 인스턴스가 1개만 생성이 된다.

 

생성자 즉, Constructor 를 private 으로 선언한다.
이렇게 되면, 외부에서 new 키워드로 인스턴스를 생성할 수 없다.
클래스 내부에서만 new 키워드를 사용하여, 자기 자신의 인스턴스를 생성이 가능하다.

 

아래 클래스 선언 방법대로, getInstance 를 static 으로 선언하고,
자기 자신의 인스턴스를 돌려주게 되면, 단 1개의 인스턴스만 생성하여 사용하게 된다.

 

public class MyClass
{
 private MyClass() { }

 private static MyClass instance;

 public static MyClass getInstance()
 {
  if(instance == null)
   instance = new MyClass();

  return instance;
 }

 public int Method1()
 {
  return 1;
 }
}

 

 

사용시에는 아래와 같이 사용한다.

 

MyClass myc = MyClass.getInstance();
int n = myc.Method1();

Posted by 헝개
개발팁2016. 7. 29. 10:01

IIS 웹서버로 요청되는 모든 웹페이지/웹서비스 요청에 대해, 퍼포먼스 로그를 남기는 방법이다.

 

퍼포먼스를 체크하는 방법은, 시작시간을 기록하고,

웹페이지나 특정 메서드가 실행되고 나서, 종료시간과의 차이를 계산해서 기록을 하면 된다.

 

DateTime _startTime = DateTime.Now;

 

// 웹페이지 / 메서드 실행.. 코드

 

TimeSpan span = DateTime.Now - _startTime;
string perfLog = span.Milliseconds.ToString() + " ms";


 

간단한 코드지만, 이를 매번 웹페이지마다 삽입하여 사용하는것은 어려우니,

HttpModule 을 이용하여, 자동으로 기록하는 방법이 있다.

 

IHttpModule 이라는 인터페이스를 이용하여, WebServiceAuthenticationModule 을 만들것이다.

이는 원래, 인증 체크를 위한 클래스인데, 이 안에, 퍼포먼스 로그 기능을 추가한 것이다.

 

별도의 프로젝트를 만들고, WebServiceAuthenticationModule  클래스를 만들어서,

Application_BeginRequest 와 Application_EndRequest 이벤트를 구현할 것이다.

이 프로젝트를 빌드해서, 별도의 DLL 을 만들면 된다.

 

ASP.NET 프로젝트에서 해당 DLL 을 참조 추가를 하고, Web.Config 에 아래와 같이 모듈을 추가 하면된다.

 

    <system.webServer>
      <!-- 인증체크 설정 -->
      <modules>
        <add  name="WebServiceAuthenticationModule"
              type="Hungry.Developer.HttpModule.WebServiceAuthenticationModule"/>
      </modules>
      ...

    </system.webServer>


 

이렇게 되면, 해당 ASP.NET  웹서비스의 모든 웹페이지는

항상 페이지 요청시, WebServiceAuthenticationModule 의 Application_BeginRequest 가 먼저 실행이 될것이고,

요청이 완료되면, Application_EndRequest 를 실행하게 될것이다.

 

이를 이용하여, 맨처음 보여줬던, 퍼포먼스 로그를 기록하면 된다.

 

// Copyright 헝그리개발자(https://bemeal2.tistory.com)
// 소스는 자유롭게 사용가능합니다. Copyright 는 삭제하지 마세요.
public sealed class WebServiceAuthenticationModule : IHttpModule
{
        private DateTime _startTime;    // 퍼포먼스 로깅을 위한 시작시간 저장 변수

        public void Init(HttpApplication app)
        {
            app.BeginRequest +=
                (new EventHandler(this.Application_BeginRequest));
            app.EndRequest +=
                (new EventHandler(this.Application_EndRequest));

        }
        private void Application_BeginRequest(object source, EventArgs e)
        {
             // 웹페이지 / 웹서비스 실행 시작시간 기록
            _startTime = DateTime.Now;

             // 인증체크
             ...
        }

        // 요청완료시, 퍼포먼스 로그 기록
        private void Application_EndRequest(object source, EventArgs e)
        {
            HttpApplication application = (HttpApplication)source;

            TimeSpan span = DateTime.Now - _startTime;
            string perfLog = application.Request.Url + " / " + span.Milliseconds.ToString() + " ms";
            LogWriter.PerfLog(perfLog);
        }

}

 

LogWriter 는 string 값을 파일에 추가하는 클래스다.

파일에 저장을 하던, DB에 저장을 하던, 원하는 방식으로 구현하면 된다.

 

아래는 위의 코드를 그대로 이용한, 실제 로그파일의 내용이다. 이중에 Performance 만 보면 된다.

 

2016-07-29 09:27:37.036 (Performance) From< HttpModule > :http://삐~/WebService/삐~Service.asmx / ReadBy / 100 ms
2016-07-29 09:27:37.359 (Debug) From< HttpModule > :인증체크 : 0 / cachekey(cUwze7r0rMTQCVHW9eXE/A==) / authkey(khMBxeLsajUjv7zdBcnrdG33JUQahNjqCqNzmH5G6nk=) / menuid(149) / permission(1)
2016-07-29 09:27:37.361 (Debug) From< HttpModule > :권한체크 : 0 / menuid(149) / permission(1) / requestURL(http://삐~/WebService/삐~Service.asmx) / webMethodName(ReadBy) / userIP(127.0.0.1)
2016-07-29 09:27:37.430 (Debug) From< HttpModule > :인증체크 : 0 / cachekey(cUwze7r0rMTQCVHW9eXE/A==) / authkey(khMBxeLsajUjv7zdBcnrdG33JUQahNjqCqNzmH5G6nk=) / menuid(149) / permission(1)
2016-07-29 09:27:37.433 (Debug) From< HttpModule > :권한체크 : 0 / menuid(149) / permission(1) / requestURL(http://삐~/WebService/삐~Service.asmx) / webMethodName(ReadBy) / userIP(127.0.0.1)
2016-07-29 09:27:37.470 (Performance) From< HttpModule > :http://삐~/WebService/삐~Service.asmx / ReadBy / 113 ms
2016-07-29 09:27:37.514 (Performance) From< HttpModule > :http://삐~/WebService/삐~Service.asmx / ReadBy / 87 ms
2016-07-29 09:27:46.580 (Debug) From< HttpModule > :인증체크 : 0 / cachekey(cUwze7r0rMTQCVHW9eXE/A==) / authkey(khMBxeLsajUjv7zdBcnrdG33JUQahNjqCqNzmH5G6nk=) / menuid(139) / permission(1)
2016-07-29 09:27:46.589 (Debug) From< HttpModule > :권한체크 : 0 / menuid(139) / permission(1) / requestURL(http://삐~/WebService/삐~Service.asmx) / webMethodName(삐~) / userIP(127.0.0.1)
2016-07-29 09:27:46.893 (Performance) From< HttpModule > :http://삐~/WebService/삐~Service.asmx / SponsorSearch / 315 ms 

 

Posted by 헝개