Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make parameters order in a post query configurable #2098

Open
Ducatel opened this issue Jun 13, 2023 · 7 comments
Open

Make parameters order in a post query configurable #2098

Ducatel opened this issue Jun 13, 2023 · 7 comments

Comments

@Ducatel
Copy link

Ducatel commented Jun 13, 2023

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

@alexeyzimarev
Copy link
Member

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.

@Ducatel
Copy link
Author

Ducatel commented Jun 15, 2023

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

  • POST param 1
  • File 1
  • POST param 2
  • File 2

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

@alexeyzimarev
Copy link
Member

I think the solution should be to do that rewrite I mentioned.

@paul-sh
Copy link

paul-sh commented Nov 30, 2023

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;
};

@b166er
Copy link

b166er commented Dec 1, 2023

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.

@Grueslayer
Copy link

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));
        };
    }
} 

@alexeyzimarev
Copy link
Member

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants