Security in OAuth

Posted in .NET Framework // Posted at 2013. 5. 29. 16:59
728x90

표준 인증체계를 정의하는 OAuth에 적용된 보안 개념들은 OAuth를 사용하지 않더라도 인터넷과 같은 열린 공간에서의 통신에서 보안을 강화하는 아키텍처로써 가치가 있다.

 

분산 시스템간 통신 시, 이러한 개념을 적절히 접목하면 보안성이 잘 갖춰진 시스템을 만들 수 있을 것이다.

 

먼저 시스템간의 통신에서 보안이란 대략 다음과 같은 요소들로 정리할 수 있겠다.

 

1. 기밀유지 (암호화)

보안의 가장 기본적인 요소로 데이터를 읽지 못하게 만들어야 한다.

공격자가 통신 패킷을 중간에 가로채서 보더라도 데이터를 읽을 수 없는 상태로 만들어야 하는데 이를 위해 각종 암호화 기법을 적용하게 된다.

 

2. 신원확인 (자격증명)

허가된 클라이언트만이 통신을 허용할 수 있도록 해야 하는데 이를 위해 클라이언트는 자신의 자격증명을 제출하도록 한다. 보통 ID/PW를 제출하기도 하고 클라이언트 인증서, 임의의 토근 등이 사용되기도 한다.

 

3. 변조 방지 (디지털서명)

공격자가 통신 패킷을 중간에 가로채서 변경하지 못하도록 해야 한다.

서버의 입장에서는 요청이 중간에 변조되지 않았는지를 확인 해야 하는데, 이를 위해 전송 데이터의 해시 값을 구하고 이 해시 값을 클라이언트의 비밀키로 암호화하도록 한다.

이를 디지털 서명이라고 하는데 변조 방지 뿐만 아니라 클라이언트가 자신이 보낸 것이 아니라고 하는 부인방지도 효과도 있다.

 

4. 재생 방지

일반적인 보안 시나리오에서 간과되기 쉬운 부분인데, 분산 환경에서의 통신에서는 특별히 주의해서 방어해야 하는 요소이다. 앞의 1,2,3 의 보안 요소들이 잘 갖춰져 있다하더라도 공격자가 중간에서 패킷을 가로채 그대로 다시 서버로 전송할 경우 이를 거부하도록 해야 한다.

 

OAuth 프로토콜은 이러한 보안 개념들을 적절히 잘 접목한 안전한 인증시스템이라 할 수 있다.

 

OAuth를 통한 인증 과정에서는 실제로 기밀유지보다는 변조방지와 재생방지에 무게 중심이 있다고 하겠다.

OAuth 인증 과정에서는 비밀번호라던가 주민번호와 같은 기밀을 유지해야 하는 민감한 데이터가 포함되지 않기 때문에 기밀유지를 위한 과도한 암호화는 불필요하다. 다만 인증된 클라이언트에 의한 유효한 요청인지가 더욱 중요하다는 것이다.

(OAuth 인증 과정 중, 사용자가 ID/PW를 입력해서 로그인 하는 과정이 있지만 이 부분은 OAuth 인증 과정이라기 보다는 사용자와 서비스 프로바이더간 보안 구간이라고 하겠다.

또한 OAuth 인증 과정을 모두 완료한 후 액세스 토큰으로 보호된 자원에 접근하는 경우에 기밀 데이터가 있을 경우 htts와 같은 별도의 암호화 장치를 둬야 한다. 이 구간 역시 OAuth 인증 과정과는 별도로 이해하기 바란다)

 

OAuth에서는 데이터의 변조 방지를 위해 해시 값과 디지털 서명을 사용한다.

이때 Consumer Secret 이 비밀키로 사용되며 HMAC-SHA1와 같은 해싱 알고리즘이 적용된다.

(참고로 OAuth 2.0의 경우, HTTPS를 사용하게 됨으로써 HAMC를 사용하지 않아도 된다)

 

그리고 재생 방지를 위해서는 Timestamp와 Nonce가 사용되는데,

Nonce는 모든 요청에 유일함이 보장되어야 하는 (클라이언트에서 생성하는) 임의의 문자열인데,

재생 공격시 이 값을 체크해서 (이미 이전 요청에 사용된 Nonce일 경우) 요청을 거부하게 된다.

 

그렇다면 Nonce의 유일성을 체크해야 하는 서버의 입장에서는 모든 요청의 Nonce 값을 보관하고 있어야 하는 부담이 있는데, 이는 Timestamp 체크로 부담을 제거하게 된다.

 

OAuth 인증 과정에서 포함되는 Timestamp는 요청을 생성한 시점의 타임스탬프 값으로써 1970년 1월 1일 00:00:00(GMT) 이후의 시간을 초로 환산한 숫자 값인데 이 값의 유효 범위를 지정해 기간이 경과한 요청은 거부하도록 한다.

 

즉 Nonce의 유일성 체크 이전에 Timestamp에 의한 유효 시간 체크를 먼저 함으로써 서버 입장에서는 유효시간 내의 Nonce값만을 관리하면 되는 것이다.

 

마지막으로 OAuth 인증 과정을 모두 마치면 클라이언트는 사용자의 인증을 대신하게 되는데 이때 Authorization Header라는 HTTP 헤더 값을 자격증명으로 제출하게 되는 셈이다.

 

이러한 OAuth 인증과정에 적용된 보안 개념들은 다른 어플리케이션을 개발할 때에도 참고하기에 충분한 가치가 있다고 하겠다.

 

 

Bit Flag of Enum

Posted in .NET Framework // Posted at 2013. 5. 28. 17:54
728x90

다음 문제를 풀어 보자.

 

문> 다음 코드에서 잘못 된 부분을 찾아 올바로 고치시오

 

     [Flags]
     enum LogType { File = 1,  Database = 2,  EventLog = 3 }

 

문제를 쉽게 풀었다면 패스~~

 

Enum을 정의할 때 FlagAttribute를 적용하면, 열거형 값들을 조합해서 사용할 수 있다.

예를 들어 File과 Database 두 값 모두 라는 의미로 다음과 같은 코드 사용이 가능하다

 

LogType logType = LogType.File | LogType.Database

...

bool isFile = (myType & LogType.File) == LogType.File;

 

FlagAttribute는 Enum을 BIT Flag 처리가 가능하도록 설정하는 것이다.

BIT Flag의 동작 방식을 이해하고 있었다면, 문제를 쉽게 풀었을 것이다.

 

열거자 목록에 사용된 값들의 bit가 켜고 끄는 형태 즉, On/Off 형태가 되도록 구성해야 한다.

그래야 bit의 OR / AND 연산이 제대로 동작하게 되는 것이다.

 

다시 말해, 각 값들의 비트 구성이 다음과 같도록 구성해야 한다.

 

 

따라서 정답은 다음과 같다.

[Flags]
enum LogType { File = 1, Database = 2, EventLog = 4 }

 

이제 다음 코드의 의미를 다시 보면,

LogType logType = LogType.File | LogType.Database

 

정수 1과 2(비트 1과 10)의 비트를 (OR 연산으로) 모두 ON 상태로 만드는 것이다.

 

그리고 다음 연산으로 자신의 비트만 켤수 있도록 비교하게 된다.

(myType & LogType.File) == LogType.File

 

----------------------------------------------------------------------------------------

 

여기서 좀 더 세련된 코딩을 하자 치면...

 

이러한 비트 구성은 2의 제곱형태가 되는 걸 알 수 있다.

 

 

 

즉 비트가 왼쪽으로 한 칸씩 쉬프트되는 구조 이므로 다음과 같이 코딩할 수 있겠다

 

