다음을 통해 공유


연습: JavaScript와 함께 최신 앱 SOAP 끝점 사용

 

게시 날짜: 2016년 11월

적용 대상: Dynamics CRM 2015

이 연습에서는 웹 리소스를 사용하여 SOAP 끝점에 사용할 JavaScript 라이브러리를 만드는 방법을 보여 줍니다. 현재 Microsoft Dynamics CRM 2015 및 Microsoft Dynamics CRM Online 2015 업데이트에서는 SOAP 끝점을 사용하여 사용 가능한 메시지를 호출하는 데 사용할 수 있는 JavaScript 라이브러리를 제공하지 않습니다. 이 연습에서는 조직 서비스 Execute 메서드를 사용하여 Assign 메시지에 대해 JScript 라이브러리를 만드는 방법을 보여 줍니다.Execute 메서드를 사용하는 다른 모든 메시지는 비슷한 패턴을 따릅니다. 메시지에 대해 JavaScript 라이브러리를 만들려면 비슷한 프로세스가 필요합니다. 이 프로세스를 사용하여 개발된 JavaScript 라이브러리의 예제는 샘플: JavaScript를 사용하여 엔터티 메타데이터 검색을 참조하십시오.

참고

SOAP 끝점을 사용하는 전체 샘플 JavaScript 라이브러리는 Sdk.Soap.js 라이브러리를 참조하십시오. Sdk.Soap.js 라이브러리는 여기서 설명 하는 프로세스를 사용하여 생성되었습니다.

프로세스는 관리되는 코드 콘솔 응용 프로그램에서 보낸 HTTP 요청과 응답을 캡처한 후 동일한 요청을 보내고 유사한 응답을 처리하는 기능이 있는 JavaScript 라이브러리를 만드는 것으로 구성됩니다.HTTP 요청과 응답을 캡처하는 Fiddler와 같은 응용 프로그램을 사용하는 것은 일반적이지만 Fiddler 및 비슷한 응용 프로그램은 온-프레미스 Microsoft Dynamics CRM 2015에 사용되는 암호화된 HTTP 트래픽을 캡처할 수 없습니다. 제공된 SOAPLogger 샘플 솔루션은 암호화되기 전에 HTTP 요청을 캡처하고 HTTP는 암호를 해독한 후에 응답합니다.Microsoft Dynamics CRM 2015 및 Microsoft Dynamics CRM Online 2015 업데이트 모두에 대해 작동하는 SOAPLogger 샘플 솔루션입니다.SOAPLogger는 JScript 라이브러리를 만드는 작업과 관련 없는 HTTP 트래픽을 필터링하여 제거합니다.

이 연습에서는 다음 작업을 안내합니다.

  1. 샘플 HTTP 요청 및 응답 캡처에서는 관리되는 코드를 사용하여 Assign 요청을 수행하는 SOAPLogger 샘플의 Run 메서드를 편집합니다.

  2. 그런 다음 SOAPLogger 솔루션을 실행하여 보낸 HTTP 요청과 받은 응답에 대한 정보를 생성합니다.

  3. JScript 라이브러리 만들기에서는 캡처한 HTTP 트래픽에 따라 JScript 라이브러리를 만들어 조직 서비스 Execute 메서드를 사용하여 Assign 메시지를 보내고 받습니다.

  4. JScript 라이브러리 테스트에서는 JScript 라이브러리를 테스트하여 레코드를 할당하는 데 사용할 수 있는지 확인합니다.

  5. 대화형 웹 리소스 만들기에서는 다음 스크린샷과 같이 Account 레코드를 선택한 사용자에게 할당하는 라이브러리를 사용하는 HTML 웹 리소스를 만듭니다.

    거래처 할당 페이지

이 완료된 연습을 나타내는 관리형 솔루션은 SDK 패키지의 SDK\SampleCode\JS\SOAPForJScript\SOAPEndpointforJScript_1_0_0_1_managed.zip에서 사용할 수 있습니다. Microsoft Dynamics CRM SDK 패키지를 다운로드합니다. 이 솔루션을 설치하고(가져오기) 완성된 결과를 볼 수 있습니다. 관리형 솔루션이므로 시스템에서 완전히 제거하기 위해 쉽게 제거(삭제)할 수 있습니다.

SDK\SampleCode\JS\SOAPForJScript\SOAPForJScript.sln은 이 연습의 코드를 사용하는 HTML 및 JS 파일이 포함된 Visual Studio 솔루션입니다.

필수 조건

  • JScript를 사용하여 수행하려는 함수에 해당하는 관리되는 코드를 작성할 수 있습니다.

  • SDK\SampleCode\CS\Client\SOAPLogger에서 SDK 다운로드 파일로 사용할 수 있는 SOAPLogger 샘플에 대한 액세스가 필요합니다.

    참고

    Microsoft Dynamics CRM SDK의 SOAPLogger 샘플은 Microsoft Visual C#에서만 사용할 수 있습니다.Microsoft Visual Basic .NET 버전을 선호하는 경우 CodePlex에서 Microsoft Dynamics CRM VB.Net SoapLogger를 참조하십시오.

  • SOAPLogger 솔루션을 실행하려면 Microsoft Visual Studio가 필요합니다.

  • JScript와 XMLHttpRequest를 사용하는 비동기 프로그래밍에 대해 잘 알고 있어야 합니다.

