'Programming'에 해당되는 글 533건

Programming/.NET

프로그램을 개발할때 오류가 발생하면 대부분 Visual Studio의 디버거 프로그램을 통해 문제가 되는 코드를 찾아 수정하는 방법으로 해결이 가능합니다.

 

static void Main(string[] args)
{
    int[] i = new int[] { 0, 1, 2, 3, 4 };
    i[5] = 0;
}

 

이 프로그램은 컴파일시에는 정상적으로 처리되지만 정작 실행할때는 오류가 발생될것입니다. 그러나 다음과 같이 오류가 발생되는 정확한 소스코드 라인을 알려주기에 곧장 문제되는 부분을 찾아 수정하는 것이 가능합니다.

 

 

빌드하고 난 이후의 실행파일을 실행했을 뿐인데 이러한 소스코드상의 정보를 출력해 디버깅이 가능하도록 할 수 있는 이유는 프로젝트 자체를 Debug 모드로 빌드했기 때문입니다. 프로그램을 Debug모드로 빌드하게 되면 컴파일러는 소스코드를 전혀 건드리지 않으면서 소스코드관련 정보를 가지고 있는 PDB(program database)파일을 생성하게 되고, 문제가 발생했을 경우 CLR이 PDB파일을 로드하여 소스코드의 정보를 알려주는 것입니다.

 

그렇다면 소스코드를 건드리지 않는다는건 무슨 뜻일까요?

 

CLR은 프로그램이 실행될때의 성능에 맞게 소스코드 자체에 최적화를 적용하게 되는데 이는 빌드되는 과정에서 '최적화'라는 단계의 작업에 해당합니다. 예를 들어

 

static void Main(string[] args)
{
    int[] i = new int[] { 0, 1, 2, 3, 4 };
    SetArray(i);
}

static void SetArray(int[] i)
{
     i[5] = 0;
}

 

위와 같은 소스코드는 최적화를 거치면 아래와 같이 바뀔것입니다.

 

static void Main(string[] args)
{
    int[] i = new int[] { 0, 1, 2, 3, 4 };
    i[5] = 0;
}

 

이렇게 되는 이유는 굳이 메서드로 분리된 코드를 유지하면 매개변수로 값을 전달하고 해당 메서드를 호출해야 하는 등의 과정이 필요하기 때문입니다. 따라서 성능상 차라리 메서드의 내용을 호출자에 통째로 넣어버리는 것이 더 빠르기 때문에 위와 같은 결과가 발생하는 것입니다.(물론 모든 상황에서 이러한 동작이 수행된다고는 보장할 수 없지만) 결국 코드의 내용이 달라져 버린다는 것이 핵심이고 이런 처리를 수행하는 것은 프로젝트의 빌드모드를 Release로 했을경우에 해당합니다.

 

프로그램을 개발하는 개발단계에서 Release로 빌드하지 않는 이유는 바로 이 때문입니다. 최적화과정에서 소스코드를 변경해 버리면 디버거는 엉뚱한 소스코드의 라인을 알려주며 오류정보를 전달할 수 있기 때문입니다. 설령 PDB파일이 있다고 하더라도 PDB는 최적화 이전의 소스코드정보를 갖고 있기 때문에 별 도움이 되지 않습니다.

 

좀 다른 얘기지만 프로그램을 만들때 특정 조건의 실행여부를 판단하기 위해 종종 Console이나 혹은 MessageBox 클래스를 통해 메세지를 표시하곤 하는데 화면에 원하는 내용을 출력하기 보다는 Debug나 Trace를 사용하면 Visual Studio의 출력창을 통해 좀더 깔끔하게 메세지를 표시할 수 있습니다.

 

static void Main(string[] args)
{
    Debug.WriteLine("실행중.");
    Trace.WriteLine("실행중.");
}

 

간혹 프로그램을 빌드하는 이 2가지 모드가 무시되는 경우가 있으나 개발단계에서는 프로그램의 정확한 디버깅을 위해 Debug모드로, 사용자에게 배포할때는 Release모드로 빌드하는 습관을 들이는 것이 좋습니다.

'Programming > .NET' 카테고리의 다른 글

