33

Given the following attempt to post data to a web service that generates PDF files, PDF rocket (which is awesome by the way).

I get the error Invalid URI: The uri string is too long
Why would anyone impose an arbitrary limit on POSTed data?

using (var client = new HttpClient())
{
    // Build the conversion options
    var options = new Dictionary<string, string>
    {
        { "value", html },
        { "apikey", ConfigurationManager.AppSettings["pdf:key"] },
        { "MarginLeft", "10" },
        { "MarginRight", "10" }
    };

    // THIS LINE RAISES THE EXCEPTION
    var content = new FormUrlEncodedContent(options);

    var response = await client.PostAsync("https://api.html2pdfrocket.com/pdf", content);
    var result = await response.Content.ReadAsByteArrayAsync();
    return result;
}

I receive this rediculous error.

 {System.UriFormatException: Invalid URI: The Uri string is too long.
   at System.UriHelper.EscapeString
   at System.Uri.EscapeDataString
   at System.Net.Http.FormUrlEncodedContent.Encode
   at System.Net.Http.FormUrlEncodedContent.GetContentByteArray

This reminds me of 640k ought to be enough... I mean really?

Jim
  • 14,952
  • 15
  • 80
  • 167
  • With a post can include the content in the http message instead of the URI. A uri has a max length of 2083 characters. – Igor Jul 18 '16 at 15:30
  • aaah, that makes sense, how? – Jim Jul 18 '16 at 15:31
  • 1
    Also why use a web service to make PDF from HTML when you can do it yourself with a free library? – DavidG Jul 18 '16 at 15:32
  • See [this so question](http://stackoverflow.com/a/23586477/1260204), in this instance they send it as json in the http message. There is no limitation on data length when it is done this way. Create a json string using `JsonConvert.SerializeObject` and then send as string content using `StringContent` and send that. – Igor Jul 18 '16 at 15:35
  • Am I therefore correct in saying that there is no built in equivalent in `HttpClient` for `UploadValues("https://api.html2pdfrocket.com/pdf", options)` in the `WebClient` class? – Jim Jul 18 '16 at 15:39
  • @DavidG - tried a plethora of free ones that did not pass muster, and for our volumes we don't currently pay for pdf rocket anyway. – Jim Jul 18 '16 at 15:46

4 Answers4

50

If, like me, you're faced with some wonky 3rd party web service that will only accept form content, you can work around the problem like this:

// Let's assume you've got your key-value pairs organised into a nice Dictionary<string, string> called formData
var encodedItems = formData.Select(i => WebUtility.UrlEncode(i.Key) + "=" + WebUtility.UrlEncode(i.Value));
var encodedContent = new StringContent(String.Join("&", encodedItems), null, "application/x-www-form-urlencoded");

// Post away!
var response = await client.PostAsync(url, encodedContent);
Mick Byrne
  • 14,394
  • 16
  • 76
  • 91
25

With a post can include the content in the http message instead of the URI. A uri has a max length of 2083 characters. You could send it as JSON in the http message instead of the URI which is the recommended way to send larger chunks of data in an HttpPost/HttpPut. I altered your code to make use of it. This assumes that your service you are contacting can work with JSON (.net Web Api out of the box should have no problem with this).

using (var client = new HttpClient())
{
    // Build the conversion options
    var options = new 
    {
        value = html,
        apikey = ConfigurationManager.AppSettings["pdf:key"],
        MarginLeft = "10",
        MarginRight = "10"
    };

    // Serialize our concrete class into a JSON String
    var stringPayload = JsonConvert.SerializeObject(options);
    var content = new StringContent(stringPayload, Encoding.UTF8, "application/json");

    var response = await client.PostAsync("https://api.html2pdfrocket.com/pdf", content);
    var result = await response.Content.ReadAsByteArrayAsync();
    return result;
}

Make sure to install newtonsoft json.

Black
  • 18,150
  • 39
  • 158
  • 271
Igor
  • 60,821
  • 10
  • 100
  • 175
  • 1
    thanks, I didn't read the name of the class carefully enough, I thought it was already a regular post. Thanks. – Jim Jul 18 '16 at 15:47
  • it would be nice to know if there's a built in equivalent to the `WebClient`, `UploadValues` method, which doesn't use json, and still manage to post all the content. – Jim Jul 18 '16 at 15:52
  • @Jim - [this SO answer](http://stackoverflow.com/a/27548828/1260204) might be the approach you are looking for. – Igor Jul 18 '16 at 15:56
  • thanks, that example posts a single value, but the principle applies - crazy it's not built in - thanks for your help. – Jim Jul 18 '16 at 15:59
  • 13
    as a matter of interest, the data I am posting is actually **not** in the url, it's posted exactly like `StringContent`. The error is not in using an incorrect method, the error is in an arbitrary limit on the `EscapeDataString` that `FormUrlEncodedContent` happens to use. – Jim Jul 18 '16 at 16:07
  • If you have a look at the UriFormatException as described at the [Uri(string) constructor reference](https://msdn.microsoft.com/en-us/library/z6c2z492(v=vs.110).aspx), you'll see that the maximum length of a URI string is 65519 characters, while the scheme of the URI string should not exceed 1023 characters. – zodo Dec 08 '16 at 13:01
  • "With a post can include the content in the http message instead of the URI." <- What does this mean? I cannot parse this sentence. – Damn Vegetables Nov 16 '19 at 15:23
13

I just solved a similar problem. For me I was integrating with a backend I didn't control and had to POST file along with form data (eg customerID) as form variables. So switching to JSON or Multipart would break the backend I didn't control. The problem was that large files would cause the FormUrlEncodedContent to throw an error saying "The uri string is too long".

This is the code that solved it for me after two days of effort (note still needs to be tweaked to be ASYNC).

private string UploadFile(string filename, int CustomerID, byte[] ImageData) {

        string Base64String = "data:image/jpeg;base64," + Convert.ToBase64String(ImageData, 0, ImageData.Length);

        var baseAddress = new Uri("[PUT URL HERE]");
        var cookieContainer = new CookieContainer();
        using (var handler = new HttpClientHandler() { AllowAutoRedirect = true, UseCookies = true, CookieContainer = cookieContainer })
        using (var client = new HttpClient(handler) { BaseAddress = baseAddress })
        {

            try {

                //ENCODE THE FORM VARIABLES DIRECTLY INTO A STRING rather than using a FormUrlEncodedContent type which has a limit on its size.        
                string FormStuff = string.Format("name={0}&file={1}&id={2}", filename, HttpUtility.UrlEncode(Base64String), CustomerID.ToString());
                //THEN USE THIS STRING TO CREATE A NEW STRINGCONTENT WHICH TAKES A PARAMETER WHICH WILL FormURLEncode IT AND DOES NOT SEEM TO THROW THE SIZE ERROR
                StringContent content = new StringContent(FormStuff, Encoding.UTF8, "application/x-www-form-urlencoded");

                //UPLOAD
                string url = string.Format("/ajax/customer_image_upload.php");
                response = client.PostAsync(url, content).Result;
                return response.Content.ToString();

            }
            catch (Exception ex) {
                return ex.ToString();
            }



        }

    }
Brian
  • 839
  • 1
  • 10
  • 14
6

@Mick Byrne : Thanks - your solution worked like a charme!

Here is my complete code:

      public async Task DateienSendenAsync (string PfadUndDatei, string Dateiname, String VRPinGUID, String ProjektGUID, String VRPinX, String VRPinY, String VRPinZ)
    {
        var client = new HttpClient();
        // Create the HttpContent for the form to be posted.
        var requestContent = new[] {
                            new KeyValuePair<string, string>("dateiname", Dateiname),

                            new KeyValuePair<string, string>("bild", Convert.ToBase64String(File.ReadAllBytes(PfadUndDatei))),
                            new KeyValuePair<string, string>("VRPinGUID", VRPinGUID),
                            new KeyValuePair<string, string>("ProjektGUID", ProjektGUID),
                            new KeyValuePair<string, string>("ebene", "ebene"),
                            new KeyValuePair<string, string>("raumnummer", "raumnummer"),
                            new KeyValuePair<string, string>("ansichtsname", "ansichtsname"),
                            new KeyValuePair<string, string>("VRPinX", VRPinX),
                            new KeyValuePair<string, string>("VRPinY", VRPinY),
                            new KeyValuePair<string, string>("VRPinZ", VRPinZ),

                            };

        String url = "http://yourhomepage/path/upload.php";

        var encodedItems = requestContent.Select(i => WebUtility.UrlEncode(i.Key) + "=" + WebUtility.UrlEncode(i.Value));
        var encodedContent = new StringContent(String.Join("&", encodedItems), null, "application/x-www-form-urlencoded");

        // Post away!
        var response = await client.PostAsync(url, encodedContent);



    }