샘플 HTTP 요청 및 응답 캡처

SOAPLogger 솔루션을 편집하여 SoapLoggerOrganizationService를 사용하여 AssignRequest를 수행하는 코드를 추가합니다.SoapLoggerOrganizationServiceIOrganizationService에서 상속되고 보내고 받은 관련 HTTP 트래픽을 표시하는 로그 파일을 생성할 수 있는 기능을 추가합니다.

  1. Microsoft Visual Studio를 사용하여 SDK\SampleCode\CS\Client\SOAPLogger\SOAPLogger.sln에 있는 SOAPLogger 솔루션을 엽니다.

  2. 다음 예제와 같이 SoapLogger.cs 파일에서 Run 메서드를 찾습니다.

    
    public void Run(ServerConnection.Configuration serverConfig)
    {
     try
     {
    
      // Connect to the Organization service. 
      // The using statement assures that the service proxy will be properly disposed.
         using (_serviceProxy = new OrganizationServiceProxy(serverConfig.OrganizationUri, serverConfig.HomeRealmUri, serverConfig.Credentials, serverConfig.DeviceCredentials))
      {
       // This statement is required to enable early-bound type support.
       _serviceProxy.EnableProxyTypes();
    
       IOrganizationService service = (IOrganizationService)_serviceProxy;
    
    
       using (StreamWriter output = new StreamWriter("output.txt"))
       {
    
        SoapLoggerOrganizationService slos = new SoapLoggerOrganizationService(serverConfig.OrganizationUri, service, output);
    
        //Add the code you want to test here:
        // You must use the SoapLoggerOrganizationService 'slos' proxy rather than the IOrganizationService proxy you would normally use.
    
    
    
       }
    
    
      }
    
     }
    
     // Catch any service fault exceptions that Microsoft Dynamics CRM throws.
     catch (FaultException<Microsoft.Xrm.Sdk.OrganizationServiceFault>)
     {
      // You can handle an exception here or pass it back to the calling method.
      throw;
     }
    }
    
  3. SystemUser 레코드에 대해 유효한 ID와 해당 사용자에게 할당되지 않은 Account 레코드의 ID를 식별합니다.

    응용 프로그램의 레코드에서 Id를 가져오려면 해당 레코드를 열고 링크 복사 명령을 사용합니다. 링크를 메모장에 붙여넣고 인코딩된 괄호(“%7b” = “{”) 및 (“%7d” = “}”)를 제외하고 id 매개 변수 값이 포함된 부분을 분리합니다. 예를 들어 다음은 Account 레코드에 대한 URL을 나타냅니다.<your organization root url>/main.aspx?etc=1&id=%7bF2CA52DE-552D-E011-A8FB-00155DB059BE%7d&pagetype=entityrecord> id 값은 F2CA52DE-552D-E011-A8FB-00155DB059BE입니다.

    해당 값을 사용하여 아래 코드에서 자리 표시자 값을 바꾸고 표시된 메서드를 실행하는 코드를 추가합니다. 할당 요청에 대한 자세한 내용은 AssignRequest를 참조하십시오.

    Guid userId = new Guid("XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX");
    Guid accountId = new Guid("XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX");
    AssignRequest assign = new AssignRequest{Assignee = new EntityReference(SystemUser.EntityLogicalName, userId),Target = new EntityReference(Account.EntityLogicalName, accountId)};
    AssignResponse assignResp = (AssignResponse)slos.Execute(assign);
    
  4. F5 키를 눌러 응용 프로그램을 디버깅합니다. 서버 및 인증 자격 증명에 대한 정보를 입력합니다. 코드가 성공적으로 실행되면 이제 지정된 사용자에게 거래처 레코드가 할당됩니다. 샘플 코드 실행 방법에 대한 자세한 내용은 Microsoft Dynamics CRM 2015 웹 서비스를 사용하여 간단한 프로그램 실행을 참조하십시오.

  5. SOAPLogger 프로젝트 폴더에서 bin 및 Debug 폴더로 이동하여 output.txt 파일을 찾습니다.

  6. output.txt 파일을 열고 내용을 검토합니다. 실제 GUID 값이 자리 표시자 “XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX”를 바꾸는 점을 제외하고 다음과 같은 모양입니다.

    HTTP REQUEST
    --------------------------------------------------
    POST <your server root>/XRMServices/2011/Organization.svc/web
    Content-Type: text/xml; charset=utf-8
    SOAPAction: https://schemas.microsoft.com/xrm/2011/Contracts/Services/IOrganizationService/Execute
    
    <s:Envelope xmlns:s="https://schemas.xmlsoap.org/soap/envelope/">
     <s:Body>
      <Execute xmlns="https://schemas.microsoft.com/xrm/2011/Contracts/Services"
               xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
       <request i:type="b:AssignRequest"
                xmlns:a="https://schemas.microsoft.com/xrm/2011/Contracts"
                xmlns:b="https://schemas.microsoft.com/crm/2011/Contracts">
        <a:Parameters xmlns:c="http://schemas.datacontract.org/2004/07/System.Collections.Generic">
         <a:KeyValuePairOfstringanyType>
          <c:key>Target</c:key>
          <c:value i:type="a:EntityReference">
           <a:Id>XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX</a:Id>
           <a:LogicalName>account</a:LogicalName>
           <a:Name i:nil="true" />
          </c:value>
         </a:KeyValuePairOfstringanyType>
         <a:KeyValuePairOfstringanyType>
          <c:key>Assignee</c:key>
          <c:value i:type="a:EntityReference">
           <a:Id>XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX</a:Id>
           <a:LogicalName>systemuser</a:LogicalName>
           <a:Name i:nil="true" />
          </c:value>
         </a:KeyValuePairOfstringanyType>
        </a:Parameters>
        <a:RequestId i:nil="true" />
        <a:RequestName>Assign</a:RequestName>
       </request>
      </Execute>
     </s:Body>
    </s:Envelope>
    --------------------------------------------------
    
    HTTP RESPONSE
    --------------------------------------------------
    <s:Envelope xmlns:s="https://schemas.xmlsoap.org/soap/envelope/">
     <s:Body>
      <ExecuteResponse xmlns="https://schemas.microsoft.com/xrm/2011/Contracts/Services"
                       xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
       <ExecuteResult i:type="b:AssignResponse"
                      xmlns:a="https://schemas.microsoft.com/xrm/2011/Contracts"
                      xmlns:b="https://schemas.microsoft.com/crm/2011/Contracts">
        <a:ResponseName>Assign</a:ResponseName>
        <a:Results xmlns:c="http://schemas.datacontract.org/2004/07/System.Collections.Generic" />
       </ExecuteResult>
      </ExecuteResponse>
     </s:Body>
    </s:Envelope>
    --------------------------------------------------
    
  7. Account 엔터티의 논리적 이름을 포함하여 코드를 사용하여 전달한 변수 위치를 기록합니다.

