11

I have been using AddParameter to include XML bodies in my HTTP requests:

request.AddParameter(contentType, body, ParameterType.RequestBody);

However, this does not seem to work for non-string bodies. (RestSharp's Http.RequestBody is a string for some reason.) I tried using AddFile(), but I can't find any way to avoid encoding the "file" as multipart/form, even if I've only supplied a single object.

I'm not at all opposed to underhanded reflection to solve this problem, but I'm hoping to avoid modifying the source just to send arbitrary data in an HTTP request.

Edit: regarding the requests I'm trying to send, they just look like this:

PUT ... HTTP/1.1
Accept: application/vnd...
Authorization: Basic ...
Content-Type: application/octet-stream

<arbitrary bytes>

Ideally, I'd like to use the same calls to send a different content type:

PUT ... HTTP/1.1
Accept: application/vnd...
Authorization: Basic ...
Content-Type: application/vnd...

<other arbitrary bytes>
ladenedge
  • 13,197
  • 11
  • 60
  • 117
  • Not entirely sure I'm grasping the problem here; all http requests are text; if you send binary, then it's encoded as base64 (normally) which is still text, albeit unreadable text. – Russ Clarke Apr 15 '12 at 02:07
  • to qualify the normally bit, there are other schemes such as uuencode which allow better compression but ultimately it's the same deal - use an encoding that allows the full value of a byte to be represented in the range of printable characters which HTTP supports. – Russ Clarke Apr 15 '12 at 02:09
  • Can you post an example of the raw http request you're trying to replicate? – John Sheehan Apr 15 '12 at 02:14
  • 1
    @RussC: HTTP bodies may contain [any byte value](http://www.w3.org/Protocols/rfc2616/rfc2616-sec7.html#sec7.2). Perhaps I don't understand what you mean? – ladenedge Apr 15 '12 at 02:39
  • @JohnSheehan: I've updated the question with some examples. Are those detailed enough? (I did omit some of the more automatic fields.) – ladenedge Apr 15 '12 at 02:40
  • @ladenedge it's not any byte value as such, it's: OCTET = . The problem is when you have to represent more than 8 bits. If you're sending a 8byte long (64bit number) then if the receiver interprets that 8byte value as 8 distinct bytes, then it's going to mess up. In other words, technically it's a byte, but the server can't assume it's a byte, so we have encoding to represent values that are larger then can be represented in 8bits. – Russ Clarke Apr 15 '12 at 04:11
  • @RussC: in this case I am setting the content type to 'application/octet-stream' (or some other "binary" content type (eg. image/jpeg)) such that the server can in fact assume the body is a sequence of bytes without structure. – ladenedge Apr 15 '12 at 05:29
  • We're still looking at this; one thing that just occurred to me, but it may not make a difference, the link you gave above in reference to HTTP Bodies, was actually about 'Entity bodies' but the requests you're trying to send would indicate that you're trying to encode your binary into the Message Body without using an Entity header ? – Russ Clarke Apr 16 '12 at 00:20
  • 2
    To get back to your original question, there's currently no support for this and it's an extremely rare request so I'm not likely to add it myself. I'd recommend juts using HttpWebRequest directly. – John Sheehan Apr 16 '12 at 05:32
  • 1
    i had a similar requirement: i need to post multipart/form-data with only parameters and without files. made a pull request on https://github.com/restsharp/RestSharp/pull/385 but did not get any attention – Martin Meixger Aug 22 '13 at 07:35

6 Answers6

9

There have been some modifications made in the latest version that allow a single file to be used without creating a multipart form request. Here is a gist that shows and example:

https://gist.github.com/hallem/5faaa6bebde50641e928

Michael
  • 168
  • 2
  • 9
  • 4
    FYI for those who, like me, skipped this answer because it says "file": it does not have to be a file. The key feature is that if you add the body with `request.AddParameter("application/pdf", documentBytes, ParameterType.RequestBody)` and `documentBytes` is a `byte[]`, then the bytes will be sent directly as the specified `Content-Type` (`application/pdf` here). – meustrus Feb 10 '16 at 15:52
5

I ran into the same issue. I had to upload exactly one file and use a specific content type for communicating with an REST Interface. You could modify Http.RequestBody to byte[] (and all dependencies on that), but i think its easier this way:

I modified RestSharp, so that it only use Multipart Encoding when number of Files > 1 or number of Files = 1 and there is also body or other post data set.

You have to modify Http.cs on Line 288 from

if(HasFiles)

to

if(Files.Count > 1 || (Files.Count == 1 && (HasBody || Parameters.Any())))

For Http.Sync.cs modify PreparePostData from

private void PreparePostData(HttpWebRequest webRequest)
{
    if (HasFiles)
    {
        webRequest.ContentType = GetMultipartFormContentType();
        using (var requestStream = webRequest.GetRequestStream())
        {
            WriteMultipartFormData(requestStream);
        }
    }

    PreparePostBody(webRequest);
}

to

private void PreparePostData(HttpWebRequest webRequest)
{
    // Multiple Files or 1 file and body and / or parameters
    if (Files.Count > 1 || (Files.Count == 1 && (HasBody || Parameters.Any())))
    {
        webRequest.ContentType = GetMultipartFormContentType();
        using (var requestStream = webRequest.GetRequestStream())
        {
            WriteMultipartFormData(requestStream);
        }
    }
    else if (Files.Count == 1)
    {
        using (var requestStream = webRequest.GetRequestStream())
        {
            Files.Single().Writer(requestStream);
        }
    }

    PreparePostBody(webRequest);
}

If you use the async version, you have to modify the code similar to the above in Http.Async.cs.

Now u can use RestSharp like this

IRestRequest request = new RestRequest("urlpath", Method.PUT);
request.AddHeader("Content-Type", "application/zip");
request.AddFile("Testfile", "C:\\File.zip");

Client.Execute(request);

AddFile also provides an overload for setting direct byte[] data instead of a path. Hope that helps.

Dresel
  • 2,375
  • 1
  • 28
  • 44
  • 1
    FWIW an alternative RestSharp modification would be to change its default encoding to [Windows-1252](http://en.wikipedia.org/wiki/Windows-1252) and pass in a string encoded as such to `AddBody`. It turns out that [a 1252-encoded string is equivalent to binary](http://stackoverflow.com/a/5798027/222481). – ladenedge Sep 03 '12 at 18:12
5

In the latest version of RestSharp at the time of writing (version 104), the modification needs to be in Http.Sync.cs , method PreparePostData, which should read as:

    private void PreparePostData(HttpWebRequest webRequest)
    {

        // Multiple Files or 1 file and body and / or parameters
        if (Files.Count > 1 || (Files.Count == 1 && (HasBody || Parameters.Count>0)))
        {
            webRequest.ContentType = GetMultipartFormContentType();
            using (var requestStream = webRequest.GetRequestStream())
            {
                WriteMultipartFormData(requestStream);
            }
        }
        else if (Files.Count == 1)
        {
            using (var requestStream = webRequest.GetRequestStream())
            {
                Files[0].Writer(requestStream);
            }
        }
        PreparePostBody(webRequest);
    }
Vincent Sos
  • 51
  • 1
  • 1
4

I had the same problem, but I didn't fancy forking the code and I didn't like the alternative suggested by Michael as the documentation says "RequestBody: Used by AddBody() (not recommended to use directly)".

Instead I replaced the RestClient.HttpFactory with my own:

RestClient client = GetClient();

var bytes = await GetBytes();
client.HttpFactory = new FactoryWithContent { GetBytes = () => new Bytes(bytes, "application/zip") };

var request = new RestRequest();
return await client.ExecutePostTaskAsync(request);

Where Bytes and FactoryWithContent look like:

public class Bytes
{
    public Bytes(byte[] value, string type)
    {
        Value = value;
        Type = type;
    }

    public byte[] Value { get; private set; }
    public string Type { get; private set; }
}

public class FactoryWithContent : IHttpFactory
{
    public IHttp Create()
    {
        var http = new Http();

        var getBytes = GetBytes;
        if (getBytes != null)
        {
            var bs = getBytes();
            http.RequestBodyBytes = bs.Value;
            http.RequestContentType = bs.Type;
        }

        return http;
    }

    public Func<Bytes> GetBytes { get; set; }
}
andygjp
  • 2,464
  • 1
  • 16
  • 11
3

I had the same issue. Turned out that RestSharp behaves in a little odd way.

NOT WORKING:

request.Parameters.Add(new Parameter() {
  ContentType = "application/x-www-form-urlencoded",
  Type = ParameterType.RequestBody,
  Value = bytes
});

WORKING (Add content-type as name):

request.Parameters.Add(new Parameter() {
  Name = "application/x-www-form-urlencoded", // This is the 'funny' part
  ContentType = "application/x-www-form-urlencoded",
  Type = ParameterType.RequestBody,
  Value = bytes
});

I tried this solution based on a comment here: https://github.com/restsharp/RestSharp/issues/901

which states "...name value will be used as Content-Type Header and contentType value will be ignored."

You dont have to add the value as the Content-Type parameter as well, but I fear that a future bug-fix might change the behaviour and then requiring the Content-Type to be used instead of name.

Stephan Møller
  • 1,247
  • 19
  • 39
0

Modifications to Http.Async.cs are also necessary for the RequestStreamCallback method. I'm actually working on getting this fix into the repo and published to Nuget as I'm helping to maintain the project now. Here's a link to the issue that's been created for this: https://github.com/restsharp/RestSharp/issues/583

Michael
  • 168
  • 2
  • 9