예전에 트위터에서 누군가 소개해줘서 알게 되었는데, 도메인이 도저히 기억나지 않아 한참 검색하다가 다시 찾아서 까먹지 않기 위해 포스트. (허무하게 gitignore.io라니…)

http://gitignore.io

git에서는 커밋에 포함하지 않기 위한 규칙을 리포지터리 최상위 .gitignore 파일에 저장하는데 이 템플릿들을 제공하는 서비스다. 사이트 들어가보면 알겠지만 필요한 OS, IDE, 개발언어나 프레임워크 등의 이름을 넣으면 그에 맞는 gitignore 파일을 제공한다.

gitignore.io

Command Line 도구를 설치하면 콘솔에서 간편하게 내려받을 수 있다. OSX에서는 다음과 같이 설치해 사용할 수 있다.

$ echo "function gi() { curl http://www.gitignore.io/api/\$@ ;}" >> ~/.bash_profile && source ~/.bash_profile
$ gi wordpress,osx >> /path/to/.gitignore

정말 사소한 부분이지만 이런 부분까지 자동화 하는 게으름(?)이 존경스럽다.

WordPress에서 제공하는 클래스인 WP_Query는 wordpress의 컨텐츠(Post, Page, Custom content etc.)를 쉽게 불러 사용할 수 있도록 도와준다. Widget이나 테마 등에서 컨텐츠 목록을 제공할 필요가 있을 때 편리하게 사용할 수 있다.

<?php    
// The Query
$the_query = new WP_Query( $args );

// The Loop
while ( $the_query->have_posts() ) {
    $the_query->the_post();
    echo '<li>' . get_the_title() . '</li>';
}

자세한 내용은 WordPress에서 제공하는 WP_Query 문서에서 확인할 수 있다.

WP_Query에서는 데이터를 가져올 때 array의 형태로 properties를 추가해 활용한다. 한 페이지에서 WP_Query를 여러번 사용할 수도 있는데 이런 경우, 재사용성을 높이기 위해 이전의 설정들이 저장되어 의도하지 않은 데이터가 출력될 때가 있다. 그런 경우를 위해 설정을 초기화 하는 함수인 wp_reset_postdata()가 제공된다.

<?php    
// The Query
$the_query = new WP_Query( $args );

// The Loop
while ( $the_query->have_posts() ) {
    $the_query->the_post();
    echo '<li>' . get_the_title() . '</li>';
}
wp_reset_postdata();

$new_query = new WP_Query( $new_args );

wp_reset_postdata()를 사용했는데도 입력한 properties가 무시되는 등 의도한 대로 동작하지 않는 경우가 있다. 이 경우는 WP_Query 클래스에서 포스트를 가져올 때 pre_get_posts hook을 실행하는데 이 포인트에 query 결과를 조작하는 action이 등록되어 있을 가능성이 높다.

이 hook을 잘못 건들면 대다수의 플러그인과 테마에서 문제가 발생할 수 있기 때문에 만지지 않는걸(should not) 권장한다. Woo Commerce와 같은, 규모가 큰 플러그인은 구현 편의를 위해 이 hook을 사용해 개발하는 경우가 있다. 즉, WP_Query가 제대로 동작하지 않는다면 다른 플러그인이나 테마에서 pre_get_posts를 조작하고 있는지 확인해야 한다.

<?php

wp_reset_postdata();

global $evil_class;
remove_action( 'pre_get_posts', array( $evil_class, 'inject_get_posts' ) );

// The Query
$the_query = new WP_Query( $args );

// The Loop
while ( $the_query->have_posts() ) {
    $the_query->the_post();
    echo '<li>' . get_the_title() . '</li>';
}

// Recover
wp_reset_postdata();
add_action( 'pre_get_posts', array( $evil_class, 'inject_get_posts' ) );

예전부터 cocos2D나 unity를 배워보고 싶었는데 몇 번 글을 보고 따라해봐도 감이 안와서 미뤄왔다. 우연히 SpriteKit 튜토리얼을 보고 따라하다보니 생각보다 쉽게 결과물이 나오길래 게임 만들어보자 마음 먹고 매일 문서 찾아보며 조금씩 만들어가고 있다. 아직 Objective-C도 익숙하지 않지만 CS 193P iPhone Application Development에서 야금야금 들었던 내용 가지고 하나씩 배우고 있다.

다음 링크는 진행하면서 도움이 된 글들을 간단하게 정리해봤다. 애플 공식 문서도 깔끔하게 정리되어 있는데 좀 예제가 적은 편이라서 기본적인 내용은 공식 문서에서 확인하고서, 실제 사용하는 방법들은 구글링, Stackoverflow에서 찾을 수 있었다.1

기초

다음 튜토리얼에서 게임에서 기초적으로 필요한 이미지 불러오기, 터치 이벤트 처리, 간단한 충돌 처리, 노드 animate 방법 등을 배울 수 있다. 리소스도 제공하고 단계별로 잘 설명하고 있어 쉽게 시작하는데 도움이 된다.

디자인/그래픽 관련

여러 장의 이미지로 에니메이션을 처리하거나 한 장의 이미지로 여러 node를 만들려고 할 때 다음 글이 도움이 된다. 이미지 하나로 처리하는 방법은 미리 SKTexture textureWithRect:inTexture:로 텍스쳐를 만들어두고 사용하면 된다.

점수나 안내 문구를 SKLabelNode로 사용하려고 했었기에 임베드 폰트 넣는 방법을 찾아봤다. SKShapeNode로 벡터 처리도 가능하다.

버튼 관련

시작, 다시하기, 처음으로 등 버튼을 만들기 위해 찾아본 버튼 관련 내용이다. 꼭 버튼 뿐만 아니라 SKNode에 다 적용되는 내용이라 이해하는데 도움이 되었다.

