3 Replies Latest reply on Aug 15, 2016 11:59 AM by Patrick Abernathy

    Publish Workbook with REST API in .Net

    Patrick Abernathy

      Good afternoon all.  I'm trying to automate our system for publishing workbooks and datasources.  Seems I've run into a snag with regards to uploading our workbook using the rest API's and was hoping someone might be able to figure out where I've gone wrong.  The project already contains a datasource.  I'm trying now to upload the workbook to that project by attaching the raw twb XML to the API call.  Below is the majority of the code that I'm using to wrap the Tableau API.  With my code, I'm receiving a 502 Bad Gateway where in Postman, calling the Tableau API, I'm receiving a 400 Bad Request.  With both scenarios, I'm unable to figure it out.  Thank you for your time.

       

       

      public void UploadWorkbook(TableauProjectModel project, string workBookXml, string xmlPath)
      {
          var boundryString = Guid.NewGuid().ToString().Replace("-", string.Empty);
          var endPoint = "sites/{siteId}/workbooks?overwrite=true";
          var body =
              "--" + boundryString + " " +
              "Content-Disposition: name=\"request_payload\" \n" +
              "Content-Type: text/xml \n" +
              "<tsRequest> \n" +
              "  <workbook name=\"Giving Insights\" showTabs=\"false\" > \n" +
              "    <connectionCredentials /> \n" +
              "    <project id=\"" + project.ProjectId + "\"/> \n" +
              "  </workbook> \n" +
              "</tsRequest> \n" +
              "\n--" + boundryString + "\n" +
              "Content-Disposition: name=\"tableau_workbook\"; filename=\"" + xmlPath + "\" \n" +
              "Content-Type: application/octet-stream \n\n" +
              workBookXml + "\n" +
              "\n--" + boundryString + "--";
      
          var client = new TableauRestClient(endPoint, HttpVerb.POST, body, "multipart/mixed; boundary=" + boundryString);
          client.MakeRequest();
      }
      
      

       

      public TableauRestClient(string endpoint, HttpVerb method, string body, string contentType = "application/xml")
      {
          EndPoint = ConfigurationHelper.InsightsApiUrl + "/" + endpoint;
          Method = method;
          ContentType = contentType;
          Body = body;
      }
      
      public string MakeRequest(params HttpStatusCode[] statusCodes)
      {
          return MakeRequest("", statusCodes);
      }
      
      public string MakeRequest(string parameters, params HttpStatusCode[] statusCodes)
      {
          var acceptedCodes = new List<HttpStatusCode>();
          acceptedCodes.Add(HttpStatusCode.OK);
      
          foreach (var statusCode in statusCodes)
          {
              acceptedCodes.Add(statusCode);
          }
      
          var signIn = SignIn();
      
          EndPoint = EndPoint.Replace("{siteId}", signIn.SiteId);
      
          var request = (HttpWebRequest)WebRequest.Create(EndPoint + parameters);
      
          request.Method = Method.ToString();
          request.ContentLength = 0;
          request.ContentType = ContentType;
          request.Headers.Add("X-Tableau-Auth", signIn.Token);
          
          if (!string.IsNullOrEmpty(Body) && (Method == HttpVerb.POST || Method == HttpVerb.PUT))
          {
              var bytes = Encoding.UTF8.GetBytes(Body);
              request.ContentLength = bytes.Length;
      
              using (var writeStream = request.GetRequestStream())
              {
                  writeStream.Write(bytes, 0, bytes.Length);
              }
          }
      
          using (var response = (HttpWebResponse) request.GetResponse())
          {
              var responseValue = string.Empty;
      
              if (acceptedCodes.All(c => c != response.StatusCode))
              {
                  var message = String.Format("Request failed. Received HTTP {0}", response.StatusCode);
                  SignOut(signIn.Token);
                  throw new ApplicationException(message);
              }
      
              // grab the response
              using (var responseStream = response.GetResponseStream())
              {
                  if (responseStream != null)
                      using (var reader = new StreamReader(responseStream))
                      {
                          responseValue = reader.ReadToEnd();
                      }
              }
      
              SignOut(signIn.Token);
      
              return responseValue;
          }
      }
      
      
      
      
      
      
      
      
      
      
      
      
      
      
        • 1. Re: Publish Workbook with REST API in .Net
          Patrick Abernathy

          Seems I'm going to answer my own question but hope someone else gets use of this.  The 502 error I was getting (which can be found in the Tableau logs directory in the vizportal directory) was "Header section has more than 10240 bytes (maybe it is not properly terminated)".  Found that .Net does something interesting with the \n character and translates it to \r\n.  By it automatically doing this, it causes a mismatch in actual content length and the content length set in the request.  I fixed this simply by changing all my \n to \r\n.  Some other things I've learned on this was that connectionCredentials requires a username and password to be there even if it's going to be ignored, second thing was, watch the spacing.  It's very literal on the documentation between line spacing and make sure that there is no extra spacing after a word.  I've updated below the new Body of the request. 

           

          var body =
              "--" + boundryString + "\r\n" +
              "Content-Disposition: name=\"request_payload\"\r\n" +
              "Content-Type: text/xml\r\n\r\n" +
              "<tsRequest>\r\n" +
              "  <workbook name=\"Giving Insights\" showTabs=\"false\" >\r\n" +
              "    <connectionCredentials name=\"" + {userName} + "\" password=\"" + {password} +"\" /> \r\n" +
              "    <project id=\"" + project.ProjectId + "\"/>\r\n" +
              "  </workbook>\r\n" +
              "</tsRequest>\r\n" +
              "\r\n--" + boundryString + "\r\n" +
              "Content-Disposition: name=\"tableau_workbook\"; filename=\"" + xmlPath + "\"\r\n" +
              "Content-Type: application/octet-stream\r\n\r\n" +
              workBookXml + "\r\n\r\n" +
              "--" + boundryString + "--";
          
          • 2. Re: Publish Workbook with REST API in .Net
            Jeff Strauss

            can you take screenprints of the payload from postman?  usually postman is a good place to start to ensure you have the proper syntax / headers / body.  And a 400 usually indicates a problem with one of the three.

            • 3. Re: Publish Workbook with REST API in .Net
              Patrick Abernathy

              Hey Jeff.  I got the code working in my .Net app and haven't gone back to Postman.  In talking with Support, they suggested not using Postman since it has a hard time with a colon in the body of a request.  If I had to guess, I think the error from Postman is either that or that I didn't have credentials in the connectionCredentials section.  I appreciate the response.