Skip to content

Conversation

@jeffl8n
Copy link
Contributor

@jeffl8n jeffl8n commented Jun 17, 2021

Catch when the converter throws an exception and return false and set the out value to default.

Addresses #33587

jeffl8n added 2 commits June 17, 2021 00:03
Add test cases for trying to convert valid and invalid GUID values.
Catch when the converter throws an exception and return false and set the out value to default.
@jeffl8n jeffl8n requested a review from a team as a code owner June 17, 2021 04:06
@ghost ghost added area-blazor Includes: Blazor, Razor Components community-contribution Indicates that the PR has been added by a community member labels Jun 17, 2021
@SteveSandersonMS
Copy link
Member

Thanks for contributing this, @jeffl8n!

Rather than adding a try/catch that affects all bind converters, would it be possible to do this inside the GUID converter only? This is because:

(1) try/catch has a perf cost on WebAssembly. It's a very small cost, but nonzero, so we try to minimize it in code paths that might run at a high frequency.
(2) Suppressing other exceptions might lose valuable information. I know that things like int.TryParse should generally not throw, and that's all the more reason to raise a serious alert if there is some case where it does unexpectedly throw.

@jeffl8n
Copy link
Contributor Author

jeffl8n commented Jun 17, 2021

Thanks for the review @SteveSandersonMS.

Should it be a new type specific handler for GUID, or should we wrap the generic type converter with the try/catch?

var converted = typeConverter.ConvertFrom(context: null, culture ?? CultureInfo.CurrentCulture, obj);

This would add the try/catch for any of the type converter not already explicitly handled in BindConverter.cs.

@SteveSandersonMS
Copy link
Member

I was expecting it to be a GUID-specific parser, i.e., a new case in this long list here:

if (typeof(T) == typeof(string))
{
parser = ConvertToString;
}
else if (typeof(T) == typeof(bool))
{
parser = ConvertToBool;
}
else if (typeof(T) == typeof(bool?))
{
parser = ConvertToNullableBool;
}
else if (typeof(T) == typeof(int))
{
parser = ConvertToInt;
}
else if (typeof(T) == typeof(int?))
{
parser = ConvertToNullableInt;
}
else if (typeof(T) == typeof(long))
{
parser = ConvertToLong;
}
else if (typeof(T) == typeof(long?))
{
parser = ConvertToNullableLong;
}
else if (typeof(T) == typeof(short))
{
parser = ConvertToShort;
}
else if (typeof(T) == typeof(short?))
{
parser = ConvertToNullableShort;
}
else if (typeof(T) == typeof(float))
{
parser = ConvertToFloat;
}
else if (typeof(T) == typeof(float?))
{
parser = ConvertToNullableFloat;
}
else if (typeof(T) == typeof(double))
{
parser = ConvertToDoubleDelegate;
}
else if (typeof(T) == typeof(double?))
{
parser = ConvertToNullableDoubleDelegate;
}
else if (typeof(T) == typeof(decimal))
{
parser = ConvertToDecimal;
}
else if (typeof(T) == typeof(decimal?))
{
parser = ConvertToNullableDecimal;
}
else if (typeof(T) == typeof(DateTime))
{
parser = ConvertToDateTime;
}
else if (typeof(T) == typeof(DateTime?))
{
parser = ConvertToNullableDateTime;
}
else if (typeof(T) == typeof(DateTimeOffset))
{
parser = ConvertToDateTime;
}
else if (typeof(T) == typeof(DateTimeOffset?))
{
parser = ConvertToNullableDateTime;
}
else if (typeof(T).IsEnum)
{
// We have to deal invoke this dynamically to work around the type constraint on Enum.TryParse.
var method = _convertToEnum ??= typeof(BindConverter).GetMethod(nameof(ConvertToEnum), BindingFlags.NonPublic | BindingFlags.Static)!;
parser = method.MakeGenericMethod(typeof(T)).CreateDelegate(typeof(BindParser<T>), target: null);
}