[Flags]
enum LogType { File = 1 << 0, Database = 1 << 1, EventLog = 1 << 2 }

 

-------------------------------------------------------------------------------------

 

마지막으로 전체 선택은 비트를 모두 켠(ON) 것이고,

그 반대는 비트를 모두 끈(OFF)한 것이므로 다음과 같이 코딩하면 된다.

[Flags]
enum LogType

{

  None = 0,  

  File = 1 << 0, Database = 1 << 1, EventLog = 1 << 2,

  All = File | Database | EventLog  

 }

 

열거자 값이 많고 계속 늘어날 수 있는 경우 모든 비트가 켜진 정수의 최대값을 지정해되 된다.

All = Int32.MaxValue

 

 

 

 

 

'.NET Framework' 카테고리의 다른 글

IOC using Ninject  (0) 2013.06.18
Security in OAuth  (0) 2013.05.29
TimeStampHasCreationTimeInFuture in WCF Security 2  (0) 2013.05.23
TimeStampHasCreationTimeInFuture in WCF Security  (0) 2013.05.14
serviceThrottling in WCF  (0) 2013.05.13

TimeStampHasCreationTimeInFuture in WCF Security 2

Posted in .NET Framework // Posted at 2013. 5. 23. 16:03
728x90

 

<system.serviceModel>
    <services>     
      <service name="WCFTestServices.Service1" behaviorConfiguration="MyServiceBehavior">


        <endpoint contract="WCFTestServices.IService1" address="" binding="customBinding" bindingConfiguration="MyCustomBinding" />
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />


      </service>
    </services>

    
    <bindings>     
      <!--
      <wsHttpBinding>
        <binding receiveTimeout="00:10:00">            
          <security mode="Message">
            <message clientCredentialType="UserName" negotiateServiceCredential="true" />
          </security>
          <readerQuotas maxArrayLength="5242880" maxStringContentLength="10485760" maxBytesPerRead="20971520" />
        </binding>
      </wsHttpBinding>
      -->
      <customBinding>
        <binding name="MyCustomBinding">
          <textMessageEncoding />
          <security authenticationMode="UserNameForCertificate">
            <localClientSettings maxClockSkew="01:00:00" />
            <localServiceSettings maxClockSkew="01:00:00" />
            <secureConversationBootstrap>
              <localClientSettings maxClockSkew="01:00:00" />
              <localServiceSettings maxClockSkew="01:00:00" />
            </secureConversationBootstrap>
          </security>
          <httpTransport />
        </binding>
      </customBinding>


    </bindings>    


    <behaviors>
      <serviceBehaviors>
        <behavior name="MyServiceBehavior">          
          <serviceMetadata httpGetEnabled="True" httpsGetEnabled="True" />
          <serviceDebug includeExceptionDetailInFaults="False" />         
          <serviceSecurityAudit auditLogLocation="Application" serviceAuthorizationAuditLevel="Failure" messageAuthenticationAuditLevel="Failure" />
          <serviceCredentials>
            <userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="WCFTestServices.CustomUserNameValidator, WCFTestServices" />           
            <serviceCertificate findValue="CN=wcftest.webzen.com"  storeLocation="LocalMachine" storeName="My" />
          </serviceCredentials>
        </behavior>
      </serviceBehaviors>     
    </behaviors>


  </system.serviceModel>

 

 

참고로 클라이언트에서 서버 인증서 루트 인증 기관 체인 유효성 체크 안하게 할려면 아래와 같이...

<behaviors>
        <endpointBehaviors>
          <behavior name="MyEndpointBehavior">
            <clientCredentials>
              <serviceCertificate>
                <authentication certificateValidationMode="None" />
              </serviceCertificate>
            </clientCredentials>          
          </behavior>
        </endpointBehaviors>
      </behaviors>

'.NET Framework' 카테고리의 다른 글

Security in OAuth  (0) 2013.05.29
Bit Flag of Enum  (0) 2013.05.28
TimeStampHasCreationTimeInFuture in WCF Security  (0) 2013.05.14
serviceThrottling in WCF  (0) 2013.05.13
Stored Procedure return value in Entity Framework  (0) 2013.05.09

TimeStampHasCreationTimeInFuture in WCF Security

Posted in .NET Framework // Posted at 2013. 5. 14. 18:37
728x90

WCF 서비스의 보안 모드를 Message 보안으로 하고 클라이언트 자격증명 타입을 UserName으로 한 후,

테스트 X.509 인증서를 생성해서 자격 증명, 암호화, 디지털 서명 등이 잘 되는지 확인을 완료했다.

 

그리고 알파 서버로 서비스를 셋팅하고 테스트를 해 보니 보안 예외가 계속 발생한다.

 

이벤트 로그를 보면 다음과 같은 로그가 남겨져 있다.

 

MessageSecurityException: The security timestamp is invalid because its creation time ('2013-05-14T03:40:51.287Z') is in the future. Current time is '2013-05-14T03:33:46.020Z' and allowed clock skew is '00:05:00'.

 

개발 PC에 서비스와 클라이언트를 모두 구동할 때는 발생하지 않던 것이, 서버를 옮긴 후 발생하는 것이다.

서버와 클라이언트 PC의 시간차에 의한 보안 예외가 발생한 것이다.

 

즉 메시지가 생성된 시점의 타임스탬프의 유효기간을 체크함으로써 재 전송 공격(Replay Attacks)을 방지하기 위한 보안 메커니즘에 위배된다는 것이다.

 

그런데 이 보안 매커니즘이 대략 난감한 상황이 될 수도 있다.

클라이언트와 서버의 시간이 어쩔 수 없이(?) 다를 수 밖에 없는 환경이거나 안정적인 타임 서버 기반으로 시간 동기화를 하지 못하는 경우에 어느 시점에 시간 차가 날지 예측할 수 없기 때문이다.

 

기본 보안 설정에서는 두 서버의 허용 시간 차가 5분이다.

 

서버와 클라이언트 시간을 동기화 해 주지 않는 이상 5분 이라는 최대 허용 시간을 늘릴 수 밖에 없는데...

문제는, 이 것이 간단히 속성 값 변경으로 될 수 없다는 것이다.

 

WCF의 커스텀 바인딩을 통해 최대 허용 시간을 변경할 수 있다고 하니, 대략 귀찮아 지게 생겼다.

 

http://msdn.microsoft.com/en-us/library/ms733063.aspx

http://www.danrigsby.com/blog/index.php/2008/08/26/changing-the-default-clock-skew-in-wcf/

http://msdn.microsoft.com/en-us/library/aa738468.aspx

 

 

 

'.NET Framework' 카테고리의 다른 글

Bit Flag of Enum  (0) 2013.05.28
TimeStampHasCreationTimeInFuture in WCF Security 2  (0) 2013.05.23
serviceThrottling in WCF  (0) 2013.05.13
Stored Procedure return value in Entity Framework  (0) 2013.05.09
Generic DataContract in WCF  (0) 2013.05.09

serviceThrottling in WCF

Posted in .NET Framework // Posted at 2013. 5. 13. 12:23
728x90

WCF 서비스의 동시 호출, 세션, 인스턴스 수를 제한하는 serviceThrottling 항목의 기본 값에 혼동이 있다.

 

자료를 찾아보면, 다들 조금씩 다르게 안내한다는 혼란만 가중시키고 있다.

http://social.msdn.microsoft.com/forums/en-US/wcf/thread/110d31c2-9468-4a02-bcf6-4974f349d4e0

 

MSDN을 다시 확인해 본다.

이 항목의 세 가지 기본 값은 .NET Framework의 버전에 따라 기본 값의 변경이 있는 듯 하다.

 