Debug / Release  (0) 2019.01.15
[ASP.NET MVC] Razor  (0) 2019.01.08
[ASP.NET MVC] URL 다루기  (0) 2018.12.18
[C#] 비트(bit) 연산자  (0) 2018.11.20
[ASP.NET MVC] 액션 메서드(Action Method)  (0) 2018.11.13
[C#] Thread  (0) 2018.11.06
0 0
Programming/.NET

1. Model 이용하기

 

public class Employee
{
    public string Name;
    public int Age;
    public string Department;
    public DateTime Doe;
}

▶ Model

 

public ActionResult Index()
{
    Employee em = new Employee() { Name = "홍길동"Department = "기획팀"Age = 33, Doe = new DateTime(2016, 12, 05) };

    return View(em);
}

▶ Controller

 

@model test.Models.Employee

@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Index</title>
</head>
<body>
    <div>
        <table style="border:solidborder-width:1px;">
            <thead>
                <tr><th>항목</th><th></th></tr>
            </thead>
            <tbody>
                <tr><td>이름</td><td>@Model.Name</td></tr>
                <tr><td>나이</td><td>@Model.Age</td></tr>
                <tr><td>부서</td><td>@Model.Department</td></tr>
            </tbody>
        </table>
    </div>
</body>
</html>
▶ View

 

2. ViewBag 이용하기

 

public ActionResult Index()
{
    Employee em = new Employee() { Name = "홍길동"Department = "기획팀"Age = 33, Doe = new DateTime(2016, 12, 05) };

    ViewBag.Title = "사원테이블";
    ViewBag.Width = "5px";
    ViewBag.Resignation = false;
    ViewBag.IsMarriage = true;
    ViewBag.IsExecutive = null;

    return View(em);
}

▶ Controller

 

@model test.Models.Employee

@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Index</title>
</head>
<body>
    <div>
        퇴사 : <input type="checkbox" checked="@ViewBag.Resignation" /><br />
        결혼 : <input type="checkbox" checked="@ViewBag.IsMarriage" /><br />
        임원 : <input type="checkbox" checked="@ViewBag.IsExecutive" />
        <table style="border:solid; border-width:@ViewBag.Width;">
            <caption>@ViewBag.Title</caption>
            <thead>
                <tr><th>항목</th><th></th></tr>
            </thead>
            <tbody>
                <tr><td>이름</td><td>@Model.Name</td></tr>
                <tr><td>나이</td><td>@Model.Age</td></tr>
                <tr><td>부서</td><td>@Model.Department</td></tr>
            </tbody>
        </table>
    </div>
</body>
</html>
▶ View

 

참고로 checkbox처리의 경우 true만 checked 처리되고 false나 null은 아예 속성처리가 무시됩니다.

 

3. 구문

 

<span>

    @if ((bool)ViewBag.Resignation)
    {
        @: 이직
    }
    else {
        @: 재직
    }
</span>

▶ View

 

Razor 구문은 @문자로 시작합니다. 참고로 문자열은 기본적으로 C#문으로 인식하는데 구문안에서 단순한 문자열을 출력할때는 @: 기호를 사용합니다.

 

public ActionResult Index()
{
    Employee[] em = {
        new Employee() { Name = "홍길동"Department = "기획팀"Age = 33, Doe = new DateTime(2016, 12, 05) },
        new Employee() { Name = "홍길순"Department = "경영팀"Age = 25, Doe = new DateTime(2004, 9, 8) },
        new Employee() { Name = "홍길남"Department = "영업팀"Age = 40, Doe = new DateTime(2012, 4, 17) }
    };

    return View(em);
}

▶ Controller

 

@model test.Models.Employee[]

@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Index</title>
</head>
<body>
    <div>
        <table style="border:solid; border-width:@ViewBag.Width;">
            <caption>@ViewBag.Title</caption>
            <thead>
                <tr><th>항목</th><th></th></tr>
            </thead>
            <tbody>
                @foreach (test.Models.Employee em in Model) {
                    <tr><td>이름</td><td>@em.Name</td></tr>
                    <tr><td>나이</td><td>@em.Age</td></tr>
                    <tr><td>부서</td><td>@em.Department</td></tr>
                }
            </tbody>
        </table>
    </div>
</body>
</html>
▶ View

 

배열과 같은 모델의 데이터를 열거할때는 @model 에서 모델형식을 Employee[] 처럼 배열로 지정해야 합니다.

 

4. Namespace

 

@using test.Models
@model test.Models.Employee[]

@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Index</title>
</head>
<body>
    <div>
        <table style="border:solid; border-width:@ViewBag.Width;">
            <caption>@ViewBag.Title</caption>
            <thead>
                <tr><th>항목</th><th></th></tr>
            </thead>
            <tbody>
                @foreach (Employee em in Model) {
                    <tr><td>이름</td><td>@em.Name</td></tr>
                    <tr><td>나이</td><td>@em.Age</td></tr>
                    <tr><td>부서</td><td>@em.Department</td></tr>
                }
            </tbody>
        </table>
    </div>
</body>
</html>
▶View

 

Namespace도 일반 C#구문과 마찬가지로 using을 사용합니다. 다만 Razor작성 규칙에 따라 @를 붙여야 합니다.

'Programming > .NET' 카테고리의 다른 글

Debug / Release  (0) 2019.01.15
[ASP.NET MVC] Razor  (0) 2019.01.08
[ASP.NET MVC] URL 다루기  (0) 2018.12.18
[C#] 비트(bit) 연산자  (0) 2018.11.20
[ASP.NET MVC] 액션 메서드(Action Method)  (0) 2018.11.13
[C#] Thread  (0) 2018.11.06
0 0
Programming/javascript jquery
$(document).ready(function(){
    alert('hello');
});


ready는 WEB문서의 DOM이 생성되면 실행되며 순서상 load보다 먼저입니다.


ready를 줄인 아래와 같은 구문도


$(function() {
  // Handler for .ready() called.
});


ready와 동일입니다.


$(window).load(function(){
    alert('hello');
});


모든 DOM이 생성되고 이미지와 같은 DOM안에 포함된 요소들까지 모두 Loading이 완료되고 나면 실행됩니다. 순서상 ready보다 다음입니다.

'Programming > javascript jquery' 카테고리의 다른 글

[jquery] ready와 load의 차이  (0) 2018.12.26
[javascript] 키입력 방지 예제  (0) 2013.12.03
0 0
Programming/.NET

보통 URL을 생성할때는 다음과 같이 하던게 일반적인 방법이었습니다.

 

<a href="/home/default.htm"></a>

 

그런데 ASP.NET MVC에서 URL을 생성할때는 URL을 생성하는 관련 메서드를 호출함으로서 이루어 집니다.

 

@Html.ActionLink("go""myPage")

 

가장 일반적으로는 @Html 의 ActionLink 헬퍼메서드를 사용하는 것입니다. 만약 이 메서드가 Home컨트롤러의 Index뷰에서 사용되었다면

 

<a href="/home/myPage">go</a>

 

위 형태의 링크를 생성할 것입니다. 이 방법이 사용되는 중요한 이유는 어떤 이유에 의해서 URL의 형태가 바뀌게 되면 사람이 직접 기존의 링크를 수정하지 않아도 해당 라우팅정보가 그대로 반영될 수 있기 때문입니다. 예를 들어 기존에 /Home/Index를 가리키던 라우팅정보가

 

routes.MapRoute("myRoute""myUrl/{action}"new { controller = "Home" });

 

이러한 라우트설정에 의해서 바뀌게 되면 ActionLink메서드는 바뀐 라우팅정보를 수용하여 다음처럼 링크경로를 변경하고 표시할 것입니다.

 

<a href="/myurl/myPage">go</a>

 

이와 같은 작동방식은 액션메서드의 속성에 라우트정보가 정의되어 있는 상태에서도

 

[Route("~/Default")]
public ActionResult MyPage()
{
    return View();
}

 

동일하게 라우팅정보를 적용시켜 아래와 같은 형태의 URL을 생성하게 됩니다.

 

<a href="/Default">go</a>

 

ActionLink 메서드는 기본적으로 현재의 컨트롤러내부를 가리키게 되는데 만약 다른 컨트롤러의 액션메서드를 호출하고자 한다면

 

@Html.ActionLink("go""myPage""myControl")

 

예제에서 처럼 직접 컨트롤러를 지정하면 됩니다.

 

<a href="/myControl/myPage">go</a>

 

컨트롤러와 액션이외에 특정 세그먼트와 값을 추가적으로 전달해야할 경우도 있는데 이럴때는 익명 형식을 통해 해당 세그먼트와 그 값을 정의해 두면 됩니다.

 

@Html.ActionLink("go""myPage"new { id = "cliel" })
<a href="/Home/myPage/cliel">go</a>

 

이렇게 처리될 수 있는 이유는 기본적으로 라우트정보가 설정되는 RouteConfig.cs 파일에 다음과 같이 라우트정보가 정의되어 있기 때문입니다.

 

routes.MapRoute(
    name"Default",
    url"{controller}/{action}/{id}",
    defaultsnew { controller = "Home"action = "Index"id = UrlParameter.Optional }
);

 

만약 id 세그먼트에 대한 정의가 라우정보에 존재하지 않았을때 같은 방식으로 ActionLink 메서드를 사용하게 되면 id는 세그먼트가 아닌 단순한 get값을 추가해 줍니다.

 

<a href="/Home/myPage?id=cliel">go</a>

 

ActionLink는 단순히 우리가 필요한 링크를 만들 수 있을 뿐만 아니라 HTML표현에 필요한 여러가지 속성들을 설정할 수도 있습니다.

 

@Html.ActionLink("go""myPage"nullnew { id = "mylink"@class = "mystyle" })
<a class="mystyle" id="mylink" href="/Home/myPage">go</a>

 

익명형식으로 설정할 속성의 이름과 속성값을 지정하고 있는데 참고로 ASP.NET MVC에서는 C#에서 특정한 예약어를 사용해야할때 앞에 @문자를 붙여 쓸 수 있습니다. 예제에서 class의 속성을 설정하기 위해 @class로 표현한것은 바로 이때문입니다.

 

물론 이외에도 다음과 같이 필요한 모든 값을 ActionLink에 전달하면 링크가 생성되는 형태를 세부적으로 다룰 수 있습니다.

 

@Html.ActionLink("go""myPage""Home""https""www.naver.com""target"new { id = "cliel" } ,new { id = "mylink"@class = "mystyle" })
<a class="mystyle" id="mylink" href="https://www.naver.com/Home/myPage/cliel#target">go</a>

 

ActionLink 메서드와 비슷하게 Action 메서드도 존재하는데 이 메서드는 링크를 생성하는게 아닌 http://cliel.com/Home/Index 와 같은 단순한 문자열만을 생성합니다.

 

뿐만 아니라 URL은 특정 라우트를 대상으로 URL을 생성할 수도 있어서 예를 들어 RouteConfig.cs 파일에 기본적으로 다음과 같은 등록된 라우트정보가 있다면

 

@Html.RouteLink("go""Default"new { id = "cliel" })

 

위와 같이 라우트명을 지정해 URL을 해당 라우트를 기반으로 하여 생성할 수 있습니다.

 

/Home/Index/cliel

 

RouteLink 메서드에서는 별도의 컨트롤러와 메서드를 지정하지 않았지만 이는 라우트에 등록된 기본값으로 대체될 수 있고 id세그먼트는 지정된 값으로 대체될 수 있기 때문입니다.

 

이제 까지살펴본 ActionLink 메서드는 뷰에서 사용이 가능한 메서드인데 때로는 컨트롤러내부에서 URL을 생성해야할 필요가 있습니다. 이럴때는 Url 객체의 Action 과 RouteUrl 메서드를 사용할 수 있습니다.

 

string url = Url.Action("Index""Home"new { id = "cliel" });

 

우선 Action 메서드는 ActionLink 메서드와 사용법이 완벽히 일치하므로 별다른 설명이 필요없을 것입니다.

 

string url = Url.RouteUrl(new { controller = "Home"action = "MyPage" });

 

RouteUrl 메서드는 RouteConfig.cs 에서 라우트를 설정할때와 비슷한 개념과 방식으로 URL을 생성할 수 있습니다.

'Programming > .NET' 카테고리의 다른 글

Debug / Release  (0) 2019.01.15
[ASP.NET MVC] Razor  (0) 2019.01.08
[ASP.NET MVC] URL 다루기  (0) 2018.12.18
[C#] 비트(bit) 연산자  (0) 2018.11.20
[ASP.NET MVC] 액션 메서드(Action Method)  (0) 2018.11.13
[C#] Thread  (0) 2018.11.06
0 0
Programming/.NET

비트 단위의 논리 연산을 수행합니다.

 

int i = 10;

int bit_and = i & 3;
int bit_or = i | 3;
int bit_xor = i ^ 3;
int bit_not = ~i;

Console.WriteLine("Result : {0} | {1}", bit_xor, bit_not);

 

& 는 and 연산으로서 같은 값인것만 1로 처리합니다. 그래서 10의 값이 1010과 3의 값인 0011을 연산하면

 

1010
0011
----
0010

 

이 되어 결과는 3이 됩니다.

 

| 는 or 연산입니다. 따라서 둘중 어느 하나라도 1이 있으면 결과는 1이 됩니다.

 

1010
0011
----
1011

 

^ 는 Xor 연산으로 둘의 값이 1이든 0이든 서로 틀려야만 1이 됩니다.

 

1010
0011
----
1001

 

마지막으로 ~는 보수입니다. 피연산자의 모든 값을 반대로 뒤집습니다.

 

1010
----
0101

 

다만 이 경우 부호를 나타내는 최상위 비트도 반대로 처리하기 때문에 결과는 -값이 됩니다.

'Programming > .NET' 카테고리의 다른 글

[ASP.NET MVC] Razor  (0) 2019.01.08
[ASP.NET MVC] URL 다루기  (0) 2018.12.18
[C#] 비트(bit) 연산자  (0) 2018.11.20
[ASP.NET MVC] 액션 메서드(Action Method)  (0) 2018.11.13
[C#] Thread  (0) 2018.11.06
[C#] MemoryStream / StreamWriter / StreamReader / BinaryWriter / BinaryReader  (0) 2018.10.31
0 0
Programming/.NET

사용자가 요청한 URL에 따라 컨트롤러 내부에 해당하는 액션메서드(Action Method)를 찾아 사용자에게 보여줍니다.

 

public ActionResult Index()
{
    return View();
}

 

액션메서드를 기본적으로 같은 이름의 뷰를 찾기 때문에 액션 메서드가 Index라면 Index.cshtml과 같은 뷰를 렌더하게 됩니다.

 

[ActionName("myIndex")]
public ActionResult MyPage()
{
    return View("MyPage");
}

 

액션 메서드에 ActionName 속성을 사용하게 되면 설정 값에 따라 액션메서드 이름을 재정의하게 됩니다. 따라서 위 예제에서는 MyIndex로 접근시 MyPage 액션메서드를 실행하게 됩니다. 다만 MyIndex로 요청을 받으면 뷰도 기본적으로 MyIndex뷰를 찾게 되므로 MyIndex라는 뷰를 따로 생성하거나 아니면 View 메서드를 호출할때 보여줄 수 있는 다른 뷰를 설정하는 방법으로 적절히 대처해야 합니다.

 

public ActionResult MyPage()
{
    return View();
}

[HttpPost]
public ActionResult MyPage(string idstring name)
{
    return View();
}

 

같은 이름의 액션메서드라도 요청방식에 따라 다른 액션메서드가 호출될 수 있도록 할 수 있습니다. 예제에서는 [HttpPost] 속성을 설정해 두번째 MyPage 액션 메서드는 Post요청에만 응답할 수 있도록 하고 있습니다. 이 외에도 HttpGet이나 HttpPut과 같은 속성을 설정할 수 있습니다.

 

[NonAction]
public ActionResult MyPage(string idstring name)
{
    return View();
}

 

NonAction 속성은 해당 메서드가 액션 메서드로서 인식될 수 없도록 합니다.

 

만약 요청으로 들어온 URL에서 어떤 액션 메서드를 반환해야 할지 찾을 수 없으면 MVC 프레임워크는 기본적으로 HTTP 404 오류를 반환합니다. 하지만 HandleUnknownAction 메서드를 재정의하면 알 수 없는 요청에 대해 다른 대체적인 방법으로 다음과 같이 처리할 수 있습니다.

 

protected override void HandleUnknownAction(string actionName)
{
    Response.Write("Error!!");
}

0 0
Programming/.NET

실행중인 프로그램은 하나의 프로세스로 볼 수 있고 프로세스는 시작될때 기본적으로 하나의 스레드를 생성하게 됩니다. 스레드가 없는 프로세스는 존재하지 않습니다. 프로세스가 무엇인가 작업을 수행하려면 반드시 해당 작업을 수행하는 스레드를 생성해야 합니다. 스레드는 프로세스의 작업단위입니다. 예를 들어 사용자로부터 특정값을 입력받아 합계를 계산하는 아래와 같은 프로그램이 있습니다.

 

static void Main(string[] args)
{
    Console.Write("첫번째 값 : ");
    string i = Console.ReadLine();

    Console.Write("두번째 값 : ");
    string j = Console.ReadLine();

    int sum = int.Parse(i) + int.Parse(j);

    Console.WriteLine("결과 : {0}", sum);

    Console.Read();
}

 

이 프로그램은 입력과 계산, 결과출력에 대한 일련의 작업이 main 메서드에 정의되어 있고 따라서 메서드 실행을 위한 스레드를 생성해야 하는 것입니다. 프로세스는 반드시 하나이상의 스레드를 생성하며 기본적으로 생성되는 최초의 스레드를 주 스레드(main thread)라고 합니다.

 

CPU는 각 프로세스마다 생성한 스레드를 하나씩 돌아가며 실행합니다. 물론 단일 CPU는 하나의 스레드만을 실행할 수 있었지만 내부에 4코어, 8코어처럼 멀티코어를 가진 CPU라면 코어수만큼의 여러 스레드를 동시에 실행할 수도 있을 것입니다. 만약 4코어라면 4개의 스레드를 동시에 순회하면서 실행하는 것이 가능합니다.  그러나 일반적으로 CPU가 실행해야할 스레드의 수는 수십개나 그 이상이기에 모든 스레드를 동시에 실행할 수는 없습니다. 4개나 8개의 스레드를 동시에 실행한다고 하더라도 이들 스레드를 실행한 뒤 종료될때까지 기다릴 수 없기에 시간을 조금씩 할당해서 대기중인 각 스레드를 모두 돌아가며 실행해야 합니다.

 

스레드안에는 CPU가 읽어 실행될 명령어집합이 존재하는데 이를 스레드문맥(thread context)이라고 합니다. 메서드로 비유하자면 메서드의 시작부터 마지막까지 작성된 일련의 코드에 해당하는 것입니다. CPU는 스레드의 문맥을 읽어 실행하다가 다른 스레드를 실행하기 직전에 현재 실행중인 스레드의 상태를 스레드문맥에 저장합니다. 그런뒤 다른 스레드로 실행을 이동하게 되는데, 현재상태를 문맥에 저장해야 하는 이유는 스레드의 실행계획이 담긴 스케쥴링에 따라 CPU가 다시 해당 스레드를 실행할때 이전에 저장된 상태를 스레드문맥으로부터 읽어들여서 마치 이전부터 계속 실행중이었던것처럼 스레드의 실행을 이어나갈 수 있기 때문입니다.

 

Thread는 기본적으로 현재 실행중인 스레드에 접근할 수 있는 몇가지 속성을 제공하며 필요한 경우 새로운 새로운 스레드를 생성할 수도 있습니다.

 

static void Main(string[] args)
{
    Thread t = new Thread(myThread);
    t.Start();

    Console.Read();
}

static void myThread()
{
    Console.WriteLine("hahaha");
}

 

스레드를 생성할때는 스레드를 통해 실행할 메서드를 제공해야 합니다. 이때 메서드는 스레드로서 실행하는 하나의 작업단위, 즉. 명령의 집합이라고 볼 수 있습니다.

 

스레드가 생성되 Start() 메서드가 호출되면 이제 하나의 프로세스는 2개의 스레드가 생성된 것이며 CPU에 의해 각 스레드는 별도의 작업으로서 실행될 것입니다.

 

여기서 스레드가 왜 필요한지는 스레드의 작업단위인 메서드를 보면 알 수 있습니다. 현재 메서드의 명령을 그대로 실행하면서 동시에 다른 작업을 해야하는 경우가 발생하는 것입니다. 예를 들어

 

static void Main(string[] args)
{
    while (true) {
        string s = Console.ReadLine();

        if (s.Trim() == "exit")
            return;

        Console.WriteLine(s);
    }
}

 

위와 같은 프로그램의 경우 사용자로부터 값을 입력받아 입력받은 값을 그대로 뿌려주도록 되어 있습니다. 프로그램을 종료하려면 'exit'를 입력하면 됩니다. 그런데 좀 억지스럽지만 이 프로그램에서 3초씩마다 뭔가를 계산하고 출력하는 기능을 추가해야 한다고 가정해 보겠습니다. 그러면서 본래 프로그램이 수행하는 작업은 계속되어야 합니다.

 

static void Main(string[] args)
{
    myThread();

    while (true) {
        string s = Console.ReadLine();

        if (s.Trim() == "exit")
            return;

        Console.WriteLine(s);
    }
}

static void myThread()
{
    while (true) {
        Thread.Sleep(3000);
        Console.WriteLine("처리됨");
    }
}

 

위 예제는 스레드를 사용하지 않고 만든 경우인데 이렇게 해서는 제대로 프로그램을 사용할 수 없게 됩니다. 주 스레드는 첫명령인 myThread() 메서드를 호출하는데 이 메서드는 무한대로 3초마다 무엇인가를 처리하고 결과를 뿌려주기 때문입니다. 주 스레드가 이 작업에 매달리게 되고 결국 주 스레드는 하위의 while문을 실행할 여지가 없어지게 됩니다.

 

static void Main(string[] args)
{
    Thread t = new Thread(myThread);
    t.Start();

    while (true) {
        string s = Console.ReadLine();

        if (s.Trim() == "exit")
            return;

            Console.WriteLine(s);

    }
}

 

바로 이럴때 스레드가 필요합니다. 주 스레드는 myThread() 메서드 실행을 위한 별도의 스레드를 생성한뒤 자신이 처리해야할 부분을 계속 처리할 수 있게 됩니다.

 

일단 스레드가 생성되 실행되면 해당 스레드를 생성한 프로세스는 모든 스레드가 종료되어야만 종료될 수 있습니다. 이런 형태의 스레드를 전경 스레드(foreground thread)라 하는데 바꾸어 말하면 프로세스가 실행을 종료하기 위해서는 모든 전경 스레드가 종료되어야 하는 것입니다.

 

static void Main(string[] args)
{
    Thread t = new Thread(myThread);
    t.IsBackground = true;
    t.Start();
           

    Console.Read();
}

 

하지만 스레드의 IsBackground속성을 true로 설정하면 해당 스레드를 배경 스레드(background thread)로서 실행할 수 있게 됩니다. 배경 스레드는 전경 스레드와는 달리 해당 스레드의 종료여부와 상관없이 프로세스가 종료되면 같이 종료됩니다. 결국 프로세스가 종료되기 위한 스레드는 전경 스레드에 한정됩니다.

 

하지만 배경 스레드라 하더라도 다음과 같은 방법으로 스레드종료를 대기할 수 있습니다.

 

static void Main(string[] args)
{
    Thread t = new Thread(myThread);
    t.IsBackground = true;
    t.Start();

    t.Join();

    Console.Read();
}

 

스레드 처리중 join() 메서드를 만나면 t에 해당하는 스레드가 종료될때까지 t스레드를 호출한 주 스레드는 무한정 대기하게 됩니다.

 

스레드를 통해 메서드를 호출할때 필요하다면 특정 값을 메서드에 전달할 수도 있습니다.

 

static void Main(string[] args)
{
    Thread t = new Thread(myThread);
    t.Start(100);

    Console.Read();
}

static void myThread(object o)
{
    Console.WriteLine("입력값 : " + o.ToString());
}

 

전달가능한 매개변수는 object형이어야 하며 단 하나의 매개변수만 가능합니다. 여러개의 매개변수를 전달해야 한다면 배열을 사용하거나 사용자 정의 타입을 구현해 필요한 데이터를 전달하면 됩니다.

 

static void Main(string[] args)
{
    myValue mv = new myValue();
    mv.param1 = 100;
    mv.param2 = 200;

    Thread t = new Thread(myThread);
    t.Start(mv);

    Console.Read();
}

static void myThread(object o)
{
    myValue mv = (myValue)o;
    Console.WriteLine("입력값 : " + mv.param2);
}

 

0 0
Programming/.NET

MemoryStream은 일련의 바이트 데이터를 메모리에 쓰고 읽는 기능을 제공합니다.

 

byte[] cb = BitConverter.GetBytes('a');
byte[] ib = BitConverter.GetBytes(1000);

MemoryStream ms = new MemoryStream();

 

ms.Write(cb, 0, cb.Length);
ms.Write(ib, 0, ib.Length);

 

예제에서는 문자형 a와 정수형 1000데이터를 바이트로 변환해 MemoryStream으로 메모리에 쓰고 있습니다. 메모리에 쓸때는 Write메서드를 사용하며 0부터 각 데이터 배열길이까지 전체를 쓰도록 하고 있습니다.

 

byte[] cb = BitConverter.GetBytes('a');
byte[] ib = BitConverter.GetBytes(1000);

MemoryStream ms = new MemoryStream();

 

ms.Write(cb, 0, cb.Length);
ms.Write(ib, 0, ib.Length);

ms.Position = 0;

byte[] outcb = new byte[2];
byte[] outib = new byte[4];

ms.Read(outcb, 0, 2);
ms.Read(outib, 0, 4);

char c = BitConverter.ToChar(outcb, 0);
int i = BitConverter.ToInt32(outib, 0);

Console.WriteLine(c);
Console.WriteLine(i);

 

메모리에 데이터를 쓴뒤 읽기 위해서는 Position 속성을 0으로 하여 MemoryStream의 내부 배열위치를 초기화해야 합니다. 그래야 다시 처음부터 데이터를 읽을 수 있습니다. 읽기 메서드는 Read이며 이때 데이터를 읽어들이기 위한 byte배열이 별도로 필요합니다.

 

byte[] cb = BitConverter.GetBytes('a');
byte[] ib = BitConverter.GetBytes(1000);

MemoryStream ms = new MemoryStream();

 

ms.Write(cb, 0, cb.Length);
ms.Write(ib, 0, ib.Length);

ms.Position = 0;

byte[] outData = ms.ToArray();

char c = BitConverter.ToChar(outData, 0);
int i = BitConverter.ToInt32(outData, 2);

Console.WriteLine(c);
Console.WriteLine(i);

 

위 예제는 MemoryStream의 ToArray() 메서드를 통해 byte배열 전체를 가져오도록한 것입니다. 이렇게 전체를 가져오고 나면 해당 byte배열에 원하는 데이터를 가져올때 가져올 데이터의 시작위치를 지정하는것에 주의(int i = BitConverter.ToInt32(outData, 2))하도록 합니다.

 

같은 방법으로 문자열을 MemoryStream에 담을 수도 있습니다.

 

MemoryStream ms = new MemoryStream();

byte[] b = Encoding.UTF8.GetBytes("Hello World!");
ms.Write(b, 0, b.Length);

 

문자열은 인코딩을 통해서 byte배열에 담는데 이걸 좀더 간단히 하기위해 StreamWriter를 사용할 수 있습니다.

 

MemoryStream ms = new MemoryStream();
StreamWriter sw = new StreamWriter(ms, Encoding.UTF8);

sw.WriteLine("hello world!");
sw.Flush();

 

StreamWriter의 WriteLine메서드는 문자열쓰기 전용이며 정수형등 다른 단일 데이터의 경우 Write메서드를 사용합니다.

 

StreamWriter가 데이터를 쓸때는 곧장 MemoryStream에 기록하는 것이 아니라 내부적으로 갖고 있는 일정한 크기의 byte배열버퍼에 데이터를 기록합니다. 그러다 만약 일정한 크기에 도달하면 그때 MemoryStream에 기록하게 되는데 마지막에 있는 Flush() 메서드는 쓸데이터가 버퍼에 다 차지않더라도 MemoryStream에 접근해 데이터를 기록하도록 하는 메서드입니다.

 

MemoryStream ms = new MemoryStream();
StreamWriter sw = new StreamWriter(ms, Encoding.UTF8);

sw.WriteLine("hello world!");
sw.Flush();

ms.Position = 0;

StreamReader sr = new StreamReader(ms, Encoding.UTF8);
string s = sr.ReadToEnd();

Console.WriteLine(s);

 

StreamWriter로 쓴 데이터는 다시 StreamReader로 읽을 수 있습니다. 인코딩방식은 StreamWriter를 사용할때와 똑같이 지정해야 하며 데이터를 반드시 Position을 0으로 맞춰야 합니다.

 

MemoryStream ms = new MemoryStream();

BinaryWriter bw = new BinaryWriter(ms);
bw.Write("hello ");
bw.Write("world!" + Environment.NewLine);
bw.Flush();

 

BinaryWriter는 메모리에 2진데이터를 쓰기위한 전용입니다. BinaryWriter를 통해 데이터를 쓰는 경우 byte배열 중간중간에 다음 몇바이트가 데이터바이트인지를 나타내는 구분자가 추가됩니다. 때문에 뭔가 규격화된 데이터를 다루고자할때는 BinaryWriter를 사용합니다. 쓰기 작업에 대한 효휼성도 좋아서 성능이 우선시 되는 상황이라면 BinaryWriter도 좋은 선택이 될 수 있습니다.

 

MemoryStream ms = new MemoryStream();

BinaryWriter bw = new BinaryWriter(ms);
bw.Write("hello ");
bw.Write("world!" + Environment.NewLine);
bw.Flush();

ms.Position = 0;

BinaryReader br = new BinaryReader(ms);
string s = br.ReadString();
s += br.ReadString();

Console.WriteLine(s);

 

BinaryWriter로 작성된 데이터는 BinaryReader로 읽을 수 있습니다. 예제에서는 ReadString() 메서드를 2번 호출해 문자열데이터를 읽고 있는데 ReadString()메서드를 한번 호출할때마다 BinaryWriter를 사용할때 Write메서드로 작성한 데이터를 한묶음씩 읽어오기 때문입니다.

0 0
Programming/.NET

Visual Studio -> APS.NET MVC 프로젝트에서 cshtml파일을 생성할때나 혹은 기존 파일을 열지 못할때

 

%LocalAppData%\Microsoft\VisualStudio\14.0\ComponentModelCache

 

우선 Visual Studio를 닫고 이 폴더의 파일을 모두 삭제한 뒤 다시 실행해보자...

 

관련 오류 : Invalid pointer (Exception from HRESULT: 0x80004003 (E_POINTER))

0 0
Programming/.NET

TCP는 UDP에서 보장받지 못하는 신뢰성을 확보할 수 있습니다. 즉, 데이터를 송신하면 수신받은 측에서는 데이터를 받았다는 응답을 반드시 해야 하고 만약 응답이 없으면 데이터를 자동으로 다시 보내는등의 동작을 수행합니다. 뿐만 아니라 데이터의 송신순서와 수신순서가 일치하는등 높은 신뢰성을 유지합니다.

 

TCP도 UDP때와 마찬가지로 클라이언트에서 특정 내용을 보내면 서버에서 'server : '문자열을 붙여 회신하는 방식으로 구현해 보고자 합니다.

 

static void Main(string[] args)
{
    Thread t = new Thread(myMethod);
    t.IsBackground = true;

    t.Start();

Console.Read();

}

 

static void myMethod()
{
    using (Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) {
        IPEndPoint iEp = new IPEndPoint(IPAddress.Any, 2020);
        socket.Bind(iEp);
        socket.Listen(10);

        while (true) {
            Socket client = socket.Accept();

            myData md = new myData();
            md.client = client;
            md.rec = new byte[1024];

            client.BeginReceive(md.rec, 0, md.rec.Length, SocketFlags.None, myRecCallBack, md);
        }
    }
}

 

서버에서는 우선 TCP통신을 위한 소켓을 생성합니다. UDP와는 다르게 SocketType을 Stream으로 하며 ProtocolType을 TCP로 지정합니다.

 

그런다음 모든 IP에 대해 2020포트로 데이터 수신을 대기하도록 합니다. 여기까지는 UDP와 비슷합니다. 그런데 TCP에서는 Bind를 하고 난뒤 Listen메서드를 호출해서 클라이언트의 접속을 대기해야 합니다. Listen메서드 호출시 전달된 정수값은 클라이언트를 최대보관할 수 있는 큐의 값입니다. 10이면 10개의 클라이언트접속을 보관하겠다는 의미입니다.

 

이제 while문을 통해 무한정 클라이언트의 접속을 처리하게 되는데 UDP에서는 클라이언트와 1:1로 연결되는 형태가 아니므로 데이터의 송수신을 위해 일일이 클라이언트의 접점을 필요로 했으나 TCP는 클라이언트와 직접적으로 1:1연결되는 형태이므로 클라이언트의 접점을 필요로 하지 않습니다. 대신 Socket의 Accept메서드를 통해 접속중인 클라이언트의 개체를 꺼냄으로서 각 클라이언트를 구별할 수 있고 통신도 이 Accept에서 나온 개체와 통신하게 됩니다. 소켓은 그저 클라이언트의 접속을 생성해줄 뿐입니다.

 

이제 BeginReceive메서드로 가져온 접속중인 클라언트에게서 데이터를 수신받기 위해 BeginReceive메서드를 호출합니다. 이는 비동기 호출입니다. 물론 동기호출로 Receive메서드를 사용할 수 있지만 해당 클라이언트와의 통신이 모두 마무리될때까지 다른 클라이언트는 무작정 대기상태에 들어가게 되므로 성능상 불리하게 됩니다.

 

static void myRecCallBack(IAsyncResult ar)
{
    myData md = (myData)ar.AsyncState;

    int i = md.client.EndReceive(ar);

    string s = Encoding.UTF8.GetString(md.rec, 0, i);

    byte[] sndB = Encoding.UTF8.GetBytes("server : " + s);
    md.client.BeginSend(sndB, 0, sndB.Length, SocketFlags.None, mySadCallBack, md.client);
}

 

비동기 호출에서 데이터가 수신완료될때 호출될 콜백메서드입니다. 이 콜백메서드에서는 접속중인 클라이언트와 데이터를 수신한 바이트배열 모두를 다루어야 하기에 비동기메서드호출시

 

public class myData {

public Socket client;
    public byte[] rec;
}

 

위와 같이 별도의 클래스를 만들어 client와 배열 모두를 담아 마지막 매개변수로 전달해야 합니다.

 

그러면 콜백에서는 해당 데이터타입으로 변환하여 수신을 마무리 짓고 받은 데이터를 문자열로 변환해 'server : '를 붙여 다시 BeginSend메서드로 데이터를 보냅니다.

 

static void mySadCallBack(IAsyncResult ar)
{
    Socket client = (Socket)ar.AsyncState;
    client.EndSend(ar);
    client.Close();
}

 

데이터를 보낼 메서드도 비동기로 호출하였기 때문에 이를 처리할 콜백메서드가 필요합니다. 이 콜백메서드는 데이터 송신을 모두 완료한뒤 호출되므로 송신을 마무리짓고 클라이언트와의 접속을 해제합니다.

 

static void Main(string[] args)
{
    Thread t = new Thread(myMethod);
    t.IsBackground = true;

 

        t.Start();

Console.Read();

}


static void myMethod(object o)
{
    Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

    IPAddress ip = IPAddress.Parse("192.168.20.126");
    EndPoint sEP = new IPEndPoint(ip, 2020);

    socket.Connect(sEP);

    byte[] send = Encoding.UTF8.GetBytes("hi server!!");
    socket.Send(send);

    byte[] rec = new byte[1024];
    int i = socket.Receive(rec);
    string s = Encoding.UTF8.GetString(rec, 0, i);

    Console.WriteLine(s);

    socket.Close();
}

 

클라이언트는 비교적 간소합니다. Stream와 Tcp로 소켓을 생성해 접속할 서버의 접점을 생성하고 Connect메서드를 통해 서버에 접속합니다.

 

그리고 Send메서드로 서버에 데이터를 보내고 다시 Receive메서드로 서버에서 보낸 데이터를 수신해 사용자에게 표시합니다. 이와 같은 방법으로 클라이언트와 통신하는데 대한 대부분의 서버를 구성할 수 있습니다.

 

static void myRecCallBack(IAsyncResult ar)
{
    myData md = (myData)ar.AsyncState;

    int i = md.client.EndReceive(ar);

    string s = Encoding.UTF8.GetString(md.rec, 0, i); //일단 웹서버 테스트이므로 수신내용은 무시

    string sendMsg = "HTTP/1.0 200 OK/nContent-Type: text/html; charset=UTF-8\r\n\r\n" +
                    "<html><body>Web Server Response</body></html>";

    byte[] sndB = Encoding.UTF8.GetBytes(sendMsg);
    md.client.BeginSend(sndB, 0, sndB.Length, SocketFlags.None, mySadCallBack, md.client);
}

 

위 예제는 myRecCallBack메서드를 수정해 웹서버를 구성한 것입니다. 테스트용이므로 클라이언트에게 어떤 요청을 받든지 그것은 무시하기로 하고 응답에 대한 적절한 메세지를 작성해 클라언트에 보내면 됩니다. 클라이언트는 웹브라우저를 통해 설정된 포트로 접근하면 수신된 내용을 화면에 표시할 것입니다.

tcp
0 0
Programming/.NET

ASP.NET MVC에서는 기본구조인 컨트롤러와 뷰, 그리고 라우팅시스템까지 전 구조를 통째로 분리하여 '영역'이라는 것을 만들고 이를 웹프로그램에 그대로 적용할 수 있습니다.

 

프로젝트에 '영역(Area)'을 추가하려면 프로젝트에서 마우스 오른쪽 버튼을 눌러 'Add -> Area'를 클릭합니다. 그런다음 적당한 영역이름을 입력하고 'OK'버튼을 클릭하면 다음과 같은 영역이 프로젝트에 생성됩니다.

 

 

예제는 이름을 'MyArea'로 하였습니다.

 

영역을 추가하고 나면 MyAreaAreaRegistration.cs 파일을 자동으로 열어 보여주는데 이 파일에서 RegisterArea 메서드에 보면 MyArea 영역에 대한 라우트정보가 새로 구성되어 있음을 확인할 수 있습니다.

 

public class MyAreaAreaRegistration : AreaRegistration 
{
    public override string AreaName 
    {
        get 
        {
            return "MyArea";
        }
    }

    public override void RegisterArea(AreaRegistrationContext context) 
    {
        context.MapRoute(
            "MyArea_default",
            "MyArea/{controller}/{action}/{id}",
            new { action = "Index"id = UrlParameter.Optional }
        );
    }
}

 

클래스는 AreaRegistration 클래스를 상속받아 내부에 RegisterArea 메서드를 통해 라우트정보를 등록하고 있는데 이 메서드는 프로젝트의 Global.asax 파일에서

 

AreaRegistration.RegisterAllAreas();

 

AreaRegistration 개체의 RegisterAllAreas 정적메서드를 호출하여 AreaRegistration 를 상속받은 모든 클래스를 찾도록 합니다. 그런 후 해당 클래스가 존재하면 내부에 RegisterArea 메서드를 호출함으로서 라우트가 적용될 수 있도록 합니다.

 

라우트정보에 따라 새롭게 생성된 영역은 이제부터 컨트롤러와 뷰를 적절히 배치하여 MyArea/Home/Index 와 같은 형식으로 접근이 가능하게 됩니다. 그러나 주의해야할 것이 있는데 만약 메인 프로젝트에서 Home컨트롤러가 존재하고 영역에도 Home컨트롤러가 존재한다면 /Home/Index 형식으로 접근할때 오류를 발생시킬 수 있습니다. 따라서 다음과 같이 우선적으로 처리될 기본 네임스페이스를 지정해야 합니다.

 

routes.MapRoute("Default""{controller}/{action}/{id}"new { controller = "Home"action = "Index"id = UrlParameter.Optional }new[] { "WebApplication1.Controllers" });

 

참고로 네임스페이스의 우선순위는 Global.asax 의 Application_Start 메서드 에서도 다음과 같이 설정할 수 있습니다.

 

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();
    RouteConfig.RegisterRoutes(RouteTable.Routes);

    ControllerBuilder.Current.DefaultNamespaces.Add("MyNamespace");
    ControllerBuilder.Current.DefaultNamespaces.Add("MyName*");
}

 

MyName에 *은 해당 네임스페이스와 자식 네임스페이스도 포함된다는 것을 의미합니다.

 

'영역'이라는 것은 새롭게 프로젝트에 추가해서 만들 수 있을 뿐만 아니라 필요하다면 기존에 있던 컨트롤러에 영역속성을 추가하여 새로운 영역을 생성하는 것도 가능합니다.

 

[RouteArea("abc")]
public class HomeController : Controller
{
    // GET: Home
    [Route("Home")]
    public ActionResult Index()
    {
        return View();
    }

    public ActionResult MyPage()
    {
        return View();
    }
}

 

기존에 HomeController에 RouteArea 속성을 추가하여 abc라는 영역을 생성하였습니다. 또한 Index 액션메서드에는 Route 속성도 추가되었으므로 /abc/Home/Index 과 같은 방법으로 접근할 수 있게 됩니다.

 

참고로 만약 영역안에서 다른 영역에 존재하는 액션메서드를 찾아가는 링크를 생성해야 한다면 area 속성을 만들어서 해당하는 영역을 지정해 줘야 합니다.

 

@Html.ActionLink("go""Index"new { area = "MyArea2" })

만약 별도의 영역이 아닌 최상위의 경우라면 area는 ""형식으로 공백을 지정하도록 합니다.

'Programming > .NET' 카테고리의 다른 글

Invalid pointer (Exception from HRESULT: 0x80004003 (E_POINTER))  (0) 2018.10.24
[C#] 네트워크 - TCP  (0) 2018.10.17
[ASP.NET MVC] 영역(Area)  (0) 2018.10.10
[C#] 네트워크 - UDP  (0) 2018.09.20
[C#] async / await  (0) 2018.09.04
[C#] Path  (0) 2018.08.23
0 0
Programming/Microsoft SQL Server

일반적으로 텍스트 검색시 사용하는 쿼리로 Like검색을 들 수 있습니다.

 

Select *
From [Sales].[OrderLines]
Where [Description] Like '%car%';

 

그런데 위와 같은 방식은 테이블스캔작업이 발생되기에 행(Row)이 많으면 원하는 내용을 찾는데 오랜 시간이 소모될 것입니다. 물론 인덱스를 타게 할 수도 있으나 car%와 같은 방법으로 해야만 해서 내용 중간에 'car'가 있는 데이터는 찾을 수 없고 더군다나 무작위의 문자열을 다루는 열(Column)에는 특별한 경우가 아닌이상 인덱스를 걸어놓지 않습니다.

 

결국 신문기사 검색과 같은 텍스트검색을 위해서는 Like이외에 다른 방법이 필요하게 됐고 보통의 인덱스처럼 열(Column)에 인덱스를 적용하는 것이 아니라 특정 단어나 문장을 대상으로 인덱스를 적용하는 '전체 텍스트 검색'을 사용자에게 제공하게 되었습니다.

 

전체 텍스트 검색기능을 이용하려면 일단 서버에 SQL Full-text Filter Daemon 서비스가 실행중이어야 하는데

 

 

이 서비스는 SQL Server를 설치할때 'Full-Text and Semantic Extractions for Search'항목을 옵션으로 지정해 설치를 해야 합니다.

 

 

이것으로 보아 전체 텍스트 검색을 위한 엔진이 별도로 존재한다는 것을 알 수 있을 것입니다. 만약 사용자가 전체 텍스트 검색을 위한 쿼리를 실행하면 전체 텍스트 엔진은 전체 텍스트 인덱스를 검색해 해당 열을 찾아 그 결과를 SQL Server 서비스에 알려주고 서비스는 그 열의 내용을 가져와 사용자에게 반환하는 방식으로 전체 텍스트검색을 수행하게 됩니다.

 

그럼 실제 검색할 문자열이 존재하는 테이블을 대상으로 전체 텍스트 검색을 사용해 보겠습니다. 아래 예제의 대상은 WideWorldLmporters라는 Sample 데이터베이스를 사용한 것입니다.

 

전체 텍스트 검색사용을 위해서는 우선 전체 텍스트 카탈로그를 생성해야 합니다. 이는 전체 텍스트 인덱스를 저장하고 있는 저장소 역활을 수행합니다.

 

Create Fulltext Catalog SalesOrderLinesCatalog As Default;

 

카타로그는 Create Fulltext Catalog 구문을 사용하며 SalesOrderLinesCatalog라는 이름으로 카타로그를 생성하였습니다. 참고로 sys.fulltext_catalogs 테이블을 살펴보면 생성한 카타로그의 정보를 확인해 볼 수 있습니다.

 

Select *
From sys.fulltext_catalogs;

 

만약 생성한 카탈로그의 is_default값이 1이면 해당 카탈로그가 기본 카탈로그임을 의미하는 것입니다.

 

카탈로그 삭제는 Drop FullText를 사용합니다.

 

Drop Fulltext Catalog SalesOrderLinesCatalog

 

카탈로그를 생성하고 나면 이제 해당 카탈로그에 전체 텍스트검색 인덱스를 생성해야 합니다.

 

Create FullText Index On Sales.OrderLines([Description])
Key Index PK_Sales_OrderLines
On SalesOrderLinesCatalog
With Change_Tracking Auto;

 

위 예제에서 인덱스는 Sales.OrderLines 테이블의 Description 열에 생성하였으며 전체 텍스트 검색엔진이 검색후 결과를 PK_Sales_OrderLines 인덱스키로 SQL Server 서비스에 반환하도록 하였습니다. 즉, 전체 텍스트 검색엔진은 검색 후에 그 내용자체가 아닌 해당 내용이 있는 열을 가리키고 있는 키값을 반환하기 때문에 전체 텍스트 인덱스를 생성하려고 하는 테이블은 반드시 값이 중복될 수 없는 Primary Key나 Unique Key를 가지고 있어야 합니다. 참고로 Description 열은 nVarChar형인데 char, nChar, text, nText, xml등 문자열이 들어가는 거의 모든 열에 지정이 가능합니다. 또한 일반 인덱스와 다르게 전체 텍스트 인덱스는 테이블당 하나만 생성이 가능합니다.

 

혹시 인덱스 생성시 Key Index로 어떤걸 지정해야 할지 모르는 경우라면

 

Select [name]
From sysindexes
Where OBJECT_ID('Sales.OrderLines') = id;

 

처럼 특정 테이블의 개체를 확인하고 PK로 시작하는 이름을 확인하시기 바랍니다.

 

그리고 해당 인덱스는 On구문을 통하여 이전에 만들어둔 전체 텍스트 카탈로그인 SalesOrderLinesCatalog에 생성되도록 합니다. 다만 SalesOrderLinesCatalog는 현재 기본 카탈로그이므로 생략이 가능합니다.

 

그리고 마지막으로 만약 실제 테이블의 값(Sales.OrderLines의 Description열)이 변경되는 경우 이를 인덱스에도 자동으로 반영되도록 Change_Tracing 옵션을 Auto로 지정하였습니다. 성능이 문제가 되는 경우라면 Auto대신 Munual을 지정해 변경데이터를 수동으로 적용시켜 주는 방법도 있으며 Off를 통해 아예 변경내용을 업데이트하지 않도록 처리할 수도 있습니다.

 

인덱스를 생성 후 sys.fulltext_indexes 테이블을 통해 인덱스의 상황을 확인할 수 있습니다.

 

Select *
From sys.fulltext_indexes;

 

만약 결과에서 is_enabled가 1이라면 현재 해당 인덱스는 활성화중이라는 것을 의미합니다.

 

인덱스를 생성하고 나면 어떤 단어들이 인덱스로 생성되었는가를 확인할 수 있습니다.

 

Select *
From sys.dm_fts_index_keywords(DB_ID(), OBJECT_ID('Sales.OrderLines'));

 

위 결과에서 display_term은 생성된 단어들 그리고 column_id는 해당 단어가 들어간 열의 순서값(2라면 2번째열), 마지막으로 document_count는 해당 단어가 몇번이나 들어가 있는지를 나타냅니다.

 

단어를 나열해 보면 가령 필요없는 단어들까지 인덱스로 만들어놓은걸 확인할 수 있습니다. 예들 들어 'ta' 이라는 단어까지 인덱스로 만들필요가 없다면 이들 단어는 중지목록에 포함시켜 인덱스에서 제외할 수 있습니다.

 

특정 단어를 중지목록에 넣으려면 우선 중지목록자체를 생성해야 합니다. SSMS(SQL Server Management Studio)에서 Object Explorer창을 통해 원하는 DB의 Storage->Full Text Stoplists 항목을 찾아가 마우스 오른쪽 버튼을 눌러 'New Full-Text Stoplist'메뉴를 선택합니다.

 

 

Full-text stoplist name에 적절한 목록이름을 입력하고 아래 옵션중 원하는 방식으로 목록옵션을 선택합니다. 옵션중에서 Create an empty stoplist는 말 그대로 빈목록을 만들고 거기에서 시작하겠다는 의미이며 Create from the system stoplist는 이미 시스템상에 만들어져 있는 목록에서 추가하는 형태로 새로운 목록을 만들겠다는 것을 의미합니다. 마지막으로 Create from an existing full-text stoplist는 다른 곳에서 만들어진 목록을 가져와 추가하는 형태로 목록을 만드는 경우 선택하는 옵션인데 이들은 인덱스를 생성하고자 하는 목적에 따라 적절이 선택하시면 됩니다. 예제에서는 이름을 mystoplist하고 Create an empty stoplist를 선택하도록 하겠습니다.

 

 

중지목록을 만들고 나면 위에서 처럼 목록이 생성되는 것을 확인할 수 있습니다. 아이콘 모양을 언뜻보면 뭔가 중지된 느낌이 들지만 중지목록이라는 의미로 모양자체가 원래 그런것이니 신경쓰지 않아도 됩니다.

 

이 작업은 아래 쿼리를 통해서도 동일하게 처리할 수 있습니다.

 

Create FullText Stoplist mystoplist;

 

혹은 시스템 목록에서 만들고자 하는 경우

 

Create FullText Stoplist mystoplist From System StopList;

 

이제 목록에 인덱스에서 예외로 하고자 하는 단어를 추가해 봅시다. 목록에서 마우스 오른쪽 버튼을 눌러 Properties(속성)을 선택합니다.

 

 

위 화면에서 예외로 하고자 하는 단어를 입력하고 OK(확인)버튼을 누르면 해당 단어가 추가됩니다. 이 작업은 아래 쿼리를 통해서도 동일하게 처리할 수 있습니다.

 

Alter FullText StopList mystoplist Add 'ta' Language 'English';

 

이제 기존의 만들어둔 인덱스를 삭제하고

 

Drop FullText Index On Sales.OrderLines;

 

새롭게 인덱스를 구성해 보겠습니다. 쿼리는 이전과 완전히 동일하지만 중지목록에 있는 단어들은 제외하도록 지정해야 합니다.

 

Create FullText Index On Sales.OrderLines([Description])
Key Index PK_Sales_OrderLines
On SalesOrderLinesCatalog
With Change_Tracking Auto, StopList = mystoplist;

 

위에서 처럼 인덱스를 생성하고 나면 중지목록에 있는 단어는 더이상 포함되어 있지 않음을 확인할 수 있습니다.

 

인덱스를 생성하였으면 이제 테이블에서 전체텍스트 검색을 통해 데이터를 찾아보로고 하겠습니다.

 

Select *
From Sales.OrderLines
Where CONTAINS([Description], 'to');

 

Description 컬럼에 to라는 단어가 들어간 모든 데이터를 찾도록 합니다.

 

실행계획에서 FullTextMatch를 확인해 볼 수 있습니다.

 

Select *
From Sales.OrderLines
Where Contains([Description], 'to*');

 

to로 시작하는 단어를 찾습니다.

 

Select *
From Sales.OrderLines
Where CONTAINS([Description], 'to And become');

 

to 와 become 두개가 동시에 들어간 데이터를 찾습니다.

 

Select *
From Sales.OrderLines
Where Contains([Description], 'Near((to, become), 2)');

 

to와 become라는 단어가 서로 근접한것을 찾습니다. 이때 이 두단어 사이에 최대 2개까지의 단어만 포함하도록 합니다.

 

Select *
From Sales.OrderLines
Where Contains([Description], 'IsAbout (to weight (1.0), become weight (0.5))');

 

가중치를 부여한 검색을 수행합니다. 0~1까지 지정이 가능하며 숫자가 높을수록 가중치를 많이 부여합니다.

 

Select *
From Sales.OrderLines
Where CONTAINS([Description], 'to Or box');

 

to 또는 box단어가 존재하는 데이터를 찾습니다.

 

Select *
From Sales.OrderLines
Where FreeText([Description], 'press F1 to');

 

FreeText는 빗스한 내용을 찾아주는 역활을 수행합니다. 예제에서 press F1 to로 지정했는데 이와 완전히 동일하지 않더라도 비슷한 내용이 있으면 그 결과를 반환할 것입니다.

 

Select *
From ContainsTable(Sales.OrderLines, [Description], 'to');

 

ContainsTable은 지정한 열을 포함하는 키값별로 지정한 단어를 가장 많이 그리고 정확하게 포함하고 있는 가중치값을 표현합니다. 비슷한 기능으로 FreeTextTable이 있으며 개념은 FreeText와 같습니다.

'Programming > Microsoft SQL Server' 카테고리의 다른 글

[SQL] 전체 텍스트 검색  (0) 2018.09.27
트랜잭션 (Transaction)  (0) 2018.09.12
분산 트랜잭션 설정  (0) 2018.07.11
[SQL] 기본언어확인및 변경  (0) 2018.01.30
[SQL] 스키마(Schema)  (0) 2018.01.23
[SQL] 다른 이름으로 DB복원  (0) 2017.09.05
0 0
Programming/.NET

UDP는 간단하게 서버와 클라이언트가 데이터를 송수신할 수 있지만 일단 데이터를 보내면 상대측이 잘 받았는지 확인할 도리가 없고 심지어 여러건의 데이터를 나누어 보내면 순서대로 받을 수 있을지 조차 장담할 수 없습니다. 따라서 신뢰성이 너무 약해 잘 쓰지 않는 통신방식입니다.

 

지금은 간단하게 C#으로 UDP통신이 가능한 서버와 클라이언트 프로그램을 구현해 보고자 합니다. 통신 방식은 클라이언트가 서버에 특정 데이터를 보내면 서버는 이 데이터를 받아 앞에 'server : ' 라는 문자열을 추가한뒤 다시 클라이언트에 보내는 방식입니다.

 

static void Main(string[] args)
{
    Thread t = new Thread(myMethod);
    t.IsBackground = true;

t.Start();

Console.Read();

}

 

static void myMethod(object o)
{
    using (Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp)) {
        IPEndPoint iEp = new IPEndPoint(IPAddress.Any, 2020);
        socket.Bind(iEp);

        byte[] bRec = new byte[1024];
        EndPoint ep = new IPEndPoint(IPAddress.None, 0);

        while (true) {
            int i = socket.ReceiveFrom(bRec, ref ep);
            string s = Encoding.UTF8.GetString(bRec, 0, i);

            byte[] bSnd = Encoding.UTF8.GetBytes("server : " + s);
            socket.SendTo(bSnd, ep);
        }
    }
}

 

우선 서버측부터 구현해 보겠습니다. 제일 먼저 필요한건 소켓입니다. Socket클래스를 통해 socket을 생성하는데 ProtocolType을 Udp로 지정해 UDP로 통신할 것임을 알려줍니다.

 

서버는 경우에 따라 다수의 IP를 가질 수 있습니다. 이 가운데 지금 통신을 위해 어떤 IP와 포트로 수신대기를 해야하는지를 정해야 하며 그 접점을 IPEndPoint클래스로 설정합니다. IPAddress.Any는 현재 시스템에 마련된 모든 IP를 대상으로 하겠다는 것을 의미하며 2020은 포트번호에 해당합니다. 만약 특정 IP를 명시적으로 지정하고자 한다면 다음과 같이 코드를 수정할 수 있습니다.

 

IPAddress ip = IPAddress.Parse("192.168.20.126");
IPEndPoint iEp = new IPEndPoint(ip, 2020);

 

접점이 마련되었으면 socket의 Bind 메서드를 통해 해당 접점정보를 전달하여 설정된 네트워크정보로 데이터를 수신대기하도록 합니다.

 

bRec는 클라이언트로 부터 데이터가 수신되면 받을 버퍼이며 ep는 클라이언트측 접점정보를 가진 개체입니다. 그런데 어떤 클라이언트로 부터 데이터가 수신될지 모르니 IPEndPoint로 인스턴스를 생성할때는 IP를 지정하지 않고 또한 포트도 0으로 설정할 수 밖에 없습니다. 특정 내용으로 설정한다 하더라도 이개체는 socket의 ReceiveFrom메서드에서 데이터가 수신될때 데이터를 보낸 접점정보를 담아 반환하게될 것이므로 초기설정정보는 그렇게 의미가 없습니다.

 

ReceiveFrom메서드에서 데이터가 수신되면 bRec버퍼에 수신된 내용을 담은 후 UTF8인코딩을 통해 문자열로 변환한 후 앞단에 'sever : '라는 내용을 붙여 SendTo메서드를 통해 받은 데이터를 다시 보냅니다. 이때 어느 클라이언트로 보낼지는 ep개체를 통해 알 수 있습니다. ep는 초기화 시에는 의미없는 내용이었지만 ReceiveForm에서 정보가 담겨지게 되므로 클라이언트의 접점정보를 알 수 있게 됩니다.

 

또한 SendTo로 데이터를 보낼때는 보낼 수 있는 데이터의 크기도 생각을 해봐야 합니다. 이론상으로는 최대 65535byte인데 이는 네트워크통신에서 패킷에 붙는 별도의 정보로 인해 크기가 줄어들 수 있을 뿐만 아니라 각종 네트워크장비에서 통과할 수 있는 데이터의 양을 제한하는 경우도 있으므로 주의해야 합니다.

 

static void Main(string[] args)
{
    Thread t = new Thread(myMethod);
    t.IsBackground = true;


    t.Start();

    Console.Read();
}

static void myMethod(object o)
{
    Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);

    IPAddress ip = IPAddress.Parse("192.168.20.126");
    EndPoint sEP = new IPEndPoint(ip, 2020);

    EndPoint cEP = new IPEndPoint(IPAddress.None, 0);

    byte[] send = Encoding.UTF8.GetBytes("hi server!!");
    socket.SendTo(send, sEP);

    byte[] rec = new byte[1024];
    int i = socket.ReceiveFrom(rec, ref cEP);
    string s = Encoding.UTF8.GetString(rec, 0, i);

    Console.WriteLine(s);

    socket.Close();
}

 

위 예제는 클라이언트측 예제로서 소켓생성방법은 서버와 같습니다.

 

클라이언트입장에서 데이터를 보낼때 어느 서버로 보낼지를 알아야 하므로 그에 대한 설정을 진행합니다. 서버의 IP는 192.168.20.126(여러분의 환경에 따라 IP는 다를 것입니다!)이며 포트 2020으로 접속할 것입니다. 또한 서버에 데이터를 보낸 후 다시 서버에서 메세지를 수신받아야 하는데 이때 필요한 접점정보도 필요합니다. 다만 이미 서버정보에 대해서는 알고 있으나 어차피 ReceiveFrom에서 데이터를 보낸 대상의 접점정보가 파악될 것이므로 굳이 IP와 포트정보를 따로 지정하지 않습니다.

 

데이터를 보낼때는 보낼 내용을 UTF8로 인코딩하여 바이트배열에 담아 SendTo메서드에 전달해 데이터를 송신합니다. 이때 서버측 접점정보도 매개변수를 통해 같이 전달합니다.

 

서버에 데이터를 송신하면 그 즉시 서버에서 특정 메세지가 회신될 것이므로 그 회신데이터를 담을 버퍼를 마련해 두고 ReceiveFrom 메서드에 전달합니다. 그리고 버퍼에 담긴 내용을 다시 UTF8로 디코딩하여 사용자에게 보내줍니다.

 

UDP통신은 앞서 말한대로 데이터를 송수신할때의 신뢰성이 떨어지지만 데이터를 보내면 데이터를 받는 쪽에서는 보낸만큼 한꺼번에 데이터를 수신받을 수 있는등 통신방식은 대단히 간결하다는 장점이 있습니다.

'Programming > .NET' 카테고리의 다른 글

[C#] 네트워크 - TCP  (0) 2018.10.17
[ASP.NET MVC] 영역(Area)  (0) 2018.10.10
[C#] 네트워크 - UDP  (0) 2018.09.20
[C#] async / await  (0) 2018.09.04
[C#] Path  (0) 2018.08.23
[ASP.NET MVC] 뷰 (View)  (0) 2018.08.14
udp
0 0
Programming/Microsoft SQL Server

MS SQL Server에서 데이터베이스(DB)를 생성하면 실제 데이터베이스파일이 만들어 지는데 일부로 변경한 경우가 아니라면 아래와 같은 위치에 파일이 생성될것입니다.

 

C:\Program Files\Microsoft SQL Server\MSSQL12.MSSQLSERVER\MSSQL\DATA

 

실제 위와 같은 위치로 들어가면 확장자가 mdf인 파일과 ldf인 파일을 확인해 볼 수 있는데, 예를 들어 abc라는 데이터베이스를 생성했다면 abc.mdf와 abc_log.ldf파일이 존재함을 알 수 있습니다. 여기서 mdf는 실제 데이터가 들어가 있는 파일이며 ldf가 트랜잭션로그파일입니다. 이들 파일은 일반적으로는 한개만 존재할 경우가 많지만 필요에 따라 여러개로 나뉘어질 수 있는데 만약 데이터파일이 나뉘어진거라면 *.ndf형식으로 데이터파일이 생성되어 있을 것입니다.

 

이번에 주목해야할 것은 ldf즉 트랜잭션로그파일입니다. 이 파일은 Update나 Insert, Delete등의 데이터 변경작업을 기록하는 파일입니다. 만약 사용자가 Select와 같은 데이터조회 쿼리를 서버에 전달하면 서버는 mdf에서 원하는 데이터를 찾아 사용자에게 돌려주게 됩니다. 당연히 이 과정에서는 따로 트랜잭션로그가 남지 않습니다.(참고로 로그를 남기는 행위는 어떻게든 성능면에서는 불이익입니다. 트랜잭션로그도 마찬가지인데 로그를 남기지 않도록 하면 데이터의 무결성을 보증하기는 힘들어지지만 성능은 훨씬 좋아질것입니다.)

 

아래 쿼리는 데이터 변경 쿼리로 트랜잭션로그를 남기게 될 것인데 여기서 로그는 변경구문 그 자체를 기록하는것을 의미합니다.

 

Update [Person].[Person]
Set Title = NULL,
    FirstName = 'Ovidiu',
    MiddleName = 'V',
    LastName = 'Cracium'
Where BusinessEntityID = 11;

Update [Person].[Person]
Set Title = 'Ms.',
    FirstName = 'Janice',
    MiddleName = 'M',
    LastName = 'Galvin'
Where BusinessEntityID = 13;

 

그런데 위 구문은 Update가 2개이므로 2번의 트랜잭션을 발생시키게 됩니다. MS SQL은 기본적으로 자동 커밋이기에 사용자가 따로 트랜잭션시작과 종료시점을 명시하지 않는다면 변경 구문 하나하나마다 별도의 트랜잭션을 자동적으로 추가하게 됩니다.

 

Begin Tran

Update [Person].[Person]
Set Title = NULL,
     FirstName = 'Ovidiu',
     MiddleName = 'V',
     LastName = 'Cracium'
Where BusinessEntityID = 11;
Commit Tran;

Begin Tran
Update [Person].[Person]
Set Title = 'Ms.',
     FirstName = 'Janice',
     MiddleName = 'M',
     LastName = 'Galvin'
Where BusinessEntityID = 13;
Commit Tran;

 

그래서 만약 여러개의 변경구문을 묶어 단 한번만 트랜잭션을 유발시키려면 Begin Tran와 Commit Tran사이에 필요한 내용을 모두 명시해야 합니다.

 

Begin Tran

Update [Person].[Person]
Set Title = NULL,
     FirstName = 'Ovidiu',
     MiddleName = 'V',
     LastName = 'Cracium'
Where BusinessEntityID = 11;

Update [Person].[Person]
Set Title = 'Ms.',
     FirstName = 'Janice',
     MiddleName = 'M',
     LastName = 'Galvin'
Where BusinessEntityID = 13;
Commit Tran;

 

여기서 Begin Tran과 Commit Tran에서의 Tran은 Transaction의 줄임표현이며 원한다면 Begin Transaction처럼 풀어써주는것도 가능합니다.

 

트랜잭션은 위에서 처럼 Begin Tran과 Commit Tran을 명시적으로 해줘야 하는 경우도 있고 오라클처럼 Begin Tran은 자동으로 붙여주되 Commit Tran이나 Rollback Tran은 명시적으로 붙여줘야 하는 방식을 취할 수도 있습니다. 이러한 방식을 암시적 트랜잭션이라고 하는데 MS-SQL에서 이 방식을 사용하려면 다음과 같이 설정을 부여해야 합니다.

 

Set Implicit_Transactions On;

 

다만 일반적으로 트랜잭션은 중복으로 실행될 수 있지만 암시적 트랜잭션은 Commit이나 Rollback을 만나기 전까지는 중복실행이 불가능합니다.

 

위에서 처럼 MS-SQL서버에 트랜잭션이 발생하게 되면 그러니까 Begin Tran이 시작되면 ldf에 Begin Tran구문을 기록하고 변경 쿼리의 내용대로 데이터를 임시로 변경하게 됩니다. 임시로 변경한다는 것은 실제 데이터를 변경하는 것이 아니라 변경이 필요한 행 데이터(페이지)를 메모리캐시에 올려놓고 올려놓은 데이터를 대상으로 변경하는 것을 의미합니다. 그리고 나서 변경 쿼리에 일련번호를 부여하여 ldf파일에 로그로 기록하게 됩니다. 이 과정을 Commit이나 Rollback이 나올때까지 변경쿼리마다 하나씩 반복합니다.

 

만약 이 과정중 Commit Tran을 만나게 되면 ldf에 로그를 기록하고 캐시에 기록된 내용대로 실제 mdf데이터에 해당 내용을 적용(Commit)하게 됩니다. 그리고 나서 ldf의 Commit Tran부분에 체크포인트를 설정합니다. 체크포인트는 지금까지의 내용이 모두 실제 mdf에 적용되었음을 표시하는 것인데 만약 CheckPoint 문을 임의로 실행하면 캐시의 내용을 실제 데이터에 적용하고 그 시점에 체크포인트를 설정할 수 있도록 합니다.

 

Begin Tran부터 변경쿼리를 통해 캐시내용을 바꾸는 과정중 어떠한 이유로 인해서 서버나 혹은 서비스가 종료되는 상황이 발생하면 메모리의 내용이 모두 사라지게 될 것입니다. 변경내용이 모두 메모리에 존재하고 있었는데 이 캐시데이터를 모두 잃어버리게 되었고 로그상 아직 Begin Tran중이었으니 처음 Begin Tran이나 혹은 이전 체크포인트까지의 처리를 모두 취소하여 데이터의 무결성을 유지하게 됩니다.

 

그런데 처리과정이 ldf에 Commit Tran을 기록하는 것까지 도달하였으나 캐시데이터가 아직 실제 데이터에 적용되기 전에 사고가 발생한 경우라면 Begin Tran시점(또는 이전 체크포인트 이후)부터 모든 쿼리에 대한 트랜잭션을 다시 재 수행하여 변경절차를 마무리하게 됩니다.

 

Commit Tran은 캐시데이터를 실제 데이터에 적용하지만 Rollback Tran은 변경이전으로 되돌리는 작업을 수행합니다. 그런데 트랜잭션이 여러개 중첩되어 사용되는 경우 Rollback Tran을 사용할때 오해할 수 있는 부분이 하나 있는데 Rollback Tran은 이전의 트랜잭션을 되돌리는 것이 아니라 지금까지의 모든 트랜잭션을 되돌린다는 것입니다.

 

Begin Tran
     Update [Person].[Person]
     Set Title = NULL,
      FirstName = 'Ovidiu1',
      MiddleName = 'V1',
      LastName = 'Cracium1'
     Where BusinessEntityID = 11;

Begin Tran
      Update [Person].[Person]

Set EmailPromotion = 1
Where BusinessEntityID = 11;

RollBack Tran;

 

Begin Tran 이후 다시 Begin Tran으로 트랜잭션을 두번 호출하고 마지막에 RollBack Tran으로 변경 데이터를 되돌립니다. 얼핏봐서는 이전의 Begin Tran만 되돌릴것 같지만 사실은 걸려있는 모든 트랜잭션을 취소합니다. 따라서 만약 특정 트랜잭션만을 명시해 취소하고자 한다면 특정 지점에서 트랜잭션을 Save하고 Save한 트랜잭션까지 취소하는 방법을 써야 합니다.

 

Begin Tran
     Update [Person].[Person]
     Set Title = NULL,
     FirstName = 'Ovidiu1',
     MiddleName = 'V1',
     LastName = 'Cracium1'
     Where BusinessEntityID = 11;
Save Tran my_tran;
Begin Tran
     Update [Person].[Person]
     Set EmailPromotion = 1
     Where BusinessEntityID = 11;
RollBack Tran my_tran;

 

중간에 Save Tran으로 트랜잭션 지점을 생성하고 RollBack Tran에서 복원지점을 명시하면 명시한 지점까지만 트랜잭션을 복원하게 됩니다. 따라서 위 쿼리의 경우 EmailPromotion을 Update하는 부분만 적용되고 그 위로는 트랜잭션이 취소되지 않습니다.

 

참고로 특정 테이블에 트랜잭션이 걸려져 있는 상태라면 해당 테이블에는 잠금(Lock)이 발생할 수 있습니다. 이 현상을 확인하려면 Begin Tran을 발생시키고 다른 세션(별도의 쿼리창)에서 해당 테이블의 Select를 시도해 보면 쉽게 확인 할 수 있습니다. 따라서 임의로 트랜잭션을 구현할때는 주의해야 하며 필요한 경우 아래 쿼리를 실행(Select가 필요한 세션안에서)하여 테이블잠김상태라도 데이터를 가져올 수 있도록 설정할 수 있습니다.

 

Alter Database [대상DB] Set Allow_Snapshot_Isolation On;
Set Transaction Isolation Level Snapshot;

 

특정 세션에서 트랜잭션을 발생시킨뒤 데이터 변경 쿼리를 실행하고 테이블을 조회해 보면 변경된 상태의 데이터가 출력됨을 확인할 수 있는데 이것은 실제 테이블의 데이터를 가져오는 것이 아니라 캐시상태의 데이터를 가져와 보여주고 있기 때문입니다.

 

Snapshot을 취소하려면 다음과 같이 수행합니다.

 

Alter Database AdventureWorks Set Allow_Snapshot_Isolation Off;

 

위에서 알아본대로 트랜잭션은 데이터의 무결성을 유지하기 위한 아주 좋은 장치입니다. 그런데 트랜잭션은 장애발생에 대한 조치용이지 모든걸(모든 장애를) 해결해 주는 것으로 오해해서는 안됩니다. 예를 들어 아래 쿼리를 보면

 

Begin Tran

Update [Person].[Person]
Set Title = NULL,
     FirstName = 'Ovidiu1',
     MiddleName = 'V1',
     LastName = 'Cracium1'
Where BusinessEntityID = 11;

Update [Person].[Person]
Set EmailPromotion = 'ABC'
Where BusinessEntityID = 11;
Commit Tran;

 

두번째 Update문에서 EmailPromotion은 Int형인데 여기에 'ABC'라는 문자열을 처리하도록 하고 있습니다. 트랜잭션에 대해 오해를 하고 있으면 여기에서 실패가 발생해 자동적으로 모든 Update문이 RollBack되리라 생각할 수 있겠지만 사실은 그렇지 않습니다. 이것은 엄밀히 말해 장애가 아닌 오류상황이기 때문입니다. 따라서 이런 경우에는 운영자가 명시적으로 Rollback이 되도록 처리를 해줘야 하며 이 때 가장 많이 쓰이는 방식은 Try Catch 구문을 활용하는 것입니다.

 

Begin Try

Begin Tran
Update [Person].[Person]
Set Title = NULL,
FirstName = 'Ovidiu1',
MiddleName = 'V1',
LastName = 'Cracium1'
Where BusinessEntityID = 11;

Update [Person].[Person]
Set EmailPromotion = 'ABC'
Where BusinessEntityID = 11;
Commit Tran;

End Try
Begin Catch
     Rollback Tran;
End Catch;

 

Begin Tran안에서 오류가 발생하면 Begin Catch에서 잡아 RollBack Tran을 실행할 것입니다.

'Programming > Microsoft SQL Server' 카테고리의 다른 글

[SQL] 전체 텍스트 검색  (0) 2018.09.27
트랜잭션 (Transaction)  (0) 2018.09.12
분산 트랜잭션 설정  (0) 2018.07.11
[SQL] 기본언어확인및 변경  (0) 2018.01.30
[SQL] 스키마(Schema)  (0) 2018.01.23
[SQL] 다른 이름으로 DB복원  (0) 2017.09.05
0 0
Programming/.NET

특정 메서드의 비동기 호출을 시도할때는 대부분 작업을 진행하는 메서드부분과 작업을 완료하고 호출되는 메서드를 스레드로 분리하면서 이루어집니다. 하지만 async / await 를 사용하면 비동기호출시 완료처리를 위해 추가했던 별도의 메서드를 분리할 필요가 없어집니다.

 

using (FileStream fs = new FileStream(@"C:\\test.txt"FileMode.OpenFileAccess.ReadFileShare.Read)) {
    byte[] b = new byte[fs.Length];
    fs.Read(b, 0, b.Length);

    string s = Encoding.UTF8.GetString(b);
}

 

이 예제는 FileStream을 통해 파일 내용을 읽어들이는 동기식 방법입니다. 이 예제를 async / await 가 없을때 비동기식으로 바꾸면 다음과 같이 할 수 있습니다.

 

static byte[] b;
static void Main(string[] args)
{
    FileStream fs = new FileStream("test.txt"FileMode.OpenFileAccess.ReadFileShare.Read);
    b = new byte[fs.Length];
    fs.BeginRead(b, 0, b.LengthReadComplete, fs);


    Console.WriteLine("현재 스레드 완료");
    Console.Read();
}

static void ReadComplete(IAsyncResult ar)
{
    FileStream fileStream = (FileStream)ar.AsyncState;
    fileStream.EndRead(ar);

    string s = Encoding.UTF8.GetString(b);

    Console.WriteLine(s);
}

 

예제에서 BeginRead 메서드를 보시면 작업이 완료되었을때 ReadComplete메서드를 호출하는 것을 확인할 수 있습니다. 이것은 다시 async / await 를 사용해 아래와 같이 바꿔볼 수 있습니다.

 

static byte[] b;
static void Main(string[] args)
{
    fileRead();

    Console.WriteLine("현재 스레드 완료");
    Console.Read();
}

static async void fileRead()
{
    FileStream fs = new FileStream("test.txt"FileMode.OpenFileAccess.ReadFileShare.Read);
    b = new byte[fs.Length];

    await fs.ReadAsync(b, 0, b.Length);

    string s = Encoding.UTF8.GetString(b);
    Console.WriteLine(s);
}

 

보시는 것처럼 완료후의 메서드로 따로 분리되지 않았습니다. 대신 ReadAsync메서드로 바뀌었는데 이 메서드는 특별히 await구문을 사용한 비동기 호출을 지원하기 위한 것이며 앞에 await 가 따로 지정되었습니다. 이렇게 하면 await가 사용된 구문 아래에 모든 처리는 해당 메서드처리가 모두 완료되고 나서야 처리가 진행됩니다.

 

fileRead메서드에는 async가 구문이 포함되어 있는데 이는 해당 메서드안에서 await가 사용될 것임을 알려주고 있습니다. async가 필요한 이유는 본래 await는 C# 5.0이후에 추가된 것으로 그 버전이전에 변수이름같이 await가 사용된 경우를 대비하기 위해서 입니다.

 

또한 C#에는 async / await 를 사용을 좀더 간단히 하기위해서 기존의 복잡한 비동기호출이 필요한 메서드 이외에 async / await 를 사용할 수 있는 메서드를 추가했습니다.

 

static void Main(string[] args)
{
    getWebData();

    Console.WriteLine("현재 스레드 완료");
    Console.Read();
}

static async void getWebData()
{
    HttpClient hc = new HttpClient();
    string s = await hc.GetStringAsync("http://www.naver.com/");

    Console.WriteLine(s);
}

 

위 예제는 HttpClient를 사용하여 특정 웹페이지의 데이터를 가져오는 구문인데 async / await 로 비동기호출을 시도했고 이에 대응하기 위해 await가 지원되는 GetStringAsync메서드를 호출하였습니다.

 

static void Main(string[] args)
{
    doWork();
    Console.WriteLine("현재 스레드 완료");
    Console.Read();
}

static async void doWork()
{
    Task<int> t1 = work5();
    Task<int> t2 = work10();

    await Task.WhenAll(t1, t2);

    Console.WriteLine("비동기 작업 완료");

    Console.WriteLine("결과 : {0}, {1}", t1.Result, t2.Result);
}

static Task<intwork5()
{
    return Task.Factory.StartNew(() => {
        Thread.Sleep(5000);
        Console.WriteLine("5초 작업 완료");
        return 3;
    });
}

static Task<intwork10()
{
    return Task.Factory.StartNew(() => {
        Thread.Sleep(10000);
        Console.WriteLine("10초 작업 완료");
        return 10;
    });
}

 

async / await 를 활용하면 위와 같이 특정 작업을 비동기로 병렬수행하는 것이 가능합니다. 이때 WhenAll은 Task타입을 반환하도록 되어 있어서 await 를 붙일 수 있는데 비슷한 처리를 하는 WaitAll 메서드는 작업이 완료될때까지 현재 스레드에서 대기해야 합니다.

'Programming > .NET' 카테고리의 다른 글

[ASP.NET MVC] 영역(Area)  (0) 2018.10.10
[C#] 네트워크 - UDP  (0) 2018.09.20
[C#] async / await  (0) 2018.09.04
[C#] Path  (0) 2018.08.23
[ASP.NET MVC] 뷰 (View)  (0) 2018.08.14
[C#] using static  (0) 2018.07.27
0 0
Programming/.NET

System.IO.Path는 파일경로와 관련해 몇몇 유용한 정적메서드를 제공합니다.

 

string s = Path.ChangeExtension("sample.txt", ".jpg");

 

ChangeExtension메서드는 주어진 파일의 확장자를 지정한 확장자로 변경한 결과를 반환합니다. 실제 파일명을 바꾸지는 않습니다.

 

string s = Path.Combine("C:\\aaa", "sample.txt");

 

Combine은 주어진 경로를 합쳐 하나로 만듭니다. 이때 인자는 param형태로 전달하므로 다수의 인자를 전달할 수 있습니다.

 

string s = Path.GetExtension("sample.txt");

 

해당 경로에 대한 파일명에서 확장자만을 .과 함께 추출합니다.

 

string s = Path.GetFileNameWithoutExtension("sample.txt");

 

반면 확장자를 제외한 파일명만 가져오려 한다면 GetFileNameWithoutExtension() 메서드를 사용합니다.

 

string s = Path.GetFileName("sample.txt");

 

주어진 경로에서 전체파일명만을 가져옵니다.

 

string s = Path.GetFullPath("sample.txt");

 

주어진 경로에서 파일명을 제외한 경로만을 가져옵니다.

 

string s = Path.GetRandomFileName();

 

GetRandomFileName()은 임의의 파일명을 반환합니다.

 

string s = Path.GetTempFileName();

 

임시폴더에 임의의 파일을 만들고 전체경로를 반환합니다.

 

string s = Path.GetTempPath();

 

임시폴더 경로를 반환합니다.

'Programming > .NET' 카테고리의 다른 글

[C#] 네트워크 - UDP  (0) 2018.09.20
[C#] async / await  (0) 2018.09.04
[C#] Path  (0) 2018.08.23
[ASP.NET MVC] 뷰 (View)  (0) 2018.08.14
[C#] using static  (0) 2018.07.27
[ASP.NET MVC] 헬퍼메서드(Helper Method)  (0) 2018.07.18
0 0
Programming/.NET

1. 레이아웃 섹션

 

섹션을 사용하면 뷰(View)내부의 특정 컨텐츠영역을 공유되는 레이아웃에 렌더할 수 있습니다.

 

@{
    ViewBag.Title = "Index";
}

<h2>Index</h2>

@section Top {
    <div>Top 영역</div>
}

<div>뷰 본문</div>

@section Bottom{
    <div>Bottom 영역</div>
}

 

레이아웃을 사용하는 뷰페이지(Index)의 예입니다. 섹션은 레이아웃 페이지에 표시할 내용으로서 HTML이나 Razor구문을 포함할 수 있습니다.

 

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>@ViewBag.Title</title>
</head>
<body>
    @RenderSection("Top")
    <div>
        @RenderBody()
    </div>
    @RenderSection("Bottom")
</body>
</html>

 

레이아웃 페이지에서는 RenderSection 메서드를 통해 출력할 영역을 지정합니다. 그러면 해당하는 섹션에서 뷰에 정의된 내용이 표현되고 나머지는 RenderBody 메서드로 출력됩니다.

 

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>@ViewBag.Title</title>
</head>
<body>
    @if (IsSectionDefined("Top"))
    {
        @RenderSection("Top")
    }
    else
    {
        <div>없음</div>
    }
    <div>
        @RenderBody()
    </div>
    @RenderSection("Bottom")
</body>
</html>

 

레이아웃에서 특정 세션의 정의여부는 IsSectionDefined 메서드로 확인할 수 있습니다.

 

@RenderSection("Top"false)

 

RenderSection 두번째 매개변수를 false로 전달하면 만약 해당하는 섹션이 뷰에서 정의되어 있지 않더라도 예외를 발생시키지 않도록 합니다.

 

2. 부분뷰

 

부분뷰는 여러 페이지에서 중복적으로 보여져야할 특정 컨텐츠의 내용을 담아놓은 뷰로서 다른 뷰에서 부분뷰를 포함시킴으로서 여러 뷰에서 동일한 컨텐츠를 표현하는 것이 가능합니다.

 

우선 뷰를 부분뷰로 생성하기 위해서는 뷰를 생성할때 Add View 화면에서 Create as a partial view 를 선택합니다.

 

 

부분뷰 파일이 생성되면 파일내용을 아래와 같이 작성합니다. 파일 내용은 html과 razor구문이 포함될 수 있습니다.

 

<div>
    이름 : @Html.TextBox("user_name") <br />
    아이디 : @Html.TextBox("user_id")
</div>

 

 

부분뷰를 위와 같이 만들어 두면 다른 뷰파일에서는 부분뷰의 내용이 필요한 부분에 @Html.Partial 메서드로 부분뷰를 포함시킬 수 있습니다.

 

@{
    ViewBag.Title = "Index";
}

<h2>Index</h2>

@section Top {
    <div>Top 영역</div>
}

<div>뷰 본문</div>
@Html.Partial("MyPartialView")

@section Bottom{
    <div>Bottom 영역</div>
}

 

부분뷰는 일반적인 뷰와 마찬가지로 강력한 형식의 뷰로 만들면 특정 모델개체를 뷰로 전달할 수 있습니다.

 

@model object

<div>
    이름 : @(((string[])Model)[0]) <br />

    아이디 : @(((string[])Model)[1])
</div>

 

예제의 부분뷰는 IEnumerable 형식을 모델을 받는 강력한 형식의 뷰로서 전달받은 모델을 통해 배열을 열거하고 있습니다.

 

@Html.Partial("MyPartialView"new[] { "홍길동""hong" })

 

강력한 형식의 부분뷰를 포함할때는 두번째 매개변수에 전달하고자 하는 모델개체를 지정하면 됩니다.

 

3. 자식 액션

 

프로그램의 비지니스 로직에서 동적으로 생성된 데이터를 다수의 뷰에서 포함해야 하는 경우에는 해당 로직을 포함한 프로그램부분을 자식 액션으로 만들고 뷰에서 이를 포함하는 형태로 처리할 수 있습니다. 예를 들어 현재 시간과 날짜를 포함하는 특정 내용을 여러 뷰에서 포함해 출력해야 하는 경우

 

public ActionResult NowTime()
{
    return PartialView((object)DateTime.Now.ToString("yyyy년 MM월 dd일 HH시 mm분입니다."));
}

 

우선 필요한 액션메서드를 만들고 해당 메서드에서 뷰를 호출하도록 합니다. 이때 PartialView로 부분뷰를 호출하도록 하고 있으므로

 

@model string

<span> 안녕하세요 현재시간 @Model 입니다.</span>

 

뷰를 부분뷰로 만들어야 합니다. 예제에서는 강력한 형식의 부분뷰를 생성하였습니다.

 

@Html.Action("NowTime")

 

일반뷰에서는 위와 같이 Action 메서드로 자식액션을 호출할 수 있습니다. 그러면 자식액션은 부분뷰를 불러와 Action 메서드가 호출된 부분에 포함시킬것입니다.

 

@Html.Action("NowTime""MyController")

 

Action 메서드는 기본적으로 현재의 컨트롤러에서 액션메서드를 찾게 되는데 예제에서처럼 두번째 매개변수로 컨트롤러명을 지정하면 해당 컨트롤러에서 액션메서드를 찾게 됩니다.

 

그런데 NowTime 액션메서드는 /Home/NowTime 과 같은 URL요청에도 응답할 수 있습니다. 만약 이 액션메서드를 반드시 자식액션메서드의 요청에만 응답하도록 하려면

 

[ChildActionOnly]
public ActionResult NowTime()
{
    return PartialView((object)DateTime.Now.ToString("yyyy년 MM월 dd일 HH시 mm분입니다."));
}

 

위에서 처럼 ChildActionOnly 속성을 추가해 줘야 합니다.

'Programming > .NET' 카테고리의 다른 글

[C#] async / await  (0) 2018.09.04
[C#] Path  (0) 2018.08.23
[ASP.NET MVC] 뷰 (View)  (0) 2018.08.14
[C#] using static  (0) 2018.07.27
[ASP.NET MVC] 헬퍼메서드(Helper Method)  (0) 2018.07.18
[C#] extern  (0) 2018.07.03
0 0
Programming/.NET

static 형식의 메서드를 호출하는 경우 기존에는 호출자의 타입을 같이 동반해야 했습니다.

 

Console.WriteLine("안녕하세요.");

 

예를 들어 WriteLine의 경우 Console타입을 같이 써야하는데 using static으로 타입명을 지정하면 static 메서드만 바로 호출할 수 있습니다.

 

using static System.Console;

 

Console.WriteLine("안녕하세요.");
WriteLine("안녕하세요.");

 

이러한 방식은 enum과

 

using static test.Program.Machine;

public enum Machine
{
    On,
    Off
}

 

static void Main(string[] args)
{
    Machine m = On;

    Console.Read();
}

 

const 에도 그대로 적용될 수 있습니다.

 

public class myClass
{
    public const int i = 100;
    public const int j = 200;
}

 

using static myClass;

static void Main(string[] args)
{
    int num = i;

    Console.Read();
}

'Programming > .NET' 카테고리의 다른 글

[C#] Path  (0) 2018.08.23
[ASP.NET MVC] 뷰 (View)  (0) 2018.08.14
[C#] using static  (0) 2018.07.27
[ASP.NET MVC] 헬퍼메서드(Helper Method)  (0) 2018.07.18
[C#] extern  (0) 2018.07.03
[C#] BinaryFormatter / XmlSerializer / DataContractJsonSerializer  (0) 2018.06.26
0 0
Programming/.NET

1. 인라인 헬퍼 메서드

 

만들고자 하는 헬퍼 메서드를 뷰내부에 특정 이름으로 정의하고 정의된 메서드를 같은 뷰안에서 호출하는 방식으로 사용되는 메서드입니다.

 

@{
    ViewBag.Title = "Index";
}

@helper Sum(int iint j) {
    <span>결과 => @(i  + j)</span>
}

<h2>Index</h2>

계산 : @Sum(10, 20)

 

인라인 헬퍼메서드는 @Helper 를 사용해 만들어지며 일반 헬퍼메서드와 동일한 방법으로 호출하고 사용할 수 있습니다. 다만 @Helper 로 만들어진 메서드는 반환값을 가질 수 없고 내부에서 컨텐츠표현으로 처리되어야 합니다.

 

2. 외부 헬퍼 메서드

 

헬퍼 메서드를 직접 만든다는 면에서 인라인 헬퍼 메서드와 개념은 동일하지만 메서드자체를 별도의 클래스등에 풀어놓는 방식입니다.

 

public static class MyHelper
{
    public static int Sum(this HtmlHelper htmlint iint j)
    {
        return i + j;
    }
}

 

외부 헬퍼메서드는 보시는것처럼 확장메서드로 구현되며 HtmlHelper 개체를 매개변수로 지정해야 합니다.

 

@using WebApplication1.Test

@{
    ViewBag.Title = "Index";
}

<h2>Index</h2>

계산 : @Html.Sum(10, 20)

 

외부 헬퍼메서드를 사용하려면 @Html. 으로 해당 헬퍼메서드를 호출하면 됩니다.

 

3. 내장 헬퍼 메서드

 

(1) Form

 

@using (Html.BeginForm()) {
    
}

 

Form은 BeginForm 메서드를 통해 구현되며 이것을 using으로 감싸면 처음에는 <form>으로 끝에는 </form>으로 닫히는 Form을 생성합니다.

 

BeginForm 메서드는 아무런 매개변수 없이 단독으로 호출되면 기본적으로 입력된 데이터를 같은 액션메서드의 POST로 전달합니다.

 

public ActionResult Index()
{
    return View(new Member());
}

[HttpPost]
public ActionResult Index(Member m)
{
    return View(m);
}

 

만약 다른 곳으로 POST가 전달되어야 한다면 전달되길 원하는 컨트롤러와 액션메서드를 다음과 같이 지정해야 합니다.

 

@using (Html.BeginForm("MyMember""MyController")) {
    
}

 

또는 특정한 라우트설정을 따라가는 Form을 생성해야 한다면 BeginForm 대신 BeginRouteForm 메서드를 사용할 수도 있습니다.

 

@using (Html.BeginRouteForm("Default"new { id = "newMember" })) {
    
}

 

예제에서는 RouteConfig.cs 파일에 등록된 내용중 이름이 Default 인 라우트설정을 참조하도록 하였으며 동시에 id 세그먼트값을 지정하고 있습니다.

 

(2) HTML요소

 

public class Member
{
    public string ID { getset}
    public string Name { getset}
    public string Email { getset}
    public Role Role { getset}
    public List<SelectListItemFamily {
            get {
                return new List<SelectListItem>  {
                    new SelectListItem { Text = "부",  Value = "F"},

                    new SelectListItem { Text = "모",  Value = "M"},
                    new SelectListItem { Text = "형",  Value = "B"}
                };
            }
        }

}

 

public enum Role
{
    Admin,
    Member,
    Guest
}

 

@using (Html.BeginForm()) {
    <label>아이디</label> @Html.TextBox("ID"Model.ID)<br />
    <label>이름</label> @Html.TextBox("Name"Model.Name)<br />
    <label>이메일</label> @Html.TextBox("Email"Model.Email)<br />
    <input type="submit" value="제출" />
}

 

HTML 요소중 텍스트박스는 TextBox 메서드를 호출함으로서 사용할 수 있습니다. 메서드를 호출할때 첫번째는 id와 name속성을 설정하는 값이며 두번째는 해당 입력요소 자체의 값을 의미합니다. 이밖에도 CheckBox, Hidden, RadioButton, Password, TextArea 등의 메서드를 통해 원하는 Input요소를 생성할 수 있습니다.

 

@using (Html.BeginForm()) {
    <label>아이디</label> @Html.TextBox("ID")<br />
    <label>이름</label> @Html.TextBox("Name")<br />
    <label>이메일</label> @Html.TextBox("Email")<br />
    <input type="submit" value="제출" />
}

 

HTML 요소를 생성할때 첫번째 매개변수를 통해 특정 값을 전달하면 ViewBag이나 Model에서 일치하는 항목이 있는지를 확인하고 자동으로 해당하는 value값을 설정하게 됩니다.

 

@using (Html.BeginForm()) {
    <label>아이디</label> @Html.TextBoxFor(x => x.ID)<br />
    <label>이름</label> @Html.TextBoxFor(x => x.Name)<br />
    <label>이메일</label> @Html.TextBoxFor(x => x.Email)<br />
    <input type="submit" value="제출" />
}

 

HTML 요소가 특정 Model에 종속되는 경우라면 ~For 메서드를 사용하여 강력한 형식의 요소를 생성할 수도 있습니다. 이렇게 하면 id나 name속성값등을 잘못입력했을경우에 대한 문제를 최소한으로 줄일 수 있습니다.

 

@using (Html.BeginForm()) {
    <label>역활</label>@Html.DropDownList("Role"new SelectList(new[] { "Admin""Member""Guest" }))
    <input type="submit" value="제출" />
}

 

물론 ~For 메서드를 통해 좀더 안전한 강력한 형식의 요소를 생성할 수도 있습니다.

 

@using (Html.BeginForm()) {
    <label>역활</label>@Html.DropDownListFor(x => Model.Rolenew SelectList(Enum.GetNames(typeof(WebApplication1.Infrastructure.Role))))
    <input type="submit" value="제출" />
}

 

참고로 DropDownList나 ListBox는 IEnumerable 형식을 열거하므로 예제에서의 Family 또한 Select 요소로서 적용할 수 있습니다.

 

@using (Html.BeginForm()) {
    <label>역활</label>@Html.ListBoxFor(x => x.FamilyModel.Family)
    <input type="submit" value="제출" />
}

 

ASP.NET MVC 에서는 HTML 요소 생성시 TextBox나 ListBox처럼 요소생성에 필요한 메서드를 직접 호출하는 것 이외에도 Model의 데이터기반하에 자동적으로 요소가 생성될 수 있도록 하는 방법이 제공되고 있습니다.

 

public class Member
{
    public string ID { getset}
    public string Name { getset}
    public string Email { getset}
    public DateTime BirthDay { getset}
    public bool IsDrawal { getset}
}

 

Model을 위와 같이 정의하고

 

@using (Html.BeginForm()) {
    <label>역활</label> @:&#58; @Html.EditorFor(x => x.ID)<br />
    <label>이름</label>  @:&#58; @Html.EditorFor(x => x.Name)<br />
    <label>이메일</label> @:&#58; @Html.Editor("Email")<br />
    <label>생년월일</label> @:&#58; @Html.EditorFor(x => x.BirthDay)<br />
    <label>탈퇴여부</label> @:&#58; @Html.Editor("IsDrawal")<br />
    <input type="submit" value="제출" />
}

 

Editor나 EditorFor메서드를 통해 요소생성을 시도하면 해당 데이터타입에 맞는 요소를 자동적으로 출력할 것입니다. 모델기반 요소생성은 Editor 이외에 Display(읽기전용-태그없이 값만 표시함), Label(라벨태그) 등이 있으며 각 메서드 이름에 For가 붙는 메서드는 강력한 형식으로 요소를 생성하는 메서드입니다.

 

@using (Html.BeginForm()) {
    @Html.EditorForModel()
    <input type="submit" value="제출" />
}

 

위 예제는 HTML 요소에 필요한 모든 요소에 대해 일일이 해당 메서드를 호출하지 않고서도 일괄 생성하는 방법을 보여주고 있습니다. EditorForModel은 Model을 기반으로 편집상태의 요소를 생성하며 LabelForModel은 Label 태그로, DisplayForModel은 속성값으로만 Model을 표현하도록 합니다.

 

~ForModel 메서드를 통해 HTML요소를 생성하는 방식은 순전히 Model의 데이터타입을 통해 적절하다고 판단되는 HTML요소를 추정하여 생성할 뿐입니다. 가장 간단하게 요소를 생성하지만 일부는 의도하지 않은 형태의 요소를 생성할 수도 있는데 이러한 문제는 Model에 직접적인 세부사항을 지정함으로서 해결될 수 있습니다.

 

public class Member
{
    [HiddenInput]
    public string ID { getset}
    public string Name { getset}
    public string Email { getset}
    public DateTime BirthDay { getset}
    public bool IsDrawal { getset}
}

 

HiddenInput은 편집이 불가능하도록 해당 필드의 값만을 표시합니다.

 

[HiddenInput(DisplayValue =false)]

 

하지만 HiddenInput 에서 DisplayValue 속성을 False로 지정하면 아예 값조차도 표시하지 않도록 할 수 있습니다.

 

[ScaffoldColumn(false)]

 

ScaffoldColumn(false)는 [HiddenInput(DisplayValue =false)] 보다 더 강력한 형식으로 아예 해당필드에 대한 요소표현을 생략하도록 합니다. 눈에 보이지 않는 것에서 끝내는 것이 아니라 어떠한 형태의 요소로도 해당 필드의 데이터를 표시하지 않습니다.

 

[Display(Name ="이름")]
public string Name { getset}

 

특정 필드에 대해 Label 태그로 제목(이름)을 표현할때는 필드명이 기본적으로 적용됩니다. 하지만 이를 바꾸고자 한다면 Display의 Name속성을 통해 변경하고자 하는 명칭을 지정할 수 있습니다.

 

[DataType(DataType.EmailAddress)]
public string Email { getset}

 

DataType은 특정 필드에 대해 데이터의 포멧형식을 지정합니다.

 

[UIHint("Date")]
public DateTime BirthDay { getset}

 

UIHint는 HTML요소 생성시 HTML에 해당 데이터타입에 맞는 속성을 추가하도록 합니다. 이렇게 되면 예를 들어 날짜타입인 경우 날짜입력에 편리한 달력등을 표시할 수도 있을 것입니다. (오페라와 같은 브라우저는 이 설정의 적용을 바로 확인할 수 있으나 다른 브라우저의 경우 현재까지 HTML5의 기능이 완벽히 구현되지 않은 상태이므로 변화가 아예 없거나 제한적인 부분만을 표시할 수 있습니다.)

 

위 예제에서 Member 클래스는 임의로 제작한 Model 클래스에 해당합니다. 이런 모델클래스의 경우에는 별 문제가 없으나 만약 특정 모델클래스가 EntityFramework 등을 이용해 자동으로 생성된 모델 클래스라면 속성값을 지정하기 어려울 수 있습니다. 대부분 자동생성되는 모델 클래스의 경우 클래스에 어떠한 변경사항이 발생하면 해당 클래스의 변경사항이 감지되어 자동으로 클래스를 재 작성하거나 오류를 발생시키기 때문입니다. 때문에 지정한 속성값들이 제거되거나 사용이 불가능해 질 수 있습니다.

 

public partial class Member
{
    public string ID { getset}
    public string Name { getset}
    public string Email { getset}
    public DateTime BirthDay { getset}
    public bool IsDrawal { getset}
}

public partial class MemberMetaData
{
    public string ID { getset}
    public string Name { getset}
    public string Email { getset}
    public DateTime BirthDay { getset}
    public bool IsDrawal { getset}
}

 

이러한 문제를 해결하기 위해서는 원본 클래스를 partial 클래스로 만들고 각 필드가 동일한 별도의 클래스를 partial로 생성한뒤 해당 클래스에 필요한 속성값을 부여해야 합니다.

 

[MetadataType(typeof(MemberMetaData))]
public partial class Member
{
    public string ID { getset}
    public string Name { getset}
    public string Email { getset}
    public DateTime BirthDay { getset}
    public bool IsDrawal { getset}
}

public partial class MemberMetaData
{
    public string ID { getset}
    public string Name { getset}
    public string Email { getset}

    [DataType(DataType.Date)]
    public DateTime BirthDay { getset}
    public bool IsDrawal { getset}
}

 

원본 모델클래스에는 MetaDataType을 사용하여 필요한 타입이 있는 클래스를 지정함으로서 이전처럼 모델클래스에 직접적으로 속성을 설정한 것과 동일한 효과를 얻을 수 있습니다.

 

public partial class Member
{
    public string ID { getset}
    public string Name { getset}
    public string Email { getset}
    public DateTime BirthDay { getset}
    public bool IsDrawal { getset}
    public MemberType MT { getset}
}

public enum MemberType
{
    Admin,
    Member,
    Guest
}

 

위와 같은 구조의 모델클래스의 경우 MemberType인 경우에는 특별히 DropDownList로 표현하고 싶지만 기존의 템블릿만으로는 이를 명시할 방법이 없습니다. 이런 상황에서는 대략 2가지 정도의 해결방법이 존재하는데 우선 첫번째 방법으로 아래와 같이 EditorForModel에서 요소생성이 제외되도록 하고 해당 필드에 대해 별도의 요소생성을 지시하는 것입니다.

 

[HiddenInput(DisplayValue =false)]
public MemberType MT { getset}

 

@using (Html.BeginForm()) {
    @Html.EditorForModel()<br />
    @Html.DropDownListFor(x => x.MTnew SelectList(Enum.GetNames(typeof(WebApplication1.Infrastructure.MemberType))))
    <input type="submit" value="제출" />
}

 

두번째는 아예 MemberType인 경우 무조건 DropDownList 형태로 표현되도록 템블릿을 직접 지정하는 방법입니다. 이 방법을 위해서는 우선 /Views/Shared 폴더 하위에 EditorTemplates 라는 폴더를 새로 추가하고 해당 폴더안에 MemberType.cshtml 파일을 생성해야 합니다. 주의할점은 EditorTemplates이라는 폴더 이름은 약속된 이름이므로 다른 이름으로 폴더를 생성해서는 안되며 내부에 생성하는 cshtml 파일이름또한 템블릿을 지정하려는 클래스 혹은 타입명과 일치해야 한다는 것입니다. 다만 편집이 아닌 읽기전용으로의 템블릿 생성의 경우에는 DisplayTemplates 으로 폴더를 생성해야 합니다.

 

@model WebApplication1.Infrastructure.MemberType

@Html.DropDownListFor(x => xnew SelectList(Enum.GetNames(typeof(WebApplication1.Infrastructure.MemberType))))

 

MemberType.cshtml 파일에서는 위와 같이 생성하려는 타입과 요소를 지정하면 MVC 프레임워크는 자동으로 해당 템블릿을 적용할 것입니다. 따라서 기존의 EditorForModel 메서드만으로 원하는 요소를 생성할 수 있게 됩니다.

 

이러한 방법은 UIHint 에서 데이터타입을 지정하는 것만으로도 원하는 요소를 생성할 수 있게 합니다. 예를 들어 DateTime 형식을 년월일로 표현하려면 /Views/Shared/EditorTemplates 하위에 DateTime.cshtml 파일을 생성하고

 

@model DateTime

@Html.TextBoxFor(x => xModel.ToString("yyyy년 MM월 dd일"))

 

위와 같이 원하는 데이터타입에 템플릿을 만들어 두면

 

[UIHint("DateTime")]
public DateTime BirthDay { getset}

 

UIHint 속성에 해당 데이터타입을 지정하는 것만으로 필요한 요소를 생성할 수 있습니다. 재미있는건 템블릿을 생성할때 파일안에 포함된 모든 내용을 렌더하게 되므로 추가적으로 필요한 다른 요소까지도 부수적으로 포함시켜 요소생성시 필요한 모든 요소를 렌더할 수 있다는 것입니다.

 

@model DateTime

<span style="color:red">시간 : </span>
@Html.TextBoxFor(x => xModel.ToString("yyyy년 MM월 dd일"))

'Programming > .NET' 카테고리의 다른 글

[ASP.NET MVC] 뷰 (View)  (0) 2018.08.14
[C#] using static  (0) 2018.07.27
[ASP.NET MVC] 헬퍼메서드(Helper Method)  (0) 2018.07.18
[C#] extern  (0) 2018.07.03
[C#] BinaryFormatter / XmlSerializer / DataContractJsonSerializer  (0) 2018.06.26
[C#] Encoding / BitConverter  (0) 2018.06.19
0 0
Programming/Microsoft SQL Server

하나의 MS-SQL 서버에서 다른 MS-SQL서버로 Insert나 Update, Delete작업의 수행시 아래와 같은 메세지를 볼 수 있습니다.

[분산 트랜잭션을 시작할 수 없으므로 요청한 작업을 수행할 수 없습니다.]

이 문제를 해결하려면 다음 절차를 따라합니다. [Server 2008이상]


1. 시작 -> 실행 에서 'dcomcnfg'를 입력합니다.


2. 구성 요소 서비스 -> 컴퓨터 -> 내 컴퓨터 -> Distributed Transaction Coordinator -> 로컬 DTC를 찾아갑니다.



3. 로컬 DTC에서 마우스 오른쪽 버튼을 눌러 '속성'을 클릭한뒤 '보안'탭에서 다음과 같이 설정합니다.



4. 위와 같은 설정을 통신하는 다른 서버에도 동일하게 적용합니다.


5. 방화벽설정에서 DTC관련 항목을 모두 예외처리합니다.


6. 트랜잭션 수행시 SET XACT_ABORT ON; 으로 시작하고 SET XACT_ABORT OFF; 로 끝을 맺습니다.

'Programming > Microsoft SQL Server' 카테고리의 다른 글

[SQL] 전체 텍스트 검색  (0) 2018.09.27
트랜잭션 (Transaction)  (0) 2018.09.12
분산 트랜잭션 설정  (0) 2018.07.11
[SQL] 기본언어확인및 변경  (0) 2018.01.30
[SQL] 스키마(Schema)  (0) 2018.01.23
[SQL] 다른 이름으로 DB복원  (0) 2017.09.05
0 0
Programming/.NET

C#과 같은 닷넷언어로 만들어진 프로그램은 플렛폼의 근간을 .NET에 두고 있는데 C나 C++로 만들어진 프로그램과는 메모리 관리방법이 다릅니다. 흔히 관리코드(.NET)와 비관리코드로 나누곤 하는데, 같은 .NET세계의 프로그램은 예를 들어 VB.NET과 C#에서 만든 서로간의 프로그램은 간단히 '참조'라는 방법을 통해서 아주 쉽게 서로간의 기능을 호출하여 사용하는 것이 가능하지만 C/C++처럼 완전히 다른 체계의 프로그램이라면 얘기가 조금 달라집니다.

 

세상의 모든 프로그램을 VB.NET이나 C#과 같은 닷넷호환언어로 만들 수는 없고, 때로는 필요한 기능을 C나 C++을 이용해 라이브러리(DLL)로 만들어 C#등의 프로그램에서 이들의 기능을 호출하는 방법으로 사용해야할 경우가 있는데 대표적인게 윈도우 API를 호출하는 경우입니다.

 

[DllImport("user32.dll")]
private static extern int RegisterHotKey(int hwnd, int id, int fsModifiers, int vk);

위 예제는 extern을 사용해 user32.dll에서 RegisterHotKey메서드를 호출하기위한 선언문입니다. 자세한 내용은 아래 링크를 참고해 주세요.

 

2011/05/31 - [Programming/Windows API] - RegisterHotKey - Windows Hotkey 설정

 

사실 extern예약어는 메서드의 본체가 없어도 컴파일을 정상적으로 수행시키는 역활을 할 뿐입니다. 예제에서도 RegisterHotKey 메서드에 대한 선언만 할뿐 메서드의 본체는 따로 정의하지 않았는데 이 메서드가 어디를 통해서 호출되어야 하는가는 DllImport특성 에서 파일(user32.dll)명을 통해 지정합니다. 그러면 이 파일에 존재하는 API함수인 RegisterHotKey메서드를 호출함으로서 처리되는 것입니다.

 

위와 같이 선언하고 나면 다음의 예제처럼 메서드를 호출하여 원하는 기능을 수행할 수 있습니다.

 

RegisterHotKey((int)this.Handle, 1122, MOD_CONTROL, (int)Keys.A);

 

extern에 관해서 윈도우 API를 호출하는 것으로 예제를 설명해 드렸지만 특정한 기능을 수행하는 프로그램을 직접 C/C++을 통해 DLL로 제작하였다면 위와 똑같은 방법으로 C#에서 호출하여 사용하는 것이 가능합니다. 다만 C/C++에서 C#에서 사용가능하도록 데이터형이나 타입을 맞추어야 할것입니다.

'Programming > .NET' 카테고리의 다른 글

[C#] using static  (0) 2018.07.27
[ASP.NET MVC] 헬퍼메서드(Helper Method)  (0) 2018.07.18
[C#] extern  (0) 2018.07.03
[C#] BinaryFormatter / XmlSerializer / DataContractJsonSerializer  (0) 2018.06.26
[C#] Encoding / BitConverter  (0) 2018.06.19
[C#] fixed  (0) 2018.06.13
0 0
Programming/.NET

C#에서는 문자열이나 정수와 같은 타입의 데이터를 byte배열로 변환하는 직렬화를 수행할 수 있으며 물론 그 반대로 가능합니다. 그렇다면 기본 데이터타입이 아닌 직접 만든 클래스와 같은 요소는 어떻게 직렬화를 수행할 수 있을까?

 

System.Runtime.Serialization.Formatters.Binary.BinaryFormatter가 그 답이 될 수 있습니다.

 

예를 들어 아래와 같은 클래스가 있습니다.

 

class Employee
{
    public Employee(string name, string department, int level)
    {
        Name = name;
        Department = department;
        Level = level;
    }

    public string Name
    {
        get;
        set;
    }

    public string Department
    {
        get;
        set;
    }

    public int Level
    {
        get;
        set;
    }
}

 

이 클래스를 직렬화하기 위해서는 우선 [Serializable] 이라는 특성을 클래스에 추가해 해당 클래스가 직렬화가능함을 알려줘야 합니다.

 

[Serializable]
class Employee

 

그리고 직렬화를 수행하려는 클래스에 대해 필요한 적절한 작업을 수행한 뒤

 

Employee e1 = new test.Employee("홍길동", "관리부", 4);

 

BinaryFormatter를 통해 MemoryStream으로 다음과 같이 직렬화를 수행합니다.

 

System.Runtime.Serialization.Formatters.Binary.BinaryFormatter bf = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();

MemoryStream ms = new MemoryStream();
bf.Serialize(ms, e1);

 

대부분 네트워크를 통해 다른 시스템으로 개체전체를 전송하고자 할때 직렬화를 많이 수행하는데 문제는 직렬화방식이 내부에 감춰져 있어서 .NET이 아닌 다른 플렛폼에서는 역직렬화가 힘들다는 단점이 있습니다.

 

Employee e1 = new test.Employee("홍길동", "관리부", 4);

System.Runtime.Serialization.Formatters.Binary.BinaryFormatter bf = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();

MemoryStream ms = new MemoryStream();
bf.Serialize(ms, e1);

ms.Position = 0;

Employee e2 = (Employee)bf.Deserialize(ms);

Console.WriteLine(e2.Name);

 

역직렬화는 간단합니다. BinaryFormatter의 Deserialize메서드를 호출해 Stream으로 읽기만 하면 됩니다. 물론 이 방법은 타 플렛폼과는 맞추기 힘든 부분이 있지만 같은 .NET환경이라면 비교적 빠르고 간단히 처리할 수 있습니다.

 

BinaryFormatter는 주어진 요소를 Binary로 직렬화하지만 System.Xml.Serialization.XmlSerializer는 문자열형태(기본 UTF8로 인코딩)로 직렬화를 수행합니다. 그런데 이름에서도 알 수 있듯이 그 문자열 포멧 형식이 XML입니다. 다만 XmlSerializer를 사용하려면 클래스를 다음과 같이 바꿔야 합니다.

 

public class Employee
{
    public Employee()
    { }
    public Employee(string name, string department, int level)
    {
        Name = name;
        Department = department;
        Level = level;
    }

    public string Name
    {
        get;
        set;
    }

    public string Department
    {
        get;
        set;
    }

    public int Level
    {
        get;
        set;
    }
}

 

먼저 클래스에 public 접근제한자가 있어야 하며 클래스안에 기본생성자를 포함해야 합니다. 하지만 이전에 추가했던 [Serializable]특성은 필요하지 않습니다.

 

Employee e1 = new test.Employee("홍길동", "관리부", 4);

System.Xml.Serialization.XmlSerializer xs = new System.Xml.Serialization.XmlSerializer(typeof(Employee));

MemoryStream ms = new MemoryStream();
xs.Serialize(ms, e1);

ms.Position = 0;

Employee e2 = (Employee)xs.Deserialize(ms);

Console.WriteLine(e2.Name);

 

BinaryFormatter와 클래스명만 다를뿐 사용법의 거의 똑같습니다. 차이라면 클래스를 통해 개체를 생성하는 부분에서는 XML스키마 작성을 위해 직렬화하려는 요소의 타입을 명시해야 한다는 것입니다.

 

XmlSerializer는 BinaryFormatter보다는 성능이 떨어지지만 XML이라는 표준포멧으로 직렬화를 수행하기에 타 플렛폼과의 호환성을 확보할 수 있다는 장점이 있습니다. 단, 직렬화되는 요소는 public제한자가 있는 필드만 가능합니다.

 

.NET 아닌 타 플렛폼입장에서 BinaryFormatter은 Binary변환으로 빠르게 처리될 수 있기는 하나 호환성에 문제가 생길 수 있고 XmlSerializer는 다소 긴 XML데이터를 생성하기는 하지만 XML포멧으로 인해 호환성문제는 해결될 수 있습니다. 각각 장단점이 존재하는데 이 둘의 장점만을 결합한 것이 바로 System.Runtime.Serialization.Json.DataContractJsonSerializer입니다. DataContractJsonSerializer는 문자열로 직렬화를 수행하기는 하지만 json포멧이기에 XML보다 훨씬 적은 데이터용량을 가질 수 있고 포멧또한 타 플렛폼에서 많이 다루는 것이므로 호환성도 확보할 수 있습니다.

 

Employee e1 = new test.Employee("홍길동", "관리부", 4);

System.Runtime.Serialization.Json.DataContractJsonSerializer dc = new System.Runtime.Serialization.Json.DataContractJsonSerializer(typeof(Employee));

MemoryStream ms = new MemoryStream();
dc.WriteObject(ms, e1);

ms.Position = 0;

Employee e2 = (Employee)dc.ReadObject(ms);

Console.WriteLine(e2.Name);

 

DataContractJsonSerializer에서 직렬화는 WriteObject()로 역직렬화는 ReadObject()메서드로 처리합니다.

'Programming > .NET' 카테고리의 다른 글

[ASP.NET MVC] 헬퍼메서드(Helper Method)  (0) 2018.07.18
[C#] extern  (0) 2018.07.03
[C#] BinaryFormatter / XmlSerializer / DataContractJsonSerializer  (0) 2018.06.26
[C#] Encoding / BitConverter  (0) 2018.06.19
[C#] fixed  (0) 2018.06.13
[C#] DateTime  (0) 2018.05.29
0 0
Programming/.NET

컴퓨터는 내부적으로 데이터를 숫자로 처리합니다. A, B, C와 같은 문자도 마찬가지입니다. 그럼 시스템은 내부적으로 문자를 처리하기 위해 숫자로 표현해야 하는데 이 과정을 인코딩(Encoding)이라고 합니다.

 

다만 특정 문자를 표현하기 위해 내부적으로 문자를 어떤 숫자로 처리해야 하는가가 정해져야 하며 이를 정리한 코드체계중 대표적으로 ASCII코드가 있습니다. ASCII코드는 A를 65, B를 66... 으로 표현합니다. 하지만 ASCII코드는 7bit를 기반으로 하기에 영문자나 숫자, 일부특수문자만을 표현할 수 있을뿐 한글, 한자, 일어와 같은 문자는 표현할 수 있는 방법이 없습니다. 그래서 대한민국은 한글표현을 위해 EUC-KR, CP949, KS_C_5601-1987등의 인코딩방식을 만들게 되고 다른 나라들도 자국의 언어를 위해 별도의 인코딩방식을 마련해 뒀습니다.

 

이처럼 문자를 숫자로 처리하는 인코딩방식이 너무 많이 늘어나자 근래에는 유니코드라는 산업표준으로 문자를 인코딩하는 추세입니다.(부호화 방식에 따라 UTF-7, UTF-8, UTF-16, UTF-32등으로 나뉘어짐)

 

만약 .NET환경에서 다른 시스템이나 폴렛폼에서 다루고자 하는 인코딩방식에 맞추려고 할때 System.Text.Encoding을 사용하면 이러한 작업을 좀더 쉽게 구현할 수 있습니다.

 

예를 들어 타 시스템에서 문자를 UTF-8방식으로 사용한다면 아래와 같이 처리할 수 있을 것입니다.

 

string sample = "hello world!";
byte[] b = Encoding.UTF8.GetBytes(sample); //타 시스템으로 전송하기 위한 처리

 

sample = Encoding.UTF8.GetString(b); //타 시스템에서 전달받은 문자를 처리
Console.WriteLine(sample);

 

참고로 문자열을 제외한 다른 타입은 인코딩방식이 고정되어 있습니다. 그래서 이런 경우에는 특별히 System.BitConverter를 사용할 수 있습니다.

 

byte[] b = BitConverter.GetBytes(100);
int i = BitConverter.ToInt16(b, 0);

Console.WriteLine(i);

 

'Programming > .NET' 카테고리의 다른 글

[C#] extern  (0) 2018.07.03
[C#] BinaryFormatter / XmlSerializer / DataContractJsonSerializer  (0) 2018.06.26
[C#] Encoding / BitConverter  (0) 2018.06.19
[C#] fixed  (0) 2018.06.13
[C#] DateTime  (0) 2018.05.29
[C#] 생성자와 소멸자  (0) 2018.05.23
0 0
Programming/.NET

unsafe 영역안에서 포이터를 사용하는건 스택으로 데이터를 저장하는 값형식의 변수에서만 가능합니다. 참조형식의 변수에서 포인터를 사용할 수 없는 이유는 참조형식의 데이터는 힙에 할당되고 힙은 가비지 컬렉터에 의해 위치가 바뀔 수 있기 때문입니다. 결국 참조형식의 주소를 갖고 있던 포인터는 언제라도 전혀 엉뚱한 곳을 가리킬 수 있기 때문입니다.

 

fixed는 힙에 할당된 참조 형식의 인스턴스를 가비지 컬렉터가 건드리지 못하게 하여 위에서 말한 문제를 해결할 수 있도록 합니다.

 

static void Main()
{
    unsafe {
        string s = "abcdefg";

        fixed (char* c = s.ToCharArray()) {
        for (int i = 0; i < s.Length; i++)
            Console.WriteLine(*(c + i));
        }
}

 

string 형은 참조형으로 값을 힙에 할당합니다. 이 string형의 시작주소를 포인터 c에 할당할때 fixed를 사용하므로서 string형인 s 인스턴스의 힙주소는 고정될 것입니다.

'Programming > .NET' 카테고리의 다른 글

[C#] BinaryFormatter / XmlSerializer / DataContractJsonSerializer  (0) 2018.06.26
[C#] Encoding / BitConverter  (0) 2018.06.19
[C#] fixed  (0) 2018.06.13
[C#] DateTime  (0) 2018.05.29
[C#] 생성자와 소멸자  (0) 2018.05.23
[C#] 개체의 생성과 해제  (0) 2018.05.15
0 0
Programming/.NET

System.DateTime를 이용해 시간을 생성하는 방법은 다음과 같습니다.

 

DateTime dt = new DateTime(2015, 01, 01);

 

현재 시간을 알려면 Now 속성을 사용합니다.

 

Console.WriteLine(DateTime.Now.ToString());

 

UtcNow 속성은 UTC기준 현재 시간을 의미합니다.

 

Console.WriteLine(DateTime.UtcNow.ToString());

 

UTC(Universal Time, Coordinated)는 협정세계시로서 GMT(Greenwich Mean Time)라는 그리니치 평균시와 함께 세계 표준시로 사용되고 있습니다. UTC는 영국 그리니치 천문대가 있는 위치의 0도를 0시기준으로 하고 동쪽지역 분기선 이내에는 시간이 증가하고 서쪽으로는 시간이 감소하는 형태를 이루고 있습니다. 따라서 동쪽 분기선 안에 있는 대한민국은 +9시간을 해야하고 이시간이 한국 표준시(KST : Korea Standard Time)가 됩니다.

 

시간을 임의로 더하거나 빼고싶다면 AddMonths, AddDays등의 메서드를 이용할 수 있습니다.

 

DateTime plusMonth = DateTime.Now.AddDays(1);
DateTime minusHour = DateTime.Now.AddHours(-1);

 

이때 양수는 그만큼의 시간을 더하고 음수는 그만큼의 시간을 감소시킵니다. 이것 외에도 DateTime끼리 연산하는 경우도 있으나 이때는 사칙연산중 -만 허용하며 결과값은 TimeSpan으로 받아야 합니다.

 

DateTime now = DateTime.Now;
DateTime plusMonth = DateTime.Now.AddMonths(-1);

TimeSpan ts = now - plusMonth;

 

좀더 세밀한 시간을 알고자 한다면 Ticks속성을 사용할 수 있습니다. Ticks는 1년 1월 1일 12시부터 지금까지 100나노초 간격으로 흐른시간을 나타냅니다.

 

Console.WriteLine(DateTime.Now.Ticks.ToString());

 

그런데 이 Ticks값은 유닉스계열이나 기타 다른 플렛폼에서 기준으로 하는 1970년 1월 1일과는 다르기 때문에 만약 이런 플렛폼들과 호환성을 맞추려 한다면 1970년만큼의 시간을 더해야합니다. 다행이 100나노초단위는 같으므로 다음과 같이 처리할 수 있습니다.

 

Console.WriteLine(((DateTime.UtcNow.Ticks - 621355968000000000) / 1000).ToString());

 

 

'Programming > .NET' 카테고리의 다른 글

[C#] Encoding / BitConverter  (0) 2018.06.19
[C#] fixed  (0) 2018.06.13
[C#] DateTime  (0) 2018.05.29
[C#] 생성자와 소멸자  (0) 2018.05.23
[C#] 개체의 생성과 해제  (0) 2018.05.15
[C#] 호출자 정보 (CallerMemberName / CallerFilePath / CallerLineNumber)  (0) 2018.05.08
0 0
Programming/.NET

클래스의 생성자는 클래스명과 같은 메서드를 정의함으로서 구현할 수 있습니다.

 

class MyClass
{
    public MyClass()
    {

    }
}

 

생성자는 말 그대로 클래스의 개체가 생성될때 호출되는 메서드입니다. 여기에 각종 변수의 초기화나 혹은 필요한 매개변수를 받아 클래스에서 사용되는 데이터를 초기화 하는 것이 가능합니다.

 

class MyClass
{
    public MyClass(int i, int j)
    {
        MyValue = i + j;
    }

    public int MyValue
    {
        get;
        set;
    }
}

 

예제에서 클래스는 개체생성시 두개의 매개변수를 받아 MyValue라는 속성값을 초기화 하도록 하였습니다.

 

static void Main(string[] args)
{
    MyClass m = new MyClass(10, 20);
    Console.WriteLine(m.MyValue);
}

 

소멸자는 GC에 의해 해당 개체의 자원을 해제할때 호출되는 메서드입니다. 이 메서드는 클래스이름앞에 ~문자만 붙이면 소멸자 메서드가 됩니다.

 

class MyClass
{
    public MyClass(int i, int j)
    {
        MyValue = i + j;
    }

    public int MyValue
    {
        get;
        set;
    }

    ~MyClass()
    {

    }
}

 

소멸자는 임의로 호출하는 것이 불가능하며 GC가 수행될때 호출됩니다. 소멸자는 그 특성으로 인해 소멸자에서 뭔가를 임의로 처리하는 경우는 거의 없습니다. 간혹 비관리메모리를 사용한 경우 GC에 의해 해제될 수 없기에 소멸자에서 명시적으로 해제에 필요한 코드를 담는 경우가 있기는 하지만 활용도가 크지는 않습니다.

 

class MyClass : IDisposable
{
    IntPtr myMemory;

    public MyClass()
    {
        myMemory = Marshal.AllocCoTaskMem(1024);
    }

    public void Dispose()
    {
        Marshal.FreeCoTaskMem(myMemory);
    }

    ~MyClass()
    {
        Dispose();
    }
}

 

소멸자가 있는 클래스에 new로 개체를 생성하면 이 개체를 힙에 저장하고 동시에 '종료큐'라고 하는 곳에도 개체를 등록하게 됩니다. GC가 이 개체를 힙에서 제거하고자 할때 종료큐에 들어간 개체를 Freachable에 옮기게 되면 CLR에 의해 생성된 스레드가 Freachable안에 존재하는 개체의 소멸자를 호출한뒤 Freachable안에서 개체를 제거하게 됩니다. 그리고 힙에 존재하는 개체는 GC가 실행될때 비로소 힙에서 제거됩니다. 이 복잡한 과정때문에 소멸자가 존재하는 클래스는 GC에 부담스럽게 작용하고 때문에 꼭 필요하지 않다면 소멸자를 별도로 생성하는건 권장하지 않습니다.

 

하지만 만약 클래스의 Dispose() 메서드가 명시적으로 호출될 수 있는 상황이고 이때 GC가 소멸자를 다룰 필요가 없는 경우라면 Dispose() 메서드에서 GC의 SuppressFinalize() 메서드를 호출하여 종료큐에서 개체를 제거하도록 할 수 있습니다. 이렇게 되면 GC가 소멸자를 고려할 필요가 없으므로 GC의 부담을 줄여줄 수 있게 됩니다.

 

class MyClass : IDisposable
{
    IntPtr myMemory;

    public MyClass()
    {
        myMemory = Marshal.AllocCoTaskMem(1024);
    }

    public void Dispose()
    {
        objectRemove(true);
    }

    private void objectRemove(bool self_call)
    {
        Marshal.FreeCoTaskMem(myMemory);

        if (self_call)
            GC.SuppressFinalize(this);
    }

    ~MyClass()
    {
        objectRemove(false);
    }
}

'Programming > .NET' 카테고리의 다른 글

[C#] fixed  (0) 2018.06.13
[C#] DateTime  (0) 2018.05.29
[C#] 생성자와 소멸자  (0) 2018.05.23
[C#] 개체의 생성과 해제  (0) 2018.05.15
[C#] 호출자 정보 (CallerMemberName / CallerFilePath / CallerLineNumber)  (0) 2018.05.08
[C#] 람다식 (Lambda expression)  (0) 2018.05.02
0 0
Programming/.NET

C#에서 개체는 new로 할당될 수 있습니다.

 

static void Main(string[] args)
{
    MyClass m = new MyClass();
}

 

그런데 개체를 생성하고 난 후 해당 개체를 명시적으로 해제하고자 한다면 개체를 생성한 클래스에서 IDisposable 인터페이스를 상속해 해제에 필요한 Dispose()  메서드를 구현해야 합니다.

 

static void Main(string[] args)
{
    MyClass m = new MyClass();
    m.Dispose();
}

 

class MyClass : IDisposable
{
    public void Dispose()
    {
        this.Dispose();
    }
}

 

직접 구현한 클래스가 아닌 BCL의 다른 클래스라 하더라도 자원해제가 필요한 클래스는 IDisposable 인터페이스를 상속받고 있으므로 Dispose() 메서드를 호출할 수 있습니다. 그러나 Dispose()메서드를 호출하는 구문을 작성했어도 내부적으로 오류가 발생하면 해당 구문이 호출되지 않을 수도 있습니다.

 

static void Main(string[] args)
{
    MyClass m = null;

    try {
        m = new MyClass();
    } catch { } finally {
        m.Dispose();
    }
}

 

그래서 try ~ catch구문을 사용해 오류발생상황과는 상관없이 Dispose()를 호출할 수 있도록 해주거나

 

static void Main(string[] args)
{
    using (MyClass m = new MyClass()) {
        //
    }
}

 

아니면 using으로 감싸는 방법을 쓰는것이 좋습니다. using은 using영역이 끝나는 지점에서 개체의 Dispose() 메서드를 자동으로 호출합니다.

0 0
Programming/.NET

특정 메서드가 호출될때 해당 메서드는 호출하는 측의 정보를 확인할 수 있습니다.

 

static void myMethod(string s[CallerMemberName] string cmn = ""[CallerFilePath] string cfp = ""[CallerLineNumber] int cln = 0)
{
    Console.WriteLine(s + "호출자 메서드 이름 : {0} - 호출자 파일 경로 : {1} - 호출자 소스코드 라인번호 : {2}"cmncfpcln);
}

 

메서드에서 호출자의 정보를 확인할 수 있는건 예제에서 쓰인 단 3가지 뿐입니다. 메서드에서 매개변수형식을 이와 같이 정의해 놓으면 해당 매개변수에는 호출자 정보가 자동으로 들어가게 됩니다.

 

따라서 호출하는 쪽에서는 원래 호출하는 방식 그대로 메서드를 호출하면 됩니다.

 

myMethod("호출자 정보 => ");

0 0
Programming/.NET

람다식은 수학에서의 람다대수형식을 C#언어의 문법으로 표현한것입니다. 람다식을 사용하면 기존의 길고 복잡했던 구문을 간소화하고 단순화시킬 수 있습니다.

 

예를 들어 익명메서드를 사용한 하나의 예를

 

delegate int callMethod(int iint j);

static void Main(string[] args)
{
    callMethod cm = delegate (int iint b)
    {
        return i + b;
    };

    Console.Read();
}

 

람다식으로 표현하면 다음과 같이 할 수 있습니다.

 

delegate int callMethod(int iint j);

static void Main(string[] args)
{
    callMethod cm = (ib) => i + b;

    Console.Read();
}

 

우선 callMethod 타입에서는 델리게이트 선언으로 인해 델리게이트형식의 익명메서드, 그리고 해당 메서드에 int형 매개변수 2가 전달된다는 것을 알 수 있습니다. 따라서 deleteage와 int형식은 다음과 같이 생략할 수 있습니다.

 

delegate (int i, int b) -> (i, b)

 

그리고 이 생략된 표현이 람다식임을 => 문자로 알려줍니다.

 

람다식 표현에서 반환값이 없는 경우에는 중괄호({})가 있어야 하지만 반환값이 존재하는 경우에는 return과 해당 부분을 감싸는 중괄호가 생략될 수 있기에 최종적으로 다음과 같은 표현이 완성됩니다.

 

(i, b) => i + b;

 

callMethod cm = (ib) => i + b;
Console.WriteLine(cm(100, 200));

 

예제에서는 람다식으로 델리게이트를 사용해 익명 메서드를 구현하고 있습니다. 이것은 일반적인 익명 메서드도 마찬가지지만 람다식으로 익명 메서드를 구현하기 위해서는 일일이 델리게이트를 선언해야 한다는 것을 의미합니다. 이런 불편함을 감소시키고자 .NET에서는 델리게이트의 일부 형식을 구현한 Action과 Func를 마련해 두었습니다.

 

static void Main(string[] args)
{
    Action<int> a_i = (i) => { Console.WriteLine("number : {0}"i)};
    a_i(100);

    Console.Read();
}

 

먼저 Action은 반환값이 없는 익명메서드를 구현하는데 사용됩니다. Action에 보시면 매개변수로 받을 수 있는 데이터형식을 <int>로 정의했는데 이것은 Action<T>로 제네릭화 되어있다는 것을 알 수 있습니다. 이런 사항은 Func도 마찬가지입니다.

 

Func<intintint> cal = (ij) => i + j;
int sum = cal(100, 200);
Console.WriteLine(sum);

 

Func는 반환값이 있는 경우에 사용할 수 있습니다. 예제에서 Func는 매개변수 데이터 형식으로 int형 3개가 존재하는데 처음 2개는 i와 j에 대응되고 나머지 하나는 반환형식에 해당합니다.

 

Action과 Func사용시 매개변수는 최대 16개까지 유동적으로 사용할 수 있습니다.

 

람다식은 익명메서드를 구현하는것 뿐만 아니라 컬렉션에도 적용할 수 있습니다. 예를 들어

 

List<int> l = new List<int{ 1, 2, 3, 4, 5 };

 

위와 같은 List의 경우 각 요소를 순회하려면 다음과 같이 할 수 있지만

 

foreach (int i in l)
    Console.WriteLine(i);

 

Action 람다식을 적용하면

 

l.ForEach((el) => { Console.WriteLine(el)});

 

이와 같이 처리할 수 있습니다. 예제를 보면 컬렉션의 ForEach메서드를 사용하는데 이 메서드는 Action<T>타입을 받으므로 람다식의 Action을 전달하면 됩니다.  다음 예제는 ForEach의 또 다른 처리방식을 보여주고 있습니다.

 

Array.ForEach(l.ToArray()(el) => { Console.WriteLine(el)});

 

Action은 리턴값을 가지지 않는다고 하였습니다. 따라서 특정 조건에 해당하는 요소만을 반환받으려면 Func를 사용할 수 있습니다.

 

List<int> r = l.FindAll((el) => el > 3);
r.ForEach((el) => { Console.WriteLine(el)});

 

이때 메서드는 FindAll을 사용하며 Func를 구현하는 람다식을 통해 3보다 큰 값만 가져오도록 하였습니다. FindAll과 비슷한 메서드로 Where도 존재하는데

 

IEnumerable<int> r = l.Where((el) => el > 3);
Array.ForEach(r.ToArray()(el) => { Console.WriteLine(el)});

 

이때 Where는 List가 아닌 IEnumerable형식으로 결과를 반환합니다. IEnumerable은 자체적인 ForEach메서드를 지원하지 않으므로 Array의 ForEach를 호출해 결과요소를 반환받아야 합니다. 이때 만약 반환되는 데이터 타입을 달리해야 한다면 Select를 사용할 수 있습니다.

 

IEnumerable<double> r = l.Select((el) => double.Parse((el > 3).ToString()));

 

FindAll과 Where메서드는 결과는 같지만 동작방식은 약간 다른데 Where메서드는 호출되는 즉시 동작하지 않고 ForEach로 배열순회가 되서야 실행이 됩니다. 이를 지연된 평가라고 하는데 이때문에 요소를 임의적으로 정리만 해준다면 Where에서 다루어지는 요소가 전체가 아닌 일부만으로 처리될 수 있기에 성능적으로 도움이 될 수 있습니다.

 

위 예제에서 사용된 Select도 마찬가지인데 이는 ConvertAll에서 지연된 평가를 위해 만들어진 메서드입니다. 이처럼 람다식자체는 메서드나 컬렉션에서 특정 연산을 위해 사용될 수 있습니다.

 

이 외에도 람다식은 식자체를 데이터로서 취급하는 방법이 존재합니다. 예를 들어 Func에 관한 람다식을 데이터로 하려면

 

Expression<Func<intintint>> exp = (ij) => i + j;

 

위와 같이 할 수 있습니다. Expression<T>에서 T는 식에 해당하는 델리게이트타입이 전달되며 exp가 식을 담고 있는 변수에 해당합니다. 따라서 Expression 개체는 다음 형태처럼 구문에 대한 개별적인 표현이 가능합니다.

 

Expression<Func<intintint>> exp = (ij) => i + j;

BinaryExpression be = (BinaryExpression)exp.Body;
Console.WriteLine(be.ToString());

 

exp에서 Body는 식의 본체를 의미하고 이를 출력해 보면 (i + j)라는 내용을 확인할 수 있습니다. 그런데 반대의 경우도 가능해서 Expression을 통해 식을 구성할 수도 있습니다.

 

ParameterExpression Lpe = Expression.Parameter(typeof(int)"i");
ParameterExpression Rpe = Expression.Parameter(typeof(int)"j");

BinaryExpression plusBe = Expression.Add(Lpe, Rpe);

Expression<Func<intintint>> exp = Expression<Func<intintint>>.Lambda<Func<intintint>>(plusBe, new ParameterExpression[] { Lpe, Rpe });

 

좌항과 우항을 ParameterExprsssion으로 구성하고 BinaryExpression을 통해 2항 연산에 관한 식을 정의합니다. 이때 Expression의 Add 메서드를 통해 +연산자를 적용한뒤 연산자와 각항을 구성하고 Lamda메서드로 전체식을 구성합니다. 물론 BinaryExpression외에도 루프를 표현하는 LoopExpression이나 상수식을 표현하는 ConstantExpression등 구성할 수 있는 다양한 타입의 식이 존재합니다.

 

Console.WriteLine(exp.ToString());
Func<intintint> f = exp.Compile();
Console.WriteLine(f(10, 20));

 

위에서 처럼 식을 데이터로서 취급하면서도 컴파일과정을 통해 실제 실행가능한 코드로 동작시킬 수도 있습니다.

 

Expression<Func<intintint>> exp = (ij) => i + j;

Func<intintint> f = exp.Compile();
Console.WriteLine(f(10, 20));

 

식을 데이터로서 다루고자 하는 이러한 방법은 프로그램이 동작하는 시점에 필요한 처리로직을 동적으로 생성하고 실행할 수 있다는 것인데 사실 잘 쓰이지는 않는 방법입니다. 그냥 이런게 가능하다는 것만 알아두고 넘어가도 좋을듯 합니다.

 

참고로 람다식을 이용하면 익명메서드와 비슷하게 단일문의 메서드를 생성할 수 있습니다.

 

static void Main(string[] args)
{
    int sum = plus(10, 20);

    Console.Read();
}

static int plus(int iint j) => (i + j);

 

plus메서드는 2개의 매개변수값을 받아 하나의 결과를 반환하는 단일문으로 메서드가 구현되었습니다.

0 0
Programming/.NET

ASP.NET MVC에서는 URL을 통해 HTTP요청을 할때 URL에 들어간 요청내용을 .NET개체로 변환해 처리할 수 있습니다. 이 과정을 바인딩(Binding)이라고 하는데 이것은 string이나 int형의 단일 형식 데이터부터 사용자 지정 클래스까지 다양한 개체를 바인딩으로 다룰 수 있다는 것을 의미합니다.

 

public ActionResult Index()
{
    string[] s = new string[5] { "aa""bb""cc""dd""ee" };
    return View(s);
}

 

액션메서드에서 문자열 배열을 뷰에 전달하고 있습니다.

 

@model string[]

@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Index</title>
</head>
<body>
    @using (Html.BeginForm()) {
        <table border="1">
            <tbody>
                <tr>

                    @for (int i = 0; i <= 4; i++) {
                        <td>@Html.TextBox("s"Model[i])</td>
                    }
                </tr>
            </tbody>
        </table>
        <input type="submit" value="확인" />
    }
</body>
</html>

 

뷰에서는 model을 string[] 형식으로 취해 값을 받을 수 있고 이때 Model은 string 배열이 되므로 string 배열과 동일한 방식으로 접근할 수 있게 됩니다.

 

[HttpPost]
public ActionResult Index(string[] s)
{
    return View("Result"s);
}

 

또다른 Index 액션메서드는 뷰에서 사용자가 특정 값을 입력하고 '확인'을 눌렀을때 Post로 배열을 전달할 수 있도록 합니다. 이것은 배열을 바인딩 처리하는 하나의 예로서 볼 수 있습니다.

 

@model string[]

@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Result</title>
</head>
<body>
    <table border="1">
        <tbody>
            <tr>
                @foreach (string s in Model)
                {

                    <td>@s</td>
                }
            </tr>
        </tbody>
    </table>
</body>
</html>

 

배열을 전달받은 뷰에서도 마찬가지로 string[] 형식을 취하여 Model을 통해 데이터를 다룰 수 있도록 합니다.

 

public ActionResult Index()
{
    List<string> s = new List<string>() { "aa""bb""cc""dd""ee" };
    return View(s);
}

 

이 예제는 기존의 단순한 배열에서 List형식의 컬렉션으로 변경한 결과를 보여주고 있습니다.

 

@model IList<string>

@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Index</title>
</head>
<body>
    @using (Html.BeginForm()) {
        <table border="1">
            <tbody>
                <tr>

                    @foreach (string s in Model) {
                        <td>@Html.TextBox("s", s)</td>
                    }
                </tr>
            </tbody>
        </table>
        <input type="submit" value="확인" />
    }
</body>
</html>

 

모델의 형식을 IList<string>으로 잡는것 외에는 큰 차이가 존재하지 않습니다. 여기서 중요한 것은 바인딩을 통해 .NET의 개체를 손쉽게 처리할 수 있다는 것입니다.

 

public class Member
{
    public int Idx { getset}
    public string Name { getset}
    public string Email { getset}
    public DateTime BirthDay { getset}
    public int Point { getset}
}
▶ Model

 

public class HomeController : Controller
{
    Member[] mb = {
        new Member() { Idx = 1, Name = "홍길동"Email = "hong1@cliel.com"Point = 100, BirthDay = new DateTime(1981, 11, 14) },
        new Member() { Idx = 2, Name = "홍길남"Email = "hong2@cliel.com"Point = 110, BirthDay = new DateTime(1980, 4, 23) },
        new Member() { Idx = 3, Name = "홍길석"Email = "hong3@cliel.com"Point = 55, BirthDay = new DateTime(1973, 8, 19) },
        new Member() { Idx = 4, Name = "홍길순"Email = "hong4@cliel.com"Point = 80, BirthDay = new DateTime(1992, 6, 5) },
        new Member() { Idx = 5, Name = "홍길영"Email = "hong5@cliel.com"Point = 98, BirthDay = new DateTime(1986, 3, 8) }
    };

    // GET: Home
    public ActionResult Index(int id = 1)
    {
        int defaultId = 1;
        ViewBag.id = mb.ToList().Select(x => new SelectListItem { Value = x.Idx.ToString(), Text = x.Name, Selected = (x.Idx == defaultId) });

        IEnumerable<Member> member = mb.Where(x => x.Idx == id);
        return View(member);
    }
}

▶ Controller

 

@using WebApplication1.Classes
@model IEnumerable<WebApplication1.Classes.Member>

@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Index</title>
</head>
<body>
    <div>
        <table border="1">
            <thead><tr><th>이름</th><th>포인트</th><th>생년월일</th></tr></thead>
            <tbody>
                @foreach (Member m in Model) {
                    <tr>
                        <td>@m.Name</td>
                        <td>@m.Point</td>
                        <td>@m.BirthDay.ToString("yyyy년MM월dd일")</td>
                    </tr>
                }
            </tbody>
        </table>
    </div>
    <br />
    @using (Html.BeginForm()) {
        @Html.DropDownList("id")
        <input type="submit" value="확인" />
    }
</body>
</html>

▶ View

 

 

이 예제에서는 Routing 구성에 따라 URL에서 id라는 세그먼트를 옵션으로 받을 수 있으며 Controller에서 id값을 받아 Member중에 Idx와 동일한 명단을 출력할 것입니다. 여기서 중요한 것은 id로 전달된 3이라는 숫자값입니다. URL을 통해 숫자 3을 전달했는데 Controller에서 이 값을 int형의 개체로 받아 내부에서 적절한 처리를 수행하고 있는 아주 간단한 바인딩처리의 예입니다.

 

URL을 통해 HTTP요청을 하는 경우 id와 같은 세그먼트 바인딩은 매개변수의 이름과 요청하는 식별자의 이름이 같고 데이터형식이 일치할 수 있는 경우에만 동작할 수 있습니다. 예제에서는 매개변수의 이름(id)과 라우팅정보에 해당하는 id이름이 같고, 3이라는 숫자는 int형에 대응될 수 있기 때문에 정상적으로 처리될 수 있었습니다. 또한 액션 메서드에서는 int형 id에 기본으로 1이라는 값을 주었는데 이는 id값이 URL에서 생략되었을때 대체적으로 사용할 수 있는 값을 제공하기 위함입니다.

 

바인딩은 단순 데이터에만 국한되지 않고 특정 개체자체를 전달할 수도 있습니다. 특히 이러한 사례는 HTTP POST에서 잘 살펴볼 수 있습니다.

 

public ActionResult AddMember()
{
    return View(new Member());
}

▶ Action

 

@model WebApplication1.Classes.Member

@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>AddMember</title>
</head>
<body>
    @using (Html.BeginForm()) {
        @Html.EditorFor(x => x.Idx)<br/>
        @Html.EditorFor(x => x.Name)<br />
        @Html.EditorFor(x => x.Email)<br />
        @Html.EditorFor(x => x.Point)<br />
        @Html.EditorFor(x => x.BirthDay)<br />
        <input type="submit" value="확인" />
    }
</body>
</html>
▶ View

 

AddMember는 Member를 추가할 경우의 입력폼을 가진 View를 출력합니다. 이 View에서 Member에 관한 각 항목을 입력하고 '확인'버튼을 누르면

 

[HttpPost]
public ActionResult AddMember(Member m)
{
    return View("ResultMember"m);
}

 

Member개체는 POST를 통해 같은 메서드로 바인딩처리되고

 

@model WebApplication1.Classes.Member

@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>AddMember</title>
</head>
<body>
    @using (Html.BeginForm()) {
        @Html.EditorFor(x => x.Idx)<br/>
        @Html.EditorFor(x => x.Name)<br />
        @Html.EditorFor(x => x.Email)<br />
        @Html.EditorFor(x => x.Point)<br />
        @Html.EditorFor(x => x.BirthDay)<br />
        <input type="submit" value="확인" />
    }
</body>
</html>

 

ResultMember View를 통해 입력결과를 확인할 수 있습니다. 이 예제는 실제 Member에 대한 추가작업을 수행하지는 않지만 HTTP POST를 통해 특정 개체를 전달하고 전달된 개체를 바인딩하여 그대로 활용하는 방식을 볼 수 있습니다.

 

또한 ASP.NET MVC에서는 이러한 바인딩동작을 좀더 유연하게 처리할 수 있도록 Bind라는 속성을 제공하고 있습니다.

 

public ActionResult AddMember([Bind(Exclude"Point")]Member m)
{
    return View("ResultMember"m);
}

 

Bind 의 Exclude 속성은 지정한 이름에 해당하는 클래스의 멤버에 대해서는 아예 바인딩을 처리하지 않도록 지시할 수 있습니다. 물론 아래와 같이 클래스개체에 직접적으로 지정하는 것도 가능합니다.

 

[Bind(Exclude = "Point")]
public class Member

 

반대의 개념으로 Include도 있는데 이 속성에 멤버를 지정하면 해당하는 멤버만 바인딩에 포함시키고 그 외에 모든 멤버를 바인딩에서 제외하게 됩니다.

 

또 하나 유용하게 활용할 수 있는 속성으로는 Prefix라는 것이 있는데 이 기능을 제대로 살펴보기 위해서는 우선 다음과 같이 클래스의 구조를 살짝 변경해야 합니다.

 

public class Member
{
    public int Idx { getset}
    public UserName Name { getset}
    public string Email { getset}
    public DateTime BirthDay { getset}
    public int Point { getset}
}

public class UserName
{
    public string Name { getset}
    public string Nick { getset}
}

 

Member클래스의 Name을 하위 클래스로 구현해 실제이름과 별명을 같이 지정할 수 있게 확장한 형태라고 가정해 보겠습니다.

 

List<Membermb = new List<Classes.Member{
    new Member() { Idx = 1, Name = new UserName() { Name = "홍길동"Nick = "홍1" }Email = "hong1@cliel.com"Point = 100, BirthDay = new DateTime(1981, 11, 14) },
    new Member() { Idx = 2, Name = new UserName() { Name = "홍길남"Nick = "홍2" }Email = "hong2@cliel.com"Point = 110, BirthDay = new DateTime(1980, 4, 23) },
    new Member() { Idx = 3, Name = new UserName() { Name = "홍길석"Nick = "홍3" }Email = "hong3@cliel.com"Point = 55, BirthDay = new DateTime(1973, 8, 19) },
    new Member() { Idx = 4, Name = new UserName() { Name = "홍길순"Nick = "홍4" }Email = "hong4@cliel.com"Point = 80, BirthDay = new DateTime(1992, 6, 5) },
    new Member() { Idx = 5, Name = new UserName() { Name = "홍길동"Nick = "홍5" }Email = "hong5@cliel.com"Point = 98, BirthDay = new DateTime(1986, 3, 8) }
};

▶ Controller

 

public ActionResult Index(intid = 1)
{
    int defaultId = 1;
    ViewBag.id = mb.ToList().Select(x => new SelectListItem { Value = x.Idx.ToString(), Text = x.Name.Name, Selected = (x.Idx == defaultId) });

    IEnumerable<Member> member = mb.Where(x => x.Idx == id);
    return View(member);
}

▶ Action

 

물론 그에 맞게 새롭게 List와 기본액션메서드를 변경해야 합니다.

 

<body>

    @using (Html.BeginForm()) {
        @Html.EditorFor(x => x.Idx)<br/>
        @Html.EditorFor(x => x.Name.Name)<br />
        @Html.EditorFor(x => x.Name.Nick)<br />
        @Html.EditorFor(x => x.Email)<br />
        @Html.EditorFor(x => x.Point)<br />
        @Html.EditorFor(x => x.BirthDay)<br />
        <input type="submit" value="확인" />
    }
</body>

 

하위 클래스의 경우 속성값 설정에는 보시는 바와 같이 [개체명].[속성] 형태로 지정되어야 합니다. 그런데 이런 형태의 하위 클래스사용은 생각보다 빈번하게 다루어질 수 있어서 간혹 다른 클래스사이의 바인딩을 수행해야 하는 경우도 있습니다. 예를 들어

 

public class MemberName
{
    public string Name { getset}
    public string Nick { getset}
}

 

AddMember 에서 지정한 사용자 이름과 별명을 이 새로운 클래스로 바인딩을 해야 한다고 했을때

 

public ActionResult AddMember2(MemberName mn)
{
    return View("ResultMemberName"mn);
}

 

그에 따른 액션메서드와

 

@model WebApplication1.Classes.MemberName

@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title></title>
</head>
<body>
    <table border="1">
        <thead><tr><th>이름</th><th>별명</th></tr></thead>
        <tbody>
            <tr>
                <td>@Model.Name</td>
                <td>@Model.Nick</td>
            </tr>
        </tbody>
    </table>
</body>
</html>

 

뷰를 추가한뒤

 

<body>

     @using (Html.BeginForm("AddMember2""Home")) {
        @Html.EditorFor(x => x.Idx)<br/>
        @Html.EditorFor(x => x.Name.Name)<br />
        @Html.EditorFor(x => x.Name.Nick)<br />
        @Html.EditorFor(x => x.Email)<br />
        @Html.EditorFor(x => x.Point)<br />
        @Html.EditorFor(x => x.BirthDay)<br />
        <input type="submit" value="확인" />
    }
</body>

 

AddMember.cshtml을 위와 같이 수정하고 '확인'을 누르게 되면 새롭게 추가된 뷰에는 아무것도 나오지 않게 됩니다. 왜냐하면 AddMember 에서는 여전히 Name값을 전달하기 위해 Name.Name과 Name.Nick 에서 Name이라는 공통된 접두사를 사용하고 있기 때문입니다. 이 접두사는 새로 생성한 MemberName 클래스에는 존재하지 않는 접두사이기에 입력한 값을 가져올 수 없습니다.

 

public ActionResult AddMember2([Bind(Prefix="Name")]MemberName mn)
{
    return View("ResultMemberName"mn);
}

 

이 문제를 해결하려면 매개변수를 통해 개체를 전달할때 Bind 의 Prefix 속성을 통하여 값을 가져올 접두사를 지정하는 것입니다. 이렇게 하면 자연스럽게 AddMember에서 Name.Name과 Name.Nick를 통해 값을 가져올 수 있게 됩니다.

 

지금까지는 단일 클래스모델의 바인딩을 알아봤는데 가장 처음에 말씀드렸던 배열의 바인딩을 이용하면 다중 클래스모델의 바인딩도 손쉽게 다룰 수 있습니다.

 

public ActionResult AddMember()
{
    return View(new Member());
}

[HttpPost]
public ActionResult AddMember(List<Memberm)
{
    return View("ResultMember"m);
}

 

AddMember의 액션메서드를 위와 같이 수정합니다. 특히 두번째 AddMember의 액션메서드는 매개변수로 받는 Member를 List 컬렉션으로 바인딩하고 있습니다.

 

@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>AddMember</title>
</head>
<body>
    @using (Html.BeginForm()) {
        for (int i = 0; i <= 2; i++) {
            @Html.Editor("[" + i + "].Name.Name")<br />
            @Html.Editor("[" + i + "].Point")<br />
            <br /><br />
        }

        <input type="submit" value="확인" />
    }
</body>
</html>

 

처음 시작되는 기본뷰에서는 필요한 속성들을 [0]과 같은 접두사를 통해 지정하고 있습니다. 여기서 []안에 숫자는 Member에 대한 각 개체의 배열순번과 일치하여 해당 순번의 배열에 되는 Member개체에 값을 전달하게 됩니다.

 

@using WebApplication1.Classes
@model List<WebApplication1.Classes.Member>

@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>ResultMember</title>
</head>
<body>
    <table border="1">
        @foreach (Member m in Model) {
            <tr>
                <td>
                    @m.Name.Name
                </td>
                <td>
                    @m.Point
                </td>
            </tr>
        }
    </table>
</body>
</html>

POST이후 보여질 뷰에서는 모델형식을 List<WebApplication1.Classes.Member> 형식으로 잡고 순차적으로 순회를 하게 되면 List에 담긴 각 Member개체의 값을 출력하게 됩니다.

0 0
1 2 3 4 ··· 18
블로그 이미지

클리엘