블로그 이전
http://blog.aliencube.org
블로그 주소는 동일합니다만, 텀블러에서 워드프레스로 이전합니다. 이곳의 포스트는 모두 옮겨졌습니다만 백업 차원에서 남겨둡니다.
AnasAbdin
Aqua Utopia|海の底で記憶を紡ぐ

Product Placement
d e v o n

@theartofmadeline

Andulka
Show & Tell
Cosimo Galluzzi

No title available
TVSTRANGERTHINGS
trying on a metaphor

❣ Chile in a Photography ❣
One Nice Bug Per Day

JBB: An Artblog!
Sweet Seals For You, Always

★
wallacepolsom
🪼

Origami Around
Cosmic Funnies

seen from South Africa
seen from Sweden
seen from United States

seen from Canada

seen from Malaysia

seen from Malaysia

seen from United Kingdom
seen from Germany
seen from United States
seen from Spain
seen from United States

seen from Thailand

seen from United States
seen from United States

seen from Germany
seen from Türkiye
seen from United States

seen from T1

seen from Germany
seen from United States
@aliencube-community
블로그 이전
http://blog.aliencube.org
블로그 주소는 동일합니다만, 텀블러에서 워드프레스로 이전합니다. 이곳의 포스트는 모두 옮겨졌습니다만 백업 차원에서 남겨둡니다.
데메테르의 법칙 Law of Demeter
데메테르는 그리스 신화에 나오는 추수의 신이다. 로마신화에서는 세레스 Ceres 라고 불리는 바로 그 신. 하지만, 그 데메테르하고 이 법칙하고는 상관없다는 것이 함정. [위키피디아]1에서는 데메테르의 법칙을 아래와 같이 정의하고 있다.
데메테르의 법칙에서는 어떤 객체 O의 메소드 m는 다음과 같은 종류의 객체에 있는 메소드들만 실행시킬 수 있다.
O 자체
m 의 변수
m 안에서 만들어진 객체
O가 직접 관리하는 콤포넌트 객체
m의 스코프 안에서 O가 접근 가능한 전역변수
좀 말이 어려운데, Richard Carr의 [The Law of Demeter]2 포스트에 좀 더 쉬운 설명이 있다.
어떤 클라스의 멤버 – 메소드 또는 속성 – 는 반드시 다음과 같은 객체들의 멤버들만을 실행시켜야 한다:
해당 메소드 또는 속성이 선언된 객체
메소드의 파라미터로 보내진 객체
메소드 또는 속성이 직접 초기화시킨 객체
호출을 위한 메소드 또는 속성으로서 같은 클라스 안에서 선언된 객체
전역 객체
아래 예제 코드를 보자. ASP.NET MVC 웹사이트를 개발하다보면 콘트롤러에서 흔히 볼 수 있는 상황이다.
public class ProductController : Controller { private IProductService _service; public ProductController(IProductService service) { this._service = service; } public ActionResult Index() { var products = this._service.Repository.Get(); return View(products); } } public class ProductService : IProductService { public ProductService(IProductRepository repository) { this.Repository = repository; } public IProductRepository Repository { get; private set; } }
위의 코드에서 Index 액션을 보면 대략 예상이 가능하겠지만 ProductService라는 서비스 레이어 안에서 ProductRepository라는 데이터 리포지토리 패턴을 통해 CRUD를 구현하고 있다. Index 액션은 전체 제품 리스트를 보여주는 뷰를 갖고 있어서 전체 제품 리스트는 서비스 안에 구현된 리포지토리의 Get 메소드를 통해 가져오게 된다. 이렇게 메소드 체이닝을 하는 것이 바로 데메테르의 법칙을 위반하는 것이 된다. ProductController 객체는 생성자를 통해 변수로 받은 ProductService 객체의 메소드 또는 속성을 호출해야 하지 그 내부에 있는 ProductRepository 객체의 Get 메소드를 직접 호출해서는 안된다. ProductRepository 객체의 현재 상태가 null이라면 해당 코드는 NullReferenceException을 던지기 때문이다. 따라서 ProductService 클라스 안에 추가적인 메소드를 선언해주는 방식으로 리팩토링을 해야 한다.
public class ProductController : Controller { private IProductService _service; public ProductController(IProductService service) { this._service = service; } public ActionResult Index() { var products = this._service.GetProducts(); return View(products); } } public class ProductService : IProductService { private IProductRepository _repository; public ProductService(IProductRepository repository) { this._repository = repository; } public IList<Product> GetProducts() { return this._repository.Get(); } }
즉 ProductRepository 객체를 public 속성이나 필드로 두는 것이 아니라 내부적으로 encapsulation 시키고 ProductRepository 클라스의 멤버는 ProductService 클라스의 멤버를 통해 호출하는 방식으로 하게 되면, ProductController 클라스는 직접적으로 관련이 있는 콤포넌트인 ProductSerivce에 대해서만 통제권을 가질 수 있어서 보다 안전하고 유연한 코드를 작성할 수 있게 된다.
이런식으로 메소드 체이닝을 최대한 줄이는 것이 바람직한 객체지향 프로그래밍이라고 할 수 있겠다. 하지만 이렇게 프로그래밍을 하게 되면 추가적인 메소드를 작성해야 하는 부담이 생기게 되는데, 이것이 꼭 부정적이라고는 할 수 없는 것이 객체 사이의 의존성을 최소화하는 방식으로 유연하게 개발을 할 수 있기 때문이다.
단, LINQ를 쓰는 상황이라면 얘기가 달라진다. LINQ에서는 특성상 메소드 체이닝이 필수일 수 밖에 없는지라, 이 데메테르의 법칙에서 벗어날 수 있는데, 그 이유는 메소드 체이닝을 하는 것과 상관없이 항상 리턴타입이 동일하기 때문이다. 위의 예제 코드에 나온 ProductRepository 클라스의 Get 메소드는 아마도 내부적으로 아래와 같이 구현이 되어 있을 것이다.
public class ProductRepository : IProductRepository { private CompanyDataContext _context; public ProductRepository(ICompanyDataContext context) { this._context = context as CompanyDataContext; } public IList<Product> Get() { return this._context .Products .Where(p => p.IsActive) .OrderBy(p => p.DateRegistered) .ToList(); } }
여기서 Products, Where 그리고 OrderBy는 모두 동일한 IEnumerable<Product> 객체를 반환한다. 즉 LINQ를 이용한 메소드 체이닝의 경우 메소드마다 동일한 데이터 타입을 반환하기 때문에 이 데메테르의 법칙을 위반하지 않고 안전하게 사용할 수 있다.
참조:
[Law of Demeter]1 from Wikipedia
[The Law of Demeter]2 by Richard Carr
http://en.wikipedia.org/wiki/Law_of_Demeter ↩︎ ↩︎
http://www.blackwasp.co.uk/LawOfDemeter.aspx ↩︎ ↩︎
엔티티 프레임워크 Code First 방법론 #3
엔티티 프레임워크 Code First 방법론 #1
엔티티 프레임워크 Code First 방법론 #2
엔티티 프레임워크 Code First 방법론 #3
Foreign Key 설정하기
앞서 Data Annotation 방법을 통해 테이블의 컬럼들에 대한 속성을 제어하는 방법에 대해 알아보았다. 이번에는 테이블 각각에 대한 관계를 설정하는 방법에 대해 논의해 보도록 하자.
현재 Products 테이블과 Orders 테이블은 각각 상품 정보, 주문 정보를 가지고 있다. 이 둘은 many-to-many 관계이므로, 정규화를 거쳐 one-to-many 관계로 바꾸어 주어야 하는데, 그 결과로 ProductOrders 테이블이 만들어졌다. 따라서, 이 ProductOrders 테이블에 있는 ProductId와 OrderId가 Forein Key 로서 역할을 해야 한다. 아래와 같이 ProductOrder 클라스를 수정해 보자.
public class ProductOrder { [Key] public int ProductOrderId { get; set; } [Required(ErrorMessage = "ProductId must be set")] public int ProductId { get; set; } [ForeignKey("ProductId")] public virtual Product Product { get; set; } [Required(ErrorMessage = "OrderId must be set")] public int OrderId { get; set; } [ForeignKey("OrderId")] public virtual Order Order { get; set; } [Required(ErrorMessage = "AmountOrdered must be set")] public int AmountOrdered { get; set; } }
위 코드에서 주목해야 할 부분은 public virtual Product Product { get; set; } 부분과 public virtual Order Order { get; set; } 부분이다. ProductOrder 클라스가 ProductId, OrderId 필드를 각각 해당하는 Product, Order 클라스의 Foreign Key 로서 인식시키기 위하여 버추얼 프로퍼티를 추가시켰다. 이렇게 추가시켜 컴파일 한 후, F5 키를 다시 한 번 눌러 이 변경사항을 반영시켜 보자. 그리고, SQL Server Management Studio 를 통해 변경된 사항을 확인해 보도록 하자.
위 이미지에서 볼 수 있다시피 ProductId와 OrderId가 Foreign Key로 지정이 되었고, 해당하는 키값은 테이블 스키마와 테이블 이름의 조합 – 여기서는 dbo.ProductOrders_dbo.Products_ProductId – 으로 이루어진 것을 볼 수 있다.
다음에는 Fluent API를 이용하여 테이블을 생성하는 방법을 알아보도록 하자.
AngularJS 디펜던시 인젝션(DI) 이해하기
객체지향 프로그래밍에서 Dependency Injection (DI) 개념은 아주 중요한데, 개별 객체들 사이에 의존성이 줄어들어야 – 다른 말로 느슨한 결합 (loosely coupled)을 이루거나 – 유지보수 및 확장성, 그리고 테스트 가용성 측면에서 많은 이득을 볼 수 있다. 일반적으로 Java 또는 C# 프로그래밍에서는 아래와 같은 형태로 DI를 구성한다.
public class ProductController : ApiController { public ProductController(IProductService service) { this._service = service; } private IProductService _service; public HttpResponseMessage Get(int id) { var product = this._service.GetProduct(id); return new HttpResponseMessage(200, product); } }
위의 코드는 C#으로 구현한 간단한 Web API 콘트롤러이다. 콘트롤러 인스턴스를 초기화할 때, 생성자의 파라미터로서 IProductService 인터페이스를 가진 인스턴스를 주입시킨다. 이렇게 하면 저 콘트롤러는 파라미터로 들어온 인스턴스가 어떻게 내부적으로 구현이 되는지 알 필요가 없다. 따라서, ProductService 클라스에 변경사항이 생겨도 아무런 문제가 되질 않는다.
이런 식으로 DI를 구성하는 것이 일반적인데, AngularJS에서는 독특한 방식으로 DI를 생성한다. 아무래도 자바스크립트라는 스크립트 언어의 특성 때문이 아닐까 싶기도 한데, 이부분은 여기서 논의할 것은 아니니 다른 기회를 이용하도록 하자. AngularJS에서 DI를 구현하는 방법은 상당히 다양하다. 우선 간단한 HTML 코드를 작성해 보자.
<html ng-app="diSample"> <body> <div ng-controller="sampleController"> <ul> <li>text: {{text}}</li> <li>di1text1: {{di1text1}}</li> <li>di1text2: {{di1text2}}</li> <li>di2text: {{di2text}}</li> <li>di3text: {{di3text}}</li> <li>di4text: {{di4text}}</li> <li>di5text: {{di5text}}</li> <li>di6text: {{di6text}}</li> <li>di7text: {{di7text}}</li> </ul> </div> </body> </html>
diSample이라는 모듈의 sampleController를 통해 총 아홉개의 데이터를 바인딩 시키는 모델이다. 저 콘트롤러를 담은 자바스크립트는 아래와 같다.
(function(angular) { var module = angular.module("diSample", []); module.controller("sampleController", function($scope) { $scope.text = "TEXT"; }); })(angular);
이렇게 하면 {{text}} 부분이 TEXT로 바뀌어 나오게 된다. 여기서 DI를 적용시켜보자. 우선 $provide 서비스를 이용하는 방법이다.
module.config(function ($provide) { $provide.provider("di1", function(){ this.$get = function(){ return function($scope, text) { $scope.di1text2 = "DI1 TEXT 2"; return text; }; }; }); });
$provide 서비스를 이용해서 di1이라는 인스턴스를 생성한다. 그리고 콘트롤러 선언부분을 아래와 같이 수정해 준다.
module.controller("sampleController", function($scope, di1) { $scope.text = "TEXT"; $scope.di1text1 = di1($scope, "DI1 TEXT 1"); });
이렇게 하면 콘트롤러에서 di1() 인스턴스를 호출하게 되면 그 결과가 $scope.di1text1과 $scope.di1text2에 반영되어 화면에 나타난다. 하지만, 이것보다 좀 더 간단한 방법으로 동일한 결과를 얻을 수 있다. 위의 module.config() 안쪽에 아래와 같은 코드를 넣는다.
$provide.factory("di2", function(){ return function(text) { return text; }; });
이것은 위의 $provide.provider() 펑션을 좀 더 간단하게 한 것으로 $provide.factory() 펑션을 쓰고 있다. 이것을 더욱 간단하게 하면 아래와 같이 $provide.value() 형태로도 쓸 수 있다.
$provide.value("di3", function(text) { return text; });
이제 콘트롤러를 아래와 같이 바꾸어보자.
module.controller("sampleController", function($scope, di1, d2, d3) { $scope.text = "TEXT"; $scope.di1text1 = di1($scope, "DI1 TEXT 1"); $scope.di2text = di2("DI2 TEXT"); $scope.di3text = di3("DI3 TEXT"); });
이렇게 콘트롤러를 변경한 후에 결과를 확인해 보면di1, di2, di3 객체가 어떻게 쓰였는지 알 수 있다. 심지어 이보다 더 간단하게 DI를 적용시킬 수도 있다. 위의 예제 코드는 모두 module.config() 안에 $provide 서비스를 포함시킨 후 그 스코프 안에서 DI를 위한 객체들을 생성시켜 놓는 것이었다면, 이것을 좀 더 간단하게 해서 module.provider(), module.factory(), module.value() 형태로도 사용할 수 있다. 아래 코드를 살펴보도록 하자.
// Using "provider" shortcut module.provider("di4", function(){ this.$get = function(){ return function(text) { return text; }; }; }); // Using "factory" shortcut module.factory("di5", function(){ return function(text) { return text; }; }); // Using "value" shortcut module.value("di6", function(text) { return text; });
차이점을 발견할 수 있는가? 이렇게 만들어 놓은 객체들을 다시 콘트롤러를 수정하여 적용시켜 보자.
module.controller("sampleController", function($scope, di1, d2, d3, d4, d5, d6) { $scope.text = "TEXT"; $scope.di1text1 = di1($scope, "DI1 TEXT 1"); $scope.di2text = di2("DI2 TEXT"); $scope.di3text = di3("DI3 TEXT"); $scope.di4text = di4("DI4 TEXT"); $scope.di5text = di5("DI5 TEXT"); $scope.di6text = di6("DI6 TEXT"); });
이렇게 콘트롤러를 수정한 후 결과를 보면 어떻게 달라졌는지 확인할 수 있을 것이다. 마지막으로 $injector 서비스를 이용하여 DI를 구현하는 방법이다. 위의 콘트롤러를 아래와 같이 수정하자.
module.controller("sampleController", function($scope, $injector, di1, d2, d3, d4, d5, d6) { $scope.text = "TEXT"; $scope.di1text1 = di1($scope, "DI1 TEXT 1"); $scope.di2text = di2("DI2 TEXT"); $scope.di3text = di3("DI3 TEXT"); $scope.di4text = di4("DI4 TEXT"); $scope.di5text = di5("DI5 TEXT"); $scope.di6text = di6("DI6 TEXT"); var di7 = $injector.get("di6") $scope.di7text = di7("DI7 TEXT"); });
이렇게 수정한 후에 다시 결과를 확인해 보도록 하자.
정리하자면, AngularJS에서 DI는 다양한 방법 – 여기서는 총 일곱가지 방법 – 으로 구현이 가능하다. 방법은 서로 다르지만 모두 동일한 결과를 가져온다. 그 중에서 가장 간결한 방법은 module.value() 방법이고, 가장 복잡한 방법은 module.config()에 $provide 서비스를 이용하여 provider 펑션을 호출하는 것이다. 간결할 수록 개발자가 콘트롤해야 하는 부분이 줄어들고, 복잡할 수록 개발자가 관여해야 하는 부분이 늘어난다. 요구사항의 복잡도에 따라 선택해서 사용하면 될 것이다.
이상으로 간단하게 AngularJS에서 DI를 활용하는 방법에 대해 논의해 보았다. 위의 코드는
http://jsfiddle.net/bluemood/QC9pW/
이곳에서 테스트해 볼 수 있다.
참고:
https://github.com/angular/angular.js/wiki/Understanding-Dependency-Injection
유튜브 동영상: http://youtu.be/1CpiB3Wk25U?t=37m
정규표현식 성능 향상 팁
데이터 웨어하우징에 ETL 프로세스는 반드시 필요하다. 이 과정에서 데이터 클렌징을 포함한 텍스트 프로세싱을 진행하게 되는데, 정규표현식은 이 텍스트 프로세싱의 핵심 요소들 중 하나이다. 일반적인 상황에서 정규표현식은 아래와 같은 형태로 사용한다.
var value = "abcdefg"; var pattern = @"^abc"; if (Regex.IsMatch(value, pattern)) { Console.WriteLine("Match found"); }
위의 예제와 같이 정규표현식은 정적 메소드인 Regex.Ismatch()의 형태로 쓰였다. 물론 아래와 같은 형태로 쓰일 수도 있다.
var value = "abcdefg"; var pattern = @"^abc"; var regex = new Regex(pattern); if (regex.IsMatch(value)) { Console.WriteLine("Match found"); }
위의 예제 코드는 정적 메소드인 Regex.IsMatch()를 사용하는 대신 Regex 인스턴스를 사용한다. 그렇다면 이 둘의 차이는 무엇일까? 바로 성능의 차이라고 할 수 있다. 컴파일 시점에 이미 정규표현식 객체를 포함하고 있는가, 런타임 시점에 그때그때 정규표현식 객체를 초기화 시켜 사용하는가의 차이라고도 할 수 있는데, 일반적인 용도로 사용한다면 두 가지 방법들 사이에는 큰 차이가 없다. 하지만, 대용량의 데이터를 처리하는데 있어서는 조그마한 차이가 엄청난 성능의 향상 혹은 저하를 가져올 수 있다.
Regex.IsMatch() 메소드는 정적 메소드로서 내부적으로 아래와 같은 형태로 구현된다.
public static IsMatch(string input, string pattern) { var regex = new Regex(pattern); return regex.IsMatch(input); }
즉, 정적 메소드를 호출할 때마다 Regex 인스턴스가 만들어지고, 쓰이고, 없어지기를 반복한다. 따라서, 동일한 반복작업을 하는 경우 동일한 Regex 인스턴스를 한 번 만들어두고 재활용을 한다면 엄청난 성능의 향상을 볼 수 있다. http://www.dotnetperls.com/regex-performance 에서는 컴파일을 하게 된다면 대략 30%의 성능 향상을 나타낸다고 한다.
개발자들의 세계에서는 If you are doing something repeatedly, you are doing it wrong 이라는 금언이 있다. 즉, 뭔가 동일한 작업을 반복적으로 한다면, 그건 뭔가 잘못된 것이라는 것이다. 결국 그부분에서 성능 향상을 꾀할 수 있다는 말과 동일하다. 위의 정규표현식 예제도 마찬가지로, 동일한 정규표현식을 여러번 사용한다면, 그것은 미리 컴파일을 해놓고 재활용할 수 있게끔 하는 것이 성능 향상에 유리하다는 말과 동일하다.
참조:
http://stackoverflow.com/questions/5854063/how-to-optimize-regular-expression-performance
http://stackoverflow.com/questions/414328/using-static-regex-ismatch-vs-creating-an-instance-of-regex
http://www.dotnetperls.com/regex-performance
http://blogs.msdn.com/b/bclteam/archive/2010/06/25/optimizing-regular-expression-performance-part-i-working-with-the-regex-class-and-regex-objects.aspx
http://blogs.msdn.com/b/bclteam/archive/2010/08/03/optimizing-regular-expression-performance-part-ii-taking-charge-of-backtracking.aspx
http://blogs.msdn.com/b/bclteam/archive/2011/03/28/optimizing-regex-performance-part-3-ron-petrusha.aspx
ASP.NET MVC 4 Web API 에서 null값 해결하기
ASP.NET MVC 4 Web API를 사용하면 RESTful 웹서비스를 손쉽게 해결할 수 있다. 그런데, 문제는 jQuery 또는 AngularJS 같은 자바스크립트 라이브러리를 통해 AJAX 콜을 이용하여 JSON 문자열을 Web API로 넘겨주게 되면, 특히 POST 혹은 PUT 메소드의 경우, Web API 콘트롤러에서 null값으로 떨어지는 경우를 보게 된다.
이것은 JSON 문자열을 파싱할 때 해당 자바스크립트 프레임웍이 갖는 특징으로, 약간의 부가적인 조치를 취해주면 해결할 수 있다.
var data = { "id": 1, "username": "abc", "email": "[email protected]" };
위의 JSON 객체를 jQuery AJAX 콜을 통해 서버에 전송하는 경우와 AngularJS의 AjAX 콜을 통해 서버에 전송하는 경우를 살펴보자.
// AJAX call using jQuery. $.ajax({ "type": "POST", "url": "/api/user", "dataType": "json", "data": JSON.stringify(data), "success": function(result) { // Do something } }); // AJAX call using AngularJS $http({ "method": "POST", "url": "/api/user", "data": JSON.stringify(data) }).success(function(result){ // Do something. });
Fiddler 또는 FireBug, 크롭 웹 개발자 도구 등을 통해 AJAX 리퀘스트 데이터를 보면 아래와 같다.
{ "id": 1, "username": "abc", "email": "[email protected]" }
그러나, 이 데이터는 Web API 콘트롤러를 통해 보면 null값으로 전송이 된다.
public HttpResponseMessage Post([FromBody]string value) { // Do something. }
Web API 콘트롤러의 Post 메소드는 대략 위와 같은 형태가 될텐데, 이 때 value값이 null이 되는 것이다. 그 이유는 JSON.stringify(data)된 데이터 앞에 반드시 =가 있어야 하는데, 그것이 없기 때문이다. 이것을 해결하기 위해 다음과 같이 처리한다.
// AJAX call using jQuery. $.ajax({ "type": "POST", "url": "/api/user", "dataType": "json", "data": { "": JSON.stringify(data) }, "success": function(result) { // Do something } });
jQuery 에서는 key 값이 없는 JSON 객체로 한 번 더 감싸주면 된다.
// AJAX call using AngularJS $http({ "method": "POST", "url": "/api/user", "data": "=" + JSON.stringify(data), "headers": { "Content-Type": "application/x-www-form-urlencoded" } }).success(function(result){ // Do something. });
AngularJS 에서는 단순히 =를 앞에 붙여주고, 헤더의 Content-Type을 기본값인 application/json에서 application/x-www-form-urlencoded으로 바꾸어준다. 그리고 나서 다시 Web API를 호출해 보면 정상적으로 JSON 스트링을 받아오는 것을 볼 수 있다.
참고: http://encosia.com/using-jquery-to-post-frombody-parameters-to-web-api
엔티티 프레임워크 Code First 방법론 #2
엔티티 프레임워크 Code First 방법론 #1
엔티티 프레임워크 Code First 방법론 #2
데이터 타입 설정하기
앞서 Local DB에 테이블을 생성하는 방법까지 알아 보았다. 만들어진 테이블은 아래와 같다.
여기서 눈여겨 봐야 할 것은 각각의 필드 데이터 타입 및 크기이다.
NULL vs NOT NULL
NVARCHAR(MAX)
decimal 데이터 타입 크기
int, datetime
앞의 글에서 언급한 바와 같이 엔티티 이름 + Id 형태의 프로퍼티가 있을 경우 해당 프로퍼티는 자동으로 PK 처리가 된다. int, bool, DateTime 같은 경우에는 기본적으로 Nullable 속성을 갖고 있지 않으므로 테이블 생성시에도 마찬가지로 자동으로 NOT NULL 속성을 갖는다. 반면에 string 데이터 타입의 경우에는 NULL을 허용하므로 테이블 필드 역시 NULL로 설정한다. 만약 int, bool, DateTime 데이터 타입을 갖는 필드에 NULL 값을 허용하려면 프로퍼티 설정시 int?, bool?, DateTime?과 같은 형태로 Nullable 속성을 추가해 주면 된다. 또한 닷넷 코드는 기본적으로 유니코드를 지원하므로 string 데이터 타입은 곧바로 nvarchar 타입으로 대응하게 된다.
그렇다면, 바로 이 string 데이터 타입의 크기는 어떻게 조정을 할까? 별다른 설정이 없을 경우에는 무조건 MAX값을 갖는데, 일반적으로 테이블의 필드는 nvarchar 타입이라 하더라도 최대 크기를 정해놓는다. 이렇게 데이터의 크기를 결정해주는 방법은 크게 두가지 방식이 있다.
Data Annotations
Fluent API
여기서는 먼저 첫번째 Data Annotations 방식을 다루기로 하고 다음에 Fluent API를 논의하도록 하자.
Data Annotations
Data Annotation을 위해서는 각각의 클라스명 또는 프로퍼티명에 속성 클라스를 지정해 준다. 앞서 생성했던 엔티티들을 아래와 같이 바꾸어 보도록 하자.
[Table("ProductInfo")] public class Product { [Key] public int ProductId { get; set; } [Required(ErrorMessage="ProductName must be set")] [MaxLength(128, ErrorMessage="ProductName is too long")] [MinLength(4, ErrorMessage="ProductName is too short")] public string ProductName { get; set; } [Column(Name="Description", TypeName="NTEXT")] public string ProductDescription { get; set; } [NotMapped] public string ProductAlias { get; set; } [Required(ErrorMessage = "UnitPrice must be set")] public decimal UnitPrice { get; set; } [Required(ErrorMessage = "DateCreated must be set")] public DateTime DateCreated { get; set; } } public class Order { [Key] public int OrderId { get; set; } [Required(ErrorMessage = "DateOrdered must be set")] public DateTime DateOrdered { get; set; } [Required(ErrorMessage = "OrderBy must be set")] public int OrderBy { get; set; } } public class ProductOrder { [Key] public int ProductOrderId { get; set; } [Required(ErrorMessage = "ProductId must be set")] public int ProductId { get; set; } [Required(ErrorMessage = "OrderId must be set")] public int OrderId { get; set; } [Required(ErrorMessage = "AmountOrdered must be set")] public int AmountOrdered { get; set; } }
Data Annotation을 위해서는 크게 두가지 속성 클라스가 있다. 하나는 Validation 속성 클라스이고 다른 하나는 Annotation 속성 클라스이다. 위의 코드에서 Required, MaxLength, MinLength 등과 같은 속성 클라스들이 Validation 속성 클라스이고, Key, NotMapped, Table, Column 등과 같은 속성 클라스들을 가리켜 Annotation 속성 클라스라고 부른다. 이밖에도 더 많은 속성 클라스들이 있으니 자세한 사항은 MSDN을 참고하도록 하자.
이렇게 속성클라스들을 생성하고 난 후에 F5 키를 눌러 한 번 디버깅을 한 후 MS SQL Server Management Studio로 접속해서 테이블 구조를 확인해 보면 아래와 같이 변한 것을 확인할 수 있다.
Products 테이블의 이름이 ProductInfo로 바뀌었다
ProductName 컬럼의 데이터 크기가 MAX에서 128로 바뀌었다.
ProductDescription 컬럼명이 Description으로 바뀌었다.
ProductDescription 데이터 타입이 NTEXT로 바뀌었다.
다음에는 각각의 테이블마다 FK를 설정하는 방법에 대해 논의해 보도록 하자.
엔티티 프레임워크 Code First 방법론 #1
엔티티 프레임워크 Code First 방법론 #1
엔티티 프레임워크 Code First 방법론 #2
엔티티 프레임워크(Entity Framework, EF)가 가진 수많은 장점들 중 하나는 데이터베이스로부터 직접 ORM 매핑 클라스를 생성해 준다는 데 있다. 데이터베이스 연결을 위한 로그인 정보만 지정해주면 해당 데이터베이스의 모든 테이블, 스토어드 프로시저, 함수 등을 모두 객체화하여 손쉽게 코드에서 사용할 수 있게 해주는 것이다. 하지만, 이 방법의 문제점들 중 하나는 데이터베이스 스키마를 갱신한 후 EF에서 해당 갱신 내역을 업데이트하면, 수동으로 설정한 부분들은 모두 사라지게 된다. 따라서, 이런 문제점을 해결하기 위해서는 보통 두가지 방법 중 하나를 사용한다.
partial 클라스를 이용하여 수동 변경 사항 분리시키기
EF Code FIrst 방법 적용하기
여기서는 두번째 Code First 방법에 대해 논의해 보고자 한다. EF Code First 방법은 보통 아래와 같은 순서를 거쳐 적용할 수 있다.
데이터베이스 Connection String 설정하기
우선 web.config 혹은 app.config 파일의 <connectionStrings> 섹션을 아래와 같이 작성한다.
<connectionStrings> <clear /> <add name="ApplicationDataContext" connectionString="Data Source=(LocalDB)\v11.0;Initial Catalog=ApplicationDatabase;Persist Security Info=True;Integrated Security=True;MultipleActiveResultSets=True;Connect Timeout=30" providerName="System.Data.SqlClient" /> </connectionStrings>
위의 내용은 Visual Studio 2012부터 사용할 수 있는 Local DB를 이용하여 데이터베이스를 만드는 Connection String이다. 서버명은 (LocalDB)\v11.0, 데이터베이스명은 ApplicationDatabase, 그리고 Windows 통합 인증 방법을 사용하는 것으로 설정해 두었다. 이 Connection String을 좀 더 보기 쉽게 설정하 싶다면 GitHub에 올라가 있는 오픈소스들 중 하나인 Data Access Framework 라이브러리를 참조할 수도 있다.
DbContext 클라스 상속 받기
Connection String 설정이 끝났다면, 새로운 ApplicationDataContext 클라스를 하나 생성한다. 이 클라스는 DbContext 클라스를 상속받아 사용한다.
public partial class ApplicationDataContext : DbContext { #region Constructors public ApplicationDataContext() : base("ApplicationDatabase") { ... } public ApplicationDataContext(string connectionString) : base (connectionString) { ... } #endregion }
이렇게 작성한 ApplicationDataContext는 아래와 같이 사용할 수 있다.
using (var context = new ApplicationDataContext()) { ... }
Constructor 파라미터 없이 직접 Context를 생성하는 것과, Connection String 파라미터를 받아 생성하는 것, 이렇게 두가지가 있다. 파라미터 없이 직접 Context를 생성하는 경우, 디폴트로 ApplicationDataBase를 이용한다. 만약, 지정한 데이터베이스 서버에 해당하는 이름이 없을 경우 새롭게 데이터베이스를 생성하게 된다.
public partial class ApplicationDataContext : DbContext { ... #region Properties public DbSet<Product> Products { get; set; } public DbSet<Order> Orders { get; set; } public DbSet<ProductOrder> ProductOrders { get; set; } #endregion }
엔티티 추가하기
데이터베이스 설정이 끝났다면, 실제로 사용할 테이블을 지정해 주어야 한다. 위와 같이 Products 테이블과 Orders 테이블, 그리고 ProductOrders 테이블이 있다고 가정한다면 해당 엔티티 클라스를 아래와 같이 추가한다.
public partial class Product { public int ProductId { get; set; } public string ProductName { get; set; } public string ProductDescription { get; set; } public decimal UnitPrice { get; set;} public DateTime DateCreated { get; set; } } public partial class Order { public int OrderId { get; set; } public DateTime DateOrdered { get; set; } public int OrderBy { get; set; } } public partial class ProductOrder { public int ProductOrderId { get; set; } public int ProductId { get; set; } public int OrderId { get; set; } public int AmountOrdered { get; set; } }
이렇게 Product, Order, ProductOrder 엔티티를 작성한 후 F5 키를 눌러 디버깅을 한 번 시도한다. 그리고 나서 Microsoft SQL Server Management Studio를 이용해 LocalDB로 접속해 보면 실제로 아래 그림과 같이 ApplicationDatabase 데이터베이스에 Products, Orders, ProductOrders 테이블이 만들어져 있는 것을 확인할 수 있다.
여기서 주목해야 할 부분이 하나 있는데, ProductId, OrderId, ProductOrderId는 모두 특별한 설정 없이도 Primary Key로 되어 있다. 이는 클라스의 이름에 Id가 붙으면 자동으로 PK로 인식하게끔 하는 EF의 자동 매핑 기능이다.
다음에는 이 엔티들 사이에 관계를 설정하는 것에 대해 논의해 보도록 하자.
Entity Framework 커넥션 스트링 가변적으로 구성하기
Entity Framework (EF) 은 닷넷 어플리케이션 개발시 사용할 수 있는 ORM 도구들 중 하나이다. 다른 ORM 도구들에 비해 러닝커브도 적을 뿐 아니라 사용이 꽤 직관적이기 때문이다. 다만, 한가지 불편한 점이 있다면 데이터베이스 커넥션 스트링이 너무 길다는 것. 보통 web.config 혹은 app.config에 들어가는 EF 커넥션 스트링은 대략 아래와 같은 형태이다.
<connectionStrings> <add name="ApplicationDataContext" connectionString="metadata=res://*/ApplicationDataContext.csdl|res://*/ApplicationDataContext.ssdl|res://*/ApplicationDataContext.msl;provider=System.Data.SqlClient;provider connection string="data source=(LocalDB)\v11.0;attachdbfilename=|Data Directory|AdventureWorks.mdf;UserId=username;Password=passwordintegrated security=False;connect timeout=30;MultipleActiveResultSets=True;App=EntityFramework"" providerName="System.Data.EntityClient" /> </connectionStrings>
이렇게 너무 커넥션 스트링 부분이 너무 길다보니 한눈에 들어오지도 않을 뿐더러, 상황에 따라 적절하게 서버가 바뀐다거나 데이터베이스가 바뀐다거나 하는 경우에는 수정하기가 쉽지 않다. 하지만, .edmx 파일을 이용해 EF를 구성할 경우 세 가지의 서로 다른 인자를 받아들이는 생성자가 생기는데, 그중 하나는 이 커넥션 스트링을 유연하게 구성할 수 있는 방법을 제시한다. 아래는 .edmx 파일을 이용하여 생성한 EF 데이터 콘텍스트의 생성자들이다.
public partial class ApplicationDataContext : DbContext { // Initialises a new instance of the ApplicationDataContext object. public ApplicationDataContext() { ... } // Initialises a new instance of the ApplicationDataContext object // with the given connection string. public ApplicationDataContext(string connectionString) { ... } // Initialises a new instance of the ApplicationDataContext object // with the given entity connection instance. public ApplicationDataContext(EntityConnection conn) { ... } }
맨 아래 생성자를 보면 EntityConnection 인스턴스를 인자로 받아 EF 데이터 콘텍스트를 생성하는 것을 볼 수 있다. 바로 이 생성자를 이용하여 상황에 따라 유연하게 커넥션 스트링을 작성할 수 있다. 아래 코드는 이 방법을 이용하는 테스트 케이스 메소드이다.
[Test] [TestCase("serverName", "dbName", "username", "password", "System.Data.SqlClient", true)] public void TestDatabaseConnection_SendParameters_GetDatabaseConnected(string serverName, string dbName, string username, string password, string provider, bool connected) { var sqlBuilder = new SqlConnectionStringBuilder(); sqlBuilder.DataSource = serverName; sqlBuilder.InitialCatalog = dbName; sqlBuilder.Username = username; sqlBuilder.Password = password; sqlBuiler.IntegratedSecurity = false; var efBuilder = new EntityConnectionStringBuilder(); efBuilder.Provider = provider; efBuilder.ProviderConnectionString = sqlBuilder.ToString(); efBuilder.MetaData = String.Format(@"res://*/{0}.csdl|res://*/{0}.ssdl|res://*/{0}.msl", "ApplicationDataContext"); using (var conn = new EntityConnection(efBuilder.ToString())) { conn.Open(); Assert.AreEqual(connected, conn.State == ConnectionState.Open); conn.Close(); } }
위의 코드에서 알 수 있다시피,
SqlConnectionStringBuilder 인스턴스를 통해 기본적인 커넥션 스트링을 만들고,
그것을 다시 EntityConnectionStringBuilder 인스턴스로 한 번 더 감싸준 후에,
이것을 EntityConnection 객체로 보내 데이터베이스 커넥션을 완성한다.
이렇게 만들어진 EntityConnection 인스턴스는 맨 위의 ApplicationDataContext(EntityConnection conn) { ... } 생성자의 인자로 쓰여 데이터베이스 트랜잭션을 위한 EF 인스턴스 초기화를 가능하게 한다.
위의 테스트 코드에서 알 수 있다시피, web.config 또는 app.config 사용시 굳이 기나긴 커넥션 스트링을 사용하는 것 보다는 의미있는 데이터 – 서버명, DB명, 유저네임, 패스워드 등 – 를 <appSettings> 섹션에 넣어두고 그것을 상황에 따라 가변적으로 호출하여 커넥션을 만드는 것이 개발자 관점에서는 더욱 수월한 일이 아닐까 한다.
참조: EntityConnectionStringBuilder Class
CQRS 초간단 정리
CQRS (Command Query Responsibility Segregation) 은 수시로 확장이 용이한 대규모 엔터프라이즈 환경 혹은 클라우드 환경에서 사용하는 아키텍처 패턴 중 하나.
아주아주 간단한 설명은 아래와 같다.
In an oversimplified manner, CQRS separates commands (that change the data) from the queries (that read the data).
– Rinat Abdullin from CQRS Starting Page
간단하게 번역을 해보자면
CRQS는 (데이터를 변경하는) Command 부분을 (데이터를 읽어들이는) Query 로부터 분리시킨다
라는 것이다. 자세하게 들어가자면 한도 끝도 없지만, 일단 수박 겉핥기 식으로 간단한 이해를 해보도록 하자.
대부분의 데이터베이스 트랜잭션은 데이터베이스로부터 데이터를 읽어들여 화면에 뿌려주는 것이다. 이때, 데이터베이스에서 데이터를 읽는 시점과 화면에 렌더링을 하는 시점은 반드시 차이가 생기기 마련이며, 렌더링하는 데이터는 이미 실제 데이터와는 차이가 생기게 마련이다. CQRS 패턴은 이점을 인정하고 여기서부터 시작한다. 따라서, 굳이 하나의 데이터베이스 안에서 CRUD의 R에 해당하는 기능과 나머지 CUD 기능을 공존시키는 것이 의미가 없다는 것이 이 패턴의 핵심. 어차피 R의 결과물은 정도의 차이는 있을지언정 실제 데이터와 다르니 캐쉬로 돌려서 더욱 빠르게 사용자들이 읽어들일 수 있도록 하고, CUD는 메시지 큐를 통해 실제 데이터를 변경시키고, 그 변경이 일어나는 시점에 이벤트를 발생시켜서 캐쉬를 업데이트하는 방식으로 진행하자는 것.
일반적인 시스템에서는 굳이 CQRS 패턴을 적용시킬 필요 까지는 없지만, Windows Azure 라든가 AWS 같은 클라우드 기반 서비스를 이용하면서 Scalability가 시스템의 핵심 요소로 작용할 땐 반드시 고려해야 하는 것들 중 하나.
CQRS 패턴과 관련한 내용은 검색능력이 딸려서 그런지 한국어 관련 내용이 거의 없지 싶다. 가장 추천할만한 링크는 위 인용구문의 당사자인 Rinat Abdullin이 운영하는 웹사이트의 CQRS 섹션인 CQRS Starting Page 이곳이다. 다들 여기서부터 출발점을 삼으라고 추천하더군.
이외에 다른 읽어볼만한 링크들은 아래와 같다.
CQRS Starting Page
Clarified CQRS
CQRS on Windows Azure
CQRS Journey
유연한 코드를 위해 HttpContext 대신 HttpContextBase 사용하기
닷넷 기반 웹사이트 개발시 항상 쓰는 객체는 HttpContext 객체이다. 서버 요청, 서버 응답, 현재 사용자, 세션, 쿠키 등등... 이 HttpContext 객체가 담당하는 일은 무궁무진하다. 하지만, 이 객체는 HttpContext.Current의 싱글톤 인스턴스로만 사용이 가능한데, 이것은 구상 클라스(Concrete Class)여서 단위 테스트를 할 때에는 사용을 할 수가 없다. 이 문제를 해결하고자 닷넷 프레임웍 3.5부터 새롭게 나타난 것이 바로 HttpContextBase라는 추상 클라스(Abstract Class)이다.
이 HttpContextBase라는 추상 클라스를 통해 단위 테스트라든가 테스트 주도 개발 방법론(TDD)에서 주로 사용하는 Mocking을 자유롭게 구현할 수 있다. 아래 코드를 보면 대략의 감을 잡을 수 있을 것이다.
HttpContextBase contextBase = new HttpContextWrapper(HttpContext.Current);
위의 예제 코드는 HttpContextWrapper 클라스를 이용하여 HttpContextBase 객체를 리턴하는 것이다. 실제 동작하는 코드는 이렇게 작성할 수 있고, 단위 테스트에서는 아래와 같이 작성할 수도 있다. Nunit과 NSubstitue를 사용한다고 가정하자.
public class HomeControllerTest { private HttpContextBase _context; [SetUp] public void Init() { this._context = Substitute.For<HttpContextBase>(); ... } ... [Test] public void Test() { var controller = new HomeController(this._context); var result = controller.Index(); ... } }
위의 테스트 클라스를 보면 HttpContextBase를 mocking 하여 콘트롤러에 Dependency Injection을 시키는 것을 볼 수 있다. 만약 Unity라든가 Autofac 같은 IoC 콘테이너를 사용하게 된다면 HttpContextBase역시도 직접 사용하는 것보다는 IHttpContextBaseWrapper 같은 형태로 한 번 더 감싸서 사용하면 된다.
참고: HttpContext vs HttpContextBase vs HttpContextWrapper
크로스 브라우징을 위한 HTML5 Boilerplate + 추가 스크립트
HTML5 Boilerplate (H5BP)는 프론트엔드 개발자들이 웹페이지를 제작하는데 필요한 베스트 프랙티스들을 모아놓은 템플릿이다. 다양한 브라우저 환경, 특히 구버전의 인터넷 익스플로러(IE)에서 HTML5 + CSS3 조합의 풍부한 기능을 이용하기를 원한다면 이 H5BP의 사용은 필수불가결하다 할 수 있다.
하지만, H5BP가 만능은 아닌 것이, 다양한 브라우저를 지원하는 웹페이지를 제작하는데 꼭 필요한 최소한의 장치들만을 포함하고 있기 때문에 개발 환경에 따라 몇가지를 추가해야 하는 것은 분명하다. 따라서, 이 포스트에서는 H5BP에 추가적으로 어떤 것들이 더 필요한지에 대해 논의해 보기로 한다.
HTML 문서 기본 골격 구조
우선, 기본적인 H5BP에서 제안하는 HTML 문서의 골격구조부터 살펴보자. 아래의 내용은 H5BP 리포지토리의 index.html이다. (https://raw.github.com/h5bp/html5-boilerplate/master/index.html)
<!DOCTYPE html> <!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]--> <!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]--> <!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]--> <!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]--> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title></title> <meta name="description" content=""> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- Place favicon.ico and apple-touch-icon.png in the root directory --> <link rel="stylesheet" href="css/normalize.css"> <link rel="stylesheet" href="css/main.css"> <script src="js/vendor/modernizr-2.6.2.min.js"></script> </head> <body> <!--[if lt IE 7]> <p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p> <![endif]--> <!-- Add your site or application content here --> <p>Hello world! This is HTML5 Boilerplate.</p> <script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script> <script>window.jQuery || document.write('<script src="js/vendor/jquery-1.10.2.min.js"><\/script>')</script> <script src="js/plugins.js"></script> <script src="js/main.js"></script> <!-- Google Analytics: change UA-XXXXX-X to be your site's ID. --> <script> (function(b,o,i,l,e,r){b.GoogleAnalyticsObject=l;b[l]||(b[l]= function(){(b[l].q=b[l].q||[]).push(arguments)});b[l].l=+new Date; e=o.createElement(i);r=o.getElementsByTagName(i)[0]; e.src='//www.google-analytics.com/analytics.js'; r.parentNode.insertBefore(e,r)}(window,document,'script','ga')); ga('create','UA-XXXXX-X');ga('send','pageview'); </script> </body> </html>
짧은 지식으로는 <meta> 태그와 <link> 태그 들은 자체적으로 닫는 태그를 갖고 있어야 하는 것으로 알고 있다. 따라서, 이부분은 <meta ... />, <link ... /> 형태로 바꾸어 주는 것이 좋다.
기본 문자셋
<meta charset="utf-8" />
유니코드 문자셋으로 웹페이지를 제공하는 것이 좋다.
IE 호환성 모드 메타 태그
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
이 내용은 사용자가 IE를 사용해서 페이지를 열었을 때, 해당 IE가 지원할 수 있는 최신의 호환성 모드로 페이지를 렌더링하게끔 하는 것이다. 하지만, 이 메타 태그는 인트라넷 상에서는 해석할 수가 없기 때문에 웹서버에서도 자체적으로 커스텀 헤더 안에 위의 호환성 모드를 뿌려주는 것이 좋다. 이에 덧붙여 만약 사용자가 IE 크롬 익스텐션을 사용할 경우에는 아래와 같이 수정하여 크롬 익스텐션으로 렌더링하는 것을 강제하게끔 할 수도 있다.
<meta http-equiv="X-UA-Compatible" content="IE=edge, chrome=1" />
파비콘
다양한 사이즈의 파비콘을 준비해 두면 다양한 디바이스에서 파비콘들의 모양이 깨지는 현상을 방지할 수 있다. H5BP의 목적에 맞게끔 다양한 사이즈의 파비콘을 생성해 주는 사이트는 http://iconifier.net과 같은 것들이 있다.
CSS 정규화
<link rel="stylesheet" href="css/normalize.css" />
웹브라우저들마다 저마다의 방식으로 기본 렌더링 모드를 제공하고 있다. 따라서, 가급적이면 브라우저 고유의 렌더링 모드 대신 이렇게 정규화시킨 CSS를 이용하여 브라우저에 상관없이 동일한 렌더링을 제공하는 것이 좋다. 참고로, 트위터 부트스트랩과 동시에 사용하는 경우에는 이미 동일한 내용의 정규화 CSS가 들어 있기 때문에 필요없다.
Modernizr – HTML5 엘리먼트 폴리필
IE8 이하 버전에서는 HTML5에서 새롭게 제공하는 태그들을 인식할 수 없다. 따라서, 이를 인식시키는 작업이 필요한데, 이것을 이 modernizr가 담당한다. html5shiv.js로도 충분히 가능하지만, modernizr는 이를 포함한 다양한 기능들을 포함하고 있으므로 이것을 사용하는 것이 좋다. 참고로, modernizr는 <head> 태그 안에서 로딩하는 유일한(?) 자바스크립트이다.
이상으로 H5BP에서 제공하는 템플릿에 대해 간략하게 확인해 봤다. 이어서 아래에 언급하는 부분은 H5BP에 부가적으로 추가하면 좋을 내용들이다.
jQuery
자바스크립트 프레임웍의 대표주자. 굳이 없어도 괜찮지만, 어지간하면 로드하는 것이 좋다. IE9 이하 버전을 지원하기 위해서는 jQuery 1.x 버전을 사용한다. 만약 트위터 부트스트랩을 사용한다면, jQuery 로딩은 필수.
Placeholders.js
IE9 이하의 브라우저에서는 HTML5에서 새롭게 제공하는 <input> 태그의 placeholder를 제대로 인식하지 못한다. 이를 위해 Placeholders.js(http://jamesallardice.github.io/Placeholders.js) 라는 폴리필을 로드한다. 이와 더불어 Placeholders.js의 기능을 보완해주는 Placeholders.js Monkey Patch(https://github.com/aliencube/Placeholders.js-Monkey-Patch)도 함께 로드한다. 참고로 Placeholders.js Monkey Patch는 jQeury가 있어야만 실행 가능하다.
EcmaScript 5 지원
<!--[if lte IE 8]> <script type="text/javascript" src="js/es5-shim.min.js"></script> <![endif]-->
IE8에서 사용하는 자바스크립트 엔진은 오래된 것이라서 EcmaScript 5를 지원하지 못한다. EcmaScript 5에서는 Array와 관련해서 향상된 기능을 제공하므로 이를 인식시켜주는 확장 라이브러리가 필요한데, 이 때 es5-shim.js(https://github.com/kriskowal/es5-shim)가 필요하다.
linq.js
<script type="text/javascript" src="js/linq.min.js"></script>
만약 .NET의 LINQ에 익숙하다면 linq.js(http://linqjs.codeplex.com)를 설치해 보는 것도 좋다.
이상으로 H5BP 템플릿에 덧붙여 함께 사용하면 좋을만한 자바스크립트 라이브러리들을 나열해 봤다. 이는 물론 최소한으로 필요한 것들이고 프로젝트의 성격마다 더 많은 것들을 추가할 수 있을 것이다.
Placeholders.js Monkey Patch 해설
이전 글인 IE8 에서 input 태그와 textarea 태그에 placeholder 속성 적용하기에서 Placeholders.js를 이용하면 jquery.placeholder 플러그인보다 여러모로 사용이 편리하다고 언급한 적이 있다.
그런데, 이 Placeholderes.js 역시도 다른 DOM 엘리먼트들의 이벤트들에서는 기대하는 대로 작동하지 않는 경우가 있다. 이런 경우를 보정하기 위해서 Placeholders.js Monkey Patch를 만들어서 배포하게 됐다. Placehoders.js는 모든 input:text 엘리먼트와 textarea 엘리먼트에 몇가지 비표준 속성을 추가하는 폴리필 (polyfill) 형태로 이루어진다. 그중에 몇가지 예를 들자면 아래와 같은 것들이 있다.
data-placeholder-value
data-placeholder-active
data-placeholder-bound
이 세가지가 input:text 엘리멘트에 쓰이는 대표적인 폴리필 속성들이다. IE8에서 Placeholders.js를 적용한 페이지를 열어보면 아래와 같은 형태로 보이는 것을 알 수 있다.
<input type="text" id="username" class="placehldersjs" name="username" placeholder="Username" data-placeholder-value="Username" data-placeholder-bound="true" data-placeholder-active="true" value="Username" />
Placeholders.js는 placeholder 속성을 사용할 수 없기 때문에, placeholder 속성값을 대신 value 속성값에 넣어주고, 그것을 placeholdersjs 라는 CSS 클라스를 이용해 색상을 조정한다. placeholdersjs 클라스의 기본값은 #ccc이다. 물론 해당하는 input 엘리먼트에 이미 값이 있다면 아래와 같은 형태로 나타나게 된다.
<input type="text" id="username" name="username" placeholder="Username" value="joebloggs" />
그런데, 문제는 다른 DOM 엘리먼트들과 상호작용을 하면서 이 폴리필들이 제대로 작동을 하지 않는 경우가 있다. 예를 들어서 드롭다운리스트에서 값을 바꿀 때와 같은 현상들이 바로 그것인데, 그럴 때 이 몽키 패치가 역할을 하게 된다. 일반적으로 이 Placeholders.js 자바스크립트는 IE9 이하에서만 적용시키면 되기 때문에 아래와 같은 형태로 호출한다.
<!--[if lte IE 9]> <script type="text/javascript" src="js/Placeholders.min.js"></script> <![endif]-->
마찬가지로 몽키 패치 역시도 아래와 같은 형태로 호출을 하면 다른 브라우저 혹은 IE10 이상에서는 영향을 받지 않는다.
<!--[if lte IE 9]> <script type="text/javascript" src="js/jquery.Placeholders.monkey.patch.js"></script> <![endif]-->
input:text와 textarea 엘리먼스 속성 재설정
이렇게 호출을 해놓은 상태에서는 몽키패치에서 선언한 함수들은 오로지 IE9 이하에서만 사용할 수 있게 된다. 몽키 패치에서 선언한 함수들은 Placeholders.js에서 정의한 속성과 CSS 클라스가 제대로 작동하고 있는지 검증하는 역할을 한다. 예를 들어 #select-box라는 id 값을 갖는 드롭다운리스트가 있다고 가정을 할 때 $("#select-box").change() 또는 $("#select-box").click()의 콜백 함수를 이용하여 검증을 하는 것이다.
$("#select-box").change(function () { if (typeof (applyPlaceholderAttributes) == typeof (Function)) { $("input:text, textarea").each(function () { applyPlaceholderAttributes($(this)); }); } });
이런 식으로 applyPlaceholderAttributes() 함수를 호출하면 알아서 보정을 해준다. 여기서 if (typeof(applyPlaceholderAttributes) == typeof(Function)) { ... } 블록은 IE9 이하에서 이 몽키 패치를 로드했을 경우에는 true, 아니면 false 값을 갖는다.
만약 조금 더 구체적으로 input 엘리먼트들의 범위를 지정하고 싶다면 몽키 패치를 호출할 때 아래와 같은 형태로 호출하면 된다.
<!--[if lte IE 9]> <script type="text/javascript" src="js/jquery.Placeholders.monkey.patch.js"></script> <script type="text/javascript"> Placeholders.For = { "inputs": ["input:text", "textarea"], "fakePasswords": ["#fake-password"], "forms": ["form"] }; </script> <![endif]-->
Placeholders.For 라는 JSON 객체를 선언해서 input 엘리먼트들의 범위를 정할 수 있다. 그렇게 한 후 드롭다운리스트의 이벤트 역시도 아래와 같이 살짝 바꿔주면 끝.
if (typeof (applyPlaceholderAttributes) == typeof (Function)) { $.each(Placeholders.For.inputs, function (i, element) { $(element).each(function () { applyPlaceholderAttributes($(this)); }); }); }
input:password 속성 재설정
Placeholders.js의 가장 치명적인 단점(?)은 IE8 이하에서는 패스워드 필드가 placeholder 값이 보이지 않고 ********와 같이 실제로 패스워드를 입력한 것처럼 보인다는 것이다. IE9 이상에서는 input 엘리먼트의 type 속성을 바꿀 수 있어서 Placeholders.js 내부적으로 password 타입에서 text 타입으로 바꾸어서 placeholder 속성값을 보여주다가 실제 패스워드 입력을 위해 포커스를 이동시키면 다시 text 타입에서 password 타입으로 바꾸는 기능을 한다.
하지만, IE8 이하에서는 이렇게 type을 바꿀 수 없기 때문에 이것이 불가능하다. 몽키 패치는 IE8을 위해 #fake-password라는 텍스트 필드를 준비해 놓고 사용자가 패스워드 입력을 위해 포커스를 이동시키면 원래 패스워드 필드를 보여주고, 포커스를 벗어나면 패스워드 필드를 감추고 fake password 필드를 보여주는 형식으로 처리하게끔 했다. 물론, 이를 위해 다음과 같은 부가적인 마크업이 필요하다.
<!--[if lte IE 8]> <input type="text" id="fake-password" name="fake-password" placeholder="Password" data-placeholder-password="password" required="required" style="display: none;" /> <![endif]-->
이렇게 해놓으면 IE8 이하의 브라우저에서만 위의 내용을 로드하게 되고 이것이 기존의 패스워드 필드를 대체하는 역할을 한다,
form 전송
Placeholders.js의 치명적인 문제점들 중 하나는 input 엘리먼트의 value 속성을 이용하다보니, 서버로 이 placeholder 값들을 전송한다는 것에 있다. 따라서, 폼 전송 직전에 이러한 불필요한 placeholder 값들을 제거할 필요가 있다. 사실, 이부분은 Placeholders.For JSON 객체에 적용시키고자 하는 form 엘리먼트들을 지정해 놓으면 알아서 불필요한 값들을 걷어내 준다.
이렇게 Placeholders.js Monkey Patch의 작동원리에 대해 간단하게 설명을 해 보았다.
닷넷의 LINQ 기능을 자바스크립트로 이용하기
LINQ (Language Integrated Query)는 닷넷 개발시 가장 강력한 기능들 중의 하나이다. LINQ-to-SQL, LINQ-to-XML, LINQ-to-OBJECT 등 거의 모든 상황에서 LINQ를 사용할 수 있다. LINQ는 보통 아래와 같은 형태로 많이 쓰인다.
var products = (from p in context.Products where p.ProductName.Contains("ABC") select p).ToList();
또는
var products = context.Products .SingleOrDefault(p => p.ProductId == 123);
하지만 이것의 가장 큰 단점(?)이라면, 프론트엔드에서 쓰이는 자바스크립트로는 이러한 기능을 사용할 수 없다는 데 있다. 그래서 나타난 것이 바로 linq.js이다.
linq.js 소개
이 자바스크립트 프레임웍을 사용하면, LINQ에서 제공하는 대부분의 기능들을 자바스크립트 상에서도 거의 동일한 문법으로 사용할 수 있다. 또한, 이 linq.js는 jQuery 플러그인도 제공하므로, 더더욱 편리하게 쓸 수 있다.
이 linq.js를 사용하기 위해서는 우선 라이브러리를 불러들여야 한다. 기왕이면 jQuery를 호출한 후에 이 linq.js를 호출하도록 하자.
<script src="/path/to/linq.js"></script> <script src="/path/to/linq.jquery.js"></script>
이렇게 호출해 놓으면 그냥 사용이 가능하다. 대략의 사용 방법은 아래와 같다.
var jsonArray = [ { "user": { "id": 100, "screen_name": "d_linq" }, "text": "to objects" }, { "user": { "id": 130, "screen_name": "c_bill" }, "text": "g" }, { "user": { "id": 155, "screen_name": "b_mskk" }, "text": "kabushiki kaisha" }, { "user": { "id": 301, "screen_name": "a_xbox" }, "text": "halo reach" } ]; // ["b_mskk:kabushiki kaisha", "c_bill:g", "d_linq:to objects"] var queryResult = Enumerable.From(jsonArray) .Where(function (x) { return x.user.id < 200 }) .OrderBy(function (x) { return x.user.screen_name }) .Select(function (x) { return x.user.screen_name + ':' + x.text }) .ToArray();
조금 더 편하게 람다 함수를 이용한다면 아래와 같이 사용할 수도 있다. 위와 아래는 동일한 결과를 가져온다.
// shortcut! string lambda selector var queryResult2 = Enumerable.From(jsonArray) .Where("$.user.id < 200") .OrderBy("$.user.screen_name") .Select("$.user.screen_name + ':' + $.text") .ToArray();
마찬가지로 jQuery 플러그인을 사용한다면 더욱 간결한 표현을 쓸 수 있다.
// $.Enumerable: // 알럿 메시지로 짝수만 보여준다. 총 다섯번. $.Enumerable.Range(1, 10).Where("$%2==0").ForEach("alert($)"); // TojQuery - Enumerable to jQuery // #select1 이라고 하는 ID를 가진 드롭다운리스트에 옵션을 1부터 10까지 추가한다. $.Enumerable.Range(1, 10) .Select(function (i) { return $("<option>").text(i)[0] }) .TojQuery() .appendTo("#select1"); // toEnumerable - jQuery to Enumerable // 1부터 10까지 추가한 값을 모두 더한다. var sum = $("#select1").children() .toEnumerable() .Select("parseInt($.text())") .Sum(); // 55
이렇게 쉽게 사용이 가능하다. 더 많은 레퍼런스는 이곳에서 확인할 수 있다.
IE6 - 8 에서 linq.js 사용하기 – es5-shim
문제는 역시나 IE6, IE7, IE8 에서 발생한다. 그 이유는 기본적인 자바스크립트 처리 엔진의 버전으로, EcmaScript 5 를 지원하지 않는 브라우저이기 때문이다. 즉, 배열 처리가 상당히 제한적이라서 이 linq.js가 제대로 동작하지 않는다.
하지만, 이럴 때 es5-shim 이라는 자바스크립트를 추가해 주면 IE6-8 에서도 이 linq.js를 사용할 수 있다.
만약 html5shiv를 사용한다면, 바로 밑에 이 es5-shim 라이브러리를 추가하도록 하자. 아니라면 modernizr 바로 밑에 추가해도 된다.
<script src="/path/to/es5-shim.min.js"></script>
단순히 이렇게 추가하는 것 만으로 linq.js의 기능들을 모두 사용할 수 있게 된다.
인트라넷상에서 강제로 IE 문서모드 전환하기
일반적으로 IE의 문서모드 Document Mode는 브라우저의 버전에 맞춰 자동으로 설정된다. IE8을 사용중이라면 문서모드는 IE8이고, IE9을 사용중이라면 문서모드는 자동으로 IE9이 되는 것이다.
HTML5 Boilerplate는 이 문서모드를 HTML5에 맞추기 위해 가급적이면 아래 <meta> 태그를 <head> 태그 바로 밑에 위치시키는 것을 추천하고 있다.
<meta http-equiv="X-UA-Compatible" content="IE=edge, chrome=1" />
그런데, 문제는 웹사이트가 인트라넷 상에 존재할 때에는 IE는 무조건 IE7 모드로 바꿔버리는 것에 있다.
아무리 저 위의 <meta> 태그를 넣어도 무조건 IE7 모드로 바뀌기 때문에 일반적인 방법으로는 인트라넷 상에서 IE8 이상의 문서모드를 구현할 수 없다.
따라서, HTTP Response Header 부분에 강제로 위의 내용을 삽입해야 하는데, 이것은 웹서버에서 설정이 가능하다.
위 이미지는 IIS 6 에서 헤더를 설정하는 방법이고, 아래 이미지는 IIS 7 또는 그 이상에서 헤더를 설정하는 방법이다.
위와 같이 웹서버에서 헤더에 정보를 설정하게 되면 아래와 같이 바뀌는 것을 볼 수 있다.
참고로, 아래 이미지는 IE가 <meta> 태그와 HTTP Response Header를 통해 어떻게 적절한 문서모드를 설정하는지에 대한 MSDN 공식 설명이다.
참조:
How Internet Explorer Chooses Between Document Modes
X-UA-Compatibility Meta Tag and HTTP Response Header
Compatibility View
!DOCTYPE Declaration
Configuring IIS to work around webpage display issues in Internet Explorer 8.0
Windows Service 중지시 COM 객체 죽이기
닷넷으로 Windows Service를 개발하여 시스템에 설치한 후 업데이트라든가 여타의 이유로 Windows Service를 언인스톨, 중지 혹은 재실행해야 하는 경우가 많다. 이 과정이 딱히 어렵거나 하진 않은데 Windows Service가 다른 COM 객체를 호출하는 경우에는 문제가 하나 있다.
COM 객체는 기본적으로 닷넷 프레임웍 바깥에서 만들어진 것들이 대부분이라서 닷넷 프레임웍에서 콘트롤할 수 없다고 생각하면 되는데, 이럴 경우 Windows Service를 중지시키면 보통은 COM 객체 역시도 프로세스가 자동으로 죽고 메모리에서 내려오게 된다. 하지만, 여전히 메모리상에서 좀비프로세스로 남아있는 경우도 많다. 이럴 땐 어쩔 수 없이 수동으로 프로세스를 죽여야 하는데...
이럴 경우 그나마 이들 자식 프로세스를 자동으로 죽이는 방법은 서비스 종료 이벤트시 프로세스를 죽이는 로직을 추가하는 것이다.
Marshal.ReleaseComObject(instance);
보통은 이 명령어 한줄로 충분한데, 그래도 죽지 않고 살아 있다면 아래와 같은 로직을 추가한다.
var processName = "MyProcess"; var processes = Process.GetProcessesByName(processName); foreach (var process in processes) { process.Kill(); }
그럼 확실히 죽일 수 있다.
IE8 에서 input 태그와 textarea 태그에 placeholder 속성 적용하기
IE8은 공식적으로 HTML5 태그들을 지원하지 않는다. 따라서, 하위 버전의 IE들을 위해 Modernzr 라든가 html5shiv 같은 자바스크립트들을 이용하면 HTML5에서 새롭게 나타난 엘리먼트 태그들을 사용할 수 있다.
그럼에도 불구하고 placeholder 속성은 IE8에서 사용할 수 없는데, 이럴 때 사용할 수 있는 jQuery 플러그인이 있다. 이름하여 jquery.placeholder. 사용법도 간단하다.
<script src="/path/jquery.placeholder.js"></script>
먼저 jQuery를 로딩한 다음 jquery.placeholder.js를 로딩한다. 그 다음에 다음과 같이 선언하면 끝.
(function ($) { $(document).ready(function () { $("input, textarea").placeholder(); }); })(jQuery);
참 쉽죠?
그런데, 이게 문제가 하나 있다. Bootstrap 이랑 함께 쓰면 <input type="password" ... /> 과 같은 패스워드형 input 엘리먼트는 레이아웃이 깨져 버린다. 그래서, 다 좋은데 이걸 쓰지 못하는 것이 참 안타까움. 결국 다른 것을 하나 더 발견했는데, 이것의 이름은 placeholders.js.
이건 사용방법이 더 쉽다. jQuery 플러그인이 아니기 때문에 독립적으로 작동하는데 아래와 같이 스크립트만 불러오면 끝.
<script src="/path/Placeholders.js"></script>
둘 중 하나를 사용하면 IE8 에서도 placeholder 속성을 사용할 수 있다.