.NET Framework 3.5

http://msdn.microsoft.com/ko-kr/library/vstudio/ms731379(v=vs.90).aspx

 

.NET Framework 4.0

http://msdn.microsoft.com/ko-kr/library/vstudio/ms731379(v=vs.100).aspx

 

.NET Framework 4.5

http://msdn.microsoft.com/ko-kr/library/vstudio/ms731379(v=vs.110).aspx

 

결국 최신 버전인 4.5을 기준으로 각각의 기본 값을 정리하면,

maxConcurrentCalls = 16 * processor count

maxConcurrentInstances = maxConcurrentCalls  + maxConcurrentSessions

maxConcurrentSessions = 100 * processor count

Stored Procedure return value in Entity Framework

Posted in .NET Framework // Posted at 2013. 5. 9. 18:50
728x90

Entity Framework 는 다 좋은데, 저장 프로시저의 return 값을 처리할 수 없다는게 단점이다.

 

물론 시각에 따라서 이것이 단점이냐 하는 것은 논란(?)의 소지가 있겠지만...

실제로, Entity Framework가 SQL을 대체하는 것이 아니라 ORM 프레임워크이기 때문에 SQL의 모든 기능을 지원하지 않는 것이 단점이라고 단정할 수 없다는 예기가... 인터넷 상에 존재한다.

 

하지만 개인적으로 참으로 아쉬운 부분이다.

이미 만들어진 저장 프로시저 들이 return value를 많이 사용하고 있는 상황에서,

모델 계층을 Entity Framework 로 마이그레이션 하려고 하니 더욱 아쉬운 생각이 든다.

 

특히 MS의 또 다른 기술인 LINQ TO SQL에서는 return value를 지원하기에, Entity Framework의 버전 업에 해당 기능이 추가 되기를 기대해 보지만 아직인 듯 하다.

 

그렇다고 Entity Framework를 도입하려고 기존에 잘 운영되고 있는 수 많은 저장 프로시저들을 select 혹은 ouput 변수로 수정한다는 것도 현실적이지 않다.

 

그렇다면 return value 처리를 위해서만 별도로 LINQ TO SQL 혹은 ADO.NET을 사용할 수도 있겠지만,

MODEL 계층의 기술 기반이 복잡해지고 이중화 된다는게 맘에 들지 않는다.

 

그럼. 마지막으로 사용할 수 있는 카드는,

어떤 식으로든 Entity Framework 상에서 저장 프로시저의 return value를 처리하기만 하면 된다.

 

다시 말하지만 '어떤 식으로든...' .

 

다음의 코드는 Entity Framework로 저장 프로시저의 return value를 반환받는 코드이다.

썩 맘에 들진 않지만, 불가피한 경우에 고민해 볼 만 하다.

 

-- 저장 프로시저

CREATE PROC [dbo].[USP_Test]
AS
BEGIN  
 return 5
END

 

//Entity Framework로 저장프로시저의 return value

var returnValueParam = new SqlParameter();
returnValueParam.ParameterName = "@returnValueParam";
returnValueParam.SqlDbType = SqlDbType.Int;
returnValueParam.Direction = ParameterDirection.Output;
var data = context.Database.SqlQuery<int>("EXEC @returnValueParam = [dbo].[USP_Test]", returnValueParam);

               

try
{
        data.SingleOrDefault();                   
}
catch { }

 

string returnValue = returnValueParam.Value.ToString();

 

 

 

Generic DataContract in WCF

Posted in .NET Framework // Posted at 2013. 5. 9. 11:53
728x90

WCF 서비스의 반환 값으로 사용자 정의 객체를 사용한다.

WCF 서비스와 클라이언트의 데이터 계약을 위한 DataContract Attribute 설정을 통해 시리얼라이즈 되어 통신이 가능해 진다.

 

그런데 이 반환 객체의 멤버 중, Collection 타입의 멤버는 내부 Item 타입만 다를 뿐이라서 상위 타입 선언을 통해 하나의 클래스만 유지하고 싶었다.

 

예를 들어서 이런 식이다.

[DataContract]

public class ReturnCollectionCategory : ReturnBaseObj
{
       [DataMember]
       public IList<Category> ResultCollection { get; set; }

}

 

[DataContract]

public class ReturnCollectionProduct : ReturnBaseObj
{
       [DataMember]
       public IList<Product> ResultCollection { get; set; }

}

 

위의 코드를 보면, ResultCollection 프로퍼티의 아이템 타입만 다를 뿐 나머지는 모두 동일하다.

따라서 최상위 타입인 System.Object로 아래와 같이 처리하려 했다

 

[DataContract]

public class ReturnCollectionCategory : ReturnBaseObj
{
[DataMember]
public IList<System.Object> ResultCollection { get; set; }

}

 

이렇게 하면 하나의 클래스를 유지하면서 다형적으로 처리할 수 있게 된다.

그런데 문제는 WCF 환경에서 이 객체를 원격 통신용 객체로 이용할 때 발생한다. System.Object 타입을 시리얼라이즈 할 수 없다는 것이다.

 

그래서 아래와 같이 지네릭을 이용하기로 한다.

지네릭을 이용하면, 객체 생성 시점에 타입이 결정되기 때문에 원격 통신에도 문제없이 잘 동작한다.

[DataMember] 
public class ReturnCollectionObj<T> : ReturnBaseObj
{
     [DataMember]
     public IList<T> ResultCollection { get; set; }

 

추가로, 이렇게  서비스 단에서 지네릭 기반으로 생성된 객체를 원격으로 전달받는 클라이언트에서는 지네릭 버전이 아니라 이미 결정된 객체 이름으로 전달받게 된다.

 

즉 서비스 참조로 생성된 프록시 객체를 보면 지네릭 버전의 객체 이름에 임의의 문자열이 추가된 것을 확인할 수 있다. 실제 서비스 환경에서는 이러한 상황이 달갑지 않다.

따라서 아래와 같이 이름을 지정하면 좋을 것이다.

 

[DataContract(Name="ReturnCollectionObj{0}")] 

public class ReturnCollectionObj<T> : ReturnBaseObj
{
[DataMember]
public IList<T> ResultCollection { get; set; }
}

 

이렇게 하면 클라이언트에서 반환 받는 객체 이름은 타입이 결정된 이름이 붙여 진다.

예를 들어 Product 타입으로 객체를 생성했다면, ReturnCollectionObjProduct 가 된다.

 

 

 

 

WCF Data Service VS ASP.NET Web API

Posted in .NET Framework // Posted at 2013. 1. 8. 14:39
728x90

기존 프로젝트에 데이터 제공을 목적으로 하는 중계 서비스가 존재하고 있다

 

수 년전에 개발된 거라,

개발 모델이 과거에 머물러 있고 이기종간 다중값의 데이터 교환을 위해 2차원 배열에 의존하고 있는 구조이다.

 

이 서비스를 좀 더 진보된 형태로 개선하기 위한 프로젝트를 착수하게 되었다

 

큰 틀에서의 개선 목적은 다음과 같다

- 더 효율적인 상호 운영성

- 보다 진보된 개발 모델 차용

 

그리고 좀 더 세부적인 것까지 본다면 아래 요건도 고려되어야 한다

- 제공되는 데이터의 가공 용이성 

- 기존 서비스의 마이그레이션 용이성

- 더 좋은 생산성과 유지보수성

- 비즈니스 기능 외 인증,보안 필터와 같은 추가 로직의 삽입 용이성

 

닷넷 개발환경의 관점에서 대략 아래와 같은 개발 모델로 압축 시킬 수 있다

1) WCF 웹 프로그래밍 모델

2) WCF Data Service

3) ASP.NET Web API

 

