Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.

Update M.E.DependencyModel to use S.T.Json source for netstandard2.0 #5009

Merged
merged 32 commits into from
Jan 29, 2019
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
744a6d8
Update M.E.DependencyModel to use in-box S.T.Json for netstandard2.0
ahsonkhan Jan 19, 2019
f5d1518
Move StreamBufferWriter to a separate file.
ahsonkhan Jan 19, 2019
a376775
Return array back to the pool on success.
ahsonkhan Jan 19, 2019
f4bbc80
Update test json and remove trailing commas to make it valid according
ahsonkhan Jan 23, 2019
b83045b
Fix off-by-one error in skip, add a null string check, and remove other
ahsonkhan Jan 23, 2019
8fe4cc2
Write end of object before flushing.
ahsonkhan Jan 23, 2019
3bf070a
Add missing write end array to close the array block.
ahsonkhan Jan 23, 2019
acad54f
Update to latest source package with some API changes.
ahsonkhan Jan 23, 2019
394f779
Address PR feedback.
ahsonkhan Jan 23, 2019
4395708
Split DepContextJsonReader into common, newtonsoft, and netstandard.
ahsonkhan Jan 23, 2019
b4b36bb
Try to minimize the diff between Newtonsoft and S.T.Json.
ahsonkhan Jan 23, 2019
b1d01f4
Remove the .Common prefix for the source that is shared between the
ahsonkhan Jan 23, 2019
85a3d17
Explicitly add access modifiers and some code formatting fix.
ahsonkhan Jan 23, 2019
122c0a3
Add debug asserts and add comment to where ReadToEnd comes from.
ahsonkhan Jan 24, 2019
c5b79d2
Mark methods as static where possible.
ahsonkhan Jan 24, 2019
b86b283
Update code formatting (use var, inline outs, etc.)
ahsonkhan Jan 24, 2019
d22662f
Use implicit cast to span and use try/finally block instead.
ahsonkhan Jan 24, 2019
2a1d74b
Always check to resize in case stream.Length changes.
ahsonkhan Jan 24, 2019
ba48a0b
Use an array-based buffer writer rather than one that supports stream.
ahsonkhan Jan 24, 2019
d1e6cd1
Add a unified json reader to reduce code duplication.
ahsonkhan Jan 24, 2019
72ba51e
Remove JsonTextReaderExtensions and cleanup some changes to keep the
ahsonkhan Jan 24, 2019
ccb0f16
Skip comments by default.
ahsonkhan Jan 24, 2019
586274c
Allow comments on the reader but throw FormatException to mimic current
ahsonkhan Jan 24, 2019
06c41cd
Adjust the exception message comparion assert.
ahsonkhan Jan 24, 2019
ca1c967
Use the latest LangVersion (7.3) required by the source package.
ahsonkhan Jan 24, 2019
c304bb3
Rename partial files using Utf8JsonReader/JsonTextReader suffixes.
ahsonkhan Jan 24, 2019
58210c7
Address PR feedback - cleanup csproj and remove unused APIs.
ahsonkhan Jan 28, 2019
a3245f5
Use JsonTextWriter (instead of JObject) and extract out common code by
ahsonkhan Jan 28, 2019
23e4096
Rename Write to WriteCore to match the Read
ahsonkhan Jan 28, 2019
1b7a600
Explicitly disallow comments and update test to assert
ahsonkhan Jan 28, 2019
17cd80e
Add source package version to Versions.props.
ahsonkhan Jan 29, 2019
5e77559
Merge branch 'master' of https://github.com/dotnet/core-setup into Us…
ahsonkhan Jan 29, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

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
Expand Up @@ -5,7 +5,7 @@ namespace Microsoft.Extensions.DependencyModel
{
internal class DependencyContextStrings
{
internal const char VersionSeperator = '/';
internal const char VersionSeparator = '/';

internal const string CompileTimeAssembliesKey = "compile";

Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ private JObject WriteRuntimeTargetInfo(DependencyContext context)
new JProperty(DependencyContextStrings.RuntimeTargetNamePropertyName,
context.Target.IsPortable ?
context.Target.Framework :
context.Target.Framework + DependencyContextStrings.VersionSeperator + context.Target.Runtime
context.Target.Framework + DependencyContextStrings.VersionSeparator + context.Target.Runtime
),
new JProperty(DependencyContextStrings.RuntimeTargetSignaturePropertyName,
context.Target.RuntimeSignature
Expand Down Expand Up @@ -108,7 +108,7 @@ private JObject WriteTargets(DependencyContext context)

return new JObject(
new JProperty(context.Target.Framework, WriteTarget(context.CompileLibraries)),
new JProperty(context.Target.Framework + DependencyContextStrings.VersionSeperator + context.Target.Runtime,
new JProperty(context.Target.Framework + DependencyContextStrings.VersionSeparator + context.Target.Runtime,
WriteTarget(context.RuntimeLibraries))
);
}
Expand All @@ -117,7 +117,7 @@ private JObject WriteTarget(IReadOnlyList<Library> libraries)
{
return new JObject(
libraries.Select(library =>
new JProperty(library.Name + DependencyContextStrings.VersionSeperator + library.Version, WriteTargetLibrary(library))));
new JProperty(library.Name + DependencyContextStrings.VersionSeparator + library.Version, WriteTargetLibrary(library))));
}

private JObject WritePortableTarget(IReadOnlyList<RuntimeLibrary> runtimeLibraries, IReadOnlyList<CompilationLibrary> compilationLibraries)
Expand Down Expand Up @@ -148,7 +148,7 @@ private JObject WritePortableTarget(IReadOnlyList<RuntimeLibrary> runtimeLibrari

var library = (Library)compilationLibrary ?? (Library)runtimeLibrary;
targetObject.Add(
new JProperty(library.Name + DependencyContextStrings.VersionSeperator + library.Version,
new JProperty(library.Name + DependencyContextStrings.VersionSeparator + library.Version,
WritePortableTargetLibrary(runtimeLibrary, compilationLibrary)
)
);
Expand Down Expand Up @@ -349,7 +349,7 @@ private JObject WriteLibraries(DependencyContext context)
{
var allLibraries =
context.RuntimeLibraries.Cast<Library>().Concat(context.CompileLibraries)
.GroupBy(library => library.Name + DependencyContextStrings.VersionSeperator + library.Version);
.GroupBy(library => library.Name + DependencyContextStrings.VersionSeparator + library.Version);

return new JObject(allLibraries.Select(libraries => new JProperty(libraries.Key, WriteLibrary(libraries.First()))));
}
Expand Down
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
Copy link
Member

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.

Copy link
Member

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 just

reader.Next();
return reader.GetStringValue();

The return value of Next is important when using the reader in async/chunked mode; and an exception model is what it is...

{
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)
Copy link
Author

Choose a reason for hiding this comment

The 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}");
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Utf8JsonReader doesn't track/create a path.

Copy link
Member

Choose a reason for hiding this comment

The 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.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO you should only be using public APIs from the reader.

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

Copy link
Member

Choose a reason for hiding this comment

The 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 JObject to simply the JsonTextReader.

dotnet/cli@a02d002

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?

Copy link
Author

@ahsonkhan ahsonkhan Jan 23, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You get a JsonReaderException with LineNumber and BytePosition included in the message, when reading an invalid JSON.

The message string is generally one of the following:
https://github.com/dotnet/corefx/blob/2d9dd533e16889ea4d279162df4039982bfbbca2/src/System.Text.Json/src/Resources/Strings.resx#L120-L284
See where we throw them:
https://github.com/dotnet/corefx/blob/2d9dd533e16889ea4d279162df4039982bfbbca2/src/System.Text.Json/src/System/Text/Json/ThrowHelper.cs#L231

For example, for invalid JSON, you could see a message like: Expected end of string, but instead reached end of data.
OR as another example:
Message: System.Text.Json.JsonReaderException : '}' is an invalid start of a property name. Expected a '"'. LineNumber: 7 | BytePositionInLine: 4.

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: Cannot get the value of a token type '{0}' as a {1}.
https://github.com/dotnet/corefx/blob/2d9dd533e16889ea4d279162df4039982bfbbca2/src/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.TryGet.cs#L26

This exception message wouldn't contain the info about where we expect a new object/array.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

https://github.com/dotnet/corefx/issues/34768

}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,41 @@
<TargetFrameworks>net451;netstandard1.3;netstandard1.6;netstandard2.0</TargetFrameworks>
Copy link
Author

Choose a reason for hiding this comment

The 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' ">
Expand Down
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
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should consider adding such an adapter between IBufferWriter and Stream, publicly.

cc @stephentoub, @davidfowl

Copy link
Member

Choose a reason for hiding this comment

The 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).

Copy link
Member

@davidfowl davidfowl Jan 19, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is something that @JeremyKuhne might have looked into.
/cc @danmosemsft

Copy link
Author

Choose a reason for hiding this comment

The 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
}
}
}
Loading