From f3af0ce61b44c234931b3110883a261793025064 Mon Sep 17 00:00:00 2001 From: Alexey Zimarev Date: Tue, 7 Jun 2022 13:28:09 +0200 Subject: [PATCH] Added custom props attribute (#1868) * Added custom props attribute * Updated the docs * Values must match --- docs/usage.md | 15 ++++ ...{MiscExtensions.cs => StreamExtensions.cs} | 38 +--------- src/RestSharp/Parameters/ObjectParser.cs | 73 +++++++++++++++++++ test/RestSharp.Tests/ObjectParserTests.cs | 40 ++++++++++ 4 files changed, 129 insertions(+), 37 deletions(-) rename src/RestSharp/Extensions/{MiscExtensions.cs => StreamExtensions.cs} (53%) create mode 100644 src/RestSharp/Parameters/ObjectParser.cs create mode 100644 test/RestSharp.Tests/ObjectParserTests.cs diff --git a/docs/usage.md b/docs/usage.md index 40169b44e..d19374204 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -215,6 +215,21 @@ request.AddParameter("ids", "123,456"); Remember that `AddObject` only works if your properties have primitive types. It also works with collections of primitive types as shown above. +If you need to override the property name or format, you can do it using the `RequestProperty` attribute. For example: + +```csharp +public class RequestModel { + // override the name and the format + [RequestAttribute(Name = "from_date", Format = "d")] + public DateTime FromDate { get; set; } +} + +// add it to the request +request.AddObject(new RequestModel { FromDate = DateTime.Now }); +``` + +In this case, the request will get a GET or POST parameter named `from_date` and its value would be the current date in short date format. + ### Url Segment Unlike `GetOrPost`, this `ParameterType` replaces placeholder values in the `RequestUrl`: diff --git a/src/RestSharp/Extensions/MiscExtensions.cs b/src/RestSharp/Extensions/StreamExtensions.cs similarity index 53% rename from src/RestSharp/Extensions/MiscExtensions.cs rename to src/RestSharp/Extensions/StreamExtensions.cs index 38902a6c7..74b7e684b 100644 --- a/src/RestSharp/Extensions/MiscExtensions.cs +++ b/src/RestSharp/Extensions/StreamExtensions.cs @@ -17,7 +17,7 @@ namespace RestSharp.Extensions; /// /// Extension method overload! /// -static class MiscExtensions { +static class StreamExtensions { /// /// Read a stream into a byte array /// @@ -39,40 +39,4 @@ public static async Task ReadAsBytes(this Stream input, CancellationToke return ms.ToArray(); } - - internal static IEnumerable<(string Name, string? Value)> GetProperties(this object obj, params string[] includedProperties) { - // automatically create parameters from object props - var type = obj.GetType(); - var props = type.GetProperties(); - - foreach (var prop in props) { - if (!IsAllowedProperty(prop.Name)) - continue; - - var val = prop.GetValue(obj, null); - - if (val == null) - continue; - - var propType = prop.PropertyType; - - if (propType.IsArray) { - var elementType = propType.GetElementType(); - var array = (Array)val; - - if (array.Length > 0 && elementType != null) { - // convert the array to an array of strings - var values = array.Cast().Select(item => item.ToString()); - yield return (prop.Name, string.Join(",", values)); - - continue; - } - } - - yield return (prop.Name, val.ToString()); - } - - bool IsAllowedProperty(string propertyName) - => includedProperties.Length == 0 || includedProperties.Length > 0 && includedProperties.Contains(propertyName); - } } \ No newline at end of file diff --git a/src/RestSharp/Parameters/ObjectParser.cs b/src/RestSharp/Parameters/ObjectParser.cs new file mode 100644 index 000000000..9076fdc6e --- /dev/null +++ b/src/RestSharp/Parameters/ObjectParser.cs @@ -0,0 +1,73 @@ +// 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. +// + +using System.Reflection; + +namespace RestSharp; + +static class ObjectParser { + public static IEnumerable<(string Name, string? Value)> GetProperties(this object obj, params string[] includedProperties) { + // automatically create parameters from object props + var type = obj.GetType(); + var props = type.GetProperties(); + + foreach (var prop in props.Where(x => IsAllowedProperty(x.Name))) { + var val = prop.GetValue(obj, null); + + if (val == null) continue; + + yield return prop.PropertyType.IsArray + ? GetArray(prop, val) + : GetValue(prop, val); + } + + string? ParseValue(string? format, object? value) => format == null ? value?.ToString() : string.Format($"{{0:{format}}}", value); + + (string, string?) GetArray(PropertyInfo propertyInfo, object? value) { + var elementType = propertyInfo.PropertyType.GetElementType(); + var array = (Array)value!; + + var attribute = propertyInfo.GetCustomAttribute(); + var name = attribute?.Name ?? propertyInfo.Name; + + if (array.Length > 0 && elementType != null) { + // convert the array to an array of strings + var values = array + .Cast() + .Select(item => ParseValue(attribute?.Format, item)); + return (name, string.Join(",", values)); + } + + return (name, null); + } + + (string, string?) GetValue(PropertyInfo propertyInfo, object? value) { + var attribute = propertyInfo.GetCustomAttribute(); + var name = attribute?.Name ?? propertyInfo.Name; + var val = ParseValue(attribute?.Format, value); + return (name, val); + } + + bool IsAllowedProperty(string propertyName) + => includedProperties.Length == 0 || includedProperties.Length > 0 && includedProperties.Contains(propertyName); + } +} + +[AttributeUsage(AttributeTargets.Property)] +public class RequestPropertyAttribute : Attribute { + public string? Name { get; set; } + + public string? Format { get; set; } +} diff --git a/test/RestSharp.Tests/ObjectParserTests.cs b/test/RestSharp.Tests/ObjectParserTests.cs new file mode 100644 index 000000000..8c0c3097b --- /dev/null +++ b/test/RestSharp.Tests/ObjectParserTests.cs @@ -0,0 +1,40 @@ +using System.Globalization; + +namespace RestSharp.Tests; + +public class ObjectParserTests { + [Fact] + public void ShouldUseRequestProperty() { + var now = DateTime.Now; + var dates = new[] { now, now.AddDays(1), now.AddDays(2) }; + var request = new TestObject { + SomeData = "test", + SomeDate = now, + Plain = 123, + PlainArray = dates, + DatesArray = dates + }; + + var parsed = request.GetProperties().ToDictionary(x => x.Name, x => x.Value); + parsed["some_data"].Should().Be(request.SomeData); + parsed["SomeDate"].Should().Be(request.SomeDate.ToString("d")); + parsed["Plain"].Should().Be(request.Plain.ToString()); + // ReSharper disable once SpecifyACultureInStringConversionExplicitly + parsed["PlainArray"].Should().Be(string.Join(",", dates.Select(x => x.ToString()))); + parsed["dates"].Should().Be(string.Join(",", dates.Select(x => x.ToString("d")))); + } + + class TestObject { + [RequestProperty(Name = "some_data")] + public string SomeData { get; set; } + + [RequestProperty(Format = "d")] + public DateTime SomeDate { get; set; } + + [RequestProperty(Name = "dates", Format = "d")] + public DateTime[] DatesArray { get; set; } + + public int Plain { get; set; } + public DateTime[] PlainArray { get; set; } + } +}