Allow providing any arguments to components

This commit is contained in:
Oleksandr Liakhevych 2021-10-10 22:38:21 +03:00
Родитель 10f8ab28c5
Коммит e90714251c
3 изменённых файлов: 106 добавлений и 89 удалений

Просмотреть файл

@ -55,7 +55,7 @@ namespace Microsoft.MobileBlazorBindings.Core
/// <param name="parent"></param>
/// <param name="parameters"></param>
/// <returns></returns>
public async Task<IComponent> AddComponent(Type componentType, IElementHandler parent, Dictionary<string, string> parameters = null)
public async Task<IComponent> AddComponent(Type componentType, IElementHandler parent, Dictionary<string, object> parameters = null)
{
try
{
@ -71,7 +71,7 @@ namespace Microsoft.MobileBlazorBindings.Core
_componentIdToAdapter[componentId] = rootAdapter;
SetNavigationParameters(component, parameters);
SetParameterArguments(component, parameters);
await RenderRootComponentAsync(componentId).ConfigureAwait(false);
return component;
@ -146,110 +146,35 @@ namespace Microsoft.MobileBlazorBindings.Core
return result;
}
public static void SetNavigationParameters(IComponent component, Dictionary<string, string> parameters)
internal static void SetParameterArguments(IComponent component, Dictionary<string, object> arguments)
{
if (component == null)
{
throw new ArgumentNullException(nameof(component));
}
if (parameters == null || parameters.Count == 0)
if (arguments == null || arguments.Count == 0)
{
//parameters will often be null. e.g. if you navigate with no parameters or when creating a root component.
return;
}
foreach (var parameter in parameters)
foreach (var parameter in arguments)
{
var prop = component.GetType().GetProperty(parameter.Key);
if (prop != null)
{
var parameterAttribute = prop.GetCustomAttribute(typeof(ParameterAttribute));
if (parameterAttribute == null)
{
throw new InvalidOperationException($"Object of type '{component.GetType()}' has a property matching the name '{parameter.Key}', but it does not have [ParameterAttribute] or [CascadingParameterAttribute] applied.");
}
if (TryParse(prop.PropertyType, parameter.Value, out var result))
{
prop.SetValue(component, result);
}
else
{
throw new InvalidOperationException($"Unable to set property {parameter.Key} on object of type '{component.GetType()}'.The value {parameter.Value}. can not be converted to a {prop.PropertyType.Name}");
}
}
else
if (prop == null)
{
throw new InvalidOperationException($"Object of type '{component.GetType()}' does not have a property matching the name '{parameter.Key}'.");
}
}
}
/// <summary>
/// Converts a string into the specified type. If conversion was successful, parsed property will be of the correct type and method will return true.
/// If conversion fails it will return false and parsed property will be null.
/// This method supports the 8 data types that are valid navigation parameters in Blazor. Passing a string is also safe but will be returned as is because no conversion is neccessary.
/// </summary>
/// <param name="type"></param>
/// <param name="s"></param>
/// <param name="result">The parsed object of the type specified. This will be null if conversion failed.</param>
/// <returns>True if s was converted successfully, otherwise false</returns>
public static bool TryParse(Type type, string s, out object result)
{
bool success;
var parameterAttribute = prop.GetCustomAttribute(typeof(ParameterAttribute));
if (parameterAttribute == null)
{
throw new InvalidOperationException($"Object of type '{component.GetType()}' has a property matching the name '{parameter.Key}', but it does not have [ParameterAttribute] or [CascadingParameterAttribute] applied.");
}
if (type == typeof(string))
{
result = s;
success = true;
prop.SetValue(component, parameter.Value);
}
else if (type == typeof(int))
{
success = int.TryParse(s, out var parsed);
result = parsed;
}
else if (type == typeof(Guid))
{
success = Guid.TryParse(s, out var parsed);
result = parsed;
}
else if (type == typeof(bool))
{
success = bool.TryParse(s, out var parsed);
result = parsed;
}
else if (type == typeof(DateTime))
{
success = DateTime.TryParse(s, out var parsed);
result = parsed;
}
else if (type == typeof(decimal))
{
success = decimal.TryParse(s, out var parsed);
result = parsed;
}
else if (type == typeof(double))
{
success = double.TryParse(s, out var parsed);
result = parsed;
}
else if (type == typeof(float))
{
success = float.TryParse(s, out var parsed);
result = parsed;
}
else if (type == typeof(long))
{
success = long.TryParse(s, out var parsed);
result = parsed;
}
else
{
result = null;
success = false;
}
return success;
}
}
}

