Merge object_parameters
This commit is contained in:
Коммит
3d6a786489
|
@ -43,7 +43,7 @@ namespace Microsoft.MobileBlazorBindings.Core
|
|||
/// <param name="parent"></param>
|
||||
/// <param name="parameters"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<TComponent> AddComponent<TComponent>(IElementHandler parent, Dictionary<string, string> parameters = null) where TComponent : IComponent
|
||||
public async Task<TComponent> AddComponent<TComponent>(IElementHandler parent, Dictionary<string, object> parameters = null) where TComponent : IComponent
|
||||
{
|
||||
return (TComponent)await AddComponent(typeof(TComponent), parent, parameters).ConfigureAwait(false);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -155,112 +155,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)
|
||||
if (prop == null)
|
||||
{
|
||||
throw new InvalidOperationException($"Object of type '{component.GetType()}' does not have a property matching the name '{parameter.Key}'.");
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
throw new InvalidOperationException($"Object of type '{component.GetType()}' does not have a property matching the name '{parameter.Key}'.");
|
||||
prop.SetValue(component, parameter.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <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;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly:InternalsVisibleTo("Microsoft.MobileBlazorBindings.UnitTests, PublicKey=0024000004800000140100000602000000240000525341310008000001000100d569ddedcc845677b8d3a876b4fd0aee523a4260bfa2a62590184038fd15e6a78b4931a1501644ad1a087b2d3f949e407e52e98ec8fdfb49228b0e7abafb99aa83a5bb6021181a8a69e17e1b0ab4d9fdb1402b254cb56006c35fc46904ed83d1d795a0ceaf34600f3344718f8f81aa79a305fcd87acf01be47d29ddc4dc22db66bf2aea3102b1d51961acb0f3a8e66fcba8705c23f868cebfc7487f741dd3c249acf0bdbe5ad183cf2c5c20abaed017ca2e1b44f10504b90eee3245152ff2bd0198041645435ddaf4fb2cb5c1bc95c5915d7d338f1f20a38fe91892b5baa6b974630b9eb5ea508e2589d0bd8ea255b9b0869ed1b843521c9fc511d482a81c7df")]
|
|
@ -3,6 +3,7 @@
|
|||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -13,36 +13,6 @@ namespace Microsoft.MobileBlazorBindings.UnitTests
|
|||
[TestFixture]
|
||||
public class NativeComponentRendererTests
|
||||
{
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
}
|
||||
private static Guid testGuid = Guid.NewGuid();
|
||||
public static IEnumerable<TestCaseData> TryParseTestData
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return new TestCaseData("s", typeof(string), "s", true).SetName("Parse valid string");
|
||||
yield return new TestCaseData("5", typeof(int), 5, true).SetName("Parse valid int");
|
||||
yield return new TestCaseData("invalid text", typeof(int), 0, false).SetName("Parse invalid int");
|
||||
yield return new TestCaseData("2020-05-20", typeof(DateTime), new DateTime(2020, 05, 20), true).SetName("Parse valid date");
|
||||
yield return new TestCaseData("invalid text", typeof(DateTime), new DateTime(), false).SetName("Parse invalid date");
|
||||
yield return new TestCaseData(testGuid.ToString(), typeof(Guid), testGuid, true).SetName("Parse valid GUID");
|
||||
yield return new TestCaseData("invalid text", typeof(Guid), new Guid(), false).SetName("Parse invalid GUID");
|
||||
yield return new TestCaseData("{'value': '5'}", typeof(object), null, false).SetName("Parse POCO should find null operation");
|
||||
}
|
||||
}
|
||||
|
||||
[TestCaseSource(typeof(NativeComponentRendererTests), nameof(TryParseTestData))]
|
||||
public void TryParseTest(string s, Type type, object expectedResult, bool expectedSuccess)
|
||||
{
|
||||
var success = NativeComponentRenderer.TryParse(type, s, out var result);
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.AreEqual(expectedResult, result);
|
||||
Assert.AreEqual(expectedSuccess, success);
|
||||
});
|
||||
}
|
||||
|
||||
#pragma warning disable CA1034 // Nested types should not be visible; this is test-only code
|
||||
public class TestComponent : ComponentBase
|
||||
|
@ -50,6 +20,8 @@ namespace Microsoft.MobileBlazorBindings.UnitTests
|
|||
{
|
||||
[Parameter] public string StringParameter { get; set; }
|
||||
[Parameter] public int IntParameter { get; set; }
|
||||
[Parameter] public int? NullableIntParameter { get; set; }
|
||||
[Parameter] public object ObjectParameter { get; set; }
|
||||
public string NonParameter { get; set; }
|
||||
}
|
||||
|
||||
|
@ -57,40 +29,45 @@ namespace Microsoft.MobileBlazorBindings.UnitTests
|
|||
{
|
||||
get
|
||||
{
|
||||
yield return new TestCaseData(new Dictionary<string, string> { { "StringParameter", "paravalue" } }, "paravalue").SetName("Set string parameter");
|
||||
yield return new TestCaseData(new Dictionary<string, string> { { "IntParameter", "5" } }, 5).SetName("Set int parameter");
|
||||
yield return new TestCaseData(new Dictionary<string, object> { { "StringParameter", "paravalue" } }).SetName("Set string parameter");
|
||||
yield return new TestCaseData(new Dictionary<string, object> { { "StringParameter", null } }).SetName("Set string parameter to null");
|
||||
yield return new TestCaseData(new Dictionary<string, object> { { "IntParameter", 5 } }).SetName("Set int parameter");
|
||||
yield return new TestCaseData(new Dictionary<string, object> { { "NullableIntParameter", 5 } }).SetName("Set int? parameter");
|
||||
yield return new TestCaseData(new Dictionary<string, object> { { "NullableIntParameter", null } }).SetName("Set int? parameter to null");
|
||||
yield return new TestCaseData(new Dictionary<string, object> { { "ObjectParameter", "stringObject" } }).SetName("Set object parameter");
|
||||
}
|
||||
}
|
||||
|
||||
[TestCaseSource(typeof(NativeComponentRendererTests), nameof(SetParameterTestData))]
|
||||
public void SetParameterTest(Dictionary<string, string> parameters, object expected)
|
||||
[TestCaseSource(nameof(SetParameterTestData))]
|
||||
public void SetParameterTest(Dictionary<string, object> parameters)
|
||||
{
|
||||
var component = new TestComponent();
|
||||
NativeComponentRenderer.SetNavigationParameters(component, parameters);
|
||||
NativeComponentRenderer.SetParameterArguments(component, parameters);
|
||||
|
||||
var prop = component.GetType().GetProperty(parameters.FirstOrDefault().Key);
|
||||
var value = prop.GetValue(component);
|
||||
Assert.AreEqual(expected, value);
|
||||
var parameterKeyValue = parameters.FirstOrDefault();
|
||||
var prop = component.GetType().GetProperty(parameterKeyValue.Key);
|
||||
var actualValue = prop.GetValue(component);
|
||||
Assert.AreEqual(parameterKeyValue.Value, actualValue);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetIntToString()
|
||||
{
|
||||
var component = new TestComponent();
|
||||
var expected = "NotAnInt";
|
||||
var value = "NotAnInt";
|
||||
|
||||
var parameters = new Dictionary<string, string> { { "IntParameter", expected } };
|
||||
Assert.Throws<InvalidOperationException>(() => NativeComponentRenderer.SetNavigationParameters(component, parameters));
|
||||
var parameters = new Dictionary<string, object> { { "IntParameter", value } };
|
||||
Assert.Throws<ArgumentException>(() => NativeComponentRenderer.SetParameterArguments(component, parameters));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetNonParameter()
|
||||
{
|
||||
var component = new TestComponent();
|
||||
var expected = "NonParameter";
|
||||
var value = "NonParameter";
|
||||
|
||||
var parameters = new Dictionary<string, string> { { "NonParameter", expected } };
|
||||
Assert.Throws<InvalidOperationException>(() => NativeComponentRenderer.SetNavigationParameters(component, parameters));
|
||||
var parameters = new Dictionary<string, object> { { "NonParameter", value } };
|
||||
Assert.Throws<InvalidOperationException>(() => NativeComponentRenderer.SetParameterArguments(component, parameters));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
using Microsoft.MobileBlazorBindings.Core;
|
||||
using NUnit.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.MobileBlazorBindings.UnitTests.ShellNavigation
|
||||
{
|
||||
public class ShellNavigationManagerTests
|
||||
{
|
||||
private static Guid testGuid = Guid.NewGuid();
|
||||
public static IEnumerable<TestCaseData> TryParseTestData
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return new TestCaseData("s", typeof(string), "s", true).SetName("Parse valid string");
|
||||
yield return new TestCaseData("5", typeof(int), 5, true).SetName("Parse valid int");
|
||||
yield return new TestCaseData("5", typeof(int?), 5, true).SetName("Parse valid int?");
|
||||
yield return new TestCaseData("invalid text", typeof(int), 0, false).SetName("Parse invalid int");
|
||||
yield return new TestCaseData("2020-05-20", typeof(DateTime), new DateTime(2020, 05, 20), true).SetName("Parse valid date");
|
||||
yield return new TestCaseData("invalid text", typeof(DateTime), new DateTime(), false).SetName("Parse invalid date");
|
||||
yield return new TestCaseData(testGuid.ToString(), typeof(Guid), testGuid, true).SetName("Parse valid GUID");
|
||||
yield return new TestCaseData(testGuid.ToString(), typeof(Guid?), testGuid, true).SetName("Parse valid GUID?");
|
||||
yield return new TestCaseData("invalid text", typeof(Guid), new Guid(), false).SetName("Parse invalid GUID");
|
||||
yield return new TestCaseData("{'value': '5'}", typeof(object), null, false).SetName("Parse POCO should find null operation");
|
||||
}
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(TryParseTestData))]
|
||||
public void TryParseTest(string s, Type type, object expectedResult, bool expectedSuccess)
|
||||
{
|
||||
var success = ShellNavigationManager.TryParse(type, s, out var result);
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.AreEqual(expectedResult, result);
|
||||
Assert.AreEqual(expectedSuccess, success);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -21,7 +21,7 @@ namespace Microsoft.MobileBlazorBindings
|
|||
|
||||
public override Dispatcher Dispatcher { get; } = new XamarinDeviceDispatcher();
|
||||
|
||||
public Task<TComponent> AddComponent<TComponent>(MC.Element parent, Dictionary<string, string> parameters = null) where TComponent : IComponent
|
||||
public Task<TComponent> AddComponent<TComponent>(MC.Element parent, Dictionary<string, object> parameters = null) where TComponent : IComponent
|
||||
{
|
||||
if (parent is MC.Application app)
|
||||
{
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly:InternalsVisibleTo("Microsoft.MobileBlazorBindings.UnitTests, PublicKey=0024000004800000140100000602000000240000525341310008000001000100d569ddedcc845677b8d3a876b4fd0aee523a4260bfa2a62590184038fd15e6a78b4931a1501644ad1a087b2d3f949e407e52e98ec8fdfb49228b0e7abafb99aa83a5bb6021181a8a69e17e1b0ab4d9fdb1402b254cb56006c35fc46904ed83d1d795a0ceaf34600f3344718f8f81aa79a305fcd87acf01be47d29ddc4dc22db66bf2aea3102b1d51961acb0f3a8e66fcba8705c23f868cebfc7487f741dd3c249acf0bdbe5ad183cf2c5c20abaed017ca2e1b44f10504b90eee3245152ff2bd0198041645435ddaf4fb2cb5c1bc95c5915d7d338f1f20a38fe91892b5baa6b974630b9eb5ea508e2589d0bd8ea255b9b0869ed1b843521c9fc511d482a81c7df")]
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче