diff --git a/RestSharp.sln.DotSettings b/RestSharp.sln.DotSettings
index 975adf68e..0d8bf09d0 100644
--- a/RestSharp.sln.DotSettings
+++ b/RestSharp.sln.DotSettings
@@ -82,6 +82,7 @@
True
True
True
+ True
True
True
True
diff --git a/src/RestSharp/Request/RestRequestExtensions.cs b/src/RestSharp/Request/RestRequestExtensions.cs
index c6dc9d4cf..807d72662 100644
--- a/src/RestSharp/Request/RestRequestExtensions.cs
+++ b/src/RestSharp/Request/RestRequestExtensions.cs
@@ -12,8 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+using System.ComponentModel;
+using System.Linq.Expressions;
+using System.Reflection;
using System.Text.RegularExpressions;
-using RestSharp.Extensions;
using RestSharp.Serializers;
namespace RestSharp;
@@ -208,6 +210,25 @@ public static RestRequest AddParameter(this RestRequest request, string? name, o
? request.AddBodyParameter(name, value)
: request.AddParameter(Parameter.CreateParameter(name, value, type, encode));
+ ///
+ /// Adds the provided parameters container to the request.
+ ///
+ ///
+ /// The provided model has its public properties interpreted as parameters of the provided .
+ ///
+ /// Request instance
+ /// An arbitrary model containing public properties that should be interpreted as parameters of the provided type
+ /// Enum value specifying what kind of parameters are being added
+ /// Encode the value or not, default true
+ /// The type of the arbitrary model being provided as a parameters container. This information is needed in order to
+ /// cache properties based on their static type, for performance reasons.
+ /// The provided instance updated.
+ public static RestRequest AddParameters(this RestRequest request, TParams @params, ParameterType type, bool encode = true)
+ where TParams : class {
+ request.Parameters.AddParameters(Parameterizer.GetParameters(@params, type, encode));
+ return request;
+ }
+
static RestRequest AddBodyParameter(this RestRequest request, string? name, object value)
=> name != null && name.Contains("/")
? request.AddBody(value, name)
@@ -432,4 +453,98 @@ static void CheckAndThrowsDuplicateKeys(ICollection
if (duplicateKeys.Any()) throw new ArgumentException($"Duplicate header names exist: {string.Join(", ", duplicateKeys)}");
}
+
+ static class Parameterizer where TParams : class {
+ static readonly IReadOnlyCollection Properties =
+ typeof(TParams).GetProperties(BindingFlags.Public | BindingFlags.Instance)
+ .Select(ParameterizedProperty.GetParameterizedPublicInstanceProperty)
+ .ToArray();
+
+ ///
+ /// Instantiates and gets a new instance containing the values of the public properties of the provided
+ /// as its underlying parameters.
+ ///
+ /// An arbitrary model containing public properties that should be interpreted as parameters of the provided type
+ /// Enum value specifying what kind of parameter is being added
+ /// Encode the value or not, default true
+ /// A new parameters collection containing the values of the public properties of the provided model as its underlying
+ /// parameters
+ internal static ParametersCollection GetParameters(TParams @params, ParameterType type, bool encode = true) {
+ var parameters = Properties.Select(
+ param => Parameter.CreateParameter(
+ param.Name,
+ param.GetPropertyValue(@params),
+ type,
+ encode
+ )
+ );
+ return new ParametersCollection(parameters);
+ }
+
+ sealed record ParameterizedProperty {
+ ///
+ /// Gets the of the property from which this
+ /// instance was created.
+ ///
+ internal string Name { get; }
+ readonly Func _getPropertyValue;
+
+ ParameterizedProperty(string name, Func getPropertyValue) {
+ Name = name;
+ _getPropertyValue = getPropertyValue;
+ }
+
+ ///
+ /// Gets the value of the property this instance represents for the provided model.
+ ///
+ /// The parameters container from which to get the value of the property this instance represents.
+ /// The value of the property this instance represents for the specified parameters container.
+ internal string GetPropertyValue(TParams @params) => _getPropertyValue(@params);
+
+ ///
+ /// Returns a new instance from the provided .
+ /// otherwise returns null.
+ ///
+ ///
+ /// The provided getter must be known not to be null before calling this method. It must also be an instance
+ /// property, as opposed to a static one.
+ ///
+ /// The property from which to try creating a new parameterized property.
+ /// A new parameterized property caching the information of the provided instance.
+ internal static ParameterizedProperty GetParameterizedPublicInstanceProperty(PropertyInfo property)
+ => new ParameterizedProperty(property.Name, MakeGetPropertyValue(property.GetGetMethod()));
+
+ static Func MakeGetPropertyValue(MethodInfo getter) {
+ var paramsParam = Expression.Parameter(typeof(TParams));
+
+ Expression callGetter = Expression.Call(paramsParam, getter);
+ var convertToStringExpression = GetConvertToStringExpression();
+
+ Expression GetConvertToStringExpression() {
+ var getterReturnType = getter.ReturnType;
+
+ if (getterReturnType == typeof(string)) {
+ return callGetter;
+ }
+
+ var toStringConverter = TypeDescriptor.GetConverter(getterReturnType);
+
+ var convertToStringMethod = typeof(TypeConverter).GetMethod(
+ nameof(TypeConverter.ConvertToInvariantString),
+ new[] { getterReturnType }
+ )!;
+
+ var convertToStringMethodFirstParamType = convertToStringMethod.GetParameters().First().ParameterType;
+
+ if (getterReturnType.IsValueType && !convertToStringMethodFirstParamType.IsValueType) {
+ callGetter = Expression.Convert(callGetter, convertToStringMethodFirstParamType);
+ }
+
+ return Expression.Call(Expression.Constant(toStringConverter), convertToStringMethod, callGetter);
+ }
+
+ return Expression.Lambda>(convertToStringExpression, paramsParam).Compile();
+ }
+ }
+ }
}
diff --git a/test/RestSharp.Tests/RestRequestExtensionsTests.cs b/test/RestSharp.Tests/RestRequestExtensionsTests.cs
new file mode 100644
index 000000000..5bbc71af5
--- /dev/null
+++ b/test/RestSharp.Tests/RestRequestExtensionsTests.cs
@@ -0,0 +1,48 @@
+// Copyright © 2009-2020 John Sheehan, Andrew Young, Alexey Zimarev and RestSharp community
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#nullable enable
+using System.ComponentModel;
+
+namespace RestSharp.Tests;
+
+public sealed class RestRequestExtensionsTests {
+ [Fact]
+ public void RestRequest_AddParameters_AddsParameters() {
+ var model = new User(Guid.Parse("27b7acdd-6184-4e21-9b64-cdaa2b2477bd"), "Joe", null, DateTime.Parse("2022-01-01T00:00:00Z"), 100, 100, 100);
+ var request = new RestRequest().AddParameters(model, ParameterType.QueryString);
+
+ string ConvertToInvariantString(object value) => TypeDescriptor.GetConverter(value.GetType()).ConvertToInvariantString(value);
+
+ var expectedParameters = new ParametersCollection(
+ new[] {
+ new QueryParameter(nameof(User.Id), ConvertToInvariantString(model.Id)),
+ new QueryParameter(nameof(User.FirstName), model.FirstName),
+ new QueryParameter(nameof(User.LastName), model.LastName),
+ new QueryParameter(nameof(User.LastLogin), ConvertToInvariantString(model.LastLogin)),
+ new QueryParameter(nameof(User.NameSpan), model.NameSpan.ToString()),
+ new QueryParameter(nameof(User.Score), ConvertToInvariantString(model.Score)),
+ new QueryParameter(nameof(User.Age), ConvertToInvariantString(model.Age)),
+ new QueryParameter(nameof(User.Special), ConvertToInvariantString(model.Special))
+ }
+ );
+
+ request.Parameters.Should().BeEquivalentTo(expectedParameters);
+ }
+
+ sealed record User(Guid Id, string FirstName, string? LastName, DateTime? LastLogin, int Score, uint Age, nint Special) {
+ public ReadOnlySpan NameSpan => FirstName.AsSpan();
+ }
+}