데이터 저장하기/불러오기

High Score를 저장하기 위해 찾아봤는데 여러 저장 방법이 있지만 NSUserDefaults를 사용하기로 했다.

Footnotes

  1. 아직 나온지 얼마 안되서 그런지 검색이 잘 안되는 경향이 있다.

회사에서는 지난번 한국 다녀오면서 구입한 레오폴드 FC700 키보드와 애플 블루투스 키보드를 사용하고 있는데 정작 집에서는 별도 키보드 없이 에어를 계속 사용해왔다. 올해 들어서 어깨 결림이 조금 심해지길래 가만 고민해봤더니 집에서 너무 구부정한 자세로 컴퓨터를 사용하고 있지 않았나 생각이 들어 사무실에서 블루투스 키보드를 들고다니며 사용해봤더니 조금 개선이 되는 기분이 들었다.

그래서 애플 블루투스 키보드를 구입할까 했는데 가격도 있고, 아이패드나 아이폰에서 자유롭게 써보고 싶다는 생각에 알아보다가 로지텍 무선 키보드 리뷰를 보게 되었다. 인터넷으로 판매하는 곳을 한참 찾다가 생각보다 많이 비싸 구입하지 못했었는데 요번 문구류 사러 OfficeWorks 갔다가 생각보다 저렴하게 팔고 있길래 구입해왔다.

Logitech Wireless Solar Keyboard K760

좋더라

  • 솔라 패널로 따로 충전할 필요가 없음. (실내 조명으로도 충분하다고)
  • 휴대하기에 살짝 큰 느낌은 들지만 얇아서 괜찮은 편.
  • 블루투스 기기를 3대까지 저장해두고 빠르게 전환 가능. (F1 ~ F3)
  • 맥용 키보드 레이아웃.

거슬리더라

  • 애플 키보드에서 F1 ~ F3 단축키를 자주 사용한다면 불편. 내 경우엔 F3을 정말 많이 쓰는 편이라서 빈번하게 눌려 페어링이 풀린다.
  • 애플 블루투스 키보드보다 키 소리 좀 더 큰 편. 키감이 가벼워서 그런지 몰라도 소리가 좀 남.

기존 블루투스 키보드와 거의 일치하는 크기라서 따로 적응한다고 시간을 쓰지 않아 좋았다. F1~f3을 별도키로 구성했으면 좀 더 편하게 쓸 수 있을듯 싶다. 기기에 대한 자세한 사항은 로지텍 공식 사이트에서 확인할 수 있다.

애플 블루투스 키보드와의 비교

포스트를 작성한지 시간이 꽤 지났고 더이상 부트캠프를 사용하고 있지 않아 질문을 하셔도 답변 드리기 어렵습니다. 이 모든 과정은 글에 있는 다른 블로그 포스트 링크와 구글 검색으로 해결할 수 있는 부분이니 참고하시기 바랍니다.

부트캠프를 통해 Thunderbolt 외장 하드에 Windows 8을 설치한 과정을 기록한 포스트다. 관련 글이 많은데 windows에서의 작업 없이 설치한 경우의 글은 없는 것 같아 정리해봤다.

이 글은 상당히 불친절하다. 아래의 준비물과 순서를 보고 이해가 되지 않는다면 끝없는 인내심이 필요하다. 무슨 이야기인지 조금이라도 모르겠다 싶으면 이 글 말고 부트 캠프 없이 외장 하드로 맥에서 윈도우 설치/부팅하기 또는 맥북에어13(2013 하스웰) 부트캠프 윈도우8 설치 포스트를 참고하자.

동기

맥북에어 2012 모델인데 Windows는 딱히 필요 없을 것이란 생각에 128GB SSD로 구입했다. 그러던 중 닷넷 스터디로 인해 Windows가 필요하게 되었는데 VMWare를 통해 Windows를 외부 하드에서 구동하니 실제 사용하기 어려울 정도로 느렸다. 훨씬 빠른 속도의 thunderbolt를 이용한 외장하드를 쓴다면, 거기다 저장 매체를 SSD를 쓴다면 쾌적하게 쓸 수 있지 않을까 하는 생각에 LaCie thunderbolt SSD 256GB를 구입해 Windows를 설치하게 되었다.

삽질의 주된 원인

가장 큰 문제는 Windows 설치 시 하드로 인식하느냐, usb로 인식하느냐의 차이가 있다. 부트캠프에서 windows를 usb에 옮겨 담아 실질적으로 windows의 설치 흐름을 따라서 진행하게 되는데 설치를 진행하다보면 파티션을 선택하는 부분에서 USB 또는 firewire로 연결된 장비에는 Windows를 설치할 수 없다는 메시지와 함께 더이상 진행할 수 없게 된다.

또한 Windows 콘솔 환경에서 diskpart를 이용해 파티션을 설정해야 한다. 맥에서 포맷해봐야 해당 파티션을 윈도우 설치 시 제대로 포맷되지 않는다.

준비물

  • 선더볼트 지원 외장하드 : 이 하드에 Windows를 설치 할 예정
  • 4GB 이상의 USB 메모리 : 부트캠프를 통해 Windows 이미지를 메모리에 설치
  • 설치할 Windows DVD 또는 ISO 파일 : 부트캠프에서 이미지를 설치할 때 사용
  • Windows Command Prompt에 대한 얕은 이해 (도스 명령어 정도)
  • 강인한 정신 등등