이 세가지 중, oData 질의를 지원하는 것은 2),3)번이다.

또한 WCF Web API는 ASP.NET Web API로 대체되었다는 MS의 취지와 보다, 진보된 개발 모델 차용이라는 목적에 충실할 경우에도 2),3)번으로 압축할 수 있다

 

그렇다면 과정 WCF Data Service로 할 건인가? ASP.NET Web API로 할 것인가에 귀착된다.

두 개발 모델의 차이점과 선택 기준을 살펴 보기 위해 관련 자료를 검색해 봤다

 

먼저 oData 개념의 근원지이자 닷넷 환경의 개발사인 MS의 기술 자료부터 살펴 보고,

 

http://msdn.microsoft.com/en-us/data/odata

 

이 사이트의 포럼에 검색을 수행해 본다.

http://social.msdn.microsoft.com/Search/en-US/data?query=web%20api&rq=meta:Search.MSForums.GroupID(787c8d54-d241-48d2-8522-bcc5d7e41315)+site:microsoft.com&rn=All+Data+Platform+Development+Forums

 

http://www.codeproject.com/Articles/341414/WCF-or-ASP-NET-Web-APIs-My-two-cents-on-the-subjec

 

그리고 WCF 자체와 비교한 아래 글도 참고할 만 하다

http://mattmilner.com/Milner/Blog/post/2012/02/28/WebAPI-or-WCF.aspx

 

ASP.NET Web API에 대한 스콧 형님의 간단한 단상도 볼 만 하다

http://weblogs.asp.net/scottgu/archive/2012/02/23/asp-net-web-api-part-1.aspx

 

구글의 아래 키워드로 검색하면 적당한 참고 자료를 찾을 수 있다

검색 키워드: wcf data service vs web api

 

기술적인 정보와 참고 자료로 선정하는데 적잖은 혼란을 느끼던 중,

간과해서는 안될 매우 중요한 팩터인 '실제 구축하는 시스템의 성격'이라는 축을 고민하기에 이르렀다

 

실제 구축하는 시스템의 성격은 대략 아래와 같다

 

1) 조회 90%, 입력/수정: 10%

전체 서비스에서 대략 10%를 제외하면 모두 데이터 제공 즉 조회 기능이다.

 

2) 한정된 데이터 제공

선택의 기준에서 아주 중요한 부분이란 생각이 들었다.

현재 개선을 하고자 하는 시스템의 경우 보안 목적으로 중계 서비스 역할을 하고 있다

즉 데이터 제공이 목적이긴 하지만 본질은 클라이언트가 직접 데이터 저장소에 액세스 할 수 없다는 목적을 달성하기 위해 만들어진 중계 서비스인 것이다. 그러기에 데이터를 광범위하게 제공하기 보다는 한정된 데이터를 특정 로직에 의해 가공해서 제공하는 성격이 짙다는 것이다.

 

WCF Data Service의 경우 oData 질의를 필수로 동반하기에, 데이터 제공의 한정을 위해서는 데이터 모델 차원에서 규칙을 부여할 수 밖에 없다. 반면 Web API의 경우 oData 질의는 선택사항일 수 있고 상호 협의된 형태의 데이터 반환만 가능하도록 비즈니스 처리가 가능하다.

 

WCF Data Serivce가 Web API에 비해 보다 더 깊은 oData를 지원한다고는 하지만, 실제 시스템의 활용도 측면에서는 어쩌면 불필요한 요소일지도 모른다.

 

WCF Data Service를 놓지기 싫었던 이유 중 하나는,

클라이언트가 닷넷 기반일 경우 데이터 질의를 LINQ 쿼리로 직접 할 수 있다는 부가적인 장점이었는데 이 부분은 포기해도 무방한 사소한 장점일 수 있다.

 

그리고 개발 생산성 측면에서도 WCF Data Service가 더 적은 코드를 요구하지만 데이터 원본 자체의 넓고 다양한 제공일 경우가 아니고서는(데이터 원본 대비 한정되고 가공된 데이터 제공) 그 가치가 크지 않겠다는 판단이다.

 

MS가 두 가지 유사한 개발 모델을 제공해서 약간의 혼동이 있으나, 결국 중요한 것은 두 개발 모델의 설계 사상과 특징에 기반한 현재 시스템 성격을 투영해 선택하는 것이 최선이 아닐까 한다.

 

'.NET Framework' 카테고리의 다른 글

Stored Procedure return value in Entity Framework  (0) 2013.05.09
Generic DataContract in WCF  (0) 2013.05.09
LING to SQL에서 다중 결과 셋 받기  (0) 2012.11.13
MVC 다중 폼 유효성 체크  (0) 2012.09.07
Razor 구문  (0) 2011.07.19

LING to SQL에서 다중 결과 셋 받기

Posted in .NET Framework // Posted at 2012. 11. 13. 20:04
728x90

현재 카테고리에 맞지 않는 영역이지만....

관리도 안될게 뻔한 카테고리를 새로 만들고 싶지 않아 ASP.NET MVC 카테고리에 꺼적인다

 

ASP.NET MVC로 DB 관련 개발할 때,

EntityFramework 나 LING to SQL을 사용하곤 한다

 

근데 이 두 넘은 다 좋은데, 저장 프로시저의 다중 결과 셋을 지원하지 않는다.

 

음.. 지원하지 않는다기 보다는 자동으로 생성되는 designer.cs에만 의존하면 그렇다고 해야 정확한 표현인 듯 싶다.

 

예를 들어,

게시판 글 보기 페이지일 경우, 글 내용과 해당 글에 대한 댓글 정보를 하나의 프로시저에서 두 개의 결과 셋으로 반환하도록 할 경우 처음으로 반환된 결과 셋이 자동으로 바인딩 되는 것이다

 

물론 이런 상황이라면 글 내용은 OUTPUT 변수로 받고 댓글 리스트는 결과셋으로 받아서 해결 가능하다. 실제로 이렇게 사용한 적이 꽤 많다.

 

그러나 문제는 정말로 리스트형태의 결과 셋이 여러 개일 경우이다. 이젠 더 이상 OUTPUT 반환 값에 의존할 수 없게 되었다.

 

저장프로시저의 시나리오는 대략 이렇다.

1. OUTPUT 변수로 몇 가지 값을 반환한다

2. 3개의 결과 셋(레코드 셋이라는 표현을 좋아하는 사람이 있다)도 같이 반환한다

 

그러니깐, 의미적으로 총 3가지 형태의 결과값을 받고 싶은 게다

(OUTPUT 반환 값 + 결과셋1 + 결과셋2 + 결과셋3)

 

물론 결과셋의 개수는 중요치 않다. 두 개 이상의 결과셋을 반환한다는 게 중요하다

 

이제 이 프로시저를 LINK to SQL로 연동해서 결과를 처리하고 싶다

 

먼저 desiner.cs 가 자동 생성한 아래의 코드를 보자

 [global::System.Data.Linq.Mapping.FunctionAttribute(Name="dbo.USP_GetGameView")]
  public ISingleResult<USP_GetGameViewResult> USP_GetGameView([global::System.Data.Linq.Mapping.ParameterAttribute(Name="GameNo", DbType="SmallInt")] System.Nullable<short> gameNo, [global::System.Data.Linq.Mapping.ParameterAttribute(Name="GameName", DbType="NVarChar(100)")] ref string gameName, [global::System.Data.Linq.Mapping.ParameterAttribute(Name="Score", DbType="Decimal(2,1)")] ref System.Nullable<decimal> score, [global::System.Data.Linq.Mapping.ParameterAttribute(Name="Description", DbType="NVarChar(400)")] ref string description)
  {
   IExecuteResult result = this.ExecuteMethodCall(this, ((MethodInfo)(MethodInfo.GetCurrentMethod())), gameNo, gameName, score, description);
   gameName = ((string)(result.GetParameterValue(1)));
   score = ((System.Nullable<decimal>)(result.GetParameterValue(2)));
   description = ((string)(result.GetParameterValue(3)));
   return ((ISingleResult<USP_GetGameViewResult>)(result.ReturnValue));
  }

 

