Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix DefaultBinder conversions from Dictionary<string,object> #2052

Closed
wants to merge 3 commits into from

Conversation

joaompneves
Copy link
Contributor

@joaompneves joaompneves commented May 17, 2017

The following example will work and not crash:

binder = new DefaultBinder(new DefaultFieldNameConverter());
obj = new Dictionary<string, object> { { "key", "value" } };
binder.Bind(new [] {  obj }, typeof(Dictionary<string, object>[])); => returns [ obj ]
binder.Bind(obj, typeof(Dictionary<string, object>)); => returns obj
  1. Dictionaries were wrongly considered collections.
  2. Some conversions involving Dictionaries caused NullReferenceExceptions to be thrown

…The following example will work and not crash:

binder = new DefaultBinder(new DefaultFieldNameConverter());
result = new[] { new Dictionary<string, object> { { "key", "value" } } };
binder.Bind(result, typeof(Dictionary<string, object>[]));
@amaitland
Copy link
Member

What's the go here? Is there actually a bug?

@joaompneves
Copy link
Contributor Author

There is but I have to take a deep look at it, that's why I closed it for now. I'll re-open later.

…The following example will work and not crash: binder = new DefaultBinder(new DefaultFieldNameConverter()); result = new[] { new Dictionary<string, object> { { "key", "value" } } }; binder.Bind(result, typeof(Dictionary<string, object>[]));
@joaompneves joaompneves changed the title Fix DefaultBinder conversions of Dictionary<string,object>[] Fix DefaultBinder conversions from Dictionary<string,object> May 18, 2017
@joaompneves joaompneves reopened this May 18, 2017
Copy link
Member

@amaitland amaitland left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there is a cleaner way to resolve this, comments made inline.

Before this can be merged we'll need to add some code examples/use cases to BindingTest.html. Unit Tests probably should be added......

@@ -72,6 +72,10 @@ public virtual object Bind(object obj, Type modelType)
{
throw new ArgumentException("When modelType is an enumerable it must specify the type.", "modelType");
}
}
else if (modelType.IsGenericObject())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type checking here seems a little weak, I think we'd be better off immediately after the obj == null check to using something like

//If the object can be directly assigned to the modelType then return immediately. 
if(modelType.IsAssignableFrom(obj.GetType()))
{
	return obj;
}

/// </summary>
/// <param name="source">source type</param>
/// <returns>return true if is a generic js object</returns>
public static bool IsGenericObject(this Type source)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name of this method is confusing, I look at it and think of .Net generics, I don't actually think it's required either if we make a few other simplifications.

@@ -94,7 +103,7 @@ public static bool IsAssignableToGenericType(this Type givenType, Type genericTy
/// </summary>
/// <param name="source">The type to check.</param>
/// <returns><see langword="true" /> if the type is an collection, otherwise <see langword="false" />.</returns>
public static bool IsCollection(this Type source)
private static bool IsCollection(this Type source)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reason for making this private is?

//If the type is a dictionary and the PropertyType isn't then we'll bind.
if (obj.GetType() == dictionaryType && modelProperty.PropertyType != dictionaryType)
if (obj.GetType().IsGenericObject() && !modelProperty.PropertyType.IsGenericObject())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As the two if branches here are duplicated I think we can simplify the whole BindValue method. We should be able to simplify things quite a bit, using something like the following.

protected virtual void BindValue(BindingMemberInfo modelProperty, object obj, BindingContext context)
{
	if(obj == null)
	{
		return;
	}

	if (modelProperty.PropertyType.IsAssignableFrom(obj.GetType()))
	{
		//Simply set the property
		modelProperty.SetValue(context.Model, obj);
	}
	else
	{
		//Cannot directly set the property attempt to bind
		var model = Bind(obj, modelProperty.PropertyType);

		modelProperty.SetValue(context.Model, model);
	}
}

@@ -190,7 +192,7 @@ protected virtual object CreateModel(Type modelType, Type genericType)

protected virtual object GetValue(string propertyName, BindingContext context)
{
if (context.Object.GetType() == typeof(Dictionary<string, object>))
if (context.Object.GetType().IsGenericObject())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please revert this and remove IsGenericObject extension method.

@@ -67,7 +66,17 @@ public static bool IsArray(this Type source)
/// <returns>return if collection, array or enumerable</returns>
public static bool IsCollectionOrArray(this Type source)
{
return source.IsCollection() || source.IsArray() || source.IsEnumerable();
return !source.IsGenericObject() && (source.IsCollection() || source.IsArray() || source.IsEnumerable());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm hopeful we can revert this change with the other changes I've proposed.

@@ -92,7 +96,7 @@ public virtual object Bind(object obj, Type modelType)

if (val != null)
{
if (val.GetType() == typeof(Dictionary<string, object>))
if (val.GetType().IsGenericObject())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please revert this and remove IsGenericObject extension method.

@amaitland amaitland added this to the 59.0.0 milestone May 22, 2017
amaitland added a commit that referenced this pull request May 30, 2017
… - greatly simplifies the code

Remove IsCollectionOrArray extension method, use the long hand - makes the code more readable.
Switch to using Activator.CreateInstance directly
Remove a large number of unused extensions in ModelBindingExtensions
Alternate to #2052
@amaitland
Copy link
Member

I've merged my proposed changes in commit 0d48e30

Code is greatly simplified. If your still experiencing a problem I believe the simplifications are a better starting point for any further bug fixes. Where possible please include a test case that reproduces your problem, would make my life a lot easier, thank you.

@amaitland amaitland closed this May 30, 2017
@joaompneves
Copy link
Contributor Author

It works as expected, great!
Where should I add the test case?
In the PR description I added a snippet of code (a test case) that was crashing before.

@amaitland amaitland modified the milestones: 59.0.0, 62.0.0 Dec 13, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants