Giter Site home page Giter Site logo

Comments (19)

davidfowl avatar davidfowl commented on May 4, 2024 1

Memory stream is a bad idea that’s why we don’t do that πŸ™‚.

from carter.

jchannon avatar jchannon commented on May 4, 2024 1

from carter.

jchannon avatar jchannon commented on May 4, 2024

I don't really know JSON.Net that well in all honesty and dislike the dependency generally due to binding redirect hell πŸ˜„ however I took that decision took a dependency on it to get going ASAP. I like the idea of a async BindAndValidate though, just not sure how we do that looking at the link you gave, as it would load the body into a JToken, JArray etc then I assume we have to convert from that to the model. I'm confused why they didn't make their JsonSerialzier.Deserialize method async.

I'm also tempted to look at this new library and dropping the Json.Net dependency https://github.com/neuecc/Utf8Json/

Thoughts?

from carter.

fdipuma avatar fdipuma commented on May 4, 2024

I understand your concerns on taking a dependency on Json.NET, but I do not really believe moving to another serialization library will improve much this issue.

From what I understand Json.NET is the de-facto standard when talking about Json serialization on .NET (which means the probability to already have it as a dependency inside the project is high).

While I do not fully agree on the decision of some team (see ASP.NET MVC Core team) to take this dependency as immutable, I really believe that if we want to fully be dependency-less we should somewhat mimic what Nancy already done: an internal basic Json serializer (similar to SimpleJson with an already implemented class), with pluggable alternative serializers.

Something like (stubbed):

public interface IModelBinderSerializer
{
    void Serialize<T>(T value, Stream stream);
    Task SerializeAsync<T>(T value, Stream stream);    
    T Deserialize<T>(Stream stream);
    Task<T> DeserializeAsync<T>(Stream stream);
}

This will certainly remove any direct dependency on serializers. We could then add different packages for Json.NET, Utf8Json, Whatever.

Regarding the Json.NET approach (assuming we want to keep it as a dependency) I was thinking to something simple and straightforward:

public static async Task<T> BindAsync<T>(this HttpRequest request, CancellationToken cancellationToken = default(CancellationToken))
{
    JToken token;
    using (var streamReader = new StreamReader(request.Body))
    using (var jsonTextReader = new JsonTextReader(streamReader))
    {
        token = await JToken.LoadAsync(jsonTextReader, cancellationToken);
    }
    return token.ToObject<T>();
}

I do not really know why they do not implement a friendly JsonConvert.DeserializeAsync method, but I understand that now this is the only approach for async based deserialization.

Looking forward for your thoughts!

from carter.

jchannon avatar jchannon commented on May 4, 2024

Whilst I think the stub is fine to make it like Nancy, for now I think I want to keep Botwin "opinionated" but performant.

I'd like to find out if swapping JsonConvert.Deserialize to JToken.LoadAsync is more performant somehow.

UPDATE Looking at decompiled DeserializeObject it uses the StreamReader & JsonTextReader but its not async

from carter.

jchannon avatar jchannon commented on May 4, 2024

@davidfowl any thoughts?

from carter.

davidfowl avatar davidfowl commented on May 4, 2024

Whilst I think the stub is fine to make it like Nancy, for now I think I want to keep Botwin "opinionated" but performant.

Do you have any performance tests?

If Bind is doing synchronous IO then users of Botwin will have a scalability issue, to mitigate that in MVC currently is this what the JsonInputFormatter does:

https://github.com/aspnet/Mvc/blob/0989e60f735eabfba71b6db6e08cfa7f84c329ac/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonInputFormatter.cs#L149-L150

The goal is to move away from this and use the newer JSON.NET so that we can do true async reads. You can use a similar approach if the JSON serializer doesn't support async reads.

from carter.

jchannon avatar jchannon commented on May 4, 2024

Guess I should run wrk on both approaches and check the results πŸ˜„

from carter.

jchannon avatar jchannon commented on May 4, 2024

I added the below code to load the json async and validate the model async and the wrk results are inconcusive πŸ˜•

        public static async Task<(ValidationResult ValidationResult, T Data)> BindAndValidateAsync<T>(this HttpRequest request)
        {
            var data = await request.BindAsync<T>();
            if (data == null)
            {
                data = Activator.CreateInstance<T>();
            }

            var validatorLocator = request.HttpContext.RequestServices.GetService<IValidatorLocator>();

            var validator = validatorLocator.GetValidator<T>();

            if (validator == null)
            {
                return (new ValidationResult(new[] { new ValidationFailure(typeof(T).Name, "No validator found") }), default(T));
            }

            var result = await validator.ValidateAsync(data);
            return (result, data);
        }
        
        public static async Task<T> BindAsync<T>(this HttpRequest request, CancellationToken cancellationToken = default(CancellationToken))
        {
            JToken token;
            using (var streamReader = new StreamReader(request.Body))
            using (var jsonTextReader = new JsonTextReader(streamReader))
            {
                token = await JToken.LoadAsync(jsonTextReader, cancellationToken);
            }
            return token.ToObject<T>();
        }

