NetTopologySuite / NetTopologySuite.IO.GeoJSON

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

Document how to use IO.GeoJSON in combination with ASP.NET Core #5

Closed airbreather closed 5 years ago

airbreather commented 6 years ago

From @dalbani on March 1, 2018 11:34

It would be nice to have some documentation on how to use NetTopologySuite.IO.GeoJSON in combination with ASP.NET Core. I've managed to configure the MVC engine to produce GeoJSON using NTS serializer, but I couldn't get it to consume GeoJSON to work.

Here's some pseudo-code of what I did to configure MVC:

namespace MyApp
{
    public class Startup
    {
        // ...
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
            services.AddSingleton<IConfigureOptions<MvcOptions>, ConfigureMvcOptions>();
        }
        // ...
    }

    public class ConfigureMvcOptions : IConfigureOptions<MvcOptions>
    {
        private readonly ILogger<MvcOptions> _logger;
        private readonly ObjectPoolProvider _objectPoolProvider;

        public ConfigureMvcOptions(ILogger<MvcOptions> logger, ObjectPoolProvider objectPoolProvider)
        {
            _logger = logger;
            _objectPoolProvider = objectPoolProvider;
        }

        public void Configure(MvcOptions mvcOptions)
        {
            mvcOptions.InputFormatters.Insert(0, new GeoJsonInputFormatter(
                _logger, new JsonSerializerSettings(), ArrayPool<char>.Shared, _objectPoolProvider));

            mvcOptions.OutputFormatters.Insert(0, new GeoJsonOutputFormatter(
                new JsonSerializerSettings
                {
                    // omit "crs: null" properties in GeoJSON output
                    NullValueHandling = NullValueHandling.Ignore
                }, ArrayPool<char>.Shared));
        }
    }
}

The GeoJSON input/output formatters:

namespace MyApp.Formatters
{
    public class GeoJsonInputFormatter : JsonInputFormatter
    {
        public GeoJsonInputFormatter(ILogger logger, JsonSerializerSettings serializerSettings,
            ArrayPool<char> charPool, ObjectPoolProvider objectPoolProvider, bool suppressInputFormatterBuffering = false)
            : base(logger, serializerSettings, charPool, objectPoolProvider, suppressInputFormatterBuffering)
        {
            SupportedMediaTypes.Clear();
            SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/geo+json"));
        }

        protected override JsonSerializer CreateJsonSerializer()
        {
            return GeoJsonSerializer.Create(SerializerSettings, GeometryFactory.Default);
        }
    }

    public class GeoJsonOutputFormatter : JsonOutputFormatter
    {
        public GeoJsonOutputFormatter(JsonSerializerSettings serializerSettings, ArrayPool<char> charPool)
            : base(serializerSettings, charPool)
        {
            SupportedMediaTypes.Clear();
            SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/geo+json"));
        }

        protected override JsonSerializer CreateJsonSerializer()
        {
            return GeoJsonSerializer.Create(SerializerSettings, GeometryFactory.Default);
        }
    }
}

And a controller:

namespace MyApp.Controllers
{
    [Route("[controller]")]
    public class MyController : Controller
    {
        [HttpPost]
        [Consumes("application/geo+json")]
        [Produces("application/geo+json")]
        [ProducesResponseType(typeof(FeatureCollection), 200)]
        public IActionResult Post([FromBody] FeatureCollection featureCollection /* or Feature feature */)
        {
            return Ok(featureCollection); // the featureCollection object is always null here?!
        }
    }
}

Yet, setting a TraceWriter on the JSON serializer, I could confirm that the GeoJSON was successfully parsed. But somewhere in ASP.NET / MVC is the FeatureCollection object not properly passed back to the controller's function. Is there any experts in the room? :-)

I'm using the latest 1.15.0 pre-release version, in combination with ASP.NET Core 2.0 (on Linux).

Thanks for your help!

Copied from original issue: NetTopologySuite/NetTopologySuite#215

tomek14 commented 5 years ago

@airbreather Do you have any progress in this issue ?

jochenjonc commented 5 years ago

Here is how I do it in a ASP.Net Core Web Api project.

 public class Startup
    {
        // ...
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc(options =>
                {
                    // Prevent the following exception: 'This method does not support GeometryCollection arguments'
                    // See: https://github.com/npgsql/Npgsql.EntityFrameworkCore.PostgreSQL/issues/585
                    options.ModelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(Point)));
                    options.ModelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(LineString)));
                    options.ModelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(MultiLineString)));
                })
                .AddJsonOptions(options =>
                {
                    foreach (var converter in GeoJsonSerializer.Create(new GeometryFactory(new PrecisionModel(), 4326)).Converters)
                    {
                        options.SerializerSettings.Converters.Add(converter);
                    }
                })
                .SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
            ...
        }
        // ...
    }