할당 작업을 수행한 HTTP 트래픽의 관련 부분을 성공적으로 캡처했습니다.

JScript 라이브러리 만들기

라이브러리를 생성하는 방법은 정확히 개발자에게 달려 있습니다. 각 JScript에는 다음 요소가 있어야 합니다.

  • 함수 이름이 고유한지 확인하는 데 도움이 되는 전략입니다.

  • 웹 리소스의 SOAP 끝점에 대한 URL을 검색하는 수단입니다.

  • 요청을 보내고 응답을 비동기적으로 받는 메서드를 구분합니다.

  • 반환된 WCF 오류를 처리하는 함수입니다.

다음 JScript 라이브러리에는 이러한 요소가 포함되고 SOAPLogger를 사용하여 캡처한 HTTP 요청과 응답에 따라 Assign 메시지를 사용하는 assignRequestassignResponse 함수가 포함됩니다.


if (typeof (SDK) == "undefined")
{ SDK = { __namespace: true }; }
//This will establish a more unique namespace for functions in this library. This will reduce the 
// potential for functions to be overwritten due to a duplicate name when the library is loaded.
SDK.SOAPSamples = {
 _getClientUrl: function () {
  ///<summary>
  /// Returns the URL for the SOAP endpoint using the context information available in the form
  /// or HTML Web resource.
  ///</summary
  var OrgServicePath = "/XRMServices/2011/Organization.svc/web";
  var clientUrl = "";
  if (typeof GetGlobalContext == "function") {
   var context = GetGlobalContext();
   clientUrl = context.getClientUrl();
  }
  else {
   if (typeof Xrm.Page.context == "object") {
    clientUrl = Xrm.Page.context.getClientUrl();
   }
   else
   { throw new Error("Unable to access the server URL"); }
  }

  return clientUrl + OrgServicePath;
 },
 assignRequest: function (Assignee, Target, Type, successCallback, errorCallback) {
  ///<summary>
  /// Sends the Assign Request
  ///</summary>
  this._parameterCheck(Assignee, "GUID", "The SDK.SOAPSamples.assignRequest method Assignee parameter must be a string Representing a GUID value.");
  ///<param name="Assignee" Type="String">
  /// The GUID representing the  System user that the record will be assigned to.
  ///</param>
  this._parameterCheck(Target, "GUID", "The SDK.SOAPSamples.assignRequest method Target parameter must be a string Representing a GUID value.");
  ///<param name="Target" Type="String">
  /// The GUID representing the user-owned entity record that will be assigne to the Assignee.
  ///</param>
  this._parameterCheck(Type, "String", "The SDK.SOAPSamples.assignRequest method Type parameter must be a string value.");
  Type = Type.toLowerCase();
  ///<param name="Type" Type="String">
  /// The Logical name of the user-owned entity. For example, 'account'.
  ///</param>
  if (successCallback != null)
  this._parameterCheck(successCallback, "Function", "The SDK.SOAPSamples.assignRequest method successCallback parameter must be a function.");
  ///<param name="successCallback" Type="Function">
  /// The function to perform when an successfult response is returned.
  ///</param>
  this._parameterCheck(errorCallback, "Function", "The SDK.SOAPSamples.assignRequest method errorCallback parameter must be a function.");
  ///<param name="errorCallback" Type="Function">
  /// The function to perform when an error is returned.
  ///</param>
  //The request is simply the soap envelope captured by the SOAPLogger with variables added for the 
  // values passed. All quotations must be escaped to create valid JScript strings.
  var request = [];

     request.push("<s:Envelope xmlns:s=\"https://schemas.xmlsoap.org/soap/envelope/\">");
     request.push("<s:Body>");
     request.push("<Execute xmlns=\"https://schemas.microsoft.com/xrm/2011/Contracts/Services\"");
     request.push(" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">");
     request.push("<request i:type=\"b:AssignRequest\"");
     request.push(" xmlns:a=\"https://schemas.microsoft.com/xrm/2011/Contracts\"");
     request.push(" xmlns:b=\"https://schemas.microsoft.com/crm/2011/Contracts\">");
     request.push("<a:Parameters xmlns:c=\"http://schemas.datacontract.org/2004/07/System.Collections.Generic\">");
     request.push("<a:KeyValuePairOfstringanyType>");
     request.push("<c:key>Target</c:key>");
     request.push("<c:value i:type=\"a:EntityReference\">");
     request.push("<a:Id>" + this._xmlEncode(Target) + "</a:Id>");
     request.push("<a:LogicalName>" + this._xmlEncode(Type) + "</a:LogicalName>");
     request.push("<a:Name i:nil=\"true\" />");
     request.push("</c:value>");
     request.push("</a:KeyValuePairOfstringanyType>");
     request.push("<a:KeyValuePairOfstringanyType>");
     request.push("<c:key>Assignee</c:key>");
     request.push("<c:value i:type=\"a:EntityReference\">");
     request.push("<a:Id>" + this._xmlEncode(Assignee) + "</a:Id>");
     request.push("<a:LogicalName>systemuser</a:LogicalName>");
     request.push("<a:Name i:nil=\"true\" />");
     request.push("</c:value>");
     request.push("</a:KeyValuePairOfstringanyType>");
     request.push("</a:Parameters>");
     request.push("<a:RequestId i:nil=\"true\" />");
     request.push("<a:RequestName>Assign</a:RequestName>");
     request.push("</request>");
     request.push("</Execute>");
     request.push("</s:Body>");
     request.push("</s:Envelope>");

  var req = new XMLHttpRequest();
  req.open("POST", SDK.SOAPSamples._getClientUrl(), true)
  // Responses will return XML. It isn't possible to return JSON.
  req.setRequestHeader("Accept", "application/xml, text/xml, */*");
  req.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
  req.setRequestHeader("SOAPAction", "https://schemas.microsoft.com/xrm/2011/Contracts/Services/IOrganizationService/Execute");
  req.onreadystatechange = function () { SDK.SOAPSamples.assignResponse(req, successCallback, errorCallback); };
  req.send(request.join(""));

 },
 assignResponse: function (req, successCallback, errorCallback) {
  ///<summary>
  /// Recieves the assign response
  ///</summary>
  ///<param name="req" Type="XMLHttpRequest">
  /// The XMLHttpRequest response
  ///</param>
  ///<param name="successCallback" Type="Function">
  /// The function to perform when an successfult response is returned.
  /// For this message no data is returned so a success callback is not really necessary.
  ///</param>
  ///<param name="errorCallback" Type="Function">
  /// The function to perform when an error is returned.
  /// This function accepts a JScript error returned by the _getError function
  ///</param>
     if (req.readyState == 4) {
         req.onreadystatechange = null; //avoids memory leaks
   if (req.status == 200) {
    if (successCallback != null)
    { successCallback(); }
   }
   else {
    errorCallback(SDK.SOAPSamples._getError(req.responseXML));
   }
  }
 },
 _getError: function (faultXml) {
  ///<summary>
  /// Parses the WCF fault returned in the event of an error.
  ///</summary>
  ///<param name="faultXml" Type="XML">
  /// The responseXML property of the XMLHttpRequest response.
  ///</param>
  var errorMessage = "Unknown Error (Unable to parse the fault)";
  if (typeof faultXml == "object") {
   try {
    var bodyNode = faultXml.firstChild.firstChild;
    //Retrieve the fault node
    for (var i = 0; i < bodyNode.childNodes.length; i++) {
     var node = bodyNode.childNodes[i];

     //NOTE: This comparison does not handle the case where the XML namespace changes
     if ("s:Fault" == node.nodeName) {
      for (var j = 0; j < node.childNodes.length; j++) {
       var faultStringNode = node.childNodes[j];
       if ("faultstring" == faultStringNode.nodeName) {
        errorMessage = faultStringNode.textContent;
        break;
       }
      }
      break;
     }
    }
   }
   catch (e) { };
  }
  return new Error(errorMessage);
 },
 _xmlEncode: function (strInput) {
  var c;
  var XmlEncode = '';

  if (strInput == null) {
   return null;
  }
  if (strInput == '') {
   return '';
  }

  for (var cnt = 0; cnt < strInput.length; cnt++) {
   c = strInput.charCodeAt(cnt);

   if (((c > 96) &amp;&amp; (c < 123)) ||
            ((c > 64) &amp;&amp; (c < 91)) ||
            (c == 32) ||
            ((c > 47) &amp;&amp; (c < 58)) ||
            (c == 46) ||
            (c == 44) ||
            (c == 45) ||
            (c == 95)) {
    XmlEncode = XmlEncode + String.fromCharCode(c);
   }
   else {
    XmlEncode = XmlEncode + '&amp;#' + c + ';';
   }
  }

  return XmlEncode;
 },
 _parameterCheck: function (parameter, type, errorMessage) {
  switch (type) {
   case "String":
    if (typeof parameter != "string") {
     throw new Error(errorMessage);
    }
    break;
   case "Function":
    if (typeof parameter != "function") {
     throw new Error(errorMessage);
    }
    break;
   case "EntityFilters":
    var found = false;
    for (x in this.EntityFilters) {
     if (this.EntityFilters[x] == parameter) {
      found = true;
      break;
     }
    }
    if (!found) {
     throw new Error(errorMessage);
    }
    break;
   case "Boolean":
    if (typeof parameter != "boolean") {
     throw new Error(errorMessage);
    }
    break;
   case "GUID":
    var re = new RegExp("[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}");
    if (!(typeof parameter == "string" &amp;&amp; re.test(parameter))) {
     throw new Error(errorMessage);
    }

    break;
   default:
    throw new Error("An invalid type parameter value was passed to the SDK.MetaData._parameterCheck function.");
    break;
  }
 },
 __namespace: true
};

JScript 라이브러리 테스트

JScript 라이브러리를 준비한 후 예상대로 작동하는지 확인하기 위해 몇몇 초기 테스트를 수행해야 합니다. 인증은 응용 프로그램 내에서만 발생하므로 라이브러리 콘텐츠를 사용하여 Microsoft Dynamics CRM 2015에서 스크립트 웹 리소스를 만들어야 합니다. 라이브러리의 함수를 사용하려면 UI 요소인 라이브러리의 JScript 함수를 호출하기 위해 양식 또는 HTML 웹 리소스가 필요합니다. 다음 절차는 라이브러리의 함수를 테스트하기 위해 간단한 HTML 웹 리소스 사용법을 설명합니다.

  1. 다음 코드를 사용하여 HTML 문서를 만듭니다.

    
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
     <title>Assign Test</title>
     <meta http-equiv="X-UA-Compatible" content="IE=edge" />
      <script src="../ClientGlobalContext.js.aspx" type="text/javascript"></script>
     <script src="Scripts/SDK.REST.js" type="text/javascript"></script>
     <script src="Scripts/SDK.SOAPSample.Assign.js" type="text/javascript"></script>
     <script type="text/javascript">
      var resultsArea = null;
      var userid;
      var accountid;
      var userLookup;
      var accountLookup;
      var accountsToShow = 10;
      var usersToShow = 10;
      var users = [];
      var accounts = [];
    
      document.onreadystatechange = function () {
       ///<summary>
       /// Initializes the sample when the document is ready
       ///</summary>
       if (document.readyState == "complete") {
        userid = document.getElementById("userid");
        accountid = document.getElementById("accountid");
        resultsArea = document.getElementById("results");
        userLookup = document.getElementById("userLookup");
        accountLookup = document.getElementById("accountLookup");
    
        populateUserLookup();
        populateAccountLookup();
    
       }
      }
    
      function testAssign() {
       //The field to enter the user id of the person the account record should be assigned to.
       userid.value;
       // The field to enter the account id of the account record to assign to the user
       accountid.value;
       // Since the response does not include any data to parse, simply display a success message.
       var successCallback = function () { setText(resultsArea, "Success!"); };
       // Display the error from the message passed back from the response.
       var errorCallback = function (error) { setText(resultsArea,"errorCallback error: "+error.message); };
       // Call the function
       try {
        SDK.SOAPSamples.assignRequest(userid.value, accountid.value, "account", successCallback, errorCallback);
       }
       catch (e) {
        setText(resultsArea, e.message);
       }
    
      }
    
      function populateUserLookup() {
       SDK.REST.retrieveMultipleRecords("SystemUser",
       "$top=" + usersToShow + "&amp;$select=FullName,SystemUserId&amp;$filter=FullName ne 'INTEGRATION' and FullName ne 'SYSTEM'",
       function (results) {
        for (var i = 0; i < results.length; i++) {
         users.push(results[i]);
        }
       },
       function (error) {
        alert(error.message);
       },
       function () {
        for (var i = 0; i < users.length; i++) {
         var user = users[i];
         userLookup.options[i] = new Option(user.FullName, user.SystemUserId);
        }
        userid.value = userLookup.options[0].value;
    
        userLookup.onchange = function () {
         userid.value = userLookup.options[userLookup.selectedIndex].value;
         clearMessage();
        };
    
       });
      }
    
      function populateAccountLookup() {
       SDK.REST.retrieveMultipleRecords("Account",
        "$top=" + accountsToShow + "&amp;$select=AccountId,Name,OwnerId",
       function (results) {
        for (var i = 0; i < results.length; i++) {
         accounts.push(results[i]);
    
        }
       },
       function (error) {
        alert(error.message);
       },
       function () {
        for (var i = 0; i < accounts.length; i++) {
         var account = accounts[i];
         accountLookup.options[i] = new Option(account.Name + " : " + account.OwnerId.Name, account.AccountId);
    
        }
        accountid.value = accountLookup.options[0].value;
    
        accountLookup.onchange = function () {
         accountid.value = accountLookup.options[accountLookup.selectedIndex].value;
         clearMessage();
        };
       });
      }
    
      function clearMessage() {
       setText(resultsArea, "");
      }
    
      function setText(element, text) {
       if (typeof (element.innerText) != "undefined") {
        element.innerText = text;
       }
       else {
        element.textContent = text;
       }
    
      }
    
     </script>
    </head>
    <body>
     <table>
      <tr>
       <td>
        <label for="userid">
         SystemUserId:</label>
       </td>
       <td>
        <input id="userid" type="text" style="width:270px;"  />
       </td>
       <td>
       <select id="userLookup"></select>
       </td>
      </tr>
      <tr>
       <td>
        <label for="accountid">
         AccountId:</label>
       </td>
       <td>
        <input id="accountid" type="text" style="width:270px;" />
       </td>
          <td>
       <select id="accountLookup"></select>
       </td>
      </tr>
     </table>
    
    
     <button id="testAssign" title="Click this button to test the testAssign function." onclick="testAssign();">
      Test Assign</button>
     <div id="results">
      &amp;nbsp;</div>
    </body>
    </html>
    
  2. 이 코드를 사용하여 HTML 웹 리소스를 만듭니다.HTML 웹 리소스의 전체 이름은 솔루션의 사용자 지정 접두사에 따라 다릅니다. 솔루션 사용자 지정 접두사가 "new"라고 가정하고 이 HTML 웹 리소스 이름을 “new_/AssignTest.htm”라고 지정합니다. 모든 웹 리소스가 동일한 사용자 지정 접두사를 사용하는 한 사용된 특정 사용자 지정 접두사는 이 예제에 영향을 미치지 않습니다.

  3. JScript 라이브러리의 콘텐츠를 사용하여 스크립트 웹 리소스를 만듭니다. 이 웹 리소스의 이름은 “new_/Scripts/SDK.SOAPSample.Assign.js”입니다.

  4. 모든 사용자 지정을 게시하고 new_/AssignTest.htm 웹 리소스를 엽니다.미리 보기 단추를 클릭합니다.

  5. No text is specified for bookmark or legacy link 'c9a435ab-a4cb-4e0c-9ef7-b7ba66278407#BKMK_EditSoapLogger'. 절차의 3단계에서 사용된 동일한 프로세스를 사용하면 유효한 시스템 사용자 및 거래처 레코드 Id 값을 식별하고 페이지에 붙여넣습니다. 그런 다음 Test Assign을 클릭하여 성공 응답을 얻었는지 확인합니다.

  6. 오류가 제대로 구문 분석되는지 확인하기 위해 잘못된 값을 입력합니다.

    예를 들어, 거래처 Id 대신 “A Store (sample)”를 입력할 경우 다음과 같은 오류 메시지를 예상해야 합니다.

    The formatter threw an exception while trying to deserialize the message: 
    There was an error while trying to deserialize parameter https://schemas.microsoft.com/xrm/2011/Contracts/Services:request. 
    The InnerException message was 'There was an error deserializing the object of type Microsoft.Xrm.Sdk.OrganizationRequest. 
    The value 'A Store (sample)' cannot be parsed as the type 'Guid'.'.  Please see InnerException for more details.
    

    AccountId에 대해 거래처 레코드가 없을 경우 예상되는 메시지는 다음과 같습니다.

    Account With Id = 883c3084-1f2f-e011-ad66-00155dba3814 Does Not Exist
    

대화형 웹 리소스 만들기

다음은 양식 스크립트, 리본 명령 또는 웹 리소스에서 JScript 라이브러리를 사용하는 단계입니다. 다음 코드 샘플은 거래처 레코드를 사용자에게 할당하기 위해 대화형 사용자 인터페이스를 제공하도록 SystemUserAccount 레코드를 검색하기 위해 REST 끝점을 사용하는 HTML 웹 리소스를 나타냅니다. 이 페이지는 REST 끝점을 사용하여 JSON 개체를 사용할 수 있도록 Scripts/SDK.SOAPSample.Assign.js 스크립트 웹 리소스와 json2.js 라이브러리를 사용하는 스크립트 웹 리소스를 사용합니다.SDK.REST.js 라이브러리는 REST 끝점을 사용하여 데이터를 검색하는 데 사용됩니다.


<html lang="en-us">
<head>
 <title>Assign Accounts Page</title>
 <meta http-equiv="X-UA-Compatible" content="IE=edge" />
 <style type="text/css">
  body
  {
   font-family: Segoe UI;
   background-color: #EFF2F6;
  }
  .dataTable
  {
   border-collapse: collapse;
   border: 1px solid black;
  }
  .tableHead
  {
   background-color: #C0C0C0;
   width: 100%;
  }
  .grid
  {
   border-bottom: 1px solid black;
  }
  td
  {
   padding-left: 5px;
   padding-right: 5px;
  }
  #accountList
  {
   background-color: White;
  }
  #checkboxCol
  {
   border-right: 1px solid black;
   width: 20px;
  }
  #nameCol
  {
   border-right: 1px solid black;
   width: 300px;
  }
  #ownerCol
  {
   width: 300px;
  }
 </style>
 <script src="../ClientGlobalContext.js.aspx" type="text/javascript"></script>
 <script src="Scripts/SDK.REST.js" type="text/javascript"></script>
 <script src="Scripts/SDK.SOAPSample.Assign.js" type="text/javascript"></script>
 <script type="text/javascript">
  //Set variables for page elements
  var userSelect; //The select control used to select the user to assign records to.
  var accountList; //The tbody element that rows will be added to for each retrieved account
  var selectAll; //The checkbox to select all the retrieved accounts
  var btnAssign; //The button to assign assign the accounts
  var tableCaption; //A label hidden on load
  var dataTable; //the table element hidden on load
  var alertFlag; // Alert flag to indicate the changes
  var users = []; //SystemUser records retrieved
  var accounts = []; //Accounts not assigned to the currently selected user.
  var accountsToShow = 5;
  var suppressRetrievedAccountsAlert = false;
  var accountsToAssign = [];
  var userId = null;

  function startSample() {
   ///<summary>
   /// Starts the sample
   ///</summary>
   alertFlag = document.getElementById("dispalert");
   userSelect = document.getElementById("userList");
   //A new set of a 5 accounts will be retrieved when the user changes
   userSelect.onchange = getAccounts;

   accountList = document.getElementById("accountList");
   selectAll = document.getElementById("selectAll");
   //When the select all checkbox is clicked, toggle the selection for each row of the table.
   selectAll.onclick = toggleSelectAllRecords;
   btnAssign = document.getElementById("btnAssign");
   //Add a helper function to enable or disable the assign button.
   btnAssign.setEnabled = setEnabled;
   //Set the event handler to the Assign button
   btnAssign.onclick = assignAccounts;
   tableCaption = document.getElementById("tableCaption");
   dataTable = document.getElementById("dataTable");
   var divSample = document.getElementById("divSample");
   //Load the list of users
   getUsers();
   divSample.style.display = "block";

   document.getElementById("btnStart").setAttribute("disabled", "disabled");

  }
  function getUsers() {
   SDK.REST.retrieveMultipleRecords("SystemUser",
   "$select=FullName,SystemUserId&amp;$filter=FullName ne 'INTEGRATION' and FullName ne 'SYSTEM'",
   function (results) {
    for (var i = 0; i < results.length; i++) {
     users.push(results[i]);
    }
   },
   function (error) {
    showMessage(error.message);
   },
   function () {
    if (users.length > 1) {
     for (var i = 0; i < users.length; i++) {
      var user = users[i];
      userSelect.options[i + 1] = new Option(user.FullName, user.SystemUserId);
     }
     userSelect.removeAttribute("disabled");

     if (alertFlag.checked == true) {
      alert(users.length + " system users retrieved");
     }

    }
    else {
     var notification = "This sample requires that more than one user is available in the organization.";
     showMessage(notification);
     if (alertFlag.checked == true) {
      alert("This sample requires that more than one user is available in the organization.");
     }

    }
   });
  }
  function getAccounts() {
   //Clear out any records displayed in the table
   clearAccountList();
   var selectedUserId = userSelect.options[userSelect.selectedIndex].value;
   if (selectedUserId != "none") {

    SDK.REST.retrieveMultipleRecords("Account",
    "$top=" + accountsToShow + "&amp;$select=AccountId,Name,OwnerId&amp;$filter=OwnerId/Id ne (guid'" + encodeURIComponent(selectedUserId) + "')",
    function (results) {
     accounts.length = 0;
     for (var i = 0; i < results.length; i++) {
      accounts.push(results[i]);
     }
    },
    function (error) {
     showMessage(error.message);
    },
    function () {
     //onComplete
     if (accounts.length > 0) {

      for (var i = 0; i < accounts.length; i++) {
       var account = accounts[i];

       var row = document.createElement("tr");
       row.setAttribute("class", "grid");
       row.setAttribute("id", account.AccountId);
       var checkboxCell = document.createElement("td");
       var checkbox = document.createElement("input");
       checkbox.setAttribute("type", "checkbox");
       checkbox.onclick = validateAssignButton;
       checkboxCell.appendChild(checkbox);
       row.appendChild(checkboxCell);
       var nameCell = document.createElement("td");
       setText(nameCell, account.Name);
       row.appendChild(nameCell);
       var userNameCell = document.createElement("td");
       setText(userNameCell, account.OwnerId.Name);
       row.appendChild(userNameCell);
       accountList.appendChild(row);



      }

      if (alertFlag.checked == true &amp;&amp; !suppressRetrievedAccountsAlert) {
       alert(accounts.length + " account records retrieved.");
       suppressRetrievedAccountsAlert = false;

      }
     }
     else {
      //If no records are returned display a message.
      clearAccountList();
      var row = document.createElement("tr");
      var cell = document.createElement("td");
      cell.setAttribute("colSpan", "3");
      setText(cell, "No Accounts were retrieved");
      row.appendChild(cell);
      accountList.appendChild(row);

     }
     //Show any of the UI elements that are initially hidden
     setVisibleUIElements(true);
    });
   }
   else { setVisibleUIElements(false); }
  }
  function assignAccounts() {
   ///<summary>
   /// queues the selected accounts to be assigned sequentially
   ///</summary>
   userId = userSelect.options[userSelect.selectedIndex].value;

   var checkboxes = accountList.getElementsByTagName("input");
   for (var i = checkboxes.length - 1; i >= 0; i--) {
    if (checkboxes[i].checked) {
     var accountId = checkboxes[i].parentElement.parentElement.id;
     accountsToAssign.push(accountId);
    }
   }
   assignNextAccount();
   selectAll.checked = false;
  }
  function assignNextAccount() {
   /// <summary>Assigns the queued accounts</summary>
   //Prevents a generic SQL error that can occur when too many assign requests occur in rapid succession
   if (accountsToAssign.length > 0) {
    SDK.SOAPSamples.assignRequest(userId, accountsToAssign.shift(), "account", function () {
     assignNextAccount();
    }, function (error) {
     showMessage("There was an error assigning the account with Id :" + accountId + ". The error message is " + error.message + ".");
     assignNextAccount();
    });

   }
   else {
    suppressRetrievedAccountsAlert = true;
    getAccounts();
    btnAssign.setEnabled(false)
    if (alertFlag.checked == true) {
     alert("Record assignment completed and next set of records retrieved.");
    }
   }


  }
  function showMessage(message) {
   ///<summary>
   /// Helper function to display message on the page if necessary.
   ///</summary
   var dvMessage = document.createElement("div");
   dvMessage.innerHTML = SDK.SOAPSamples._xmlEncode(message);
   document.getElementById("status").appendChild(dvMessage);
  }
  function clearAccountList() {
   ///<summary>
   /// Helper function remove rows from the Account List table.
   ///</summary
   for (var i = (accountList.rows.length - 1) ; i >= 0; i--) {
    accountList.deleteRow(i);
   }
   accounts.length = 0;

  }
  function toggleSelectAllRecords() {
   ///<summary>
   /// Helper function to toggle all selected rows in the account list table.
   ///</summary
   var checkboxes = accountList.getElementsByTagName("input");
   for (var i = 0; i < checkboxes.length; i++) {
    checkboxes[i].checked = this.checked;
   }
   btnAssign.setEnabled(this.checked);

  }
  function validateAssignButton() {
   ///<summary>
   /// Helper function to enable the Assign Records button when rows are selected.
   ///</summary
   if (this.checked == true)
   { btnAssign.setEnabled(true); }
   else {
    selectAll.checked = false;
    var checkboxes = accountList.getElementsByTagName("input");
    var checked = false;
    for (var i = 0; i < checkboxes.length; i++) {
     if (checkboxes[i].checked == true) {
      checked = true;
      break;
     }
    }
    btnAssign.setEnabled(checked);
   }
  }
  function setEnabled(bool) {
   ///<summary>
   /// Helper method attached to the Assign button to make it easier to enable/disable the button.
   ///</summary
   if (bool)
   { this.removeAttribute("disabled"); }
   else
   { this.setAttribute("disabled", "disabled"); }
  }
  function setVisibleUIElements(display) {
   ///<summary>
   /// Helper function to show those UI elements initially hidden.
   ///</summary
   if (display) {
    show(tableCaption);
    show(dataTable);
    show(btnAssign);
   }
   else {
    hide(tableCaption);
    hide(dataTable);
    hide(btnAssign);
   }

  }
  function show(element) {
   if (element.tagName.toLowerCase() == "table") {
    element.style.display = "table";
   }
   else {
    element.style.display = "block";
   }

  }
  function hide(element) {
   element.style.display = "none";
  }
  // setText  mitigate differences in how browsers set or get text content.
  function setText(node, text) {
   if (typeof (node.innerText) != "undefined") {
    node.innerText = text;
   }
   else {
    node.textContent = text;
   }

  }

 </script>
</head>
<body>
 <h1>
  Assign Accounts Sample
 </h1>
 <p>
  This page requires JavaScript and will update dynamically.
 </p>
 <p>
  <input id="dispalert" name="dispalert" type="checkbox" value="alert" /><label for="dispalert">Display alert window when data changes.</label>
 </p>
 <p>
  Click the <b>Start</b> button to begin the sample.
 </p>
 <input type="button" id="btnStart" name="btnStart" value="Start" onclick="startSample()" />
 <div id="divSample" style="display: none">
  <label for="userList">
   User:
  </label>
  <select id="userList" name="userList" title="Select a system user from this list." disabled>
   <option value="none">Select a User...</option>
  </select>
  <p id="tableCaption" style="display: none;">
   Top 5 Accounts not assigned to the selected user:
  </p>
  <table class="dataTable" id="dataTable" style="display: none; width: 100%;">
   <thead>
    <tr class="tableHead">
     <th scope="col" id="checkboxCol">
      <input id="selectAll" name="selectAll" title="Select this to select all records" type="checkbox" /><label for="selectAll">Select&amp;nbsp;All</label>
     </th>
     <th scope="col" id="nameCol">
      Name
     </th>
     <th scope="col" id="ownerCol">
      Owner
     </th>
    </tr>
   </thead>
   <tbody id="accountList"></tbody>
  </table>


  <label style="display: none;" for="btnAssign">
   Click to assign selected records
  </label>
  <button id="btnAssign" name="btnAssign" disabled style="display: none; float: right;">
   Assign Records
  </button>
  <label style="display: none;" for="btnAssign">
   Click to assign selected records
  </label>
  <button id="Button1" name="btnAssign" disabled style="display: none; float: right;">
   Assign Records
  </button>
 </div>
 <div id="status">
  &amp;nbsp;
 </div>
</body>
</html>

참고 항목

AssignRequest
샘플: JavaScript를 사용하여 엔터티 메타데이터 검색
웹 리소스를 사용하여 최신 응용 프로그램에 대해 최신 앱 SOAP 끝점 사용
웹 리소스에서 웹 서비스 데이터 사용(OData 및 최신 앱 SOAP 끝점)
응용 프로그램 및 서버 확장 작성
Microsoft Dynamics CRM 2015용 JavaScript 라이브러리
기술 문서: REST 끝점과 함께 옵션 집합 옵션 사용 - JScript

© 2017 Microsoft. All rights reserved. 저작권 정보