NetTopologySuite / NetTopologySuite.IO.GeoJSON

GeoJSON IO module for NTS.
BSD 3-Clause "New" or "Revised" License
113 stars 46 forks source link

Using NetTopologySuite.Io.GeoJSON4STJ with FluentValidation throws ArgumentOutOfRangeException #109

Open jeroenwo opened 2 years ago

jeroenwo commented 2 years ago

Good morning,

I am using NetTopologySuite.Io.GeoJSON4STJ in combination with FluentValidation, and when I try to do a POST to an endpoint with for example an IFeature with Polygon data, I get the following exception:

System.ArgumentOutOfRangeException: Specified argument was out of the range of valid values. (Parameter 'X called on empty Point') at NetTopologySuite.Geometries.Point.get_X()

I have narrowed it down to thFluentValidation initialization:

Reproduction:

  1. Use the example vor from
  2. Add <PackageReference Include="FluentValidation.AspNetCore" Version="10.3.3" />
  3. Add the following line in Program.cs: builder.Services.AddFluentValidation();
  4. Create a POST that accepts an IFeature
  5. Set a breakpoint in the controller method
  6. Run the project
  7. Run Swagger
  8. POST a Polygon

Here is an example of the project I use: TestGeoJsonWebApi.zip

The complete stack trace:

System.ArgumentOutOfRangeException: Specified argument was out of the range of valid values. (Parameter 'X called on empty Point') at NetTopologySuite.Geometries.Point.get_X() at Microsoft.Extensions.Internal.PropertyHelper.CallNullSafePropertyGetter[TDeclaringType,TValue](Func2 getter, Object target) at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.DefaultComplexObjectValidationStrategy.Enumerator.<>c__DisplayClass13_1.<MoveNext>b__1() at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationEntry.get_Model() at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor.VisitChildren(IValidationStrategy strategy) at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor.VisitComplexType(IValidationStrategy defaultStrategy) at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor.VisitImplementation(ModelMetadata& metadata, String& key, Object model) at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor.Visit(ModelMetadata metadata, String key, Object model) at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor.VisitChildren(IValidationStrategy strategy) at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor.VisitComplexType(IValidationStrategy defaultStrategy) at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor.VisitImplementation(ModelMetadata& metadata, String& key, Object model) at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor.Visit(ModelMetadata metadata, String key, Object model) at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor.VisitChildren(IValidationStrategy strategy) at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor.VisitComplexType(IValidationStrategy defaultStrategy) at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor.VisitImplementation(ModelMetadata& metadata, String& key, Object model) at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor.Visit(ModelMetadata metadata, String key, Object model) at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor.VisitChildren(IValidationStrategy strategy) at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor.VisitComplexType(IValidationStrategy defaultStrategy) at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor.VisitImplementation(ModelMetadata& metadata, String& key, Object model) at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor.Visit(ModelMetadata metadata, String key, Object model) at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor.VisitChildren(IValidationStrategy strategy) at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor.VisitComplexType(IValidationStrategy defaultStrategy) at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor.VisitImplementation(ModelMetadata& metadata, String& key, Object model) at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor.Visit(ModelMetadata metadata, String key, Object model) at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor.Validate(ModelMetadata metadata, String key, Object model, Boolean alwaysValidateAtTopLevel, Object container) at FluentValidation.AspNetCore.FluentValidationVisitor.<>n__1(ModelMetadata metadata, String key, Object model, Boolean alwaysValidateAtTopLevel, Object container) at FluentValidation.AspNetCore.FluentValidationVisitor.<>c__DisplayClass2_0.<Validate>g__BaseValidate|0() in /media/jskinner/Linux2/code/FluentValidation/src/FluentValidation.AspNetCore/FluentValidationVisitor.cs:line 44 at FluentValidation.AspNetCore.FluentValidationVisitor.ValidateInternal(ModelMetadata metadata, String key, Object model, Func1 continuation) in /media/jskinner/Linux2/code/FluentValidation/src/FluentValidation.AspNetCore/FluentValidationVisitor.cs:line 62 at FluentValidation.AspNetCore.FluentValidationVisitor.Validate(ModelMetadata metadata, String key, Object model, Boolean alwaysValidateAtTopLevel, Object container) in /media/jskinner/Linux2/code/FluentValidation/src/FluentValidation.AspNetCore/FluentValidationVisitor.cs:line 46 at Microsoft.AspNetCore.Mvc.ModelBinding.ObjectModelValidator.Validate(ActionContext actionContext, ValidationStateDictionary validationState, String prefix, Object model, ModelMetadata metadata, Object container) at Microsoft.AspNetCore.Mvc.ModelBinding.ParameterBinder.EnforceBindRequiredAndValidate(ObjectModelValidator baseObjectValidator, ActionContext actionContext, ParameterDescriptor parameter, ModelMetadata metadata, ModelBindingContext modelBindingContext, ModelBindingResult modelBindingResult, Object container) at Microsoft.AspNetCore.Mvc.ModelBinding.ParameterBinder.BindModelAsync(ActionContext actionContext, IModelBinder modelBinder, IValueProvider valueProvider, ParameterDescriptor parameter, ModelMetadata metadata, Object value, Object container) at Microsoft.AspNetCore.Mvc.Controllers.ControllerBinderDelegateProvider.<>cDisplayClass0_0.<gBind|0>d.MoveNext() --- End of stack trace from previous location --- at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.gAwaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.gAwaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.gAwaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope) at Microsoft.AspNetCore.Routing.EndpointMiddleware.gAwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger) at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context) at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext) at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider) at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

I know it isn't a purely NTS problem but more an integration thing, but I was wondering if you can shed some light on it..

airbreather commented 2 years ago

Very related to #68

jeroenwo commented 2 years ago

That issue was openend in 2020 and isn't closed yet. Does that mean that all hope is lost (in my case ;)? Without FV, everything seems to be in working order..

FObermaier commented 2 years ago

Would it be sufficient to avoid throwing exceptions on properties? If so, that can be discussed, I think.

airbreather commented 2 years ago

I think changing the property getters not to throw is a good idea regardless. Perhaps return double.NaN.

That said, ultimately, if your model contains an object that's serialized using our GeoJSON stuff and you use Swashbuckle or a similar tool to auto-generate the spec for it by reflecting over the CLR model instead of using that same GeoJSON stuff, then you're going to get an incorrect result. Changing a few property getters to stop throwing errors won't fix that.

jeroenwo commented 2 years ago

As a workaround, I have removed builder.Services.AddFluentValidation();. Since I am not using FV in the web API for model validation but more downstream, I was able to inject them using builder.Services.AddValidatorsFromAssemblyContaining<MyCustomValidator>(); and retrieving it via the IServiceProvider.

I can use NTS.IO.GeoJSON4STJ now..

shanerogers commented 1 year ago

@jeroenwo we also discovered this issue yesterday.. sigh. This really does need fixing. Luckily we also do not use fluent validation in our asp.net pipeline. Our fluent validation occurs in our queries/commands. How can we get the Point and other objects to not throw an exception on {get} I wonder if the nullable changes 'required' keywords for C# can be used and would help...?

Atomosk commented 1 year ago

As a workaround you can add the FluentValidation.AspNetCore nuget and use the [ValidateNever] attribute on a Geometry property.