Skip to content

Commit

Permalink
Merge branch 'object_parameters' of https://github.com/Dreamescaper/B…
Browse files Browse the repository at this point in the history
…lazorBindings.Maui into Dreamescaper-object_parameters
  • Loading branch information
Eilon committed Jun 7, 2022
2 parents da040c6 + f95ed23 commit df7c963
Show file tree
Hide file tree
Showing 8 changed files with 174 additions and 135 deletions.
101 changes: 12 additions & 89 deletions src/Microsoft.MobileBlazorBindings.Core/NativeComponentRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ internal ElementManager ElementManager
/// <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
{
Expand All @@ -70,7 +70,7 @@ public async Task<IComponent> AddComponent(Type componentType, IElementHandler p
_componentIdToAdapter[componentId] = rootAdapter;
SetNavigationParameters(component, parameters);
SetParameterArguments(component, parameters);
await RenderRootComponentAsync(componentId).ConfigureAwait(false);
return component;
Expand Down Expand Up @@ -154,112 +154,35 @@ internal NativeComponentAdapter CreateAdapterForChildComponent(IElementHandler p
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;

type = Nullable.GetUnderlyingType(type) ?? type;
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;
}
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;
prop.SetValue(component, parameter.Value);
}
return success;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
using System.Runtime.CompilerServices;

[assembly:InternalsVisibleTo("Microsoft.MobileBlazorBindings.UnitTests, PublicKey=0024000004800000140100000602000000240000525341310008000001000100d569ddedcc845677b8d3a876b4fd0aee523a4260bfa2a62590184038fd15e6a78b4931a1501644ad1a087b2d3f949e407e52e98ec8fdfb49228b0e7abafb99aa83a5bb6021181a8a69e17e1b0ab4d9fdb1402b254cb56006c35fc46904ed83d1d795a0ceaf34600f3344718f8f81aa79a305fcd87acf01be47d29ddc4dc22db66bf2aea3102b1d51961acb0f3a8e66fcba8705c23f868cebfc7487f741dd3c249acf0bdbe5ad183cf2c5c20abaed017ca2e1b44f10504b90eee3245152ff2bd0198041645435ddaf4fb2cb5c1bc95c5915d7d338f1f20a38fe91892b5baa6b974630b9eb5ea508e2589d0bd8ea255b9b0869ed1b843521c9fc511d482a81c7df")]
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<TargetFramework>netcoreapp3.1</TargetFramework>

<IsPackable>false</IsPackable>
<SignAssembly>true</SignAssembly>
</PropertyGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,84 +13,61 @@ 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
#pragma warning restore CA1034 // Nested types should not be visible
{
[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; }
}

public static IEnumerable<TestCaseData> SetParameterTestData
{
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));
}
}
}
Original file line number Diff line number Diff line change
@@ -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);
});
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public static class MobileBlazorBindingsHostExtensions
//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)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
using System.Runtime.CompilerServices;

[assembly:InternalsVisibleTo("Microsoft.MobileBlazorBindings.UnitTests, PublicKey=0024000004800000140100000602000000240000525341310008000001000100d569ddedcc845677b8d3a876b4fd0aee523a4260bfa2a62590184038fd15e6a78b4931a1501644ad1a087b2d3f949e407e52e98ec8fdfb49228b0e7abafb99aa83a5bb6021181a8a69e17e1b0ab4d9fdb1402b254cb56006c35fc46904ed83d1d795a0ceaf34600f3344718f8f81aa79a305fcd87acf01be47d29ddc4dc22db66bf2aea3102b1d51961acb0f3a8e66fcba8705c23f868cebfc7487f741dd3c249acf0bdbe5ad183cf2c5c20abaed017ca2e1b44f10504b90eee3245152ff2bd0198041645435ddaf4fb2cb5c1bc95c5915d7d338f1f20a38fe91892b5baa6b974630b9eb5ea508e2589d0bd8ea255b9b0869ed1b843521c9fc511d482a81c7df")]

0 comments on commit df7c963

Please sign in to comment.