Просмотреть файл

@ -50,7 +50,7 @@ namespace Microsoft.MobileBlazorBindings
//This version also allows an optional set of parameters
//The only downside is you can't have design/compiletime type safety
//There's a lot of duplicate code between the two, can probably refactor the core of the method into a separate method that they both call
public static async Task<IComponent> AddComponent(this IServiceProvider services, XF.Element parent, Type type, System.Collections.Generic.Dictionary<string, string> parameters = null)
public static async Task<IComponent> AddComponent(this IServiceProvider services, XF.Element parent, Type type, System.Collections.Generic.Dictionary<string, object> parameters = null)
{
if (services is null)
{

Просмотреть файл

@ -108,7 +108,8 @@ namespace Microsoft.MobileBlazorBindings
var renderer = new MobileBlazorBindingsRenderer(_services, _services.GetRequiredService<ILoggerFactory>());
#pragma warning restore CA2000 // Dispose objects before losing scope
var addComponentTask = renderer.AddComponent(componentType, container, route.Parameters);
var convertedParameters = ConvertParameters(componentType, route.Parameters);
var addComponentTask = renderer.AddComponent(componentType, container, convertedParameters);
var elementAddedTask = container.WaitForElementAsync();
await Task.WhenAny(addComponentTask, elementAddedTask).ConfigureAwait(false);
@ -147,5 +148,96 @@ namespace Microsoft.MobileBlazorBindings
}
}
}
internal static Dictionary<string, object> ConvertParameters(Type componentType, Dictionary<string, string> parameters)
{
if (parameters is null)
{
return null;
}
var convertedParameters = new Dictionary<string, object>();
foreach (var keyValue in parameters)
{
var propertyType = componentType.GetProperty(keyValue.Key)?.PropertyType ?? typeof(string);
if (!TryParse(propertyType, keyValue.Value, out var parsedValue))
{
throw new InvalidOperationException($"The value {keyValue.Value} can not be converted to a {propertyType.Name}");
}
convertedParameters[keyValue.Key] = parsedValue;
}
return convertedParameters;
}
/// <summary>
/// Converts a string into the specified type. If conversion was successful, parsed property will be of the correct type and method will return true.
/// If conversion fails it will return false and parsed property will be null.
/// This method supports the 8 data types that are valid navigation parameters in Blazor. Passing a string is also safe but will be returned as is because no conversion is neccessary.
/// </summary>
/// <param name="type"></param>
/// <param name="s"></param>
/// <param name="result">The parsed object of the type specified. This will be null if conversion failed.</param>
/// <returns>True if s was converted successfully, otherwise false</returns>
internal static bool TryParse(Type type, string s, out object result)
{
bool success;
type = Nullable.GetUnderlyingType(type) ?? type;
if (type == typeof(string))
{
result = s;
success = true;
}
else if (type == typeof(int))
{
success = int.TryParse(s, out var parsed);
result = parsed;
}
else if (type == typeof(Guid))
{
success = Guid.TryParse(s, out var parsed);
result = parsed;
}
else if (type == typeof(bool))
{
success = bool.TryParse(s, out var parsed);
result = parsed;
}
else if (type == typeof(DateTime))
{
success = DateTime.TryParse(s, out var parsed);
result = parsed;
}
else if (type == typeof(decimal))
{
success = decimal.TryParse(s, out var parsed);
result = parsed;
}
else if (type == typeof(double))
{
success = double.TryParse(s, out var parsed);
result = parsed;
}
else if (type == typeof(float))
{
success = float.TryParse(s, out var parsed);
result = parsed;
}
else if (type == typeof(long))
{
success = long.TryParse(s, out var parsed);
result = parsed;
}
else
{
result = null;
success = false;
}
return success;
}
}
}