성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] Java - How to use the Foreign Funct...
[정성태] 제가 큰 실수를 했군요. ^^; Delegate를 통한 Bein...
[정성태] Working with Rust Libraries from C#...
[정성태] Detecting blocking calls using asyn...
[정성태] 아쉽게도, 커뮤니티는 아니고 개인 블로그입니다. ^^
[정성태] 질문이 잘 이해가 안 됩니다. 우선, 해당 소스코드에서 ILis...
[양승조
] var대신 dinamic으로 선언해서 해결은 했습니다. 맞는 해...
[양승조
] 또 막혔습니다. ㅠㅠ var list = props[i].Ge...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
[정성태] "props[i].GetValue(props[i])" 코드에서 ...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>Entity Framework 4.1 - CodeFirst 개체의 직렬화 시 순환 참조 해결하는 방법 - 두 번째 이야기</h1> <p> 지난번 이야기에서 언급했던, <br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Entity Framework 4.1 - CodeFirst 개체의 직렬화 시 순환 참조 해결하는 방법 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/1086'>http://www.sysnet.pe.kr/2/0/1086</a> </pre> <br /> 순환 참조를 해결하는 두 번째 방법을 살펴볼 텐데요. 혹시 이에 대한 힌트가 지난번 오류 메시지에서 약간 포함되어 있었다는 것을 눈치채신 분이 계실까요? ^^<br /> <br /> <div style='BACKGROUND-COLOR: #ccffcc; padding: 10px 10px 5px 10px; MARGIN: 0px 10px 10px 10px; FONT-FAMILY: Malgun Gothic, Consolas, Verdana; COLOR: #005555'> System.Runtime.Serialization.SerializationException: Object graph for type 'System.Collections.Generic.HashSet`1[[SubEntity, App_Code.xdcj8jsd, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null]]' contains cycles and <span style='color: blue; font-weight: bold'>cannot be serialized if reference tracking is disabled.</span> </div><br /> <br /> 결국 "reference tracking" 옵션을 활성화시키면 된다는 이야기인데요. 즉, WCF 서비스의 출력으로 사용되는 DataContractSerializer에 "reference tracking" 옵션을 켜두고 그것을 지정해 주면 되는 것입니다.<br /> <br /> 이와 관련해서 검색해 보면, 다행히 그 방법이 설명된 글들이 나옵니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Preserving Object Reference in WCF ; http://blogs.msdn.com/b/sowmy/archive/2006/03/26/561188.aspx Specifying Data Transfer in Service Contracts ; <a target='tab' href='https://docs.microsoft.com/en-us/dotnet/framework/wcf/feature-details/specifying-data-transfer-in-service-contracts'>https://docs.microsoft.com/en-us/dotnet/framework/wcf/feature-details/specifying-data-transfer-in-service-contracts</a> </pre> <br /> 순서를 나열해 보면 다음과 같습니다.<br /> <br /> <ol> <li>svc의 Factory 재정의</li> <li>ServiceHostFactory에 사용자 정의 ServiceHost 설정</li> <li>ServiceHost의 OnOpening 메서드를 재정의해서 사용자 정의 DataContractSerializerOperationBehavior 설정</li> <li>DataContractSerializerOperationBehavior의 CreateSerializer 메서드를 재정의해서 "reference tracking"이 가능한 DataContractSerializer 설정</li> </ol> <br /> 뭐... 약간은 복잡하긴 하지만, 그래도 할만한 수준입니다. ^^ 지난번 글에서 문제가 되었던 소스 코드로부터 위의 순서를 그대로 적용해 보겠습니다.<br /> <br /> 우선 Service.svc 파일을 열어서 다음과 같이 ServiceFactory를 지정합니다. (주의: Web Site 모델의 경우, 네임스페이스가 생략되므로 아래와 같이 클래스 명만 지정하면 되지만, Web Application Project의 경우에는 필히 네임스페이스를 같이 지정해 주어야 합니다.)<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > <% @ServiceHost Language=C# Debug="true" Service="MyService" <span style='color: blue; font-weight: bold'>Factory="CustomServiceFactory"</span> CodeBehind="~/App_Code/Service.cs" %> </pre> <br /> Factory를 지정했으니, 그 이름에 맞게 클래스 파일을 하나 생성하고 다음과 같이 CreateServiceHost 메서드를 재정의합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.ServiceModel.Activation; public class CustomServiceFactory : ServiceHostFactory { protected override System.ServiceModel.ServiceHost <span style='color: blue; font-weight: bold'>CreateServiceHost</span>(Type serviceType, Uri[] baseAddresses) { <span style='color: blue; font-weight: bold'>return new CustomServiceHost(serviceType, baseAddresses);</span> } } </pre> <br /> 이어서, CustomServiceHost 클래스 파일을 생성하고 OnOpeneing 메서드를 재정의 해줍니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.ServiceModel; public class CustomServiceHost : ServiceHost { public CustomServiceHost(object singletonInstance, params Uri[] baseAddresses) : base(singletonInstance, baseAddresses) { } public CustomServiceHost(Type serviceType, params Uri[] baseAddresses) : base(serviceType, baseAddresses) { } protected override void OnOpened() { base.OnOpened(); } protected override void <span style='color: blue; font-weight: bold'>OnOpening</span>() { base.OnOpening(); <span style='color: blue; font-weight: bold'>AddBehavior();</span> } } </pre> <br /> AddBehavior에서는 개별 WCF 메서드에 새로운 DataContractSerializerOperationBehavior를 설정해 주면 되는데요. 이에 대해서는 위에서 제가 소개한 "Preserving Object Reference in WCF" 글에 포함된 소스 코드에 따라 다음과 같이 구현해 줄 수 있습니다. (사실, 이 코드에는 문제가 있지만 나중에 지적하겠습니다.)<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > void AddBehavior() { if (base.Description != null) { foreach (ServiceEndpoint ep in this.Description.Endpoints) { foreach (OperationDescription op in ep.<span style='color: blue; font-weight: bold'>Contract.Operations</span>) { op.Behaviors.Add(<span style='color: blue; font-weight: bold'>new ReferencePreservingDataContractSerializerOperationBehavior</span>(op)); } } } } </pre> <br /> 거의 다 왔군요. 마지막으로 ReferencePreservingDataContractSerializerOperationBehavior 클래스 파일을 만들고 아래와 같이 CreateSerializer 메서드를 재정의해주면 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.ServiceModel.Description; using System.Runtime.Serialization; using System.Xml; public class ReferencePreservingDataContractSerializerOperationBehavior : DataContractSerializerOperationBehavior { public ReferencePreservingDataContractSerializerOperationBehavior(OperationDescription operationDescription) : base(operationDescription) { } public override XmlObjectSerializer CreateSerializer( Type type, string name, string ns, IList<Type> knownTypes) { return CreateDataContractSerializer(type, name, ns, knownTypes); } private static XmlObjectSerializer CreateDataContractSerializer( Type type, string name, string ns, IList<Type> knownTypes) { return CreateDataContractSerializer(type, name, ns, knownTypes); } public override XmlObjectSerializer <span style='color: blue; font-weight: bold'>CreateSerializer</span>(Type type, XmlDictionaryString name, XmlDictionaryString ns, IList<Type> knownTypes) { <span style='color: blue; font-weight: bold'> return new DataContractSerializer</span>(type, name, ns, knownTypes, 0x7FFF /*maxItemsInObjectGraph*/, false/*ignoreExtensionDataObject*/, <span style='color: blue; font-weight: bold'>true/*preserveObjectReferences*/,</span> null/*dataContractSurrogate*/); } } </pre> <br /> 이걸로 일단 구현은 끝입니다. 빌드하고 실행해 보면, 결과는 어떨까요? ^^<br /> <br /> 아쉽게도 여전히 오류가 발생합니다. 문제를 추적해 보면, AddBehavior 메서드의 Description.Endpoints 루프가 실행되지 않는 것을 확인할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > void AddBehavior() { if (base.Description != null) { foreach (ServiceEndpoint ep in this.Description.Endpoints) { System.Diagnostics.Debug.WriteLine("실행되지 않음!!!"); foreach (OperationDescription op in ep.Contract.Operations) { op.Behaviors.Add(new ReferencePreservingDataContractSerializerOperationBehavior(op)); } } } } </pre> <br /> 왜냐하면, 이렇게 .svc 파일의 ServiceFactory를 재정의해서 Endpoints를 열람하려면 web.config에서 명시적으로 endpoint 노드를 지정해 주어야만 열람이 되기 때문입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > <system.serviceModel> <services> <service name="MyService"> <span style='color: blue; font-weight: bold'><endpoint contract="IMyService" binding="basicHttpBinding"/></span> </service> </services> <behaviors> <serviceBehaviors> <behavior name=""> <serviceDebug includeExceptionDetailInFaults="true"/> <serviceMetadata httpGetEnabled="true"/> </behavior> </serviceBehaviors> </behaviors> </system.serviceModel> </pre> <br /> 다시 테스트를 해볼까요? 하지만, 그래도 오류가 발생할 것입니다. 다행히 오류 메시지를 살펴보면 문제의 원인을 쉽게 파악할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > An ExceptionDetail, likely created by IncludeExceptionDetailInFaults=true, whose value is: System.InvalidOperationException: <span style='color: blue; font-weight: bold'>An exception was thrown in a call to a WSDL export extension: ReferencePreservingDataContractSerializerOperationBehavior</span> contract: http://tempuri.org/:IMyService ----> System.ArgumentException: Calling <span style='color: blue; font-weight: bold'>IWsdlExportExtension.ExportContract twice with the same ContractDescription is not supported.</span> at System.ServiceModel.Description.MessageContractExporter.CreateMessage(MessageDescription message, Int32 messageIndex, Message& wsdlMessage) at System.ServiceModel.Description.MessageContractExporter.ExportMessage(Int32 messageIndex, Object state) at System.ServiceModel.Description.MessageContractExporter.ExportMessageContract() at System.ServiceModel.Description.WsdlExporter.CallExtension(WsdlContractConversionContext contractContext, IWsdlExportExtension extension) --- End of inner ExceptionDetail stack trace --- </pre> <br /> 말 그대로, DataContractSerializerOperationBehavior가 2개 이상 설정되었다는 것! 왜냐하면 기본적으로 모든 WCF 메서드에 DataContractSerializerOperationBehavior가 존재하기 때문인데, 이로 인해 사실상 AddBehavior 코드가 다음과 같이 변경되어야만 합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > void AddBehavior() { if (base.Description != null) { foreach (ServiceEndpoint ep in this.Description.Endpoints) { foreach (OperationDescription op in ep.Contract.Operations) { <span style='color: blue; font-weight: bold'> DataContractSerializerOperationBehavior dataContractBehavior = op.Behaviors.Find<DataContractSerializerOperationBehavior>() as DataContractSerializerOperationBehavior; if (dataContractBehavior != null) { op.Behaviors.Remove(dataContractBehavior); }</span> op.Behaviors.Add(new ReferencePreservingDataContractSerializerOperationBehavior(op)); } } } } </pre> <br /> 이제 정말 끝난 걸까요? ^^ 넵. 이것으로 모든 오류 수정이 끝났고, 이제 GetMyEntities WCF 메서드를 호출해도 정상적으로 동작을 합니다.<br /> <br /> <a target='tab' href='http://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=615&boardid=331301885'>첨부된 파일은 위의 코드를 포함한 예제 프로젝트</a>입니다.<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1772
(왼쪽의 숫자를 입력해야 합니다.)