tomek14 commented 5 years ago

I still have a problem with Posting data to controller. Using code provided by @jochenjonc and posting { "type": "Feature", "geometry": { "type": "Point", "coordinates": [ 18.5775, 54.4017 ] }, "properties": { "name": " ExampleName", } } I 've got an error

Operation does not support GeometryCollection arguments : at NetTopologySuite.Geometries.Geometry.CheckNotGeometryCollection(IGeometry g) in C:\TeamCity\BuildAgent\work\d70261de4df18bef\NetTopologySuite\Geometries\Geometry.cs:line 2075\r\n at NetTopologySuite.Geometries.GeometryCollection.get_Boundary() in C:\TeamCity\BuildAgent\work\d70261de4df18bef\NetTopologySuite\Geometries\GeometryCollection.cs:line 233\r\n at Microsoft.Extensions.Internal.PropertyHelper.CallNullSafePropertyGetter[TDeclaringType,TValue](Func`2 getter, Object target)\r\n at Microsoft.AspNetCore.Mvc.Internal.DefaultComplexObjectValidationStrategy.Enumerator.GetModel(Object container, ModelMetadata property)\r\n at Microsoft.AspNetCore.Mvc.Internal.DefaultComplexObjectValidationStrategy.Enumerator.<>cDisplayClass10_0.b__1()\r\n at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationEntry.get_Model()\r\n at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor.VisitChildren(IValidationStrategy strategy)\r\n at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor.VisitComplexType(IValidationStrategy defaultStrategy)\r\n at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor.Visit(ModelMetadata metadata, String key, Object model)\r\n at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor.VisitChildren(IValidationStrategy strategy)\r\n at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor.VisitComplexType(IValidationStrategy defaultStrategy)\r\n at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor.Visit(ModelMetadata metadata, String key, Object model)\r\n at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor.VisitChildren(IValidationStrategy strategy)\r\n at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor.VisitComplexType(IValidationStrategy defaultStrategy)\r\n at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor.Visit(ModelMetadata metadata, String key, Object model)\r\n at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor.Validate(ModelMetadata metadata, String key, Object model, Boolean alwaysValidateAtTopLevel)\r\n at Microsoft.AspNetCore.Mvc.ModelBinding.ObjectModelValidator.Validate(ActionContext actionContext, ValidationStateDictionary validationState, String prefix, Object model, ModelMetadata metadata)\r\n at Microsoft.AspNetCore.Mvc.ModelBinding.ParameterBinder.EnforceBindRequiredAndValidate(ObjectModelValidator baseObjectValidator, ActionContext actionContext, ParameterDescriptor parameter, ModelMetadata metadata, ModelBindingContext modelBindingContext, ModelBindingResult modelBindingResult)\r\n at Microsoft.AspNetCore.Mvc.ModelBinding.ParameterBinder.BindModelAsync(ActionContext actionContext, IModelBinder modelBinder, IValueProvider valueProvider, ParameterDescriptor parameter, ModelMetadata metadata, Object value)\r\n at Microsoft.AspNetCore.Mvc.Internal.ControllerBinderDelegateProvider.<>cDisplayClass0_0.<g__Bind|0>d.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeInnerFilterAsync()\r\n at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextExceptionFilterAsync()

When I remove coordinates form point it gets deserialized to controllers action but with geometry null. Any ideas what am I doing wrong ?

tomek14 commented 5 years ago

Ok, I figured out, just added this line:


options.ModelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(Feature)));
FObermaier commented 5 years ago

I create a HowTo ... wiki page

HarelM commented 5 years ago

Seems like version 3.0 stopped using JSON.Net by default. Have any of you tried to use it and make it work? I'm currently updating to 3.0 and am looking into this SO question: https://stackoverflow.com/questions/55666826/where-did-imvcbuilder-addjsonoptions-go-in-net-core-3-0 Let me know if you manage to make it work before I do...

jonnekleijer commented 4 years ago

@FObermaier

I got a similar error message when consuming a polygon geojson: Operation does not support GeometryCollection arguments.

Maybe add this as well in the wiki?

 options.ModelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(Polygon)));

In my case, we accept all kind of geometries in geojson (point, polygon etc.), so I only added Geometry and no the other options suggested in the wiki:

 options.ModelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(Geometry)));

Do you see any objects why to use solely Geometry?

HossienMehdikhah commented 4 years ago

I create a HowTo ... wiki page

Hi I Use This tutorial. it Works But I Use Odata too,Odata Create RefrenceLoop. I Prevent With options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore; but I Use This Tutorial ReferenceLoop Created. I Dont Want It