오늘은 .NET Framework 4 에서 가장 많은 충돌을 야기할 수 있는 변동 사항 중 하나를 오늘 아티클에서 잠시 다루어보기로 하겠습니다. 바로 ISerializable 인터페이스에 관한 것인데요, Serializable Attribute와 NonSerialized Attribute를 이용하여 제어하는 것 만으로는 불충분한 경우 즐겨 사용해오던 인터페이스입니다. 그렇지만 여기에 급격한 변화 (Breaking Changes)가 .NET Framework 4에 더해지게 되었는데, Partial Trust Mode로 실행되는 어셈블리에서 이 인터페이스를 구현하는 로직을 호출하게 되는 경우, ISerializable 인터페이스의 GetObjectData에 추가된 SecurityCritical Attribute에 의해 호출이 거부됩니다.
저 개인적으로는 XML-RPC의 .NET Framework 버전의 Implementation 라이브러리 (http://www.xml-rpc.net/)를 .NET Framework 4로 업그레이드하여 Windows Azure에 Deploy하면서 이러한 현상을 겪었는데, 이 라이브러리에서 제공하는 Custom Exception 클래스 상의 GetObjectData 메서드가 문제의 원인이 되었습니다. .NET Framework 4 환경에서 사용하도록 별도의 프로젝트 파일을 만들어서 Predefine Condition을 부여하여 ISerializable 인터페이스를 이용하지 않도록 코드를 수정한 이후에는 문제가 잘 해결되었습니다.
만약 ISerializable 인터페이스의 기능을 그대로 가져갈 필요가 있다면, http://msdn.microsoft.com/en-us/library/system.runtime.serialization.isafeserializationdata.aspx 의 내용을 참고하시어 ISafeSerializationData 인터페이스를 구현하는 별도의 코드를 작성해야 합니다. 아래는 MSDN Library에서 발췌한 샘플 코드입니다.
using System;
using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.Serialization;
using System.IO;
using System.Runtime.Serialization.Formatters.Soap;
using System.Security;
// [assembly: SecurityCritical(SecurityCriticalScope.Everything)]
// Using the SecurityCriticalAttribute prohibits usage of the
// ISafeSerializationData interface.
[assembly: AllowPartiallyTrustedCallers]
namespace ISafeSerializationDataExample
{
class Test
{
public static void Main()
{
try
{
// This code forces a division by 0 and catches the
// resulting exception.
try
{
int zero = 0;
int ecks = 1 / zero;
}
catch (Exception ex)
{
// Create a new exception to throw.
NewException newExcept = new NewException("Divided by", 0);
// This FileStream is used for the serialization.
FileStream fs =
new FileStream("NewException.dat",
FileMode.Create);
try
{
// Serialize the exception.
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(fs, newExcept);
// Rewind the stream and deserialize the exception.
fs.Position = 0;
NewException deserExcept =
(NewException)formatter.Deserialize(fs);
Console.WriteLine(
"Forced a division by 0, caught the resulting exception, \n" +
"and created a derived exception with custom data. \n" +
"Serialized the exception and deserialized it:\n");
Console.WriteLine("StringData: {0}", deserExcept.StringData);
Console.WriteLine("intData: {0}", deserExcept.IntData);
}
catch (SerializationException se)
{
Console.WriteLine("Failed to serialize: {0}",
se.ToString());
}
finally
{
fs.Close();
Console.ReadLine();
}
}
}
catch (NewException ex)
{
Console.WriteLine("StringData: {0}", ex.StringData);
Console.WriteLine("IntData: {0}", ex.IntData);
}
}
}
[Serializable]
public class NewException : Exception
{
// Because we don't want the exception state to be serialized normally,
// we take care of that in the constructor.
[NonSerialized]
private NewExceptionState m_state = new NewExceptionState();
public NewException(string stringData, int intData)
{
// Instance data is stored directly in the exception state object.
m_state.StringData = stringData;
m_state.IntData = intData;
// In response to SerializeObjectState, we need to provide
// any state to serialize with the exception. In this
// case, since our state is already stored in an
// ISafeSerializationData implementation, we can
// just provide that.
SerializeObjectState += delegate(object exception,
SafeSerializationEventArgs eventArgs)
{
eventArgs.AddSerializedState(m_state);
};
// An alternate implementation would be to store the state
// as local member variables, and in response to this
// method create a new instance of an ISafeSerializationData
// object and populate it with the local state here before
// passing it through to AddSerializedState.
}
// There is no need to supply a deserialization constructor
// (with SerializationInfo and StreamingContext parameters),
// and no need to supply a GetObjectData implementation.
// Data access is through the state object (m_State).
public string StringData
{
get { return m_state.StringData; }
}
public int IntData
{
get { return m_state.IntData; }
}
// Implement the ISafeSerializationData interface
// to contain custom exception data in a partially trusted
// assembly. Use this interface to replace the
// Exception.GetObjectData method,
// which is now marked with the SecurityCriticalAttribute.
[Serializable]
private struct NewExceptionState : ISafeSerializationData
{
private string m_stringData;
private int m_intData;
public string StringData
{
get { return m_stringData; }
set { m_stringData = value; }
}
public int IntData
{
get { return m_intData; }
set { m_intData = value; }
}
// This method is called when deserialization of the
// exception is complete.
void ISafeSerializationData.CompleteDeserialization
(object obj)
{
// Since the exception simply contains an instance of
// the exception state object, we can repopulate it
// here by just setting its instance field to be equal
// to this deserialized state instance.
NewException exception = obj as NewException;
exception.m_state = this;
}
}
}
}
또는, 코드의 수정을 최소화하기 위하여 설정 파일 (app.config이나 web.config)에 LegacyCASMode Property를 사용하도록 설정을 구성할 수 있습니다. 이에 대한 내용은 http://msdn.microsoft.com/ko-kr/library/tkscy493.aspx 의 내용을 참고하시면 됩니다. 아래는 MSDN Library에서 발췌한 사용 예시입니다.
<location allowOverride="false">
<system.web>
<securityPolicy>
<trustLevel name="Full" policyFile="internal" />
<trustLevel name="High" policyFile="web_hightrust.config" />
<trustLevel name="Medium" policyFile="web_mediumtrust.config" />
<trustLevel name="Low" policyFile="web_lowtrust.config" />
<trustLevel name="Minimal" policyFile="web_minimaltrust.config"/>
</securityPolicy>
</system.web>
</location>
<location allowOverride="false">
<system.web>
<trust level="Medium" originUrl="" />
</system.web>
</location>
<location allowOverride="true" path="Default Web Site/Temp">
<system.web>
<trust level="Medium" originUrl="" />
</system.web>
</location>
'Software Development > .NET Framework' 카테고리의 다른 글
| .NET 4에서의 ISerializable 인터페이스에 대한 주의 사항 (0) | 2010/06/25 |
|---|---|
| String.Format으로 할 수 있는 일들 (0) | 2010/01/31 |
| C# Image Viewer Control 샘플 (2) | 2010/01/19 |
| AppDomain 프로그래밍에 대한 이야기 (2) | 2010/01/18 |
| 네이버 오픈 API X Windows Forms Binding (2) | 2009/12/20 |
| 메서드에 PreCondition/PostCondition 구현하기 (0) | 2009/10/20 |