매우 복잡한(?) 코드에 의미 두지 말자. designer.cs 가 자동 생성해 준 코드를 그대로 옮긴 것이니..

그리고 저장프로시저의 원형에 의미도 두지 말자. 그냥 대충 다중 셋을 반환하는 저장프로시저라고 생각하면 된다. 중요한 것은 ISingleResult를 반환한다는 점이다. 그래서 다중 결과 셋의 첫 번째 결과만 바인딩 되는 것이다.

 

그렇다면 해결책은 ISingleResult가 아닌 다중 셋을 반환할 수 있도록 IMultipleResults을 반환하면 된다.

이를 위한 새로운 메서드를 정의해야 하는 데, designer.cs가 자동 생성한 클래스를 사용할 생각은 말아야 한다. DB 연동이 추가/제거/변경 될 경우 이 클래스는 다시 생성되기 때문에 자신의 코드가 모두 사라질 수 있기 때문이다.

 

그렇기 때문에 파티셜 클래스 기법을 이용하면 된다.

 

designer.cs가 생성한 클래스와 동일한 이름으로 파티셜 클래스를 선언하고 다중 셋을 반환할 수 있도록 다음과 같이 작성할 수 있다

public partial class MobileDBDataContext
    {
        [Function(Name = "dbo.USP_GetGameView")]
        [ResultType(typeof(ScreenShot))]
        [ResultType(typeof(Video))]
        [ResultType(typeof(CommentInfo))]       
        public IMultipleResults GetGameView(short? gameNo, ref string gameName, ref decimal? score, ref string description)
        {
            IExecuteResult result = this.ExecuteMethodCall(this, ((MethodInfo)(MethodInfo.GetCurrentMethod())), gameNo, gameName, score, description);
            gameName = ((string)(result.GetParameterValue(1)));
            score = ((System.Nullable<decimal>)(result.GetParameterValue(2)));
            description = ((string)(result.GetParameterValue(3)));

            return (IMultipleResults) result.ReturnValue;
        }
    }

 

자동생성된 이전의 코드와 거의 유사한 것을 알 수 있다

다른 점이라면 IMultipleResults 타입을 반환한다는 점과, 다중 셋의 결과를 자동으로 바인딩 하기 위한 클래스 선언이 있다는 점이다.

 

LINQ to SQL을 사용하면서 다중 결과 셋에 대한 목마름이 있었던 사람은 위의 코드만으로도 그 즉시 적용가능하리라 본다.

 

 

 

 

'.NET Framework' 카테고리의 다른 글

Generic DataContract in WCF  (0) 2013.05.09
WCF Data Service VS ASP.NET Web API  (2) 2013.01.08
MVC 다중 폼 유효성 체크  (0) 2012.09.07
Razor 구문  (0) 2011.07.19
ASP.NET Razor  (5) 2010.12.13

MVC 다중 폼 유효성 체크

Posted in .NET Framework // Posted at 2012. 9. 7. 17:10
728x90

MVC 기반의 사용자 화면에서는 다중 입력 폼(form)을 허용한다.

사실 허용한다기 보다는 일반적인 HTML 페이지의 동작을 그대로 수용한 것이다

 

이전 개발 환경인 ASP.NET WebForm에서는 서버 측 폼을 기반으로 작업을 많이 했으며 한 페이지에 오직 하나의 서버 폼이 존재할 수 있었다

 

MVC환경에서는 서버 측 폼이라는 개념을 사용하지 않기에 (일반적인 HTML 규칙과 같이) 다중 폼의 사용이 가능한 것이다.

 

그리고 MVC에서는 폼 입력에 대한 유효성 체크와 유효성 메시지 노출을 자동화 시켜 주는 편리한 기능도 있다

 

그런데 이 둘(다중 폼 & 유효성 체크)을 조합해서 사용하다 보면, 의도치 않는 결과를 만나곤 한다

예를들어, 다중 폼 환경에서 특정 하나의 폼의 유효성 체크가 다른 폼의 유효성 체크와 통합되어 표시되는 현상 같은 것이다.

 

두 개의 폼의 입력 필드 이름을 다르게 해서 각각의 입력 컨트롤의 유효성 실패 표시는 쉽게 구분할 수 있으나

@Html.ValidationSummary와 같은 유효성 메시지를 각 폼에 독립적으로 사용하고자 할 때는 기대처럼 동작하지 않는다. (파샬뷰로 분리된 다중 폼이라고 해도 결국 하나로 합쳐지기 때문에 현상은 동일하다)

 

이에 대한 솔루션을 제공하는 아티클이 있다

 

ASP.Net MVC: Validation a on page with multiple forms

 

간단하게 설명하자면,

필드 이름을 폼 마다 (접두어를 두어) 구분하고, 사용자 정의 HtmlHelper를 만들어서 자신의 이름과 일치하는 폼에 대해서만 유효성 체크 Summary(ValidationSummary)를 반환하도록 한 것이다. 그리고 뷰에서는 이 사용자 정의 HtmlHelper를 통해 유효성 메시지를 표시하도록 한다.

 

이러한 접근 방식이 마음에 들지 않는다면, 다음과 같이 설계 할 수 있겠다

 

1) 다중 폼을 사용하지 않기

2) 다중 폼을 사용하되, MVC 유효성 체크에 의존하지 않기

3) 위 아티클의 방식을 나름 응용해서 사용하기

(입력 폼에 자동 모델 바인딩을 사용할 경우, 입력 필드 이름에 의존적이기 때문에 필드 이름을 변경하는 게 불편할 수도 있다)

'.NET Framework' 카테고리의 다른 글

WCF Data Service VS ASP.NET Web API  (2) 2013.01.08
LING to SQL에서 다중 결과 셋 받기  (0) 2012.11.13
Razor 구문  (0) 2011.07.19
ASP.NET Razor  (5) 2010.12.13
웹 리소스 요청 막기, HttpNotFoundHandler  (0) 2010.08.09

Razor 구문

Posted in .NET Framework // Posted at 2011. 7. 19. 10:05
728x90
체계적으로 한번은 봐야하는 Razor!. 다른 곳에 집중하느라 아직 제대로 훓어 보지 못하고 있다.
일단 간단한 Razor 구문을 정리해 본다.

1. Razor 코드 블럭
Razor 에서는 골뱅이(@) 키워드가 핵심이다.
과거 ASP.NET 에서 인라인 코드 작성할 때 사용했던 아래와 같은 구문이,
<script runat="server"> ... </script>

Razor에서는 다음과 같이 축약되었다. 과거에 존재했던, 스크립트 선언문과 서버로직이라는 명시가 필요 없어져 매우 간단해졌다
@{ ... }

이 코드 블럭에 닷넷 로직을 구현할 수 있다. 대략 다음과 같이...
@{
int i = 10;
Response.Write(i.ToString());
    }