설치 과정

  1. 먼저 맥에서 Bootcamp Assistant를 실행해 windows 이미지를 usb 메모리에 담는다. usb 메모리의 내용은 초기화되므로 주의하고, 또 다른 외장 하드나 맥OS가 설치되어 있는 드라이브를 선택하지 않도록 신중하게 선택한다.
  2. 이미지를 usb에 담는 과정은 조금 지나면 끝난다. 완료되었으면 맥을 종료하고 다시 켤 때 option키를 눌러 부트로더로 진입한다.
  3. 부트로더로 진입하면 ELI 어쩌고 또는 Windows가 보이는데 뭐가 다른지 모르겠다. ELI로 진입한다.
  4. 그러면 Windows 로고가 나오면서 설치 화면 안내가 나온다. 설치를 바로 하는 것이 아니라 하단의 Repair 버튼을 눌러 command prompt로 진입해야 한다.
  5. 부트 캠프 없이 외장 하드로 맥에서 윈도우 설치/부팅하기 글의 4. 외장 하드 파티션 나누기 부분을 따라서 파티션 작업을 진행하는데 부팅용 파티션이라는 부분을 다음 순서를 위해 4GB 이상으로 설정한다.
  6. 새로 생성한 파티션 중 부팅용 파티션에 메모리에 들어있던 내용을 콘솔을 통해 전부 복사한다. (4GB가 넘음)
  7. 컴퓨터를 종료하고 USB 메모리를 뺀 후에 다시 option키를 눌러 부트로더 진입, 윈도우 시동을 한다.
  8. 외장 하드의 두번째 파티션에 Windows를 설치한다.
  9. 설치를 완료하고 재부팅해 윈도우에 들어가면 멀티부팅 설정이 되어 있는데 Setup이 아닌 Windows를 선택해 진입한다. 윈도우즈 7 멀티부팅 제거 방법 (8도 같은 방법으로 가능)
  10. Boot Camp Support Software를 내려받아 설치한다.

왜 외장 하드에 메모리 내용을 옮겨야 하나

메모리로 부팅하면 일종의 LiveCD와 같이 동작하는 상태인데 이런 경우 외장하드를 USB나 firewire로 연결한 기기라고 뜨거나 윈도우를 설치할 수 없는 상태라고 설치를 진행하지 못하게 막는다. 해당 하드에 설치 이미지를 복사하면 일반 하드로 인식해서 설치가 진행된다.

부트 캠프 없이 외장 하드로 맥에서 윈도우 설치/부팅하기 글과 다른 부분은 무엇인가

Windows 환경을 설치하지 않아도 된다는 점이다. 해당 글에서는 부팅 파티션을 생성해주는 도구를 사용하는데 Windows 환경이 있어야만 한다. 4GB 라는 용량이 낭비 같지만 문제가 생겼을 때 복구용으로 쓸 수 있다는 이점도 있다.

맥의 디스크 관리자(Disk Utility)로는 파티션 분리가 안되나

맥은 ntfs가 지원되지 않고 별도의 프로그램을 설치해야 한다. 내 경우는 ntfs-3g가 설치되어 있는데 디스크 관리자(Disk Utility)에서 ntfs로 포맷이 가능했지만 뭔가 인식이 제대로 안되었다. 그래서 Windows의 command prompt에서 포맷했더니 그때야 제대로 인식이 되었다.

성능 및 호환성

SSD에 선더볼트라서 엄청나게 빠르다. Windows 8.1이라서 성능 점수 표시 부분이 없어져 딱히 인증 할 만한 자료가 없다. 현재 Visual Studio 2012, SQL Server 등 Windows 개발 환경 설정을 모두 했는데 상당히 빠르다.

대부분의 드라이버가 잘 인식되서 정상적으로 동작한다. 화면이 어두워지고 최대 밝기로 올려도 밝아지지 않는 문제가 있었는데 Windows의 절전 모드 설정을 변경해 해결했다.

트랙패드 역방향 문제가 있는데 아직 해결하지 못했다. Registry를 수정해주면 된다고 하는데 수정해도 적용이 안된다.

결론

비싸더라도 큰 SSD가 탑재된 맥북을 구입하자.

속도가 궁금하면 다음 영상을 참고하자.