jeffl8n added 2 commits June 17, 2021 21:47
Remove try/catch added in TryConvertTo  and replace with Guid Converter
{
value = default;
return false;
}
Copy link
Member

Choose a reason for hiding this comment

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

I must be missing something here. How come you don't need a try/catch any more? I thought that adding the try/catch was the main reason for the change.

Does structuring the code this way somehow mean there isn't an unhandled exception any more? If you could clarify that would be great!

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, because before the GuidConverter would call the ctor which throws on invalid values. #33587 (comment)
You can see it in the stack trace from here #33587 (comment)


Should there be another case for Guid??

Copy link
Contributor Author

@jeffl8n jeffl8n Jun 18, 2021

Choose a reason for hiding this comment

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

That's correct. Calling Guid.TryParse sets the ThrowStyle to None - https://github.com/dotnet/runtime/blob/5221db92ed579f8ffa56f1d8e84f07ba6d896077/src/libraries/System.Private.CoreLib/src/System/Guid.cs#L242
But calling the Guid ctor sets the ThrowStyle to All - https://github.com/dotnet/runtime/blob/5221db92ed579f8ffa56f1d8e84f07ba6d896077/src/libraries/System.Private.CoreLib/src/System/Guid.cs#L210

Should there be another case for Guid??

Do you mean like the check for null/empty string? The Guid class performs several checks for length and formatting, so that current check could be removed as well, but I left it since it would prevent having to call TryParse for the simplest invalid case.

Copy link
Contributor

Choose a reason for hiding this comment

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

I meant for the typeof(Guid?) type, all the others do both checks like typeof(DateTimeOffset) and typeof(DateTimeOffset?):

else if (typeof(T) == typeof(DateTimeOffset))
{
parser = ConvertToDateTime;
}
else if (typeof(T) == typeof(DateTimeOffset?))
{
parser = ConvertToNullableDateTime;
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You're right. I've just added another case for Nullable Guid.

@SteveSandersonMS SteveSandersonMS added this to the .NET 7 Planning milestone Sep 14, 2021
@SteveSandersonMS
Copy link
Member

A quick note on this: on investigation I realise this doesn't impact using @bind with a GUID property/field at all, since the existing parsing logic inside EventCallbackFactoryBinderExtensions.CreateBinderCore already handles any exception from the typeconverter and rejects the invalid value cleanly as we'd want.

This change only affects someone calling BindConverter.TryConvertTo<Guid>(...) directly from their own code, which is legal, but not a mainstream scenario.

Since we're late in 6.0 now we're trying not to take inessential changes, so I'll mark this for 7.0. We can merge it into main. Note that the logic inside ConvertToNullableGuidCore in this PR seems incorrect, as it's treating null/blank inputs as parsing failures when for nullable outputs the other conversion logic treats them as successfully parsed as null. Also it's missing unit tests for the nullable cases.

* main:
  Handle more cases with the new entry point pattern (dotnet#33500)
  [main] Update dependencies from dotnet/runtime dotnet/efcore (dotnet#33560)
  Refactor LongPolling in Java to avoid stackoverflow (dotnet#33564)
  Optimize QueryCollection (dotnet#32829)
  Switch to in-org action (dotnet#33610)
  Improve Codespaces + C# extension interaction (dotnet#33614)
@jeffl8n
Copy link
Contributor Author

jeffl8n commented Sep 18, 2021

Thanks @SteveSandersonMS. I added the missing tests for the Guid? type and have corrected the parsing success to true in the case of null/empty string trying to be parsed.

@SteveSandersonMS
Copy link
Member

SteveSandersonMS commented Jan 20, 2022

@jeffl8n Now that 7.0 work is underway, we're ready to proceed with this. However the underlying code has changed too much to merge this directly, since we started using file-scoped namespaces and the default indentation level has changed. I also wanted to tweak the test cases a bit.

So, I've filed a continuation at #39649 and will close this one in favour of that. The new PR still credits you as the commit author :)

@jeffl8n jeffl8n deleted the issue-33587 branch January 20, 2022 17:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-blazor Includes: Blazor, Razor Components community-contribution Indicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants