Comments (19)
Memory stream is a bad idea thatβs why we donβt do that π.
from carter.
from carter.
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.
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.
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.
@davidfowl any thoughts?
from carter.
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:
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.
Guess I should run wrk on both approaches and check the results π
from carter.
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.
@JamesNK does the above code look right to read a request body and deserialize async?
from carter.
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.
Can you share some of the results and testing profile you used?
from carter.
Using wrk I did 4 threads 100 connections for 10seconds. Results were roughly 8-9k req/s
from carter.
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.
@JamesNK like this? https://github.com/jchannon/Botwin/blob/master/src/ModelBinding/BindExtensions.cs#L40
from carter.
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.
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.
@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.
from carter.
Related Issues (20)
- Carter module and dependency injection HOT 2
- Module grouping in Swagger HOT 1
- publish problem HOT 4
- New build tool HOT 7
- How-to inject ILogger using Serilog? HOT 1
- Ability to update Carter package references in several repos
- Suggestion: Add functionality to create a "full" solution
- Possible startup performance issue HOT 6
- How to Authorize Endpoint for specific Role HOT 1
- `WithGroupName` and `WithDescription` results in that module not being surface in SwaggerUI HOT 6
- [Question] Is it possible to do property injection on a CarterModulse version 7 and autofac HOT 1
- Conditionaly Load UnLoad Module HOT 1
- How to configure my filters HOT 1
- [QUESTION] Any sample code using .WithOpenApi HOT 1
- Add support for registering an endpoint filter using an endpoint filter factory HOT 1
- [Question] The future of Carter HOT 1
- Endpoints Not Visible in EndpointsApiExplorer HOT 4
- suggest for nested work HOT 1
- Error On Register IN DI HOT 5
- Removed support to apply endpoint conventions globally HOT 2
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
π Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google β€οΈ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from carter.