델파이7 웹서비스 클라이언트 델파이7에서 C#으로 작성한 웹서비스 함수를 사용하기 위한 간략한 방법을 정리해 보았습니다. 웹서비스에 있어서 HTTP,XML, SOAP, WSDL , UDDI 등의 기본 개념은 어느 정도 알고 계셔야 합니다. 최초작성 : 2006. 10.27 일차수정 : 2007. 04.30 방재웅(shinjijoa@empal.com) 버전 0.2
0. 강좌 개요 아시다시피, 90년대 중후반, 분산객체가 유행하면서, DCOM, CORBA, 델파이 MIDAS 등의 3-Tier기술들이 많이 사용되었습니다. (2) 그러나 DCOM(COM+) 등의 서비스는 TCP 135 포트를 사용하므로, 방화벽으로 막혀있는 서버에는 접근할 수 가 없게 됩니다. 그러나 웹서비스는 기본적으로 HTTP 80 포트를 사용합니다. 따라써 요즘은 웹서비스도 많이 사용하는 추세입니다. (가장 중요함) (3) 웹서비스는 이러한 단점을 보완하고 XML 등을 이용하여 데이터 송/수신시에 데이터 파싱을 좀더 쉽게 할 수 있습니다. (개인 적인 생각) (4) 웹서비스 모듈은 자바나, C#, PHP, 심지어 델파이로도 개발 가능합니다. 그러나 이쪽은 우리 클라이언트 개발자들이 지향할 분야는 아니므로 우리는 클라이언트에 집중하기로 합시다 ^^;
1. 테스트를 위해서 C# 웹서비스 생성 MS 비주얼 스튜디오 2005 의 C# 웹서비스 프로젝트 생성 (3) 프로젝트를 실행하시면 http://localhost:4325/WebSite5/Service.asmx 이런 비슷한 주소를 IE를 이용해 접근할 수 있습니다. using System; using System.Web; using System.Web.Services; using System.Web.Services.Protocols; [WebService(Namespace = "http://tempuri.org/")] [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)] public class Service : System.Web.Services.WebService { public Service () { //디자인된 구성 요소를 사용하는 경우 다음 줄의 주석 처리를 제거합니다. //InitializeComponent(); } [WebMethod] public string HelloWorld() { return " Hello World ";
2. 델파이와 파라메터 연동 테트스를 위해서 C# 소스 수정 굵게 표시한 부분이 수정된 부분입니다 (2) 이 함수는 파라메터로 들어온 숫자에 +1을 해서 리턴합니다. using System; using System.Web; using System.Web.Services; using System.Web.Services.Protocols; //[WebService(Namespace = "http://tempuri.org/")] //[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)] [System.Web.Services.Protocols.SoapRpcService] //새롭게 추가한 부분 public class Service : System.Web.Services.WebService { public Service () { //디자인된 구성 요소를 사용하는 경우 다음 줄의 주석 처리를 제거합니다. //InitializeComponent(); } [WebMethod] public string HelloWorld(int i) { i++; return " Hello World " + i.ToString(); ;
실행화면 1
실행화면 2
실행화면 3
3. 델파이에서 웹서비스 함수 연결하기 메뉴 File/New/Other… 선택 WebServices 탭 선택 WSDL Importer 선택
4. 델파이에서 웹서비스 WSDL 지정 #1 아까 작성된 웹서비스 URL 끝에 ?wsdl 을 붙여 넣습니다. (2) Next 버튼 클릭 * WSDL 이란 일종의 함수(웹서비스) 설계도 입니다.
4. 델파이에서 웹서비스 WSDL 지정 #2 Finish 버튼 클릭
4. 델파이에서 웹서비스 WSDL 지정 #3 위 과정을 거치면 델파이에서 해당 웹서비스를 사용할 수 있는 기본 인터페이스 .pas 파일을 생성하여 줍니다. 적당한 위치에 저장하세요
5. 델파이 프로젝트 생성 웹서비스를 테스트하기 위해서 새로운 프로젝트를 생성합니다. 아까 만들었던 .pas 파일을 uses 절에 추가합니다.
6. 웹서비스 함수 호출 버튼 이벤트 핸들러에 다음과 같이 입력합니다. procedure TForm1.Button1Click(Sender: TObject); begin memo1.Text := GetServiceSoap().HelloWorld(10); end;
10을 넘겨주고 +1 이된 11을 받은 것을 확인할 수 있습니다. 7. 리턴결과 10을 넘겨주고 +1 이된 11을 받은 것을 확인할 수 있습니다.
8. 델파이가 생성해준 .pas 파일 분석1 function GetServiceSoap(UseWSDL: Boolean; Addr: string; HTTPRIO: THTTPRIO): ServiceSoap; 이 함수가 핵심입니다. (함수명은 웹서비스에 따라서 틀려질 수 있음) 이 함수를 호출하면 ////////////////////////////////////////// ServiceSoap = interface(IInvokable) ['{77573149-9C57-FA51-F11F-EFD527C91BD9}'] function HelloWorld(const i: Integer): WideString; stdcall; end; 위와같은 인터페이스 객체를 넘겨줍니다. 우리는 단순히 GetServiceSoap().HelloWorld(10); 이런식으로 호출만 하면 됩니다. 즉, [ GetServiceSoap().호출하고자하는함수명 ]
8. 델파이가 생성해준 .pas 파일 분석2 그러나 실제 프로젝트는 위처럼 단순한 함수는 드물고 거의 대부분 레코드 목록을 뿌려줄 것입니다. ////////////////////// sample OrgManageSoap = interface(IInvokable) ['{F1D37F61-EAE9-EA4E-C2ED-1CE90314F779}'] function GetHq: ArrayOfHqInfo; stdcall; function GetNsc(const strBunbu: WideString): ArrayOfNscInfo; stdcall; function GetParent(const strNscCd: WideString; const nMode: Integer): ArrayOfParent; stdcall; function GetChild(const strChild: WideString; const nMode: Integer): ArrayOfChild; stdcall; end; /////////////////////// 여기에서 리턴형식을 보면 전부 ArrayXXXX 이런 식입니다. (즉, 리턴 자체가 레코드의 배열이라는 의미) 각 리턴형의 클래스는 다음처럼 정의되어 있습니다. Child = class(TRemotable) private FSITE_CD: WideString; FSITE_NAME: WideString; published property SITE_CD: WideString read FSITE_CD write FSITE_CD; property SITE_NAME: WideString read FSITE_NAME write FSITE_NAME; ArrayOfChild = array of Child; //클래스의 배열 TYPE 선언 //참고로, C#에서 기본레코드셋으로 넘기면 FSITE_CD, FSITE_NAME 등의 필드가 생성되지 않습니다. //C# 쪽에서 어떤 처리를 해 주어야지 저렇게 나옵니다. 안그러면 Fscheme 만 딸랑 생깁니다.
8. 델파이가 생성해준 .pas 파일 분석3 이렇게 ArrayOfChild = array of Child; 클래스의 배열형태로 넘어올 경우에는 다음처럼 파싱하시면 됩니다. procedure TForm1.Button2Click(Sender: TObject); var ar: ArrayOfNscInfo; str: WideString; i: integer; begin str := '01'; ar := GetOrgManageSoap().GetNsc(str); i := 0; while i <= HIGH(ar) do str := ar[i].NSC_CD + ar[i].NSC_CD_NAME; memo1.Lines.Add(str); INC(i); end;
9. C#에서의 코딩 예제 (DB쿼리 후에 레코드셋을 리턴하는 방법) using System; using System.Web; using System.Collections; using System.Web.Services; using System.Web.Services.Protocols; using System.Data; using System.Data.SqlClient; using System.IO; using System.Xml; using System.Data.OleDb; /// <summary> /// WSCategory의 요약 설명입니다. /// </summary> //[WebService(Namespace = "http://tempuri.org/")] //[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)] [System.Web.Services.Protocols.SoapRpcService] public class WSCategory : System.Web.Services.WebService { public WSCategory () { //디자인된 구성 요소를 사용하는 경우 다음 줄의 주석 처리를 제거합니다. //InitializeComponent(); } [WebMethod] public string HelloWorld() { return "Hello World"; #region 카테고리 마스터 테이블 정보 /// public class CATEGORY_MASTER { public string WC_NO = ""; public string WC_TITLE = ""; public string WC_IDX = ""; public string G_CODE = ""; #endregion public CATEGORY_MASTER[] CategoryMaster() DataSet dsResult = null; CATEGORY_MASTER[] CategoryMasterManage = { }; try //DB SqlConnection con = new SqlConnection(MyDataBase.GetConnectionString()); string strSQL = "select * from tbl_website_category order by wc_idx"; dsResult = new DataSet(); SqlDataAdapter objAdapter = new SqlDataAdapter(strSQL, con); objAdapter.Fill(dsResult, "tbl_website_category"); CategoryMasterManage = GetCategoryMasterInfo(dsResult); catch (Exception ex) ex.ToString(); return CategoryMasterManage; //데이터 셋을 가져와서 클래스부분에서 선언한 값에 넣어줍니다. private CATEGORY_MASTER[] GetCategoryMasterInfo(DataSet dsResult) { DataTable dtReturn = null; CATEGORY_MASTER[] CategoryMasterManage = { }; int i = 0; try CategoryMasterManage = new CATEGORY_MASTER[dsResult.Tables[0].Rows.Count]; foreach (DataRow dr in dsResult.Tables[0].Rows) dtReturn = dsResult.Tables[0]; DataRow drList = dtReturn.Rows[i]; CategoryMasterManage[i] = new CATEGORY_MASTER(); CategoryMasterManage[i].WC_NO = drList["WC_NO"].ToString(); CategoryMasterManage[i].WC_TITLE = drList["WC_TITLE"].ToString(); CategoryMasterManage[i].WC_IDX = drList["WC_IDX"].ToString(); CategoryMasterManage[i].G_CODE = drList["G_CODE"].ToString(); i++; } catch (Exception ex) ex.ToString(); return CategoryMasterManage;
기타. 1 C# 코드에서 다음처럼 [WebService(Namespace = "http://tempuri.org/")] [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)] public class Service : System.Web.Services.Protocols.SoapHttpClientProtocol { } 위에 두줄을 주석처리 하지 않고, System.Web.Services.Protocols.SoapHttpClientProtocol 에서 바로 상속받는 방법도 있다고 합니다만, 테스트는 제대로 못해 보았습니다. ^^; 그리고 HTTPRIO 컴포넌트를 사용해서 하는 방법도 있긴 합니다. 다음 링크 참고 하시구요, http://www.agnisoft.com/white_papers/SOAP1.asp 델파이7 철저공략? 이란 책에도 어느정도 설명은 나와있습니다.. ////////////////////// 델파이를 쭉쓰다가, 몇 년 손을 떼고, MFC로만 작업했는데, 다시 델파이 써보니 확실히 개발 속도자체가 빠르긴 합니다. (개발시 개발자 실수에 의한 에러도 적구요)
기타. 2 순수 XML 데이터 자체를 얻으시려면 델파이7의 WebServices 탭에 있는 HTTPRIO 컴포넌트를 폼 위에 올리시고 GetServiceSoap(False, '', HTTPRio1).함수명(); 처럼 호출 하 신 후, HTTPRio1의 OnAfterExecute 이벤트 핸들러 에 다음과 같이 코딩하세요 SOAPResponse.Position := 0; Memo1.Lines.LoadFromStream(SOAPResponse); SOAPResponse.Position := 0;
기타. 3 – 한글깨짐 문제 델마당 강좌란에 있는 글을 여기에 붙였습니다. I recently ran into a problem sending Unicode data from a Delphi 7 client to a MS C# web service. The web service was expecting a XML string that contained Unicode content. Although the imported wsdl defined the parameters as WideString, XML content in UTF-16 format was getting corrupt when received at the service. The problem turned out to be a bad setting on THTTPRIO.HTTPWebNode. Although THTTPRIO.Converter was converting the UTF-16 string to UTF-8 properly, the HTTP communication was not defining that the XML content being sent was actually UTF-8. I believe that IIS or .net was assuming the request was in the default OS encoding (Windows-1252) because it was not specified. Changing the setting THTTPRIO.HTTPWebNode.UseUTF8InHeader from the default "false" to "true" corrected my problem when receiving Unicode content in the C# web service. Why this defaults to false I have no idea because the THTTPRIO.Converter defaults to converting the SOAP message content to UTF-8. I hope this helps someone.