Open Ducatel opened 1 year ago
It can be done that parameters are added to the request in the same order as they are added to RestRequest
. Parameter types already exist. It is a bit unclear from the issue description if it's about query parameters alone (they should be already added in the same order as supplied), or any parameter. I remember issues asking for parameters order in multipart forms.
The cross-type parameter order is an issue with current implementation as RestSharp adds them by parameter type (all POST parameters, all files, etc), so order between parameter types is not kept. It can be solved, but it means that the request builder needs to be rebuilt.
The cross-type parameter order is an issue with current implementation as RestSharp adds them by parameter type (all POST parameters, all files, etc), so order between parameter types is not kept. It can be solved, but it means that the request builder needs to be rebuilt.
It's exactly that. In your case we need to have files at the end and POST parameters in first places. The solution I explain allow to solve my current issue, but not mixing doing stuff like
To do that, we indeed have to rebuild the query builder or allow to use a different query builder.
Maybe an IQueryBuilder
interface which expose BuildContent
method.
Or allow the usage of child class from RequestContent
which allow overriding BuildContent
I think the solution should be to do that rewrite I mentioned.
Here's my use case for the reference: the web server requires files to go after other parameters in multipart POST requests. Meanwhile, RestSharp 110.2.0 adds files first. My workaround is to reorder content manually in OnBeforeRequest
. The OrderBy
method uses stable sort, so the relative order between other parameters is preserved.
request.OnBeforeRequest += message =>
{
if (message.Content is MultipartFormDataContent oldContent)
{
var newContent = new MultipartFormDataContent(
oldContent.Headers.ContentType?.Parameters?.FirstOrDefault(p => p.Name == "boundary")?.Value
?? "---" + Guid.NewGuid().ToString("N"));
foreach (var c in oldContent.OrderBy(content => content is StreamContent ? 1 : 0))
{
newContent.Add(c);
}
message.Content = newContent;
}
return ValueTask.CompletedTask;
};
if it helps anyone, I was able to find a solution as follows. In my API requirements, there must be some JSON metadata at the first part of the multipart message, and only then the binary file part. I had previously added the metadata as a JSON string in ParameterType.Body, and then the file, so I had to organize the order myself (see 2115#issuecomment-1636157099) now I have converted the metadata json string into a jsonobject and passed it as a separate file with ParameterType.File. So I end up with ONLY multiple ParameterType.File that I can add in my specific order because I don't include mixed paramater types.
I've taken @paul-sh 's approach and made a hack to provide a solution for @Ducatel 's problem (while @b166er 's way can fix most APIs)
You need to use my AddFileAtIndex(...)
methods instead of the original AddFile(...)
ones, like
request.AddParameter("Payload1", "{ \"x\" : \"y\" }");
request.AddFileAtIndex("File1", new byte[3] { 1, 2, 3 }, "xyz1.pdf");
request.AddFileAtIndex("File2", new byte[3] { 1, 2, 3 }, "xyz2.pdf");
request.AddParameter("Payload2", "{ \"x\" : \"y\" }");
request.AddFileAtIndex("File3", new byte[3] { 1, 2, 3 }, "xyz3.pdf");
request.AddHeader("Content-Type", "multipart/form-data");
request.AlwaysMultipartFormData = true;
The implementation is done in a class extension for RestRequest:
namespace RestSharp
{
public static partial class RestRequestFileIndexExtensions
{
public const string FileIndexParameterNamePrefix = "$RestSharpAddFile_";
public static RestRequest AddFileAtIndex(
this RestRequest request,
string name,
string path,
ContentType contentType = null,
FileParameterOptions options = null
)
{
request = request.AddFile(name, path, contentType, options);
request = request.AddParameter(FileIndexParameterNamePrefix + (request.Files.Count - 1).ToString(CultureInfo.InvariantCulture), @"");
request.OnBeforeRequest -= OnBeforeRequestAddFileIndex;
request.OnBeforeRequest += OnBeforeRequestAddFileIndex;
return request;
}
public static RestRequest AddFileAtIndex(
this RestRequest request,
string name,
byte[] bytes,
string fileName,
ContentType contentType = null,
FileParameterOptions options = null
)
{
request = request.AddFile(name, bytes, fileName, contentType, options);
request = request.AddParameter(FileIndexParameterNamePrefix + (request.Files.Count - 1).ToString(CultureInfo.InvariantCulture), @"");
request.OnBeforeRequest -= OnBeforeRequestAddFileIndex;
request.OnBeforeRequest += OnBeforeRequestAddFileIndex;
return request;
}
public static RestRequest AddFileAtIndex(
this RestRequest request,
string name,
Func<Stream> getFile,
string fileName,
ContentType contentType = null,
FileParameterOptions options = null
)
{
request = request.AddFile(name, getFile, fileName, contentType, options);
request = request.AddParameter(FileIndexParameterNamePrefix + (request.Files.Count - 1).ToString(CultureInfo.InvariantCulture), @"");
request.OnBeforeRequest -= OnBeforeRequestAddFileIndex;
request.OnBeforeRequest += OnBeforeRequestAddFileIndex;
return request;
}
public static Func<HttpRequestMessage, ValueTask> OnBeforeRequestAddFileIndex = message =>
{
if (message.Content is MultipartFormDataContent oldContent)
{
var newContent = new MultipartFormDataContent(
oldContent.Headers.ContentType?.Parameters?.FirstOrDefault(p => p.Name == "boundary")?.Value?.Trim('"')
?? "---" + Guid.NewGuid().ToString("N"));
var streamContentList = oldContent.Where(content => content is StreamContent).ToList();
foreach (var c in oldContent.Where(content => !(content is StreamContent)))
{
if (c.Headers.ContentDisposition.Name.StartsWith(FileIndexParameterNamePrefix))
{
int index = int.Parse(c.Headers.ContentDisposition.Name.Substring(FileIndexParameterNamePrefix.Length), CultureInfo.InvariantCulture);
newContent.Add(streamContentList[index]);
}
else
{
newContent.Add(c);
}
}
message.Content = newContent;
}
return new ValueTask(Task.Delay(0));
};
}
}
I believe it's all fixable in RestSharp, but it requires a complete rewrite of the RequestContent
class. Unfortunately, I don't have time for that right now.
Hi,
has already mention in this issue #1937 I create this issue to speak about technical solution I will PR.
Is your feature request related to a problem? Please describe.
Some API require an exact order of fields in POST multipart formdata. I the actual behavior the file is always put in first.
Describe the solution you'd like Make fields order parametable for each request.
To do that, I plan to add in RestRequest an array of 3 Enum value (like
ParameterType.File
,ParameterType.Body
,ParameterType.Post
) which will define the order. (Maybe the enum already exist ? I don't really search for yet ) The default order will be the actual one [File, body, post] to not break existing code.In the
RequestContent.BuildContent
I will use this array to re-order call from line 46 to 53 (maybe will add headers in the list also, not sure yet)Do you thinks this can be a good solution ? Or do you have better idea ?
Describe alternatives you've considered No work around found