from carter.

jchannon avatar jchannon commented on May 4, 2024

@JamesNK does the above code look right to read a request body and deserialize async?

from carter.

jchannon avatar jchannon commented on May 4, 2024

Used the same async code above with https://github.com/neuecc/Utf8Json/ and again results are no different. I wonder if I'm not stressing the fwk hard enough and each approach is handling fine at the levels the test is running at

from carter.

fdipuma avatar fdipuma commented on May 4, 2024

Can you share some of the results and testing profile you used?

from carter.

jchannon avatar jchannon commented on May 4, 2024

Using wrk I did 4 threads 100 connections for 10seconds. Results were roughly 8-9k req/s

from carter.

JamesNK avatar JamesNK commented on May 4, 2024

It would probably be cheaper to load a copy of the request body into a MemoryStream than a JToken. JToken has various internal references as well as internal dictionaries to speed up fetching properties by name, but takes up more memory.

from carter.

jchannon avatar jchannon commented on May 4, 2024

@JamesNK like this? https://github.com/jchannon/Botwin/blob/master/src/ModelBinding/BindExtensions.cs#L40

from carter.

fdipuma avatar fdipuma commented on May 4, 2024

A quick-and-dirty solution may be something like this:

public static T Bind<T>(this HttpRequest request)
{
    return request.Body.BindFromStream<T>();
}

private const int BufferSize = 1024 * 4;

public static async Task<T> BindAsync<T>(this HttpRequest request, CancellationToken cancellationToken = default(CancellationToken))
{
    using(var ms = new MemoryStream())
    {
        await request.Body.CopyToAsync(ms, BufferSize, cancellationToken);
        ms.Position = 0;
        return ms.BindFromStream<T>();
    }    
}

private static T BindFromStream<T>(this Stream stream)
{
    using (var streamReader = new StreamReader(stream))
    using (var jsonTextReader = new JsonTextReader(streamReader))
    {
        return JsonSerializer.Deserialize<T>(jsonTextReader);
    }
}

I believe a more efficient approach should leverage the use of ArrayPool and stuff as @davidfowl has shown (similar to what ASP.NET MVC team has already done).

from carter.

stephenpope avatar stephenpope commented on May 4, 2024

This seemed the most logical place to put this .. I can open up a seperate issue if needed.

I was starting to use Carter (4.2.0) with Netcore 3.0 Preview 6 (3.0.100-preview6-12264).

When trying to call BindAndValidate I get the following error ..

System.InvalidOperationException: Synchronous operations are disallowed. Call ReadAsync or set AllowSynchronousIO to true instead.
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpRequestStream.Read(Byte[] buffer, Int32 offset, Int32 count)
   at System.IO.StreamReader.ReadBuffer(Span`1 userBuffer, Boolean& readToUserBuffer)
   at System.IO.StreamReader.ReadSpan(Span`1 buffer)
   at System.IO.StreamReader.Read(Char[] buffer, Int32 index, Int32 count)
   at Newtonsoft.Json.JsonTextReader.ReadData(Boolean append, Int32 charsRequired)
   at Newtonsoft.Json.JsonTextReader.ParseValue()
   at Newtonsoft.Json.JsonReader.ReadAndMoveToContent()
   at Newtonsoft.Json.JsonReader.ReadForType(JsonContract contract, Boolean hasConverter)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize(JsonReader reader, Type objectType, Boolean checkAdditionalContent)
   at Newtonsoft.Json.JsonSerializer.DeserializeInternal(JsonReader reader, Type objectType)
   at Newtonsoft.Json.JsonSerializer.Deserialize(JsonReader reader, Type objectType)
   at Newtonsoft.Json.JsonSerializer.Deserialize[T](JsonReader reader)
   at Carter.ModelBinding.BindExtensions.Bind[T](HttpRequest request)
   at Carter.ModelBinding.BindExtensions.BindAndValidate[T](HttpRequest request)

It seems to relate to this announcement -> dotnet/aspnetcore#7644 .. that synchronousIO is disallowed by default in NetCore 3.

Adding :

var syncIOFeature = req.HttpContext.Features.Get<IHttpBodyControlFeature>();
                
if (syncIOFeature != null)
{
    syncIOFeature.AllowSynchronousIO = true;
}

.. before calling it solves the issue. But it seems that BindAndValidate method is going to blow up by default from NetCore 3 onwards.

from carter.

danielmoncada avatar danielmoncada commented on May 4, 2024

@jchannon, has there been an update on a BindAsync? Currently migrating to ASP.NET Core 3, and am looking for a solution. Performance wise, not sure where to go, as @JamesNK indicates to use a MemoryStream, but @davidfowl states that it's a bad idea. @fdipuma curious on which route you took here.

Also note that JSON.Net is a must for us at the moment, as System.Text.Json does not support dyanmic/expando types.

from carter.

jchannon avatar jchannon commented on May 4, 2024

from carter.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.