Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 22 additions & 2 deletions Src/Support/Google.Apis.Core/ApplicationContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,33 @@ limitations under the License.

namespace Google
{
/// <summary>Defines the context in which this library runs. It allows setting up custom loggers.</summary>
/// <summary>Defines the context in which this library runs. It allows setting up custom loggers and performance options.</summary>
public static class ApplicationContext
{
private static ILogger logger;

// For testing
internal static void Reset() => logger = null;
internal static void Reset()
{
logger = null;
EnableReflectionCache = false;
}

/// <summary>
/// Gets or sets whether to enable reflection result caching for request parameter properties.
/// </summary>
/// <remarks>
/// <para>
/// When enabled, <see cref="System.Reflection.PropertyInfo"/> lookups for request parameter
/// properties are cached per request type, eliminating repeated reflection overhead.
/// </para>
/// <para>
/// Default is <c>false</c>. Set to <c>true</c> early in application startup before making
/// any API requests. This setting is intended for applications that make many requests and
/// where reflection overhead has been identified as a bottleneck.
/// </para>
/// </remarks>
public static bool EnableReflectionCache { get; set; }

/// <summary>Returns the logger used within this application context.</summary>
/// <remarks>It creates a <see cref="NullLogger"/> if no logger was registered previously</remarks>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

using Google.Apis.Util;

Expand Down Expand Up @@ -142,23 +143,16 @@ public static ParameterCollection FromQueryString(string qs)
/// </summary>
public static ParameterCollection FromDictionary(IDictionary<string, object> dictionary)
{
// Convert to typed dictionary (defaulting to Query) so we can reuse the shared expansion logic.
var typedDict = dictionary.ToDictionary(
kvp => kvp.Key,
kvp => new ParameterValue(RequestParameterType.Query, kvp.Value));

// Expand any enumerable values into repeated parameters.
var collection = new ParameterCollection();
foreach (KeyValuePair<string, object> pair in dictionary)
foreach (var param in ParameterUtils.ExpandParametersWithTypes(typedDict))
{
// Try parsing the value of the pair as an enumerable.
var valueAsEnumerable = pair.Value as IEnumerable;
if (!(pair.Value is string) && valueAsEnumerable != null)
{
foreach (var value in valueAsEnumerable)
{
collection.Add(pair.Key, Util.Utilities.ConvertToString(value));
}
}
else
{
// Otherwise just convert it to a string.
collection.Add(pair.Key, pair.Value == null ? null : Util.Utilities.ConvertToString(pair.Value));
}
collection.Add(param.Name, param.Value);
}
return collection;
}
Expand Down
121 changes: 101 additions & 20 deletions Src/Support/Google.Apis.Core/Requests/Parameters/ParameterUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ limitations under the License.
*/

using System;
using System.Collections;
using System.Collections.Generic;
using System.Net.Http;
using System.Linq;
Expand All @@ -38,7 +39,7 @@ public static class ParameterUtils
/// <see cref="Google.Apis.Util.RequestParameterAttribute"/> attribute.
/// </summary>
/// <param name="request">
/// A request object which contains properties with
/// A request object which contains properties with
/// <see cref="Google.Apis.Util.RequestParameterAttribute"/> attribute. Those properties will be serialized
/// to the returned <see cref="System.Net.Http.FormUrlEncodedContent"/>.
/// </param>
Expand All @@ -61,17 +62,40 @@ public static FormUrlEncodedContent CreateFormUrlEncodedContent(object request)
/// <see cref="Google.Apis.Util.RequestParameterAttribute"/> attribute.
/// </summary>
/// <param name="request">
/// A request object which contains properties with
/// A request object which contains properties with
/// <see cref="Google.Apis.Util.RequestParameterAttribute"/> attribute. Those properties will be set
/// in the output dictionary.
/// </param>
public static IDictionary<string, object> CreateParameterDictionary(object request)
{
var dict = new Dictionary<string, object>();
// Use the typed implementation, then drop type information to preserve the legacy return type.
return CreateParameterDictionaryWithTypes(request)
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Value);
}

/// <summary>
/// Creates a parameter dictionary with type information by using reflection to iterate over all properties with
/// <see cref="Google.Apis.Util.RequestParameterAttribute"/> attribute.
/// </summary>
/// <param name="request">
/// A request object which contains properties with
/// <see cref="Google.Apis.Util.RequestParameterAttribute"/> attribute. Those properties will be set
/// in the output dictionary along with their parameter type.
/// </param>
/// <exception cref="InvalidOperationException">
/// Thrown when multiple properties set the same parameter name to non-null values.
/// </exception>
/// <returns>
/// A dictionary where the key is the parameter name and the value is a ParameterValue containing type and value.
/// </returns>
private static IDictionary<string, ParameterValue> CreateParameterDictionaryWithTypes(object request)
{
var dict = new Dictionary<string, ParameterValue>();
IterateParameters(request, (type, name, value) =>
{
if (dict.TryGetValue(name, out var existingValue))
if (dict.TryGetValue(name, out var existingEntry))
{
var existingValue = existingEntry.Value;
// Repeated enum query parameters end up with two properties: a single
// one, and a Repeatable<T> (where the T is always non-nullable, whether or not the parameter
// is optional). If both properties are set, we fail. Note that this delegate is called
Expand All @@ -81,22 +105,22 @@ public static IDictionary<string, object> CreateParameterDictionary(object reque
if (existingValue is null && value is object)
{
// Overwrite null value with non-null value
dict[name] = value;
dict[name] = new ParameterValue(type, value);
}
else if (value is null)
{
// Ignore new null value
}
else
{
// Throw if we see a second null value
// Throw if we see a second non-null value
throw new InvalidOperationException(
$"The query parameter '{name}' is set by multiple properties. For repeated enum query parameters, ensure that only one property is set to a non-null value.");
}
}
else
{
dict.Add(name, value);
dict.Add(name, new ParameterValue(type, value));
}
});
return dict;
Expand All @@ -108,8 +132,8 @@ public static IDictionary<string, object> CreateParameterDictionary(object reque
/// </summary>
/// <param name="builder">The request builder</param>
/// <param name="request">
/// A request object which contains properties with
/// <see cref="Google.Apis.Util.RequestParameterAttribute"/> attribute. Those properties will be set in the
/// A request object which contains properties with
/// <see cref="Google.Apis.Util.RequestParameterAttribute"/> attribute. Those properties will be set in the
/// given request builder object
/// </param>
public static void InitParameters(RequestBuilder builder, object request)
Expand All @@ -120,6 +144,70 @@ public static void InitParameters(RequestBuilder builder, object request)
});
}

/// <summary>
/// Sets request parameters in the given builder with all properties with the
/// <see cref="Google.Apis.Util.RequestParameterAttribute"/> attribute
/// by expanding <see cref="IEnumerable"/> values into multiple parameters.
/// </summary>
/// <param name="builder">The request builder</param>
/// <param name="request">
/// A request object which contains properties with
/// <see cref="Google.Apis.Util.RequestParameterAttribute"/> attribute. Those properties will be set in the
/// given request builder object
/// </param>
public static void InitParametersWithExpansion(RequestBuilder builder, object request)
{
// Use typed methods to preserve RequestParameterType information
var parametersWithTypes = CreateParameterDictionaryWithTypes(request);

// Expand and add all parameters to the builder with their correct types
foreach (var param in ExpandParametersWithTypes(parametersWithTypes))
{
builder.AddParameter(param.Type, param.Name, param.Value);
}
}

/// <summary>
/// Expands a dictionary of typed parameters into a sequence of <see cref="TypedParameter"/> instances.
/// </summary>
/// <remarks>
/// If a parameter value implements <see cref="System.Collections.IEnumerable"/> (and is not a <see cref="string"/>), it is expanded into
/// multiple <see cref="TypedParameter"/> instances with the same name and <see cref="RequestParameterType"/>.
/// This supports repeatable parameters represented as <see cref="Google.Apis.Util.Repeatable{T}"/> (which is <see cref="System.Collections.IEnumerable"/>) and other
/// enumerable values.
/// </remarks>
/// <param name="dictionary">
/// A dictionary where the key is the parameter name and the value is a <see cref="ParameterValue"/> containing both
/// the parameter type and raw value.
/// </param>
/// <returns>
/// An enumerable of <see cref="TypedParameter"/> instances, with enumerable values expanded into repeated parameters.
/// </returns>
internal static IEnumerable<TypedParameter> ExpandParametersWithTypes(IDictionary<string, ParameterValue> dictionary)
{
foreach (var pair in dictionary)
{
var paramType = pair.Value.Type;
var value = pair.Value.Value;
var name = pair.Key;

// Try parsing the value as an enumerable.
var valueAsEnumerable = value as IEnumerable;
if (!(value is string) && valueAsEnumerable != null)
{
foreach (var elem in valueAsEnumerable)
{
yield return new TypedParameter(paramType, name, Utilities.ConvertToString(elem));
}
}
else
{
// Otherwise just convert it to a string.
yield return new TypedParameter(paramType, name, Utilities.ConvertToString(value));
}
}
}

/// <summary>
/// Iterates over all <see cref="Google.Apis.Util.RequestParameterAttribute"/> properties in the request
/// object and invokes the specified action for each of them.
Expand All @@ -128,18 +216,11 @@ public static void InitParameters(RequestBuilder builder, object request)
/// <param name="action">An action to invoke which gets the parameter type, name and its value</param>
private static void IterateParameters(object request, Action<RequestParameterType, string, object> action)
{
// Use reflection to build the parameter dictionary.
foreach (PropertyInfo property in request.GetType().GetProperties(BindingFlags.Instance |
BindingFlags.Public))
// Use ReflectionCache to avoid repeated reflection + attribute lookup on every call.
foreach (var propertyWithAttribute in ReflectionCache.GetRequestParameterProperties(request.GetType()))
{
// Retrieve the RequestParameterAttribute.
RequestParameterAttribute attribute =
property.GetCustomAttributes(typeof(RequestParameterAttribute), false).FirstOrDefault() as
RequestParameterAttribute;
if (attribute == null)
{
continue;
}
var property = propertyWithAttribute.Property;
var attribute = propertyWithAttribute.Attribute;

// Get the name of this parameter from the attribute, if it doesn't exist take a lower-case variant of
// property name.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
Copyright 2026 Google Inc

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 Google.Apis.Util;

namespace Google.Apis.Requests.Parameters
{
/// <summary>
/// Represents a parameter value together with its <see cref="RequestParameterType"/> metadata.
/// </summary>
/// <remarks>
/// <see cref="Value"/> contains the raw CLR value (prior to any string conversion for the wire format).
/// </remarks>
internal readonly struct ParameterValue
{
/// <summary>
/// Gets the parameter type (Path, Query, etc.)
/// </summary>
public RequestParameterType Type { get; }

/// <summary>
/// Gets the parameter value.
/// </summary>
public object Value { get; }

/// <summary>
/// Constructs a new parameter value with type.
/// </summary>
public ParameterValue(RequestParameterType type, object value)
{
Type = type;
Value = value;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
Copyright 2026 Google Inc

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 Google.Apis.Util;

namespace Google.Apis.Requests.Parameters
{
/// <summary>
/// Represents a parameter together with its <see cref="RequestParameterType"/> metadata.
/// </summary>
/// <remarks>
/// <see cref="Value"/> is the string value to be sent on the wire (after conversion via <c>Utilities.ConvertToString</c>).
/// </remarks>
internal readonly struct TypedParameter
{
/// <summary>Gets the parameter type (Path, Query, etc.)</summary>
public RequestParameterType Type { get; }

/// <summary>Gets the parameter name.</summary>
public string Name { get; }

/// <summary>Gets the parameter value.</summary>
public string Value { get; }

/// <summary>Constructs a new typed parameter.</summary>
public TypedParameter(RequestParameterType type, string name, string value)
{
Type = type;
Name = name;
Value = value;
}
}
}
Loading
Loading