[iframe src=”http://www.youtube.com/embed/KoiaKLWfGto” width=”100%” height=”480″]

근래 아이폰, 아이패드, 맥북 프로 등 통칭 레티나로 일컬어지는, 고밀도 디스플레이 기기가 늘고 있다. 그에 따라서 웹페이지도 레티나 해상도에 대응을 하기 시작했는데, 각 이미지를 2배의 해상도로 저장한 후 css 또는 js를 이용해 치환하는 형태 또는 svg, canvas 등을 이용하는 방식으로 대응하고 있다. 여러 방법 중 svg를 활용하는 방법을 살펴보려고 한다.

svg는 xml로 작성된 벡터 이미지 포맷으로 대다수의 최신 브라우저에서 지원하고 있어 이와 같은 문제를 해결하는데 도움이 된다. svg를 이용하는 장점은 다음과 같다.

  • 이미지를 2번 이상 생성하지 않아도 된다. 하나의 이미지로 여러 해상도를 지원할 수 있으며 단일 파일로 모두 제어할 수 있으므로 유지보수에 용이하다.
  • svg 엘리먼트를 이용해 인라인으로 사용하면 stylesheet나 js를 이용해 동적으로 활용할 수 있다.

물론 svg를 사용할 때 단점도 분명 존재한다.

  • 확대/축소에 따라서 의도와 다른 형태로 렌더링 될 수 있다. 예를 들면 비트맵에서는 쉽게 가능한 1px 선을 벡터 방식에선 면으로 표현해야 하기 때문에 그리기 어렵다.
  • IE 8 이하, Android 내장 브라우저 (2.1, 2.2, 2.3)에서는 지원하지 않는다.1

위에서 살펴본 장단점에 따라 svg를 사용할 수 있는지, 어떤 경우에 적용할 것인지 전략을 세워야 한다. 특히 렌더링에서 차이를 보이는 부분으로 인해 iOS에서 벡터 앱 아이콘을 사용할 수 있는 상황에도 애플앱들은 비트맵으로 작성한 아이콘을 쓰고 있는 예도 있다.2

svg 사용하기

svg를 웹에서 사용하는 방법은 img 엘리먼트와 svg 엘리먼트를 이용하거나 css를 통해 활용할 수 있다.

img를 이용해 일반 이미지처럼 사용할 수 있다.

<img src="logo.svg" alt="Weird Meetup" />

그리고 svg 엘리먼트를 통해 직접 넣을 수 있다. 이렇게 작성하면 다소 지저분해지는 경향이 있지만 svg 내부의 엘리먼트도 css로 제어할 수 있다는 장점도 있다. 아래는 이상한 모임 로고의 글자 부분이다.

<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="500px"
 height="500px" viewBox="0 0 500 500" enable-background="new 0 0 500 500" xml:space="preserve">
    <g id="이상한모임">
        <g>
            <path fill="#000000" d="M180.647,389.499c-2.698,0-5.852-1.786-5.852-6.649v-9.042c0-3.951,2.584-6.991,6.991-6.991h3.381
                c4.217,0,6.839,3.153,6.839,7.105v9.156c0,3.837-2.545,6.421-5.205,6.421H180.647z M184.447,385.586
                c1.899,0,2.773-1.824,2.773-3.268v-8.055c0-1.634-1.254-3.078-2.773-3.078h-1.9c-1.52,0-2.925,1.444-2.925,3.078v8.055
                c0,2.47,0.988,3.268,2.925,3.268H184.447z M200.291,364.309h-5.091v31.953h5.091V364.309z"/>
            <path fill="#000000" d="M216.556,365.639c0.342,4.521,4.597,11.702,7.485,15.273h-5.737c-1.52-1.823-3.153-5.015-4.407-7.94
                c-1.292,2.583-2.28,5.167-4.445,7.94h-6.421c5.433-4.559,8.207-13.374,8.435-15.273H216.556z M226.283,384.712
                c2.735,0,4.939,1.71,4.939,4.104v3.039c0,2.09-2.127,4.027-4.787,4.027h-12.918c-3.496,0-5.396-1.178-5.396-4.065v-2.887
                c0-2.812,2.128-4.218,5.281-4.218H226.283z M214.922,388.663c-1.444,0-1.976,0.38-1.976,1.292v1.102
                c0,1.103,1.064,1.103,2.052,1.103h9.727c0.912,0,2.052-0.381,2.052-1.103v-1.178c0-0.722-0.76-1.216-2.052-1.216H214.922z
                 M235.135,374.947h-4.255v7.979h-4.863v-18.655h4.863v6.231h4.255V374.947z"/>
            <path fill="#000000" d="M248.742,366.437h6.117v3.495h-2.735c0.874,0.912,1.405,2.166,1.405,3.496v3.951
                c0,3.191-2.355,5.471-5.053,5.471h-4.711c-2.698,0-5.091-2.279-5.091-5.091v-4.331c0-1.33,0.532-2.584,1.406-3.496h-2.66v-3.495
                h6.117v-2.166h5.205V366.437z M263.484,395.502h-22.721v-10.524h4.788v6.003h17.934V395.502z M249.122,376.847v-2.432
                c0-1.216-0.912-2.127-2.166-2.127h-1.596c-1.216,0-2.318,0.911-2.318,2.127v2.432c0,1.254,0.722,2.166,1.938,2.166h2.127
                C248.362,379.013,249.122,378.101,249.122,376.847z M262.42,372.819h4.483v4.56h-4.483v8.473h-4.901v-21.619h4.901V372.819z"/>
            <path fill="#000000" d="M298.063,392.767h-28.876v-4.445h11.893v-4.293h5.091v4.293h11.893V392.767z M272.493,367.12v15.616
                h22.455V367.12H272.493z M290.236,378.557h-12.538v-7.295h12.538V378.557z"/>
            <path fill="#000000" d="M315.621,370.806v5.243c0,2.926-2.317,5.243-5.091,5.243h-4.901c-2.773,0-5.281-2.317-5.281-5.243v-5.243
                c0-2.926,2.508-5.243,5.281-5.243h4.901C313.304,365.562,315.621,367.88,315.621,370.806z M326.031,384.598v11.664h-22.492
                v-11.664H326.031z M311.176,371.87c0-1.292-0.987-2.242-2.241-2.242h-1.71c-1.254,0-2.355,0.95-2.355,2.242v3.115
                c0,1.292,1.102,2.279,2.355,2.279h1.71c1.254,0,2.241-0.987,2.241-2.279V371.87z M322.422,388.587h-14.817v3.609h14.817V388.587z
                 M326.031,364.232h-4.407v18.124h4.407V364.232z"/>
        </g>
    </g>
</svg>

어떻게 지저분한지 보여주기 위해 위 예를 넣었다. 다섯글자일 뿐인데 이렇게 지저분하다.

modernizr와 css를 이용한 이미지 대체

위와 같이 직접 적용하면 svg를 지원하지 않는 브라우저에서는 이미지가 나타나지 않기 때문에 modernizr를 통해 svg 지원 여부를 확인하고 그에 따라 css로 이미지를 교체해주는 방법을 활용할 수 있다.

modernizr를 적용한 모습

Modernizr를 웹페이지에 적용하면 해당 브라우저에서 어떤 기능을 지원하는지 html의 클래스로 선언해준다. html에 적용된 클래스를 플래그로 이용해 css 배경을 대체/적용하는 방식으로 svg 미지원 브라우저 문제를 해결할 수 있다.

#logo { background: url("logo.svg"); }
.no-svg #logo { background: url("logo.png"); }

svg 적용 사례

  • Apple : apple 사이트의 메뉴 등에서 svg를 사용함. 벡터 이미지는 서체 표현에 용이함.
  • Mailchimp : 동보메일 서비스인 mailchimp도 이번 개편에서 svg를 곳곳에서 사용하고 있음. 특히 체크박스나 라디오 버튼을 svg로 대체하고 있는 점이 독특.
  • FontAwesome : 아이콘 폰트를 제공하는 서비스인데 svg로 된 포멧도 지원함.

이외에도 svg를 활용한 곳을 심심하지 않게 찾아볼 수 있다. svg도 이제 지원하는 브라우저가 많기도 하고 하위 호환을 고려하기도 큰 어려움이 없기 때문에 예전에 비해 많이 사용하는 추세다. 위의 예시에서 눈치챘을 수도 있지만 이상한모임의 로고도 svg를 사용했다.

읽어볼 만한 글

다음 두 포스트는 레티나 지원을 위한 다양한 방법에 대해 체계적으로 잘 정리된 글이다.

간단한 svg 적용 방법, 위에서 언급한 fontawesome를 이용하는 방법은 다음의 링크에서 확인해볼 수 있다.

Footnotes

  1. svg의 브라우저 지원 여부는 caniuse.com의 svg 페이지에서 확인해볼 수 있다.

  2. iOS7으로 오면서 애플앱도 벡터 방식 아이콘을 쓰는 것 같더라. (잘 모름)

읽기 전에

Mono에서 웹개발을 하고 싶다면 OWIN 프로젝트를 활용하자. 차후 .NET mvc 프레임웍도 owin 기반에서 구동 가능할 예정이다.

tl;dr

  • Mono에서 MVC5 지금은 안됨
  • .Net 개발은 정신 건강을 위해 Windows 위에서 하자

요즘 닷넷 스터디를 한창 하고 있는데 요번에 새로 나온 MVC5를 기준으로 스터디가 진행되고 있다. 아직 윈도우 개발 환경이 준비 안된 탓에 이 MVC5 프로젝트를 Mono 환경에서 구동해보려고 했는데 결과적으로는 운용조차 해볼 수 없었다. 안된다고 딱 잘라 말하는 글이 하나도 없어서 에러 로그를 정리해 올려보려고 한다. 참고로 Mono의 호환 현황은 Mono 공식 사이트의 Compatibility에서 확인할 수 있다.1

웹으로 접속하면 다음의 에러가 발생한다.

Missing method System.Web.Hosting.HostingEnvironment::get_InClientBuildManager() in assembly /Library/Frameworks/Mono.framework/Versions/3.2.4/lib/mono/gac/System.Web/4.0.0.0__b03f5f7f11d50a3a/System.Web.dll, referenced in assembly /private/tmp/root-temp-aspnet-0/8717103c/assembly/shadow/d4ff52ca/402bd257_94d4809d_00000001/WebActivatorEx.dll

Application Exception System.TypeLoadException Could not load type 'System.Web.Http.WebHost.HttpControllerHandler' from assembly 'System.Web.Http.WebHost, Version=5.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'.

Description: HTTP 500.Error processing request. Details: Non-web exception. Exception origin (name of application or object): System.Web. Exception stack trace: at System.Web.Routing.RouteCollection.GetRouteData (System.Web.HttpContextBase httpContext)
[0x00000] in <filename unknown>:0 at System.Web.Routing.UrlRoutingModule.PostResolveRequestCache (System.Web.HttpContextBase context)
[0x00000] in <filename unknown>:0 at System.Web.Routing.UrlRoutingModule.PostResolveRequestCache (System.Object o, System.EventArgs e)
[0x00000] in <filename unknown>:0 at System.Web.HttpApplication+<RunHooks>c__Iterator5.MoveNext ()
[0x00000] in <filename unknown>:0 at System.Web.HttpApplication+<Pipeline>c__Iterator6.MoveNext ()
[0x00000] in <filename unknown>:0 at System.Web.HttpApplication.Tick ()
[0x00000] in <filename unknown>:0 Version Information: 3.2.4 ((no/294f999 Fri Oct 25 20:18:12 EDT 2013); ASP.NET Version: 4.0.30319.17020 Powered by Mono

위 에러는 get_InClientBuildManager 메소드가 없어 나타나는 문제로 Mono에서 구현된 System.Web.Hosting.HostingEnvironment에 해당 메소드가 구현되어 있지 않다. 그래서 MS에서 배포한 라이브러리 dll을 사용해 시도했다. System.Web.dll을 local copy 해서 다시 구동했다. 다음은 MVC5 프로젝트를 Mono의 xsp4로 구동했을 때 나오는 에러다.

Handling exception type TargetInvocationException Message is Exception has been thrown by the target of an invocation. IsTerminating is set to True System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation.

Server stack trace: at System.Reflection.MonoCMethod.InternalInvoke (System.Object obj, System.Object[] parameters)
[0x00000] in <filename unknown>:0 at System.Reflection.MonoCMethod.DoInvoke (System.Object obj, BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture)
[0x00000] in <filename unknown>:0 at System.Reflection.MonoCMethod.Invoke (System.Object obj, BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture)
[0x00000] in <filename unknown>:0 at System.Reflection.MethodBase.Invoke (System.Object obj, System.Object[] parameters)
[0x00000] in <filename unknown>:0 at System.Runtime.Serialization.ObjectRecord.LoadData (System.Runtime.Serialization.ObjectManager manager, ISurrogateSelector selector, StreamingContext context)
[0x00000] in <filename unknown>:0 at System.Runtime.Serialization.ObjectManager.DoFixups ()
[0x00000] in <filename unknown>:0 at System.Runtime.Serialization.Formatters.Binary.ObjectReader.ReadNextObject (System.IO.BinaryReader reader)
[0x00000] in <filename unknown>:0 at System.Runtime.Serialization.Formatters.Binary.ObjectReader.ReadObjectGraph (BinaryElement elem, System.IO.BinaryReader reader, Boolean readHeaders, System.Object& result, System.Runtime.Remoting.Messaging.Header[]& headers)
[0x00000] in <filename unknown>:0 at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.NoCheckDeserialize (System.IO.Stream serializationStream, System.Runtime.Remoting.Messaging.HeaderHandler handler)
[0x00000] in <filename unknown>:0 at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize (System.IO.Stream serializationStream)
[0x00000] in <filename unknown>:0 at System.Runtime.Remoting.RemotingServices.DeserializeCallData (System.Byte[] array)
[0x00000] in <filename unknown>:0 at (wrapper xdomain-dispatch) System.AppDomain:DoCallBack (object,byte[]&,byte[]&)

Exception rethrown at [0]: ---> System.ArgumentException: Couldn't bind to method 'SetHostingEnvironment'. at System.Delegate.GetCandidateMethod (System.Type type, System.Type target, System.String method, BindingFlags bflags, Boolean ignoreCase, Boolean throwOnBindFailure)
[0x00000] in <filename unknown>:0 at System.Delegate.CreateDelegate (System.Type type, System.Type target, System.String method, Boolean ignoreCase, Boolean throwOnBindFailure)
[0x00000] in <filename unknown>:0 at System.Delegate.CreateDelegate (System.Type type, System.Type target, System.String method)
[0x00000] in <filename unknown>:0 at System.DelegateSerializationHolder+DelegateEntry.DeserializeDelegate (System.Runtime.Serialization.SerializationInfo info)
[0x00000] in <filename unknown>:0 at System.DelegateSerializationHolder..ctor (System.Runtime.Serialization.SerializationInfo info, StreamingContext ctx)
[0x00000] in <filename unknown>:0 at (wrapper managed-to-native) System.Reflection.MonoCMethod:InternalInvoke (System.Reflection.MonoCMethod,object,object[],System.Exception&) at System.Reflection.MonoCMethod.InternalInvoke (System.Object obj, System.Object[] parameters)
[0x00000] in <filename unknown>:0 --- End of inner exception stack trace --- at (wrapper xdomain-invoke) System.AppDomain:DoCallBack (System.CrossAppDomainDelegate) at (wrapper remoting-invoke-with-check) System.AppDomain:DoCallBack (System.CrossAppDomainDelegate) at System.Web.Hosting.ApplicationHost.CreateApplicationHost (System.Type hostType, System.String virtualDir, System.String physicalDir)
[0x00000] in <filename unknown>:0 at Mono.WebServer.VPathToHost.CreateHost (Mono.WebServer.ApplicationServer server, Mono.WebServer.WebSource webSource)
[0x00000] in <filename unknown>:0 at Mono.WebServer.XSP.Server.RealMain (System.String[] args, Boolean root, IApplicationHost ext_apphost, Boolean quiet)
[0x00000] in <filename unknown>:0 at Mono.WebServer.XSP.Server.Main (System.String[] args)
[0x00000] in <filename unknown>:0 [ERROR] FATAL UNHANDLED EXCEPTION: System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation.

Server stack trace: at System.Reflection.MonoCMethod.InternalInvoke (System.Object obj, System.Object[] parameters)
[0x00000] in <filename unknown>:0 at System.Reflection.MonoCMethod.DoInvoke (System.Object obj, BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture)
[0x00000] in <filename unknown>:0 at System.Reflection.MonoCMethod.Invoke (System.Object obj, BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture)
[0x00000] in <filename unknown>:0 at System.Reflection.MethodBase.Invoke (System.Object obj, System.Object[] parameters)
[0x00000] in <filename unknown>:0 at System.Runtime.Serialization.ObjectRecord.LoadData (System.Runtime.Serialization.ObjectManager manager, ISurrogateSelector selector, StreamingContext context)
[0x00000] in <filename unknown>:0 at System.Runtime.Serialization.ObjectManager.DoFixups ()
[0x00000] in <filename unknown>:0 at System.Runtime.Serialization.Formatters.Binary.ObjectReader.ReadNextObject (System.IO.BinaryReader reader)
[0x00000] in <filename unknown>:0 at System.Runtime.Serialization.Formatters.Binary.ObjectReader.ReadObjectGraph (BinaryElement elem, System.IO.BinaryReader reader, Boolean readHeaders, System.Object& result, System.Runtime.Remoting.Messaging.Header[]& headers)
[0x00000] in <filename unknown>:0 at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.NoCheckDeserialize (System.IO.Stream serializationStream, System.Runtime.Remoting.Messaging.HeaderHandler handler)
[0x00000] in <filename unknown>:0 at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize (System.IO.Stream serializationStream)
[0x00000] in <filename unknown>:0 at System.Runtime.Remoting.RemotingServices.DeserializeCallData (System.Byte[] array)
[0x00000] in <filename unknown>:0 at (wrapper xdomain-dispatch) System.AppDomain:DoCallBack (object,byte[]&,byte[]&)

Exception rethrown at [0]: ---> System.ArgumentException: Couldn't bind to method 'SetHostingEnvironment'. at System.Delegate.GetCandidateMethod (System.Type type, System.Type target, System.String method, BindingFlags bflags, Boolean ignoreCase, Boolean throwOnBindFailure)
[0x00000] in <filename unknown>:0 at System.Delegate.CreateDelegate (System.Type type, System.Type target, System.String method, Boolean ignoreCase, Boolean throwOnBindFailure)
[0x00000] in <filename unknown>:0 at System.Delegate.CreateDelegate (System.Type type, System.Type target, System.String method)
[0x00000] in <filename unknown>:0 at System.DelegateSerializationHolder+DelegateEntry.DeserializeDelegate (System.Runtime.Serialization.SerializationInfo info)
[0x00000] in <filename unknown>:0 at System.DelegateSerializationHolder..ctor (System.Runtime.Serialization.SerializationInfo info, StreamingContext ctx)
[0x00000] in <filename unknown>:0 at (wrapper managed-to-native) System.Reflection.MonoCMethod:InternalInvoke (System.Reflection.MonoCMethod,object,object[],System.Exception&) at System.Reflection.MonoCMethod.InternalInvoke (System.Object obj, System.Object[] parameters)
[0x00000] in <filename unknown>:0 --- End of inner exception stack trace --- at (wrapper xdomain-invoke) System.AppDomain:DoCallBack (System.CrossAppDomainDelegate) at (wrapper remoting-invoke-with-check) System.AppDomain:DoCallBack (System.CrossAppDomainDelegate) at System.Web.Hosting.ApplicationHost.CreateApplicationHost (System.Type hostType, System.String virtualDir, System.String physicalDir)
[0x00000] in <filename unknown>:0 at Mono.WebServer.VPathToHost.CreateHost (Mono.WebServer.ApplicationServer server, Mono.WebServer.WebSource webSource)
[0x00000] in <filename unknown>:0 at Mono.WebServer.XSP.Server.RealMain (System.String[] args, Boolean root, IApplicationHost ext_apphost, Boolean quiet)
[0x00000] in <filename unknown>:0 at Mono.WebServer.XSP.Server.Main (System.String[] args)

위 기록을 보면 Mono에서 구현한 xsp 서버에 연결이 되지 않는데 SetHostingEnvironment 메소드가 제대로 바인딩 되지 않는다. 에러가 나는 두가지 모두 빌드는 제대로 진행 되는 것 보면, 추측컨데 xsp에서 구현되지 않은 부분이 문제일 뿐이지 본 프로젝트에 작성된 코드는 문제가 없는 것 같다. xsp 구현을 들여다보고 손을 댈려고 했지만, 간단한 문제라면 이미 수정이 되었을 것이고 그로 미루어 볼 때 내 작업으로 만들 수 있는 수준이 아닌듯 싶어서 코드를 좀 보다가 말았다. (게다가… C#은 전혀 모르는 쪽이니까.)

MVC5가 OWIN을 지원한다는 얘기를 들었는데 xsp 이외의 방법으로 이종 플랫폼에서 구동할 수 있는 방법이 있을 법도 하다. 하지만 문서도 잘 검색이 안되고 사용자도 너무 작아, 명목상의 존재 의의만 다하고 있는 것 같아 아쉽다.2 윈도우 환경을 위해 구입한 선더볼트 외장하드가 오면 mono를 쓸 일이 없어져 그렇게 큰 고민거리는 아니긴 하지만, 다른 플랫폼에서 닷넷을 오픈소스로 구현하기 위해 애쓰는 모습이 존경스럽다. 이 구현이 성숙해 닷넷 플랫폼을 온전한 오픈소스로 구동할 수 있다면 더 재미있는 프로젝트가 닷넷으로 작성되지 않을까 생각해본다.

  • 이 목록을 확인하지 않고서 무턱대고 시작한게 화근이었다. 아직 MVC4도 완전히 지원하는 상황이 아니다. 
  • 그리고 이 문제를 해결하기 위해 엄청 검색을 했었는데 비슷한 문제를 버전 대대로 겪은 사람들이 많이 나왔다. 물론 구현 자체가 없으니 아무도 해결하지 못했다. 
  • 점심 먹으며 글을 읽다가 참 인상적인 내용의 포스트를 보게 되어 허락받고 글을 옮겨봤다. 꼭 이 글에서 얘기하는 특정적인 상황 뿐만 아니라, 우리가 일상적으로 자주 겪는 상황에도 충분히 적용될 수 있는 이야기다. 원문은 Why ask why? – Ned Batchelder에서 확인할 수 있다.


    나는 파이썬 질문을 하는 사람들에게 도움을 주고 있다. 보스턴 파이썬 모임의 지인이든, IRC 채널에서 만난 완전히 모르는 사람이든 말이다. 이 상황에서 간혹 뜬금없게 오해할 때가 있다. “왜”라고 질문하는걸 오해하는 경우도 여기에 포함된다.

    답을 찾기 위해 돕는 동안, 종종 질문자에게 “왜?” 라는 질문을 하게 된다. 예를 들면, 누군가가 두개의 파이썬을 랩탑에 설치해야 한다면서 도움을 요청했다. 그런 상황에서 나는 “왜 두번째 파이썬이 필요해요?” 라고 물어볼 것이다. 그럼 다른 사람이 그 글을 보고 웃게 되는데 내가 “너는 두번째 파이썬을 설치할 필요가 없다.” 라고 말한 것으로 생각하기 때문이다.

    이는 일반적인 반응이다. 왜냐고 되물어보는 것은 비난처럼 느껴질 수 있다. “왜 XYZ로 했나요?”라고 말을 하면 “이 멍청이, 넌 XYZ로 하지 말았어야해.” 라고 이해한다는 얘기다. 그러나 내가 왜냐고 되물어보는 것은 정말로 다음과 같은 의도에서 물어보는 것이다. “나는 당신이 XYZ를 한 이유에 대해서 이해하길 원합니다.”

    영어는 위와 같은 이유로 어려운 경우가 있다. 특히 IRC와 같이 비언어적 표현이 없는, 온전히 문자로만 전달되는 상황에서 특히 그렇다. 이런 질문을 누가 봐도 부드럽다 느낄 정도로 물어보기 위해 단어를 더 덧붙여 질문해야 한다. 예를 들면,

    왜 다른 파이썬을 설치해야 하나요?

    이보다 다음처럼 되물어보는 쪽이 낫다.

    왜 다른 파이썬을 설치해야 했는지 물어봐도 될까요? 이유를 이해하는 것은 중요한 단서를 제공하거든요.

    그러므로 내가 만약 왜냐고 되물어본다면, 이를 개인적인 의미로 듣지 말자. 난 정말로 무슨 이유에서 그런건지 알고 싶을 뿐이다. 만약 내가 정말 당신의 잘못을 꾸짖기 원했다면, “당신은 XYZ로 하지 말았어야 했어요.”, “XYZ는 좋은 아이디어가 아니에요.” 라고 말했을 것이다.

    때로는 사람들이 내가 왜냐고 되물어보는 이유를 알게 될 때, 오히려 그 질문에 발끈하는 경우가 있다. 그들은 이유는 중요하지 않다고 고집하고, 왜 단순한 질문에 간단한 답을 내놓지 못하는가에 대해 의아해한다.

    내가 되물어보는 경우의 75% 가량은 내가 큰 그림을 알게 되고 나서 질문의 답변이 달라진다. 일반적으로 사람들이 문제가 발생하고 해결책을 찾다가 막다른 길에 닿았을 때, 사람들은 막다른 길에 닿았다는 사실에 대해서만 질문을 한다. 당연한 일이다. 그러고 그들이 더이상 해결하지 못하는 상태에 빠지 이유는 문제의 처음부터 다시 출발해서 살펴보지 않기 때문에 더 나은 해결책을 찾지 못하는 것이다. 더 나은 길을 찾는걸 돕기 위해서는 그 질문의 이유를 이해해야 한다.

    왜냐고 되물어보는 과정을 통해서 문제가 나타나기 이전에 선택했던 부분에 대해 살펴보고, 이러한 방법으로 전체적인 문제를 살펴보는 과정은 문제 해결에 도움이 된다. 문제에 관한 모든 정보를 알게 된다는 것은 함께 더 좋은 해결책을 찾을 수 있다는 뜻이다.

    내가 더 큰 문제에 대해 설명해달라고 물어보면 개인적으로 받아들이지 말자. 복잡한 문제를 해결하는 것은 힘들다. 간단하게 선택한 첫번째 과정이 두번째 과정을 필요보다 더 어렵게 만드는 경우가 있기 때문이다. 도와주는 사람이 더 큰 그림에 대해 물어볼 때, 질문하는 사람을 면박 주려고 물어보는 것이 아니라, 최고의 해결책을 찾는데 도움을 주기 위해 물어보는 것이다.

    이런 현상에 대해 XY 문제라는 표현을 쓴다. X라는 문제에 대해 Y라는 해결책을 선택했는데, 이게 동작하지 않을 때, X라는 문제점을 물어보는 것이 아니라 Y라는 해결책이 왜 안되는가에 대해 물어본다는 말이다. 몇 사람들은 자신도 모르게 문제점과 해결책의 관계에 대해 중요하게 생각하지 않는다. 이는 아주 일반적인 상황이며 자신의 질문에 대해 고집을 부린다. 그래서 때때로 질문하는 사람은 대안을 빨리 알아채지 못한다. 또 때로는 질문하는 사람이 답하려는 사람보다 시간을 안쓰려고 명확하게 질문하지 않는 경우도 있다. 이유가 어찌되었든, 이런 일은 항상 일어나고, 답변하는 사람들이 XY 문제와 같은 이유로 화가 나면 다시는 질문에 답하는 일을 하지 않을 것이다.

    질문하는 사람들에게: 만약 누군가 왜냐고 물어보거나, 당신은 “XY 문제”를 가지고 있다고 얘기할 때, 그들은 당신에게 최적의 해결책을 찾는 것을 도와주려고 하는 것이다. 기분 나빠하지 말자. 우리는 힘겹게 학습하고 있고 복잡한 상황을 극복하려고 함께 노력하고 있기 때문이다.


    내 스스로도 알면서 잘 안되는 부분이라 글을 읽으면서 뜨끔뜨끔 했다.

    위에서 이야기한 XY 문제는 대다수의 커뮤니케이션에서 쉽게 발생한다. 단순하게 보면 별 이야기도 아닌데 의외로 직간접적으로 자주 겪는다. (내가 질문자 일 때도, 답변자 일 때도 있었던 것 보면 정말 흔한 상황이다.) 유대가 모자란 상황에서도 발생하지만 친밀한 경우에도 이와 같은 오해는 종종 일어난다. 그게 친구와도 나타나고, 직장 생활에서, 학교에서, 어디서든 쉽게 나타나는 일이다.

    배움에 있어서는 적극적인 어린이가 되어야 한다. 부끄러움 없이 지금 내가 아는 부분을 모두 보여주고 물어봐야 빠르게 답을 얻을 수 있다. 내가 무엇을 모르는지 정확히 아는 것이 현명한 사람이란 이야기가 있다. 아는 척 숨기고 있는다면 평생 배우지 못한 상태로 살게 되는 것과 같고 모르는 것보다 더 부끄러운 일이다.

    왜라는 질문에 솔직해지자. 타인에게도 나에게도.

    색상을 바꿔요

    눈에 편한 색상을 골라보세요 :)

    Darkreader 플러그인으로 선택한 색상이 제대로 표시되지 않을 수 있습니다.