여기서 한가지 주의할 점은, 코드 블럭을 시작하는 @와 중괄호({) 사이에는 공백이 허용되지 않는다는 것이다.
즉, 아래와 같이 작성하면 런타임 오류를 만나게 된다.
@
{
   ...
}

2. Razor 주석
다른 구문을 보기 전에 주석문 정의를 살펴보자. Razor의 코드 블럭안에서의 주석은, 이전과 동일하게 한줄 주석은 //, 여러줄 주석은 /* ... */ 로 가능하다. 대략 다음과 같다.
@{
        //주석...
        /*
        int i = 5;
        Response.Write(i.ToString());
        */
    }

코드 블럭 바깥에서의 주석 역시 지원하는데 다음과 같이 사용가능하다. @와 *를 같이 사용한다.
@*
       Razor 주석
*@

참고로 당연한 말이지만, Razor 주석은 HTML 주석(<!-- ... -->과는 달리, 서버측 주석이기 때문에 페이지 페이지 랜더링 후, DOM으로 생성되지 않는다.

3. 변수 출력
이전 환경의 ASP.NET 에서는 서버 로직에 구현된 변수를 페이지에 출력하기 위해서는 코드 블럭 안에서는Response.Write 메서드,  코드 블럭 바깥에서는 <%= 변수 %> 구문을 사용했었다. 그러나 Razor 에서는 두 경우 모두 변수에 @만 붙여주면 된다.

@{  
       var str = "Hello, Razor!";  
       @str;
    }

코드 블럭 바깥에서도 단순히 아래와 같이 변수를 출력할 수 있다.
@str

4. Razor 구문 안의 일반 텍스트
만일 Razor 구문안에서 일반 텍스트를 한번에 표현하고 싶을 경우 @: 키워드를 사용할 수 있다.
이전 같으면 변수와 문자열을 + 연산자로 합치고 Response.Write 해야 할 것을 다음과 같이 간단해졌다.
@{      
       var str = "Hello, Razor!";
       @:Plane Text... @str;
   }

5. Razor 구문 안의 마크업 태그
일반 텍스트와는 달리 태그의 경우에는 Razor 구문에서 별다른 키워드 없이 바로 사용가능하다
@{      
       var str = "Hello, Razor!";
      <b>@str</b>
   }

그리고 태그와 함께라면 일반 텍스트도 바로 사용 가능하다.
@{      
       var str = "Hello, Razor!";
      <b>반갑습니다</b>
   }

Razor 엔진에서는 태그를 인식해서 특별한 구문 없이 바로 출력 가능하도록 한 것 같다.

그렇다면 Razor 엔진이 HTML 태그를 모두 기억하고 있는 것일까? 그렇진 않은 것 같다. 다음과 같이 의미없는 태그를 삽입해도 오류 없이 결과를 내뱉는걸 보니, 태그를 기억하는 것이 아니라 태그 기호( <태그>)를 인식하는 듯 하다.
@{      
       var str = "Hello, Razor!";
      <btt>@str;</btt>
   }

한가지 주의할 점은, Razor 구문안에 태그를 혼용할 경우 반드시 여는 태그와 닫는 태그가 쌍으로 존재해야 한다. 다시 말해 다음과 같은 태그 작성은 허용되지 않는다.
@{      
       var str = "Hello, Razor!";
      <b>@str;
   }

6. 조건문
조건문 역시 @ 키워드와 함께 사용할 수 있다. 다음과 같이.
@if(1==1) {
        <font size=@i>@str</font>
    }

7. 반복문
반목문도 사용패턴이 동일하다. 아래 코드는 목록을 만드는 반복문 예를 보여준다.
<ul>
    @for (int i = 0; i < 5; i++)
    {

        <li>@i</li>
    }
  </ul>

동일한 예를 while 반목문으로 처리하는 다음과 같다.
<ul>
    @{
        int i = 0;
        while(i < 5)
        {

            <li>@(i++)</li>
        }
     }
 </ul>

8. 키워드가 아닌 문자로써 '@' 사용하기
Razor 에서는 @ 자체가 키워드이기 문자 출력에 바로 사용할 수 없다. 즉 실제 '@'라는 문자를 출력하고 싶을 경우에는 다음과 같이 @@ 연속해서 두번 써 줘야 한다. 이렇게 하면 Razor 엔진은 @를 키워드로 인식하지 않고 일반 문자로 출력해 준다.
<span>@@난 그냥 골뱅이 문자야</span>


지금까지 아주 기본적인 Razor 구문을 살펴 봤다. 실제 구현시에는 더 많은 상황이 닥칠 것이다.
그럴땐 아래 asp.net 사이트 글을 참조하도록 하자. Razor 구문의 대부분의 규칙을 확인할 수 있다

=> Introduction to ASP.NET Web Programming Using the Razor Syntax

'.NET Framework' 카테고리의 다른 글

LING to SQL에서 다중 결과 셋 받기  (0) 2012.11.13
MVC 다중 폼 유효성 체크  (0) 2012.09.07
ASP.NET Razor  (5) 2010.12.13
웹 리소스 요청 막기, HttpNotFoundHandler  (0) 2010.08.09
ASP.NET MVC, 폼 데이타 전송하기  (0) 2010.08.06

ASP.NET Razor

Posted in .NET Framework // Posted at 2010. 12. 13. 18:24
728x90

아... 이 코드의 유연함(?)을 보라!


[출처: 코드파티 ASP.NET Razor 동영상 강의 중...]

과거 ASP의 스파게티가 생각나지 않는가?
ASP.NET의 또 하나의 새로운 시도 Razor(레이저라고 발음)의 코드이다

빠른 개발, 쉬운 개발을 지향하며 탄생한 레이저!
확실히 ASP.NET 웹폼의 그것과는 비교할 수 없이 유연하다

이런 코딩 스타일은 확실히 스파게티 모양새다.
시대가 변하면서 코딩 스타일을 바라보는 시각도 변하는 것 같다.

HTML 코드와 ASP.NET 코드를 철저히 분리하려고 시도하면서 칭송(?) 받았던 ASP.NET 웹폼의 코딩 스타일은 확실히 무거운 느낌이었다.

물론 Razor의 특징을 이 코드블록만으로 설명할 수는 없다.
장점이 분명 있으며 나 역시 프로젝트에 도입을 하려 한다.

그러나 일단 코딩 스타일을 스파게티로 구성할 수 있도록 열어 둔 점은 확실히 시대가 또 변했음을 느낀다

웹 리소스 요청 막기, HttpNotFoundHandler

Posted in .NET Framework // Posted at 2010. 8. 9. 12:11
728x90

ASP.NET MVC 모델에서는 (Views 폴더의) 모든 웹 리소스에 대한 직접 요청을 막아 두었다

사용자 화면(뷰)에 해당하는 Views 폴더에 있는 모든 aspx 파일에 대한 다음과 같은 요청은
모두 404 Not Found 로 처리된다
http://yourdomain.com/Views/Index.aspx

이것은 기존 웹폼 모델과 대조되는 면으로,
ASP.NET MVC에서의 사용자 화면(뷰)은 모두 컨트롤러에 의해 선택되고 랜더 되게 하기 위함이다
따라서 리소스에 대한 직접 요청은 의도적으로 막고 있는 것이다

Views 폴더의 Web.config 에 정의된 HttpNotFoundHandler
Visual Studio 에서 ASP.NET MVC 프로젝트를 생성하면 총 두개의 Web.config 가 생성된다
루트에 있는 Web.config 는 기존과 같이 응용프로그램 전역적인 설정 파일이며
Views 폴더의 Web.config 는 Views 폴더에만 적용되는 설정 파일인데, 이 파일에 정의된
HttpNotFoundHandler 가 리소스 직접 요청에 대한 Not Found 처리를 하는 HttpHandler 이다

<httpHandlers>
     <add path="*" verb="*"  type="System.Web.HttpNotFoundHandler"/>
</httpHandlers>

위와같이 정의된 핸들러에 의해 Views 폴더의 aspx 파일을 포함한 모든 직접 요청은 404로 처리된다

참고로 ASP.NET 웹폼 모델에서는 *.aspx 에 대한 처리는 System.Web.UI.PageHandlerFactory 라는
핸들러에 의해 처리되었다

HTTP 핸들러에 대한 개념과 기본 등록된 핸들러 정보는 다음 링크에서 확인할 수 있다
[ASP.NET] HTTP Handler
[ASP.NET] HttpHandler- Demo
[ASP.NET] Machine.config 미리 정의된 HttpHandler


ASPX 파일만 막기
기본 구성으로는 Views 폴더의 모든 리소스에 대한 직접 접근을 막고 있다
경우에 따라서는 Views 폴더에 ASPX 외에 다양한 웹 리소스가 위치할 수 있다

예를 들어 이미지파일이나 css, js 파일, pdf 파일 등을 들 수 있다
이러한 웹 리소스에 대한 직접 요청은 정상적으로 되길 원할 수도 있다
그렇다면 핸들러 정보를 다음과 같이 수정하여 ASPX 파일만 막도록 하면 된다

<httpHandlers>
     <add path="*.aspx" verb="*"  type="System.Web.HttpNotFoundHandler"/>
</httpHandlers>

그러나 ASP.NET MVC 모델에서는 뷰를 제외한 기타 웹 리소스를 Views 폴더에 두지 않기를 권장한다.이미지나 CSS, JS 와 같은 정적이고 공개적인 웹 리소스들은 자동으로 생성된 Content 폴더에 두는 것이다

참고: http://haacked.com/archive/2008/06/25/aspnetmvc-block-view-access.aspx

'.NET Framework' 카테고리의 다른 글

Razor 구문  (0) 2011.07.19
ASP.NET Razor  (5) 2010.12.13
ASP.NET MVC, 폼 데이타 전송하기  (0) 2010.08.06
ASP.NET MVC 에서 요청 매개변수 넘기기  (0) 2010.08.05
ASP.NET MVC, Hello World  (1) 2010.08.04

ASP.NET MVC, 폼 데이타 전송하기

Posted in .NET Framework // Posted at 2010. 8. 6. 16:38
728x90

앞서 ASP.NET MVC 에서 요청 매개변수 넘기기 에서는 URL 질의 방식에 ASP.NET MVC이
어떻게 반응하는지 알아 보았다. URL에 매개변수를 전달하는 방식은 GET 요청에 해당한다

이번에는 ASP.NET MVC이  POST 요청을 어떻게 처리하는지 알아 보도록 하자

POST 요청의 보편적인 모습은, HTML 입력 양식을 이용한 폼 데이타 제출이다
이 글에서는 간단한 회원가입 폼을 POST 로 전송하고 이를 처리하는 방법을 다룬다

ASP.NET MVC 프로젝트를 하나 생성하고 컨트롤러에 엑션 메서드를 하나 추가한다

- HomeController.cs 에 액션 메서드 추가하기
public ActionResult RegisterForm()
{            
    return View();
}

- 회원 가입 페이지 (뷰)  생성하기
그리고 이 액션 메서드에 해당하는 뷰를 생성한다
생성된 RegisterForm.aspx 뷰 페이지는 회원가입 입력 양식을 아래와 같이 정의한다
<body>
    <h1>회원가입</h1>    
    <form action="/Home/RegisterForm" method="post">        
        <p>아이디: <input type="text" name="MemberID" /></p>
        <p>비밀번호: <input type="password" name="Password" /></p>
        <p>닉네임: <input type="text" name="NickName" /></p>
        <p>이메일: <input type="text" name="Email" /></p>
        <input type="submit" value="가입하기" />           
    </form>
</body>

전형적인 폼 양식이다. 이제 프로젝트를 빌드하고 페이지를 호출해 보자
http://localhost:11102/Home/RegisterForm




- 모델 정의 하기
아직 폼 데이터를 처리 할 수 있는 것은 아니다. 회원정보에 해당하는 자료형을 모델로 정의해 보자.
이 모델을 기반으로 폼 데이터가 처리될 것이며 결과 뷰역시 이 모델을 기반으로 작성될 것이다

프로젝트의 Model 폴더에 Member.cs 라는 클래스를 만들고 다음과 같이 작성한다
이때 RegisterForm 뷰에서 정의한 폼 요소들의 Name 속성과 일대일 대응하도록 속성명을 정의하자
public class Member
{
    public string MemberID { get; set; }
    public string Password { get; set; }
    public string NickName { get; set; }
    public string Email { get; set; }

   
public void Submit()
{
        //멤버정보를 DB에 입력하는 등의 실제 작업을 처리한다
    }
}

- 컨트롤러 수정하기
그리고 폼 데이타 수신을 위해 컨트롤러를 다음과 같이 수정한다
AcceptVerbs 어트리뷰트를 통해 GET 과 POST 요청을 직접 명시한다
이렇게 하면 get, post 요청에 적절한 액션메서드가 선택될 것이다
즉 동일한 {controller}/{action} 요청이지만 get, post 요청에 따라 (이름은 같지만) 다른 메서드가
호출되는 것이다

또한 각 액션 메서드의 반환 값을 보면,
get 요청에는 기본 뷰인 RegisterForm.aspx 가 랜더되도록 했으며(return View();),
post 요청에는 명시적으로 이름을 지정하여 RegisterComplete.aspx 뷰가 랜더되도록 하였으며
mebmer 객체를 뷰로 전달하고 있다
(return View("RegisterComplete", member))


그리고 post 요청에 해당하는 액션 메서드의 매개변수를 주의깊게 보자
모델에서 정의한 Member 객체를 post 메서드의 매개변수로 취하고 있다

ASP.NET MVC '모델 바인딩 매커니즘'은 폼 전송으로 전달된 데이터의 키(Key) 정보를 바탕으로
Member 객체의 속성명에 일대일 매칭시켜 값을 자동으로 바인딩 시켜 준다

<input type=text name=MemberID>의 값이 Member.MemberID 로 자동 바인딩이 된다는 말이다

public class HomeController : Controller
{
    [AcceptVerbs(HttpVerbs.Get)]
    public ActionResult RegisterForm()
    {            
        return View();
    }

    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult RegisterForm(Member member)
    {
        member.Submit();
        return View("RegisterComplete", member);
    }
}

- 강력한 형식의 뷰 만들기 (회원가입 완료 페이지(뷰) 생성하기)
이제 회원가입 완료 페이지에 해당하는 RegisterComplete 뷰를 생성해 보자
Controller 아무 영역에다 대고 마우스 우클릭 -> Add View 해서 뷰를 생성하는데
아래 그림과 같이 'Create a strongly-typed view' 를 선택해서 모델로 정의했던
Member 클래스를 View data class 로 선택하도록 한다

이렇게 특정 클래스를 기반으로 강력한 형식의 뷰를 만들게 되면
View data class에 정의된 객체를 뷰에 직접 랜더링 할 수 있게 된다


그리고 생성된 RegisterComplete.aspx 는 다음과 같이 작성한다
이 뷰는 강력한 형식으로 생성된 뷰이기 때문에 Model 이라는 키워드를 통해 직접 모델 데이터에
접근할 수 있게 된다
. 아래 코드는 폼 데이터로 전송되어 모델 객체에 바인딩 된 값을 다시 결과 화면에
뿌리고 있는 것이다
<body>    
    <h1>회원가입을 축하드립니다</h1>
    <p>아이디: <%= Model.MemberID %></p>
    <p>닉네임: <%= Model.NickName %></p>
    <p>이메일: <%= Model.Email %></p> 
</body>





'.NET Framework' 카테고리의 다른 글

ASP.NET Razor  (5) 2010.12.13
웹 리소스 요청 막기, HttpNotFoundHandler  (0) 2010.08.09
ASP.NET MVC 에서 요청 매개변수 넘기기  (0) 2010.08.05
ASP.NET MVC, Hello World  (1) 2010.08.04
벌써 ASP.NET MVC 3 ?  (0) 2010.08.03

ASP.NET MVC 에서 요청 매개변수 넘기기

Posted in .NET Framework // Posted at 2010. 8. 5. 13:39
728x90

이전 ASP나 ASP.NET 웹폼 모델에서는 URL의 꼬리표에 붙여 있는 매개변수를
Request 객체를 통해전달 받을 수 있었다

즉 다음과 같이 두 개의 매개변수를 URL 에 붙여서 매개변수를 전달하게 되면,
요청 URL: http://yourdomain/main.aspx?param1=value1&param2=value2

아래처럼 매개변수 정보를 취할 수 있다(ASP.NET 기준)
string value1 = Request.QueryString["param1"].ToString();
string value2 = Request.QueryString["param2"].ToString();

그렇다면 ASP.NET MVC 모델에서는 매개변수를 어떻게 전달하고 받는지 알아보도록 하자

기본은 동일하다
ASP.NET MVC 역시 이전 웹폼 모델과 동일한 형태로 매개변수를 넘기고 받을 수 있다

즉 아래와 같이 요청 URL 에 ? 꼬리표로 매개변수를 전달하면
HomeController 의 Index 액션 메서드에서 다음과 같이 매개변수에 접근할 수 있게 된다

요청 URL: http://localhost:11102/Home/Index/?name=Park&age=10

public
ActionResult Index()
{
    string name = Request.QueryString["name"].ToString();
   string age = Request.QueryString["age"].ToString(); 

    ViewData["param"] = String.Format("Hello, {0}, Your age {1}", name, age);

    return View();
}


액센메서드의 매개변수로 전달 받기
URL 에 포함되어 있는 매개변수를 액션 메서드의 매개변수로 전달 받을 수도 있다
아래 코드는 Index 매개변수를 통해 URL로 전달되는 매개변수를 전달 받고 있다


요청 URL: http://localhost:11102/Home/Index/?name=Park&age=10

public
ActionResult Index(string name, string age)

{

    ViewData["param"] = String.Format("Hello, {0}, Your age {1}", name, age);

    return View();

}

액션 메서드를 통해 매개변수를 전달 받는 것은 ASP.NET MVC 프레임워크에서 제공해 주는 기능이며
이전 형태 보다는 조금 더 ASP.MVC 스럽다고 할 수 있겠다

액션 메서드로 매개변수를 전달 받을 경우 URL 에 정의된 매개변수 명과 액션메서드에 정의된
매개변수 명이 동일해야 한다는 규칙이 있다(순서는 상관 없다. 변수 이름이 중요하다)



참고로 URL 에 매개변수가 있다고 해서 반드시 액션 메서드에 매개변수를 정의 해야 하는 것은 아니다
그리고 그 반대의 경우, 즉 액션메서드에 매개변수가 정의 되었다고 해서 반드시 URL에 매개변수를 넘겨야 하는 것은 아니다 (물론 이 두 경우는 매개변수를 사용하지 않을 경우이다)

예를 들어 URL 에 매개변수가 생략되었을 경우 액션메서드로는 null 을 전달하게 될 것이다


URL 스키마 사용자 정의
앞서 Request.QueryString 으로 매개변수를 가져오는 방식에 비해 액션 메서드의 매개변수로 가져오는 것이 보다 깔끔하고 직관적이라는하다는 것을 느낄 수 있다.
이것은 매개변수를 가져오는 측면에서 ASP.NET MVC 가 지원해 주는 부분이 되겠다

이번에는 매개변수를 전달하는 측면에서의 ASP.MVC 의 장점을 살펴 보자

앞서 요청 URL은 다음과 같은 스키마로 이루어 졌었다
http://localhost:11102/Home/Index/?name=Park&age=10

전통적인 방식으로 URL에 ? 꼬리표를 통해 매개변수를 전달하고 있다
우리는 이러한 요청 URL을 보다 깔끔하게 변경하고 싶다

ASP.NET MVC 에서는 URL 스키마를 개발자의 입맛대로(?) 재 구성할 수 있는 방법을 제공해 준다.

이는 ASP.NET MVC의 라우팅 엔진이 제공해 주는 기능인데,
Global.asax 의 RegisterRoutes 메서드의 커스트마이징을 통해 구현할 수 있다

우선 매개변수를 어떤 형태의 URL 스키마로  정의할 지 결정해야 하는데,
다음과 같이 '/(슬래쉬)'를 계속 이어가고 싶다고 가정해 보자

http://localhost:11102/Home/Index/매개변수1/매개변수2

이와 같은 URL 스키마를 실현하기 위해 Global.asax 파일에 정의된
RegisterRoutes 메서드를 약간 수정해 보자

public static void RegisterRoutes(RouteCollection routes)

{

    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

 

    routes.MapRoute(

        "Default",

 

        "{controller}/{action}/{name}/{age}", // URL with parameters

 

              new { controller = "Home", action = "Index",

name = UrlParameter.Optional,

                         age = UrlParameter.Optional }

  );

}


MapRoute 메서드의 두 번째 정보에 URL 스키마를 정의한다
우리가 결정한 URL 스키마를 실현하기 위해 /(슬래시)로 매개변수를 취할 수있도록 하였다
컨트롤러/액션메서드/name매개변수/age매개변수 형태로 정의함
=> "{controller}/{action}/{name}/{age}"


그리고 다음과 같이 각 매개변수의 옵션 여부를 지정하였다
이는 name, age 정보가 URL에서 생략될 수 있다는 의미이다
name = UrlParameter.Optional, 
age = UrlParameter.Optional

이제 구성이 완료 되었으니 다음과 같은 요청에 정상적으로 반응하게 될 것이다
http://localhost:11102/Home/Index/Park/10


만일 다음과 같은 URL을 원한다면,
http://localhost:11102/Home/Index/Park-10

다음과 같이 정의하면 된다
"{controller}/{action}/{name}-{age}"

참고로 이런 식으로 URL 스키마 재정의로 매개변수 정보를 구성하였다면,
더 이상 Request.QueryString 로 받을 수 없다는 것에 주의하자
-----------------------------------------------------------------------------------

마지막으로 URL 재구성된 상태에서 전통적인 ? 매개변수 전달을 같이 혼용하면 어떻게 될까?
다시 말해 다음과 같은 URL 요청은 어떻게 되는지 살펴 보자

요청 URL: http://localhost:11102/Home/Index/Park/10?name=Kim&age=20

액션메서드:
public
ActionResult Index(string name, string age)

{

    ViewData["param"] = String.Format("Hello, {0}, Your age {1}", name, age);

    return View();

}

이를 경우 URL 로 재구성된 매개변수 정보가 먼저 참조 된다
즉 URL 요청으로 부터 전달되는 name, age 라는 매개변수가 각각 두 개씩 이지만
URL 재구성에 의한 name=Park, age = 10 이 전달되는 것이다

그러나 만일 URL 스키마에서 매개변수 정보를 생략하고 다음과 같이 요청 된다면,
http://localhost:11102/Home/Index/?name=Kim&age=20

결과는 ?  매개변수가 전달되게 된다

ASP.NET MVC 는 라우팅 구성 정보에 의거한 적절한 매개변수를 가져올 수 없는 경우
전통적인 ? 질의 문자열에서 매개변수를 가져오게 되는 것이다