-
Notifications
You must be signed in to change notification settings - Fork 217
Update M.E.DependencyModel to use S.T.Json source for netstandard2.0 #5009
Changes from 13 commits
744a6d8
f5d1518
a376775
f4bbc80
b83045b
8fe4cc2
3bf070a
acad54f
394f779
4395708
b4b36bb
b1d01f4
85a3d17
122c0a3
c5b79d2
b86b283
d22662f
2a1d74b
ba48a0b
d1e6cd1
72ba51e
ccb0f16
586274c
06c41cd
ca1c967
c304bb3
58210c7
a3245f5
23e4096
1b7a600
17cd80e
5e77559
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
// Copyright (c) .NET Foundation and contributors. All rights reserved. | ||
// Licensed under the MIT license. See LICENSE file in the project root for full license information. | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Text.Json; | ||
|
||
namespace Microsoft.Extensions.DependencyModel | ||
{ | ||
internal static class JsonTextReaderExtensions | ||
{ | ||
internal static bool TryReadStringProperty(this ref Utf8JsonReader reader, out string name, out string value) | ||
{ | ||
name = null; | ||
value = null; | ||
if (reader.Read() && reader.TokenType == JsonTokenType.PropertyName) | ||
{ | ||
name = reader.GetString(); | ||
|
||
if (reader.Read()) | ||
{ | ||
if (reader.TokenType == JsonTokenType.String) | ||
{ | ||
value = reader.GetString(); | ||
} | ||
else | ||
{ | ||
reader.Skip(); | ||
} | ||
} | ||
|
||
return true; | ||
} | ||
|
||
return false; | ||
} | ||
|
||
internal static void Skip(this ref Utf8JsonReader jsonReader) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is an issue to add this API: https://github.com/dotnet/corefx/issues/33295 |
||
{ | ||
if (jsonReader.TokenType == JsonTokenType.PropertyName) | ||
{ | ||
jsonReader.Read(); | ||
} | ||
|
||
if (jsonReader.TokenType == JsonTokenType.StartObject || jsonReader.TokenType == JsonTokenType.StartArray) | ||
{ | ||
int depth = jsonReader.CurrentDepth; | ||
while (jsonReader.Read() && depth <= jsonReader.CurrentDepth) | ||
{ | ||
} | ||
} | ||
} | ||
|
||
internal static void ReadStartObject(this ref Utf8JsonReader reader) | ||
{ | ||
reader.Read(); | ||
CheckStartObject(ref reader); | ||
} | ||
|
||
internal static void CheckStartObject(ref Utf8JsonReader reader) | ||
{ | ||
if (reader.TokenType != JsonTokenType.StartObject) | ||
{ | ||
throw CreateUnexpectedException(ref reader, "{"); | ||
} | ||
} | ||
|
||
internal static void CheckEndObject(this ref Utf8JsonReader reader) | ||
{ | ||
if (reader.TokenType != JsonTokenType.EndObject) | ||
{ | ||
throw CreateUnexpectedException(ref reader, "}"); | ||
} | ||
} | ||
|
||
internal static string[] ReadStringArray(this ref Utf8JsonReader reader) | ||
{ | ||
reader.Read(); | ||
if (reader.TokenType != JsonTokenType.StartArray) | ||
{ | ||
throw CreateUnexpectedException(ref reader, "["); | ||
} | ||
|
||
var items = new List<string>(); | ||
|
||
while (reader.Read() && reader.TokenType == JsonTokenType.String) | ||
{ | ||
items.Add(reader.GetString()); | ||
} | ||
|
||
if (reader.TokenType != JsonTokenType.EndArray) | ||
{ | ||
throw CreateUnexpectedException(ref reader, "]"); | ||
} | ||
|
||
return items.ToArray(); | ||
} | ||
|
||
internal static string ReadAsString(this ref Utf8JsonReader reader) | ||
{ | ||
reader.Read(); | ||
if (reader.TokenType == JsonTokenType.Null) | ||
{ | ||
return null; | ||
} | ||
if (reader.TokenType != JsonTokenType.String) | ||
{ | ||
throw CreateUnexpectedException(ref reader, "a JSON string token"); | ||
} | ||
return reader.GetString(); | ||
} | ||
|
||
internal static bool ReadAsBoolean(this ref Utf8JsonReader reader) | ||
{ | ||
reader.Read(); | ||
if (reader.TokenType != JsonTokenType.True && reader.TokenType != JsonTokenType.False) | ||
{ | ||
throw CreateUnexpectedException(ref reader, "a JSON true or false literal token"); | ||
} | ||
return reader.GetBoolean(); | ||
} | ||
|
||
internal static bool ReadAsNullableBoolean(this ref Utf8JsonReader reader, bool defaultValue) | ||
{ | ||
reader.Read(); | ||
if (reader.TokenType != JsonTokenType.True && reader.TokenType != JsonTokenType.False) | ||
{ | ||
if (reader.TokenType == JsonTokenType.Null) | ||
{ | ||
return defaultValue; | ||
} | ||
throw CreateUnexpectedException(ref reader, "a JSON true or false literal token"); | ||
} | ||
return reader.GetBoolean(); | ||
} | ||
|
||
internal static Exception CreateUnexpectedException(ref Utf8JsonReader reader, string expected) | ||
{ | ||
return new FormatException($"Unexpected character encountered, excepted '{expected}' " + | ||
$"at line {reader.CurrentState._lineNumber} position {reader.CurrentState._bytePositionInLine}"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IMO you should only be using public APIs from the reader. If position info isn't already public then this is a good argument for providing it. If you need it here then other users of the reader will also likely use it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Agreed. However, I don't really understand the need for this method. Why not rely on the exceptions thrown by the JsonReader itself? But, you are right that we may want to expose these publicly. cc @eerhardt There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks like the code was that way since it was migrated from using I assume it is so we can give "good" exception messages about exactly where we expect a new object/array/etc. What is the exception message you get when you rely on the exceptions thrown by the JsonReader itself? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You get a The message string is generally one of the following: For example, for invalid JSON, you could see a message like: Now, assuming that the JSON itself is valid, and we are trying to validate the schema, calling GetString on something other than json string would throw an invalid operation exception with an invalid cast message: This exception message wouldn't contain the info about where we expect a new object/array. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,29 +6,41 @@ | |
<TargetFrameworks>net451;netstandard1.3;netstandard1.6;netstandard2.0</TargetFrameworks> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What should we do about the other TFMs? |
||
<TargetFrameworks Condition="'$(OS)' != 'Windows_NT'">netstandard1.3;netstandard1.6;netstandard2.0</TargetFrameworks> | ||
</PropertyGroup> | ||
|
||
<Choose> | ||
<!-- | ||
Newtonsoft.Json v11.0.1 is the first verison that targets netstandard2.0. Since we added a target for netstandard2.0 | ||
so users aren't forced to download the 1.x dependencies, it makes sense to use this version of Newtonsoft.Json. | ||
However, we still use the previous (v9.0.1) for other TFMs, so existing users don't need to upgrade their Newtonsoft.Json. | ||
For example, the SDK targets net4x and is loaded in VS, so it can't upgrade to a new Newtonsoft.Json. | ||
--> | ||
<When Condition="'$(TargetFramework)' == 'netstandard2.0'"> | ||
<PropertyGroup> | ||
<NewtonsoftJsonPackageVersion>11.0.1</NewtonsoftJsonPackageVersion> | ||
</PropertyGroup> | ||
</When> | ||
<Otherwise> | ||
<PropertyGroup> | ||
<NewtonsoftJsonPackageVersion>9.0.1</NewtonsoftJsonPackageVersion> | ||
</PropertyGroup> | ||
</Otherwise> | ||
</Choose> | ||
|
||
<!--These properties are necessary to build the source package--> | ||
<PropertyGroup Condition="'$(TargetFramework)' == 'netstandard2.0'"> | ||
<LangVersion>latest</LangVersion> | ||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> | ||
<!-- Suppress warnings for unnecessary CLSCompliance attributes --> | ||
<NoWarn>3021</NoWarn> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\Microsoft.DotNet.PlatformAbstractions\Microsoft.DotNet.PlatformAbstractions.csproj" /> | ||
<PackageReference Include="Newtonsoft.Json" Version="$(NewtonsoftJsonPackageVersion)" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup Condition="'$(TargetFramework)' != 'netstandard2.0'"> | ||
ahsonkhan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
<PackageReference Include="Newtonsoft.Json" Version="9.0.1" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'"> | ||
<PackageReference Include="Microsoft.Bcl.Json.Sources" Version="4.6.0-preview.19072.2"> | ||
<PrivateAssets>All</PrivateAssets> | ||
</PackageReference> | ||
<PackageReference Include="System.Memory" Version="4.5.2" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup Condition="'$(TargetFramework)' != 'netstandard2.0'"> | ||
<Compile Remove="DependencyContextJsonReader.Netstandard2_0.cs" /> | ||
<Compile Remove="DependencyContextWriter.Netstandard2_0.cs" /> | ||
<Compile Remove="JsonTextReaderExtensions.Netstandard2_0.cs" /> | ||
<Compile Remove="StreamBufferWriter.Netstandard2_0.cs" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'"> | ||
ahsonkhan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
<Compile Remove="DependencyContextJsonReader.Newtonsoft.cs" /> | ||
<Compile Remove="DependencyContextWriter.cs" /> | ||
<Compile Remove="JsonTextReaderExtensions.cs" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard1.3' Or '$(TargetFramework)' == 'netstandard1.6' "> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
// Copyright (c) .NET Foundation and contributors. All rights reserved. | ||
// Licensed under the MIT license. See LICENSE file in the project root for full license information. | ||
|
||
using System; | ||
using System.Buffers; | ||
using System.Diagnostics; | ||
using System.IO; | ||
|
||
namespace Microsoft.Extensions.DependencyModel | ||
{ | ||
internal struct StreamBufferWriter : IBufferWriter<byte>, IDisposable | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should consider adding such an adapter between IBufferWriter and Stream, publicly. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes please. We have something similar in SignalR https://github.com/aspnet/AspNetCore/blob/958e9049894137bb08c0d239dca0cda7c850a691/src/SignalR/common/Shared/MemoryBufferWriter.cs#L14 (ignore the fact that it's also a Stream, we just did that for other reasons). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also the PipeWriter implemenation over a Stream is 90% similar https://github.com/aspnet/AspNetCore/blob/958e9049894137bb08c0d239dca0cda7c850a691/src/Http/Http/src/StreamPipeWriter.cs. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is something that @JeremyKuhne might have looked into. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
{ | ||
private Stream _stream; | ||
private byte[] _rentedBuffer; | ||
|
||
private const int MinimumBufferSize = 256; | ||
|
||
public StreamBufferWriter(Stream stream, int bufferSize = MinimumBufferSize) | ||
{ | ||
if (bufferSize <= 0) | ||
throw new ArgumentException(nameof(bufferSize)); | ||
|
||
_stream = stream ?? throw new ArgumentNullException(nameof(stream)); | ||
|
||
_rentedBuffer = ArrayPool<byte>.Shared.Rent(bufferSize); | ||
} | ||
|
||
public void Advance(int count) | ||
{ | ||
_stream.Write(_rentedBuffer, 0, count); | ||
_rentedBuffer.AsSpan(0, count).Clear(); | ||
ahsonkhan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
/// <summary> | ||
/// Returns rented buffer back to the pool | ||
/// </summary> | ||
public void Dispose() | ||
{ | ||
_rentedBuffer.AsSpan().Clear(); | ||
ArrayPool<byte>.Shared.Return(_rentedBuffer); | ||
_rentedBuffer = null; | ||
} | ||
|
||
public Memory<byte> GetMemory(int sizeHint = 0) | ||
{ | ||
if (sizeHint < 0) | ||
throw new ArgumentException(nameof(sizeHint)); | ||
|
||
if (sizeHint == 0) | ||
{ | ||
int newSize = _rentedBuffer.Length == 0 ? MinimumBufferSize : checked(_rentedBuffer.Length * 2); | ||
byte[] temp = _rentedBuffer; | ||
_rentedBuffer = ArrayPool<byte>.Shared.Rent(newSize); | ||
temp.AsSpan().Clear(); | ||
ahsonkhan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
ArrayPool<byte>.Shared.Return(temp); | ||
} | ||
else if (sizeHint > _rentedBuffer.Length) | ||
{ | ||
var temp = _rentedBuffer; | ||
_rentedBuffer = ArrayPool<byte>.Shared.Rent(sizeHint); | ||
temp.AsSpan().Clear(); | ||
ArrayPool<byte>.Shared.Return(temp); | ||
} | ||
Debug.Assert(_rentedBuffer.Length > 0); | ||
return _rentedBuffer; | ||
} | ||
|
||
public Span<byte> GetSpan(int sizeHint = 0) | ||
{ | ||
if (sizeHint < 0) | ||
throw new ArgumentException(nameof(sizeHint)); | ||
|
||
if (sizeHint == 0) | ||
{ | ||
int newSize = _rentedBuffer.Length == 0 ? MinimumBufferSize : checked(_rentedBuffer.Length * 2); | ||
byte[] temp = _rentedBuffer; | ||
_rentedBuffer = ArrayPool<byte>.Shared.Rent(newSize); | ||
temp.AsSpan().Clear(); | ||
ArrayPool<byte>.Shared.Return(temp); | ||
} | ||
else if (sizeHint > _rentedBuffer.Length) | ||
{ | ||
var temp = _rentedBuffer; | ||
_rentedBuffer = ArrayPool<byte>.Shared.Rent(sizeHint); | ||
temp.AsSpan().Clear(); | ||
ArrayPool<byte>.Shared.Return(temp); | ||
} | ||
Debug.Assert(_rentedBuffer.Length > 0); | ||
return _rentedBuffer; | ||
ahsonkhan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It feels like all these methods are things that most consumers would want... and it is obviously a non-trivial amount of code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@eerhardt A lot of the code in these extension methods is just about changing the exception type, and combining the move and get operations. If InvalidOperationException were acceptable then
ReadAsString()
is justThe return value of Next is important when using the reader in async/chunked mode; and an exception model is what it is...