diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 7024d02f9..0da8dcb66 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,9 +1,8 @@ { // See https://go.microsoft.com/fwlink/?LinkId=733558 // for the documentation about the tasks.json format - "version": "0.1.0", + "version": "2.0.0", "command": "dotnet", - "isShellCommand": true, "args": [], "options": { "env": { @@ -12,11 +11,20 @@ }, "tasks": [ { - "taskName": "build", - "args": [ "src/ServiceStack.Text.sln", "-v", "m" ], - "isBuildCommand": true, - "showOutput": "silent", - "problemMatcher": "$msCompile" + "label": "build", + "type": "shell", + "command": "dotnet", + "args": [ + "build", + "src/ServiceStack.Text.sln", + "-v", + "m" + ], + "problemMatcher": "$msCompile", + "group": { + "_id": "build", + "isDefault": false + } } ] } \ No newline at end of file diff --git a/NuGet.Config b/NuGet.Config new file mode 100644 index 000000000..42daf5f44 --- /dev/null +++ b/NuGet.Config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/NuGet/NuGetPack.cmd b/NuGet/NuGetPack.cmd deleted file mode 100644 index a6284979e..000000000 --- a/NuGet/NuGetPack.cmd +++ /dev/null @@ -1,2 +0,0 @@ -SET NUGET=..\src\.nuget\nuget -%NUGET% pack ServiceStack.Text\servicestack.text.nuspec -symbols diff --git a/NuGet/NuGetPush.cmd b/NuGet/NuGetPush.cmd deleted file mode 100644 index 47a20b1c8..000000000 --- a/NuGet/NuGetPush.cmd +++ /dev/null @@ -1,9 +0,0 @@ -SET NUGET=..\src\.nuget\nuget -%NUGET% push ServiceStack.Text.4.0.15.nupkg -%NUGET% push ServiceStack.Text.4.0.15.symbols.nupkg - -%NUGET% push ServiceStack.Text.Pcl.4.0.15.nupkg -%NUGET% push ServiceStack.Text.Pcl.4.0.15.symbols.nupkg - -%NUGET% push ServiceStack.Text.Signed.4.0.15.nupkg -%NUGET% push ServiceStack.Text.Signed.4.0.15.symbols.nupkg diff --git a/NuGet/ServiceStack.Text/servicestack.text.nuspec b/NuGet/ServiceStack.Text/servicestack.text.nuspec deleted file mode 100644 index 28a879ab6..000000000 --- a/NuGet/ServiceStack.Text/servicestack.text.nuspec +++ /dev/null @@ -1,38 +0,0 @@ - - - - ServiceStack.Text - .NET's fastest JSON Serializer by ServiceStack - 5.0.0 - ServiceStack - ServiceStack - .NET's fastest JSON, JSV and CSV Text Serializers - - .NET's fastest JSON, JSV and CSV Text Serializers. Fast, Light, Resilient. - Contains ServiceStack's high-performance text-processing powers, for more info see: https://servicestack.net/text - - https://github.com/ServiceStack/ServiceStack.Text - https://servicestack.net/terms - true - https://servicestack.net/img/logo-32.png - JSON Text Serializer CSV JSV Dump PrettyPrint Fast - en-US - ServiceStack and contributors - - - - - - - - - - - - - - - - - - diff --git a/README.md b/README.md index 66e384270..50e856543 100644 --- a/README.md +++ b/README.md @@ -1,720 +1,5 @@ -Follow [@ServiceStack](https://twitter.com/servicestack) or join the [Google+ Community](https://plus.google.com/communities/112445368900682590445) -for updates, or [StackOverflow](http://stackoverflow.com/questions/ask) or the [Customer Forums](https://forums.servicestack.net/) for support. +Follow [@ServiceStack](https://twitter.com/servicestack), [view the docs](https://docs.servicestack.net), use [StackOverflow](https://stackoverflow.com/questions/ask?tags=servicestack,servicestack.redis) or [Customer Forums](https://forums.servicestack.net/) for support. -## FREE high-perf Text Serializers and Core Utils powering [servicestack.net](https://servicestack.net) +# Read ServiceStack.Text Docs at [docs.servicestack.net/text](https://docs.servicestack.net/text/) -ServiceStack.Text is an **independent, dependency-free** serialization library containing ServiceStack's core high-performance utils and text processing functionality, including: - - - [JSON](https://github.com/ServiceStack/ServiceStack.Text), - [JSV](http://docs.servicestack.net/jsv-format) and - [CSV](http://docs.servicestack.net/csv-format) Text Serializers - - [AutoMapping Utils](http://docs.servicestack.net/auto-mapping) - - [HTTP Utils](http://docs.servicestack.net/http-utils) - - [Dump Utils](http://docs.servicestack.net/dump-utils) - - [Fast Reflection Utils](http://docs.servicestack.net/reflection-utils) - - Several String Extensions, Collection extensions, Reflection Utils and lots more. - -### [Portable Class Library Support](https://github.com/ServiceStackApps/HelloMobile#portable-class-library-support) - -### Try out [ServiceStack.Text Live](http://gistlyn.com/text) - -A great way to try out ServiceStack.Text is on [gistlyn.com](http://gistlyn.com) which lets you immediately -run and explore all ServiceStack.Text features from the comfort of your browser with zero software install: - -[![](https://raw.githubusercontent.com/ServiceStack/Assets/master/img/livedemos/gistlyn/home-screenshot.png)](http://gistlyn.com/text) - -## Simple API - -Like most of the interfaces in ServiceStack, the API is simple. Methods that you would commonly use include: - -### Convenience Serialization Extension Methods - -```csharp -string ToJson(T) -T FromJson() - -string ToJsv(T) -T FromJsv() - -string ToCsv(T) -T FromCsv() - -string ToXml(T) -T FromXml() -``` - -### Explicit API - -#### JSON - -```csharp -string JsonSerializer.SerializeToString(T value) -void JsonSerializer.SerializeToWriter(T value, TextWriter writer) - -T JsonSerializer.DeserializeFromString(string value) -T JsonSerializer.DeserializeFromReader(TextReader reader) -``` - -#### JSV - -```csharp -string TypeSerializer.SerializeToString(T value) -void TypeSerializer.SerializeToWriter(T value, TextWriter writer) - -T TypeSerializer.DeserializeFromString(string value) -T TypeSerializer.DeserializeFromReader(TextReader reader) -``` - -#### CSV - -```csharp -string CsvSerializer.SerializeToString(T value) -void CsvSerializer.SerializeToWriter(T value, TextWriter writer) - -T CsvSerializer.DeserializeFromString(string value) -T CsvSerializer.DeserializeFromReader(TextReader reader) -``` - -Where *T* can be any .NET POCO type. That's all there is - the API was intentionally left simple :) - -### Dump Utils - -Dump / Diagnostic Extensions: - -```csharp -T Dump() -T Print() -T PrintDump() -string Fmt(args) -``` - -### Dynamic JSON parsing API - -```csharp -JsonObject.Parse() -JsonArrayObjects.Parse() -``` - -### Pretty Print JSON - -You an format JSON into a more readable format with the `IndentJson()` extension method, e.g: - -```csharp -var prettyJson = dto.ToJson().IndentJson(); -``` - -URL Extensions: - -```csharp -string GetStringFromUrl() -string GetJsonFromUrl() -string GetResponseStatus() -string UrlEncode() / UrlDecode() -string HexEscape() / HexUnescape() -string UrlFormat() / AppendPath() / AppendPaths() / WithTrailingSlash() -string AddQueryParam() / SetQueryParam() AddHashParam() / SetHashParam() -string WithoutExtension() / ParentDirectory() / ReadAllText() -``` - -#### Stream Extensions: - -```csharp -Stream WriteTo(Stream) / CopyTo() -Stream ReadLines() -Stream ReadFully() / ReadExactly() -``` - -#### String Utils: - -```csharp -string SplitOnFirst() / SplitOnLast() -string IndexOfAny() -string StripHtml() / ToCamelCase() -string SafeSubstring() -string ToUtf8Bytes() / FromUtf8Bytes() -string LeftPart() / LastLeftPart() / RightPart() / LastRightPart() -``` - -more String, Reflection, List, Dictionary, DateTime extensions... - -### Supports Dynamic JSON - -Although usually used to (de)serialize C#/.NET POCO types, it also includes a flexible API allowing you to deserialize any -JSON payload without it's concrete type, see these real-world examples: - - - [Parsing GitHub's v3 API with typed DTOs](https://github.com/ServiceStack/ServiceStack.Text/blob/master/tests/ServiceStack.Text.Tests/UseCases/GithubV3ApiTests.cs) - - [Parsing GitHub's JSON response](https://github.com/ServiceStack/ServiceStack.Text/blob/master/tests/ServiceStack.Text.Tests/UseCases/GitHubRestTests.cs) - - [Parsing Google Maps JSON Response](https://github.com/ServiceStack/ServiceStack.Text/blob/master/tests/ServiceStack.Text.Tests/UseCases/GMapDirectionsTests.cs) - - [Parsing Centroid](https://github.com/ServiceStack/ServiceStack.Text/blob/master/tests/ServiceStack.Text.Tests/UseCases/CentroidTests.cs) - -Also a thin **.NET 4.0 Dynamic JSON** wrapper around ServiceStack's JSON library is included in the -[ServiceStack.Razor](https://github.com/ServiceStack/ServiceStack.Text/blob/master/src/ServiceStack.Text/Pcl.Dynamic.cs) -project. It provides a dynamic, but more succinct API than the above options. - -## Install ServiceStack.Text - - PM> Install-Package ServiceStack.Text - -> From v4.0.62+ [ServiceStack.Text is now free!](https://github.com/ServiceStack/ServiceStack/blob/master/docs/2016/v4.0.62.md#servicestacktext-is-now-free) - -Support for PCL platfroms requires PCL adapters in: - - PM> Install-Package ServiceStack.Client - -### [Docs and Downloads for older v3 BSD releases](https://github.com/ServiceStackV3/ServiceStackV3) - -## Copying - -Since September 2013, ServiceStack source code is available under GNU Affero General Public License/FOSS License Exception, see license.txt in the source. Alternative commercial licensing is also available, contact team@servicestack.net for details. - -## Contributing - -Commits can be made to either the **master** (v4) or **v3** release branches. -Contributors need to approve the [Contributor License Agreement](https://docs.google.com/forms/d/16Op0fmKaqYtxGL4sg7w_g-cXXyCoWjzppgkuqzOeKyk/viewform) before any code will be reviewed, see the [Contributing wiki](https://github.com/ServiceStack/ServiceStack/wiki/Contributing) for more details. - -## ServiceStack.JsonSerializer - -For reasons outlined [in this blog post](https://github.com/ServiceStackV3/mythz_blog/blob/master/pages/344.md) I decided to re-use *TypeSerializer's* text processing-core to create ServiceStack.JsonSerializer - the fastest JSON Serializer for .NET. -Based on the [Northwind Benchmarks](http://mono.servicestack.net/benchmarks/) it's *3.6x* faster than .NET's BCL JsonDataContractSerializer and *3x* faster then the previous fastest JSON serializer benchmarked - [JSON.NET](http://json.codeplex.com/). - -A comprehensive set of other .NET benchmarks are maintained at [servicestack.net/benchmarks](http://mono.servicestack.net/benchmarks/) and [in the wiki](https://github.com/ServiceStack/ServiceStack/wiki/Real-world-performance). - -## ServiceStack.CsvSerializer -As CSV is an important format in many data access and migration scenarios, it became [the latest format included in ServiceStack](https://github.com/ServiceStack/ServiceStack/wiki/CSV-Format) which allows all your existing web services to take advantage of the new format without config or code-changes. As its built using the same tech that makes the JSON and JSV serializers so fast, we expect it to be the fastest POCO CSV Serializer for .NET. - -## ServiceStack.TypeSerializer and the JSV-format -Included in this project is `TypeSerializer` - A fast and compact text-based serializer for .NET. It's a light-weight compact Text Serializer which can be used to serialize .NET data types inc custom POCO's and DataContract's. More info on its JSV Format can be found on the [introductory post](https://github.com/ServiceStackV3/mythz_blog/blob/master/pages/176.md). - -## T.Dump() Extension method -Another useful library to have in your .NET toolbox is the [T.Dump() Extension Method](https://github.com/ServiceStackV3/mythz_blog/blob/master/pages/202.md). Under the hood it uses a *Pretty Print* Output of the JSV Format to recursively dump the contents of any .NET object. Example usage and output: - -```csharp -var model = new TestModel(); -model.PrintDump(); - -//Example Output -{ - Int: 1, - String: One, - DateTime: 2010-04-11, - Guid: c050437f6fcd46be9b2d0806a0860b3e, - EmptyIntList: [], - IntList: - [ - 1, - 2, - 3 - ], - StringList: - [ - one, - two, - three - ], - StringIntMap: - { - a: 1, - b: 2, - c: 3 - } -} -``` - -# ServiceStack's JsonSerializer - -ServiceStack's JsonSerializer is optimized for serializing C# POCO types in and out of JSON as fast, compact and cleanly as possible. In most cases C# objects serializes as you would expect them to without added json extensions or serializer-specific artefacts. - -JsonSerializer provides a simple API that allows you to serialize any .NET generic or runtime type into a string, TextWriter/TextReader or Stream. - -### Serialization API - -```csharp -string SerializeToString(T) -void SerializeToWriter(T, TextWriter) -void SerializeToStream(T, Stream) -string SerializeToString(object, Type) -void SerializeToWriter(object, Type, TextWriter) -void SerializeToStream(object, Type, Stream) -``` - -### Deserialization API - -```csharp -T DeserializeFromString(string) -T DeserializeFromReader(TextReader) -object DeserializeFromString(string, Type) -object DeserializeFromReader(reader, Type) -object DeserializeFromStream(Type, Stream) -T DeserializeFromStream(Stream) -``` - -### Extension methods - -```csharp -string ToJson(this T) -T FromJson(this string) -``` - -Convenient **ToJson/FromJson** extension methods are also included reducing the amount of code required, e.g: - -```csharp -new []{ 1, 2, 3 }.ToJson() //= [1,2,3] -"[1,2,3]".FromJson() //= int []{ 1, 2, 3 } -``` - -## JSON Format - -JSON is a lightweight text serialization format with a spec that's so simple that it fits on one page: [http://www.json.org](json.org). - -The only valid values in JSON are: - - * string - * number - * object - * array - * true - * false - * null - -Where most allowed values are scalar and the only complex types available are objects and arrays. Although limited, the above set of types make a good fit and can express most programming data structures. - -### number, true, false types - -All C# boolean and numeric data types are stored as-is without quotes. - -### null type - -For the most compact output null values are omitted from the serialized by default. If you want to include null values set the global configuration: - -```csharp -JsConfig.IncludeNullValues = true; -``` - -### string type - -All other scalar values are stored as strings that are surrounded with double quotes. - -### C# Structs and Value Types - -Because a C# struct is a value type whose public properties are normally just convenience properties around a single scalar value, they are ignored instead the **TStruct.ToString()** method is used to serialize and either the **static TStruct.ParseJson()**/**static TStruct.ParseJsv()** methods or **new TStruct(string)** constructor will be used to deserialize the value type if it exists. - -### array type - -Any List, Queue, Stack, Array, Collection, Enumerables including custom enumerable types are stored in exactly the same way as a JavaScript array literal, i.e: - - [1,2,3,4,5] - -All elements in an array must be of the same type. If a custom type is both an IEnumerable and has properties it will be treated as an array and the extra properties will be ignored. - -### object type - -The JSON object type is the most flexible and is how most complex .NET types are serialized. The JSON object type is a key-value pair JavaScript object literal where the key is always a double-quoted string. - -Any IDictionary is serialized into a standard JSON object, i.e: - - {"A":1,"B":2,"C":3,"D":4} - -Which happens to be the same as C# POCO types (inc. Interfaces) with the values: - -`new MyClass { A=1, B=2, C=3, D=4 }` - - {"A":1,"B":2,"C":3,"D":4} - -Only public properties on reference types are serialized with the C# Property Name used for object key and the Property Value as the value. At the moment it is not possible to customize the Property Name. - -JsonSerializer also supports serialization of anonymous types in much the same way: - -`new { A=1, B=2, C=3, D=4 }` - - {"A":1,"B":2,"C":3,"D":4} - -## Global Default JSON Configuration - -The JSON/JSV and CSV serialization can be customized globally by configuring the `JsConfig` or type-specific `JsConfig` static classes with your preferred defaults, e.g: - -```csharp -JsConfig.EmitLowercaseUnderscoreNames = true; -JsConfig.ExcludeDefaultValues = true; -``` - -The following is a list of `bool` options you can use to configure many popular preferences: - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameAlias
EmitCamelCaseNameseccn
EmitLowercaseUnderscoreNameselun
IncludeNullValuesinv
IncludeNullValuesInDictionariesinvid
IncludeDefaultEnumside
IncludePublicFieldsipf
IncludeTypeInfoiti
ExcludeTypeInfoeti
ExcludeDefaultValuesedv
ConvertObjectTypesIntoStringDictionarycotisd
TreatEnumAsIntegerteai
TryToParsePrimitiveTypeValuesttpptv
TryToParseNumericTypettpnt
ThrowOnDeserializationErrortode
EscapeUnicodeeu
EscapeHtmlCharsehc
PreferInterfacespi
SkipDateTimeConversionsdtc
AlwaysUseUtcauu
AssumeUtcau
AppendUtcOffsetauo
EscapeHtmlCharsehc
EscapeUnicodeeu
- -### DateHandler (dh) - - - - - - - - - - -
TimestampOffsetto
DCJSCompatibledcjsc
ISO8601iso8601
ISO8601DateOnlyiso8601do
ISO8601DateTimeiso8601dt
RFC1123rfc1123
UnixTimeut
UnixTimeMsutm
- -### TimeSpanHandler (tsh) - - - - -
DurationFormatdf
StandardFormatsf
- -### PropertyConvention (pc) - - - - -
Stricts
Lenientl
- -### Custom Config Scopes - -If you need to override the Global JSON Configuration defaults for adhoc JSON serialization you can use a Custom Config Scope, e.g: - -```csharp -using (JsConfig.With(emitCamelCaseNames:true, excludeDefaultValues:true)) -{ - var json = dto.ToJson(); -} -``` - -#### Create Custom Scopes using String config - -You can also create a custion config scope from a string manually using `JsConfig.CreateScope()` where you can use the full config name or their aliases, e.g: - -```csharp -using (JsConfig.CreateScope("EmitLowercaseUnderscoreNames,EDV,dh:ut")) -{ - var json = dto.ToJson(); -} -``` - -This feature is used to provide a number of different [JSON customizations in ServiceStack Services](http://docs.servicestack.net/customize-json-responses). - -### Type Configuration - -If you can't change the definition of a Type (e.g. because its in the BCL), you can specify a custom serialization / -deserialization routine to use instead. E.g. here's how you can add support for `System.Drawing.Color` and customize how `Guid` and `TimeSpan` Types are serialized: - -```csharp -JsConfig.SerializeFn = c => c.ToString().Replace("Color ","").Replace("[","").Replace("]",""); -JsConfig.DeSerializeFn = System.Drawing.Color.FromName; - -JsConfig.SerializeFn = guid => guid.ToString("D"); -JsConfig.SerializeFn = time => - (time.Ticks < 0 ? "-" : "") + time.ToString("hh':'mm':'ss'.'fffffff"); -``` - -## Custom Serialization - -Although JsonSerializer is optimized for serializing .NET POCO types, it still provides some options to change the convention-based serialization routine. - -### Using Structs to Customize JSON - -This makes it possible to customize the serialization routine and provide an even more compact wire format. - -E.g. Instead of using a JSON object to represent a point - - { Width=20, Height=10 } - -You could use a struct and reduce it to just: - - "20x10" - -By overriding **ToString()** and providing a static **Size ParseJson()** method: - -```csharp -public struct Size -{ - public double Width { get; set; } - public double Height { get; set; } - - public override string ToString() - { - return Width + "x" + Height; - } - - public static Size ParseJson(string json) - { - var size = json.Split('x'); - return new Size { - Width = double.Parse(size[0]), - Height = double.Parse(size[1]) - }; - } -} -``` - -Which would change it to the more compact JSON output: - -```csharp - new Size { Width = 20, Height = 10 }.ToJson() // = "20x10" -``` - -That allows you to deserialize it back in the same way: - -```csharp - var size = "20x10".FromJson(); -``` - -### Using Custom IEnumerable class to serialize a JSON array - -In addition to using a Struct you can optionally use a custom C# IEnumerable type to provide a strong-typed wrapper around a JSON array: - -```csharp -public class Point : IEnumerable -{ - double[] points = new double[2]; - - public double X - { - get { return points[0]; } - set { points[0] = value; } - } - - public double Y - { - get { return points[1]; } - set { points[1] = value; } - } - - public IEnumerator GetEnumerator() - { - foreach (var point in points) - yield return point; - } -} -``` - -Which serializes the Point into a compact JSON array: - -```csharp - new Point { X = 1, Y = 2 }.ToJson() // = [1,2] -``` - -## Custom Deserialization - -Because the same wire format shared between Dictionaries, POCOs and anonymous types, in most cases what you serialize with one type can be deserialized with another, i.e. an Anonymous type can be deserialized back into a Dictionary which can be deserialized into a strong-typed POCO and vice-versa. - -Although the JSON Serializer is best optimized for serializing and deserializing .NET types, it's flexible enough to consume 3rd party JSON apis although this generally requires custom de-serialization to convert it into an idiomatic .NET type. - -[GitHubRestTests.cs](https://github.com/ServiceStack/ServiceStack.Text/blob/master/tests/ServiceStack.Text.Tests/UseCases/GitHubRestTests.cs) - - 1. Using [JsonObject](https://github.com/ServiceStack/ServiceStack.Text/blob/master/src/ServiceStack.Text/JsonObject.cs) - 2. Using Generic .NET Collection classes - 3. Using Customized DTO's in the shape of the 3rd party JSON response - -[CentroidTests](https://github.com/ServiceStack/ServiceStack.Text/blob/master/tests/ServiceStack.Text.Tests/UseCases/CentroidTests.cs) is another example that uses the JsonObject to parse a complex custom JSON response. - -## Late-bound Object and Interface Runtime Types - -In order to be able to deserialize late-bound objects like `object`, `interface` properties or `abstract` classes ServiceStack needs to emit type information -in the JSON payload. By default it uses `__type` property name, but can be customized with: - -```csharp -JsConfig.TypeAttr = "$type"; -``` - -You can also configure what Type Information is emitted with: - -```csharp -JsConfig.TypeWriter = type => type.Name; -``` - -Which will just emit the name of the Type (e.g `Dog`) instead of the full Type Name. - -By default ServiceStack will scan all loaded Assemblies to find the Type, but you can tell it to use your own Type Resolver implementation by overriding `TypeFinder`, e.g: - -```csharp -JsConfig.TypeFinder = typeInfo => => -{ - var regex = new Regex(@"^(?[^:]+):#(?.*)$"); - var match = regex.Match(value); - var typeName = string.Format("{0}.{1}", match.Groups["namespace"].Value, match.Groups["type"].Value.Replace(".", "+")); - return MyFindType(typeName); -}; -``` - -### Runtime Type Whitelist - -ServiceStack only allows you to serialize "known safe Types" in late-bound properties which uses a whitelist that's pre-populated with a safe-list of -popular Data Types, DTOs and Request DTOs with the default configuration below: - -```csharp -// Allow deserializing types with [DataContract] or [RuntimeSerializable] attributes -JsConfig.AllowRuntimeTypeWithAttributesNamed = new HashSet -{ - nameof(DataContractAttribute), - nameof(RuntimeSerializableAttribute), // new in ServiceStack.Text -}; - -// Allow deserializing types implementing any of the interfaces below -JsConfig.AllowRuntimeTypeWithInterfacesNamed = new HashSet -{ - "IConvertible", - "ISerializable", - "IRuntimeSerializable", // new in ServiceStack.Text - "IMeta", - "IReturn`1", - "IReturnVoid", -}; - -// Allow object property in ServiceStack.Messaging MQ classes -JsConfig.AllowRuntimeTypeInTypesWithNamespaces = new HashSet -{ - "ServiceStack.Messaging", -}; -``` - -The above rules can be extended to allow your own conventions. If you just need to allow a specific Type you can instead just implement: - -```csharp -JsConfig.AllowRuntimeType = type => type == typeof(MyType); -``` - -If you’re in a trusted intranet environment this can also be used to disable the whitelist completely by allowing all Types to be deserialized into object properties with: - -```csharp -JsConfig.AllowRuntimeType = _ => true; -``` - -## TypeSerializer Details (JSV Format) - -Out of the box .NET provides a fairly quick but verbose Xml DataContractSerializer or a slightly more compact but slower JsonDataContractSerializer. -Both of these options are fragile and likely to break with any significant schema changes. -TypeSerializer addresses these shortcomings by being both smaller and significantly faster than the most popular options. -It's also more resilient, e.g. a strongly-typed POCO object can be deserialized back into a loosely-typed string Dictionary and vice-versa. - -With that in mind, TypeSerializer's main features are: - - - Fastest and most compact text-serializer for .NET - - Human readable and writeable, self-describing text format - - Non-invasive and configuration-free - - Resilient to schema changes - - Serializes / De-serializes any .NET data type (by convention) - + Supports custom, compact serialization of structs by overriding `ToString()` and `static T Parse(string)` methods - + Can serialize inherited, interface or 'late-bound objects' data types - + Respects opt-in DataMember custom serialization for DataContract dto types. - -These characteristics make it ideal for use anywhere you need to store or transport .NET data-types, e.g. for text blobs in a ORM, data in and out of a key-value store or as the text-protocol in .NET to .NET web services. - -As such, it's utilized within ServiceStack's other components: - - OrmLite - to store complex types on table models as text blobs in a database field and - - [ServiceStack.Redis](https://github.com/ServiceStack/ServiceStack.Redis) - to store rich POCO data types into the very fast [redis](http://redis.io) instances. - -You may also be interested in the very useful [T.Dump() extension method](https://github.com/ServiceStackV3/mythz_blog/blob/master/pages/202.md) for recursively viewing the contents of any C# POCO Type. - ---- - -# Performance -Type Serializer is actually the fastest and most compact *text serializer* available for .NET. -Out of all the serializers benchmarked, it is the only one to remain competitive with [protobuf-net's](http://code.google.com/p/protobuf-net/) very fast implementation of [Protocol Buffers](http://code.google.com/apis/protocolbuffers/) - google's high-speed binary protocol. - -Below is a series of benchmarks serialize the different tables in the [Northwind database](https://github.com/ServiceStackV3/ServiceStack.Benchmarks/blob/master/tests/ServiceStack.Northwind.Tests/Support/NorthwindData.cs) (3202 records) with the most popular serializers available for .NET: - -### Combined results for serializing / deserialzing a single row of each table in the Northwind database 1,000,000 times -_[view the detailed benchmarks](http://mono.servicestack.net/benchmarks/)_ - - - - - - - - - - - - - - - - - -
SerializerSizePeformance
Microsoft DataContractSerializer4.68x6.72x
Microsoft JsonDataContractSerializer2.24x10.18x
Microsoft BinaryFormatter5.62x9.06x
NewtonSoft.Json2.30x8.15x
ProtoBuf.net1x1x
ServiceStack TypeSerializer1.78x1.92x
- -_number of times larger in size and slower in performance than the best - lower is better_ - -Microsoft's JavaScriptSerializer was also benchmarked but excluded as it was up to 280x times slower - basically don't use it, ever. - - -# JSV Text Format (JSON + CSV) - -Type Serializer uses a hybrid CSV-style escaping + JavaScript-like text-based format that is optimized for both size and speed. I'm naming this JSV-format (i.e. JSON + CSV) - -In many ways it is similar to JavaScript, e.g. any List, Array, Collection of ints, longs, etc are stored in exactly the same way, i.e: - - [1,2,3,4,5] - -Any IDictionary is serialized like JavaScript, i.e: - - {A:1,B:2,C:3,D:4} - -Which also happens to be the same as C# POCO class with the values - -`new MyClass { A=1, B=2, C=3, D=4 }` - - {A:1,B:2,C:3,D:4} - -JSV is *white-space significant*, which means normal string values can be serialized without quotes, e.g: - -`new MyClass { Foo="Bar", Greet="Hello World!"}` is serialized as: - - {Foo:Bar,Greet:Hello World!} - - -### CSV escaping - -Any string with any of the following characters: `[]{},"` -is escaped using CSV-style escaping where the value is wrapped in double quotes, e.g: - -`new MyClass { Name = "Me, Junior" }` is serialized as: - - {Name:"Me, Junior"} - -A value with a double-quote is escaped with another double quote e.g: - -`new MyClass { Size = "2\" x 1\"" }` is serialized as: - - {Size:"2"" x 1"""} - - -## Rich support for resilience and schema versioning -To better illustrate the resilience of `TypeSerializer` and the JSV Format check out a real world example of it when it's used to [Painlessly migrate between old and new types in Redis](https://github.com/ServiceStack/ServiceStack.Redis/wiki/MigrationsUsingSchemalessNoSql). - -Support for dynamic payloads and late-bound objects is explained in the post [Versatility of JSV Late-bound objects](http://www.servicestack.net/mythz_blog/?p=314). - - -# Community Resources - - - [ServiceStack.Text has nice extension method called Dump and has a few friends - web archive](https://web.archive.org/web/20150318193203/http://blogs.lessthandot.com/index.php/desktopdev/mstech/servicestack-text-has-a-nice/) by [@chrissie1](https://twitter.com/chrissie1) - - [JSON.NET vs ServiceStack - web archive](https://web.archive.org/web/20140721081359/http://daniel.wertheim.se/2011/02/07/json-net-vs-servicestack/) - - [GithubSharp with ServiceStack.Text](https://github.com/xamarin/GithubSharp/tree/servicestack) by [@XTZGZoReX](https://twitter.com/XTZGZoReX) +### This repository [has moved](https://docs.servicestack.net/mono-repo) to [github.com/ServiceStack/ServiceStack/ServiceStack.Text](https://github.com/ServiceStack/ServiceStack/tree/main/ServiceStack.Text) diff --git a/benchmarks/ServiceStack.Text.Benchmarks/JsonSerializationBenchmarks.cs b/benchmarks/ServiceStack.Text.Benchmarks/JsonSerializationBenchmarks.cs deleted file mode 100644 index 1bd650e82..000000000 --- a/benchmarks/ServiceStack.Text.Benchmarks/JsonSerializationBenchmarks.cs +++ /dev/null @@ -1,182 +0,0 @@ -using System; -using System.IO; -using System.Text; -using BenchmarkDotNet.Attributes; -using ServiceStack.Text; -using ServiceStack.Text.Tests.DynamicModels; -using ServiceStack.Text.Json; - -namespace ServiceStack.Text.Benchmarks -{ - public class ModelWithCommonTypes - { - public char CharValue { get; set; } - - public byte ByteValue { get; set; } - - public sbyte SByteValue { get; set; } - - public short ShortValue { get; set; } - - public ushort UShortValue { get; set; } - - public int IntValue { get; set; } - - public uint UIntValue { get; set; } - - public long LongValue { get; set; } - - public ulong ULongValue { get; set; } - - public float FloatValue { get; set; } - - public double DoubleValue { get; set; } - - public decimal DecimalValue { get; set; } - - public DateTime DateTimeValue { get; set; } - - public TimeSpan TimeSpanValue { get; set; } - - public Guid GuidValue { get; set; } - - public static ModelWithCommonTypes Create(byte i) - { - return new ModelWithCommonTypes - { - ByteValue = i, - CharValue = (char)i, - DateTimeValue = new DateTime(2000, 1, 1 + i), - DecimalValue = i, - DoubleValue = i, - FloatValue = i, - IntValue = i, - LongValue = i, - SByteValue = (sbyte)i, - ShortValue = i, - TimeSpanValue = new TimeSpan(i), - UIntValue = i, - ULongValue = i, - UShortValue = i, - GuidValue = Guid.NewGuid(), - }; - } - } - - public class JsonSerializationBenchmarks - { - static ModelWithAllTypes allTypesModel = ModelWithAllTypes.Create(3); - static ModelWithCommonTypes commonTypesModel = ModelWithCommonTypes.Create(3); - static MemoryStream stream = new MemoryStream(32768); - const string serializedString = "this is the test string"; - readonly string serializedString256 = new string('t', 256); - readonly string serializedString512 = new string('t', 512); - readonly string serializedString4096 = new string('t', 4096); - - [Benchmark] - public void SerializeJsonAllTypes() - { - string result = JsonSerializer.SerializeToString(allTypesModel); - } - - [Benchmark] - public void SerializeJsonCommonTypes() - { - string result = JsonSerializer.SerializeToString(commonTypesModel); - } - - [Benchmark] - public void SerializeJsonString() - { - string result = JsonSerializer.SerializeToString(serializedString); - } - - [Benchmark] - public void SerializeJsonStringToStream() - { - stream.Position = 0; - JsonSerializer.SerializeToStream(serializedString, stream); - } - - [Benchmark] - public void SerializeJsonString256ToStream() - { - stream.Position = 0; - JsonSerializer.SerializeToStream(serializedString256, stream); - } - - [Benchmark] - public void SerializeJsonString512ToStream() - { - stream.Position = 0; - JsonSerializer.SerializeToStream(serializedString512, stream); - } - - [Benchmark] - public void SerializeJsonString4096ToStream() - { - stream.Position = 0; - JsonSerializer.SerializeToStream(serializedString4096, stream); - } - - [Benchmark] - public void SerializeJsonStringToStreamDirectly() - { - stream.Position = 0; - string tmp = JsonSerializer.SerializeToString(serializedString); - byte[] arr = Encoding.UTF8.GetBytes(tmp); - stream.Write(arr, 0, arr.Length); - } - - - [Benchmark] - public void SerializeJsonAllTypesToStream() - { - stream.Position = 0; - JsonSerializer.SerializeToStream(allTypesModel, stream); - } - - [Benchmark] - public void SerializeJsonCommonTypesToStream() - { - stream.Position = 0; - JsonSerializer.SerializeToStream(commonTypesModel, stream); - } - - [Benchmark] - public void SerializeJsonStringToStreamUsingDirectStreamWriter() - { - stream.Position = 0; - var writer = new DirectStreamWriter(stream, JsonSerializer.UTF8Encoding); - JsonWriter.WriteRootObject(writer, serializedString); - writer.Flush(); - } - - [Benchmark] - public void SerializeJsonString256ToStreamUsingDirectStreamWriter() - { - stream.Position = 0; - var writer = new DirectStreamWriter(stream, JsonSerializer.UTF8Encoding); - JsonWriter.WriteRootObject(writer, serializedString256); - writer.Flush(); - } - - [Benchmark] - public void SerializeJsonString512ToStreamUsingDirectStreamWriter() - { - stream.Position = 0; - var writer = new DirectStreamWriter(stream, JsonSerializer.UTF8Encoding); - JsonWriter.WriteRootObject(writer, serializedString512); - writer.Flush(); - } - - [Benchmark] - public void SerializeJsonString4096ToStreamUsingDirectStreamWriter() - { - stream.Position = 0; - var writer = new DirectStreamWriter(stream, JsonSerializer.UTF8Encoding); - JsonWriter.WriteRootObject(writer, serializedString4096); - writer.Flush(); - } - } -} diff --git a/benchmarks/ServiceStack.Text.Benchmarks/ParseBuiltinsBenchmarks.cs b/benchmarks/ServiceStack.Text.Benchmarks/ParseBuiltinsBenchmarks.cs deleted file mode 100644 index 1e736969e..000000000 --- a/benchmarks/ServiceStack.Text.Benchmarks/ParseBuiltinsBenchmarks.cs +++ /dev/null @@ -1,85 +0,0 @@ -using System; -using System.Globalization; -using BenchmarkDotNet.Attributes; -using ServiceStack.Text; -#if NETCOREAPP1_1 -using Microsoft.Extensions.Primitives; -#endif -using ServiceStack.Text.Support; - -namespace ServiceStack.Text.Benchmarks -{ - public class ParseBuiltinBenchmarks - { - const string int32_1 = "1234"; - const string int32_2 = "-1234"; - const string decimal_1 = "1234.5678"; - const string decimal_2 = "-1234.5678"; - const string decimal_3 = "1234.5678901234567890"; - const string decimal_4 = "-1234.5678901234567890"; - const string guid_1 = "{b6170a18-3dd7-4a9b-b5d6-21033b5ad162}"; - - readonly StringSegment ss_int32_1 = new StringSegment(int32_1); - readonly StringSegment ss_int32_2 = new StringSegment(int32_2); - readonly StringSegment ss_decimal_1 = new StringSegment(decimal_1); - readonly StringSegment ss_decimal_2 = new StringSegment(decimal_2); - readonly StringSegment ss_decimal_3 = new StringSegment(decimal_3); - readonly StringSegment ss_decimal_4 = new StringSegment(decimal_4); - readonly StringSegment ss_guid_1 = new StringSegment(guid_1); - - [Benchmark] - public void Int32Parse() - { - var res1 = int.Parse(int32_1, CultureInfo.InvariantCulture); - var res2 = int.Parse(int32_2, CultureInfo.InvariantCulture); - } - - [Benchmark] - public void StringSegment_Int32Parse() - { - var res1 = ss_int32_1.ParseInt32(); - var res2 = ss_int32_2.ParseInt32(); - } - - [Benchmark] - public void DecimalParse() - { - var res1 = decimal.Parse(decimal_1, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture); - var res2 = decimal.Parse(decimal_2, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture); - } - - [Benchmark] - public void BigDecimalParse() - { - var res1 = decimal.Parse(decimal_3, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture); - var res2 = decimal.Parse(decimal_4, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture); - } - - - [Benchmark] - public void StringSegment_DecimalParse() - { - var res1 = ss_decimal_1.ParseDecimal(true); - var res2 = ss_decimal_2.ParseDecimal(true); - } - - [Benchmark] - public void StringSegment_BigDecimalParse() - { - var res1 = ss_decimal_3.ParseDecimal(true); - var res2 = ss_decimal_4.ParseDecimal(true); - } - - [Benchmark] - public void GuidParse() - { - var res1 = Guid.Parse(guid_1); - } - - [Benchmark] - public void StringSegment_GuidParse() - { - var res1 = ss_guid_1.ParseGuid(); - } - } -} \ No newline at end of file diff --git a/benchmarks/ServiceStack.Text.Benchmarks/Program.cs b/benchmarks/ServiceStack.Text.Benchmarks/Program.cs deleted file mode 100644 index 2ca6bcabe..000000000 --- a/benchmarks/ServiceStack.Text.Benchmarks/Program.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using BenchmarkDotNet.Configs; -using BenchmarkDotNet.Jobs; -using BenchmarkDotNet.Running; -using BenchmarkDotNet.Validators; - -namespace ServiceStack.Text.Benchmarks -{ - public class Program - { - public static void Main(string[] args) - { - //BenchmarkRunner.Run( - BenchmarkRunner.Run( - ManualConfig - .Create(DefaultConfig.Instance) - //.With(Job.RyuJitX64) - .With(Job.Core) - .With(new BenchmarkDotNet.Diagnosers.CompositeDiagnoser()) - .With(ExecutionValidator.FailOnError) - ); - } - } -} diff --git a/benchmarks/ServiceStack.Text.Benchmarks/ServiceStack.Text.Benchmarks.csproj b/benchmarks/ServiceStack.Text.Benchmarks/ServiceStack.Text.Benchmarks.csproj deleted file mode 100644 index 56c274987..000000000 --- a/benchmarks/ServiceStack.Text.Benchmarks/ServiceStack.Text.Benchmarks.csproj +++ /dev/null @@ -1,30 +0,0 @@ - - - - net45;netcoreapp1.1 - ServiceStack.Text.Benchmarks - Exe - ServiceStack.Text.Benchmarks - $(PackageTargetFallback);dnxcore50 - 1.1.1 - - - - true - - - - portable - - - - - - - - - - - - - diff --git a/benchmarks/ServiceStack.Text.VersionCompareBenchmarks/Program.cs b/benchmarks/ServiceStack.Text.VersionCompareBenchmarks/Program.cs deleted file mode 100644 index c957937c1..000000000 --- a/benchmarks/ServiceStack.Text.VersionCompareBenchmarks/Program.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using BenchmarkDotNet.Configs; -using BenchmarkDotNet.Jobs; -using BenchmarkDotNet.Running; -using BenchmarkDotNet.Validators; - -namespace ServiceStack.Text.Benchmarks -{ - public class Program - { - public static void Main(string[] args) - { - var benchmarks = new[] { - typeof(JsonDeserializationBenchmarks), - typeof(ParseBuiltinBenchmarks) - }; - - var switcher = new BenchmarkSwitcher(benchmarks); - - foreach (var benchmark in benchmarks) - { - switcher.Run( - args: new[] {benchmark.Name}, - config: ManualConfig - .Create(DefaultConfig.Instance) - //.With(Job.RyuJitX64) - .With(Job.Core) - .With(Job.Clr) - .With(new BenchmarkDotNet.Diagnosers.CompositeDiagnoser()) - .With(ExecutionValidator.FailOnError) - ); - } - } - } -} diff --git a/benchmarks/ServiceStack.Text.VersionCompareBenchmarks/ServiceStack.Text.VersionCompareBenchmarks.BaseLine.csproj b/benchmarks/ServiceStack.Text.VersionCompareBenchmarks/ServiceStack.Text.VersionCompareBenchmarks.BaseLine.csproj deleted file mode 100644 index a0f37384e..000000000 --- a/benchmarks/ServiceStack.Text.VersionCompareBenchmarks/ServiceStack.Text.VersionCompareBenchmarks.BaseLine.csproj +++ /dev/null @@ -1,36 +0,0 @@ - - - - netcoreapp1.1;net46; - ServiceStack.Text.VersionCompareBenchmarks.BaseLine - Exe - ServiceStack.Text.VersionCompareBenchmarks - $(PackageTargetFallback);dnxcore50 - 1.1.2 - - - - true - - - - portable - - - - - - - - - - - - - - - - - - - diff --git a/benchmarks/ServiceStack.Text.VersionCompareBenchmarks/ServiceStack.Text.VersionCompareBenchmarks.csproj b/benchmarks/ServiceStack.Text.VersionCompareBenchmarks/ServiceStack.Text.VersionCompareBenchmarks.csproj deleted file mode 100644 index 7b5a05480..000000000 --- a/benchmarks/ServiceStack.Text.VersionCompareBenchmarks/ServiceStack.Text.VersionCompareBenchmarks.csproj +++ /dev/null @@ -1,45 +0,0 @@ - - - - netcoreapp1.1;net46; - ServiceStack.Text.VersionCompareBenchmarks - Exe - ServiceStack.Text.VersionCompareBenchmarks - $(PackageTargetFallback);dnxcore50 - 1.1.2 - - - - true - - - - portable - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/benchmarks/run-benchmarks.bat b/benchmarks/run-benchmarks.bat deleted file mode 100644 index 1f47a2c04..000000000 --- a/benchmarks/run-benchmarks.bat +++ /dev/null @@ -1,23 +0,0 @@ -@echo off -set proj=ServiceStack.Text.VersionCompareBenchmarks -set curdate= -for /f "tokens=2-4 delims=/ " %%a in ('date /t') do (set curdate=%%c-%%a-%%b) -for /f "usebackq tokens=1,2 delims==" %%a in (`wmic os get LocalDateTime /VALUE 2^>NUL`) do (if '.%%a.'=='.LocalDateTime.' set ldt=%%b) -set curdate=%ldt:~0,4%-%ldt:~4,2%-%ldt:~6,2% -echo %proj%\ServiceStack.Text.VersionCompareBenchmarks.csproj %curdate% - -mkdir Results - -rmdir /s /q bin -rmdir /s /q obj -dotnet restore %proj%\ServiceStack.Text.VersionCompareBenchmarks.csproj && dotnet build -c Release %proj%\ServiceStack.Text.VersionCompareBenchmarks.csproj -%proj%\bin\Release\net46\ServiceStack.Text.VersionCompareBenchmarks.exe -copy BenchmarkDotNet.Artifacts\results\JsonDeserializationBenchmarks-report-github.md Results\JsonDeserialization-%curdate%.md -copy BenchmarkDotNet.Artifacts\results\ParseBuiltinBenchmarks-report-github.md Results\ParseBuiltin-%curdate%.md - -rmdir /s /q bin -rmdir /s /q obj -dotnet restore %proj%\ServiceStack.Text.VersionCompareBenchmarks.BaseLine.csproj && dotnet build -c Release %proj%\ServiceStack.Text.VersionCompareBenchmarks.BaseLine.csproj -%proj%\bin\Release\net46\ServiceStack.Text.VersionCompareBenchmarks.BaseLine.exe -copy BenchmarkDotNet.Artifacts\results\JsonDeserializationBenchmarks-report-github.md Results\JsonDeserialization-baseline-%curdate%.md -copy BenchmarkDotNet.Artifacts\results\ParseBuiltinBenchmarks-report-github.md Results\ParseBuiltin-baseline-%curdate%.md diff --git a/build/build-core.proj b/build/build-core.proj new file mode 100644 index 000000000..707725c77 --- /dev/null +++ b/build/build-core.proj @@ -0,0 +1,87 @@ + + + + + + 6 + 0 + $(BUILD_NUMBER) + + + + $(MSBuildProjectDirectory)/.. + $(BuildSolutionDir)/src + $(BuildSolutionDir)/tests + Release + $(BuildSolutionDir)/NuGet/ + $(MajorVersion).$(MinorVersion).$(PatchVersion) + + + + + BeforeBuildSolutions; + BuildSolutions + + + + + + + + + + + + + + + + + + + + + + + + + + + + ServiceStackVersion = \d+\.\d+m; + ServiceStackVersion = $(MajorVersion).$(MinorVersion)$(PatchVersion)m; + + + + new DateTime.* + new DateTime($([System.DateTime]::Now.ToString(`yyyy,MM,dd`))); + + + + + <Version>[^<]* + <Version>$(PackageVersion) + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/build/build-deps.proj b/build/build-deps.proj new file mode 100644 index 000000000..38e06c69b --- /dev/null +++ b/build/build-deps.proj @@ -0,0 +1,83 @@ + + + + + + 6 + 0 + $(BUILD_NUMBER) + + + + $(MSBuildProjectDirectory)/.. + $(BuildSolutionDir)/src + $(BuildSolutionDir)/tests + Release + $(BuildSolutionDir)/NuGet/ + $(MajorVersion).$(MinorVersion).$(PatchVersion) + + + + + BeforeBuildSolutions; + BuildSolutions + + + + + + + + + + + + + + + + + + + + + + + + + ServiceStackVersion = \d+\.\d+m; + ServiceStackVersion = $(MajorVersion).$(MinorVersion)$(PatchVersion)m; + + + + new DateTime.* + new DateTime($([System.DateTime]::Now.ToString(`yyyy,MM,dd`))); + + + + + <Version>[^<]* + <Version>$(PackageVersion) + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/build/build.bat b/build/build.bat index 07cfdd11f..4b85ae7e7 100644 --- a/build/build.bat +++ b/build/build.bat @@ -1,10 +1,3 @@ -SET MSBUILD="C:\Program Files (x86)\Microsoft Visual Studio\Preview\Community\MSBuild\15.0\Bin\MSBuild.exe" - -REM %MSBUILD% build-sn.proj /target:NuGetPack /property:Configuration=Signed;RELEASE=true;PatchVersion=9 -REM %MSBUILD% build-core.proj /target:NuGetPack /property:Configuration=Release;PatchVersion=41 -REM %MSBUILD% build.proj /target:NuGetPack /property:Configuration=Release;PatchVersion=9 -REM %MSBUILD% build-pcl.proj /target:NuGetPack /property:Configuration=Release;PatchVersion=9 -REM %MSBUILD% build-sl5.proj /target:NuGetPack /property:Configuration=Release;PatchVersion=9 - -msbuild /p:Configuration=Release ..\src\ServiceStack.Text.sln +SET MSBUILD="C:\Program Files\Microsoft Visual Studio\2022\Preview\MSBuild\Current\Bin\MSBuild.exe" +%MSBUILD% build.proj /p:Configuration=Release /p:MinorVersion=12 /p:PatchVersion=1 diff --git a/build/build.proj b/build/build.proj index c1dbd646c..7d2eae117 100644 --- a/build/build.proj +++ b/build/build.proj @@ -1,102 +1,92 @@ - - - - 5 - 0 - $(BUILD_NUMBER) - - - - $(MSBuildProjectDirectory)/.. - $(BuildSolutionDir)/src - Release - $(BuildSolutionDir)/src/.nuget/nuget.exe - $(BuildSolutionDir)/NuGet/ - $(MajorVersion).$(MinorVersion).$(PatchVersion).0 - $(MajorVersion).$(MinorVersion).$(PatchVersion) - $(MajorVersion).$(MinorVersion)$(PatchVersion) - - - - - - - - - BeforeBuildSolutions; - BuildSolutions - - - - - - - - - - - - - - - - - - - - - - - AssemblyFileVersion\(\"\d+\.\d+\.\d+\.\d+\"\) - AssemblyFileVersion("$(Version)") - - - - ServiceStackVersion = \d+\.\d+m; - ServiceStackVersion = $(EnvVersion)m; - - - - new DateTime.* - new DateTime($([System.DateTime]::Now.ToString(`yyyy,MM,dd`))); - - - - - version="5\.0(\.\d+)?" - version="$(PackageVersion)" - - - - - - - - - - - - - - - - - - - - - - - - - - - + xmlns='http://schemas.microsoft.com/developer/msbuild/2003' ToolsVersion="4.0"> + + + + 6 + 0 + $(BUILD_NUMBER) + + + + $(MSBuildProjectDirectory)/.. + $(BuildSolutionDir)/src + $(BuildSolutionDir)/tests + Release + $(BuildSolutionDir)/NuGet/ + $(MajorVersion).$(MinorVersion).$(PatchVersion) + + + + + BeforeBuildSolutions; + BuildSolutions + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ServiceStackVersion = \d+\.\d+m; + ServiceStackVersion = $(MajorVersion).$(MinorVersion)$(PatchVersion)m; + + + + new DateTime.* + new DateTime($([System.DateTime]::Now.ToString(`yyyy,MM,dd`))); + + + + + <Version>[^<]* + <Version>$(PackageVersion) + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/build/copy.bat b/build/copy.bat deleted file mode 100644 index 76ee5f18b..000000000 --- a/build/copy.bat +++ /dev/null @@ -1,17 +0,0 @@ - -REM SET BUILD=Debug -SET BUILD=Release - -COPY ..\src\ServiceStack.Text\bin\%BUILD%\net45\ServiceStack.Text.* ..\..\ServiceStack\lib\net45 -COPY ..\src\ServiceStack.Text\bin\%BUILD%\netstandard2.0\ServiceStack.Text.* ..\..\ServiceStack\lib\netstandard2.0 -COPY ..\src\ServiceStack.Text\bin\%BUILD%\net45\ServiceStack.Text.* ..\..\ServiceStack.Redis\lib\net45 -COPY ..\src\ServiceStack.Text\bin\%BUILD%\netstandard2.0\ServiceStack.Text.* ..\..\ServiceStack.Redis\lib\netstandard2.0 -COPY ..\src\ServiceStack.Text\bin\%BUILD%\net45\ServiceStack.Text.* ..\..\ServiceStack.OrmLite\lib\net45 -COPY ..\src\ServiceStack.Text\bin\%BUILD%\netstandard2.0\ServiceStack.Text.* ..\..\ServiceStack.OrmLite\lib\netstandard2.0 -COPY ..\src\ServiceStack.Text\bin\%BUILD%\net45\ServiceStack.Text.* ..\..\ServiceStack.Aws\lib\net45 -COPY ..\src\ServiceStack.Text\bin\%BUILD%\netstandard2.0\ServiceStack.Text.* ..\..\ServiceStack.Aws\lib\netstandard2.0 -COPY ..\src\ServiceStack.Text\bin\%BUILD%\net45\ServiceStack.Text.* ..\..\ServiceStack.Admin\lib\net45 -COPY ..\src\ServiceStack.Text\bin\%BUILD%\netstandard2.0\ServiceStack.Text.* ..\..\ServiceStack.Admin\lib\netstandard2.0 -COPY ..\src\ServiceStack.Text\bin\%BUILD%\net45\ServiceStack.Text.* ..\..\Stripe\lib\net45 -COPY ..\src\ServiceStack.Text\bin\%BUILD%\netstandard2.0\ServiceStack.Text.* ..\..\Stripe\lib\netstandard2.0 - diff --git a/lib/copy.bat b/lib/copy.bat deleted file mode 100644 index 8e87a25a6..000000000 --- a/lib/copy.bat +++ /dev/null @@ -1,17 +0,0 @@ -REM SET BUILD=Debug -SET BUILD=Release - -COPY ..\..\ServiceStack\src\ServiceStack.Interfaces\bin\%BUILD%\net45\ServiceStack.Interfaces.* net45 -COPY ..\..\ServiceStack\src\ServiceStack.Interfaces\bin\%BUILD%\netstandard2.0\ServiceStack.Interfaces.* netstandard2.0 - -COPY ..\..\ServiceStack\src\ServiceStack.Client\bin\%BUILD%\net45\ServiceStack.Client.* net45 -COPY ..\..\ServiceStack\src\ServiceStack.Client\bin\%BUILD%\netstandard2.0\ServiceStack.Client.* netstandard2.0 - -COPY ..\..\ServiceStack\src\ServiceStack.Common\bin\%BUILD%\net45\ServiceStack.Common.* net45 -COPY ..\..\ServiceStack\src\ServiceStack.Common\bin\%BUILD%\netstandard2.0\ServiceStack.Common.* netstandard2.0 - -COPY ..\..\ServiceStack\src\ServiceStack\bin\%BUILD%\net45\ServiceStack.dll net45 -COPY ..\..\ServiceStack\src\ServiceStack\bin\%BUILD%\net45\ServiceStack.xml net45 -COPY ..\..\ServiceStack\src\ServiceStack\bin\%BUILD%\netstandard2.0\ServiceStack.dll netstandard2.0 -COPY ..\..\ServiceStack\src\ServiceStack\bin\%BUILD%\netstandard2.0\ServiceStack.xml netstandard2.0 - diff --git a/lib/net45/Northwind.Common.dll b/lib/net45/Northwind.Common.dll deleted file mode 100644 index f4e0e6e72..000000000 Binary files a/lib/net45/Northwind.Common.dll and /dev/null differ diff --git a/lib/net45/ServiceStack.Client.dll b/lib/net45/ServiceStack.Client.dll deleted file mode 100644 index b268f319e..000000000 Binary files a/lib/net45/ServiceStack.Client.dll and /dev/null differ diff --git a/lib/net45/ServiceStack.Common.dll b/lib/net45/ServiceStack.Common.dll deleted file mode 100644 index 6067f1e29..000000000 Binary files a/lib/net45/ServiceStack.Common.dll and /dev/null differ diff --git a/lib/net45/ServiceStack.Interfaces.dll b/lib/net45/ServiceStack.Interfaces.dll deleted file mode 100644 index 67b65a123..000000000 Binary files a/lib/net45/ServiceStack.Interfaces.dll and /dev/null differ diff --git a/lib/net45/ServiceStack.Text.dll b/lib/net45/ServiceStack.Text.dll deleted file mode 100644 index 68ccdf6bb..000000000 Binary files a/lib/net45/ServiceStack.Text.dll and /dev/null differ diff --git a/lib/net45/ServiceStack.dll b/lib/net45/ServiceStack.dll deleted file mode 100644 index defe8ac9d..000000000 Binary files a/lib/net45/ServiceStack.dll and /dev/null differ diff --git a/lib/net45/protobuf-net.Extensions.dll b/lib/net45/protobuf-net.Extensions.dll deleted file mode 100644 index 5660967a6..000000000 Binary files a/lib/net45/protobuf-net.Extensions.dll and /dev/null differ diff --git a/lib/net45/protobuf-net.dll b/lib/net45/protobuf-net.dll deleted file mode 100644 index 06c1ba490..000000000 Binary files a/lib/net45/protobuf-net.dll and /dev/null differ diff --git a/lib/netstandard2.0/Northwind.Common.deps.json b/lib/netstandard2.0/Northwind.Common.deps.json deleted file mode 100644 index 1ba803f90..000000000 --- a/lib/netstandard2.0/Northwind.Common.deps.json +++ /dev/null @@ -1,628 +0,0 @@ -{ - "runtimeTarget": { - "name": ".NETStandard,Version=v2.0/", - "signature": "2e9e549a51b678c30f3d1e39fd05527861f4eb76" - }, - "compilationOptions": {}, - "targets": { - ".NETStandard,Version=v2.0": {}, - ".NETStandard,Version=v2.0/": { - "Northwind.Common/1.0.0": { - "dependencies": { - "Microsoft.CSharp": "4.4.0", - "Microsoft.Extensions.Primitives": "2.0.0", - "NETStandard.Library": "2.0.0", - "System.Reflection.Emit": "4.3.0", - "System.Reflection.Emit.Lightweight": "4.3.0", - "System.Runtime": "4.3.0", - "protobuf-net": "2.3.2", - "ServiceStack.Interfaces": "5.0.0.0" - }, - "runtime": { - "Northwind.Common.dll": {} - } - }, - "Microsoft.CSharp/4.4.0": { - "runtime": { - "lib/netstandard2.0/Microsoft.CSharp.dll": {} - } - }, - "Microsoft.Extensions.Primitives/2.0.0": { - "dependencies": { - "System.Runtime.CompilerServices.Unsafe": "4.4.0" - }, - "runtime": { - "lib/netstandard2.0/Microsoft.Extensions.Primitives.dll": {} - } - }, - "Microsoft.NETCore.Platforms/1.1.0": {}, - "Microsoft.NETCore.Targets/1.1.0": {}, - "NETStandard.Library/2.0.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0" - } - }, - "protobuf-net/2.3.2": { - "dependencies": { - "NETStandard.Library": "2.0.0", - "System.Reflection.Emit": "4.3.0", - "System.Reflection.Emit.ILGeneration": "4.3.0", - "System.Reflection.Emit.Lightweight": "4.3.0", - "System.Reflection.TypeExtensions": "4.3.0", - "System.Runtime.Serialization.Primitives": "4.3.0", - "System.Xml.XmlSerializer": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/protobuf-net.dll": {} - } - }, - "System.Collections/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Diagnostics.Debug/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Globalization/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.IO/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } - }, - "System.IO.FileSystem/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.IO": "4.3.0", - "System.IO.FileSystem.Primitives": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } - }, - "System.IO.FileSystem.Primitives/4.3.0": { - "dependencies": { - "System.Runtime": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.IO.FileSystem.Primitives.dll": {} - } - }, - "System.Linq/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0" - }, - "runtime": { - "lib/netstandard1.6/System.Linq.dll": {} - } - }, - "System.Reflection/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.IO": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Reflection.Emit/4.3.0": { - "dependencies": { - "System.IO": "4.3.0", - "System.Reflection": "4.3.0", - "System.Reflection.Emit.ILGeneration": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Reflection.Emit.dll": {} - } - }, - "System.Reflection.Emit.ILGeneration/4.3.0": { - "dependencies": { - "System.Reflection": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Reflection.Emit.ILGeneration.dll": {} - } - }, - "System.Reflection.Emit.Lightweight/4.3.0": { - "dependencies": { - "System.Reflection": "4.3.0", - "System.Reflection.Emit.ILGeneration": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Reflection.Emit.Lightweight.dll": {} - } - }, - "System.Reflection.Extensions/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Reflection": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Reflection.Primitives/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Reflection.TypeExtensions/4.3.0": { - "dependencies": { - "System.Reflection": "4.3.0", - "System.Runtime": "4.3.0" - }, - "runtime": { - "lib/netstandard1.5/System.Reflection.TypeExtensions.dll": {} - } - }, - "System.Resources.ResourceManager/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Globalization": "4.3.0", - "System.Reflection": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Runtime/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0" - } - }, - "System.Runtime.CompilerServices.Unsafe/4.4.0": { - "runtime": { - "lib/netstandard2.0/System.Runtime.CompilerServices.Unsafe.dll": {} - } - }, - "System.Runtime.Extensions/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Runtime.Handles/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Runtime.InteropServices/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Reflection": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Handles": "4.3.0" - } - }, - "System.Runtime.Serialization.Primitives/4.3.0": { - "dependencies": { - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Runtime.Serialization.Primitives.dll": {} - } - }, - "System.Text.Encoding/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Text.Encoding.Extensions/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0", - "System.Text.Encoding": "4.3.0" - } - }, - "System.Text.RegularExpressions/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Globalization": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Threading": "4.3.0" - }, - "runtime": { - "lib/netstandard1.6/System.Text.RegularExpressions.dll": {} - } - }, - "System.Threading/4.3.0": { - "dependencies": { - "System.Runtime": "4.3.0", - "System.Threading.Tasks": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Threading.dll": {} - } - }, - "System.Threading.Tasks/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Threading.Tasks.Extensions/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading.Tasks": "4.3.0" - }, - "runtime": { - "lib/netstandard1.0/System.Threading.Tasks.Extensions.dll": {} - } - }, - "System.Xml.ReaderWriter/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.IO.FileSystem": "4.3.0", - "System.IO.FileSystem.Primitives": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Text.Encoding.Extensions": "4.3.0", - "System.Text.RegularExpressions": "4.3.0", - "System.Threading.Tasks": "4.3.0", - "System.Threading.Tasks.Extensions": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Xml.ReaderWriter.dll": {} - } - }, - "System.Xml.XmlDocument/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0", - "System.Xml.ReaderWriter": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Xml.XmlDocument.dll": {} - } - }, - "System.Xml.XmlSerializer/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Linq": "4.3.0", - "System.Reflection": "4.3.0", - "System.Reflection.Emit": "4.3.0", - "System.Reflection.Emit.ILGeneration": "4.3.0", - "System.Reflection.Extensions": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Reflection.TypeExtensions": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Text.RegularExpressions": "4.3.0", - "System.Threading": "4.3.0", - "System.Xml.ReaderWriter": "4.3.0", - "System.Xml.XmlDocument": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Xml.XmlSerializer.dll": {} - } - }, - "ServiceStack.Interfaces/5.0.0.0": { - "runtime": { - "ServiceStack.Interfaces.dll": {} - } - } - } - }, - "libraries": { - "Northwind.Common/1.0.0": { - "type": "project", - "serviceable": false, - "sha512": "" - }, - "Microsoft.CSharp/4.4.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-vvVR/B08YVghQ4jHEloxqw2ZWzEGE1AOA5E0DioUM3ujbXz6FD3AfB/0Jl2ohJPd0nXYGwmPe1En6HTsSriq1A==", - "path": "microsoft.csharp/4.4.0", - "hashPath": "microsoft.csharp.4.4.0.nupkg.sha512" - }, - "Microsoft.Extensions.Primitives/2.0.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-ukg53qNlqTrK38WA30b5qhw0GD7y3jdI9PHHASjdKyTcBHTevFM2o23tyk3pWCgAV27Bbkm+CPQ2zUe1ZOuYSA==", - "path": "microsoft.extensions.primitives/2.0.0", - "hashPath": "microsoft.extensions.primitives.2.0.0.nupkg.sha512" - }, - "Microsoft.NETCore.Platforms/1.1.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==", - "path": "microsoft.netcore.platforms/1.1.0", - "hashPath": "microsoft.netcore.platforms.1.1.0.nupkg.sha512" - }, - "Microsoft.NETCore.Targets/1.1.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-aOZA3BWfz9RXjpzt0sRJJMjAscAUm3Hoa4UWAfceV9UTYxgwZ1lZt5nO2myFf+/jetYQo4uTP7zS8sJY67BBxg==", - "path": "microsoft.netcore.targets/1.1.0", - "hashPath": "microsoft.netcore.targets.1.1.0.nupkg.sha512" - }, - "NETStandard.Library/2.0.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-7jnbRU+L08FXKMxqUflxEXtVymWvNOrS8yHgu9s6EM8Anr6T/wIX4nZ08j/u3Asz+tCufp3YVwFSEvFTPYmBPA==", - "path": "netstandard.library/2.0.0", - "hashPath": "netstandard.library.2.0.0.nupkg.sha512" - }, - "protobuf-net/2.3.2": { - "type": "package", - "serviceable": true, - "sha512": "sha512-QyTFEJaF0scH/Vnpk75z/eOcnuhJMWY3xZ9sSnce4E7cCRIzi+3RN5J6ldFXwrxRdwH5La85wtrklaQwVkZnsQ==", - "path": "protobuf-net/2.3.2", - "hashPath": "protobuf-net.2.3.2.nupkg.sha512" - }, - "System.Collections/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-3Dcj85/TBdVpL5Zr+gEEBUuFe2icOnLalmEh9hfck1PTYbbyWuZgh4fmm2ysCLTrqLQw6t3TgTyJ+VLp+Qb+Lw==", - "path": "system.collections/4.3.0", - "hashPath": "system.collections.4.3.0.nupkg.sha512" - }, - "System.Diagnostics.Debug/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-ZUhUOdqmaG5Jk3Xdb8xi5kIyQYAA4PnTNlHx1mu9ZY3qv4ELIdKbnL/akbGaKi2RnNUWaZsAs31rvzFdewTj2g==", - "path": "system.diagnostics.debug/4.3.0", - "hashPath": "system.diagnostics.debug.4.3.0.nupkg.sha512" - }, - "System.Globalization/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-kYdVd2f2PAdFGblzFswE4hkNANJBKRmsfa2X5LG2AcWE1c7/4t0pYae1L8vfZ5xvE2nK/R9JprtToA61OSHWIg==", - "path": "system.globalization/4.3.0", - "hashPath": "system.globalization.4.3.0.nupkg.sha512" - }, - "System.IO/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg==", - "path": "system.io/4.3.0", - "hashPath": "system.io.4.3.0.nupkg.sha512" - }, - "System.IO.FileSystem/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-3wEMARTnuio+ulnvi+hkRNROYwa1kylvYahhcLk4HSoVdl+xxTFVeVlYOfLwrDPImGls0mDqbMhrza8qnWPTdA==", - "path": "system.io.filesystem/4.3.0", - "hashPath": "system.io.filesystem.4.3.0.nupkg.sha512" - }, - "System.IO.FileSystem.Primitives/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-6QOb2XFLch7bEc4lIcJH49nJN2HV+OC3fHDgsLVsBVBk3Y4hFAnOBGzJ2lUu7CyDDFo9IBWkSsnbkT6IBwwiMw==", - "path": "system.io.filesystem.primitives/4.3.0", - "hashPath": "system.io.filesystem.primitives.4.3.0.nupkg.sha512" - }, - "System.Linq/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-5DbqIUpsDp0dFftytzuMmc0oeMdQwjcP/EWxsksIz/w1TcFRkZ3yKKz0PqiYFMmEwPSWw+qNVqD7PJ889JzHbw==", - "path": "system.linq/4.3.0", - "hashPath": "system.linq.4.3.0.nupkg.sha512" - }, - "System.Reflection/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-KMiAFoW7MfJGa9nDFNcfu+FpEdiHpWgTcS2HdMpDvt9saK3y/G4GwprPyzqjFH9NTaGPQeWNHU+iDlDILj96aQ==", - "path": "system.reflection/4.3.0", - "hashPath": "system.reflection.4.3.0.nupkg.sha512" - }, - "System.Reflection.Emit/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-228FG0jLcIwTVJyz8CLFKueVqQK36ANazUManGaJHkO0icjiIypKW7YLWLIWahyIkdh5M7mV2dJepllLyA1SKg==", - "path": "system.reflection.emit/4.3.0", - "hashPath": "system.reflection.emit.4.3.0.nupkg.sha512" - }, - "System.Reflection.Emit.ILGeneration/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-59tBslAk9733NXLrUJrwNZEzbMAcu8k344OYo+wfSVygcgZ9lgBdGIzH/nrg3LYhXceynyvTc8t5/GD4Ri0/ng==", - "path": "system.reflection.emit.ilgeneration/4.3.0", - "hashPath": "system.reflection.emit.ilgeneration.4.3.0.nupkg.sha512" - }, - "System.Reflection.Emit.Lightweight/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-oadVHGSMsTmZsAF864QYN1t1QzZjIcuKU3l2S9cZOwDdDueNTrqq1yRj7koFfIGEnKpt6NjpL3rOzRhs4ryOgA==", - "path": "system.reflection.emit.lightweight/4.3.0", - "hashPath": "system.reflection.emit.lightweight.4.3.0.nupkg.sha512" - }, - "System.Reflection.Extensions/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-rJkrJD3kBI5B712aRu4DpSIiHRtr6QlfZSQsb0hYHrDCZORXCFjQfoipo2LaMUHoT9i1B7j7MnfaEKWDFmFQNQ==", - "path": "system.reflection.extensions/4.3.0", - "hashPath": "system.reflection.extensions.4.3.0.nupkg.sha512" - }, - "System.Reflection.Primitives/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-5RXItQz5As4xN2/YUDxdpsEkMhvw3e6aNveFXUn4Hl/udNTCNhnKp8lT9fnc3MhvGKh1baak5CovpuQUXHAlIA==", - "path": "system.reflection.primitives/4.3.0", - "hashPath": "system.reflection.primitives.4.3.0.nupkg.sha512" - }, - "System.Reflection.TypeExtensions/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-7u6ulLcZbyxB5Gq0nMkQttcdBTx57ibzw+4IOXEfR+sXYQoHvjW5LTLyNr8O22UIMrqYbchJQJnos4eooYzYJA==", - "path": "system.reflection.typeextensions/4.3.0", - "hashPath": "system.reflection.typeextensions.4.3.0.nupkg.sha512" - }, - "System.Resources.ResourceManager/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-/zrcPkkWdZmI4F92gL/TPumP98AVDu/Wxr3CSJGQQ+XN6wbRZcyfSKVoPo17ilb3iOr0cCRqJInGwNMolqhS8A==", - "path": "system.resources.resourcemanager/4.3.0", - "hashPath": "system.resources.resourcemanager.4.3.0.nupkg.sha512" - }, - "System.Runtime/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==", - "path": "system.runtime/4.3.0", - "hashPath": "system.runtime.4.3.0.nupkg.sha512" - }, - "System.Runtime.CompilerServices.Unsafe/4.4.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-9dLLuBxr5GNmOfl2jSMcsHuteEg32BEfUotmmUkmZjpR3RpVHE8YQwt0ow3p6prwA1ME8WqDVZqrr8z6H8G+Kw==", - "path": "system.runtime.compilerservices.unsafe/4.4.0", - "hashPath": "system.runtime.compilerservices.unsafe.4.4.0.nupkg.sha512" - }, - "System.Runtime.Extensions/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-guW0uK0fn5fcJJ1tJVXYd7/1h5F+pea1r7FLSOz/f8vPEqbR2ZAknuRDvTQ8PzAilDveOxNjSfr0CHfIQfFk8g==", - "path": "system.runtime.extensions/4.3.0", - "hashPath": "system.runtime.extensions.4.3.0.nupkg.sha512" - }, - "System.Runtime.Handles/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-OKiSUN7DmTWeYb3l51A7EYaeNMnvxwE249YtZz7yooT4gOZhmTjIn48KgSsw2k2lYdLgTKNJw/ZIfSElwDRVgg==", - "path": "system.runtime.handles/4.3.0", - "hashPath": "system.runtime.handles.4.3.0.nupkg.sha512" - }, - "System.Runtime.InteropServices/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-uv1ynXqiMK8mp1GM3jDqPCFN66eJ5w5XNomaK2XD+TuCroNTLFGeZ+WCmBMcBDyTFKou3P6cR6J/QsaqDp7fGQ==", - "path": "system.runtime.interopservices/4.3.0", - "hashPath": "system.runtime.interopservices.4.3.0.nupkg.sha512" - }, - "System.Runtime.Serialization.Primitives/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-Wz+0KOukJGAlXjtKr+5Xpuxf8+c8739RI1C+A2BoQZT+wMCCoMDDdO8/4IRHfaVINqL78GO8dW8G2lW/e45Mcw==", - "path": "system.runtime.serialization.primitives/4.3.0", - "hashPath": "system.runtime.serialization.primitives.4.3.0.nupkg.sha512" - }, - "System.Text.Encoding/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==", - "path": "system.text.encoding/4.3.0", - "hashPath": "system.text.encoding.4.3.0.nupkg.sha512" - }, - "System.Text.Encoding.Extensions/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-YVMK0Bt/A43RmwizJoZ22ei2nmrhobgeiYwFzC4YAN+nue8RF6djXDMog0UCn+brerQoYVyaS+ghy9P/MUVcmw==", - "path": "system.text.encoding.extensions/4.3.0", - "hashPath": "system.text.encoding.extensions.4.3.0.nupkg.sha512" - }, - "System.Text.RegularExpressions/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-RpT2DA+L660cBt1FssIE9CAGpLFdFPuheB7pLpKpn6ZXNby7jDERe8Ua/Ne2xGiwLVG2JOqziiaVCGDon5sKFA==", - "path": "system.text.regularexpressions/4.3.0", - "hashPath": "system.text.regularexpressions.4.3.0.nupkg.sha512" - }, - "System.Threading/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-VkUS0kOBcUf3Wwm0TSbrevDDZ6BlM+b/HRiapRFWjM5O0NS0LviG0glKmFK+hhPDd1XFeSdU1GmlLhb2CoVpIw==", - "path": "system.threading/4.3.0", - "hashPath": "system.threading.4.3.0.nupkg.sha512" - }, - "System.Threading.Tasks/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-LbSxKEdOUhVe8BezB/9uOGGppt+nZf6e1VFyw6v3DN6lqitm0OSn2uXMOdtP0M3W4iMcqcivm2J6UgqiwwnXiA==", - "path": "system.threading.tasks/4.3.0", - "hashPath": "system.threading.tasks.4.3.0.nupkg.sha512" - }, - "System.Threading.Tasks.Extensions/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-npvJkVKl5rKXrtl1Kkm6OhOUaYGEiF9wFbppFRWSMoApKzt2PiPHT2Bb8a5sAWxprvdOAtvaARS9QYMznEUtug==", - "path": "system.threading.tasks.extensions/4.3.0", - "hashPath": "system.threading.tasks.extensions.4.3.0.nupkg.sha512" - }, - "System.Xml.ReaderWriter/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-GrprA+Z0RUXaR4N7/eW71j1rgMnEnEVlgii49GZyAjTH7uliMnrOU3HNFBr6fEDBCJCIdlVNq9hHbaDR621XBA==", - "path": "system.xml.readerwriter/4.3.0", - "hashPath": "system.xml.readerwriter.4.3.0.nupkg.sha512" - }, - "System.Xml.XmlDocument/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-lJ8AxvkX7GQxpC6GFCeBj8ThYVyQczx2+f/cWHJU8tjS7YfI6Cv6bon70jVEgs2CiFbmmM8b9j1oZVx0dSI2Ww==", - "path": "system.xml.xmldocument/4.3.0", - "hashPath": "system.xml.xmldocument.4.3.0.nupkg.sha512" - }, - "System.Xml.XmlSerializer/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-MYoTCP7EZ98RrANESW05J5ZwskKDoN0AuZ06ZflnowE50LTpbR5yRg3tHckTVm5j/m47stuGgCrCHWePyHS70Q==", - "path": "system.xml.xmlserializer/4.3.0", - "hashPath": "system.xml.xmlserializer.4.3.0.nupkg.sha512" - }, - "ServiceStack.Interfaces/5.0.0.0": { - "type": "reference", - "serviceable": false, - "sha512": "" - } - } -} \ No newline at end of file diff --git a/lib/netstandard2.0/Northwind.Common.dll b/lib/netstandard2.0/Northwind.Common.dll deleted file mode 100644 index f9387380f..000000000 Binary files a/lib/netstandard2.0/Northwind.Common.dll and /dev/null differ diff --git a/lib/netstandard2.0/ServiceStack.Client.deps.json b/lib/netstandard2.0/ServiceStack.Client.deps.json deleted file mode 100644 index 6889d2fb3..000000000 --- a/lib/netstandard2.0/ServiceStack.Client.deps.json +++ /dev/null @@ -1,1809 +0,0 @@ -{ - "runtimeTarget": { - "name": ".NETStandard,Version=v2.0/", - "signature": "e0ee401495b2f1132396231eda6cf6e145589edd" - }, - "compilationOptions": {}, - "targets": { - ".NETStandard,Version=v2.0": {}, - ".NETStandard,Version=v2.0/": { - "ServiceStack.Client/1.0.0": { - "dependencies": { - "Microsoft.Extensions.Primitives": "2.0.0", - "NETStandard.Library": "2.0.0", - "ServiceStack.Interfaces": "1.0.0", - "System.Collections.Specialized": "4.3.0", - "System.Net.Requests": "4.3.0", - "System.ServiceModel.Primitives": "4.3.0", - "System.Xml.XmlSerializer": "4.3.0", - "ServiceStack.Text": "5.0.0.0" - }, - "runtime": { - "ServiceStack.Client.dll": {} - } - }, - "Microsoft.Extensions.Primitives/2.0.0": { - "dependencies": { - "System.Runtime.CompilerServices.Unsafe": "4.4.0" - }, - "runtime": { - "lib/netstandard2.0/Microsoft.Extensions.Primitives.dll": {} - } - }, - "Microsoft.NETCore.Platforms/1.1.0": {}, - "Microsoft.NETCore.Targets/1.1.0": {}, - "Microsoft.Win32.Primitives/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "NETStandard.Library/2.0.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0" - } - }, - "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {}, - "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {}, - "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {}, - "runtime.native.System/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0" - } - }, - "runtime.native.System.IO.Compression/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0" - } - }, - "runtime.native.System.Net.Http/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0" - } - }, - "runtime.native.System.Net.Security/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0" - } - }, - "runtime.native.System.Security.Cryptography.Apple/4.3.0": { - "dependencies": { - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "4.3.0" - } - }, - "runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "dependencies": { - "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {}, - "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {}, - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple/4.3.0": {}, - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {}, - "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {}, - "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {}, - "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {}, - "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {}, - "System.Buffers/4.3.0": { - "dependencies": { - "System.Diagnostics.Debug": "4.3.0", - "System.Diagnostics.Tracing": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading": "4.3.0" - }, - "runtime": { - "lib/netstandard1.1/System.Buffers.dll": {} - } - }, - "System.Collections/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Collections.Concurrent/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Diagnostics.Tracing": "4.3.0", - "System.Globalization": "4.3.0", - "System.Reflection": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Collections.Concurrent.dll": {} - } - }, - "System.Collections.NonGeneric/4.3.0": { - "dependencies": { - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Threading": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Collections.NonGeneric.dll": {} - } - }, - "System.Collections.Specialized/4.3.0": { - "dependencies": { - "System.Collections.NonGeneric": "4.3.0", - "System.Globalization": "4.3.0", - "System.Globalization.Extensions": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Threading": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Collections.Specialized.dll": {} - } - }, - "System.ComponentModel.EventBasedAsync/4.3.0": { - "dependencies": { - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.ComponentModel.EventBasedAsync.dll": {} - } - }, - "System.Diagnostics.Debug/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Diagnostics.DiagnosticSource/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Tracing": "4.3.0", - "System.Reflection": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Diagnostics.DiagnosticSource.dll": {} - } - }, - "System.Diagnostics.Tools/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Diagnostics.Tracing/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Globalization/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Globalization.Calendars/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Globalization": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Globalization.Extensions/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Globalization": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.InteropServices": "4.3.0" - } - }, - "System.IO/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } - }, - "System.IO.Compression/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Buffers": "4.3.0", - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0", - "runtime.native.System": "4.3.0", - "runtime.native.System.IO.Compression": "4.3.0" - } - }, - "System.IO.FileSystem/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.IO": "4.3.0", - "System.IO.FileSystem.Primitives": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } - }, - "System.IO.FileSystem.Primitives/4.3.0": { - "dependencies": { - "System.Runtime": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.IO.FileSystem.Primitives.dll": {} - } - }, - "System.Linq/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0" - }, - "runtime": { - "lib/netstandard1.6/System.Linq.dll": {} - } - }, - "System.Linq.Expressions/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Linq": "4.3.0", - "System.ObjectModel": "4.3.0", - "System.Reflection": "4.3.0", - "System.Reflection.Emit": "4.3.0", - "System.Reflection.Emit.ILGeneration": "4.3.0", - "System.Reflection.Emit.Lightweight": "4.3.0", - "System.Reflection.Extensions": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Reflection.TypeExtensions": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Threading": "4.3.0" - }, - "runtime": { - "lib/netstandard1.6/System.Linq.Expressions.dll": {} - } - }, - "System.Linq.Queryable/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Linq": "4.3.0", - "System.Linq.Expressions": "4.3.0", - "System.Reflection": "4.3.0", - "System.Reflection.Extensions": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Linq.Queryable.dll": {} - } - }, - "System.Net.Http/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Diagnostics.DiagnosticSource": "4.3.0", - "System.Diagnostics.Tracing": "4.3.0", - "System.Globalization": "4.3.0", - "System.Globalization.Extensions": "4.3.0", - "System.IO": "4.3.0", - "System.IO.FileSystem": "4.3.0", - "System.Net.Primitives": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.OpenSsl": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Security.Cryptography.X509Certificates": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0", - "runtime.native.System": "4.3.0", - "runtime.native.System.Net.Http": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "System.Net.NameResolution/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Collections": "4.3.0", - "System.Diagnostics.Tracing": "4.3.0", - "System.Globalization": "4.3.0", - "System.Net.Primitives": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Principal.Windows": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0", - "runtime.native.System": "4.3.0" - } - }, - "System.Net.Primitives/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0", - "System.Runtime.Handles": "4.3.0" - } - }, - "System.Net.Requests/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Diagnostics.Tracing": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Net.Http": "4.3.0", - "System.Net.Primitives": "4.3.0", - "System.Net.WebHeaderCollection": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } - }, - "System.Net.Security/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.Win32.Primitives": "4.3.0", - "System.Collections": "4.3.0", - "System.Collections.Concurrent": "4.3.0", - "System.Diagnostics.Tracing": "4.3.0", - "System.Globalization": "4.3.0", - "System.Globalization.Extensions": "4.3.0", - "System.IO": "4.3.0", - "System.Net.Primitives": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Claims": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.OpenSsl": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Security.Cryptography.X509Certificates": "4.3.0", - "System.Security.Principal": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0", - "System.Threading.ThreadPool": "4.3.0", - "runtime.native.System": "4.3.0", - "runtime.native.System.Net.Security": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "System.Net.Sockets/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.IO": "4.3.0", - "System.Net.Primitives": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } - }, - "System.Net.WebHeaderCollection/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Net.WebHeaderCollection.dll": {} - } - }, - "System.Net.WebSockets/4.3.0": { - "dependencies": { - "Microsoft.Win32.Primitives": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading.Tasks": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Net.WebSockets.dll": {} - } - }, - "System.Net.WebSockets.Client/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.Win32.Primitives": "4.3.0", - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Diagnostics.Tracing": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Net.NameResolution": "4.3.0", - "System.Net.Primitives": "4.3.0", - "System.Net.Security": "4.3.0", - "System.Net.Sockets": "4.3.0", - "System.Net.WebHeaderCollection": "4.3.0", - "System.Net.WebSockets": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Security.Cryptography.X509Certificates": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Text.Encoding.Extensions": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0", - "System.Threading.Timer": "4.3.0" - } - }, - "System.ObjectModel/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.ObjectModel.dll": {} - } - }, - "System.Private.DataContractSerialization/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Collections.Concurrent": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Linq": "4.3.0", - "System.Reflection": "4.3.0", - "System.Reflection.Emit.ILGeneration": "4.3.0", - "System.Reflection.Emit.Lightweight": "4.3.0", - "System.Reflection.Extensions": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Reflection.TypeExtensions": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Serialization.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Text.Encoding.Extensions": "4.3.0", - "System.Text.RegularExpressions": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0", - "System.Xml.ReaderWriter": "4.3.0", - "System.Xml.XDocument": "4.3.0", - "System.Xml.XmlDocument": "4.3.0", - "System.Xml.XmlSerializer": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Private.DataContractSerialization.dll": {} - } - }, - "System.Private.ServiceModel/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Collections": "4.3.0", - "System.Collections.Concurrent": "4.3.0", - "System.Collections.NonGeneric": "4.3.0", - "System.Collections.Specialized": "4.3.0", - "System.ComponentModel.EventBasedAsync": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Diagnostics.Tracing": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.IO.Compression": "4.3.0", - "System.Linq": "4.3.0", - "System.Linq.Expressions": "4.3.0", - "System.Linq.Queryable": "4.3.0", - "System.Net.Http": "4.3.0", - "System.Net.NameResolution": "4.3.0", - "System.Net.Primitives": "4.3.0", - "System.Net.Security": "4.3.0", - "System.Net.Sockets": "4.3.0", - "System.Net.WebHeaderCollection": "4.3.0", - "System.Net.WebSockets": "4.3.0", - "System.Net.WebSockets.Client": "4.3.0", - "System.ObjectModel": "4.3.0", - "System.Reflection": "4.3.0", - "System.Reflection.DispatchProxy": "4.3.0", - "System.Reflection.Extensions": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Reflection.TypeExtensions": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices.RuntimeInformation": "4.3.0", - "System.Runtime.Serialization.Primitives": "4.3.0", - "System.Runtime.Serialization.Xml": "4.3.0", - "System.Security.Claims": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Security.Cryptography.X509Certificates": "4.3.0", - "System.Security.Principal": "4.3.0", - "System.Security.Principal.Windows": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Text.Encoding.Extensions": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0", - "System.Threading.Timer": "4.3.0", - "System.Xml.ReaderWriter": "4.3.0", - "System.Xml.XmlDocument": "4.3.0", - "System.Xml.XmlSerializer": "4.3.0" - } - }, - "System.Reflection/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.IO": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Reflection.DispatchProxy/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Linq": "4.3.0", - "System.Reflection": "4.3.0", - "System.Reflection.Emit": "4.3.0", - "System.Reflection.Emit.ILGeneration": "4.3.0", - "System.Reflection.Extensions": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Reflection.DispatchProxy.dll": {} - } - }, - "System.Reflection.Emit/4.3.0": { - "dependencies": { - "System.IO": "4.3.0", - "System.Reflection": "4.3.0", - "System.Reflection.Emit.ILGeneration": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Reflection.Emit.dll": {} - } - }, - "System.Reflection.Emit.ILGeneration/4.3.0": { - "dependencies": { - "System.Reflection": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Reflection.Emit.ILGeneration.dll": {} - } - }, - "System.Reflection.Emit.Lightweight/4.3.0": { - "dependencies": { - "System.Reflection": "4.3.0", - "System.Reflection.Emit.ILGeneration": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Reflection.Emit.Lightweight.dll": {} - } - }, - "System.Reflection.Extensions/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Reflection": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Reflection.Primitives/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Reflection.TypeExtensions/4.3.0": { - "dependencies": { - "System.Reflection": "4.3.0", - "System.Runtime": "4.3.0" - }, - "runtime": { - "lib/netstandard1.5/System.Reflection.TypeExtensions.dll": {} - } - }, - "System.Resources.ResourceManager/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Globalization": "4.3.0", - "System.Reflection": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Runtime/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0" - } - }, - "System.Runtime.CompilerServices.Unsafe/4.4.0": { - "runtime": { - "lib/netstandard2.0/System.Runtime.CompilerServices.Unsafe.dll": {} - } - }, - "System.Runtime.Extensions/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Runtime.Handles/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Runtime.InteropServices/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Reflection": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Handles": "4.3.0" - } - }, - "System.Runtime.InteropServices.RuntimeInformation/4.3.0": { - "dependencies": { - "System.Reflection": "4.3.0", - "System.Reflection.Extensions": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Threading": "4.3.0", - "runtime.native.System": "4.3.0" - }, - "runtime": { - "lib/netstandard1.1/System.Runtime.InteropServices.RuntimeInformation.dll": {} - } - }, - "System.Runtime.Numerics/4.3.0": { - "dependencies": { - "System.Globalization": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Runtime.Numerics.dll": {} - } - }, - "System.Runtime.Serialization.Primitives/4.3.0": { - "dependencies": { - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Runtime.Serialization.Primitives.dll": {} - } - }, - "System.Runtime.Serialization.Xml/4.3.0": { - "dependencies": { - "System.IO": "4.3.0", - "System.Private.DataContractSerialization": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Serialization.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Xml.ReaderWriter": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Runtime.Serialization.Xml.dll": {} - } - }, - "System.Security.Claims/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Security.Principal": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Security.Claims.dll": {} - } - }, - "System.Security.Cryptography.Algorithms/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Collections": "4.3.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Runtime.Numerics": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "runtime.native.System.Security.Cryptography.Apple": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "System.Security.Cryptography.Cng/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0" - } - }, - "System.Security.Cryptography.Csp/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.IO": "4.3.0", - "System.Reflection": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0" - } - }, - "System.Security.Cryptography.Encoding/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Collections": "4.3.0", - "System.Collections.Concurrent": "4.3.0", - "System.Linq": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "System.Security.Cryptography.OpenSsl/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Runtime.Numerics": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "System.Security.Cryptography.Primitives/4.3.0": { - "dependencies": { - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Security.Cryptography.Primitives.dll": {} - } - }, - "System.Security.Cryptography.X509Certificates/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.Globalization.Calendars": "4.3.0", - "System.IO": "4.3.0", - "System.IO.FileSystem": "4.3.0", - "System.IO.FileSystem.Primitives": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Runtime.Numerics": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Cng": "4.3.0", - "System.Security.Cryptography.Csp": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.OpenSsl": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0", - "runtime.native.System": "4.3.0", - "runtime.native.System.Net.Http": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "System.Security.Principal/4.3.0": { - "dependencies": { - "System.Runtime": "4.3.0" - }, - "runtime": { - "lib/netstandard1.0/System.Security.Principal.dll": {} - } - }, - "System.Security.Principal.Windows/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.Win32.Primitives": "4.3.0", - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Reflection": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Claims": "4.3.0", - "System.Security.Principal": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0" - } - }, - "System.ServiceModel.Primitives/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.ComponentModel.EventBasedAsync": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Net.Primitives": "4.3.0", - "System.ObjectModel": "4.3.0", - "System.Private.ServiceModel": "4.3.0", - "System.Reflection": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Serialization.Primitives": "4.3.0", - "System.Runtime.Serialization.Xml": "4.3.0", - "System.Security.Cryptography.X509Certificates": "4.3.0", - "System.Security.Principal": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0", - "System.Xml.ReaderWriter": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.ServiceModel.Primitives.dll": {} - } - }, - "System.Text.Encoding/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Text.Encoding.Extensions/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0", - "System.Text.Encoding": "4.3.0" - } - }, - "System.Text.RegularExpressions/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Globalization": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Threading": "4.3.0" - }, - "runtime": { - "lib/netstandard1.6/System.Text.RegularExpressions.dll": {} - } - }, - "System.Threading/4.3.0": { - "dependencies": { - "System.Runtime": "4.3.0", - "System.Threading.Tasks": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Threading.dll": {} - } - }, - "System.Threading.Tasks/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Threading.Tasks.Extensions/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading.Tasks": "4.3.0" - }, - "runtime": { - "lib/netstandard1.0/System.Threading.Tasks.Extensions.dll": {} - } - }, - "System.Threading.ThreadPool/4.3.0": { - "dependencies": { - "System.Runtime": "4.3.0", - "System.Runtime.Handles": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Threading.ThreadPool.dll": {} - } - }, - "System.Threading.Timer/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Xml.ReaderWriter/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.IO.FileSystem": "4.3.0", - "System.IO.FileSystem.Primitives": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Text.Encoding.Extensions": "4.3.0", - "System.Text.RegularExpressions": "4.3.0", - "System.Threading.Tasks": "4.3.0", - "System.Threading.Tasks.Extensions": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Xml.ReaderWriter.dll": {} - } - }, - "System.Xml.XDocument/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Diagnostics.Tools": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Reflection": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0", - "System.Xml.ReaderWriter": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Xml.XDocument.dll": {} - } - }, - "System.Xml.XmlDocument/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0", - "System.Xml.ReaderWriter": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Xml.XmlDocument.dll": {} - } - }, - "System.Xml.XmlSerializer/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Linq": "4.3.0", - "System.Reflection": "4.3.0", - "System.Reflection.Emit": "4.3.0", - "System.Reflection.Emit.ILGeneration": "4.3.0", - "System.Reflection.Extensions": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Reflection.TypeExtensions": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Text.RegularExpressions": "4.3.0", - "System.Threading": "4.3.0", - "System.Xml.ReaderWriter": "4.3.0", - "System.Xml.XmlDocument": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Xml.XmlSerializer.dll": {} - } - }, - "ServiceStack.Interfaces/1.0.0": { - "dependencies": { - "System.Runtime.Serialization.Primitives": "4.3.0" - }, - "runtime": { - "ServiceStack.Interfaces.dll": {} - } - }, - "ServiceStack.Text/5.0.0.0": { - "runtime": { - "ServiceStack.Text.dll": {} - } - } - } - }, - "libraries": { - "ServiceStack.Client/1.0.0": { - "type": "project", - "serviceable": false, - "sha512": "" - }, - "Microsoft.Extensions.Primitives/2.0.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-ukg53qNlqTrK38WA30b5qhw0GD7y3jdI9PHHASjdKyTcBHTevFM2o23tyk3pWCgAV27Bbkm+CPQ2zUe1ZOuYSA==", - "path": "microsoft.extensions.primitives/2.0.0", - "hashPath": "microsoft.extensions.primitives.2.0.0.nupkg.sha512" - }, - "Microsoft.NETCore.Platforms/1.1.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==", - "path": "microsoft.netcore.platforms/1.1.0", - "hashPath": "microsoft.netcore.platforms.1.1.0.nupkg.sha512" - }, - "Microsoft.NETCore.Targets/1.1.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-aOZA3BWfz9RXjpzt0sRJJMjAscAUm3Hoa4UWAfceV9UTYxgwZ1lZt5nO2myFf+/jetYQo4uTP7zS8sJY67BBxg==", - "path": "microsoft.netcore.targets/1.1.0", - "hashPath": "microsoft.netcore.targets.1.1.0.nupkg.sha512" - }, - "Microsoft.Win32.Primitives/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-9ZQKCWxH7Ijp9BfahvL2Zyf1cJIk8XYLF6Yjzr2yi0b2cOut/HQ31qf1ThHAgCc3WiZMdnWcfJCgN82/0UunxA==", - "path": "microsoft.win32.primitives/4.3.0", - "hashPath": "microsoft.win32.primitives.4.3.0.nupkg.sha512" - }, - "NETStandard.Library/2.0.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-7jnbRU+L08FXKMxqUflxEXtVymWvNOrS8yHgu9s6EM8Anr6T/wIX4nZ08j/u3Asz+tCufp3YVwFSEvFTPYmBPA==", - "path": "netstandard.library/2.0.0", - "hashPath": "netstandard.library.2.0.0.nupkg.sha512" - }, - "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-HdSSp5MnJSsg08KMfZThpuLPJpPwE5hBXvHwoKWosyHHfe8Mh5WKT0ylEOf6yNzX6Ngjxe4Whkafh5q7Ymac4Q==", - "path": "runtime.debian.8-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.debian.8-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-+yH1a49wJMy8Zt4yx5RhJrxO/DBDByAiCzNwiETI+1S4mPdCu0OY4djdciC7Vssk0l22wQaDLrXxXkp+3+7bVA==", - "path": "runtime.fedora.23-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.fedora.23-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-c3YNH1GQJbfIPJeCnr4avseugSqPrxwIqzthYyZDN6EuOyNOzq+y2KSUfRcXauya1sF4foESTgwM5e1A8arAKw==", - "path": "runtime.fedora.24-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.fedora.24-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.native.System/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-c/qWt2LieNZIj1jGnVNsE2Kl23Ya2aSTBuXMD6V7k9KWr6l16Tqdwq+hJScEpWER9753NWC8h96PaVNY5Ld7Jw==", - "path": "runtime.native.system/4.3.0", - "hashPath": "runtime.native.system.4.3.0.nupkg.sha512" - }, - "runtime.native.System.IO.Compression/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-INBPonS5QPEgn7naufQFXJEp3zX6L4bwHgJ/ZH78aBTpeNfQMtf7C6VrAFhlq2xxWBveIOWyFzQjJ8XzHMhdOQ==", - "path": "runtime.native.system.io.compression/4.3.0", - "hashPath": "runtime.native.system.io.compression.4.3.0.nupkg.sha512" - }, - "runtime.native.System.Net.Http/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-ZVuZJqnnegJhd2k/PtAbbIcZ3aZeITq3sj06oKfMBSfphW3HDmk/t4ObvbOk/JA/swGR0LNqMksAh/f7gpTROg==", - "path": "runtime.native.system.net.http/4.3.0", - "hashPath": "runtime.native.system.net.http.4.3.0.nupkg.sha512" - }, - "runtime.native.System.Net.Security/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-M2nN92ePS8BgQ2oi6Jj3PlTUzadYSIWLdZrHY1n1ZcW9o4wAQQ6W+aQ2lfq1ysZQfVCgDwY58alUdowrzezztg==", - "path": "runtime.native.system.net.security/4.3.0", - "hashPath": "runtime.native.system.net.security.4.3.0.nupkg.sha512" - }, - "runtime.native.System.Security.Cryptography.Apple/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-DloMk88juo0OuOWr56QG7MNchmafTLYWvABy36izkrLI5VledI0rq28KGs1i9wbpeT9NPQrx/wTf8U2vazqQ3Q==", - "path": "runtime.native.system.security.cryptography.apple/4.3.0", - "hashPath": "runtime.native.system.security.cryptography.apple.4.3.0.nupkg.sha512" - }, - "runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-NS1U+700m4KFRHR5o4vo9DSlTmlCKu/u7dtE5sUHVIPB+xpXxYQvgBgA6wEIeCz6Yfn0Z52/72WYsToCEPJnrw==", - "path": "runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-b3pthNgxxFcD+Pc0WSEoC0+md3MyhRS6aCEeenvNE3Fdw1HyJ18ZhRFVJJzIeR/O/jpxPboB805Ho0T3Ul7w8A==", - "path": "runtime.opensuse.13.2-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.opensuse.13.2-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-KeLz4HClKf+nFS7p/6Fi/CqyLXh81FpiGzcmuS8DGi9lUqSnZ6Es23/gv2O+1XVGfrbNmviF7CckBpavkBoIFQ==", - "path": "runtime.opensuse.42.1-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.opensuse.42.1-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-kVXCuMTrTlxq4XOOMAysuNwsXWpYeboGddNGpIgNSZmv1b6r/s/DPk0fYMB7Q5Qo4bY68o48jt4T4y5BVecbCQ==", - "path": "runtime.osx.10.10-x64.runtime.native.system.security.cryptography.apple/4.3.0", - "hashPath": "runtime.osx.10.10-x64.runtime.native.system.security.cryptography.apple.4.3.0.nupkg.sha512" - }, - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-X7IdhILzr4ROXd8mI1BUCQMSHSQwelUlBjF1JyTKCjXaOGn2fB4EKBxQbCK2VjO3WaWIdlXZL3W6TiIVnrhX4g==", - "path": "runtime.osx.10.10-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.osx.10.10-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-nyFNiCk/r+VOiIqreLix8yN+q3Wga9+SE8BCgkf+2BwEKiNx6DyvFjCgkfV743/grxv8jHJ8gUK4XEQw7yzRYg==", - "path": "runtime.rhel.7-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.rhel.7-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-ytoewC6wGorL7KoCAvRfsgoJPJbNq+64k2SqW6JcOAebWsFUvCCYgfzQMrnpvPiEl4OrblUlhF2ji+Q1+SVLrQ==", - "path": "runtime.ubuntu.14.04-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.ubuntu.14.04-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-I8bKw2I8k58Wx7fMKQJn2R8lamboCAiHfHeV/pS65ScKWMMI0+wJkLYlEKvgW1D/XvSl/221clBoR2q9QNNM7A==", - "path": "runtime.ubuntu.16.04-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.ubuntu.16.04-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-VB5cn/7OzUfzdnC8tqAIMQciVLiq2epm2NrAm1E9OjNRyG4lVhfR61SMcLizejzQP8R8Uf/0l5qOIbUEi+RdEg==", - "path": "runtime.ubuntu.16.10-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.ubuntu.16.10-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "System.Buffers/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-ratu44uTIHgeBeI0dE8DWvmXVBSo4u7ozRZZHOMmK/JPpYyo0dAfgSiHlpiObMQ5lEtEyIXA40sKRYg5J6A8uQ==", - "path": "system.buffers/4.3.0", - "hashPath": "system.buffers.4.3.0.nupkg.sha512" - }, - "System.Collections/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-3Dcj85/TBdVpL5Zr+gEEBUuFe2icOnLalmEh9hfck1PTYbbyWuZgh4fmm2ysCLTrqLQw6t3TgTyJ+VLp+Qb+Lw==", - "path": "system.collections/4.3.0", - "hashPath": "system.collections.4.3.0.nupkg.sha512" - }, - "System.Collections.Concurrent/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-ztl69Xp0Y/UXCL+3v3tEU+lIy+bvjKNUmopn1wep/a291pVPK7dxBd6T7WnlQqRog+d1a/hSsgRsmFnIBKTPLQ==", - "path": "system.collections.concurrent/4.3.0", - "hashPath": "system.collections.concurrent.4.3.0.nupkg.sha512" - }, - "System.Collections.NonGeneric/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-prtjIEMhGUnQq6RnPEYLpFt8AtLbp9yq2zxOSrY7KJJZrw25Fi97IzBqY7iqssbM61Ek5b8f3MG/sG1N2sN5KA==", - "path": "system.collections.nongeneric/4.3.0", - "hashPath": "system.collections.nongeneric.4.3.0.nupkg.sha512" - }, - "System.Collections.Specialized/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-Epx8PoVZR0iuOnJJDzp7pWvdfMMOAvpUo95pC4ScH2mJuXkKA2Y4aR3cG9qt2klHgSons1WFh4kcGW7cSXvrxg==", - "path": "system.collections.specialized/4.3.0", - "hashPath": "system.collections.specialized.4.3.0.nupkg.sha512" - }, - "System.ComponentModel.EventBasedAsync/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-fCFl8f0XdwA/BuoNrVBB5D0Y48/hv2J+w4xSDdXQitXZsR6UCSOrDVE7TCUraY802ENwcHUnUCv4En8CupDU1g==", - "path": "system.componentmodel.eventbasedasync/4.3.0", - "hashPath": "system.componentmodel.eventbasedasync.4.3.0.nupkg.sha512" - }, - "System.Diagnostics.Debug/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-ZUhUOdqmaG5Jk3Xdb8xi5kIyQYAA4PnTNlHx1mu9ZY3qv4ELIdKbnL/akbGaKi2RnNUWaZsAs31rvzFdewTj2g==", - "path": "system.diagnostics.debug/4.3.0", - "hashPath": "system.diagnostics.debug.4.3.0.nupkg.sha512" - }, - "System.Diagnostics.DiagnosticSource/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-tD6kosZnTAGdrEa0tZSuFyunMbt/5KYDnHdndJYGqZoNy00XVXyACd5d6KnE1YgYv3ne2CjtAfNXo/fwEhnKUA==", - "path": "system.diagnostics.diagnosticsource/4.3.0", - "hashPath": "system.diagnostics.diagnosticsource.4.3.0.nupkg.sha512" - }, - "System.Diagnostics.Tools/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-UUvkJfSYJMM6x527dJg2VyWPSRqIVB0Z7dbjHst1zmwTXz5CcXSYJFWRpuigfbO1Lf7yfZiIaEUesfnl/g5EyA==", - "path": "system.diagnostics.tools/4.3.0", - "hashPath": "system.diagnostics.tools.4.3.0.nupkg.sha512" - }, - "System.Diagnostics.Tracing/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-rswfv0f/Cqkh78rA5S8eN8Neocz234+emGCtTF3lxPY96F+mmmUen6tbn0glN6PMvlKQb9bPAY5e9u7fgPTkKw==", - "path": "system.diagnostics.tracing/4.3.0", - "hashPath": "system.diagnostics.tracing.4.3.0.nupkg.sha512" - }, - "System.Globalization/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-kYdVd2f2PAdFGblzFswE4hkNANJBKRmsfa2X5LG2AcWE1c7/4t0pYae1L8vfZ5xvE2nK/R9JprtToA61OSHWIg==", - "path": "system.globalization/4.3.0", - "hashPath": "system.globalization.4.3.0.nupkg.sha512" - }, - "System.Globalization.Calendars/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-GUlBtdOWT4LTV3I+9/PJW+56AnnChTaOqqTLFtdmype/L500M2LIyXgmtd9X2P2VOkmJd5c67H5SaC2QcL1bFA==", - "path": "system.globalization.calendars/4.3.0", - "hashPath": "system.globalization.calendars.4.3.0.nupkg.sha512" - }, - "System.Globalization.Extensions/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-FhKmdR6MPG+pxow6wGtNAWdZh7noIOpdD5TwQ3CprzgIE1bBBoim0vbR1+AWsWjQmU7zXHgQo4TWSP6lCeiWcQ==", - "path": "system.globalization.extensions/4.3.0", - "hashPath": "system.globalization.extensions.4.3.0.nupkg.sha512" - }, - "System.IO/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg==", - "path": "system.io/4.3.0", - "hashPath": "system.io.4.3.0.nupkg.sha512" - }, - "System.IO.Compression/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-YHndyoiV90iu4iKG115ibkhrG+S3jBm8Ap9OwoUAzO5oPDAWcr0SFwQFm0HjM8WkEZWo0zvLTyLmbvTkW1bXgg==", - "path": "system.io.compression/4.3.0", - "hashPath": "system.io.compression.4.3.0.nupkg.sha512" - }, - "System.IO.FileSystem/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-3wEMARTnuio+ulnvi+hkRNROYwa1kylvYahhcLk4HSoVdl+xxTFVeVlYOfLwrDPImGls0mDqbMhrza8qnWPTdA==", - "path": "system.io.filesystem/4.3.0", - "hashPath": "system.io.filesystem.4.3.0.nupkg.sha512" - }, - "System.IO.FileSystem.Primitives/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-6QOb2XFLch7bEc4lIcJH49nJN2HV+OC3fHDgsLVsBVBk3Y4hFAnOBGzJ2lUu7CyDDFo9IBWkSsnbkT6IBwwiMw==", - "path": "system.io.filesystem.primitives/4.3.0", - "hashPath": "system.io.filesystem.primitives.4.3.0.nupkg.sha512" - }, - "System.Linq/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-5DbqIUpsDp0dFftytzuMmc0oeMdQwjcP/EWxsksIz/w1TcFRkZ3yKKz0PqiYFMmEwPSWw+qNVqD7PJ889JzHbw==", - "path": "system.linq/4.3.0", - "hashPath": "system.linq.4.3.0.nupkg.sha512" - }, - "System.Linq.Expressions/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-PGKkrd2khG4CnlyJwxwwaWWiSiWFNBGlgXvJpeO0xCXrZ89ODrQ6tjEWS/kOqZ8GwEOUATtKtzp1eRgmYNfclg==", - "path": "system.linq.expressions/4.3.0", - "hashPath": "system.linq.expressions.4.3.0.nupkg.sha512" - }, - "System.Linq.Queryable/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-In1Bmmvl/j52yPu3xgakQSI0YIckPUr870w4K5+Lak3JCCa8hl+my65lABOuKfYs4ugmZy25ScFerC4nz8+b6g==", - "path": "system.linq.queryable/4.3.0", - "hashPath": "system.linq.queryable.4.3.0.nupkg.sha512" - }, - "System.Net.Http/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-sYg+FtILtRQuYWSIAuNOELwVuVsxVyJGWQyOnlAzhV4xvhyFnON1bAzYYC+jjRW8JREM45R0R5Dgi8MTC5sEwA==", - "path": "system.net.http/4.3.0", - "hashPath": "system.net.http.4.3.0.nupkg.sha512" - }, - "System.Net.NameResolution/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-AFYl08R7MrsrEjqpQWTZWBadqXyTzNDaWpMqyxhb0d6sGhV6xMDKueuBXlLL30gz+DIRY6MpdgnHWlCh5wmq9w==", - "path": "system.net.nameresolution/4.3.0", - "hashPath": "system.net.nameresolution.4.3.0.nupkg.sha512" - }, - "System.Net.Primitives/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-qOu+hDwFwoZPbzPvwut2qATe3ygjeQBDQj91xlsaqGFQUI5i4ZnZb8yyQuLGpDGivEPIt8EJkd1BVzVoP31FXA==", - "path": "system.net.primitives/4.3.0", - "hashPath": "system.net.primitives.4.3.0.nupkg.sha512" - }, - "System.Net.Requests/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-OZNUuAs0kDXUzm7U5NZ1ojVta5YFZmgT2yxBqsQ7Eseq5Ahz88LInGRuNLJ/NP2F8W1q7tse1pKDthj3reF5QA==", - "path": "system.net.requests/4.3.0", - "hashPath": "system.net.requests.4.3.0.nupkg.sha512" - }, - "System.Net.Security/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-IgJKNfALqw7JRWp5LMQ5SWHNKvXVz094U6wNE3c1i8bOkMQvgBL+MMQuNt3xl9Qg9iWpj3lFxPZEY6XHmROjMQ==", - "path": "system.net.security/4.3.0", - "hashPath": "system.net.security.4.3.0.nupkg.sha512" - }, - "System.Net.Sockets/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-m6icV6TqQOAdgt5N/9I5KNpjom/5NFtkmGseEH+AK/hny8XrytLH3+b5M8zL/Ycg3fhIocFpUMyl/wpFnVRvdw==", - "path": "system.net.sockets/4.3.0", - "hashPath": "system.net.sockets.4.3.0.nupkg.sha512" - }, - "System.Net.WebHeaderCollection/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-XZrXYG3c7QV/GpWeoaRC02rM6LH2JJetfVYskf35wdC/w2fFDFMphec4gmVH2dkll6abtW14u9Rt96pxd9YH2A==", - "path": "system.net.webheadercollection/4.3.0", - "hashPath": "system.net.webheadercollection.4.3.0.nupkg.sha512" - }, - "System.Net.WebSockets/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-u6fFNY5q4T8KerUAVbya7bR6b7muBuSTAersyrihkcmE5QhEOiH3t5rh4il15SexbVlpXFHGuMwr/m8fDrnkQg==", - "path": "system.net.websockets/4.3.0", - "hashPath": "system.net.websockets.4.3.0.nupkg.sha512" - }, - "System.Net.WebSockets.Client/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-QjcP33TONz6wS5+3B4JKHXxQp9b5sexHH/eGTBwG2ZhlaThGvZGm3mMEHyJB6rrLsSah3h1Gc/OkJ162iavkjA==", - "path": "system.net.websockets.client/4.3.0", - "hashPath": "system.net.websockets.client.4.3.0.nupkg.sha512" - }, - "System.ObjectModel/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-bdX+80eKv9bN6K4N+d77OankKHGn6CH711a6fcOpMQu2Fckp/Ft4L/kW9WznHpyR0NRAvJutzOMHNNlBGvxQzQ==", - "path": "system.objectmodel/4.3.0", - "hashPath": "system.objectmodel.4.3.0.nupkg.sha512" - }, - "System.Private.DataContractSerialization/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-yDaJ2x3mMmjdZEDB4IbezSnCsnjQ4BxinKhRAaP6kEgL6Bb6jANWphs5SzyD8imqeC/3FxgsuXT6ykkiH1uUmA==", - "path": "system.private.datacontractserialization/4.3.0", - "hashPath": "system.private.datacontractserialization.4.3.0.nupkg.sha512" - }, - "System.Private.ServiceModel/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-N41KTsqCL7j4jwSToNOcZJs14EVtT4bHfiztE1e+mMNqFfmITIjOoWgI1I7DpR2NrbkV5vj4f+cSGg3FszHPmA==", - "path": "system.private.servicemodel/4.3.0", - "hashPath": "system.private.servicemodel.4.3.0.nupkg.sha512" - }, - "System.Reflection/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-KMiAFoW7MfJGa9nDFNcfu+FpEdiHpWgTcS2HdMpDvt9saK3y/G4GwprPyzqjFH9NTaGPQeWNHU+iDlDILj96aQ==", - "path": "system.reflection/4.3.0", - "hashPath": "system.reflection.4.3.0.nupkg.sha512" - }, - "System.Reflection.DispatchProxy/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-vFln4g7zbLRyJbioExbMaW4BGuE2urDE2IKQk02x1y1uhQWntD+4rcYA4xQGJ19PlMdYPMWExHVQj3zKDODBFw==", - "path": "system.reflection.dispatchproxy/4.3.0", - "hashPath": "system.reflection.dispatchproxy.4.3.0.nupkg.sha512" - }, - "System.Reflection.Emit/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-228FG0jLcIwTVJyz8CLFKueVqQK36ANazUManGaJHkO0icjiIypKW7YLWLIWahyIkdh5M7mV2dJepllLyA1SKg==", - "path": "system.reflection.emit/4.3.0", - "hashPath": "system.reflection.emit.4.3.0.nupkg.sha512" - }, - "System.Reflection.Emit.ILGeneration/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-59tBslAk9733NXLrUJrwNZEzbMAcu8k344OYo+wfSVygcgZ9lgBdGIzH/nrg3LYhXceynyvTc8t5/GD4Ri0/ng==", - "path": "system.reflection.emit.ilgeneration/4.3.0", - "hashPath": "system.reflection.emit.ilgeneration.4.3.0.nupkg.sha512" - }, - "System.Reflection.Emit.Lightweight/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-oadVHGSMsTmZsAF864QYN1t1QzZjIcuKU3l2S9cZOwDdDueNTrqq1yRj7koFfIGEnKpt6NjpL3rOzRhs4ryOgA==", - "path": "system.reflection.emit.lightweight/4.3.0", - "hashPath": "system.reflection.emit.lightweight.4.3.0.nupkg.sha512" - }, - "System.Reflection.Extensions/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-rJkrJD3kBI5B712aRu4DpSIiHRtr6QlfZSQsb0hYHrDCZORXCFjQfoipo2LaMUHoT9i1B7j7MnfaEKWDFmFQNQ==", - "path": "system.reflection.extensions/4.3.0", - "hashPath": "system.reflection.extensions.4.3.0.nupkg.sha512" - }, - "System.Reflection.Primitives/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-5RXItQz5As4xN2/YUDxdpsEkMhvw3e6aNveFXUn4Hl/udNTCNhnKp8lT9fnc3MhvGKh1baak5CovpuQUXHAlIA==", - "path": "system.reflection.primitives/4.3.0", - "hashPath": "system.reflection.primitives.4.3.0.nupkg.sha512" - }, - "System.Reflection.TypeExtensions/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-7u6ulLcZbyxB5Gq0nMkQttcdBTx57ibzw+4IOXEfR+sXYQoHvjW5LTLyNr8O22UIMrqYbchJQJnos4eooYzYJA==", - "path": "system.reflection.typeextensions/4.3.0", - "hashPath": "system.reflection.typeextensions.4.3.0.nupkg.sha512" - }, - "System.Resources.ResourceManager/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-/zrcPkkWdZmI4F92gL/TPumP98AVDu/Wxr3CSJGQQ+XN6wbRZcyfSKVoPo17ilb3iOr0cCRqJInGwNMolqhS8A==", - "path": "system.resources.resourcemanager/4.3.0", - "hashPath": "system.resources.resourcemanager.4.3.0.nupkg.sha512" - }, - "System.Runtime/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==", - "path": "system.runtime/4.3.0", - "hashPath": "system.runtime.4.3.0.nupkg.sha512" - }, - "System.Runtime.CompilerServices.Unsafe/4.4.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-9dLLuBxr5GNmOfl2jSMcsHuteEg32BEfUotmmUkmZjpR3RpVHE8YQwt0ow3p6prwA1ME8WqDVZqrr8z6H8G+Kw==", - "path": "system.runtime.compilerservices.unsafe/4.4.0", - "hashPath": "system.runtime.compilerservices.unsafe.4.4.0.nupkg.sha512" - }, - "System.Runtime.Extensions/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-guW0uK0fn5fcJJ1tJVXYd7/1h5F+pea1r7FLSOz/f8vPEqbR2ZAknuRDvTQ8PzAilDveOxNjSfr0CHfIQfFk8g==", - "path": "system.runtime.extensions/4.3.0", - "hashPath": "system.runtime.extensions.4.3.0.nupkg.sha512" - }, - "System.Runtime.Handles/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-OKiSUN7DmTWeYb3l51A7EYaeNMnvxwE249YtZz7yooT4gOZhmTjIn48KgSsw2k2lYdLgTKNJw/ZIfSElwDRVgg==", - "path": "system.runtime.handles/4.3.0", - "hashPath": "system.runtime.handles.4.3.0.nupkg.sha512" - }, - "System.Runtime.InteropServices/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-uv1ynXqiMK8mp1GM3jDqPCFN66eJ5w5XNomaK2XD+TuCroNTLFGeZ+WCmBMcBDyTFKou3P6cR6J/QsaqDp7fGQ==", - "path": "system.runtime.interopservices/4.3.0", - "hashPath": "system.runtime.interopservices.4.3.0.nupkg.sha512" - }, - "System.Runtime.InteropServices.RuntimeInformation/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-cbz4YJMqRDR7oLeMRbdYv7mYzc++17lNhScCX0goO2XpGWdvAt60CGN+FHdePUEHCe/Jy9jUlvNAiNdM+7jsOw==", - "path": "system.runtime.interopservices.runtimeinformation/4.3.0", - "hashPath": "system.runtime.interopservices.runtimeinformation.4.3.0.nupkg.sha512" - }, - "System.Runtime.Numerics/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-yMH+MfdzHjy17l2KESnPiF2dwq7T+xLnSJar7slyimAkUh/gTrS9/UQOtv7xarskJ2/XDSNvfLGOBQPjL7PaHQ==", - "path": "system.runtime.numerics/4.3.0", - "hashPath": "system.runtime.numerics.4.3.0.nupkg.sha512" - }, - "System.Runtime.Serialization.Primitives/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-Wz+0KOukJGAlXjtKr+5Xpuxf8+c8739RI1C+A2BoQZT+wMCCoMDDdO8/4IRHfaVINqL78GO8dW8G2lW/e45Mcw==", - "path": "system.runtime.serialization.primitives/4.3.0", - "hashPath": "system.runtime.serialization.primitives.4.3.0.nupkg.sha512" - }, - "System.Runtime.Serialization.Xml/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-nUQx/5OVgrqEba3+j7OdiofvVq9koWZAC7Z3xGI8IIViZqApWnZ5+lLcwYgTlbkobrl/Rat+Jb8GeD4WQESD2A==", - "path": "system.runtime.serialization.xml/4.3.0", - "hashPath": "system.runtime.serialization.xml.4.3.0.nupkg.sha512" - }, - "System.Security.Claims/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-P/+BR/2lnc4PNDHt/TPBAWHVMLMRHsyYZbU1NphW4HIWzCggz8mJbTQQ3MKljFE7LS3WagmVFuBgoLcFzYXlkA==", - "path": "system.security.claims/4.3.0", - "hashPath": "system.security.claims.4.3.0.nupkg.sha512" - }, - "System.Security.Cryptography.Algorithms/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-W1kd2Y8mYSCgc3ULTAZ0hOP2dSdG5YauTb1089T0/kRcN2MpSAW1izOFROrJgxSlMn3ArsgHXagigyi+ibhevg==", - "path": "system.security.cryptography.algorithms/4.3.0", - "hashPath": "system.security.cryptography.algorithms.4.3.0.nupkg.sha512" - }, - "System.Security.Cryptography.Cng/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-03idZOqFlsKRL4W+LuCpJ6dBYDUWReug6lZjBa3uJWnk5sPCUXckocevTaUA8iT/MFSrY/2HXkOt753xQ/cf8g==", - "path": "system.security.cryptography.cng/4.3.0", - "hashPath": "system.security.cryptography.cng.4.3.0.nupkg.sha512" - }, - "System.Security.Cryptography.Csp/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-X4s/FCkEUnRGnwR3aSfVIkldBmtURMhmexALNTwpjklzxWU7yjMk7GHLKOZTNkgnWnE0q7+BCf9N2LVRWxewaA==", - "path": "system.security.cryptography.csp/4.3.0", - "hashPath": "system.security.cryptography.csp.4.3.0.nupkg.sha512" - }, - "System.Security.Cryptography.Encoding/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-1DEWjZZly9ae9C79vFwqaO5kaOlI5q+3/55ohmq/7dpDyDfc8lYe7YVxJUZ5MF/NtbkRjwFRo14yM4OEo9EmDw==", - "path": "system.security.cryptography.encoding/4.3.0", - "hashPath": "system.security.cryptography.encoding.4.3.0.nupkg.sha512" - }, - "System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-h4CEgOgv5PKVF/HwaHzJRiVboL2THYCou97zpmhjghx5frc7fIvlkY1jL+lnIQyChrJDMNEXS6r7byGif8Cy4w==", - "path": "system.security.cryptography.openssl/4.3.0", - "hashPath": "system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "System.Security.Cryptography.Primitives/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-7bDIyVFNL/xKeFHjhobUAQqSpJq9YTOpbEs6mR233Et01STBMXNAc/V+BM6dwYGc95gVh/Zf+iVXWzj3mE8DWg==", - "path": "system.security.cryptography.primitives/4.3.0", - "hashPath": "system.security.cryptography.primitives.4.3.0.nupkg.sha512" - }, - "System.Security.Cryptography.X509Certificates/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-t2Tmu6Y2NtJ2um0RtcuhP7ZdNNxXEgUm2JeoA/0NvlMjAhKCnM1NX07TDl3244mVp3QU6LPEhT3HTtH1uF7IYw==", - "path": "system.security.cryptography.x509certificates/4.3.0", - "hashPath": "system.security.cryptography.x509certificates.4.3.0.nupkg.sha512" - }, - "System.Security.Principal/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-I1tkfQlAoMM2URscUtpcRo/hX0jinXx6a/KUtEQoz3owaYwl3qwsO8cbzYVVnjxrzxjHo3nJC+62uolgeGIS9A==", - "path": "system.security.principal/4.3.0", - "hashPath": "system.security.principal.4.3.0.nupkg.sha512" - }, - "System.Security.Principal.Windows/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-HVL1rvqYtnRCxFsYag/2le/ZfKLK4yMw79+s6FmKXbSCNN0JeAhrYxnRAHFoWRa0dEojsDcbBSpH3l22QxAVyw==", - "path": "system.security.principal.windows/4.3.0", - "hashPath": "system.security.principal.windows.4.3.0.nupkg.sha512" - }, - "System.ServiceModel.Primitives/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-7jbo99dT5rmefyfCsQKIWznK/1XaYI3hiIJJmP+fdwucrSESWTSPDxBJrqE42VFsN1wgRhDLiEA78FTUc4jS8g==", - "path": "system.servicemodel.primitives/4.3.0", - "hashPath": "system.servicemodel.primitives.4.3.0.nupkg.sha512" - }, - "System.Text.Encoding/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==", - "path": "system.text.encoding/4.3.0", - "hashPath": "system.text.encoding.4.3.0.nupkg.sha512" - }, - "System.Text.Encoding.Extensions/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-YVMK0Bt/A43RmwizJoZ22ei2nmrhobgeiYwFzC4YAN+nue8RF6djXDMog0UCn+brerQoYVyaS+ghy9P/MUVcmw==", - "path": "system.text.encoding.extensions/4.3.0", - "hashPath": "system.text.encoding.extensions.4.3.0.nupkg.sha512" - }, - "System.Text.RegularExpressions/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-RpT2DA+L660cBt1FssIE9CAGpLFdFPuheB7pLpKpn6ZXNby7jDERe8Ua/Ne2xGiwLVG2JOqziiaVCGDon5sKFA==", - "path": "system.text.regularexpressions/4.3.0", - "hashPath": "system.text.regularexpressions.4.3.0.nupkg.sha512" - }, - "System.Threading/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-VkUS0kOBcUf3Wwm0TSbrevDDZ6BlM+b/HRiapRFWjM5O0NS0LviG0glKmFK+hhPDd1XFeSdU1GmlLhb2CoVpIw==", - "path": "system.threading/4.3.0", - "hashPath": "system.threading.4.3.0.nupkg.sha512" - }, - "System.Threading.Tasks/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-LbSxKEdOUhVe8BezB/9uOGGppt+nZf6e1VFyw6v3DN6lqitm0OSn2uXMOdtP0M3W4iMcqcivm2J6UgqiwwnXiA==", - "path": "system.threading.tasks/4.3.0", - "hashPath": "system.threading.tasks.4.3.0.nupkg.sha512" - }, - "System.Threading.Tasks.Extensions/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-npvJkVKl5rKXrtl1Kkm6OhOUaYGEiF9wFbppFRWSMoApKzt2PiPHT2Bb8a5sAWxprvdOAtvaARS9QYMznEUtug==", - "path": "system.threading.tasks.extensions/4.3.0", - "hashPath": "system.threading.tasks.extensions.4.3.0.nupkg.sha512" - }, - "System.Threading.ThreadPool/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-k/+g4b7vjdd4aix83sTgC9VG6oXYKAktSfNIJUNGxPEj7ryEOfzHHhfnmsZvjxawwcD9HyWXKCXmPjX8U4zeSw==", - "path": "system.threading.threadpool/4.3.0", - "hashPath": "system.threading.threadpool.4.3.0.nupkg.sha512" - }, - "System.Threading.Timer/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-Z6YfyYTCg7lOZjJzBjONJTFKGN9/NIYKSxhU5GRd+DTwHSZyvWp1xuI5aR+dLg+ayyC5Xv57KiY4oJ0tMO89fQ==", - "path": "system.threading.timer/4.3.0", - "hashPath": "system.threading.timer.4.3.0.nupkg.sha512" - }, - "System.Xml.ReaderWriter/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-GrprA+Z0RUXaR4N7/eW71j1rgMnEnEVlgii49GZyAjTH7uliMnrOU3HNFBr6fEDBCJCIdlVNq9hHbaDR621XBA==", - "path": "system.xml.readerwriter/4.3.0", - "hashPath": "system.xml.readerwriter.4.3.0.nupkg.sha512" - }, - "System.Xml.XDocument/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-5zJ0XDxAIg8iy+t4aMnQAu0MqVbqyvfoUVl1yDV61xdo3Vth45oA2FoY4pPkxYAH5f8ixpmTqXeEIya95x0aCQ==", - "path": "system.xml.xdocument/4.3.0", - "hashPath": "system.xml.xdocument.4.3.0.nupkg.sha512" - }, - "System.Xml.XmlDocument/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-lJ8AxvkX7GQxpC6GFCeBj8ThYVyQczx2+f/cWHJU8tjS7YfI6Cv6bon70jVEgs2CiFbmmM8b9j1oZVx0dSI2Ww==", - "path": "system.xml.xmldocument/4.3.0", - "hashPath": "system.xml.xmldocument.4.3.0.nupkg.sha512" - }, - "System.Xml.XmlSerializer/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-MYoTCP7EZ98RrANESW05J5ZwskKDoN0AuZ06ZflnowE50LTpbR5yRg3tHckTVm5j/m47stuGgCrCHWePyHS70Q==", - "path": "system.xml.xmlserializer/4.3.0", - "hashPath": "system.xml.xmlserializer.4.3.0.nupkg.sha512" - }, - "ServiceStack.Interfaces/1.0.0": { - "type": "project", - "serviceable": false, - "sha512": "" - }, - "ServiceStack.Text/5.0.0.0": { - "type": "reference", - "serviceable": false, - "sha512": "" - } - } -} \ No newline at end of file diff --git a/lib/netstandard2.0/ServiceStack.Client.dll b/lib/netstandard2.0/ServiceStack.Client.dll deleted file mode 100644 index fdbaf14a3..000000000 Binary files a/lib/netstandard2.0/ServiceStack.Client.dll and /dev/null differ diff --git a/lib/netstandard2.0/ServiceStack.Common.deps.json b/lib/netstandard2.0/ServiceStack.Common.deps.json deleted file mode 100644 index bfd6ec14d..000000000 --- a/lib/netstandard2.0/ServiceStack.Common.deps.json +++ /dev/null @@ -1,1322 +0,0 @@ -{ - "runtimeTarget": { - "name": ".NETStandard,Version=v2.0/", - "signature": "f819a3335cbb393ad3e29b137d855b47536c3d9b" - }, - "compilationOptions": {}, - "targets": { - ".NETStandard,Version=v2.0": {}, - ".NETStandard,Version=v2.0/": { - "ServiceStack.Common/1.0.0": { - "dependencies": { - "Microsoft.Extensions.Primitives": "2.0.0", - "NETStandard.Library": "2.0.0", - "ServiceStack.Interfaces": "1.0.0", - "System.ComponentModel.Primitives": "4.3.0", - "System.Data.Common": "4.3.0", - "System.Dynamic.Runtime": "4.3.0", - "System.Net.NetworkInformation": "4.3.0", - "System.Net.Requests": "4.3.0", - "System.Reflection.TypeExtensions": "4.3.0", - "System.Runtime.Serialization.Primitives": "4.3.0", - "ServiceStack.Text": "5.0.0.0" - }, - "runtime": { - "ServiceStack.Common.dll": {} - } - }, - "Microsoft.Extensions.Primitives/2.0.0": { - "dependencies": { - "System.Runtime.CompilerServices.Unsafe": "4.4.0" - }, - "runtime": { - "lib/netstandard2.0/Microsoft.Extensions.Primitives.dll": {} - } - }, - "Microsoft.NETCore.Platforms/1.1.0": {}, - "Microsoft.NETCore.Targets/1.1.0": {}, - "Microsoft.Win32.Primitives/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "NETStandard.Library/2.0.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0" - } - }, - "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {}, - "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {}, - "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {}, - "runtime.native.System/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0" - } - }, - "runtime.native.System.Net.Http/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0" - } - }, - "runtime.native.System.Security.Cryptography.Apple/4.3.0": { - "dependencies": { - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "4.3.0" - } - }, - "runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "dependencies": { - "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {}, - "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {}, - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple/4.3.0": {}, - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {}, - "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {}, - "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {}, - "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {}, - "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {}, - "System.Collections/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Collections.Concurrent/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Diagnostics.Tracing": "4.3.0", - "System.Globalization": "4.3.0", - "System.Reflection": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Collections.Concurrent.dll": {} - } - }, - "System.ComponentModel/4.3.0": { - "dependencies": { - "System.Runtime": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.ComponentModel.dll": {} - } - }, - "System.ComponentModel.Primitives/4.3.0": { - "dependencies": { - "System.ComponentModel": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0" - }, - "runtime": { - "lib/netstandard1.0/System.ComponentModel.Primitives.dll": {} - } - }, - "System.Data.Common/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Text.RegularExpressions": "4.3.0", - "System.Threading.Tasks": "4.3.0" - }, - "runtime": { - "lib/netstandard1.2/System.Data.Common.dll": {} - } - }, - "System.Diagnostics.Debug/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Diagnostics.DiagnosticSource/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Tracing": "4.3.0", - "System.Reflection": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Diagnostics.DiagnosticSource.dll": {} - } - }, - "System.Diagnostics.Tracing/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Dynamic.Runtime/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Linq": "4.3.0", - "System.Linq.Expressions": "4.3.0", - "System.ObjectModel": "4.3.0", - "System.Reflection": "4.3.0", - "System.Reflection.Emit": "4.3.0", - "System.Reflection.Emit.ILGeneration": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Reflection.TypeExtensions": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Threading": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Dynamic.Runtime.dll": {} - } - }, - "System.Globalization/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Globalization.Calendars/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Globalization": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Globalization.Extensions/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Globalization": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.InteropServices": "4.3.0" - } - }, - "System.IO/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } - }, - "System.IO.FileSystem/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.IO": "4.3.0", - "System.IO.FileSystem.Primitives": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } - }, - "System.IO.FileSystem.Primitives/4.3.0": { - "dependencies": { - "System.Runtime": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.IO.FileSystem.Primitives.dll": {} - } - }, - "System.Linq/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0" - }, - "runtime": { - "lib/netstandard1.6/System.Linq.dll": {} - } - }, - "System.Linq.Expressions/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Linq": "4.3.0", - "System.ObjectModel": "4.3.0", - "System.Reflection": "4.3.0", - "System.Reflection.Emit": "4.3.0", - "System.Reflection.Emit.ILGeneration": "4.3.0", - "System.Reflection.Emit.Lightweight": "4.3.0", - "System.Reflection.Extensions": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Reflection.TypeExtensions": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Threading": "4.3.0" - }, - "runtime": { - "lib/netstandard1.6/System.Linq.Expressions.dll": {} - } - }, - "System.Net.Http/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Diagnostics.DiagnosticSource": "4.3.0", - "System.Diagnostics.Tracing": "4.3.0", - "System.Globalization": "4.3.0", - "System.Globalization.Extensions": "4.3.0", - "System.IO": "4.3.0", - "System.IO.FileSystem": "4.3.0", - "System.Net.Primitives": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.OpenSsl": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Security.Cryptography.X509Certificates": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0", - "runtime.native.System": "4.3.0", - "runtime.native.System.Net.Http": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "System.Net.NetworkInformation/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.Win32.Primitives": "4.3.0", - "System.Collections": "4.3.0", - "System.Diagnostics.Tracing": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.IO.FileSystem": "4.3.0", - "System.IO.FileSystem.Primitives": "4.3.0", - "System.Linq": "4.3.0", - "System.Net.Primitives": "4.3.0", - "System.Net.Sockets": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Principal.Windows": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Overlapped": "4.3.0", - "System.Threading.Tasks": "4.3.0", - "System.Threading.Thread": "4.3.0", - "System.Threading.ThreadPool": "4.3.0", - "runtime.native.System": "4.3.0" - } - }, - "System.Net.Primitives/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0", - "System.Runtime.Handles": "4.3.0" - } - }, - "System.Net.Requests/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Diagnostics.Tracing": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Net.Http": "4.3.0", - "System.Net.Primitives": "4.3.0", - "System.Net.WebHeaderCollection": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } - }, - "System.Net.Sockets/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.IO": "4.3.0", - "System.Net.Primitives": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } - }, - "System.Net.WebHeaderCollection/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Net.WebHeaderCollection.dll": {} - } - }, - "System.ObjectModel/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.ObjectModel.dll": {} - } - }, - "System.Reflection/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.IO": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Reflection.Emit/4.3.0": { - "dependencies": { - "System.IO": "4.3.0", - "System.Reflection": "4.3.0", - "System.Reflection.Emit.ILGeneration": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Reflection.Emit.dll": {} - } - }, - "System.Reflection.Emit.ILGeneration/4.3.0": { - "dependencies": { - "System.Reflection": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Reflection.Emit.ILGeneration.dll": {} - } - }, - "System.Reflection.Emit.Lightweight/4.3.0": { - "dependencies": { - "System.Reflection": "4.3.0", - "System.Reflection.Emit.ILGeneration": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Reflection.Emit.Lightweight.dll": {} - } - }, - "System.Reflection.Extensions/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Reflection": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Reflection.Primitives/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Reflection.TypeExtensions/4.3.0": { - "dependencies": { - "System.Reflection": "4.3.0", - "System.Runtime": "4.3.0" - }, - "runtime": { - "lib/netstandard1.5/System.Reflection.TypeExtensions.dll": {} - } - }, - "System.Resources.ResourceManager/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Globalization": "4.3.0", - "System.Reflection": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Runtime/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0" - } - }, - "System.Runtime.CompilerServices.Unsafe/4.4.0": { - "runtime": { - "lib/netstandard2.0/System.Runtime.CompilerServices.Unsafe.dll": {} - } - }, - "System.Runtime.Extensions/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Runtime.Handles/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Runtime.InteropServices/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Reflection": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Handles": "4.3.0" - } - }, - "System.Runtime.Numerics/4.3.0": { - "dependencies": { - "System.Globalization": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Runtime.Numerics.dll": {} - } - }, - "System.Runtime.Serialization.Primitives/4.3.0": { - "dependencies": { - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Runtime.Serialization.Primitives.dll": {} - } - }, - "System.Security.Claims/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Security.Principal": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Security.Claims.dll": {} - } - }, - "System.Security.Cryptography.Algorithms/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Collections": "4.3.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Runtime.Numerics": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "runtime.native.System.Security.Cryptography.Apple": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "System.Security.Cryptography.Cng/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0" - } - }, - "System.Security.Cryptography.Csp/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.IO": "4.3.0", - "System.Reflection": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0" - } - }, - "System.Security.Cryptography.Encoding/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Collections": "4.3.0", - "System.Collections.Concurrent": "4.3.0", - "System.Linq": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "System.Security.Cryptography.OpenSsl/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Runtime.Numerics": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "System.Security.Cryptography.Primitives/4.3.0": { - "dependencies": { - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Security.Cryptography.Primitives.dll": {} - } - }, - "System.Security.Cryptography.X509Certificates/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.Globalization.Calendars": "4.3.0", - "System.IO": "4.3.0", - "System.IO.FileSystem": "4.3.0", - "System.IO.FileSystem.Primitives": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Runtime.Numerics": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Cng": "4.3.0", - "System.Security.Cryptography.Csp": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.OpenSsl": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0", - "runtime.native.System": "4.3.0", - "runtime.native.System.Net.Http": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "System.Security.Principal/4.3.0": { - "dependencies": { - "System.Runtime": "4.3.0" - }, - "runtime": { - "lib/netstandard1.0/System.Security.Principal.dll": {} - } - }, - "System.Security.Principal.Windows/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.Win32.Primitives": "4.3.0", - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Reflection": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Claims": "4.3.0", - "System.Security.Principal": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0" - } - }, - "System.Text.Encoding/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Text.RegularExpressions/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Globalization": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Threading": "4.3.0" - }, - "runtime": { - "lib/netstandard1.6/System.Text.RegularExpressions.dll": {} - } - }, - "System.Threading/4.3.0": { - "dependencies": { - "System.Runtime": "4.3.0", - "System.Threading.Tasks": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Threading.dll": {} - } - }, - "System.Threading.Overlapped/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Handles": "4.3.0" - } - }, - "System.Threading.Tasks/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Threading.Thread/4.3.0": { - "dependencies": { - "System.Runtime": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Threading.Thread.dll": {} - } - }, - "System.Threading.ThreadPool/4.3.0": { - "dependencies": { - "System.Runtime": "4.3.0", - "System.Runtime.Handles": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Threading.ThreadPool.dll": {} - } - }, - "ServiceStack.Interfaces/1.0.0": { - "dependencies": { - "System.Runtime.Serialization.Primitives": "4.3.0" - }, - "runtime": { - "ServiceStack.Interfaces.dll": {} - } - }, - "ServiceStack.Text/5.0.0.0": { - "runtime": { - "ServiceStack.Text.dll": {} - } - } - } - }, - "libraries": { - "ServiceStack.Common/1.0.0": { - "type": "project", - "serviceable": false, - "sha512": "" - }, - "Microsoft.Extensions.Primitives/2.0.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-ukg53qNlqTrK38WA30b5qhw0GD7y3jdI9PHHASjdKyTcBHTevFM2o23tyk3pWCgAV27Bbkm+CPQ2zUe1ZOuYSA==", - "path": "microsoft.extensions.primitives/2.0.0", - "hashPath": "microsoft.extensions.primitives.2.0.0.nupkg.sha512" - }, - "Microsoft.NETCore.Platforms/1.1.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==", - "path": "microsoft.netcore.platforms/1.1.0", - "hashPath": "microsoft.netcore.platforms.1.1.0.nupkg.sha512" - }, - "Microsoft.NETCore.Targets/1.1.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-aOZA3BWfz9RXjpzt0sRJJMjAscAUm3Hoa4UWAfceV9UTYxgwZ1lZt5nO2myFf+/jetYQo4uTP7zS8sJY67BBxg==", - "path": "microsoft.netcore.targets/1.1.0", - "hashPath": "microsoft.netcore.targets.1.1.0.nupkg.sha512" - }, - "Microsoft.Win32.Primitives/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-9ZQKCWxH7Ijp9BfahvL2Zyf1cJIk8XYLF6Yjzr2yi0b2cOut/HQ31qf1ThHAgCc3WiZMdnWcfJCgN82/0UunxA==", - "path": "microsoft.win32.primitives/4.3.0", - "hashPath": "microsoft.win32.primitives.4.3.0.nupkg.sha512" - }, - "NETStandard.Library/2.0.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-7jnbRU+L08FXKMxqUflxEXtVymWvNOrS8yHgu9s6EM8Anr6T/wIX4nZ08j/u3Asz+tCufp3YVwFSEvFTPYmBPA==", - "path": "netstandard.library/2.0.0", - "hashPath": "netstandard.library.2.0.0.nupkg.sha512" - }, - "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-HdSSp5MnJSsg08KMfZThpuLPJpPwE5hBXvHwoKWosyHHfe8Mh5WKT0ylEOf6yNzX6Ngjxe4Whkafh5q7Ymac4Q==", - "path": "runtime.debian.8-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.debian.8-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-+yH1a49wJMy8Zt4yx5RhJrxO/DBDByAiCzNwiETI+1S4mPdCu0OY4djdciC7Vssk0l22wQaDLrXxXkp+3+7bVA==", - "path": "runtime.fedora.23-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.fedora.23-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-c3YNH1GQJbfIPJeCnr4avseugSqPrxwIqzthYyZDN6EuOyNOzq+y2KSUfRcXauya1sF4foESTgwM5e1A8arAKw==", - "path": "runtime.fedora.24-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.fedora.24-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.native.System/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-c/qWt2LieNZIj1jGnVNsE2Kl23Ya2aSTBuXMD6V7k9KWr6l16Tqdwq+hJScEpWER9753NWC8h96PaVNY5Ld7Jw==", - "path": "runtime.native.system/4.3.0", - "hashPath": "runtime.native.system.4.3.0.nupkg.sha512" - }, - "runtime.native.System.Net.Http/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-ZVuZJqnnegJhd2k/PtAbbIcZ3aZeITq3sj06oKfMBSfphW3HDmk/t4ObvbOk/JA/swGR0LNqMksAh/f7gpTROg==", - "path": "runtime.native.system.net.http/4.3.0", - "hashPath": "runtime.native.system.net.http.4.3.0.nupkg.sha512" - }, - "runtime.native.System.Security.Cryptography.Apple/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-DloMk88juo0OuOWr56QG7MNchmafTLYWvABy36izkrLI5VledI0rq28KGs1i9wbpeT9NPQrx/wTf8U2vazqQ3Q==", - "path": "runtime.native.system.security.cryptography.apple/4.3.0", - "hashPath": "runtime.native.system.security.cryptography.apple.4.3.0.nupkg.sha512" - }, - "runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-NS1U+700m4KFRHR5o4vo9DSlTmlCKu/u7dtE5sUHVIPB+xpXxYQvgBgA6wEIeCz6Yfn0Z52/72WYsToCEPJnrw==", - "path": "runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-b3pthNgxxFcD+Pc0WSEoC0+md3MyhRS6aCEeenvNE3Fdw1HyJ18ZhRFVJJzIeR/O/jpxPboB805Ho0T3Ul7w8A==", - "path": "runtime.opensuse.13.2-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.opensuse.13.2-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-KeLz4HClKf+nFS7p/6Fi/CqyLXh81FpiGzcmuS8DGi9lUqSnZ6Es23/gv2O+1XVGfrbNmviF7CckBpavkBoIFQ==", - "path": "runtime.opensuse.42.1-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.opensuse.42.1-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-kVXCuMTrTlxq4XOOMAysuNwsXWpYeboGddNGpIgNSZmv1b6r/s/DPk0fYMB7Q5Qo4bY68o48jt4T4y5BVecbCQ==", - "path": "runtime.osx.10.10-x64.runtime.native.system.security.cryptography.apple/4.3.0", - "hashPath": "runtime.osx.10.10-x64.runtime.native.system.security.cryptography.apple.4.3.0.nupkg.sha512" - }, - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-X7IdhILzr4ROXd8mI1BUCQMSHSQwelUlBjF1JyTKCjXaOGn2fB4EKBxQbCK2VjO3WaWIdlXZL3W6TiIVnrhX4g==", - "path": "runtime.osx.10.10-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.osx.10.10-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-nyFNiCk/r+VOiIqreLix8yN+q3Wga9+SE8BCgkf+2BwEKiNx6DyvFjCgkfV743/grxv8jHJ8gUK4XEQw7yzRYg==", - "path": "runtime.rhel.7-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.rhel.7-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-ytoewC6wGorL7KoCAvRfsgoJPJbNq+64k2SqW6JcOAebWsFUvCCYgfzQMrnpvPiEl4OrblUlhF2ji+Q1+SVLrQ==", - "path": "runtime.ubuntu.14.04-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.ubuntu.14.04-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-I8bKw2I8k58Wx7fMKQJn2R8lamboCAiHfHeV/pS65ScKWMMI0+wJkLYlEKvgW1D/XvSl/221clBoR2q9QNNM7A==", - "path": "runtime.ubuntu.16.04-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.ubuntu.16.04-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-VB5cn/7OzUfzdnC8tqAIMQciVLiq2epm2NrAm1E9OjNRyG4lVhfR61SMcLizejzQP8R8Uf/0l5qOIbUEi+RdEg==", - "path": "runtime.ubuntu.16.10-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.ubuntu.16.10-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "System.Collections/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-3Dcj85/TBdVpL5Zr+gEEBUuFe2icOnLalmEh9hfck1PTYbbyWuZgh4fmm2ysCLTrqLQw6t3TgTyJ+VLp+Qb+Lw==", - "path": "system.collections/4.3.0", - "hashPath": "system.collections.4.3.0.nupkg.sha512" - }, - "System.Collections.Concurrent/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-ztl69Xp0Y/UXCL+3v3tEU+lIy+bvjKNUmopn1wep/a291pVPK7dxBd6T7WnlQqRog+d1a/hSsgRsmFnIBKTPLQ==", - "path": "system.collections.concurrent/4.3.0", - "hashPath": "system.collections.concurrent.4.3.0.nupkg.sha512" - }, - "System.ComponentModel/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-VyGn1jGRZVfxnh8EdvDCi71v3bMXrsu8aYJOwoV7SNDLVhiEqwP86pPMyRGsDsxhXAm2b3o9OIqeETfN5qfezw==", - "path": "system.componentmodel/4.3.0", - "hashPath": "system.componentmodel.4.3.0.nupkg.sha512" - }, - "System.ComponentModel.Primitives/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-j8GUkCpM8V4d4vhLIIoBLGey2Z5bCkMVNjEZseyAlm4n5arcsJOeI3zkUP+zvZgzsbLTYh4lYeP/ZD/gdIAPrw==", - "path": "system.componentmodel.primitives/4.3.0", - "hashPath": "system.componentmodel.primitives.4.3.0.nupkg.sha512" - }, - "System.Data.Common/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-lm6E3T5u7BOuEH0u18JpbJHxBfOJPuCyl4Kg1RH10ktYLp5uEEE1xKrHW56/We4SnZpGAuCc9N0MJpSDhTHZGQ==", - "path": "system.data.common/4.3.0", - "hashPath": "system.data.common.4.3.0.nupkg.sha512" - }, - "System.Diagnostics.Debug/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-ZUhUOdqmaG5Jk3Xdb8xi5kIyQYAA4PnTNlHx1mu9ZY3qv4ELIdKbnL/akbGaKi2RnNUWaZsAs31rvzFdewTj2g==", - "path": "system.diagnostics.debug/4.3.0", - "hashPath": "system.diagnostics.debug.4.3.0.nupkg.sha512" - }, - "System.Diagnostics.DiagnosticSource/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-tD6kosZnTAGdrEa0tZSuFyunMbt/5KYDnHdndJYGqZoNy00XVXyACd5d6KnE1YgYv3ne2CjtAfNXo/fwEhnKUA==", - "path": "system.diagnostics.diagnosticsource/4.3.0", - "hashPath": "system.diagnostics.diagnosticsource.4.3.0.nupkg.sha512" - }, - "System.Diagnostics.Tracing/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-rswfv0f/Cqkh78rA5S8eN8Neocz234+emGCtTF3lxPY96F+mmmUen6tbn0glN6PMvlKQb9bPAY5e9u7fgPTkKw==", - "path": "system.diagnostics.tracing/4.3.0", - "hashPath": "system.diagnostics.tracing.4.3.0.nupkg.sha512" - }, - "System.Dynamic.Runtime/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-SNVi1E/vfWUAs/WYKhE9+qlS6KqK0YVhnlT0HQtr8pMIA8YX3lwy3uPMownDwdYISBdmAF/2holEIldVp85Wag==", - "path": "system.dynamic.runtime/4.3.0", - "hashPath": "system.dynamic.runtime.4.3.0.nupkg.sha512" - }, - "System.Globalization/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-kYdVd2f2PAdFGblzFswE4hkNANJBKRmsfa2X5LG2AcWE1c7/4t0pYae1L8vfZ5xvE2nK/R9JprtToA61OSHWIg==", - "path": "system.globalization/4.3.0", - "hashPath": "system.globalization.4.3.0.nupkg.sha512" - }, - "System.Globalization.Calendars/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-GUlBtdOWT4LTV3I+9/PJW+56AnnChTaOqqTLFtdmype/L500M2LIyXgmtd9X2P2VOkmJd5c67H5SaC2QcL1bFA==", - "path": "system.globalization.calendars/4.3.0", - "hashPath": "system.globalization.calendars.4.3.0.nupkg.sha512" - }, - "System.Globalization.Extensions/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-FhKmdR6MPG+pxow6wGtNAWdZh7noIOpdD5TwQ3CprzgIE1bBBoim0vbR1+AWsWjQmU7zXHgQo4TWSP6lCeiWcQ==", - "path": "system.globalization.extensions/4.3.0", - "hashPath": "system.globalization.extensions.4.3.0.nupkg.sha512" - }, - "System.IO/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg==", - "path": "system.io/4.3.0", - "hashPath": "system.io.4.3.0.nupkg.sha512" - }, - "System.IO.FileSystem/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-3wEMARTnuio+ulnvi+hkRNROYwa1kylvYahhcLk4HSoVdl+xxTFVeVlYOfLwrDPImGls0mDqbMhrza8qnWPTdA==", - "path": "system.io.filesystem/4.3.0", - "hashPath": "system.io.filesystem.4.3.0.nupkg.sha512" - }, - "System.IO.FileSystem.Primitives/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-6QOb2XFLch7bEc4lIcJH49nJN2HV+OC3fHDgsLVsBVBk3Y4hFAnOBGzJ2lUu7CyDDFo9IBWkSsnbkT6IBwwiMw==", - "path": "system.io.filesystem.primitives/4.3.0", - "hashPath": "system.io.filesystem.primitives.4.3.0.nupkg.sha512" - }, - "System.Linq/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-5DbqIUpsDp0dFftytzuMmc0oeMdQwjcP/EWxsksIz/w1TcFRkZ3yKKz0PqiYFMmEwPSWw+qNVqD7PJ889JzHbw==", - "path": "system.linq/4.3.0", - "hashPath": "system.linq.4.3.0.nupkg.sha512" - }, - "System.Linq.Expressions/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-PGKkrd2khG4CnlyJwxwwaWWiSiWFNBGlgXvJpeO0xCXrZ89ODrQ6tjEWS/kOqZ8GwEOUATtKtzp1eRgmYNfclg==", - "path": "system.linq.expressions/4.3.0", - "hashPath": "system.linq.expressions.4.3.0.nupkg.sha512" - }, - "System.Net.Http/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-sYg+FtILtRQuYWSIAuNOELwVuVsxVyJGWQyOnlAzhV4xvhyFnON1bAzYYC+jjRW8JREM45R0R5Dgi8MTC5sEwA==", - "path": "system.net.http/4.3.0", - "hashPath": "system.net.http.4.3.0.nupkg.sha512" - }, - "System.Net.NetworkInformation/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-zNVmWVry0pAu7lcrRBhwwU96WUdbsrGL3azyzsbXmVNptae1+Za+UgOe9Z6s8iaWhPn7/l4wQqhC56HZWq7tkg==", - "path": "system.net.networkinformation/4.3.0", - "hashPath": "system.net.networkinformation.4.3.0.nupkg.sha512" - }, - "System.Net.Primitives/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-qOu+hDwFwoZPbzPvwut2qATe3ygjeQBDQj91xlsaqGFQUI5i4ZnZb8yyQuLGpDGivEPIt8EJkd1BVzVoP31FXA==", - "path": "system.net.primitives/4.3.0", - "hashPath": "system.net.primitives.4.3.0.nupkg.sha512" - }, - "System.Net.Requests/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-OZNUuAs0kDXUzm7U5NZ1ojVta5YFZmgT2yxBqsQ7Eseq5Ahz88LInGRuNLJ/NP2F8W1q7tse1pKDthj3reF5QA==", - "path": "system.net.requests/4.3.0", - "hashPath": "system.net.requests.4.3.0.nupkg.sha512" - }, - "System.Net.Sockets/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-m6icV6TqQOAdgt5N/9I5KNpjom/5NFtkmGseEH+AK/hny8XrytLH3+b5M8zL/Ycg3fhIocFpUMyl/wpFnVRvdw==", - "path": "system.net.sockets/4.3.0", - "hashPath": "system.net.sockets.4.3.0.nupkg.sha512" - }, - "System.Net.WebHeaderCollection/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-XZrXYG3c7QV/GpWeoaRC02rM6LH2JJetfVYskf35wdC/w2fFDFMphec4gmVH2dkll6abtW14u9Rt96pxd9YH2A==", - "path": "system.net.webheadercollection/4.3.0", - "hashPath": "system.net.webheadercollection.4.3.0.nupkg.sha512" - }, - "System.ObjectModel/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-bdX+80eKv9bN6K4N+d77OankKHGn6CH711a6fcOpMQu2Fckp/Ft4L/kW9WznHpyR0NRAvJutzOMHNNlBGvxQzQ==", - "path": "system.objectmodel/4.3.0", - "hashPath": "system.objectmodel.4.3.0.nupkg.sha512" - }, - "System.Reflection/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-KMiAFoW7MfJGa9nDFNcfu+FpEdiHpWgTcS2HdMpDvt9saK3y/G4GwprPyzqjFH9NTaGPQeWNHU+iDlDILj96aQ==", - "path": "system.reflection/4.3.0", - "hashPath": "system.reflection.4.3.0.nupkg.sha512" - }, - "System.Reflection.Emit/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-228FG0jLcIwTVJyz8CLFKueVqQK36ANazUManGaJHkO0icjiIypKW7YLWLIWahyIkdh5M7mV2dJepllLyA1SKg==", - "path": "system.reflection.emit/4.3.0", - "hashPath": "system.reflection.emit.4.3.0.nupkg.sha512" - }, - "System.Reflection.Emit.ILGeneration/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-59tBslAk9733NXLrUJrwNZEzbMAcu8k344OYo+wfSVygcgZ9lgBdGIzH/nrg3LYhXceynyvTc8t5/GD4Ri0/ng==", - "path": "system.reflection.emit.ilgeneration/4.3.0", - "hashPath": "system.reflection.emit.ilgeneration.4.3.0.nupkg.sha512" - }, - "System.Reflection.Emit.Lightweight/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-oadVHGSMsTmZsAF864QYN1t1QzZjIcuKU3l2S9cZOwDdDueNTrqq1yRj7koFfIGEnKpt6NjpL3rOzRhs4ryOgA==", - "path": "system.reflection.emit.lightweight/4.3.0", - "hashPath": "system.reflection.emit.lightweight.4.3.0.nupkg.sha512" - }, - "System.Reflection.Extensions/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-rJkrJD3kBI5B712aRu4DpSIiHRtr6QlfZSQsb0hYHrDCZORXCFjQfoipo2LaMUHoT9i1B7j7MnfaEKWDFmFQNQ==", - "path": "system.reflection.extensions/4.3.0", - "hashPath": "system.reflection.extensions.4.3.0.nupkg.sha512" - }, - "System.Reflection.Primitives/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-5RXItQz5As4xN2/YUDxdpsEkMhvw3e6aNveFXUn4Hl/udNTCNhnKp8lT9fnc3MhvGKh1baak5CovpuQUXHAlIA==", - "path": "system.reflection.primitives/4.3.0", - "hashPath": "system.reflection.primitives.4.3.0.nupkg.sha512" - }, - "System.Reflection.TypeExtensions/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-7u6ulLcZbyxB5Gq0nMkQttcdBTx57ibzw+4IOXEfR+sXYQoHvjW5LTLyNr8O22UIMrqYbchJQJnos4eooYzYJA==", - "path": "system.reflection.typeextensions/4.3.0", - "hashPath": "system.reflection.typeextensions.4.3.0.nupkg.sha512" - }, - "System.Resources.ResourceManager/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-/zrcPkkWdZmI4F92gL/TPumP98AVDu/Wxr3CSJGQQ+XN6wbRZcyfSKVoPo17ilb3iOr0cCRqJInGwNMolqhS8A==", - "path": "system.resources.resourcemanager/4.3.0", - "hashPath": "system.resources.resourcemanager.4.3.0.nupkg.sha512" - }, - "System.Runtime/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==", - "path": "system.runtime/4.3.0", - "hashPath": "system.runtime.4.3.0.nupkg.sha512" - }, - "System.Runtime.CompilerServices.Unsafe/4.4.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-9dLLuBxr5GNmOfl2jSMcsHuteEg32BEfUotmmUkmZjpR3RpVHE8YQwt0ow3p6prwA1ME8WqDVZqrr8z6H8G+Kw==", - "path": "system.runtime.compilerservices.unsafe/4.4.0", - "hashPath": "system.runtime.compilerservices.unsafe.4.4.0.nupkg.sha512" - }, - "System.Runtime.Extensions/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-guW0uK0fn5fcJJ1tJVXYd7/1h5F+pea1r7FLSOz/f8vPEqbR2ZAknuRDvTQ8PzAilDveOxNjSfr0CHfIQfFk8g==", - "path": "system.runtime.extensions/4.3.0", - "hashPath": "system.runtime.extensions.4.3.0.nupkg.sha512" - }, - "System.Runtime.Handles/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-OKiSUN7DmTWeYb3l51A7EYaeNMnvxwE249YtZz7yooT4gOZhmTjIn48KgSsw2k2lYdLgTKNJw/ZIfSElwDRVgg==", - "path": "system.runtime.handles/4.3.0", - "hashPath": "system.runtime.handles.4.3.0.nupkg.sha512" - }, - "System.Runtime.InteropServices/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-uv1ynXqiMK8mp1GM3jDqPCFN66eJ5w5XNomaK2XD+TuCroNTLFGeZ+WCmBMcBDyTFKou3P6cR6J/QsaqDp7fGQ==", - "path": "system.runtime.interopservices/4.3.0", - "hashPath": "system.runtime.interopservices.4.3.0.nupkg.sha512" - }, - "System.Runtime.Numerics/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-yMH+MfdzHjy17l2KESnPiF2dwq7T+xLnSJar7slyimAkUh/gTrS9/UQOtv7xarskJ2/XDSNvfLGOBQPjL7PaHQ==", - "path": "system.runtime.numerics/4.3.0", - "hashPath": "system.runtime.numerics.4.3.0.nupkg.sha512" - }, - "System.Runtime.Serialization.Primitives/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-Wz+0KOukJGAlXjtKr+5Xpuxf8+c8739RI1C+A2BoQZT+wMCCoMDDdO8/4IRHfaVINqL78GO8dW8G2lW/e45Mcw==", - "path": "system.runtime.serialization.primitives/4.3.0", - "hashPath": "system.runtime.serialization.primitives.4.3.0.nupkg.sha512" - }, - "System.Security.Claims/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-P/+BR/2lnc4PNDHt/TPBAWHVMLMRHsyYZbU1NphW4HIWzCggz8mJbTQQ3MKljFE7LS3WagmVFuBgoLcFzYXlkA==", - "path": "system.security.claims/4.3.0", - "hashPath": "system.security.claims.4.3.0.nupkg.sha512" - }, - "System.Security.Cryptography.Algorithms/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-W1kd2Y8mYSCgc3ULTAZ0hOP2dSdG5YauTb1089T0/kRcN2MpSAW1izOFROrJgxSlMn3ArsgHXagigyi+ibhevg==", - "path": "system.security.cryptography.algorithms/4.3.0", - "hashPath": "system.security.cryptography.algorithms.4.3.0.nupkg.sha512" - }, - "System.Security.Cryptography.Cng/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-03idZOqFlsKRL4W+LuCpJ6dBYDUWReug6lZjBa3uJWnk5sPCUXckocevTaUA8iT/MFSrY/2HXkOt753xQ/cf8g==", - "path": "system.security.cryptography.cng/4.3.0", - "hashPath": "system.security.cryptography.cng.4.3.0.nupkg.sha512" - }, - "System.Security.Cryptography.Csp/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-X4s/FCkEUnRGnwR3aSfVIkldBmtURMhmexALNTwpjklzxWU7yjMk7GHLKOZTNkgnWnE0q7+BCf9N2LVRWxewaA==", - "path": "system.security.cryptography.csp/4.3.0", - "hashPath": "system.security.cryptography.csp.4.3.0.nupkg.sha512" - }, - "System.Security.Cryptography.Encoding/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-1DEWjZZly9ae9C79vFwqaO5kaOlI5q+3/55ohmq/7dpDyDfc8lYe7YVxJUZ5MF/NtbkRjwFRo14yM4OEo9EmDw==", - "path": "system.security.cryptography.encoding/4.3.0", - "hashPath": "system.security.cryptography.encoding.4.3.0.nupkg.sha512" - }, - "System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-h4CEgOgv5PKVF/HwaHzJRiVboL2THYCou97zpmhjghx5frc7fIvlkY1jL+lnIQyChrJDMNEXS6r7byGif8Cy4w==", - "path": "system.security.cryptography.openssl/4.3.0", - "hashPath": "system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "System.Security.Cryptography.Primitives/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-7bDIyVFNL/xKeFHjhobUAQqSpJq9YTOpbEs6mR233Et01STBMXNAc/V+BM6dwYGc95gVh/Zf+iVXWzj3mE8DWg==", - "path": "system.security.cryptography.primitives/4.3.0", - "hashPath": "system.security.cryptography.primitives.4.3.0.nupkg.sha512" - }, - "System.Security.Cryptography.X509Certificates/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-t2Tmu6Y2NtJ2um0RtcuhP7ZdNNxXEgUm2JeoA/0NvlMjAhKCnM1NX07TDl3244mVp3QU6LPEhT3HTtH1uF7IYw==", - "path": "system.security.cryptography.x509certificates/4.3.0", - "hashPath": "system.security.cryptography.x509certificates.4.3.0.nupkg.sha512" - }, - "System.Security.Principal/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-I1tkfQlAoMM2URscUtpcRo/hX0jinXx6a/KUtEQoz3owaYwl3qwsO8cbzYVVnjxrzxjHo3nJC+62uolgeGIS9A==", - "path": "system.security.principal/4.3.0", - "hashPath": "system.security.principal.4.3.0.nupkg.sha512" - }, - "System.Security.Principal.Windows/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-HVL1rvqYtnRCxFsYag/2le/ZfKLK4yMw79+s6FmKXbSCNN0JeAhrYxnRAHFoWRa0dEojsDcbBSpH3l22QxAVyw==", - "path": "system.security.principal.windows/4.3.0", - "hashPath": "system.security.principal.windows.4.3.0.nupkg.sha512" - }, - "System.Text.Encoding/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==", - "path": "system.text.encoding/4.3.0", - "hashPath": "system.text.encoding.4.3.0.nupkg.sha512" - }, - "System.Text.RegularExpressions/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-RpT2DA+L660cBt1FssIE9CAGpLFdFPuheB7pLpKpn6ZXNby7jDERe8Ua/Ne2xGiwLVG2JOqziiaVCGDon5sKFA==", - "path": "system.text.regularexpressions/4.3.0", - "hashPath": "system.text.regularexpressions.4.3.0.nupkg.sha512" - }, - "System.Threading/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-VkUS0kOBcUf3Wwm0TSbrevDDZ6BlM+b/HRiapRFWjM5O0NS0LviG0glKmFK+hhPDd1XFeSdU1GmlLhb2CoVpIw==", - "path": "system.threading/4.3.0", - "hashPath": "system.threading.4.3.0.nupkg.sha512" - }, - "System.Threading.Overlapped/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-m3HQ2dPiX/DSTpf+yJt8B0c+SRvzfqAJKx+QDWi+VLhz8svLT23MVjEOHPF/KiSLeArKU/iHescrbLd3yVgyNg==", - "path": "system.threading.overlapped/4.3.0", - "hashPath": "system.threading.overlapped.4.3.0.nupkg.sha512" - }, - "System.Threading.Tasks/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-LbSxKEdOUhVe8BezB/9uOGGppt+nZf6e1VFyw6v3DN6lqitm0OSn2uXMOdtP0M3W4iMcqcivm2J6UgqiwwnXiA==", - "path": "system.threading.tasks/4.3.0", - "hashPath": "system.threading.tasks.4.3.0.nupkg.sha512" - }, - "System.Threading.Thread/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-OHmbT+Zz065NKII/ZHcH9XO1dEuLGI1L2k7uYss+9C1jLxTC9kTZZuzUOyXHayRk+dft9CiDf3I/QZ0t8JKyBQ==", - "path": "system.threading.thread/4.3.0", - "hashPath": "system.threading.thread.4.3.0.nupkg.sha512" - }, - "System.Threading.ThreadPool/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-k/+g4b7vjdd4aix83sTgC9VG6oXYKAktSfNIJUNGxPEj7ryEOfzHHhfnmsZvjxawwcD9HyWXKCXmPjX8U4zeSw==", - "path": "system.threading.threadpool/4.3.0", - "hashPath": "system.threading.threadpool.4.3.0.nupkg.sha512" - }, - "ServiceStack.Interfaces/1.0.0": { - "type": "project", - "serviceable": false, - "sha512": "" - }, - "ServiceStack.Text/5.0.0.0": { - "type": "reference", - "serviceable": false, - "sha512": "" - } - } -} \ No newline at end of file diff --git a/lib/netstandard2.0/ServiceStack.Common.dll b/lib/netstandard2.0/ServiceStack.Common.dll deleted file mode 100644 index 76d8ab5d0..000000000 Binary files a/lib/netstandard2.0/ServiceStack.Common.dll and /dev/null differ diff --git a/lib/netstandard2.0/ServiceStack.Interfaces.deps.json b/lib/netstandard2.0/ServiceStack.Interfaces.deps.json deleted file mode 100644 index af2e19c77..000000000 --- a/lib/netstandard2.0/ServiceStack.Interfaces.deps.json +++ /dev/null @@ -1,189 +0,0 @@ -{ - "runtimeTarget": { - "name": ".NETStandard,Version=v2.0/", - "signature": "ef95db5aa2a8b34fb52677236f3775186d37bec7" - }, - "compilationOptions": {}, - "targets": { - ".NETStandard,Version=v2.0": {}, - ".NETStandard,Version=v2.0/": { - "ServiceStack.Interfaces/1.0.0": { - "dependencies": { - "NETStandard.Library": "2.0.0", - "System.Runtime.Serialization.Primitives": "4.3.0" - }, - "runtime": { - "ServiceStack.Interfaces.dll": {} - } - }, - "Microsoft.NETCore.Platforms/1.1.0": {}, - "Microsoft.NETCore.Targets/1.1.0": {}, - "NETStandard.Library/2.0.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0" - } - }, - "System.Globalization/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.IO/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } - }, - "System.Reflection/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.IO": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Reflection.Primitives/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Resources.ResourceManager/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Globalization": "4.3.0", - "System.Reflection": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Runtime/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0" - } - }, - "System.Runtime.Serialization.Primitives/4.3.0": { - "dependencies": { - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Runtime.Serialization.Primitives.dll": {} - } - }, - "System.Text.Encoding/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Threading.Tasks/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - } - } - }, - "libraries": { - "ServiceStack.Interfaces/1.0.0": { - "type": "project", - "serviceable": false, - "sha512": "" - }, - "Microsoft.NETCore.Platforms/1.1.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==", - "path": "microsoft.netcore.platforms/1.1.0", - "hashPath": "microsoft.netcore.platforms.1.1.0.nupkg.sha512" - }, - "Microsoft.NETCore.Targets/1.1.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-aOZA3BWfz9RXjpzt0sRJJMjAscAUm3Hoa4UWAfceV9UTYxgwZ1lZt5nO2myFf+/jetYQo4uTP7zS8sJY67BBxg==", - "path": "microsoft.netcore.targets/1.1.0", - "hashPath": "microsoft.netcore.targets.1.1.0.nupkg.sha512" - }, - "NETStandard.Library/2.0.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-7jnbRU+L08FXKMxqUflxEXtVymWvNOrS8yHgu9s6EM8Anr6T/wIX4nZ08j/u3Asz+tCufp3YVwFSEvFTPYmBPA==", - "path": "netstandard.library/2.0.0", - "hashPath": "netstandard.library.2.0.0.nupkg.sha512" - }, - "System.Globalization/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-kYdVd2f2PAdFGblzFswE4hkNANJBKRmsfa2X5LG2AcWE1c7/4t0pYae1L8vfZ5xvE2nK/R9JprtToA61OSHWIg==", - "path": "system.globalization/4.3.0", - "hashPath": "system.globalization.4.3.0.nupkg.sha512" - }, - "System.IO/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg==", - "path": "system.io/4.3.0", - "hashPath": "system.io.4.3.0.nupkg.sha512" - }, - "System.Reflection/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-KMiAFoW7MfJGa9nDFNcfu+FpEdiHpWgTcS2HdMpDvt9saK3y/G4GwprPyzqjFH9NTaGPQeWNHU+iDlDILj96aQ==", - "path": "system.reflection/4.3.0", - "hashPath": "system.reflection.4.3.0.nupkg.sha512" - }, - "System.Reflection.Primitives/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-5RXItQz5As4xN2/YUDxdpsEkMhvw3e6aNveFXUn4Hl/udNTCNhnKp8lT9fnc3MhvGKh1baak5CovpuQUXHAlIA==", - "path": "system.reflection.primitives/4.3.0", - "hashPath": "system.reflection.primitives.4.3.0.nupkg.sha512" - }, - "System.Resources.ResourceManager/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-/zrcPkkWdZmI4F92gL/TPumP98AVDu/Wxr3CSJGQQ+XN6wbRZcyfSKVoPo17ilb3iOr0cCRqJInGwNMolqhS8A==", - "path": "system.resources.resourcemanager/4.3.0", - "hashPath": "system.resources.resourcemanager.4.3.0.nupkg.sha512" - }, - "System.Runtime/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==", - "path": "system.runtime/4.3.0", - "hashPath": "system.runtime.4.3.0.nupkg.sha512" - }, - "System.Runtime.Serialization.Primitives/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-Wz+0KOukJGAlXjtKr+5Xpuxf8+c8739RI1C+A2BoQZT+wMCCoMDDdO8/4IRHfaVINqL78GO8dW8G2lW/e45Mcw==", - "path": "system.runtime.serialization.primitives/4.3.0", - "hashPath": "system.runtime.serialization.primitives.4.3.0.nupkg.sha512" - }, - "System.Text.Encoding/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==", - "path": "system.text.encoding/4.3.0", - "hashPath": "system.text.encoding.4.3.0.nupkg.sha512" - }, - "System.Threading.Tasks/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-LbSxKEdOUhVe8BezB/9uOGGppt+nZf6e1VFyw6v3DN6lqitm0OSn2uXMOdtP0M3W4iMcqcivm2J6UgqiwwnXiA==", - "path": "system.threading.tasks/4.3.0", - "hashPath": "system.threading.tasks.4.3.0.nupkg.sha512" - } - } -} \ No newline at end of file diff --git a/lib/netstandard2.0/ServiceStack.Interfaces.dll b/lib/netstandard2.0/ServiceStack.Interfaces.dll deleted file mode 100644 index 1ff58f867..000000000 Binary files a/lib/netstandard2.0/ServiceStack.Interfaces.dll and /dev/null differ diff --git a/lib/netstandard2.0/ServiceStack.Text.dll b/lib/netstandard2.0/ServiceStack.Text.dll deleted file mode 100644 index 91d34364d..000000000 Binary files a/lib/netstandard2.0/ServiceStack.Text.dll and /dev/null differ diff --git a/lib/netstandard2.0/ServiceStack.deps.json b/lib/netstandard2.0/ServiceStack.deps.json deleted file mode 100644 index 70bd12ac5..000000000 --- a/lib/netstandard2.0/ServiceStack.deps.json +++ /dev/null @@ -1,2237 +0,0 @@ -{ - "runtimeTarget": { - "name": ".NETStandard,Version=v2.0/", - "signature": "c12e41c2f516ada1695dedd0e7db48a0739aa98d" - }, - "compilationOptions": {}, - "targets": { - ".NETStandard,Version=v2.0": {}, - ".NETStandard,Version=v2.0/": { - "ServiceStack/1.0.0": { - "dependencies": { - "Microsoft.AspNetCore.Hosting.Abstractions": "2.0.0", - "Microsoft.AspNetCore.Http": "2.0.0", - "Microsoft.AspNetCore.Http.Abstractions": "2.0.0", - "Microsoft.AspNetCore.Http.Extensions": "2.0.0", - "Microsoft.Extensions.DependencyInjection.Abstractions": "2.0.0", - "Microsoft.Extensions.Logging.Abstractions": "2.0.0", - "Microsoft.Extensions.Primitives": "2.0.0", - "NETStandard.Library": "2.0.0", - "ServiceStack.Client": "1.0.0", - "ServiceStack.Common": "1.0.0", - "ServiceStack.Interfaces": "1.0.0", - "System.Linq.Queryable": "4.3.0", - "System.Reflection.Emit": "4.3.0", - "System.Threading.Thread": "4.3.0", - "ServiceStack.Text": "5.0.0.0" - }, - "runtime": { - "ServiceStack.dll": {} - } - }, - "Microsoft.AspNetCore.Hosting.Abstractions/2.0.0": { - "dependencies": { - "Microsoft.AspNetCore.Hosting.Server.Abstractions": "2.0.0", - "Microsoft.AspNetCore.Http.Abstractions": "2.0.0", - "Microsoft.Extensions.Configuration.Abstractions": "2.0.0", - "Microsoft.Extensions.DependencyInjection.Abstractions": "2.0.0", - "Microsoft.Extensions.FileProviders.Abstractions": "2.0.0", - "Microsoft.Extensions.Hosting.Abstractions": "2.0.0", - "Microsoft.Extensions.Logging.Abstractions": "2.0.0" - }, - "runtime": { - "lib/netstandard2.0/Microsoft.AspNetCore.Hosting.Abstractions.dll": {} - } - }, - "Microsoft.AspNetCore.Hosting.Server.Abstractions/2.0.0": { - "dependencies": { - "Microsoft.AspNetCore.Http.Features": "2.0.0", - "Microsoft.Extensions.Configuration.Abstractions": "2.0.0" - }, - "runtime": { - "lib/netstandard2.0/Microsoft.AspNetCore.Hosting.Server.Abstractions.dll": {} - } - }, - "Microsoft.AspNetCore.Http/2.0.0": { - "dependencies": { - "Microsoft.AspNetCore.Http.Abstractions": "2.0.0", - "Microsoft.AspNetCore.WebUtilities": "2.0.0", - "Microsoft.Extensions.ObjectPool": "2.0.0", - "Microsoft.Extensions.Options": "2.0.0", - "Microsoft.Net.Http.Headers": "2.0.0" - }, - "runtime": { - "lib/netstandard2.0/Microsoft.AspNetCore.Http.dll": {} - } - }, - "Microsoft.AspNetCore.Http.Abstractions/2.0.0": { - "dependencies": { - "Microsoft.AspNetCore.Http.Features": "2.0.0", - "System.Text.Encodings.Web": "4.4.0" - }, - "runtime": { - "lib/netstandard2.0/Microsoft.AspNetCore.Http.Abstractions.dll": {} - } - }, - "Microsoft.AspNetCore.Http.Extensions/2.0.0": { - "dependencies": { - "Microsoft.AspNetCore.Http.Abstractions": "2.0.0", - "Microsoft.Extensions.FileProviders.Abstractions": "2.0.0", - "Microsoft.Net.Http.Headers": "2.0.0", - "System.Buffers": "4.4.0" - }, - "runtime": { - "lib/netstandard2.0/Microsoft.AspNetCore.Http.Extensions.dll": {} - } - }, - "Microsoft.AspNetCore.Http.Features/2.0.0": { - "dependencies": { - "Microsoft.Extensions.Primitives": "2.0.0" - }, - "runtime": { - "lib/netstandard2.0/Microsoft.AspNetCore.Http.Features.dll": {} - } - }, - "Microsoft.AspNetCore.WebUtilities/2.0.0": { - "dependencies": { - "Microsoft.Net.Http.Headers": "2.0.0", - "System.Text.Encodings.Web": "4.4.0" - }, - "runtime": { - "lib/netstandard2.0/Microsoft.AspNetCore.WebUtilities.dll": {} - } - }, - "Microsoft.Extensions.Configuration.Abstractions/2.0.0": { - "dependencies": { - "Microsoft.Extensions.Primitives": "2.0.0" - }, - "runtime": { - "lib/netstandard2.0/Microsoft.Extensions.Configuration.Abstractions.dll": {} - } - }, - "Microsoft.Extensions.DependencyInjection.Abstractions/2.0.0": { - "runtime": { - "lib/netstandard2.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll": {} - } - }, - "Microsoft.Extensions.FileProviders.Abstractions/2.0.0": { - "dependencies": { - "Microsoft.Extensions.Primitives": "2.0.0" - }, - "runtime": { - "lib/netstandard2.0/Microsoft.Extensions.FileProviders.Abstractions.dll": {} - } - }, - "Microsoft.Extensions.Hosting.Abstractions/2.0.0": { - "runtime": { - "lib/netstandard2.0/Microsoft.Extensions.Hosting.Abstractions.dll": {} - } - }, - "Microsoft.Extensions.Logging.Abstractions/2.0.0": { - "runtime": { - "lib/netstandard2.0/Microsoft.Extensions.Logging.Abstractions.dll": {} - } - }, - "Microsoft.Extensions.ObjectPool/2.0.0": { - "runtime": { - "lib/netstandard2.0/Microsoft.Extensions.ObjectPool.dll": {} - } - }, - "Microsoft.Extensions.Options/2.0.0": { - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "2.0.0", - "Microsoft.Extensions.Primitives": "2.0.0" - }, - "runtime": { - "lib/netstandard2.0/Microsoft.Extensions.Options.dll": {} - } - }, - "Microsoft.Extensions.Primitives/2.0.0": { - "dependencies": { - "System.Runtime.CompilerServices.Unsafe": "4.4.0" - }, - "runtime": { - "lib/netstandard2.0/Microsoft.Extensions.Primitives.dll": {} - } - }, - "Microsoft.Net.Http.Headers/2.0.0": { - "dependencies": { - "Microsoft.Extensions.Primitives": "2.0.0", - "System.Buffers": "4.4.0" - }, - "runtime": { - "lib/netstandard2.0/Microsoft.Net.Http.Headers.dll": {} - } - }, - "Microsoft.NETCore.Platforms/1.1.0": {}, - "Microsoft.NETCore.Targets/1.1.0": {}, - "Microsoft.Win32.Primitives/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "NETStandard.Library/2.0.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0" - } - }, - "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {}, - "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {}, - "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {}, - "runtime.native.System/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0" - } - }, - "runtime.native.System.IO.Compression/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0" - } - }, - "runtime.native.System.Net.Http/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0" - } - }, - "runtime.native.System.Net.Security/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0" - } - }, - "runtime.native.System.Security.Cryptography.Apple/4.3.0": { - "dependencies": { - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "4.3.0" - } - }, - "runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "dependencies": { - "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {}, - "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {}, - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple/4.3.0": {}, - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {}, - "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {}, - "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {}, - "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {}, - "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {}, - "System.Buffers/4.4.0": { - "runtime": { - "lib/netstandard2.0/System.Buffers.dll": {} - } - }, - "System.Collections/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Collections.Concurrent/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Diagnostics.Tracing": "4.3.0", - "System.Globalization": "4.3.0", - "System.Reflection": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Collections.Concurrent.dll": {} - } - }, - "System.Collections.NonGeneric/4.3.0": { - "dependencies": { - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Threading": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Collections.NonGeneric.dll": {} - } - }, - "System.Collections.Specialized/4.3.0": { - "dependencies": { - "System.Collections.NonGeneric": "4.3.0", - "System.Globalization": "4.3.0", - "System.Globalization.Extensions": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Threading": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Collections.Specialized.dll": {} - } - }, - "System.ComponentModel/4.3.0": { - "dependencies": { - "System.Runtime": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.ComponentModel.dll": {} - } - }, - "System.ComponentModel.EventBasedAsync/4.3.0": { - "dependencies": { - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.ComponentModel.EventBasedAsync.dll": {} - } - }, - "System.ComponentModel.Primitives/4.3.0": { - "dependencies": { - "System.ComponentModel": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0" - }, - "runtime": { - "lib/netstandard1.0/System.ComponentModel.Primitives.dll": {} - } - }, - "System.Data.Common/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Text.RegularExpressions": "4.3.0", - "System.Threading.Tasks": "4.3.0" - }, - "runtime": { - "lib/netstandard1.2/System.Data.Common.dll": {} - } - }, - "System.Diagnostics.Debug/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Diagnostics.DiagnosticSource/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Tracing": "4.3.0", - "System.Reflection": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Diagnostics.DiagnosticSource.dll": {} - } - }, - "System.Diagnostics.Tools/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Diagnostics.Tracing/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Dynamic.Runtime/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Linq": "4.3.0", - "System.Linq.Expressions": "4.3.0", - "System.ObjectModel": "4.3.0", - "System.Reflection": "4.3.0", - "System.Reflection.Emit": "4.3.0", - "System.Reflection.Emit.ILGeneration": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Reflection.TypeExtensions": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Threading": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Dynamic.Runtime.dll": {} - } - }, - "System.Globalization/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Globalization.Calendars/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Globalization": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Globalization.Extensions/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Globalization": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.InteropServices": "4.3.0" - } - }, - "System.IO/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } - }, - "System.IO.Compression/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Buffers": "4.4.0", - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0", - "runtime.native.System": "4.3.0", - "runtime.native.System.IO.Compression": "4.3.0" - } - }, - "System.IO.FileSystem/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.IO": "4.3.0", - "System.IO.FileSystem.Primitives": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } - }, - "System.IO.FileSystem.Primitives/4.3.0": { - "dependencies": { - "System.Runtime": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.IO.FileSystem.Primitives.dll": {} - } - }, - "System.Linq/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0" - }, - "runtime": { - "lib/netstandard1.6/System.Linq.dll": {} - } - }, - "System.Linq.Expressions/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Linq": "4.3.0", - "System.ObjectModel": "4.3.0", - "System.Reflection": "4.3.0", - "System.Reflection.Emit": "4.3.0", - "System.Reflection.Emit.ILGeneration": "4.3.0", - "System.Reflection.Emit.Lightweight": "4.3.0", - "System.Reflection.Extensions": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Reflection.TypeExtensions": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Threading": "4.3.0" - }, - "runtime": { - "lib/netstandard1.6/System.Linq.Expressions.dll": {} - } - }, - "System.Linq.Queryable/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Linq": "4.3.0", - "System.Linq.Expressions": "4.3.0", - "System.Reflection": "4.3.0", - "System.Reflection.Extensions": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Linq.Queryable.dll": {} - } - }, - "System.Net.Http/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Diagnostics.DiagnosticSource": "4.3.0", - "System.Diagnostics.Tracing": "4.3.0", - "System.Globalization": "4.3.0", - "System.Globalization.Extensions": "4.3.0", - "System.IO": "4.3.0", - "System.IO.FileSystem": "4.3.0", - "System.Net.Primitives": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.OpenSsl": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Security.Cryptography.X509Certificates": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0", - "runtime.native.System": "4.3.0", - "runtime.native.System.Net.Http": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "System.Net.NameResolution/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Collections": "4.3.0", - "System.Diagnostics.Tracing": "4.3.0", - "System.Globalization": "4.3.0", - "System.Net.Primitives": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Principal.Windows": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0", - "runtime.native.System": "4.3.0" - } - }, - "System.Net.NetworkInformation/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.Win32.Primitives": "4.3.0", - "System.Collections": "4.3.0", - "System.Diagnostics.Tracing": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.IO.FileSystem": "4.3.0", - "System.IO.FileSystem.Primitives": "4.3.0", - "System.Linq": "4.3.0", - "System.Net.Primitives": "4.3.0", - "System.Net.Sockets": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Principal.Windows": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Overlapped": "4.3.0", - "System.Threading.Tasks": "4.3.0", - "System.Threading.Thread": "4.3.0", - "System.Threading.ThreadPool": "4.3.0", - "runtime.native.System": "4.3.0" - } - }, - "System.Net.Primitives/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0", - "System.Runtime.Handles": "4.3.0" - } - }, - "System.Net.Requests/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Diagnostics.Tracing": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Net.Http": "4.3.0", - "System.Net.Primitives": "4.3.0", - "System.Net.WebHeaderCollection": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } - }, - "System.Net.Security/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.Win32.Primitives": "4.3.0", - "System.Collections": "4.3.0", - "System.Collections.Concurrent": "4.3.0", - "System.Diagnostics.Tracing": "4.3.0", - "System.Globalization": "4.3.0", - "System.Globalization.Extensions": "4.3.0", - "System.IO": "4.3.0", - "System.Net.Primitives": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Claims": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.OpenSsl": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Security.Cryptography.X509Certificates": "4.3.0", - "System.Security.Principal": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0", - "System.Threading.ThreadPool": "4.3.0", - "runtime.native.System": "4.3.0", - "runtime.native.System.Net.Security": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "System.Net.Sockets/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.IO": "4.3.0", - "System.Net.Primitives": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } - }, - "System.Net.WebHeaderCollection/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Net.WebHeaderCollection.dll": {} - } - }, - "System.Net.WebSockets/4.3.0": { - "dependencies": { - "Microsoft.Win32.Primitives": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading.Tasks": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Net.WebSockets.dll": {} - } - }, - "System.Net.WebSockets.Client/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.Win32.Primitives": "4.3.0", - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Diagnostics.Tracing": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Net.NameResolution": "4.3.0", - "System.Net.Primitives": "4.3.0", - "System.Net.Security": "4.3.0", - "System.Net.Sockets": "4.3.0", - "System.Net.WebHeaderCollection": "4.3.0", - "System.Net.WebSockets": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Security.Cryptography.X509Certificates": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Text.Encoding.Extensions": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0", - "System.Threading.Timer": "4.3.0" - } - }, - "System.ObjectModel/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.ObjectModel.dll": {} - } - }, - "System.Private.DataContractSerialization/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Collections.Concurrent": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Linq": "4.3.0", - "System.Reflection": "4.3.0", - "System.Reflection.Emit.ILGeneration": "4.3.0", - "System.Reflection.Emit.Lightweight": "4.3.0", - "System.Reflection.Extensions": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Reflection.TypeExtensions": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Serialization.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Text.Encoding.Extensions": "4.3.0", - "System.Text.RegularExpressions": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0", - "System.Xml.ReaderWriter": "4.3.0", - "System.Xml.XDocument": "4.3.0", - "System.Xml.XmlDocument": "4.3.0", - "System.Xml.XmlSerializer": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Private.DataContractSerialization.dll": {} - } - }, - "System.Private.ServiceModel/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Collections": "4.3.0", - "System.Collections.Concurrent": "4.3.0", - "System.Collections.NonGeneric": "4.3.0", - "System.Collections.Specialized": "4.3.0", - "System.ComponentModel.EventBasedAsync": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Diagnostics.Tracing": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.IO.Compression": "4.3.0", - "System.Linq": "4.3.0", - "System.Linq.Expressions": "4.3.0", - "System.Linq.Queryable": "4.3.0", - "System.Net.Http": "4.3.0", - "System.Net.NameResolution": "4.3.0", - "System.Net.Primitives": "4.3.0", - "System.Net.Security": "4.3.0", - "System.Net.Sockets": "4.3.0", - "System.Net.WebHeaderCollection": "4.3.0", - "System.Net.WebSockets": "4.3.0", - "System.Net.WebSockets.Client": "4.3.0", - "System.ObjectModel": "4.3.0", - "System.Reflection": "4.3.0", - "System.Reflection.DispatchProxy": "4.3.0", - "System.Reflection.Extensions": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Reflection.TypeExtensions": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices.RuntimeInformation": "4.3.0", - "System.Runtime.Serialization.Primitives": "4.3.0", - "System.Runtime.Serialization.Xml": "4.3.0", - "System.Security.Claims": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Security.Cryptography.X509Certificates": "4.3.0", - "System.Security.Principal": "4.3.0", - "System.Security.Principal.Windows": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Text.Encoding.Extensions": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0", - "System.Threading.Timer": "4.3.0", - "System.Xml.ReaderWriter": "4.3.0", - "System.Xml.XmlDocument": "4.3.0", - "System.Xml.XmlSerializer": "4.3.0" - } - }, - "System.Reflection/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.IO": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Reflection.DispatchProxy/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Linq": "4.3.0", - "System.Reflection": "4.3.0", - "System.Reflection.Emit": "4.3.0", - "System.Reflection.Emit.ILGeneration": "4.3.0", - "System.Reflection.Extensions": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Reflection.DispatchProxy.dll": {} - } - }, - "System.Reflection.Emit/4.3.0": { - "dependencies": { - "System.IO": "4.3.0", - "System.Reflection": "4.3.0", - "System.Reflection.Emit.ILGeneration": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Reflection.Emit.dll": {} - } - }, - "System.Reflection.Emit.ILGeneration/4.3.0": { - "dependencies": { - "System.Reflection": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Reflection.Emit.ILGeneration.dll": {} - } - }, - "System.Reflection.Emit.Lightweight/4.3.0": { - "dependencies": { - "System.Reflection": "4.3.0", - "System.Reflection.Emit.ILGeneration": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Reflection.Emit.Lightweight.dll": {} - } - }, - "System.Reflection.Extensions/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Reflection": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Reflection.Primitives/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Reflection.TypeExtensions/4.3.0": { - "dependencies": { - "System.Reflection": "4.3.0", - "System.Runtime": "4.3.0" - }, - "runtime": { - "lib/netstandard1.5/System.Reflection.TypeExtensions.dll": {} - } - }, - "System.Resources.ResourceManager/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Globalization": "4.3.0", - "System.Reflection": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Runtime/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0" - } - }, - "System.Runtime.CompilerServices.Unsafe/4.4.0": { - "runtime": { - "lib/netstandard2.0/System.Runtime.CompilerServices.Unsafe.dll": {} - } - }, - "System.Runtime.Extensions/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Runtime.Handles/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Runtime.InteropServices/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Reflection": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Handles": "4.3.0" - } - }, - "System.Runtime.InteropServices.RuntimeInformation/4.3.0": { - "dependencies": { - "System.Reflection": "4.3.0", - "System.Reflection.Extensions": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Threading": "4.3.0", - "runtime.native.System": "4.3.0" - }, - "runtime": { - "lib/netstandard1.1/System.Runtime.InteropServices.RuntimeInformation.dll": {} - } - }, - "System.Runtime.Numerics/4.3.0": { - "dependencies": { - "System.Globalization": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Runtime.Numerics.dll": {} - } - }, - "System.Runtime.Serialization.Primitives/4.3.0": { - "dependencies": { - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Runtime.Serialization.Primitives.dll": {} - } - }, - "System.Runtime.Serialization.Xml/4.3.0": { - "dependencies": { - "System.IO": "4.3.0", - "System.Private.DataContractSerialization": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Serialization.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Xml.ReaderWriter": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Runtime.Serialization.Xml.dll": {} - } - }, - "System.Security.Claims/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Security.Principal": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Security.Claims.dll": {} - } - }, - "System.Security.Cryptography.Algorithms/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Collections": "4.3.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Runtime.Numerics": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "runtime.native.System.Security.Cryptography.Apple": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "System.Security.Cryptography.Cng/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0" - } - }, - "System.Security.Cryptography.Csp/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.IO": "4.3.0", - "System.Reflection": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0" - } - }, - "System.Security.Cryptography.Encoding/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Collections": "4.3.0", - "System.Collections.Concurrent": "4.3.0", - "System.Linq": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "System.Security.Cryptography.OpenSsl/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Runtime.Numerics": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "System.Security.Cryptography.Primitives/4.3.0": { - "dependencies": { - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Security.Cryptography.Primitives.dll": {} - } - }, - "System.Security.Cryptography.X509Certificates/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.Globalization.Calendars": "4.3.0", - "System.IO": "4.3.0", - "System.IO.FileSystem": "4.3.0", - "System.IO.FileSystem.Primitives": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Runtime.Numerics": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Cng": "4.3.0", - "System.Security.Cryptography.Csp": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.OpenSsl": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0", - "runtime.native.System": "4.3.0", - "runtime.native.System.Net.Http": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "System.Security.Principal/4.3.0": { - "dependencies": { - "System.Runtime": "4.3.0" - }, - "runtime": { - "lib/netstandard1.0/System.Security.Principal.dll": {} - } - }, - "System.Security.Principal.Windows/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.Win32.Primitives": "4.3.0", - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Reflection": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Claims": "4.3.0", - "System.Security.Principal": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0" - } - }, - "System.ServiceModel.Primitives/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.ComponentModel.EventBasedAsync": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Net.Primitives": "4.3.0", - "System.ObjectModel": "4.3.0", - "System.Private.ServiceModel": "4.3.0", - "System.Reflection": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Serialization.Primitives": "4.3.0", - "System.Runtime.Serialization.Xml": "4.3.0", - "System.Security.Cryptography.X509Certificates": "4.3.0", - "System.Security.Principal": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0", - "System.Xml.ReaderWriter": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.ServiceModel.Primitives.dll": {} - } - }, - "System.Text.Encoding/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Text.Encoding.Extensions/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0", - "System.Text.Encoding": "4.3.0" - } - }, - "System.Text.Encodings.Web/4.4.0": { - "runtime": { - "lib/netstandard2.0/System.Text.Encodings.Web.dll": {} - } - }, - "System.Text.RegularExpressions/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Globalization": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Threading": "4.3.0" - }, - "runtime": { - "lib/netstandard1.6/System.Text.RegularExpressions.dll": {} - } - }, - "System.Threading/4.3.0": { - "dependencies": { - "System.Runtime": "4.3.0", - "System.Threading.Tasks": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Threading.dll": {} - } - }, - "System.Threading.Overlapped/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Handles": "4.3.0" - } - }, - "System.Threading.Tasks/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Threading.Tasks.Extensions/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading.Tasks": "4.3.0" - }, - "runtime": { - "lib/netstandard1.0/System.Threading.Tasks.Extensions.dll": {} - } - }, - "System.Threading.Thread/4.3.0": { - "dependencies": { - "System.Runtime": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Threading.Thread.dll": {} - } - }, - "System.Threading.ThreadPool/4.3.0": { - "dependencies": { - "System.Runtime": "4.3.0", - "System.Runtime.Handles": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Threading.ThreadPool.dll": {} - } - }, - "System.Threading.Timer/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Xml.ReaderWriter/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.IO.FileSystem": "4.3.0", - "System.IO.FileSystem.Primitives": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Text.Encoding.Extensions": "4.3.0", - "System.Text.RegularExpressions": "4.3.0", - "System.Threading.Tasks": "4.3.0", - "System.Threading.Tasks.Extensions": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Xml.ReaderWriter.dll": {} - } - }, - "System.Xml.XDocument/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Diagnostics.Tools": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Reflection": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0", - "System.Xml.ReaderWriter": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Xml.XDocument.dll": {} - } - }, - "System.Xml.XmlDocument/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0", - "System.Xml.ReaderWriter": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Xml.XmlDocument.dll": {} - } - }, - "System.Xml.XmlSerializer/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Linq": "4.3.0", - "System.Reflection": "4.3.0", - "System.Reflection.Emit": "4.3.0", - "System.Reflection.Emit.ILGeneration": "4.3.0", - "System.Reflection.Extensions": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Reflection.TypeExtensions": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Text.RegularExpressions": "4.3.0", - "System.Threading": "4.3.0", - "System.Xml.ReaderWriter": "4.3.0", - "System.Xml.XmlDocument": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Xml.XmlSerializer.dll": {} - } - }, - "ServiceStack.Client/1.0.0": { - "dependencies": { - "Microsoft.Extensions.Primitives": "2.0.0", - "ServiceStack.Interfaces": "1.0.0", - "System.Collections.Specialized": "4.3.0", - "System.Net.Requests": "4.3.0", - "System.ServiceModel.Primitives": "4.3.0", - "System.Xml.XmlSerializer": "4.3.0" - }, - "runtime": { - "ServiceStack.Client.dll": {} - } - }, - "ServiceStack.Common/1.0.0": { - "dependencies": { - "Microsoft.Extensions.Primitives": "2.0.0", - "ServiceStack.Interfaces": "1.0.0", - "System.ComponentModel.Primitives": "4.3.0", - "System.Data.Common": "4.3.0", - "System.Dynamic.Runtime": "4.3.0", - "System.Net.NetworkInformation": "4.3.0", - "System.Net.Requests": "4.3.0", - "System.Reflection.TypeExtensions": "4.3.0", - "System.Runtime.Serialization.Primitives": "4.3.0" - }, - "runtime": { - "ServiceStack.Common.dll": {} - } - }, - "ServiceStack.Interfaces/1.0.0": { - "dependencies": { - "System.Runtime.Serialization.Primitives": "4.3.0" - }, - "runtime": { - "ServiceStack.Interfaces.dll": {} - } - }, - "ServiceStack.Text/5.0.0.0": { - "runtime": { - "ServiceStack.Text.dll": {} - } - } - } - }, - "libraries": { - "ServiceStack/1.0.0": { - "type": "project", - "serviceable": false, - "sha512": "" - }, - "Microsoft.AspNetCore.Hosting.Abstractions/2.0.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-IR2zlm3d/CmYbkw+cMM7M6mUAi+xsFUPfWqGYqzZVC5o6jX3xD2Z4Uf44UBaWKMBf5Z7q9dodIdXxwFPF2Hxhg==", - "path": "microsoft.aspnetcore.hosting.abstractions/2.0.0", - "hashPath": "microsoft.aspnetcore.hosting.abstractions.2.0.0.nupkg.sha512" - }, - "Microsoft.AspNetCore.Hosting.Server.Abstractions/2.0.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-v2H65ix/O11HKoxhKQpljtozsD5/1tqeXr3TYnrLgfAPIsp6kTFxIcTSENoxtew7h9X14ENqUf2lBCkyCNRUuQ==", - "path": "microsoft.aspnetcore.hosting.server.abstractions/2.0.0", - "hashPath": "microsoft.aspnetcore.hosting.server.abstractions.2.0.0.nupkg.sha512" - }, - "Microsoft.AspNetCore.Http/2.0.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-2YNhcHrGxo2YufA8TYGyaEMIJwikyisZqEzHCRpIuI0D6ZXkA47U/3NJg2r/x5/gGHNM3TXO7DsqH88qRda+yg==", - "path": "microsoft.aspnetcore.http/2.0.0", - "hashPath": "microsoft.aspnetcore.http.2.0.0.nupkg.sha512" - }, - "Microsoft.AspNetCore.Http.Abstractions/2.0.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-pblZLY7IfNqhQ5wwGQ0vNq2mG6W5YgZI1fk7suEuwZsGxGEADNBAyNlTALM9L8nMXdvbp6aHP/t4wHrFpcL3Sw==", - "path": "microsoft.aspnetcore.http.abstractions/2.0.0", - "hashPath": "microsoft.aspnetcore.http.abstractions.2.0.0.nupkg.sha512" - }, - "Microsoft.AspNetCore.Http.Extensions/2.0.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-lA7Bwvur19MhXrlW0w+WBXONJMSFYY5kNazflz4MNwMZMtzwHxNA6fC5sQsssYd/XvA0gMyKwp52s68uuKLR1w==", - "path": "microsoft.aspnetcore.http.extensions/2.0.0", - "hashPath": "microsoft.aspnetcore.http.extensions.2.0.0.nupkg.sha512" - }, - "Microsoft.AspNetCore.Http.Features/2.0.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-yk62muzFTZTKCQuo3nmVPkPvGBlM2qbdSxbX62TufuONuKQrTGQ/SwhwBbYutk5/YY7u4HETu0n9BKOn7mMgmA==", - "path": "microsoft.aspnetcore.http.features/2.0.0", - "hashPath": "microsoft.aspnetcore.http.features.2.0.0.nupkg.sha512" - }, - "Microsoft.AspNetCore.WebUtilities/2.0.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-RqDEwy7jdHJ0NunWydSzJrpODnsF7NPdB0KaRdG60H1bMEt4DbjcWkUb+XxjZ15uWCMi7clTQClpPuIFLwD1yQ==", - "path": "microsoft.aspnetcore.webutilities/2.0.0", - "hashPath": "microsoft.aspnetcore.webutilities.2.0.0.nupkg.sha512" - }, - "Microsoft.Extensions.Configuration.Abstractions/2.0.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-rHFrXqMIvQNq51H8RYTO4IWmDOYh8NUzyqGlh0xHWTP6XYnKk7Ryinys2uDs+Vu88b3AMlM3gBBSs78m6OQpYQ==", - "path": "microsoft.extensions.configuration.abstractions/2.0.0", - "hashPath": "microsoft.extensions.configuration.abstractions.2.0.0.nupkg.sha512" - }, - "Microsoft.Extensions.DependencyInjection.Abstractions/2.0.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-eUdJ0Q/GfVyUJc0Jal5L1QZLceL78pvEM9wEKcHeI24KorqMDoVX+gWsMGLulQMfOwsUaPtkpQM2pFERTzSfSg==", - "path": "microsoft.extensions.dependencyinjection.abstractions/2.0.0", - "hashPath": "microsoft.extensions.dependencyinjection.abstractions.2.0.0.nupkg.sha512" - }, - "Microsoft.Extensions.FileProviders.Abstractions/2.0.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-Z0AK+hmLO33WAXQ5P1uPzhH7z5yjDHX/XnUefXxE//SyvCb9x4cVjND24dT5566t/yzGp8/WLD7EG9KQKZZklQ==", - "path": "microsoft.extensions.fileproviders.abstractions/2.0.0", - "hashPath": "microsoft.extensions.fileproviders.abstractions.2.0.0.nupkg.sha512" - }, - "Microsoft.Extensions.Hosting.Abstractions/2.0.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-qPG6Ip/AdHxMJ7j3z8FkkpCbV8yjtiFpf/aOpN3TwfJWbtYpN+BKV8Q+pqPMgk7XZivcju9yARaEVCS++hWopA==", - "path": "microsoft.extensions.hosting.abstractions/2.0.0", - "hashPath": "microsoft.extensions.hosting.abstractions.2.0.0.nupkg.sha512" - }, - "Microsoft.Extensions.Logging.Abstractions/2.0.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-6ZCllUYGFukkymSTx3Yr0G/ajRxoNJp7/FqSxSB4fGISST54ifBhgu4Nc0ItGi3i6DqwuNd8SUyObmiC++AO2Q==", - "path": "microsoft.extensions.logging.abstractions/2.0.0", - "hashPath": "microsoft.extensions.logging.abstractions.2.0.0.nupkg.sha512" - }, - "Microsoft.Extensions.ObjectPool/2.0.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-drOmgNZCJiNEqFM/TvyqwtogS8wqoWGQCW5KB/CVGKL6VXHw8OOMdaHyspp8HPstP9UDnrnuq+8eaCaAcQg6tA==", - "path": "microsoft.extensions.objectpool/2.0.0", - "hashPath": "microsoft.extensions.objectpool.2.0.0.nupkg.sha512" - }, - "Microsoft.Extensions.Options/2.0.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-sAKBgjl2gWsECBLLR9K54T7/uZaP2n9GhMYHay/oOLfvpvX0+iNAlQ2NJgVE352C9Fs5CDV3VbNTK8T2aNKQFA==", - "path": "microsoft.extensions.options/2.0.0", - "hashPath": "microsoft.extensions.options.2.0.0.nupkg.sha512" - }, - "Microsoft.Extensions.Primitives/2.0.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-ukg53qNlqTrK38WA30b5qhw0GD7y3jdI9PHHASjdKyTcBHTevFM2o23tyk3pWCgAV27Bbkm+CPQ2zUe1ZOuYSA==", - "path": "microsoft.extensions.primitives/2.0.0", - "hashPath": "microsoft.extensions.primitives.2.0.0.nupkg.sha512" - }, - "Microsoft.Net.Http.Headers/2.0.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-Rm9zeNCWyNrGnysHdRXJpNfeDVlPzzFuidSuRLRNvOrnw71vgNPlR4H9wHo2hG/oSaruukqNjK06MDQqb+eXhA==", - "path": "microsoft.net.http.headers/2.0.0", - "hashPath": "microsoft.net.http.headers.2.0.0.nupkg.sha512" - }, - "Microsoft.NETCore.Platforms/1.1.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==", - "path": "microsoft.netcore.platforms/1.1.0", - "hashPath": "microsoft.netcore.platforms.1.1.0.nupkg.sha512" - }, - "Microsoft.NETCore.Targets/1.1.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-aOZA3BWfz9RXjpzt0sRJJMjAscAUm3Hoa4UWAfceV9UTYxgwZ1lZt5nO2myFf+/jetYQo4uTP7zS8sJY67BBxg==", - "path": "microsoft.netcore.targets/1.1.0", - "hashPath": "microsoft.netcore.targets.1.1.0.nupkg.sha512" - }, - "Microsoft.Win32.Primitives/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-9ZQKCWxH7Ijp9BfahvL2Zyf1cJIk8XYLF6Yjzr2yi0b2cOut/HQ31qf1ThHAgCc3WiZMdnWcfJCgN82/0UunxA==", - "path": "microsoft.win32.primitives/4.3.0", - "hashPath": "microsoft.win32.primitives.4.3.0.nupkg.sha512" - }, - "NETStandard.Library/2.0.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-7jnbRU+L08FXKMxqUflxEXtVymWvNOrS8yHgu9s6EM8Anr6T/wIX4nZ08j/u3Asz+tCufp3YVwFSEvFTPYmBPA==", - "path": "netstandard.library/2.0.0", - "hashPath": "netstandard.library.2.0.0.nupkg.sha512" - }, - "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-HdSSp5MnJSsg08KMfZThpuLPJpPwE5hBXvHwoKWosyHHfe8Mh5WKT0ylEOf6yNzX6Ngjxe4Whkafh5q7Ymac4Q==", - "path": "runtime.debian.8-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.debian.8-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-+yH1a49wJMy8Zt4yx5RhJrxO/DBDByAiCzNwiETI+1S4mPdCu0OY4djdciC7Vssk0l22wQaDLrXxXkp+3+7bVA==", - "path": "runtime.fedora.23-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.fedora.23-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-c3YNH1GQJbfIPJeCnr4avseugSqPrxwIqzthYyZDN6EuOyNOzq+y2KSUfRcXauya1sF4foESTgwM5e1A8arAKw==", - "path": "runtime.fedora.24-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.fedora.24-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.native.System/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-c/qWt2LieNZIj1jGnVNsE2Kl23Ya2aSTBuXMD6V7k9KWr6l16Tqdwq+hJScEpWER9753NWC8h96PaVNY5Ld7Jw==", - "path": "runtime.native.system/4.3.0", - "hashPath": "runtime.native.system.4.3.0.nupkg.sha512" - }, - "runtime.native.System.IO.Compression/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-INBPonS5QPEgn7naufQFXJEp3zX6L4bwHgJ/ZH78aBTpeNfQMtf7C6VrAFhlq2xxWBveIOWyFzQjJ8XzHMhdOQ==", - "path": "runtime.native.system.io.compression/4.3.0", - "hashPath": "runtime.native.system.io.compression.4.3.0.nupkg.sha512" - }, - "runtime.native.System.Net.Http/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-ZVuZJqnnegJhd2k/PtAbbIcZ3aZeITq3sj06oKfMBSfphW3HDmk/t4ObvbOk/JA/swGR0LNqMksAh/f7gpTROg==", - "path": "runtime.native.system.net.http/4.3.0", - "hashPath": "runtime.native.system.net.http.4.3.0.nupkg.sha512" - }, - "runtime.native.System.Net.Security/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-M2nN92ePS8BgQ2oi6Jj3PlTUzadYSIWLdZrHY1n1ZcW9o4wAQQ6W+aQ2lfq1ysZQfVCgDwY58alUdowrzezztg==", - "path": "runtime.native.system.net.security/4.3.0", - "hashPath": "runtime.native.system.net.security.4.3.0.nupkg.sha512" - }, - "runtime.native.System.Security.Cryptography.Apple/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-DloMk88juo0OuOWr56QG7MNchmafTLYWvABy36izkrLI5VledI0rq28KGs1i9wbpeT9NPQrx/wTf8U2vazqQ3Q==", - "path": "runtime.native.system.security.cryptography.apple/4.3.0", - "hashPath": "runtime.native.system.security.cryptography.apple.4.3.0.nupkg.sha512" - }, - "runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-NS1U+700m4KFRHR5o4vo9DSlTmlCKu/u7dtE5sUHVIPB+xpXxYQvgBgA6wEIeCz6Yfn0Z52/72WYsToCEPJnrw==", - "path": "runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-b3pthNgxxFcD+Pc0WSEoC0+md3MyhRS6aCEeenvNE3Fdw1HyJ18ZhRFVJJzIeR/O/jpxPboB805Ho0T3Ul7w8A==", - "path": "runtime.opensuse.13.2-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.opensuse.13.2-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-KeLz4HClKf+nFS7p/6Fi/CqyLXh81FpiGzcmuS8DGi9lUqSnZ6Es23/gv2O+1XVGfrbNmviF7CckBpavkBoIFQ==", - "path": "runtime.opensuse.42.1-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.opensuse.42.1-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-kVXCuMTrTlxq4XOOMAysuNwsXWpYeboGddNGpIgNSZmv1b6r/s/DPk0fYMB7Q5Qo4bY68o48jt4T4y5BVecbCQ==", - "path": "runtime.osx.10.10-x64.runtime.native.system.security.cryptography.apple/4.3.0", - "hashPath": "runtime.osx.10.10-x64.runtime.native.system.security.cryptography.apple.4.3.0.nupkg.sha512" - }, - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-X7IdhILzr4ROXd8mI1BUCQMSHSQwelUlBjF1JyTKCjXaOGn2fB4EKBxQbCK2VjO3WaWIdlXZL3W6TiIVnrhX4g==", - "path": "runtime.osx.10.10-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.osx.10.10-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-nyFNiCk/r+VOiIqreLix8yN+q3Wga9+SE8BCgkf+2BwEKiNx6DyvFjCgkfV743/grxv8jHJ8gUK4XEQw7yzRYg==", - "path": "runtime.rhel.7-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.rhel.7-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-ytoewC6wGorL7KoCAvRfsgoJPJbNq+64k2SqW6JcOAebWsFUvCCYgfzQMrnpvPiEl4OrblUlhF2ji+Q1+SVLrQ==", - "path": "runtime.ubuntu.14.04-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.ubuntu.14.04-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-I8bKw2I8k58Wx7fMKQJn2R8lamboCAiHfHeV/pS65ScKWMMI0+wJkLYlEKvgW1D/XvSl/221clBoR2q9QNNM7A==", - "path": "runtime.ubuntu.16.04-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.ubuntu.16.04-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-VB5cn/7OzUfzdnC8tqAIMQciVLiq2epm2NrAm1E9OjNRyG4lVhfR61SMcLizejzQP8R8Uf/0l5qOIbUEi+RdEg==", - "path": "runtime.ubuntu.16.10-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.ubuntu.16.10-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "System.Buffers/4.4.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-AwarXzzoDwX6BgrhjoJsk6tUezZEozOT5Y9QKF94Gl4JK91I4PIIBkBco9068Y9/Dra8Dkbie99kXB8+1BaYKw==", - "path": "system.buffers/4.4.0", - "hashPath": "system.buffers.4.4.0.nupkg.sha512" - }, - "System.Collections/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-3Dcj85/TBdVpL5Zr+gEEBUuFe2icOnLalmEh9hfck1PTYbbyWuZgh4fmm2ysCLTrqLQw6t3TgTyJ+VLp+Qb+Lw==", - "path": "system.collections/4.3.0", - "hashPath": "system.collections.4.3.0.nupkg.sha512" - }, - "System.Collections.Concurrent/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-ztl69Xp0Y/UXCL+3v3tEU+lIy+bvjKNUmopn1wep/a291pVPK7dxBd6T7WnlQqRog+d1a/hSsgRsmFnIBKTPLQ==", - "path": "system.collections.concurrent/4.3.0", - "hashPath": "system.collections.concurrent.4.3.0.nupkg.sha512" - }, - "System.Collections.NonGeneric/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-prtjIEMhGUnQq6RnPEYLpFt8AtLbp9yq2zxOSrY7KJJZrw25Fi97IzBqY7iqssbM61Ek5b8f3MG/sG1N2sN5KA==", - "path": "system.collections.nongeneric/4.3.0", - "hashPath": "system.collections.nongeneric.4.3.0.nupkg.sha512" - }, - "System.Collections.Specialized/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-Epx8PoVZR0iuOnJJDzp7pWvdfMMOAvpUo95pC4ScH2mJuXkKA2Y4aR3cG9qt2klHgSons1WFh4kcGW7cSXvrxg==", - "path": "system.collections.specialized/4.3.0", - "hashPath": "system.collections.specialized.4.3.0.nupkg.sha512" - }, - "System.ComponentModel/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-VyGn1jGRZVfxnh8EdvDCi71v3bMXrsu8aYJOwoV7SNDLVhiEqwP86pPMyRGsDsxhXAm2b3o9OIqeETfN5qfezw==", - "path": "system.componentmodel/4.3.0", - "hashPath": "system.componentmodel.4.3.0.nupkg.sha512" - }, - "System.ComponentModel.EventBasedAsync/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-fCFl8f0XdwA/BuoNrVBB5D0Y48/hv2J+w4xSDdXQitXZsR6UCSOrDVE7TCUraY802ENwcHUnUCv4En8CupDU1g==", - "path": "system.componentmodel.eventbasedasync/4.3.0", - "hashPath": "system.componentmodel.eventbasedasync.4.3.0.nupkg.sha512" - }, - "System.ComponentModel.Primitives/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-j8GUkCpM8V4d4vhLIIoBLGey2Z5bCkMVNjEZseyAlm4n5arcsJOeI3zkUP+zvZgzsbLTYh4lYeP/ZD/gdIAPrw==", - "path": "system.componentmodel.primitives/4.3.0", - "hashPath": "system.componentmodel.primitives.4.3.0.nupkg.sha512" - }, - "System.Data.Common/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-lm6E3T5u7BOuEH0u18JpbJHxBfOJPuCyl4Kg1RH10ktYLp5uEEE1xKrHW56/We4SnZpGAuCc9N0MJpSDhTHZGQ==", - "path": "system.data.common/4.3.0", - "hashPath": "system.data.common.4.3.0.nupkg.sha512" - }, - "System.Diagnostics.Debug/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-ZUhUOdqmaG5Jk3Xdb8xi5kIyQYAA4PnTNlHx1mu9ZY3qv4ELIdKbnL/akbGaKi2RnNUWaZsAs31rvzFdewTj2g==", - "path": "system.diagnostics.debug/4.3.0", - "hashPath": "system.diagnostics.debug.4.3.0.nupkg.sha512" - }, - "System.Diagnostics.DiagnosticSource/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-tD6kosZnTAGdrEa0tZSuFyunMbt/5KYDnHdndJYGqZoNy00XVXyACd5d6KnE1YgYv3ne2CjtAfNXo/fwEhnKUA==", - "path": "system.diagnostics.diagnosticsource/4.3.0", - "hashPath": "system.diagnostics.diagnosticsource.4.3.0.nupkg.sha512" - }, - "System.Diagnostics.Tools/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-UUvkJfSYJMM6x527dJg2VyWPSRqIVB0Z7dbjHst1zmwTXz5CcXSYJFWRpuigfbO1Lf7yfZiIaEUesfnl/g5EyA==", - "path": "system.diagnostics.tools/4.3.0", - "hashPath": "system.diagnostics.tools.4.3.0.nupkg.sha512" - }, - "System.Diagnostics.Tracing/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-rswfv0f/Cqkh78rA5S8eN8Neocz234+emGCtTF3lxPY96F+mmmUen6tbn0glN6PMvlKQb9bPAY5e9u7fgPTkKw==", - "path": "system.diagnostics.tracing/4.3.0", - "hashPath": "system.diagnostics.tracing.4.3.0.nupkg.sha512" - }, - "System.Dynamic.Runtime/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-SNVi1E/vfWUAs/WYKhE9+qlS6KqK0YVhnlT0HQtr8pMIA8YX3lwy3uPMownDwdYISBdmAF/2holEIldVp85Wag==", - "path": "system.dynamic.runtime/4.3.0", - "hashPath": "system.dynamic.runtime.4.3.0.nupkg.sha512" - }, - "System.Globalization/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-kYdVd2f2PAdFGblzFswE4hkNANJBKRmsfa2X5LG2AcWE1c7/4t0pYae1L8vfZ5xvE2nK/R9JprtToA61OSHWIg==", - "path": "system.globalization/4.3.0", - "hashPath": "system.globalization.4.3.0.nupkg.sha512" - }, - "System.Globalization.Calendars/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-GUlBtdOWT4LTV3I+9/PJW+56AnnChTaOqqTLFtdmype/L500M2LIyXgmtd9X2P2VOkmJd5c67H5SaC2QcL1bFA==", - "path": "system.globalization.calendars/4.3.0", - "hashPath": "system.globalization.calendars.4.3.0.nupkg.sha512" - }, - "System.Globalization.Extensions/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-FhKmdR6MPG+pxow6wGtNAWdZh7noIOpdD5TwQ3CprzgIE1bBBoim0vbR1+AWsWjQmU7zXHgQo4TWSP6lCeiWcQ==", - "path": "system.globalization.extensions/4.3.0", - "hashPath": "system.globalization.extensions.4.3.0.nupkg.sha512" - }, - "System.IO/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg==", - "path": "system.io/4.3.0", - "hashPath": "system.io.4.3.0.nupkg.sha512" - }, - "System.IO.Compression/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-YHndyoiV90iu4iKG115ibkhrG+S3jBm8Ap9OwoUAzO5oPDAWcr0SFwQFm0HjM8WkEZWo0zvLTyLmbvTkW1bXgg==", - "path": "system.io.compression/4.3.0", - "hashPath": "system.io.compression.4.3.0.nupkg.sha512" - }, - "System.IO.FileSystem/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-3wEMARTnuio+ulnvi+hkRNROYwa1kylvYahhcLk4HSoVdl+xxTFVeVlYOfLwrDPImGls0mDqbMhrza8qnWPTdA==", - "path": "system.io.filesystem/4.3.0", - "hashPath": "system.io.filesystem.4.3.0.nupkg.sha512" - }, - "System.IO.FileSystem.Primitives/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-6QOb2XFLch7bEc4lIcJH49nJN2HV+OC3fHDgsLVsBVBk3Y4hFAnOBGzJ2lUu7CyDDFo9IBWkSsnbkT6IBwwiMw==", - "path": "system.io.filesystem.primitives/4.3.0", - "hashPath": "system.io.filesystem.primitives.4.3.0.nupkg.sha512" - }, - "System.Linq/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-5DbqIUpsDp0dFftytzuMmc0oeMdQwjcP/EWxsksIz/w1TcFRkZ3yKKz0PqiYFMmEwPSWw+qNVqD7PJ889JzHbw==", - "path": "system.linq/4.3.0", - "hashPath": "system.linq.4.3.0.nupkg.sha512" - }, - "System.Linq.Expressions/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-PGKkrd2khG4CnlyJwxwwaWWiSiWFNBGlgXvJpeO0xCXrZ89ODrQ6tjEWS/kOqZ8GwEOUATtKtzp1eRgmYNfclg==", - "path": "system.linq.expressions/4.3.0", - "hashPath": "system.linq.expressions.4.3.0.nupkg.sha512" - }, - "System.Linq.Queryable/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-In1Bmmvl/j52yPu3xgakQSI0YIckPUr870w4K5+Lak3JCCa8hl+my65lABOuKfYs4ugmZy25ScFerC4nz8+b6g==", - "path": "system.linq.queryable/4.3.0", - "hashPath": "system.linq.queryable.4.3.0.nupkg.sha512" - }, - "System.Net.Http/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-sYg+FtILtRQuYWSIAuNOELwVuVsxVyJGWQyOnlAzhV4xvhyFnON1bAzYYC+jjRW8JREM45R0R5Dgi8MTC5sEwA==", - "path": "system.net.http/4.3.0", - "hashPath": "system.net.http.4.3.0.nupkg.sha512" - }, - "System.Net.NameResolution/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-AFYl08R7MrsrEjqpQWTZWBadqXyTzNDaWpMqyxhb0d6sGhV6xMDKueuBXlLL30gz+DIRY6MpdgnHWlCh5wmq9w==", - "path": "system.net.nameresolution/4.3.0", - "hashPath": "system.net.nameresolution.4.3.0.nupkg.sha512" - }, - "System.Net.NetworkInformation/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-zNVmWVry0pAu7lcrRBhwwU96WUdbsrGL3azyzsbXmVNptae1+Za+UgOe9Z6s8iaWhPn7/l4wQqhC56HZWq7tkg==", - "path": "system.net.networkinformation/4.3.0", - "hashPath": "system.net.networkinformation.4.3.0.nupkg.sha512" - }, - "System.Net.Primitives/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-qOu+hDwFwoZPbzPvwut2qATe3ygjeQBDQj91xlsaqGFQUI5i4ZnZb8yyQuLGpDGivEPIt8EJkd1BVzVoP31FXA==", - "path": "system.net.primitives/4.3.0", - "hashPath": "system.net.primitives.4.3.0.nupkg.sha512" - }, - "System.Net.Requests/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-OZNUuAs0kDXUzm7U5NZ1ojVta5YFZmgT2yxBqsQ7Eseq5Ahz88LInGRuNLJ/NP2F8W1q7tse1pKDthj3reF5QA==", - "path": "system.net.requests/4.3.0", - "hashPath": "system.net.requests.4.3.0.nupkg.sha512" - }, - "System.Net.Security/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-IgJKNfALqw7JRWp5LMQ5SWHNKvXVz094U6wNE3c1i8bOkMQvgBL+MMQuNt3xl9Qg9iWpj3lFxPZEY6XHmROjMQ==", - "path": "system.net.security/4.3.0", - "hashPath": "system.net.security.4.3.0.nupkg.sha512" - }, - "System.Net.Sockets/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-m6icV6TqQOAdgt5N/9I5KNpjom/5NFtkmGseEH+AK/hny8XrytLH3+b5M8zL/Ycg3fhIocFpUMyl/wpFnVRvdw==", - "path": "system.net.sockets/4.3.0", - "hashPath": "system.net.sockets.4.3.0.nupkg.sha512" - }, - "System.Net.WebHeaderCollection/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-XZrXYG3c7QV/GpWeoaRC02rM6LH2JJetfVYskf35wdC/w2fFDFMphec4gmVH2dkll6abtW14u9Rt96pxd9YH2A==", - "path": "system.net.webheadercollection/4.3.0", - "hashPath": "system.net.webheadercollection.4.3.0.nupkg.sha512" - }, - "System.Net.WebSockets/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-u6fFNY5q4T8KerUAVbya7bR6b7muBuSTAersyrihkcmE5QhEOiH3t5rh4il15SexbVlpXFHGuMwr/m8fDrnkQg==", - "path": "system.net.websockets/4.3.0", - "hashPath": "system.net.websockets.4.3.0.nupkg.sha512" - }, - "System.Net.WebSockets.Client/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-QjcP33TONz6wS5+3B4JKHXxQp9b5sexHH/eGTBwG2ZhlaThGvZGm3mMEHyJB6rrLsSah3h1Gc/OkJ162iavkjA==", - "path": "system.net.websockets.client/4.3.0", - "hashPath": "system.net.websockets.client.4.3.0.nupkg.sha512" - }, - "System.ObjectModel/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-bdX+80eKv9bN6K4N+d77OankKHGn6CH711a6fcOpMQu2Fckp/Ft4L/kW9WznHpyR0NRAvJutzOMHNNlBGvxQzQ==", - "path": "system.objectmodel/4.3.0", - "hashPath": "system.objectmodel.4.3.0.nupkg.sha512" - }, - "System.Private.DataContractSerialization/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-yDaJ2x3mMmjdZEDB4IbezSnCsnjQ4BxinKhRAaP6kEgL6Bb6jANWphs5SzyD8imqeC/3FxgsuXT6ykkiH1uUmA==", - "path": "system.private.datacontractserialization/4.3.0", - "hashPath": "system.private.datacontractserialization.4.3.0.nupkg.sha512" - }, - "System.Private.ServiceModel/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-N41KTsqCL7j4jwSToNOcZJs14EVtT4bHfiztE1e+mMNqFfmITIjOoWgI1I7DpR2NrbkV5vj4f+cSGg3FszHPmA==", - "path": "system.private.servicemodel/4.3.0", - "hashPath": "system.private.servicemodel.4.3.0.nupkg.sha512" - }, - "System.Reflection/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-KMiAFoW7MfJGa9nDFNcfu+FpEdiHpWgTcS2HdMpDvt9saK3y/G4GwprPyzqjFH9NTaGPQeWNHU+iDlDILj96aQ==", - "path": "system.reflection/4.3.0", - "hashPath": "system.reflection.4.3.0.nupkg.sha512" - }, - "System.Reflection.DispatchProxy/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-vFln4g7zbLRyJbioExbMaW4BGuE2urDE2IKQk02x1y1uhQWntD+4rcYA4xQGJ19PlMdYPMWExHVQj3zKDODBFw==", - "path": "system.reflection.dispatchproxy/4.3.0", - "hashPath": "system.reflection.dispatchproxy.4.3.0.nupkg.sha512" - }, - "System.Reflection.Emit/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-228FG0jLcIwTVJyz8CLFKueVqQK36ANazUManGaJHkO0icjiIypKW7YLWLIWahyIkdh5M7mV2dJepllLyA1SKg==", - "path": "system.reflection.emit/4.3.0", - "hashPath": "system.reflection.emit.4.3.0.nupkg.sha512" - }, - "System.Reflection.Emit.ILGeneration/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-59tBslAk9733NXLrUJrwNZEzbMAcu8k344OYo+wfSVygcgZ9lgBdGIzH/nrg3LYhXceynyvTc8t5/GD4Ri0/ng==", - "path": "system.reflection.emit.ilgeneration/4.3.0", - "hashPath": "system.reflection.emit.ilgeneration.4.3.0.nupkg.sha512" - }, - "System.Reflection.Emit.Lightweight/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-oadVHGSMsTmZsAF864QYN1t1QzZjIcuKU3l2S9cZOwDdDueNTrqq1yRj7koFfIGEnKpt6NjpL3rOzRhs4ryOgA==", - "path": "system.reflection.emit.lightweight/4.3.0", - "hashPath": "system.reflection.emit.lightweight.4.3.0.nupkg.sha512" - }, - "System.Reflection.Extensions/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-rJkrJD3kBI5B712aRu4DpSIiHRtr6QlfZSQsb0hYHrDCZORXCFjQfoipo2LaMUHoT9i1B7j7MnfaEKWDFmFQNQ==", - "path": "system.reflection.extensions/4.3.0", - "hashPath": "system.reflection.extensions.4.3.0.nupkg.sha512" - }, - "System.Reflection.Primitives/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-5RXItQz5As4xN2/YUDxdpsEkMhvw3e6aNveFXUn4Hl/udNTCNhnKp8lT9fnc3MhvGKh1baak5CovpuQUXHAlIA==", - "path": "system.reflection.primitives/4.3.0", - "hashPath": "system.reflection.primitives.4.3.0.nupkg.sha512" - }, - "System.Reflection.TypeExtensions/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-7u6ulLcZbyxB5Gq0nMkQttcdBTx57ibzw+4IOXEfR+sXYQoHvjW5LTLyNr8O22UIMrqYbchJQJnos4eooYzYJA==", - "path": "system.reflection.typeextensions/4.3.0", - "hashPath": "system.reflection.typeextensions.4.3.0.nupkg.sha512" - }, - "System.Resources.ResourceManager/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-/zrcPkkWdZmI4F92gL/TPumP98AVDu/Wxr3CSJGQQ+XN6wbRZcyfSKVoPo17ilb3iOr0cCRqJInGwNMolqhS8A==", - "path": "system.resources.resourcemanager/4.3.0", - "hashPath": "system.resources.resourcemanager.4.3.0.nupkg.sha512" - }, - "System.Runtime/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==", - "path": "system.runtime/4.3.0", - "hashPath": "system.runtime.4.3.0.nupkg.sha512" - }, - "System.Runtime.CompilerServices.Unsafe/4.4.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-9dLLuBxr5GNmOfl2jSMcsHuteEg32BEfUotmmUkmZjpR3RpVHE8YQwt0ow3p6prwA1ME8WqDVZqrr8z6H8G+Kw==", - "path": "system.runtime.compilerservices.unsafe/4.4.0", - "hashPath": "system.runtime.compilerservices.unsafe.4.4.0.nupkg.sha512" - }, - "System.Runtime.Extensions/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-guW0uK0fn5fcJJ1tJVXYd7/1h5F+pea1r7FLSOz/f8vPEqbR2ZAknuRDvTQ8PzAilDveOxNjSfr0CHfIQfFk8g==", - "path": "system.runtime.extensions/4.3.0", - "hashPath": "system.runtime.extensions.4.3.0.nupkg.sha512" - }, - "System.Runtime.Handles/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-OKiSUN7DmTWeYb3l51A7EYaeNMnvxwE249YtZz7yooT4gOZhmTjIn48KgSsw2k2lYdLgTKNJw/ZIfSElwDRVgg==", - "path": "system.runtime.handles/4.3.0", - "hashPath": "system.runtime.handles.4.3.0.nupkg.sha512" - }, - "System.Runtime.InteropServices/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-uv1ynXqiMK8mp1GM3jDqPCFN66eJ5w5XNomaK2XD+TuCroNTLFGeZ+WCmBMcBDyTFKou3P6cR6J/QsaqDp7fGQ==", - "path": "system.runtime.interopservices/4.3.0", - "hashPath": "system.runtime.interopservices.4.3.0.nupkg.sha512" - }, - "System.Runtime.InteropServices.RuntimeInformation/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-cbz4YJMqRDR7oLeMRbdYv7mYzc++17lNhScCX0goO2XpGWdvAt60CGN+FHdePUEHCe/Jy9jUlvNAiNdM+7jsOw==", - "path": "system.runtime.interopservices.runtimeinformation/4.3.0", - "hashPath": "system.runtime.interopservices.runtimeinformation.4.3.0.nupkg.sha512" - }, - "System.Runtime.Numerics/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-yMH+MfdzHjy17l2KESnPiF2dwq7T+xLnSJar7slyimAkUh/gTrS9/UQOtv7xarskJ2/XDSNvfLGOBQPjL7PaHQ==", - "path": "system.runtime.numerics/4.3.0", - "hashPath": "system.runtime.numerics.4.3.0.nupkg.sha512" - }, - "System.Runtime.Serialization.Primitives/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-Wz+0KOukJGAlXjtKr+5Xpuxf8+c8739RI1C+A2BoQZT+wMCCoMDDdO8/4IRHfaVINqL78GO8dW8G2lW/e45Mcw==", - "path": "system.runtime.serialization.primitives/4.3.0", - "hashPath": "system.runtime.serialization.primitives.4.3.0.nupkg.sha512" - }, - "System.Runtime.Serialization.Xml/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-nUQx/5OVgrqEba3+j7OdiofvVq9koWZAC7Z3xGI8IIViZqApWnZ5+lLcwYgTlbkobrl/Rat+Jb8GeD4WQESD2A==", - "path": "system.runtime.serialization.xml/4.3.0", - "hashPath": "system.runtime.serialization.xml.4.3.0.nupkg.sha512" - }, - "System.Security.Claims/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-P/+BR/2lnc4PNDHt/TPBAWHVMLMRHsyYZbU1NphW4HIWzCggz8mJbTQQ3MKljFE7LS3WagmVFuBgoLcFzYXlkA==", - "path": "system.security.claims/4.3.0", - "hashPath": "system.security.claims.4.3.0.nupkg.sha512" - }, - "System.Security.Cryptography.Algorithms/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-W1kd2Y8mYSCgc3ULTAZ0hOP2dSdG5YauTb1089T0/kRcN2MpSAW1izOFROrJgxSlMn3ArsgHXagigyi+ibhevg==", - "path": "system.security.cryptography.algorithms/4.3.0", - "hashPath": "system.security.cryptography.algorithms.4.3.0.nupkg.sha512" - }, - "System.Security.Cryptography.Cng/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-03idZOqFlsKRL4W+LuCpJ6dBYDUWReug6lZjBa3uJWnk5sPCUXckocevTaUA8iT/MFSrY/2HXkOt753xQ/cf8g==", - "path": "system.security.cryptography.cng/4.3.0", - "hashPath": "system.security.cryptography.cng.4.3.0.nupkg.sha512" - }, - "System.Security.Cryptography.Csp/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-X4s/FCkEUnRGnwR3aSfVIkldBmtURMhmexALNTwpjklzxWU7yjMk7GHLKOZTNkgnWnE0q7+BCf9N2LVRWxewaA==", - "path": "system.security.cryptography.csp/4.3.0", - "hashPath": "system.security.cryptography.csp.4.3.0.nupkg.sha512" - }, - "System.Security.Cryptography.Encoding/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-1DEWjZZly9ae9C79vFwqaO5kaOlI5q+3/55ohmq/7dpDyDfc8lYe7YVxJUZ5MF/NtbkRjwFRo14yM4OEo9EmDw==", - "path": "system.security.cryptography.encoding/4.3.0", - "hashPath": "system.security.cryptography.encoding.4.3.0.nupkg.sha512" - }, - "System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-h4CEgOgv5PKVF/HwaHzJRiVboL2THYCou97zpmhjghx5frc7fIvlkY1jL+lnIQyChrJDMNEXS6r7byGif8Cy4w==", - "path": "system.security.cryptography.openssl/4.3.0", - "hashPath": "system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "System.Security.Cryptography.Primitives/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-7bDIyVFNL/xKeFHjhobUAQqSpJq9YTOpbEs6mR233Et01STBMXNAc/V+BM6dwYGc95gVh/Zf+iVXWzj3mE8DWg==", - "path": "system.security.cryptography.primitives/4.3.0", - "hashPath": "system.security.cryptography.primitives.4.3.0.nupkg.sha512" - }, - "System.Security.Cryptography.X509Certificates/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-t2Tmu6Y2NtJ2um0RtcuhP7ZdNNxXEgUm2JeoA/0NvlMjAhKCnM1NX07TDl3244mVp3QU6LPEhT3HTtH1uF7IYw==", - "path": "system.security.cryptography.x509certificates/4.3.0", - "hashPath": "system.security.cryptography.x509certificates.4.3.0.nupkg.sha512" - }, - "System.Security.Principal/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-I1tkfQlAoMM2URscUtpcRo/hX0jinXx6a/KUtEQoz3owaYwl3qwsO8cbzYVVnjxrzxjHo3nJC+62uolgeGIS9A==", - "path": "system.security.principal/4.3.0", - "hashPath": "system.security.principal.4.3.0.nupkg.sha512" - }, - "System.Security.Principal.Windows/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-HVL1rvqYtnRCxFsYag/2le/ZfKLK4yMw79+s6FmKXbSCNN0JeAhrYxnRAHFoWRa0dEojsDcbBSpH3l22QxAVyw==", - "path": "system.security.principal.windows/4.3.0", - "hashPath": "system.security.principal.windows.4.3.0.nupkg.sha512" - }, - "System.ServiceModel.Primitives/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-7jbo99dT5rmefyfCsQKIWznK/1XaYI3hiIJJmP+fdwucrSESWTSPDxBJrqE42VFsN1wgRhDLiEA78FTUc4jS8g==", - "path": "system.servicemodel.primitives/4.3.0", - "hashPath": "system.servicemodel.primitives.4.3.0.nupkg.sha512" - }, - "System.Text.Encoding/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==", - "path": "system.text.encoding/4.3.0", - "hashPath": "system.text.encoding.4.3.0.nupkg.sha512" - }, - "System.Text.Encoding.Extensions/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-YVMK0Bt/A43RmwizJoZ22ei2nmrhobgeiYwFzC4YAN+nue8RF6djXDMog0UCn+brerQoYVyaS+ghy9P/MUVcmw==", - "path": "system.text.encoding.extensions/4.3.0", - "hashPath": "system.text.encoding.extensions.4.3.0.nupkg.sha512" - }, - "System.Text.Encodings.Web/4.4.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-l/tYeikqMHX2MD2jzrHDfR9ejrpTTF7wvAEbR51AMvzip1wSJgiURbDik4iv/w7ZgytmTD/hlwpplEhF9bmFNw==", - "path": "system.text.encodings.web/4.4.0", - "hashPath": "system.text.encodings.web.4.4.0.nupkg.sha512" - }, - "System.Text.RegularExpressions/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-RpT2DA+L660cBt1FssIE9CAGpLFdFPuheB7pLpKpn6ZXNby7jDERe8Ua/Ne2xGiwLVG2JOqziiaVCGDon5sKFA==", - "path": "system.text.regularexpressions/4.3.0", - "hashPath": "system.text.regularexpressions.4.3.0.nupkg.sha512" - }, - "System.Threading/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-VkUS0kOBcUf3Wwm0TSbrevDDZ6BlM+b/HRiapRFWjM5O0NS0LviG0glKmFK+hhPDd1XFeSdU1GmlLhb2CoVpIw==", - "path": "system.threading/4.3.0", - "hashPath": "system.threading.4.3.0.nupkg.sha512" - }, - "System.Threading.Overlapped/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-m3HQ2dPiX/DSTpf+yJt8B0c+SRvzfqAJKx+QDWi+VLhz8svLT23MVjEOHPF/KiSLeArKU/iHescrbLd3yVgyNg==", - "path": "system.threading.overlapped/4.3.0", - "hashPath": "system.threading.overlapped.4.3.0.nupkg.sha512" - }, - "System.Threading.Tasks/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-LbSxKEdOUhVe8BezB/9uOGGppt+nZf6e1VFyw6v3DN6lqitm0OSn2uXMOdtP0M3W4iMcqcivm2J6UgqiwwnXiA==", - "path": "system.threading.tasks/4.3.0", - "hashPath": "system.threading.tasks.4.3.0.nupkg.sha512" - }, - "System.Threading.Tasks.Extensions/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-npvJkVKl5rKXrtl1Kkm6OhOUaYGEiF9wFbppFRWSMoApKzt2PiPHT2Bb8a5sAWxprvdOAtvaARS9QYMznEUtug==", - "path": "system.threading.tasks.extensions/4.3.0", - "hashPath": "system.threading.tasks.extensions.4.3.0.nupkg.sha512" - }, - "System.Threading.Thread/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-OHmbT+Zz065NKII/ZHcH9XO1dEuLGI1L2k7uYss+9C1jLxTC9kTZZuzUOyXHayRk+dft9CiDf3I/QZ0t8JKyBQ==", - "path": "system.threading.thread/4.3.0", - "hashPath": "system.threading.thread.4.3.0.nupkg.sha512" - }, - "System.Threading.ThreadPool/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-k/+g4b7vjdd4aix83sTgC9VG6oXYKAktSfNIJUNGxPEj7ryEOfzHHhfnmsZvjxawwcD9HyWXKCXmPjX8U4zeSw==", - "path": "system.threading.threadpool/4.3.0", - "hashPath": "system.threading.threadpool.4.3.0.nupkg.sha512" - }, - "System.Threading.Timer/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-Z6YfyYTCg7lOZjJzBjONJTFKGN9/NIYKSxhU5GRd+DTwHSZyvWp1xuI5aR+dLg+ayyC5Xv57KiY4oJ0tMO89fQ==", - "path": "system.threading.timer/4.3.0", - "hashPath": "system.threading.timer.4.3.0.nupkg.sha512" - }, - "System.Xml.ReaderWriter/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-GrprA+Z0RUXaR4N7/eW71j1rgMnEnEVlgii49GZyAjTH7uliMnrOU3HNFBr6fEDBCJCIdlVNq9hHbaDR621XBA==", - "path": "system.xml.readerwriter/4.3.0", - "hashPath": "system.xml.readerwriter.4.3.0.nupkg.sha512" - }, - "System.Xml.XDocument/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-5zJ0XDxAIg8iy+t4aMnQAu0MqVbqyvfoUVl1yDV61xdo3Vth45oA2FoY4pPkxYAH5f8ixpmTqXeEIya95x0aCQ==", - "path": "system.xml.xdocument/4.3.0", - "hashPath": "system.xml.xdocument.4.3.0.nupkg.sha512" - }, - "System.Xml.XmlDocument/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-lJ8AxvkX7GQxpC6GFCeBj8ThYVyQczx2+f/cWHJU8tjS7YfI6Cv6bon70jVEgs2CiFbmmM8b9j1oZVx0dSI2Ww==", - "path": "system.xml.xmldocument/4.3.0", - "hashPath": "system.xml.xmldocument.4.3.0.nupkg.sha512" - }, - "System.Xml.XmlSerializer/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-MYoTCP7EZ98RrANESW05J5ZwskKDoN0AuZ06ZflnowE50LTpbR5yRg3tHckTVm5j/m47stuGgCrCHWePyHS70Q==", - "path": "system.xml.xmlserializer/4.3.0", - "hashPath": "system.xml.xmlserializer.4.3.0.nupkg.sha512" - }, - "ServiceStack.Client/1.0.0": { - "type": "project", - "serviceable": false, - "sha512": "" - }, - "ServiceStack.Common/1.0.0": { - "type": "project", - "serviceable": false, - "sha512": "" - }, - "ServiceStack.Interfaces/1.0.0": { - "type": "project", - "serviceable": false, - "sha512": "" - }, - "ServiceStack.Text/5.0.0.0": { - "type": "reference", - "serviceable": false, - "sha512": "" - } - } -} \ No newline at end of file diff --git a/lib/netstandard2.0/ServiceStack.dll b/lib/netstandard2.0/ServiceStack.dll deleted file mode 100644 index 8003182f6..000000000 Binary files a/lib/netstandard2.0/ServiceStack.dll and /dev/null differ diff --git a/src/.nuget/NuGet.config b/src/.nuget/NuGet.config deleted file mode 100644 index fe6a62c92..000000000 --- a/src/.nuget/NuGet.config +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/.nuget/NuGet.exe b/src/.nuget/NuGet.exe deleted file mode 100644 index 6bb79fe53..000000000 Binary files a/src/.nuget/NuGet.exe and /dev/null differ diff --git a/src/.nuget/NuGet.targets b/src/.nuget/NuGet.targets deleted file mode 100644 index f2d0eb36f..000000000 --- a/src/.nuget/NuGet.targets +++ /dev/null @@ -1,77 +0,0 @@ - - - - $(MSBuildProjectDirectory)\..\ - - - - - $([System.IO.Path]::Combine($(SolutionDir), ".nuget")) - $([System.IO.Path]::Combine($(ProjectDir), "packages.config")) - $([System.IO.Path]::Combine($(SolutionDir), "packages")) - - - - - $(SolutionDir).nuget - packages.config - $(SolutionDir)packages - - - - - $(NuGetToolsPath)\NuGet.exe - "$(NuGetExePath)" - mono --runtime=v4.0.30319 $(NuGetExePath) - - $(TargetDir.Trim('\\')) - - - "" - - - false - - - false - - - $(NuGetCommand) install "$(PackagesConfig)" -source $(PackageSources) -o "$(PackagesDir)" - $(NuGetCommand) pack "$(ProjectPath)" -p Configuration=$(Configuration) -o "$(PackageOutputDir)" -symbols - - - - RestorePackages; - $(BuildDependsOn); - - - - - $(BuildDependsOn); - BuildPackage; - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Directory.Build.props b/src/Directory.Build.props new file mode 100644 index 000000000..9a112079b --- /dev/null +++ b/src/Directory.Build.props @@ -0,0 +1,54 @@ + + + + 6.0.3 + ServiceStack + ServiceStack, Inc. + © 2008-2022 ServiceStack, Inc + true + https://github.com/ServiceStack/ServiceStack.Text + https://servicestack.net/terms + https://servicestack.net/img/logo-64.png + https://docs.servicestack.net/release-notes-history + git + https://github.com/ServiceStack/ServiceStack.Text.git + embedded + latest + true + true + false + + + + true + true + + + + $(DefineConstants);NETFX;NET45;NET472 + True + False + ../servicestack.snk + + + + $(DefineConstants);NETSTANDARD;NETSTANDARD2_0 + + + + $(DefineConstants);NET6_0;NET6_0_OR_GREATER + + + + $(DefineConstants);NETCORE;NETCORE_SUPPORT + + + + + + + + DEBUG + + + diff --git a/src/ServiceStack.Text.sln b/src/ServiceStack.Text.sln index ab8a83331..a2c32f26d 100644 --- a/src/ServiceStack.Text.sln +++ b/src/ServiceStack.Text.sln @@ -1,15 +1,18 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 + +Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.26730.3 +VisualStudioVersion = 15.0.27004.2009 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{F7FB50ED-EAFF-4839-935A-5BB4A4158245}" ProjectSection(SolutionItems) = preProject ..\build\build.bat = ..\build\build.bat ..\build\build.proj = ..\build\build.proj ..\build\build.tasks = ..\build\build.tasks - ..\build\copy.bat = ..\build\copy.bat ..\README.md = ..\README.md - ..\NuGet\ServiceStack.Text\servicestack.text.nuspec = ..\NuGet\ServiceStack.Text\servicestack.text.nuspec + Directory.Build.props = Directory.Build.props + ServiceStack.Text\ServiceStack.Text.Core.csproj = ServiceStack.Text\ServiceStack.Text.Core.csproj + ..\build\build-core.proj = ..\build\build-core.proj + ..\tests\Directory.Build.props = ..\tests\Directory.Build.props EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServiceStack.Text", "ServiceStack.Text\ServiceStack.Text.csproj", "{579B3FDB-CDAD-44E1-8417-885C38E49A0E}" @@ -18,6 +21,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServiceStack.Text.Tests", " EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServiceStack.Text.TestsConsole", "..\tests\ServiceStack.Text.TestsConsole\ServiceStack.Text.TestsConsole.csproj", "{DD3BEB33-2509-423A-8545-CE1A83684530}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceStack.Text.Benchmarks", "..\tests\ServiceStack.Text.Benchmarks\ServiceStack.Text.Benchmarks.csproj", "{FF1E5617-FD95-496F-B591-17DA6E0B364F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Northwind.Common", "..\tests\Northwind.Common\Northwind.Common.csproj", "{573926E5-F332-462D-A740-31706708A032}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -36,6 +43,13 @@ Global {DD3BEB33-2509-423A-8545-CE1A83684530}.Debug|Any CPU.Build.0 = Debug|Any CPU {DD3BEB33-2509-423A-8545-CE1A83684530}.Release|Any CPU.ActiveCfg = Release|Any CPU {DD3BEB33-2509-423A-8545-CE1A83684530}.Release|Any CPU.Build.0 = Release|Any CPU + {FF1E5617-FD95-496F-B591-17DA6E0B364F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FF1E5617-FD95-496F-B591-17DA6E0B364F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FF1E5617-FD95-496F-B591-17DA6E0B364F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FF1E5617-FD95-496F-B591-17DA6E0B364F}.Release|Any CPU.Build.0 = Release|Any CPU + {573926E5-F332-462D-A740-31706708A032}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {573926E5-F332-462D-A740-31706708A032}.Release|Any CPU.ActiveCfg = Release|Any CPU + {573926E5-F332-462D-A740-31706708A032}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/ServiceStack.Text/AssemblyUtils.cs b/src/ServiceStack.Text/AssemblyUtils.cs index 02208ef6d..e22e260e5 100644 --- a/src/ServiceStack.Text/AssemblyUtils.cs +++ b/src/ServiceStack.Text/AssemblyUtils.cs @@ -26,8 +26,7 @@ public static class AssemblyUtils /// public static Type FindType(string typeName) { - Type type = null; - if (TypeCache.TryGetValue(typeName, out type)) return type; + if (TypeCache.TryGetValue(typeName, out var type)) return type; type = Type.GetType(typeName); if (type == null) @@ -62,7 +61,7 @@ public static Type MainInterface() var interfaceType = t.GetInterfaces().FirstOrDefault(i => !t.GetInterfaces().Any(i2 => i2.GetInterfaces().Contains(i))); if (interfaceType != null) return interfaceType; } - return t; // not safe to use interface, as it might be a superclass's one. + return t; // not safe to use interface, as it might be a superclass one. } /// diff --git a/src/ServiceStack.Text/AutoMappingUtils.cs b/src/ServiceStack.Text/AutoMappingUtils.cs index 471155d2f..15e15bd2b 100644 --- a/src/ServiceStack.Text/AutoMappingUtils.cs +++ b/src/ServiceStack.Text/AutoMappingUtils.cs @@ -17,57 +17,100 @@ namespace ServiceStack [DataContract(Namespace = "http://schemas.servicestack.net/types")] public class CustomHttpResult { } + /// + /// Customize ServiceStack AutoMapping Behavior + /// + public static class AutoMapping + { + /// + /// Register Type to Type AutoMapping converter + /// + public static void RegisterConverter(Func converter) + { + JsConfig.InitStatics(); + AutoMappingUtils.converters[Tuple.Create(typeof(From), typeof(To))] = x => converter((From)x); + } + + /// + /// Ignore Type to Type Mapping (including collections containing them) + /// + public static void IgnoreMapping() => IgnoreMapping(typeof(From), typeof(To)); + + /// + /// Ignore Type to Type Mapping (including collections containing them) + /// + public static void IgnoreMapping(Type fromType, Type toType) + { + JsConfig.InitStatics(); + AutoMappingUtils.ignoreMappings[Tuple.Create(fromType, toType)] = true; + } + + public static void RegisterPopulator(Action populator) + { + JsConfig.InitStatics(); + AutoMappingUtils.populators[Tuple.Create(typeof(Target), typeof(Source))] = (a,b) => populator((Target)a,(Source)b); + } + } + public static class AutoMappingUtils { - public static T ConvertTo(this object from) + internal static readonly ConcurrentDictionary, GetMemberDelegate> converters + = new ConcurrentDictionary, GetMemberDelegate>(); + + internal static readonly ConcurrentDictionary, PopulateMemberDelegate> populators + = new ConcurrentDictionary, PopulateMemberDelegate>(); + + internal static readonly ConcurrentDictionary,bool> ignoreMappings + = new ConcurrentDictionary,bool>(); + + public static void Reset() { - if (from == null) - return default(T); + converters.Clear(); + populators.Clear(); + ignoreMappings.Clear(); + AssignmentDefinitionCache.Clear(); + } - var fromType = from.GetType(); - if (fromType == typeof(T)) - return (T)from; + public static bool ShouldIgnoreMapping(Type fromType, Type toType) => + ignoreMappings.ContainsKey(Tuple.Create(fromType, toType)); - if (fromType.IsValueType || typeof(T).IsValueType) - { - if (!fromType.IsEnum && !typeof(T).IsEnum) - { - if (typeof(T) == typeof(char) && from is string s) - return (T)(s.Length > 0 ? (object) s[0] : null); - if (typeof(T) == typeof(string) && from is char c) - return (T)(object)c.ToString(); + public static GetMemberDelegate GetConverter(Type fromType, Type toType) + { + if (converters.IsEmpty) + return null; - var destNumberType = DynamicNumber.GetNumber(typeof(T)); - var value = destNumberType?.ConvertFrom(from); - if (value != null) - { - if (typeof(T) == typeof(char)) - return (T)(object)value.ToString()[0]; + var key = Tuple.Create(fromType, toType); + return converters.TryGetValue(key, out var converter) + ? converter + : null; + } - return (T)value; - } + public static PopulateMemberDelegate GetPopulator(Type targetType, Type sourceType) + { + if (populators.IsEmpty) + return null; - if (typeof(T) == typeof(string)) - { - var srcNumberType = DynamicNumber.GetNumber(from.GetType()); - if (srcNumberType != null) - return (T)(object)srcNumberType.ToString(from); - } - } + var key = Tuple.Create(targetType, sourceType); + return populators.TryGetValue(key, out var populator) + ? populator + : null; + } - return (T)ChangeValueType(from, typeof(T)); - } + public static T ConvertTo(this object from, T defaultValue) => + from == null || (from is string s && s == string.Empty) + ? defaultValue + : from.ConvertTo(); - if (typeof(IEnumerable).IsAssignableFrom(typeof(T))) - { - var listResult = TranslateListWithElements.TryTranslateCollections( - fromType, typeof(T), from); + public static T ConvertTo(this object from) => from.ConvertTo(skipConverters:false); + public static T ConvertTo(this object from, bool skipConverters) + { + if (from == null) + return default; - return (T)listResult; - } + if (from is T t) + return t; - var to = typeof(T).CreateInstance(); - return to.PopulateWith(from); + return (T)ConvertTo(from, typeof(T), skipConverters); } public static T CreateCopy(this T from) @@ -75,11 +118,9 @@ public static T CreateCopy(this T from) if (typeof(T).IsValueType) return (T)ChangeValueType(from, typeof(T)); - if (typeof(IEnumerable).IsAssignableFrom(typeof(T))) + if (typeof(IEnumerable).IsAssignableFrom(typeof(T)) && typeof(T) != typeof(string)) { - var listResult = TranslateListWithElements.TryTranslateCollections( - from.GetType(), typeof(T), from); - + var listResult = TranslateListWithElements.TryTranslateCollections(from.GetType(), typeof(T), from); return (T)listResult; } @@ -93,38 +134,190 @@ public static To ThenDo(this To to, Action fn) return to; } - public static object ConvertTo(this object from, Type type) + public static object ConvertTo(this object from, Type toType) => from.ConvertTo(toType, skipConverters: false); + public static object ConvertTo(this object from, Type toType, bool skipConverters) { if (from == null) return null; - if (from.GetType() == type) + var fromType = from.GetType(); + if (ShouldIgnoreMapping(fromType, toType)) + return null; + + if (!skipConverters) + { + var converter = GetConverter(fromType, toType); + if (converter != null) + return converter(from); + } + + if (fromType == toType || toType == typeof(object)) return from; - if (from.GetType().IsValueType || type.IsValueType) - return ChangeValueType(from, type); + if (fromType.IsValueType || toType.IsValueType) + return ChangeValueType(from, toType); - if (typeof(IEnumerable).IsAssignableFrom(type)) - { - var listResult = TranslateListWithElements.TryTranslateCollections( - from.GetType(), type, from); + var mi = GetImplicitCastMethod(fromType, toType); + if (mi != null) + return mi.Invoke(null, new[] { from }); + if (from is string str) + return TypeSerializer.DeserializeFromString(str, toType); + if (from is ReadOnlyMemory rom) + return TypeSerializer.DeserializeFromSpan(toType, rom.Span); + + if (toType == typeof(string)) + return from.ToJsv(); + + if (typeof(IEnumerable).IsAssignableFrom(toType)) + { + var listResult = TryConvertCollections(fromType, toType, from); return listResult; } - var to = type.CreateInstance(); - return to.PopulateInstance(from); + if (from is IEnumerable> objDict) + return objDict.FromObjectDictionary(toType); + + if (from is IEnumerable> strDict) + return strDict.ToObjectDictionary().FromObjectDictionary(toType); + + var to = toType.CreateInstance(); + return to.PopulateWith(from); } - private static object ChangeValueType(object from, Type type) + public static MethodInfo GetImplicitCastMethod(Type fromType, Type toType) + { + foreach (var mi in fromType.GetMethods(BindingFlags.Public | BindingFlags.Static)) + { + if (mi.Name == "op_Implicit" && mi.ReturnType == toType && + mi.GetParameters().FirstOrDefault()?.ParameterType == fromType) + { + return mi; + } + } + foreach (var mi in toType.GetMethods(BindingFlags.Public | BindingFlags.Static)) + { + if (mi.Name == "op_Implicit" && mi.ReturnType == toType && + mi.GetParameters().FirstOrDefault()?.ParameterType == fromType) + { + return mi; + } + } + return null; + } + + public static MethodInfo GetExplicitCastMethod(Type fromType, Type toType) + { + foreach (var mi in toType.GetMethods(BindingFlags.Public | BindingFlags.Static)) + { + if (mi.Name == "op_Explicit" && mi.ReturnType == toType && + mi.GetParameters().FirstOrDefault()?.ParameterType == fromType) + { + return mi; + } + } + foreach (var mi in fromType.GetMethods(BindingFlags.Public | BindingFlags.Static)) + { + if (mi.Name == "op_Explicit" && mi.ReturnType == toType && + mi.GetParameters().FirstOrDefault()?.ParameterType == fromType) + { + return mi; + } + } + return null; + } + + public static object ChangeValueType(object from, Type toType) { - if (from is string strValue) - return TypeSerializer.DeserializeFromString(strValue, type); + var s = from as string; + + var fromType = from.GetType(); + if (!fromType.IsEnum && !toType.IsEnum) + { + var toString = toType == typeof(string); + if (toType == typeof(char) && s != null) + return s.Length > 0 ? (object) s[0] : null; + if (toString && from is char c) + return c.ToString(); + if (toType == typeof(TimeSpan) && from is long ticks) + return new TimeSpan(ticks); + if (toType == typeof(long) && from is TimeSpan time) + return time.Ticks; + + var destNumberType = DynamicNumber.GetNumber(toType); + if (destNumberType != null) + { + if (s != null && s == string.Empty) + return destNumberType.DefaultValue; + + var value = destNumberType.ConvertFrom(from); + if (value != null) + { + return toType == typeof(char) + ? value.ToString()[0] + : value; + } + } - if (type == typeof(string)) + if (toString) + { + var srcNumberType = DynamicNumber.GetNumber(from.GetType()); + if (srcNumberType != null) + return srcNumberType.ToString(from); + } + } + + var mi = GetImplicitCastMethod(fromType, toType); + if (mi != null) + return mi.Invoke(null, new[] { from }); + + mi = GetExplicitCastMethod(fromType, toType); + if (mi != null) + return mi.Invoke(null, new[] { from }); + + if (s != null) + return TypeSerializer.DeserializeFromString(s, toType); + + if (toType == typeof(string)) return from.ToJsv(); - return Convert.ChangeType(from, type, provider: null); + if (toType.HasInterface(typeof(IConvertible))) + { + return Convert.ChangeType(from, toType, provider: null); + } + + var fromKvpType = fromType.GetTypeWithGenericTypeDefinitionOf(typeof(KeyValuePair<,>)); + if (fromKvpType != null) + { + var fromProps = TypeProperties.Get(fromKvpType); + var fromKey = fromProps.GetPublicGetter("Key")(from); + var fromValue = fromProps.GetPublicGetter("Value")(from); + + var toKvpType = toType.GetTypeWithGenericTypeDefinitionOf(typeof(KeyValuePair<,>)); + if (toKvpType != null) + { + + var toKvpArgs = toKvpType.GetGenericArguments(); + var toCtor = toKvpType.GetConstructor(toKvpArgs); + var to = toCtor.Invoke(new[] { fromKey.ConvertTo(toKvpArgs[0]), fromValue.ConvertTo(toKvpArgs[1]) }); + return to; + } + + if (typeof(IDictionary).IsAssignableFrom(toType)) + { + var genericDef = toType.GetTypeWithGenericTypeDefinitionOf(typeof(IDictionary<,>)); + var toArgs = genericDef.GetGenericArguments(); + var toKeyType = toArgs[0]; + var toValueType = toArgs[1]; + + var to = (IDictionary)toType.CreateInstance(); + to["Key"] = fromKey.ConvertTo(toKeyType); + to["Value"] = fromValue.ConvertTo(toValueType); + return to; + } + } + + return TypeSerializer.DeserializeFromString(from.ToJsv(), toType); } public static object ChangeTo(this string strValue, Type type) @@ -149,8 +342,7 @@ public static List GetPropertyNames(this Type type) { lock (TypePropertyNamesMap) { - List propertyNames; - if (!TypePropertyNamesMap.TryGetValue(type, out propertyNames)) + if (!TypePropertyNamesMap.TryGetValue(type, out var propertyNames)) { propertyNames = type.Properties().ToList().ConvertAll(x => x.Name); TypePropertyNamesMap[type] = propertyNames; @@ -202,7 +394,7 @@ public static object PopulateWith(object obj) private static object PopulateObjectInternal(object obj, Dictionary recursionInfo) { if (obj == null) return null; - if (obj is string) return obj; // prevents it from dropping into the char[] Chars property. Sheesh + if (obj is string) return obj; // prevents it from dropping into the char[] Chars property. var type = obj.GetType(); var members = type.GetPublicMembers(); @@ -224,7 +416,8 @@ private static object PopulateObjectInternal(object obj, Dictionary r public static object GetDefaultValue(this Type type) { - if (!type.IsValueType) return null; + if (!type.IsValueType) + return null; if (DefaultValueTypes.TryGetValue(type, out var defaultValue)) return defaultValue; @@ -243,6 +436,10 @@ public static object GetDefaultValue(this Type type) return defaultValue; } + public static bool IsDefaultValue(object value) => IsDefaultValue(value, value?.GetType()); + public static bool IsDefaultValue(object value, Type valueType) => value == null + || (valueType.IsValueType && value.Equals(valueType.GetDefaultValue())); + private static readonly ConcurrentDictionary AssignmentDefinitionCache = new ConcurrentDictionary(); @@ -252,7 +449,6 @@ internal static AssignmentDefinition GetAssignmentDefinition(Type toType, Type f return AssignmentDefinitionCache.GetOrAdd(cacheKey, delegate { - var definition = new AssignmentDefinition { ToType = toType, @@ -264,8 +460,7 @@ internal static AssignmentDefinition GetAssignmentDefinition(Type toType, Type f foreach (var assignmentMember in readMap) { - AssignmentMember writeMember; - if (writeMap.TryGetValue(assignmentMember.Key, out writeMember)) + if (writeMap.TryGetValue(assignmentMember.Key, out var writeMember)) { definition.AddMatch(assignmentMember.Key, assignmentMember.Value, writeMember); } @@ -317,38 +512,7 @@ private static Dictionary GetMembers(Type type, bool i map[info.Name] = new AssignmentMember(fieldInfo.FieldType, fieldInfo); continue; } - - var methodInfo = info as MethodInfo; - if (methodInfo != null) - { - var parameterInfos = methodInfo.GetParameters(); - if (isReadable) - { - if (parameterInfos.Length == 0) - { - var name = info.Name.StartsWith("get_") ? info.Name.Substring(4) : info.Name; - if (!map.ContainsKey(name)) - { - map[name] = new AssignmentMember(methodInfo.ReturnType, methodInfo); - continue; - } - } - } - else - { - if (parameterInfos.Length == 1 && methodInfo.ReturnType == typeof(void)) - { - var name = info.Name.StartsWith("set_") ? info.Name.Substring(4) : info.Name; - if (!map.ContainsKey(name)) - { - map[name] = new AssignmentMember(parameterInfos[0].ParameterType, methodInfo); - continue; - } - } - } - } } - return map; } @@ -363,18 +527,6 @@ public static To PopulateWith(this To to, From from) return to; } - public static object PopulateInstance(this object to, object from) - { - if (to == null || from == null) - return null; - - var assignmentDefinition = GetAssignmentDefinition(to.GetType(), from.GetType()); - - assignmentDefinition.PopulateWithNonDefaultValues(to, from); - - return to; - } - public static To PopulateWithNonDefaultValues(this To to, From from) { if (Equals(to, default(To)) || Equals(from, default(From))) return default(To); @@ -418,10 +570,10 @@ public static void SetProperty(this PropertyInfo propertyInfo, object obj, objec return; } - var propertySetMetodInfo = propertyInfo.GetSetMethod(nonPublic:true); - if (propertySetMetodInfo != null) + var propertySetMethodInfo = propertyInfo.GetSetMethod(nonPublic:true); + if (propertySetMethodInfo != null) { - propertySetMetodInfo.Invoke(obj, new[] { value }); + propertySetMethodInfo.Invoke(obj, new[] { value }); } } @@ -546,17 +698,17 @@ public static object CreateDefaultValue(Type type, Dictionary recursi } } - public static void SetGenericCollection(Type realisedListType, object genericObj, Dictionary recursionInfo) + public static void SetGenericCollection(Type realizedListType, object genericObj, Dictionary recursionInfo) { - var args = realisedListType.GetGenericArguments(); + var args = realizedListType.GetGenericArguments(); if (args.Length != 1) { - Tracer.Instance.WriteError("Found a generic list that does not take one generic argument: {0}", realisedListType); + Tracer.Instance.WriteError("Found a generic list that does not take one generic argument: {0}", realizedListType); return; } - var methodInfo = realisedListType.GetMethodInfo("Add"); + var methodInfo = realizedListType.GetMethodInfo("Add"); if (methodInfo != null) { var argValues = CreateDefaultValues(args, recursionInfo); @@ -617,6 +769,225 @@ public static IEnumerable> GetPropertyAttributes); + if (typeof(IList).IsAssignableFrom(toType) || toEnumObjs) + { + var to = (IList) (toType.IsArray || toEnumObjs ? new List() : toType.CreateInstance()); + var elType = toType.GetCollectionType(); + foreach (var item in values) + { + to.Add(elType != null ? item.ConvertTo(elType) : item); + } + if (elType != null && toType.IsArray) + { + var arr = Array.CreateInstance(elType, to.Count); + to.CopyTo(arr, 0); + return arr; + } + + return to; + } + + if (fromValue is IDictionary d) + { + var obj = toType.CreateInstance(); + switch (obj) + { + case List> toList: { + foreach (var key in d.Keys) + { + toList.Add(new KeyValuePair(key.ConvertTo(), d[key].ConvertTo())); + } + return toList; + } + case List> toObjList: { + foreach (var key in d.Keys) + { + toObjList.Add(new KeyValuePair(key.ConvertTo(), d[key])); + } + return toObjList; + } + case IDictionary toDict: { + if (toType.GetKeyValuePairsTypes(out var toKeyType, out var toValueType)) + { + foreach (var key in d.Keys) + { + var toKey = toKeyType != null + ? key.ConvertTo(toKeyType) + : key; + var toValue = d[key].ConvertTo(toValueType); + toDict[toKey] = toValue; + } + return toDict; + } + else + { + var from = fromValue.ToObjectDictionary(); + var to = from.FromObjectDictionary(toType); + return to; + } + } + } + } + + var genericDef = fromType.GetTypeWithGenericTypeDefinitionOf(typeof(IEnumerable<>)); + if (genericDef != null) + { + var genericEnumType = genericDef.GetGenericArguments()[0]; + var genericKvps = genericEnumType.GetTypeWithGenericTypeDefinitionOf(typeof(KeyValuePair<,>)); + if (genericKvps != null) + { + // Improve perf with Specialized handling of common KVP combinations + var obj = toType.CreateInstance(); + if (fromValue is IEnumerable> sKvps) + { + switch (obj) { + case IDictionary toDict: { + toType.GetKeyValuePairsTypes(out var toKeyType, out var toValueType); + foreach (var entry in sKvps) + { + var toKey = toKeyType != null + ? entry.Key.ConvertTo(toKeyType) + : entry.Key; + toDict[toKey] = toValueType != null + ? entry.Value.ConvertTo(toValueType) + : entry.Value; + } + return toDict; + } + case List> toList: { + foreach (var entry in sKvps) + { + toList.Add(new KeyValuePair(entry.Key, entry.Value)); + } + return toList; + } + case List> toObjList: { + foreach (var entry in sKvps) + { + toObjList.Add(new KeyValuePair(entry.Key, entry.Value)); + } + return toObjList; + } + } + } + else if (fromValue is IEnumerable> oKvps) + { + switch (obj) { + case IDictionary toDict: + { + toType.GetKeyValuePairsTypes(out var toKeyType, out var toValueType); + foreach (var entry in oKvps) + { + var toKey = entry.Key.ConvertTo(); + toDict[toKey] = toValueType != null + ? entry.Value.ConvertTo(toValueType) + : entry.Value; + } + return toDict; + } + case List> toList: { + foreach (var entry in oKvps) + { + toList.Add(new KeyValuePair(entry.Key, entry.Value.ConvertTo())); + } + return toList; + } + case List> toObjList: { + foreach (var entry in oKvps) + { + toObjList.Add(new KeyValuePair(entry.Key, entry.Value)); + } + return toObjList; + } + } + } + + + // Fallback for handling any KVP combo + var toKvpDefType = toType.GetKeyValuePairsTypeDef(); + switch (obj) { + case IDictionary toDict: + { + var keyProp = TypeProperties.Get(toKvpDefType).GetPublicGetter("Key"); + var valueProp = TypeProperties.Get(toKvpDefType).GetPublicGetter("Value"); + + foreach (var entry in values) + { + var toKvp = entry.ConvertTo(toKvpDefType); + var toKey = keyProp(toKvp); + var toValue = valueProp(toKvp); + toDict[toKey] = toValue; + } + return toDict; + } + case List> toStringList: { + foreach (var entry in values) + { + var toEntry = entry.ConvertTo(toKvpDefType); + toStringList.Add((KeyValuePair) toEntry); + } + return toStringList; + } + case List> toObjList: { + foreach (var entry in values) + { + var toEntry = entry.ConvertTo(toKvpDefType); + toObjList.Add((KeyValuePair) toEntry); + } + return toObjList; + } + case IEnumerable toList: + { + var addMethod = toType.GetMethod(nameof(IList.Add), new[] {toKvpDefType}); + if (addMethod != null) + { + foreach (var entry in values) + { + var toEntry = entry.ConvertTo(toKvpDefType); + addMethod.Invoke(toList, new[] { toEntry }); + } + return toList; + } + break; + } + } + } + } + + var fromElementType = fromType.GetCollectionType(); + var toElementType = toType.GetCollectionType(); + + if (fromElementType != null && toElementType != null && fromElementType != toElementType && + !(typeof(IDictionary).IsAssignableFrom(fromElementType) || typeof(IDictionary).IsAssignableFrom(toElementType))) + { + var to = new List(); + foreach (var item in values) + { + var toItem = item.ConvertTo(toElementType); + to.Add(toItem); + } + var ret = TranslateListWithElements.TryTranslateCollections(to.GetType(), toType, to); + return ret ?? fromValue; + } + } + else if (fromType.IsClass && + (typeof(IDictionary).IsAssignableFrom(toType) || + typeof(IEnumerable>).IsAssignableFrom(toType) || + typeof(IEnumerable>).IsAssignableFrom(toType))) + { + var fromDict = fromValue.ToObjectDictionary(); + return TryConvertCollections(fromType.GetType(), toType, fromDict); + } + + var listResult = TranslateListWithElements.TryTranslateCollections(fromType, toType, fromValue); + return listResult ?? fromValue; + } } public class AssignmentEntry @@ -698,6 +1069,34 @@ public AssignmentDefinition() public void AddMatch(string name, AssignmentMember readMember, AssignmentMember writeMember) { + if (AutoMappingUtils.ShouldIgnoreMapping(readMember.Type,writeMember.Type)) + return; + + // Ignore mapping collections if Element Types are ignored + if (typeof(IEnumerable).IsAssignableFrom(readMember.Type) && typeof(IEnumerable).IsAssignableFrom(writeMember.Type)) + { + var fromGenericDef = readMember.Type.GetTypeWithGenericTypeDefinitionOf(typeof(IDictionary<,>)); + var toGenericDef = writeMember.Type.GetTypeWithGenericTypeDefinitionOf(typeof(IDictionary<,>)); + if (fromGenericDef != null && toGenericDef != null) + { + // Check if to/from Key or Value Types are ignored + var fromArgs = fromGenericDef.GetGenericArguments(); + var toArgs = toGenericDef.GetGenericArguments(); + if (AutoMappingUtils.ShouldIgnoreMapping(fromArgs[0],toArgs[0])) + return; + if (AutoMappingUtils.ShouldIgnoreMapping(fromArgs[1],toArgs[1])) + return; + } + else if (readMember.Type != typeof(string) && writeMember.Type != typeof(string)) + { + var elFromType = readMember.Type.GetCollectionType(); + var elToType = writeMember.Type.GetCollectionType(); + + if (AutoMappingUtils.ShouldIgnoreMapping(elFromType,elToType)) + return; + } + } + this.AssignmentMemberMap[name] = new AssignmentEntry(name, readMember, writeMember); } @@ -741,20 +1140,24 @@ public void Populate(object to, object from, if (fromMember.PropertyInfo != null && propertyInfoPredicate != null) { - if (!propertyInfoPredicate(fromMember.PropertyInfo)) continue; + if (!propertyInfoPredicate(fromMember.PropertyInfo)) + continue; } var fromType = fromMember.Type; var toType = toMember.Type; try { + var fromValue = assignmentEntry.GetValueFn(from); - if (valuePredicate != null) + if (valuePredicate != null + && fromType == toType) // don't short-circuit nullable <-> non-null values { - if (!valuePredicate(fromValue, fromMember.PropertyInfo.PropertyType)) continue; + if (!valuePredicate(fromValue, fromMember.PropertyInfo.PropertyType)) + continue; } - + if (assignmentEntry.ConvertValueFn != null) { fromValue = assignmentEntry.ConvertValueFn(fromValue); @@ -770,12 +1173,17 @@ public void Populate(object to, object from, ToType.FullName, toType.Name, ex); } } + + var populator = AutoMappingUtils.GetPopulator(to.GetType(), from.GetType()); + populator?.Invoke(to, from); } } public delegate object GetMemberDelegate(object instance); public delegate object GetMemberDelegate(T instance); + public delegate void PopulateMemberDelegate(object target, object source); + public delegate void SetMemberDelegate(object instance, object value); public delegate void SetMemberDelegate(T instance, object value); public delegate void SetMemberRefDelegate(ref object instance, object propertyValue); @@ -787,12 +1195,16 @@ public static GetMemberDelegate CreateTypeConverter(Type fromType, Type toType) { if (fromType == toType) return null; + + var converter = AutoMappingUtils.GetConverter(fromType, toType); + if (converter != null) + return converter; if (fromType == typeof(string)) return fromValue => TypeSerializer.DeserializeFromString((string)fromValue, toType); if (toType == typeof(string)) - return TypeSerializer.SerializeToString; + return o => TypeSerializer.SerializeToString(o).StripQuotes(); var underlyingToType = Nullable.GetUnderlyingType(toType) ?? toType; var underlyingFromType = Nullable.GetUnderlyingType(fromType) ?? fromType; @@ -810,23 +1222,13 @@ public static GetMemberDelegate CreateTypeConverter(Type fromType, Type toType) if (underlyingToType.IsIntegerType()) return fromValue => Convert.ChangeType(fromValue, underlyingToType, null); } - else if (toType.IsNullableType()) + else if (typeof(IEnumerable).IsAssignableFrom(fromType) && underlyingToType != typeof(string)) { - return null; - } - else if (typeof(IEnumerable).IsAssignableFrom(fromType)) - { - return fromValue => - { - var listResult = TranslateListWithElements.TryTranslateCollections( - fromType, toType, fromValue); - - return listResult ?? fromValue; - }; + return fromValue => AutoMappingUtils.TryConvertCollections(fromType, underlyingToType, fromValue); } - else if (toType.IsValueType) + else if (underlyingToType.IsValueType) { - return fromValue => Convert.ChangeType(fromValue, toType, provider: null); + return fromValue => AutoMappingUtils.ChangeValueType(fromValue, underlyingToType); } else { @@ -834,6 +1236,8 @@ public static GetMemberDelegate CreateTypeConverter(Type fromType, Type toType) { if (fromValue == null) return fromValue; + if (toType == typeof(string)) + return fromValue.ToJsv(); var toValue = toType.CreateInstance(); toValue.PopulateWith(fromValue); diff --git a/src/ServiceStack.Text/CachedTypeInfo.cs b/src/ServiceStack.Text/CachedTypeInfo.cs new file mode 100644 index 000000000..4c24d6241 --- /dev/null +++ b/src/ServiceStack.Text/CachedTypeInfo.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Runtime.Serialization; +using System.Threading; + +namespace ServiceStack.Text +{ + public class CachedTypeInfo + { + static Dictionary CacheMap = new Dictionary(); + + public static CachedTypeInfo Get(Type type) + { + if (CacheMap.TryGetValue(type, out var value)) + return value; + + var instance = new CachedTypeInfo(type); + + Dictionary snapshot, newCache; + do + { + snapshot = CacheMap; + newCache = new Dictionary(CacheMap) + { + [type] = instance + }; + } while (!ReferenceEquals( + Interlocked.CompareExchange(ref CacheMap, newCache, snapshot), snapshot)); + + return instance; + } + + public CachedTypeInfo(Type type) + { + EnumInfo = EnumInfo.GetEnumInfo(type); + } + + public EnumInfo EnumInfo { get; } + } + + public class EnumInfo + { + public static EnumInfo GetEnumInfo(Type type) + { + if (type.IsEnum) + return new EnumInfo(type); + + var nullableType = Nullable.GetUnderlyingType(type); + if (nullableType?.IsEnum == true) + return new EnumInfo(nullableType); + + return null; + } + + private readonly Type enumType; + private EnumInfo(Type enumType) + { + this.enumType = enumType; + enumMemberReverseLookup = new Dictionary(StringComparer.OrdinalIgnoreCase); + + var enumMembers = enumType.GetFields(BindingFlags.Public | BindingFlags.Static); + foreach (var fi in enumMembers) + { + var enumValue = fi.GetValue(null); + var strEnum = fi.Name; + var enumMemberAttr = fi.FirstAttribute(); + if (enumMemberAttr?.Value != null) + { + if (enumMemberValues == null) + { + enumMemberValues = new Dictionary(); + } + enumMemberValues[enumValue] = enumMemberAttr.Value; + enumMemberReverseLookup[enumMemberAttr.Value] = enumValue; + } + else + { + enumMemberReverseLookup[strEnum] = enumValue; + } + } + isEnumFlag = enumType.IsEnumFlags(); + } + + private readonly bool isEnumFlag; + private readonly Dictionary enumMemberValues; + private readonly Dictionary enumMemberReverseLookup; + + public object GetSerializedValue(object enumValue) + { + if (enumMemberValues != null && enumMemberValues.TryGetValue(enumValue, out var memberValue)) + return memberValue; + if (isEnumFlag || JsConfig.TreatEnumAsInteger) + return enumValue; + return enumValue.ToString(); + } + + public object Parse(string serializedValue) + { + if (enumMemberReverseLookup.TryGetValue(serializedValue, out var enumValue)) + return enumValue; + + return Enum.Parse(enumType, serializedValue, ignoreCase: true); //Also parses quoted int values, e.g. "1" + } + } + +} \ No newline at end of file diff --git a/src/ServiceStack.Text/CharMemoryExtensions.cs b/src/ServiceStack.Text/CharMemoryExtensions.cs new file mode 100644 index 000000000..75b13f8b9 --- /dev/null +++ b/src/ServiceStack.Text/CharMemoryExtensions.cs @@ -0,0 +1,412 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; + +namespace ServiceStack.Text +{ + public static class CharMemoryExtensions + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsNullOrEmpty(this ReadOnlyMemory value) => value.IsEmpty; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsWhiteSpace(this ReadOnlyMemory value) + { + var span = value.Span; + for (int i = 0; i < span.Length; i++) + { + if (!char.IsWhiteSpace(span[i])) + return false; + } + return true; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsNullOrWhiteSpace(this ReadOnlyMemory value) => value.IsEmpty || value.IsWhiteSpace(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlyMemory Advance(this ReadOnlyMemory text, int to) => text.Slice(to, text.Length - to); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlyMemory AdvancePastWhitespace(this ReadOnlyMemory literal) + { + var span = literal.Span; + var i = 0; + while (i < span.Length && char.IsWhiteSpace(span[i])) + i++; + + return i == 0 ? literal : literal.Slice(i < literal.Length ? i : literal.Length); + } + + public static ReadOnlyMemory AdvancePastChar(this ReadOnlyMemory literal, char delim) + { + var i = 0; + var c = (char) 0; + var span = literal.Span; + while (i < span.Length && (c = span[i]) != delim) + i++; + + if (c == delim) + return literal.Slice(i + 1); + + return i == 0 ? literal : literal.Slice(i < literal.Length ? i : literal.Length); + } + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool ParseBoolean(this ReadOnlyMemory value) => MemoryProvider.Instance.ParseBoolean(value.Span); + + public static bool TryParseBoolean(this ReadOnlyMemory value, out bool result) => + MemoryProvider.Instance.TryParseBoolean(value.Span, out result); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryParseDecimal(this ReadOnlyMemory value, out decimal result) => + MemoryProvider.Instance.TryParseDecimal(value.Span, out result); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryParseFloat(this ReadOnlyMemory value, out float result) => + MemoryProvider.Instance.TryParseFloat(value.Span, out result); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryParseDouble(this ReadOnlyMemory value, out double result) => + MemoryProvider.Instance.TryParseDouble(value.Span, out result); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static decimal ParseDecimal(this ReadOnlyMemory value) => + MemoryProvider.Instance.ParseDecimal(value.Span); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float ParseFloat(this ReadOnlyMemory value) => + MemoryProvider.Instance.ParseFloat(value.Span); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double ParseDouble(this ReadOnlyMemory value) => + MemoryProvider.Instance.ParseDouble(value.Span); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static sbyte ParseSByte(this ReadOnlyMemory value) => + MemoryProvider.Instance.ParseSByte(value.Span); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte ParseByte(this ReadOnlyMemory value) => + MemoryProvider.Instance.ParseByte(value.Span); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static short ParseInt16(this ReadOnlyMemory value) => + MemoryProvider.Instance.ParseInt16(value.Span); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ushort ParseUInt16(this ReadOnlyMemory value) => + MemoryProvider.Instance.ParseUInt16(value.Span); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int ParseInt32(this ReadOnlyMemory value) => + MemoryProvider.Instance.ParseInt32(value.Span); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint ParseUInt32(this ReadOnlyMemory value) => + MemoryProvider.Instance.ParseUInt32(value.Span); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static long ParseInt64(this ReadOnlyMemory value) => + MemoryProvider.Instance.ParseInt64(value.Span); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong ParseUInt64(this ReadOnlyMemory value) => + MemoryProvider.Instance.ParseUInt64(value.Span); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Guid ParseGuid(this ReadOnlyMemory value) => + MemoryProvider.Instance.ParseGuid(value.Span); + + public static ReadOnlyMemory LeftPart(this ReadOnlyMemory strVal, char needle) + { + if (strVal.IsEmpty) return strVal; + var pos = strVal.IndexOf(needle); + return pos == -1 + ? strVal + : strVal.Slice(0, pos); + } + + public static ReadOnlyMemory LeftPart(this ReadOnlyMemory strVal, string needle) + { + if (strVal.IsEmpty) return strVal; + var pos = strVal.IndexOf(needle); + return pos == -1 + ? strVal + : strVal.Slice(0, pos); + } + + public static ReadOnlyMemory RightPart(this ReadOnlyMemory strVal, char needle) + { + if (strVal.IsEmpty) return strVal; + var pos = strVal.IndexOf(needle); + return pos == -1 + ? strVal + : strVal.Slice(pos + 1); + } + + public static ReadOnlyMemory RightPart(this ReadOnlyMemory strVal, string needle) + { + if (strVal.IsEmpty) return strVal; + var pos = strVal.IndexOf(needle); + return pos == -1 + ? strVal + : strVal.Slice(pos + needle.Length); + } + + public static ReadOnlyMemory LastLeftPart(this ReadOnlyMemory strVal, char needle) + { + if (strVal.IsEmpty) return strVal; + var pos = strVal.LastIndexOf(needle); + return pos == -1 + ? strVal + : strVal.Slice(0, pos); + } + + public static ReadOnlyMemory LastLeftPart(this ReadOnlyMemory strVal, string needle) + { + if (strVal.IsEmpty) return strVal; + var pos = strVal.LastIndexOf(needle); + return pos == -1 + ? strVal + : strVal.Slice(0, pos); + } + + public static ReadOnlyMemory LastRightPart(this ReadOnlyMemory strVal, char needle) + { + if (strVal.IsEmpty) return strVal; + var pos = strVal.LastIndexOf(needle); + return pos == -1 + ? strVal + : strVal.Slice(pos + 1); + } + + public static ReadOnlyMemory LastRightPart(this ReadOnlyMemory strVal, string needle) + { + if (strVal.IsEmpty) return strVal; + var pos = strVal.LastIndexOf(needle); + return pos == -1 + ? strVal + : strVal.Slice(pos + needle.Length); + } + + public static bool TryReadLine(this ReadOnlyMemory text, out ReadOnlyMemory line, ref int startIndex) + { + if (startIndex >= text.Length) + { + line = TypeConstants.NullStringMemory; + return false; + } + + text = text.Slice(startIndex); + + var nextLinePos = text.Span.IndexOfAny('\r', '\n'); + if (nextLinePos == -1) + { + var nextLine = text.Slice(0, text.Length); + startIndex += text.Length; + line = nextLine; + return true; + } + else + { + var nextLine = text.Slice(0, nextLinePos); + + startIndex += nextLinePos + 1; + + var span = text.Span; + if (span[nextLinePos] == '\r' && span.Length > nextLinePos + 1 && span[nextLinePos + 1] == '\n') + startIndex += 1; + + line = nextLine; + return true; + } + } + + public static bool TryReadPart(this ReadOnlyMemory text, ReadOnlyMemory needle, out ReadOnlyMemory part, ref int startIndex) + { + if (startIndex >= text.Length) + { + part = TypeConstants.NullStringMemory; + return false; + } + + text = text.Slice(startIndex); + var nextPartPos = text.Span.IndexOf(needle.Span); + if (nextPartPos == -1) + { + var nextPart = text.Slice(0, text.Length); + startIndex += text.Length; + part = nextPart; + return true; + } + else + { + var nextPart = text.Slice(0, nextPartPos); + startIndex += nextPartPos + needle.Length; + part = nextPart; + return true; + } + } + + public static void SplitOnFirst(this ReadOnlyMemory strVal, char needle, out ReadOnlyMemory first, out ReadOnlyMemory last) + { + first = default; + last = default; + if (strVal.IsEmpty) return; + + var pos = strVal.Span.IndexOf(needle); + if (pos == -1) + { + first = strVal; + } + else + { + first = strVal.Slice(0, pos); + last = strVal.Slice(pos + 1); + } + } + + public static void SplitOnFirst(this ReadOnlyMemory strVal, ReadOnlyMemory needle, out ReadOnlyMemory first, out ReadOnlyMemory last) + { + first = default; + last = default; + if (strVal.IsEmpty) return; + + var pos = strVal.Span.IndexOf(needle.Span); + if (pos == -1) + { + first = strVal; + } + else + { + first = strVal.Slice(0, pos); + last = strVal.Slice(pos + needle.Length); + } + } + + public static void SplitOnLast(this ReadOnlyMemory strVal, char needle, out ReadOnlyMemory first, out ReadOnlyMemory last) + { + first = default; + last = default; + if (strVal.IsEmpty) return; + + var pos = strVal.Span.LastIndexOf(needle); + if (pos == -1) + { + first = strVal; + } + else + { + first = strVal.Slice(0, pos); + last = strVal.Slice(pos + 1); + } + } + + public static void SplitOnLast(this ReadOnlyMemory strVal, ReadOnlyMemory needle, out ReadOnlyMemory first, out ReadOnlyMemory last) + { + first = default; + last = default; + if (strVal.IsEmpty) return; + + var pos = strVal.Span.LastIndexOf(needle.Span); + if (pos == -1) + { + first = strVal; + } + else + { + first = strVal.Slice(0, pos); + last = strVal.Slice(pos + needle.Length); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int IndexOf(this ReadOnlyMemory value, char needle) => value.Span.IndexOf(needle); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int IndexOf(this ReadOnlyMemory value, string needle) => value.Span.IndexOf(needle.AsSpan()); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int IndexOf(this ReadOnlyMemory value, char needle, int start) + { + var pos = value.Slice(start).Span.IndexOf(needle); + return pos == -1 ? -1 : start + pos; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int IndexOf(this ReadOnlyMemory value, string needle, int start) + { + var pos = value.Slice(start).Span.IndexOf(needle.AsSpan()); + return pos == -1 ? -1 : start + pos; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int LastIndexOf(this ReadOnlyMemory value, char needle) => value.Span.LastIndexOf(needle); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int LastIndexOf(this ReadOnlyMemory value, string needle) => value.Span.LastIndexOf(needle.AsSpan()); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int LastIndexOf(this ReadOnlyMemory value, char needle, int start) + { + var pos = value.Slice(start).Span.LastIndexOf(needle); + return pos == -1 ? -1 : start + pos; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int LastIndexOf(this ReadOnlyMemory value, string needle, int start) + { + var pos = value.Slice(start).Span.LastIndexOf(needle.AsSpan()); + return pos == -1 ? -1 : start + pos; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool StartsWith(this ReadOnlyMemory value, string other) => value.Span.StartsWith(other.AsSpan(), StringComparison.OrdinalIgnoreCase); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool StartsWith(this ReadOnlyMemory value, string other, StringComparison comparison) => value.Span.StartsWith(other.AsSpan(), comparison); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool EndsWith(this ReadOnlyMemory value, string other) => value.Span.EndsWith(other.AsSpan(), StringComparison.OrdinalIgnoreCase); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool EndsWith(this ReadOnlyMemory value, string other, StringComparison comparison) => value.Span.EndsWith(other.AsSpan(), comparison); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool EqualsOrdinal(this ReadOnlyMemory value, string other) => value.Span.Equals(other.AsSpan(), StringComparison.Ordinal); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool EqualsOrdinal(this ReadOnlyMemory value, ReadOnlyMemory other) => value.Span.Equals(other.Span, StringComparison.Ordinal); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlyMemory SafeSlice(this ReadOnlyMemory value, int startIndex) => SafeSlice(value, startIndex, value.Length); + + public static ReadOnlyMemory SafeSlice(this ReadOnlyMemory value, int startIndex, int length) + { + if (value.IsEmpty) return TypeConstants.NullStringMemory; + if (startIndex < 0) startIndex = 0; + if (value.Length >= startIndex + length) + return value.Slice(startIndex, length); + + return value.Length > startIndex ? value.Slice(startIndex) : TypeConstants.NullStringMemory; + } + + public static string SubstringWithEllipsis(this ReadOnlyMemory value, int startIndex, int length) + { + if (value.IsEmpty) return string.Empty; + var str = value.Slice(startIndex, length); + return str.Length == length + ? str + "..." + : str.ToString(); + } + + public static ReadOnlyMemory ToUtf8(this ReadOnlyMemory chars) => + MemoryProvider.Instance.ToUtf8(chars.Span); + + public static ReadOnlyMemory FromUtf8(this ReadOnlyMemory bytes) => + MemoryProvider.Instance.FromUtf8(bytes.Span); + } +} \ No newline at end of file diff --git a/src/ServiceStack.Text/Common/DateTimeSerializer.cs b/src/ServiceStack.Text/Common/DateTimeSerializer.cs index cb7f3e189..ae511d6ef 100644 --- a/src/ServiceStack.Text/Common/DateTimeSerializer.cs +++ b/src/ServiceStack.Text/Common/DateTimeSerializer.cs @@ -45,7 +45,7 @@ public static class DateTimeSerializer private const char XsdTimeSeparator = 'T'; private static readonly int XsdTimeSeparatorIndex = XsdDateTimeFormat.IndexOf(XsdTimeSeparator); private const string XsdUtcSuffix = "Z"; - private static readonly char[] DateTimeSeperators = new[] { '-', '/' }; + private static readonly char[] DateTimeSeparators = { '-', '/' }; private static readonly Regex UtcOffsetInfoRegex = new Regex("([+-](?:2[0-3]|[0-1][0-9]):[0-5][0-9])", PclExport.Instance.RegexOptions); public static Func OnParseErrorFn { get; set; } @@ -56,10 +56,11 @@ public static class DateTimeSerializer /// public static DateTime Prepare(this DateTime dateTime, bool parsedAsUtc = false) { - if (JsConfig.SkipDateTimeConversion) + var config = JsConfig.GetConfig(); + if (config.SkipDateTimeConversion) return dateTime; - if (JsConfig.AlwaysUseUtc) + if (config.AlwaysUseUtc) return dateTime.Kind != DateTimeKind.Utc ? dateTime.ToStableUniversalTime() : dateTime; return parsedAsUtc ? dateTime.ToLocalTime() : dateTime; @@ -88,42 +89,44 @@ public static DateTime ParseShortestXsdDateTime(string dateTimeStr) if (dateTimeStr.StartsWith(EscapedWcfJsonPrefix, StringComparison.Ordinal) || dateTimeStr.StartsWith(WcfJsonPrefix, StringComparison.Ordinal)) return ParseWcfJsonDate(dateTimeStr).Prepare(); + var config = JsConfig.GetConfig(); if (dateTimeStr.Length == DefaultDateTimeFormat.Length) { var unspecifiedDate = DateTime.Parse(dateTimeStr, CultureInfo.InvariantCulture); - if (JsConfig.AssumeUtc) + if (config.AssumeUtc) unspecifiedDate = DateTime.SpecifyKind(unspecifiedDate, DateTimeKind.Utc); return unspecifiedDate.Prepare(); } - if (dateTimeStr.Length == DefaultDateTimeFormatWithFraction.Length) + var hasUtcSuffix = dateTimeStr.EndsWith(XsdUtcSuffix); + if (!hasUtcSuffix && dateTimeStr.Length == DefaultDateTimeFormatWithFraction.Length) { - var unspecifiedDate = JsConfig.AssumeUtc + var unspecifiedDate = config.AssumeUtc ? DateTime.Parse(dateTimeStr, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal) : DateTime.Parse(dateTimeStr, CultureInfo.InvariantCulture); return unspecifiedDate.Prepare(); } - var kind = DateTimeKind.Unspecified; - switch (JsConfig.DateHandler) + var kind = hasUtcSuffix + ? DateTimeKind.Utc + : DateTimeKind.Unspecified; + switch (config.DateHandler) { case DateHandler.UnixTime: - int unixTime; - if (int.TryParse(dateTimeStr, out unixTime)) + if (int.TryParse(dateTimeStr, out var unixTime)) return unixTime.FromUnixTime(); break; case DateHandler.UnixTimeMs: - long unixTimeMs; - if (long.TryParse(dateTimeStr, out unixTimeMs)) + if (long.TryParse(dateTimeStr, out var unixTimeMs)) return unixTimeMs.FromUnixTimeMs(); break; case DateHandler.ISO8601: case DateHandler.ISO8601DateOnly: case DateHandler.ISO8601DateTime: - if (JsConfig.SkipDateTimeConversion) + if (config.SkipDateTimeConversion) dateTimeStr = RemoveUtcOffsets(dateTimeStr, out kind); break; } @@ -135,7 +138,7 @@ public static DateTime ParseShortestXsdDateTime(string dateTimeStr) if (dateTimeStr.Length >= XsdDateTimeFormat3F.Length && dateTimeStr.Length <= XsdDateTimeFormat.Length - && dateTimeStr.EndsWith(XsdUtcSuffix)) + && hasUtcSuffix) { var dateTime = Env.IsMono ? ParseManual(dateTimeStr) : null; if (dateTime != null) @@ -144,7 +147,7 @@ public static DateTime ParseShortestXsdDateTime(string dateTimeStr) return PclExport.Instance.ParseXsdDateTimeAsUtc(dateTimeStr); } - if (dateTimeStr.Length == CondensedDateTimeFormat.Length && dateTimeStr.IndexOfAny(DateTimeSeperators) == -1) + if (dateTimeStr.Length == CondensedDateTimeFormat.Length && dateTimeStr.IndexOfAny(DateTimeSeparators) == -1) { return DateTime.ParseExact(dateTimeStr, "yyyyMMdd", CultureInfo.InvariantCulture, DateTimeStyles.None); } @@ -162,17 +165,19 @@ public static DateTime ParseShortestXsdDateTime(string dateTimeStr) try { - if (JsConfig.SkipDateTimeConversion) + if (config.SkipDateTimeConversion) { - return DateTime.Parse(dateTimeStr, null, - kind == DateTimeKind.Unspecified - ? DateTimeStyles.None - : kind == DateTimeKind.Local - ? DateTimeStyles.AssumeLocal - : DateTimeStyles.AssumeUniversal); + var dateTimeStyle = kind == DateTimeKind.Unspecified + ? DateTimeStyles.None + : kind == DateTimeKind.Local + ? DateTimeStyles.AssumeLocal + : DateTimeStyles.AssumeUniversal; + if (config.AlwaysUseUtc) + dateTimeStyle |= DateTimeStyles.AdjustToUniversal; + return DateTime.Parse(dateTimeStr, null, dateTimeStyle); } - var assumeKind = JsConfig.AssumeUtc ? DateTimeStyles.AssumeUniversal : DateTimeStyles.AssumeLocal; + var assumeKind = config.AssumeUtc ? DateTimeStyles.AssumeUniversal : DateTimeStyles.AssumeLocal; var dateTime = DateTime.Parse(dateTimeStr, CultureInfo.InvariantCulture, assumeKind); return dateTime.Prepare(); } @@ -225,7 +230,8 @@ private static string RepairXsdTimeSeparator(string dateTimeStr) public static DateTime? ParseManual(string dateTimeStr) { - var dateKind = JsConfig.AssumeUtc || JsConfig.AlwaysUseUtc + var config = JsConfig.GetConfig(); + var dateKind = config.AssumeUtc || config.AlwaysUseUtc ? DateTimeKind.Utc : DateTimeKind.Local; @@ -417,7 +423,7 @@ public static TimeSpan ParseTimeSpan(string dateTimeStr) public static TimeSpan ParseNSTimeInterval(string doubleInSecs) { - var secs = double.Parse(doubleInSecs); + var secs = double.Parse(doubleInSecs, CultureInfo.InvariantCulture); return TimeSpan.FromSeconds(secs); } @@ -442,16 +448,23 @@ public static TimeSpan ParseXsdTimeSpan(string dateTimeStr) public static string ToShortestXsdDateTimeString(DateTime dateTime) { - var timeOfDay = dateTime.TimeOfDay; + var config = JsConfig.GetConfig(); + + dateTime = dateTime.UseConfigSpecifiedSetting(); + if (!string.IsNullOrEmpty(config.DateTimeFormat)) + { + return dateTime.ToString(config.DateTimeFormat, CultureInfo.InvariantCulture); + } + var timeOfDay = dateTime.TimeOfDay; var isStartOfDay = timeOfDay.Ticks == 0; - if (isStartOfDay && !JsConfig.SkipDateTimeConversion) + if (isStartOfDay && !config.SkipDateTimeConversion) return dateTime.ToString(ShortDateTimeFormat, CultureInfo.InvariantCulture); var hasFractionalSecs = (timeOfDay.Milliseconds != 0) || (timeOfDay.Ticks % TimeSpan.TicksPerMillisecond != 0); - if (JsConfig.SkipDateTimeConversion) + if (config.SkipDateTimeConversion) { if (!hasFractionalSecs) return dateTime.Kind == DateTimeKind.Local @@ -479,6 +492,9 @@ public static string ToShortestXsdDateTimeString(DateTime dateTime) static readonly char[] TimeZoneChars = new[] { '+', '-' }; + private const string MinDateTimeOffsetWcfValue = "\\/Date(-62135596800000)\\/"; + private const string MaxDateTimeOffsetWcfValue = "\\/Date(253402300799999)\\/"; + /// /// WCF Json format: /Date(unixts+0000)/ /// @@ -486,6 +502,11 @@ public static string ToShortestXsdDateTimeString(DateTime dateTime) /// public static DateTimeOffset ParseWcfJsonDateOffset(string wcfJsonDate) { + if (wcfJsonDate == MinDateTimeOffsetWcfValue) + return DateTimeOffset.MinValue; + if (wcfJsonDate == MaxDateTimeOffsetWcfValue) + return DateTimeOffset.MaxValue; + if (wcfJsonDate[0] == '\\') { wcfJsonDate = wcfJsonDate.Substring(1); @@ -582,14 +603,21 @@ public static TimeZoneInfo GetLocalTimeZoneInfo() internal static TimeZoneInfo LocalTimeZone = GetLocalTimeZoneInfo(); - public static void WriteWcfJsonDate(TextWriter writer, DateTime dateTime) + private static DateTime UseConfigSpecifiedSetting(this DateTime dateTime) { if (JsConfig.AssumeUtc && dateTime.Kind == DateTimeKind.Unspecified) { - dateTime = DateTime.SpecifyKind(dateTime, DateTimeKind.Utc); + return DateTime.SpecifyKind(dateTime, DateTimeKind.Utc); } + return dateTime; + } - switch (JsConfig.DateHandler) + public static void WriteWcfJsonDate(TextWriter writer, DateTime dateTime) + { + var config = JsConfig.GetConfig(); + + dateTime = dateTime.UseConfigSpecifiedSetting(); + switch (config.DateHandler) { case DateHandler.ISO8601: writer.Write(dateTime.ToString("o", CultureInfo.InvariantCulture)); @@ -609,7 +637,7 @@ public static void WriteWcfJsonDate(TextWriter writer, DateTime dateTime) string offset = null; if (dateTime.Kind != DateTimeKind.Utc) { - if (JsConfig.DateHandler == DateHandler.TimestampOffset && dateTime.Kind == DateTimeKind.Unspecified) + if (config.DateHandler == DateHandler.TimestampOffset && dateTime.Kind == DateTimeKind.Unspecified) offset = UnspecifiedOffset; else offset = LocalTimeZone.GetUtcOffset(dateTime).ToTimeOffsetString(); @@ -617,8 +645,8 @@ public static void WriteWcfJsonDate(TextWriter writer, DateTime dateTime) else { // Normally the JsonDateHandler.TimestampOffset doesn't append an offset for Utc dates, but if - // the JsConfig.AppendUtcOffset is set then we will - if (JsConfig.DateHandler == DateHandler.TimestampOffset && JsConfig.AppendUtcOffset.HasValue && JsConfig.AppendUtcOffset.Value) + // the config.AppendUtcOffset is set then we will + if (config.DateHandler == DateHandler.TimestampOffset && config.AppendUtcOffset) offset = UtcOffset; } @@ -640,7 +668,7 @@ public static string ToWcfJsonDate(DateTime dateTime) return StringBuilderThreadStatic.ReturnAndFree(sb); } } - + public static void WriteWcfJsonDateTimeOffset(TextWriter writer, DateTimeOffset dateTimeOffset) { if (JsConfig.DateHandler == DateHandler.ISO8601) diff --git a/src/ServiceStack.Text/Common/DeserializeArray.cs b/src/ServiceStack.Text/Common/DeserializeArray.cs index 2bed75437..af392953d 100644 --- a/src/ServiceStack.Text/Common/DeserializeArray.cs +++ b/src/ServiceStack.Text/Common/DeserializeArray.cs @@ -12,13 +12,7 @@ using System; using System.Collections.Generic; -using System.Reflection; -using System.Linq; using System.Threading; -#if NETSTANDARD2_0 -using Microsoft.Extensions.Primitives; -#endif -using ServiceStack.Text.Support; namespace ServiceStack.Text.Common { @@ -28,20 +22,19 @@ public static class DeserializeArrayWithElements private static Dictionary ParseDelegateCache = new Dictionary(); - private delegate object ParseArrayOfElementsDelegate(StringSegment value, ParseStringSegmentDelegate parseFn); + public delegate object ParseArrayOfElementsDelegate(ReadOnlySpan value, ParseStringSpanDelegate parseFn); public static Func GetParseFn(Type type) { - var func = GetParseStringSegmentFn(type); - return (s, d) => func(new StringSegment(s), v => d(v.Value)); + var func = GetParseStringSpanFn(type); + return (s, d) => func(s.AsSpan(), v => d(v.ToString())); } - private static readonly Type[] signature = {typeof(StringSegment), typeof(ParseStringSegmentDelegate)}; + private static readonly Type[] signature = {typeof(ReadOnlySpan), typeof(ParseStringSpanDelegate)}; - public static Func GetParseStringSegmentFn(Type type) + public static ParseArrayOfElementsDelegate GetParseStringSpanFn(Type type) { - ParseArrayOfElementsDelegate parseFn; - if (ParseDelegateCache.TryGetValue(type, out parseFn)) return parseFn.Invoke; + if (ParseDelegateCache.TryGetValue(type, out var parseFn)) return parseFn.Invoke; var genericType = typeof(DeserializeArrayWithElements<,>).MakeGenericType(type, typeof(TSerializer)); var mi = genericType.GetStaticMethod("ParseGenericArray", signature); @@ -67,29 +60,25 @@ public static class DeserializeArrayWithElements private static readonly ITypeSerializer Serializer = JsWriter.GetTypeSerializer(); public static T[] ParseGenericArray(string value, ParseStringDelegate elementParseFn) => - ParseGenericArray(new StringSegment(value), v => elementParseFn(v.Value)); + ParseGenericArray(value.AsSpan(), v => elementParseFn(v.ToString())); - public static T[] ParseGenericArray(StringSegment value, ParseStringSegmentDelegate elementParseFn) + public static T[] ParseGenericArray(ReadOnlySpan value, ParseStringSpanDelegate elementParseFn) { - if (!(value = DeserializeListWithElements.StripList(value)).HasValue) return null; - if (value.Length == 0) return new T[0]; + if ((value = DeserializeListWithElements.StripList(value)).IsNullOrEmpty()) + return value.IsEmpty ? null : new T[0]; - if (value.GetChar(0) == JsWriter.MapStartChar) + if (value[0] == JsWriter.MapStartChar) { - var itemValues = new List(); + var itemValues = new List(); var i = 0; do { - itemValues.Add(Serializer.EatTypeValue(value, ref i)); + var spanValue = Serializer.EatTypeValue(value, ref i); + itemValues.Add((T)elementParseFn(spanValue)); Serializer.EatItemSeperatorOrMapEndChar(value, ref i); } while (i < value.Length); - var results = new T[itemValues.Count]; - for (var j = 0; j < itemValues.Count; j++) - { - results[j] = (T)elementParseFn(itemValues[j]); - } - return results; + return itemValues.ToArray(); } else { @@ -119,28 +108,26 @@ public static T[] ParseGenericArray(StringSegment value, ParseStringSegmentDeleg internal static class DeserializeArray where TSerializer : ITypeSerializer { - private static Dictionary ParseDelegateCache = new Dictionary(); + private static Dictionary ParseDelegateCache = new Dictionary(); - public static ParseStringDelegate GetParseFn(Type type) => v => GetParseStringSegmentFn(type)(new StringSegment(v)); + public static ParseStringDelegate GetParseFn(Type type) => v => GetParseStringSpanFn(type)(v.AsSpan()); - public static ParseStringSegmentDelegate GetParseStringSegmentFn(Type type) + public static ParseStringSpanDelegate GetParseStringSpanFn(Type type) { - ParseStringSegmentDelegate parseFn; - if (ParseDelegateCache.TryGetValue(type, out parseFn)) return parseFn; + if (ParseDelegateCache.TryGetValue(type, out var parseFn)) return parseFn; var genericType = typeof(DeserializeArray<,>).MakeGenericType(type, typeof(TSerializer)); - var mi = genericType.GetStaticMethod("GetParseStringSegmentFn"); - var parseFactoryFn = (Func)mi.MakeDelegate( - typeof(Func)); + var mi = genericType.GetStaticMethod("GetParseStringSpanFn"); + var parseFactoryFn = (Func)mi.MakeDelegate( + typeof(Func)); parseFn = parseFactoryFn(); - Dictionary snapshot, newCache; + Dictionary snapshot, newCache; do { snapshot = ParseDelegateCache; - newCache = new Dictionary(ParseDelegateCache); - newCache[type] = parseFn; + newCache = new Dictionary(ParseDelegateCache) {[type] = parseFn}; } while (!ReferenceEquals( Interlocked.CompareExchange(ref ParseDelegateCache, newCache, snapshot), snapshot)); @@ -154,67 +141,64 @@ internal static class DeserializeArray { private static readonly ITypeSerializer Serializer = JsWriter.GetTypeSerializer(); - private static readonly ParseStringSegmentDelegate CacheFn; + private static readonly ParseStringSpanDelegate CacheFn; static DeserializeArray() { - CacheFn = GetParseStringSegmentFn(); + CacheFn = GetParseStringSpanFn(); } - public static ParseStringDelegate Parse => v => CacheFn(new StringSegment(v)); + public static ParseStringDelegate Parse => v => CacheFn(v.AsSpan()); - public static ParseStringSegmentDelegate ParseStringSegment => CacheFn; + public static ParseStringSpanDelegate ParseStringSpan => CacheFn; - public static ParseStringDelegate GetParseFn() => v => GetParseStringSegmentFn()(new StringSegment(v)); + public static ParseStringDelegate GetParseFn() => v => GetParseStringSpanFn()(v.AsSpan()); - public static ParseStringSegmentDelegate GetParseStringSegmentFn() + public static ParseStringSpanDelegate GetParseStringSpanFn() { var type = typeof(T); if (!type.IsArray) - throw new ArgumentException(string.Format("Type {0} is not an Array type", type.FullName)); + throw new ArgumentException($"Type {type.FullName} is not an Array type"); if (type == typeof(string[])) return ParseStringArray; if (type == typeof(byte[])) - return v => ParseByteArray(v.Value); + return v => ParseByteArray(v.ToString()); var elementType = type.GetElementType(); - var elementParseFn = Serializer.GetParseStringSegmentFn(elementType); + var elementParseFn = Serializer.GetParseStringSpanFn(elementType); if (elementParseFn != null) { - var parseFn = DeserializeArrayWithElements.GetParseStringSegmentFn(elementType); + var parseFn = DeserializeArrayWithElements.GetParseStringSpanFn(elementType); return value => parseFn(value, elementParseFn); } return null; } - public static string[] ParseStringArray(StringSegment value) + public static string[] ParseStringArray(ReadOnlySpan value) { - if (!(value = DeserializeListWithElements.StripList(value)).HasValue) return null; - return value.Length == 0 - ? TypeConstants.EmptyStringArray - : DeserializeListWithElements.ParseStringList(value).ToArray(); + if ((value = DeserializeListWithElements.StripList(value)).IsNullOrEmpty()) + return value.IsEmpty ? null : TypeConstants.EmptyStringArray; + return DeserializeListWithElements.ParseStringList(value).ToArray(); } + public static string[] ParseStringArray(string value) => ParseStringArray(value.AsSpan()); - public static string[] ParseStringArray(string value) - { - if ((value = DeserializeListWithElements.StripList(value)) == null) return null; - return value == string.Empty - ? TypeConstants.EmptyStringArray - : DeserializeListWithElements.ParseStringList(value).ToArray(); - } + public static byte[] ParseByteArray(string value) => ParseByteArray(value.AsSpan()); - public static byte[] ParseByteArray(string value) + public static byte[] ParseByteArray(ReadOnlySpan value) { - var isArray = !string.IsNullOrEmpty(value) && value.Length > 1 && value[0] == '['; - if ((value = DeserializeListWithElements.StripList(value)) == null) return null; - if ((value = Serializer.UnescapeString(value)) == null) return null; - return value == string.Empty - ? TypeConstants.EmptyByteArray - : !isArray - ? Convert.FromBase64String(value) - : DeserializeListWithElements.ParseByteList(value).ToArray(); + var isArray = value.Length > 1 && value[0] == '['; + + if ((value = DeserializeListWithElements.StripList(value)).IsNullOrEmpty()) + return value.IsEmpty ? null : TypeConstants.EmptyByteArray; + + if ((value = Serializer.UnescapeString(value)).IsNullOrEmpty()) + return TypeConstants.EmptyByteArray; + + return !isArray + ? value.ParseBase64() + : DeserializeListWithElements.ParseByteList(value).ToArray(); } } } \ No newline at end of file diff --git a/src/ServiceStack.Text/Common/DeserializeBuiltin.cs b/src/ServiceStack.Text/Common/DeserializeBuiltin.cs index 0812a3c1c..78471e323 100644 --- a/src/ServiceStack.Text/Common/DeserializeBuiltin.cs +++ b/src/ServiceStack.Text/Common/DeserializeBuiltin.cs @@ -11,29 +11,25 @@ // using System; -using System.Globalization; -#if NETSTANDARD2_0 -using Microsoft.Extensions.Primitives; -#endif -using ServiceStack.Text.Support; +using ServiceStack.Text.Json; namespace ServiceStack.Text.Common { public static class DeserializeBuiltin { - private static readonly ParseStringSegmentDelegate CachedParseFn; + private static readonly ParseStringSpanDelegate CachedParseFn; static DeserializeBuiltin() { - CachedParseFn = GetParseStringSegmentFn(); + CachedParseFn = GetParseStringSpanFn(); } - public static ParseStringDelegate Parse => v => CachedParseFn(new StringSegment(v)); + public static ParseStringDelegate Parse => v => CachedParseFn(v.AsSpan()); - public static ParseStringSegmentDelegate ParseStringSegment => CachedParseFn; + public static ParseStringSpanDelegate ParseStringSpan => CachedParseFn; - private static ParseStringDelegate GetParseFn() => v => GetParseStringSegmentFn()(new StringSegment(v)); + private static ParseStringDelegate GetParseFn() => v => GetParseStringSpanFn()(v.AsSpan()); - private static ParseStringSegmentDelegate GetParseStringSegmentFn() + private static ParseStringSpanDelegate GetParseStringSpanFn() { var nullableType = Nullable.GetUnderlyingType(typeof(T)); if (nullableType == null) @@ -42,52 +38,48 @@ private static ParseStringSegmentDelegate GetParseStringSegmentFn() switch (typeCode) { case TypeCode.Boolean: - //Lots of kids like to use '1', HTML checkboxes use 'on' as a soft convention - return value => - value.Length == 1 ? - value.Equals("1") - : value.Length == 2 ? - value.Equals("on") : - value.ParseBoolean(); - - case TypeCode.Byte: - return value => value.ParseByte(); + return value => value.ParseBoolean(); case TypeCode.SByte: - return value => value.ParseSByte(); + return SignedInteger.ParseObject; + case TypeCode.Byte: + return UnsignedInteger.ParseObject; case TypeCode.Int16: - return value => value.ParseInt16(); + return SignedInteger.ParseObject; case TypeCode.UInt16: - return value => value.ParseUInt16(); + return UnsignedInteger.ParseObject; case TypeCode.Int32: - return value => value.ParseInt32(); + return SignedInteger.ParseObject; case TypeCode.UInt32: - return value => value.ParseUInt32(); + return UnsignedInteger.ParseObject; case TypeCode.Int64: - return value => value.ParseInt64(); + return SignedInteger.ParseObject; case TypeCode.UInt64: - return value => value.ParseUInt64(); + return UnsignedInteger.ParseObject; + case TypeCode.Single: - return value => float.Parse(value.Value, CultureInfo.InvariantCulture); + return value => MemoryProvider.Instance.ParseFloat(value); case TypeCode.Double: - return value => double.Parse(value.Value, CultureInfo.InvariantCulture); + return value => MemoryProvider.Instance.ParseDouble(value); case TypeCode.Decimal: - return value => value.ParseDecimal(allowThousands: true); + return value => MemoryProvider.Instance.ParseDecimal(value); case TypeCode.DateTime: - return value => DateTimeSerializer.ParseShortestXsdDateTime(value.Value); + return value => DateTimeSerializer.ParseShortestXsdDateTime(value.ToString()); case TypeCode.Char: - return value => - { - char cValue; - return char.TryParse(value.Value, out cValue) ? cValue : '\0'; - }; + return value => value.Length == 0 ? (char)0 : value.Length == 1 ? value[0] : JsonTypeSerializer.Unescape(value)[0]; } if (typeof(T) == typeof(Guid)) return value => value.ParseGuid(); if (typeof(T) == typeof(DateTimeOffset)) - return value => DateTimeSerializer.ParseDateTimeOffset(value.Value); + return value => DateTimeSerializer.ParseDateTimeOffset(value.ToString()); if (typeof(T) == typeof(TimeSpan)) - return value => DateTimeSerializer.ParseTimeSpan(value.Value); + return value => DateTimeSerializer.ParseTimeSpan(value.ToString()); +#if NET6_0 + if (typeof(T) == typeof(DateOnly)) + return value => DateOnly.FromDateTime(DateTimeSerializer.ParseShortestXsdDateTime(value.ToString())); + if (typeof(T) == typeof(TimeOnly)) + return value => TimeOnly.FromTimeSpan(DateTimeSerializer.ParseTimeSpan(value.ToString())); +#endif } else { @@ -95,52 +87,50 @@ private static ParseStringSegmentDelegate GetParseStringSegmentFn() switch (typeCode) { case TypeCode.Boolean: - return value => value.IsNullOrEmpty() ? - (bool?)null - : value.Length == 1 ? - value.Equals("1") - : value.Length == 2 ? - value.Equals("on") : - value.ParseBoolean(); - - case TypeCode.Byte: - return value => value.IsNullOrEmpty() ? (byte?)null : value.ParseByte(); + return value => value.IsNullOrEmpty() + ? (bool?)null + : value.ParseBoolean(); case TypeCode.SByte: - return value => value.IsNullOrEmpty() ? (sbyte?)null : value.ParseSByte(); + return SignedInteger.ParseNullableObject; + case TypeCode.Byte: + return UnsignedInteger.ParseNullableObject; case TypeCode.Int16: - return value => value.IsNullOrEmpty() ? (short?)null : value.ParseInt16(); + return SignedInteger.ParseNullableObject; case TypeCode.UInt16: - return value => value.IsNullOrEmpty() ? (ushort?)null : value.ParseUInt16(); + return UnsignedInteger.ParseNullableObject; case TypeCode.Int32: - return value => value.IsNullOrEmpty() ? (int?)null : value.ParseInt32(); + return SignedInteger.ParseNullableObject; case TypeCode.UInt32: - return value => value.IsNullOrEmpty() ? (uint?)null : value.ParseUInt32(); + return UnsignedInteger.ParseNullableObject; case TypeCode.Int64: - return value => value.IsNullOrEmpty() ? (long?)null : value.ParseInt64(); + return SignedInteger.ParseNullableObject; case TypeCode.UInt64: - return value => value.IsNullOrEmpty() ? (ulong?)null : value.ParseUInt64(); + return UnsignedInteger.ParseNullableObject; + case TypeCode.Single: - return value => value.IsNullOrEmpty() ? (float?)null : float.Parse(value.Value, CultureInfo.InvariantCulture); + return value => value.IsNullOrEmpty() ? (float?)null : value.ParseFloat(); case TypeCode.Double: - return value => value.IsNullOrEmpty() ? (double?)null : double.Parse(value.Value, CultureInfo.InvariantCulture); + return value => value.IsNullOrEmpty() ? (double?)null : value.ParseDouble(); case TypeCode.Decimal: - return value => value.IsNullOrEmpty() ? (decimal?)null : value.ParseDecimal(allowThousands: true); + return value => value.IsNullOrEmpty() ? (decimal?)null : value.ParseDecimal(); case TypeCode.DateTime: - return value => DateTimeSerializer.ParseShortestNullableXsdDateTime(value.Value); + return value => DateTimeSerializer.ParseShortestNullableXsdDateTime(value.ToString()); case TypeCode.Char: - return value => - { - char cValue; - return value.IsNullOrEmpty() ? (char?)null : char.TryParse(value.Value, out cValue) ? cValue : '\0'; - }; + return value => value.IsEmpty ? (char?)null : value.Length == 1 ? value[0] : JsonTypeSerializer.Unescape(value)[0]; } if (typeof(T) == typeof(TimeSpan?)) - return value => DateTimeSerializer.ParseNullableTimeSpan(value.Value); + return value => DateTimeSerializer.ParseNullableTimeSpan(value.ToString()); if (typeof(T) == typeof(Guid?)) return value => value.IsNullOrEmpty() ? (Guid?)null : value.ParseGuid(); if (typeof(T) == typeof(DateTimeOffset?)) - return value => DateTimeSerializer.ParseNullableDateTimeOffset(value.Value); + return value => DateTimeSerializer.ParseNullableDateTimeOffset(value.ToString()); +#if NET6_0 + if (typeof(T) == typeof(DateOnly?)) + return value => value.IsNullOrEmpty() ? default : DateOnly.FromDateTime(DateTimeSerializer.ParseShortestXsdDateTime(value.ToString())); + if (typeof(T) == typeof(TimeOnly?)) + return value => value.IsNullOrEmpty() ? default : TimeOnly.FromTimeSpan(DateTimeSerializer.ParseTimeSpan(value.ToString())); +#endif } return null; diff --git a/src/ServiceStack.Text/Common/DeserializeCollection.cs b/src/ServiceStack.Text/Common/DeserializeCollection.cs index 8b360281d..40e99b0c0 100644 --- a/src/ServiceStack.Text/Common/DeserializeCollection.cs +++ b/src/ServiceStack.Text/Common/DeserializeCollection.cs @@ -12,14 +12,7 @@ using System; using System.Collections.Generic; -using System.Reflection; using System.Threading; -using System.Linq; -#if NETSTANDARD2_0 -using Microsoft.Extensions.Primitives; -#else -using ServiceStack.Text.Support; -#endif namespace ServiceStack.Text.Common { @@ -28,9 +21,9 @@ internal static class DeserializeCollection { private static readonly ITypeSerializer Serializer = JsWriter.GetTypeSerializer(); - public static ParseStringDelegate GetParseMethod(Type type) => v => GetParseStringSegmentMethod(type)(new StringSegment(v)); + public static ParseStringDelegate GetParseMethod(Type type) => v => GetParseStringSpanMethod(type)(v.AsSpan()); - public static ParseStringSegmentDelegate GetParseStringSegmentMethod(Type type) + public static ParseStringSpanDelegate GetParseStringSpanMethod(Type type) { var collectionInterface = type.GetTypeWithGenericInterfaceOf(typeof(ICollection<>)); if (collectionInterface == null) @@ -44,7 +37,7 @@ public static ParseStringSegmentDelegate GetParseStringSegmentMethod(Type type) return value => ParseIntCollection(value, type); var elementType = collectionInterface.GetGenericArguments()[0]; - var supportedTypeParseMethod = Serializer.GetParseStringSegmentFn(elementType); + var supportedTypeParseMethod = Serializer.GetParseStringSpanFn(elementType); if (supportedTypeParseMethod != null) { var createCollectionType = type.HasAnyTypeDefinitionsOf(typeof(ICollection<>)) @@ -56,30 +49,28 @@ public static ParseStringSegmentDelegate GetParseStringSegmentMethod(Type type) return null; } - public static ICollection ParseStringCollection(string value, Type createType) => ParseStringCollection( - new StringSegment(value), createType); + public static ICollection ParseStringCollection(string value, Type createType) => ParseStringCollection(value.AsSpan(), createType); - public static ICollection ParseStringCollection(StringSegment value, Type createType) + public static ICollection ParseStringCollection(ReadOnlySpan value, Type createType) { var items = DeserializeArrayWithElements.ParseGenericArray(value, Serializer.ParseString); return CollectionExtensions.CreateAndPopulate(createType, items); } - public static ICollection ParseIntCollection(string value, Type createType) => ParseIntCollection( - new StringSegment(value), createType); + public static ICollection ParseIntCollection(string value, Type createType) => ParseIntCollection(value.AsSpan(), createType); - public static ICollection ParseIntCollection(StringSegment value, Type createType) + public static ICollection ParseIntCollection(ReadOnlySpan value, Type createType) { - var items = DeserializeArrayWithElements.ParseGenericArray(value, x => int.Parse(x.Value)); + var items = DeserializeArrayWithElements.ParseGenericArray(value, x => x.ParseInt32()); return CollectionExtensions.CreateAndPopulate(createType, items); } public static ICollection ParseCollection(string value, Type createType, ParseStringDelegate parseFn) => - ParseCollection(new StringSegment(value), createType, v => parseFn(v.Value)); + ParseCollection(value.AsSpan(), createType, v => parseFn(v.ToString())); - public static ICollection ParseCollection(StringSegment value, Type createType, ParseStringSegmentDelegate parseFn) + public static ICollection ParseCollection(ReadOnlySpan value, Type createType, ParseStringSpanDelegate parseFn) { - if (!value.HasValue) return null; + if (value.IsEmpty) return null; var items = DeserializeArrayWithElements.ParseGenericArray(value, parseFn); return CollectionExtensions.CreateAndPopulate(createType, items); @@ -88,20 +79,20 @@ public static ICollection ParseCollection(StringSegment value, Type create private static Dictionary ParseDelegateCache = new Dictionary(); - private delegate object ParseCollectionDelegate(StringSegment value, Type createType, ParseStringSegmentDelegate parseFn); + private delegate object ParseCollectionDelegate(ReadOnlySpan value, Type createType, ParseStringSpanDelegate parseFn); public static object ParseCollectionType(string value, Type createType, Type elementType, ParseStringDelegate parseFn) => - ParseCollectionType(new StringSegment(value), createType, elementType, v => parseFn(v.Value)); + ParseCollectionType(value.AsSpan(), createType, elementType, v => parseFn(v.ToString())); - public static object ParseCollectionType(StringSegment value, Type createType, Type elementType, ParseStringSegmentDelegate parseFn) + static Type[] arguments = { typeof (ReadOnlySpan), typeof(Type), typeof(ParseStringSpanDelegate) }; + + public static object ParseCollectionType(ReadOnlySpan value, Type createType, Type elementType, ParseStringSpanDelegate parseFn) { - ParseCollectionDelegate parseDelegate; - if (ParseDelegateCache.TryGetValue(elementType, out parseDelegate)) + if (ParseDelegateCache.TryGetValue(elementType, out var parseDelegate)) return parseDelegate(value, createType, parseFn); - var mi = typeof(DeserializeCollection).GetStaticMethod("ParseCollection", - new[] { typeof (StringSegment), typeof(Type), typeof(ParseStringSegmentDelegate)}); + var mi = typeof(DeserializeCollection).GetStaticMethod("ParseCollection", arguments); var genericMi = mi.MakeGenericMethod(new[] { elementType }); parseDelegate = (ParseCollectionDelegate)genericMi.MakeDelegate(typeof(ParseCollectionDelegate)); diff --git a/src/ServiceStack.Text/Common/DeserializeCustomGenericType.cs b/src/ServiceStack.Text/Common/DeserializeCustomGenericType.cs index 2aa23f1bf..1116e0e6f 100644 --- a/src/ServiceStack.Text/Common/DeserializeCustomGenericType.cs +++ b/src/ServiceStack.Text/Common/DeserializeCustomGenericType.cs @@ -1,10 +1,6 @@ using System; using System.Linq; using ServiceStack.Text.Json; -#if NETSTANDARD2_0 -using Microsoft.Extensions.Primitives; -#endif -using ServiceStack.Text.Support; namespace ServiceStack.Text.Common { @@ -13,9 +9,9 @@ internal static class DeserializeCustomGenericType { private static readonly ITypeSerializer Serializer = JsWriter.GetTypeSerializer(); - public static ParseStringDelegate GetParseMethod(Type type) => v => GetParseStringSegmentMethod(type)(new StringSegment(v)); + public static ParseStringDelegate GetParseMethod(Type type) => v => GetParseStringSpanMethod(type)(v.AsSpan()); - public static ParseStringSegmentDelegate GetParseStringSegmentMethod(Type type) + public static ParseStringSpanDelegate GetParseStringSpanMethod(Type type) { if (type.Name.IndexOf("Tuple`", StringComparison.Ordinal) >= 0) return x => ParseTuple(type, x); @@ -23,9 +19,9 @@ public static ParseStringSegmentDelegate GetParseStringSegmentMethod(Type type) return null; } - public static object ParseTuple(Type tupleType, string value) => ParseTuple(tupleType, new StringSegment(value)); + public static object ParseTuple(Type tupleType, string value) => ParseTuple(tupleType, value.AsSpan()); - public static object ParseTuple(Type tupleType, StringSegment value) + public static object ParseTuple(Type tupleType, ReadOnlySpan value) { var index = 0; Serializer.EatMapStartChar(value, ref index); @@ -40,10 +36,10 @@ public static object ParseTuple(Type tupleType, StringSegment value) var keyValue = Serializer.EatMapKey(value, ref index); Serializer.EatMapKeySeperator(value, ref index); var elementValue = Serializer.EatValue(value, ref index); - if (!keyValue.HasValue) continue; + if (keyValue.IsEmpty) continue; - var keyIndex = keyValue.Substring("Item".Length).ToInt() - 1; - var parseFn = Serializer.GetParseStringSegmentFn(genericArgs[keyIndex]); + var keyIndex = keyValue.Slice("Item".Length).ParseInt32() - 1; + var parseFn = Serializer.GetParseStringSpanFn(genericArgs[keyIndex]); argValues[keyIndex] = parseFn(elementValue); Serializer.EatItemSeperatorOrMapEndChar(value, ref index); diff --git a/src/ServiceStack.Text/Common/DeserializeDictionary.cs b/src/ServiceStack.Text/Common/DeserializeDictionary.cs index f72c56476..078362b50 100644 --- a/src/ServiceStack.Text/Common/DeserializeDictionary.cs +++ b/src/ServiceStack.Text/Common/DeserializeDictionary.cs @@ -13,17 +13,7 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Reflection; -using System.Text; using System.Threading; -using System.Linq; -using ServiceStack.Text.Json; -using ServiceStack.Text.Pools; -using ServiceStack.Text.Support; - -#if NETSTANDARD2_0 -using Microsoft.Extensions.Primitives; -#endif namespace ServiceStack.Text.Common { @@ -35,44 +25,46 @@ public static class DeserializeDictionary const int KeyIndex = 0; const int ValueIndex = 1; - public static ParseStringDelegate GetParseMethod(Type type) => v => GetParseStringSegmentMethod(type)(new StringSegment(v)); + public static ParseStringDelegate GetParseMethod(Type type) => v => GetParseStringSpanMethod(type)(v.AsSpan()); - public static ParseStringSegmentDelegate GetParseStringSegmentMethod(Type type) + public static ParseStringSpanDelegate GetParseStringSpanMethod(Type type) { var mapInterface = type.GetTypeWithGenericInterfaceOf(typeof(IDictionary<,>)); if (mapInterface == null) { - var fn = PclExport.Instance.GetDictionaryParseStringSegmentMethod(type); + var fn = PclExport.Instance.GetDictionaryParseStringSpanMethod(type); if (fn != null) return fn; if (type == typeof(IDictionary)) { - return GetParseStringSegmentMethod(typeof(Dictionary)); + return GetParseStringSpanMethod(typeof(Dictionary)); } if (typeof(IDictionary).IsAssignableFrom(type)) { return s => ParseIDictionary(s, type); } - throw new ArgumentException(string.Format("Type {0} is not of type IDictionary<,>", type.FullName)); + throw new ArgumentException($"Type {type.FullName} is not of type IDictionary<,>"); } //optimized access for regularly used types if (type == typeof(Dictionary)) - { return ParseStringDictionary; - } if (type == typeof(JsonObject)) - { return ParseJsonObject; + if (typeof(JsonObject).IsAssignableFrom(type)) + { + var method = typeof(DeserializeDictionary).GetMethod("ParseInheritedJsonObject"); + method = method.MakeGenericMethod(type); + return Delegate.CreateDelegate(typeof(ParseStringSpanDelegate), method) as ParseStringSpanDelegate; } var dictionaryArgs = mapInterface.GetGenericArguments(); - var keyTypeParseMethod = Serializer.GetParseStringSegmentFn(dictionaryArgs[KeyIndex]); + var keyTypeParseMethod = Serializer.GetParseStringSpanFn(dictionaryArgs[KeyIndex]); if (keyTypeParseMethod == null) return null; - var valueTypeParseMethod = Serializer.GetParseStringSegmentFn(dictionaryArgs[ValueIndex]); + var valueTypeParseMethod = Serializer.GetParseStringSpanFn(dictionaryArgs[ValueIndex]); if (valueTypeParseMethod == null) return null; var createMapType = type.HasAnyTypeDefinitionsOf(typeof(Dictionary<,>), typeof(IDictionary<,>)) @@ -81,15 +73,48 @@ public static ParseStringSegmentDelegate GetParseStringSegmentMethod(Type type) return value => ParseDictionaryType(value, createMapType, dictionaryArgs, keyTypeParseMethod, valueTypeParseMethod); } - public static JsonObject ParseJsonObject(string value) => ParseJsonObject(new StringSegment(value)); + public static JsonObject ParseJsonObject(string value) => ParseJsonObject(value.AsSpan()); + + public static T ParseInheritedJsonObject(ReadOnlySpan value) where T : JsonObject, new() + { + if (value.Length == 0) + return null; + + var index = VerifyAndGetStartIndex(value, typeof(T)); + + var result = new T(); + + if (Json.JsonTypeSerializer.IsEmptyMap(value, index)) return result; + + var valueLength = value.Length; + while (index < valueLength) + { + var keyValue = Serializer.EatMapKey(value, ref index); + Serializer.EatMapKeySeperator(value, ref index); + var elementValue = Serializer.EatValue(value, ref index); + if (keyValue.IsEmpty) continue; + + var mapKey = keyValue.ToString(); + var mapValue = elementValue.Value(); + + result[mapKey] = mapValue; - public static JsonObject ParseJsonObject(StringSegment value) + Serializer.EatItemSeperatorOrMapEndChar(value, ref index); + } + + return result; + } + + public static JsonObject ParseJsonObject(ReadOnlySpan value) { + if (value.Length == 0) + return null; + var index = VerifyAndGetStartIndex(value, typeof(JsonObject)); var result = new JsonObject(); - if (JsonTypeSerializer.IsEmptyMap(value, index)) return result; + if (Json.JsonTypeSerializer.IsEmptyMap(value, index)) return result; var valueLength = value.Length; while (index < valueLength) @@ -97,10 +122,10 @@ public static JsonObject ParseJsonObject(StringSegment value) var keyValue = Serializer.EatMapKey(value, ref index); Serializer.EatMapKeySeperator(value, ref index); var elementValue = Serializer.EatValue(value, ref index); - if (!keyValue.HasValue) continue; + if (keyValue.IsEmpty) continue; - var mapKey = keyValue.Value; - var mapValue = elementValue.Value; + var mapKey = keyValue.ToString(); + var mapValue = elementValue.Value(); result[mapKey] = mapValue; @@ -110,18 +135,18 @@ public static JsonObject ParseJsonObject(StringSegment value) return result; } - public static Dictionary ParseStringDictionary(string value) => ParseStringDictionary(new StringSegment(value)); + public static Dictionary ParseStringDictionary(string value) => ParseStringDictionary(value.AsSpan()); - public static Dictionary ParseStringDictionary(StringSegment value) + public static Dictionary ParseStringDictionary(ReadOnlySpan value) { - if (!value.HasValue) + if (value.IsEmpty) return null; var index = VerifyAndGetStartIndex(value, typeof(Dictionary)); var result = new Dictionary(); - if (JsonTypeSerializer.IsEmptyMap(value, index)) return result; + if (Json.JsonTypeSerializer.IsEmptyMap(value, index)) return result; var valueLength = value.Length; while (index < valueLength) @@ -129,12 +154,12 @@ public static Dictionary ParseStringDictionary(StringSegment val var keyValue = Serializer.EatMapKey(value, ref index); Serializer.EatMapKeySeperator(value, ref index); var elementValue = Serializer.EatValue(value, ref index); - if (!keyValue.HasValue) continue; + if (keyValue.IsEmpty) continue; var mapKey = Serializer.UnescapeString(keyValue); var mapValue = Serializer.UnescapeString(elementValue); - result[mapKey.Value] = mapValue.Value; + result[mapKey.ToString()] = mapValue.Value(); Serializer.EatItemSeperatorOrMapEndChar(value, ref index); } @@ -142,20 +167,20 @@ public static Dictionary ParseStringDictionary(StringSegment val return result; } - public static IDictionary ParseIDictionary(string value, Type dictType) => ParseIDictionary(new StringSegment(value), dictType); + public static IDictionary ParseIDictionary(string value, Type dictType) => ParseIDictionary(value.AsSpan(), dictType); - public static IDictionary ParseIDictionary(StringSegment value, Type dictType) + public static IDictionary ParseIDictionary(ReadOnlySpan value, Type dictType) { - if (!value.HasValue) return null; + if (value.IsEmpty) return null; var index = VerifyAndGetStartIndex(value, dictType); - var valueParseMethod = Serializer.GetParseStringSegmentFn(typeof(object)); + var valueParseMethod = Serializer.GetParseStringSpanFn(typeof(object)); if (valueParseMethod == null) return null; var to = (IDictionary)dictType.CreateInstance(); - if (JsonTypeSerializer.IsEmptyMap(value, index)) return to; + if (Json.JsonTypeSerializer.IsEmptyMap(value, index)) return to; var valueLength = value.Length; while (index < valueLength) @@ -164,14 +189,14 @@ public static IDictionary ParseIDictionary(StringSegment value, Type dictType) Serializer.EatMapKeySeperator(value, ref index); var elementStartIndex = index; var elementValue = Serializer.EatTypeValue(value, ref index); - if (!keyValue.HasValue) continue; + if (keyValue.IsEmpty) continue; var mapKey = valueParseMethod(keyValue); if (elementStartIndex < valueLength) { Serializer.EatWhitespace(value, ref elementStartIndex); - to[mapKey] = DeserializeType.ParsePrimitive(elementValue.Value, value.GetChar(elementStartIndex)); + to[mapKey] = DeserializeType.ParsePrimitive(elementValue.Value(), value[elementStartIndex]); } else { @@ -188,32 +213,38 @@ public static IDictionary ParseDictionary( string value, Type createMapType, ParseStringDelegate parseKeyFn, ParseStringDelegate parseValueFn) { - return ParseDictionary(new StringSegment(value), + return ParseDictionary(value.AsSpan(), createMapType, - v => parseKeyFn(v.Value), - v => parseValueFn(v.Value) + v => parseKeyFn(v.ToString()), + v => parseValueFn(v.ToString()) ); } public static IDictionary ParseDictionary( - StringSegment value, Type createMapType, - ParseStringSegmentDelegate parseKeyFn, ParseStringSegmentDelegate parseValueFn) + ReadOnlySpan value, Type createMapType, + ParseStringSpanDelegate parseKeyFn, ParseStringSpanDelegate parseValueFn) { - if (!value.HasValue) return null; + if (value.IsEmpty) return null; + + var to = (createMapType == null) + ? new Dictionary() + : (IDictionary)createMapType.CreateInstance(); + + var objDeserializer = Json.JsonTypeSerializer.Instance.ObjectDeserializer; + if (to is Dictionary && objDeserializer != null && typeof(TSerializer) == typeof(Json.JsonTypeSerializer)) + return (IDictionary) objDeserializer(value); + + var config = JsConfig.GetConfig(); var tryToParseItemsAsDictionaries = - JsConfig.ConvertObjectTypesIntoStringDictionary && typeof(TValue) == typeof(object); + config.ConvertObjectTypesIntoStringDictionary && typeof(TValue) == typeof(object); var tryToParseItemsAsPrimitiveTypes = - JsConfig.TryToParsePrimitiveTypeValues && typeof(TValue) == typeof(object); + config.TryToParsePrimitiveTypeValues && typeof(TValue) == typeof(object); var index = VerifyAndGetStartIndex(value, createMapType); - var to = (createMapType == null) - ? new Dictionary() - : (IDictionary)createMapType.CreateInstance(); - - if (JsonTypeSerializer.IsEmptyMap(value, index)) return to; + if (Json.JsonTypeSerializer.IsEmptyMap(value, index)) return to; var valueLength = value.Length; while (index < valueLength) @@ -222,14 +253,14 @@ public static IDictionary ParseDictionary( Serializer.EatMapKeySeperator(value, ref index); var elementStartIndex = index; var elementValue = Serializer.EatTypeValue(value, ref index); - if (!keyValue.HasValue) continue; + if (keyValue.IsNullOrEmpty()) continue; TKey mapKey = (TKey)parseKeyFn(keyValue); if (tryToParseItemsAsDictionaries) { Serializer.EatWhitespace(value, ref elementStartIndex); - if (elementStartIndex < valueLength && value.GetChar(elementStartIndex) == JsWriter.MapStartChar) + if (elementStartIndex < valueLength && value[elementStartIndex] == JsWriter.MapStartChar) { var tmpMap = ParseDictionary(elementValue, createMapType, parseKeyFn, parseValueFn); if (tmpMap != null && tmpMap.Count > 0) @@ -237,15 +268,15 @@ public static IDictionary ParseDictionary( to[mapKey] = (TValue)tmpMap; } } - else if (elementStartIndex < valueLength && value.GetChar(elementStartIndex) == JsWriter.ListStartChar) + else if (elementStartIndex < valueLength && value[elementStartIndex] == JsWriter.ListStartChar) { - to[mapKey] = (TValue)DeserializeList, TSerializer>.ParseStringSegment(elementValue); + to[mapKey] = (TValue)DeserializeList, TSerializer>.ParseStringSpan(elementValue); } else { to[mapKey] = (TValue)(tryToParseItemsAsPrimitiveTypes && elementStartIndex < valueLength - ? DeserializeType.ParsePrimitive(elementValue.Value, value.GetChar(elementStartIndex)) - : parseValueFn(elementValue)); + ? DeserializeType.ParsePrimitive(elementValue.Value(), value[elementStartIndex]) + : parseValueFn(elementValue).Value()); } } else @@ -253,11 +284,11 @@ public static IDictionary ParseDictionary( if (tryToParseItemsAsPrimitiveTypes && elementStartIndex < valueLength) { Serializer.EatWhitespace(value, ref elementStartIndex); - to[mapKey] = (TValue)DeserializeType.ParsePrimitive(elementValue.Value, value.GetChar(elementStartIndex)); + to[mapKey] = (TValue)DeserializeType.ParsePrimitive(elementValue.Value(), value[elementStartIndex]); } else { - to[mapKey] = (TValue)parseValueFn(elementValue); + to[mapKey] = (TValue)parseValueFn(elementValue).Value(); } } @@ -267,10 +298,10 @@ public static IDictionary ParseDictionary( return to; } - private static int VerifyAndGetStartIndex(StringSegment value, Type createMapType) + private static int VerifyAndGetStartIndex(ReadOnlySpan value, Type createMapType) { var index = 0; - if (!Serializer.EatMapStartChar(value, ref index)) + if (value.Length > 0 && !Serializer.EatMapStartChar(value, ref index)) { //Don't throw ex because some KeyValueDataContractDeserializer don't have '{}' Tracer.Instance.WriteDebug("WARN: Map definitions should start with a '{0}', expecting serialized type '{1}', got string starting with: {2}", @@ -282,23 +313,21 @@ private static int VerifyAndGetStartIndex(StringSegment value, Type createMapTyp private static Dictionary ParseDelegateCache = new Dictionary(); - private delegate object ParseDictionaryDelegate(StringSegment value, Type createMapType, - ParseStringSegmentDelegate keyParseFn, ParseStringSegmentDelegate valueParseFn); + private delegate object ParseDictionaryDelegate(ReadOnlySpan value, Type createMapType, + ParseStringSpanDelegate keyParseFn, ParseStringSpanDelegate valueParseFn); public static object ParseDictionaryType(string value, Type createMapType, Type[] argTypes, ParseStringDelegate keyParseFn, ParseStringDelegate valueParseFn) => - ParseDictionaryType(new StringSegment(value), createMapType, argTypes, - v => keyParseFn(v.Value), v => valueParseFn(v.Value)); + ParseDictionaryType(value.AsSpan(), createMapType, argTypes, + v => keyParseFn(v.ToString()), v => valueParseFn(v.ToString())); - static readonly Type[] signature = {typeof(StringSegment), typeof(Type), typeof(ParseStringSegmentDelegate), typeof(ParseStringSegmentDelegate)}; + static readonly Type[] signature = {typeof(ReadOnlySpan), typeof(Type), typeof(ParseStringSpanDelegate), typeof(ParseStringSpanDelegate)}; - public static object ParseDictionaryType(StringSegment value, Type createMapType, Type[] argTypes, - ParseStringSegmentDelegate keyParseFn, ParseStringSegmentDelegate valueParseFn) + public static object ParseDictionaryType(ReadOnlySpan value, Type createMapType, Type[] argTypes, + ParseStringSpanDelegate keyParseFn, ParseStringSpanDelegate valueParseFn) { - - ParseDictionaryDelegate parseDelegate; var key = new TypesKey(argTypes[0], argTypes[1]); - if (ParseDelegateCache.TryGetValue(key, out parseDelegate)) + if (ParseDelegateCache.TryGetValue(key, out var parseDelegate)) return parseDelegate(value, createMapType, keyParseFn, valueParseFn); var mi = typeof(DeserializeDictionary).GetStaticMethod("ParseDictionary", signature); @@ -309,8 +338,9 @@ public static object ParseDictionaryType(StringSegment value, Type createMapType do { snapshot = ParseDelegateCache; - newCache = new Dictionary(ParseDelegateCache); - newCache[key] = parseDelegate; + newCache = new Dictionary(ParseDelegateCache) { + [key] = parseDelegate + }; } while (!ReferenceEquals( Interlocked.CompareExchange(ref ParseDelegateCache, newCache, snapshot), snapshot)); diff --git a/src/ServiceStack.Text/Common/DeserializeKeyValuePair.cs b/src/ServiceStack.Text/Common/DeserializeKeyValuePair.cs index 4491ac8b7..8ded3e2ca 100644 --- a/src/ServiceStack.Text/Common/DeserializeKeyValuePair.cs +++ b/src/ServiceStack.Text/Common/DeserializeKeyValuePair.cs @@ -11,19 +11,10 @@ // using System; -using System.Collections; using System.Collections.Generic; -using System.Reflection; using System.Runtime.Serialization; -using System.Text; using System.Threading; using ServiceStack.Text.Json; -using ServiceStack.Text.Pools; -#if NETSTANDARD2_0 -using Microsoft.Extensions.Primitives; -#endif -using ServiceStack.Text.Support; - namespace ServiceStack.Text.Common { @@ -35,17 +26,17 @@ internal static class DeserializeKeyValuePair const int KeyIndex = 0; const int ValueIndex = 1; - public static ParseStringDelegate GetParseMethod(Type type) => v => GetParseStringSegmentMethod(type)(new StringSegment(v)); + public static ParseStringDelegate GetParseMethod(Type type) => v => GetParseStringSpanMethod(type)(v.AsSpan()); - public static ParseStringSegmentDelegate GetParseStringSegmentMethod(Type type) + public static ParseStringSpanDelegate GetParseStringSpanMethod(Type type) { var mapInterface = type.GetTypeWithGenericInterfaceOf(typeof(KeyValuePair<,>)); var keyValuePairArgs = mapInterface.GetGenericArguments(); - var keyTypeParseMethod = Serializer.GetParseStringSegmentFn(keyValuePairArgs[KeyIndex]); + var keyTypeParseMethod = Serializer.GetParseStringSpanFn(keyValuePairArgs[KeyIndex]); if (keyTypeParseMethod == null) return null; - var valueTypeParseMethod = Serializer.GetParseStringSegmentFn(keyValuePairArgs[ValueIndex]); + var valueTypeParseMethod = Serializer.GetParseStringSpanFn(keyValuePairArgs[ValueIndex]); if (valueTypeParseMethod == null) return null; var createMapType = type.HasAnyTypeDefinitionsOf(typeof(KeyValuePair<,>)) @@ -57,14 +48,14 @@ public static ParseStringSegmentDelegate GetParseStringSegmentMethod(Type type) public static object ParseKeyValuePair( string value, Type createMapType, ParseStringDelegate parseKeyFn, ParseStringDelegate parseValueFn) => - ParseKeyValuePair(new StringSegment(value), createMapType, - v => parseKeyFn(v.Value), v => parseValueFn(v.Value)); + ParseKeyValuePair(value.AsSpan(), createMapType, + v => parseKeyFn(v.ToString()), v => parseValueFn(v.ToString())); public static object ParseKeyValuePair( - StringSegment value, Type createMapType, - ParseStringSegmentDelegate parseKeyFn, ParseStringSegmentDelegate parseValueFn) + ReadOnlySpan value, Type createMapType, + ParseStringSpanDelegate parseKeyFn, ParseStringSpanDelegate parseValueFn) { - if (!value.HasValue) return default(KeyValuePair); + if (value.IsEmpty) return default(KeyValuePair); var index = VerifyAndGetStartIndex(value, createMapType); @@ -79,26 +70,27 @@ public static object ParseKeyValuePair( Serializer.EatMapKeySeperator(value, ref index); var keyElementValue = Serializer.EatTypeValue(value, ref index); - if (key.CompareIgnoreCase("key")) + if (key.CompareIgnoreCase("key".AsSpan())) keyValue = (TKey)parseKeyFn(keyElementValue); - else if (key.CompareIgnoreCase("value")) + else if (key.CompareIgnoreCase("value".AsSpan())) valueValue = (TValue)parseValueFn(keyElementValue); - else - throw new SerializationException("Incorrect KeyValuePair property: " + key); + else if (!key.SequenceEqual(JsConfig.TypeAttr.AsSpan())) + throw new SerializationException("Incorrect KeyValuePair property: " + key.ToString()); + Serializer.EatItemSeperatorOrMapEndChar(value, ref index); } return new KeyValuePair(keyValue, valueValue); } - private static int VerifyAndGetStartIndex(StringSegment value, Type createMapType) + private static int VerifyAndGetStartIndex(ReadOnlySpan value, Type createMapType) { var index = 0; if (!Serializer.EatMapStartChar(value, ref index)) { //Don't throw ex because some KeyValueDataContractDeserializer don't have '{}' Tracer.Instance.WriteDebug("WARN: Map definitions should start with a '{0}', expecting serialized type '{1}', got string starting with: {2}", - JsWriter.MapStartChar, createMapType != null ? createMapType.Name : "Dictionary<,>", value.Substring(0, value.Length < 50 ? value.Length : 50)); + JsWriter.MapStartChar, createMapType != null ? createMapType.Name : "Dictionary<,>", value.Substring(0, value.Length < 50 ? value.Length : 50)); } return index; } @@ -106,23 +98,21 @@ private static int VerifyAndGetStartIndex(StringSegment value, Type createMapTyp private static Dictionary ParseDelegateCache = new Dictionary(); - private delegate object ParseKeyValuePairDelegate(StringSegment value, Type createMapType, - ParseStringSegmentDelegate keyParseFn, ParseStringSegmentDelegate valueParseFn); + private delegate object ParseKeyValuePairDelegate(ReadOnlySpan value, Type createMapType, + ParseStringSpanDelegate keyParseFn, ParseStringSpanDelegate valueParseFn); public static object ParseKeyValuePairType(string value, Type createMapType, Type[] argTypes, ParseStringDelegate keyParseFn, ParseStringDelegate valueParseFn) => - ParseKeyValuePairType(new StringSegment(value), createMapType, argTypes, - v => keyParseFn(v.Value), v => valueParseFn(v.Value)); + ParseKeyValuePairType(value.AsSpan(), createMapType, argTypes, + v => keyParseFn(v.ToString()), v => valueParseFn(v.ToString())); - static readonly Type[] signature = { typeof(StringSegment), typeof(Type), typeof(ParseStringSegmentDelegate), typeof(ParseStringSegmentDelegate) }; + static readonly Type[] signature = { typeof(ReadOnlySpan), typeof(Type), typeof(ParseStringSpanDelegate), typeof(ParseStringSpanDelegate) }; - public static object ParseKeyValuePairType(StringSegment value, Type createMapType, Type[] argTypes, - ParseStringSegmentDelegate keyParseFn, ParseStringSegmentDelegate valueParseFn) + public static object ParseKeyValuePairType(ReadOnlySpan value, Type createMapType, Type[] argTypes, + ParseStringSpanDelegate keyParseFn, ParseStringSpanDelegate valueParseFn) { - - ParseKeyValuePairDelegate parseDelegate; var key = GetTypesKey(argTypes); - if (ParseDelegateCache.TryGetValue(key, out parseDelegate)) + if (ParseDelegateCache.TryGetValue(key, out var parseDelegate)) return parseDelegate(value, createMapType, keyParseFn, valueParseFn); var mi = typeof(DeserializeKeyValuePair).GetStaticMethod("ParseKeyValuePair", signature); diff --git a/src/ServiceStack.Text/Common/DeserializeListWithElements.cs b/src/ServiceStack.Text/Common/DeserializeListWithElements.cs index 0fa6923c7..bb1f0a575 100644 --- a/src/ServiceStack.Text/Common/DeserializeListWithElements.cs +++ b/src/ServiceStack.Text/Common/DeserializeListWithElements.cs @@ -11,17 +11,9 @@ // using System; -using System.Linq; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Reflection; using System.Threading; -using ServiceStack.Text.Json; -#if NETSTANDARD2_0 -using Microsoft.Extensions.Primitives; -#endif -using ServiceStack.Text.Support; - namespace ServiceStack.Text.Common { @@ -33,22 +25,21 @@ public static class DeserializeListWithElements private static Dictionary ParseDelegateCache = new Dictionary(); - private delegate object ParseListDelegate(StringSegment value, Type createListType, ParseStringSegmentDelegate parseFn); + public delegate object ParseListDelegate(ReadOnlySpan value, Type createListType, ParseStringSpanDelegate parseFn); public static Func GetListTypeParseFn( Type createListType, Type elementType, ParseStringDelegate parseFn) { - var func = GetListTypeParseStringSegmentFn(createListType, elementType, v => parseFn(v.Value)); - return (s, t, d) => func(new StringSegment(s), t, v => d(v.Value)); + var func = GetListTypeParseStringSpanFn(createListType, elementType, v => parseFn(v.ToString())); + return (s, t, d) => func(s.AsSpan(), t, v => d(v.ToString())); } - private static readonly Type[] signature = {typeof(StringSegment), typeof(Type), typeof(ParseStringSegmentDelegate)}; + private static readonly Type[] signature = {typeof(ReadOnlySpan), typeof(Type), typeof(ParseStringSpanDelegate)}; - public static Func GetListTypeParseStringSegmentFn( - Type createListType, Type elementType, ParseStringSegmentDelegate parseFn) + public static ParseListDelegate GetListTypeParseStringSpanFn( + Type createListType, Type elementType, ParseStringSpanDelegate parseFn) { - ParseListDelegate parseDelegate; - if (ParseDelegateCache.TryGetValue(elementType, out parseDelegate)) + if (ParseDelegateCache.TryGetValue(elementType, out var parseDelegate)) return parseDelegate.Invoke; var genericType = typeof(DeserializeListWithElements<,>).MakeGenericType(elementType, typeof(TSerializer)); @@ -59,8 +50,7 @@ public static Func GetL do { snapshot = ParseDelegateCache; - newCache = new Dictionary(ParseDelegateCache); - newCache[elementType] = parseDelegate; + newCache = new Dictionary(ParseDelegateCache) { [elementType] = parseDelegate }; } while (!ReferenceEquals( Interlocked.CompareExchange(ref ParseDelegateCache, newCache, snapshot), snapshot)); @@ -68,38 +58,33 @@ public static Func GetL return parseDelegate.Invoke; } - public static string StripList(string value) - { - return StripList(new StringSegment(value)).Value; - } - - public static StringSegment StripList(StringSegment value) + public static ReadOnlySpan StripList(ReadOnlySpan value) { if (value.IsNullOrEmpty()) - return default(StringSegment); + return default; value = value.Trim(); const int startQuotePos = 1; const int endQuotePos = 2; - var ret = value.GetChar(0) == JsWriter.ListStartChar - ? value.Subsegment(startQuotePos, value.Length - endQuotePos) + var ret = value[0] == JsWriter.ListStartChar + ? value.Slice(startQuotePos, value.Length - endQuotePos) : value; - var pos = 0; - Serializer.EatWhitespace(ret, ref pos); - var val = ret.Subsegment(pos, ret.Length - pos); + var val = ret.AdvancePastWhitespace(); + if (val.Length == 0) + return TypeConstants.EmptyStringSpan; return val; } public static List ParseStringList(string value) { - return ParseStringList(new StringSegment(value)); + return ParseStringList(value.AsSpan()); } - public static List ParseStringList(StringSegment value) + public static List ParseStringList(ReadOnlySpan value) { - if (!(value = StripList(value)).HasValue) return null; - if (value.Length == 0) return new List(); + if ((value = StripList(value)).IsNullOrEmpty()) + return value.IsEmpty ? null : new List(); var to = new List(); var valueLength = value.Length; @@ -109,7 +94,7 @@ public static List ParseStringList(StringSegment value) { var elementValue = Serializer.EatValue(value, ref i); var listValue = Serializer.UnescapeString(elementValue); - to.Add(listValue.Value); + to.Add(listValue.Value()); if (Serializer.EatItemSeperatorOrMapEndChar(value, ref i) && i == valueLength) { // If we ate a separator and we are at the end of the value, @@ -121,12 +106,12 @@ public static List ParseStringList(StringSegment value) return to; } - public static List ParseIntList(string value) => ParseIntList(new StringSegment(value)); + public static List ParseIntList(string value) => ParseIntList(value.AsSpan()); - public static List ParseIntList(StringSegment value) + public static List ParseIntList(ReadOnlySpan value) { - if (!(value = StripList(value)).HasValue) return null; - if (value.Length == 0) return new List(); + if ((value = StripList(value)).IsNullOrEmpty()) + return value.IsEmpty ? null : new List(); var to = new List(); var valueLength = value.Length; @@ -135,17 +120,19 @@ public static List ParseIntList(StringSegment value) while (i < valueLength) { var elementValue = Serializer.EatValue(value, ref i); - to.Add(int.Parse(elementValue.Value)); + to.Add(MemoryProvider.Instance.ParseInt32(elementValue)); Serializer.EatItemSeperatorOrMapEndChar(value, ref i); } return to; } - public static List ParseByteList(string value) + public static List ParseByteList(string value) => ParseByteList(value.AsSpan()); + + public static List ParseByteList(ReadOnlySpan value) { - if ((value = StripList(value)) == null) return null; - if (value == string.Empty) return new List(); + if ((value = StripList(value)).IsNullOrEmpty()) + return value.IsEmpty ? null : new List(); var to = new List(); var valueLength = value.Length; @@ -154,7 +141,7 @@ public static List ParseByteList(string value) while (i < valueLength) { var elementValue = Serializer.EatValue(value, ref i); - to.Add(byte.Parse(elementValue)); + to.Add(MemoryProvider.Instance.ParseByte(elementValue)); Serializer.EatItemSeperatorOrMapEndChar(value, ref i); } @@ -169,44 +156,46 @@ public static class DeserializeListWithElements public static ICollection ParseGenericList(string value, Type createListType, ParseStringDelegate parseFn) { - return ParseGenericList(new StringSegment(value), createListType, v => parseFn(v.Value)); + return ParseGenericList(value.AsSpan(), createListType, v => parseFn(v.ToString())); } - - public static ICollection ParseGenericList(StringSegment value, Type createListType, ParseStringSegmentDelegate parseFn) + public static ICollection ParseGenericList(ReadOnlySpan value, Type createListType, ParseStringSpanDelegate parseFn) { - if (!(value = DeserializeListWithElements.StripList(value)).HasValue) return null; - - var isReadOnly = createListType != null - && (createListType.IsGenericType && createListType.GetGenericTypeDefinition() == typeof(ReadOnlyCollection<>)); - + var isReadOnly = createListType != null && (createListType.IsGenericType && createListType.GetGenericTypeDefinition() == typeof(ReadOnlyCollection<>)); var to = (createListType == null || isReadOnly) ? new List() : (ICollection)createListType.CreateInstance(); - if (value.Length == 0) + var objSerializer = Json.JsonTypeSerializer.Instance.ObjectDeserializer; + if (to is List && objSerializer != null && typeof(TSerializer) == typeof(Json.JsonTypeSerializer)) + return (ICollection)objSerializer(value); + + if ((value = DeserializeListWithElements.StripList(value)).IsEmpty) + return null; + + if (value.IsNullOrEmpty()) return isReadOnly ? (ICollection)Activator.CreateInstance(createListType, to) : to; var tryToParseItemsAsPrimitiveTypes = - JsConfig.TryToParsePrimitiveTypeValues && typeof(T) == typeof(object); + typeof(T) == typeof(object) && JsConfig.TryToParsePrimitiveTypeValues; if (!value.IsNullOrEmpty()) { var valueLength = value.Length; var i = 0; Serializer.EatWhitespace(value, ref i); - if (i < valueLength && value.GetChar(i) == JsWriter.MapStartChar) + if (i < valueLength && value[i] == JsWriter.MapStartChar) { do { var itemValue = Serializer.EatTypeValue(value, ref i); - if (itemValue.HasValue) + if (!itemValue.IsEmpty) { to.Add((T)parseFn(itemValue)); } else { - to.Add(default(T)); + to.Add(default); } Serializer.EatWhitespace(value, ref i); } while (++i < value.Length); @@ -219,12 +208,13 @@ public static ICollection ParseGenericList(StringSegment value, Type createLi var startIndex = i; var elementValue = Serializer.EatValue(value, ref i); var listValue = elementValue; - if (listValue.HasValue) + var isEmpty = listValue.IsNullOrEmpty(); + if (!isEmpty) { if (tryToParseItemsAsPrimitiveTypes) { Serializer.EatWhitespace(value, ref startIndex); - to.Add((T)DeserializeType.ParsePrimitive(elementValue.Value, value.GetChar(startIndex))); + to.Add((T)DeserializeType.ParsePrimitive(elementValue.Value(), value[startIndex])); } else { @@ -236,12 +226,12 @@ public static ICollection ParseGenericList(StringSegment value, Type createLi { // If we ate a separator and we are at the end of the value, // it means the last element is empty => add default - to.Add(default(T)); + to.Add(default); continue; } - if (!listValue.HasValue) - to.Add(default(T)); + if (isEmpty) + to.Add(default); } } @@ -256,20 +246,20 @@ public static ICollection ParseGenericList(StringSegment value, Type createLi public static class DeserializeList where TSerializer : ITypeSerializer { - private static readonly ParseStringSegmentDelegate CacheFn; + private static readonly ParseStringSpanDelegate CacheFn; static DeserializeList() { - CacheFn = GetParseStringSegmentFn(); + CacheFn = GetParseStringSpanFn(); } - public static ParseStringDelegate Parse => v => CacheFn(new StringSegment(v)); + public static ParseStringDelegate Parse => v => CacheFn(v.AsSpan()); - public static ParseStringSegmentDelegate ParseStringSegment => CacheFn; + public static ParseStringSpanDelegate ParseStringSpan => CacheFn; - public static ParseStringDelegate GetParseFn() => v => GetParseStringSegmentFn()(new StringSegment(v)); + public static ParseStringDelegate GetParseFn() => v => GetParseStringSpanFn()(v.AsSpan()); - public static ParseStringSegmentDelegate GetParseStringSegmentFn() + public static ParseStringSpanDelegate GetParseStringSpanFn() { var listInterface = typeof(T).GetTypeWithGenericInterfaceOf(typeof(IList<>)); if (listInterface == null) @@ -281,16 +271,16 @@ public static ParseStringSegmentDelegate GetParseStringSegmentFn() if (typeof(T) == typeof(List)) return DeserializeListWithElements.ParseIntList; - + var elementType = listInterface.GetGenericArguments()[0]; - var supportedTypeParseMethod = DeserializeListWithElements.Serializer.GetParseStringSegmentFn(elementType); + var supportedTypeParseMethod = DeserializeListWithElements.Serializer.GetParseStringSpanFn(elementType); if (supportedTypeParseMethod != null) { var createListType = typeof(T).HasAnyTypeDefinitionsOf(typeof(List<>), typeof(IList<>)) ? null : typeof(T); - var parseFn = DeserializeListWithElements.GetListTypeParseStringSegmentFn(createListType, elementType, supportedTypeParseMethod); + var parseFn = DeserializeListWithElements.GetListTypeParseStringSpanFn(createListType, elementType, supportedTypeParseMethod); return value => parseFn(value, createListType, supportedTypeParseMethod); } @@ -302,20 +292,20 @@ public static ParseStringSegmentDelegate GetParseStringSegmentFn() internal static class DeserializeEnumerable where TSerializer : ITypeSerializer { - private static readonly ParseStringSegmentDelegate CacheFn; + private static readonly ParseStringSpanDelegate CacheFn; static DeserializeEnumerable() { - CacheFn = GetParseStringSegmentFn(); + CacheFn = GetParseStringSpanFn(); } - public static ParseStringDelegate Parse => v => CacheFn(new StringSegment(v)); + public static ParseStringDelegate Parse => v => CacheFn(v.AsSpan()); - public static ParseStringSegmentDelegate ParseStringSegment => CacheFn; + public static ParseStringSpanDelegate ParseStringSpan => CacheFn; - public static ParseStringDelegate GetParseFn() => v => GetParseStringSegmentFn()(new StringSegment(v)); + public static ParseStringDelegate GetParseFn() => v => GetParseStringSpanFn()(v.AsSpan()); - public static ParseStringSegmentDelegate GetParseStringSegmentFn() + public static ParseStringSpanDelegate GetParseStringSpanFn() { var enumerableInterface = typeof(T).GetTypeWithGenericInterfaceOf(typeof(IEnumerable<>)); if (enumerableInterface == null) @@ -330,12 +320,12 @@ public static ParseStringSegmentDelegate GetParseStringSegmentFn() var elementType = enumerableInterface.GetGenericArguments()[0]; - var supportedTypeParseMethod = DeserializeListWithElements.Serializer.GetParseStringSegmentFn(elementType); + var supportedTypeParseMethod = DeserializeListWithElements.Serializer.GetParseStringSpanFn(elementType); if (supportedTypeParseMethod != null) { const Type createListTypeWithNull = null; //Use conversions outside this class. see: Queue - var parseFn = DeserializeListWithElements.GetListTypeParseStringSegmentFn( + var parseFn = DeserializeListWithElements.GetListTypeParseStringSpanFn( createListTypeWithNull, elementType, supportedTypeParseMethod); return value => parseFn(value, createListTypeWithNull, supportedTypeParseMethod); diff --git a/src/ServiceStack.Text/Common/DeserializeSpecializedCollections.cs b/src/ServiceStack.Text/Common/DeserializeSpecializedCollections.cs index 936f7ca00..868c1c912 100644 --- a/src/ServiceStack.Text/Common/DeserializeSpecializedCollections.cs +++ b/src/ServiceStack.Text/Common/DeserializeSpecializedCollections.cs @@ -1,31 +1,26 @@ using System; using System.Collections; using System.Collections.Generic; -#if NETSTANDARD2_0 -using Microsoft.Extensions.Primitives; -#else -using ServiceStack.Text.Support; -#endif namespace ServiceStack.Text.Common { internal static class DeserializeSpecializedCollections where TSerializer : ITypeSerializer { - private readonly static ParseStringSegmentDelegate CacheFn; + private static readonly ParseStringSpanDelegate CacheFn; static DeserializeSpecializedCollections() { - CacheFn = GetParseStringSegmentFn(); + CacheFn = GetParseStringSpanFn(); } - public static ParseStringDelegate Parse => v => CacheFn(new StringSegment(v)); + public static ParseStringDelegate Parse => v => CacheFn(v.AsSpan()); - public static ParseStringSegmentDelegate ParseStringSegment => CacheFn; + public static ParseStringSpanDelegate ParseStringSpan => CacheFn; - public static ParseStringDelegate GetParseFn() => v => GetParseStringSegmentFn()(new StringSegment(v)); + public static ParseStringDelegate GetParseFn() => v => GetParseStringSpanFn()(v.AsSpan()); - public static ParseStringSegmentDelegate GetParseStringSegmentFn() + public static ParseStringSpanDelegate GetParseStringSpanFn() { if (typeof(T).HasAnyTypeDefinitionsOf(typeof(Queue<>))) { @@ -49,36 +44,36 @@ public static ParseStringSegmentDelegate GetParseStringSegmentFn() return GetGenericStackParseFn(); } - var fn = PclExport.Instance.GetSpecializedCollectionParseStringSegmentMethod(typeof(T)); + var fn = PclExport.Instance.GetSpecializedCollectionParseStringSpanMethod(typeof(T)); if (fn != null) return fn; if (typeof(T) == typeof(IEnumerable) || typeof(T) == typeof(ICollection)) { - return GetEnumerableParseStringSegmentFn(); + return GetEnumerableParseStringSpanFn(); } - return GetGenericEnumerableParseStringSegmentFn(); + return GetGenericEnumerableParseStringSpanFn(); } - public static Queue ParseStringQueue(string value) => ParseStringQueue(new StringSegment(value)); + public static Queue ParseStringQueue(string value) => ParseStringQueue(value.AsSpan()); - public static Queue ParseStringQueue(StringSegment value) + public static Queue ParseStringQueue(ReadOnlySpan value) { - var parse = (IEnumerable)DeserializeList, TSerializer>.ParseStringSegment(value); + var parse = (IEnumerable)DeserializeList, TSerializer>.ParseStringSpan(value); return new Queue(parse); } - public static Queue ParseIntQueue(string value) => ParseIntQueue(new StringSegment(value)); + public static Queue ParseIntQueue(string value) => ParseIntQueue(value.AsSpan()); - public static Queue ParseIntQueue(StringSegment value) + public static Queue ParseIntQueue(ReadOnlySpan value) { - var parse = (IEnumerable)DeserializeList, TSerializer>.ParseStringSegment(value); + var parse = (IEnumerable)DeserializeList, TSerializer>.ParseStringSpan(value); return new Queue(parse); } - internal static ParseStringSegmentDelegate GetGenericQueueParseFn() + internal static ParseStringSpanDelegate GetGenericQueueParseFn() { var enumerableInterface = typeof(T).GetTypeWithGenericInterfaceOf(typeof(IEnumerable<>)); var elementType = enumerableInterface.GetGenericArguments()[0]; @@ -86,28 +81,28 @@ internal static ParseStringSegmentDelegate GetGenericQueueParseFn() var mi = genericType.GetStaticMethod("ConvertToQueue"); var convertToQueue = (ConvertObjectDelegate)mi.MakeDelegate(typeof(ConvertObjectDelegate)); - var parseFn = DeserializeEnumerable.GetParseStringSegmentFn(); + var parseFn = DeserializeEnumerable.GetParseStringSpanFn(); return x => convertToQueue(parseFn(x)); } - public static Stack ParseStringStack(string value) => ParseStringStack(new StringSegment(value)); + public static Stack ParseStringStack(string value) => ParseStringStack(value.AsSpan()); - public static Stack ParseStringStack(StringSegment value) + public static Stack ParseStringStack(ReadOnlySpan value) { - var parse = (IEnumerable)DeserializeList, TSerializer>.ParseStringSegment(value); + var parse = (IEnumerable)DeserializeList, TSerializer>.ParseStringSpan(value); return new Stack(parse); } - public static Stack ParseIntStack(string value) => ParseIntStack(new StringSegment(value)); + public static Stack ParseIntStack(string value) => ParseIntStack(value.AsSpan()); - public static Stack ParseIntStack(StringSegment value) + public static Stack ParseIntStack(ReadOnlySpan value) { - var parse = (IEnumerable)DeserializeList, TSerializer>.ParseStringSegment(value); + var parse = (IEnumerable)DeserializeList, TSerializer>.ParseStringSpan(value); return new Stack(parse); } - internal static ParseStringSegmentDelegate GetGenericStackParseFn() + internal static ParseStringSpanDelegate GetGenericStackParseFn() { var enumerableInterface = typeof(T).GetTypeWithGenericInterfaceOf(typeof(IEnumerable<>)); @@ -116,18 +111,18 @@ internal static ParseStringSegmentDelegate GetGenericStackParseFn() var mi = genericType.GetStaticMethod("ConvertToStack"); var convertToQueue = (ConvertObjectDelegate)mi.MakeDelegate(typeof(ConvertObjectDelegate)); - var parseFn = DeserializeEnumerable.GetParseStringSegmentFn(); + var parseFn = DeserializeEnumerable.GetParseStringSpanFn(); return x => convertToQueue(parseFn(x)); } public static ParseStringDelegate GetEnumerableParseFn() => DeserializeListWithElements.ParseStringList; - public static ParseStringSegmentDelegate GetEnumerableParseStringSegmentFn() => DeserializeListWithElements.ParseStringList; + public static ParseStringSpanDelegate GetEnumerableParseStringSpanFn() => DeserializeListWithElements.ParseStringList; - public static ParseStringDelegate GetGenericEnumerableParseFn() => v => GetGenericEnumerableParseStringSegmentFn()(new StringSegment(v)); + public static ParseStringDelegate GetGenericEnumerableParseFn() => v => GetGenericEnumerableParseStringSpanFn()(v.AsSpan()); - public static ParseStringSegmentDelegate GetGenericEnumerableParseStringSegmentFn() + public static ParseStringSpanDelegate GetGenericEnumerableParseStringSpanFn() { var enumerableInterface = typeof(T).GetTypeWithGenericInterfaceOf(typeof(IEnumerable<>)); if (enumerableInterface == null) return null; @@ -138,7 +133,7 @@ public static ParseStringSegmentDelegate GetGenericEnumerableParseStringSegmentF var convertFn = fi.GetValue(null) as ConvertObjectDelegate; if (convertFn == null) return null; - var parseFn = DeserializeEnumerable.GetParseStringSegmentFn(); + var parseFn = DeserializeEnumerable.GetParseStringSpanFn(); return x => convertFn(parseFn(x)); } diff --git a/src/ServiceStack.Text/Common/DeserializeType.cs b/src/ServiceStack.Text/Common/DeserializeType.cs index 7eaae7326..47571abe6 100644 --- a/src/ServiceStack.Text/Common/DeserializeType.cs +++ b/src/ServiceStack.Text/Common/DeserializeType.cs @@ -12,13 +12,9 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.Linq; using System.Reflection; -using ServiceStack.Text.Support; -#if NETSTANDARD2_0 -using Microsoft.Extensions.Primitives; -#endif +using System.Runtime.CompilerServices; namespace ServiceStack.Text.Common { @@ -27,79 +23,107 @@ public static class DeserializeType { private static readonly ITypeSerializer Serializer = JsWriter.GetTypeSerializer(); - internal static ParseStringDelegate GetParseMethod(TypeConfig typeConfig) => v => GetParseStringSegmentMethod(typeConfig)(new StringSegment(v)); + internal static ParseStringDelegate GetParseMethod(TypeConfig typeConfig) => v => GetParseStringSpanMethod(typeConfig)(v.AsSpan()); - internal static ParseStringSegmentDelegate GetParseStringSegmentMethod(TypeConfig typeConfig) + internal static ParseStringSpanDelegate GetParseStringSpanMethod(TypeConfig typeConfig) { var type = typeConfig.Type; if (!type.IsStandardClass()) return null; - var map = DeserializeTypeRef.GetTypeAccessorMap(typeConfig, Serializer); + var accessors = DeserializeTypeRef.GetTypeAccessors(typeConfig, Serializer); var ctorFn = JsConfig.ModelFactory(type); - if (map == null) + if (accessors == null) return value => ctorFn(); + + if (typeof(TSerializer) == typeof(Json.JsonTypeSerializer)) + return new StringToTypeContext(typeConfig, ctorFn, accessors).DeserializeJson; - return typeof(TSerializer) == typeof(Json.JsonTypeSerializer) - ? (ParseStringSegmentDelegate)(value => DeserializeTypeRefJson.StringToType(typeConfig, value, ctorFn, map)) - : value => DeserializeTypeRefJsv.StringToType(typeConfig, value, ctorFn, map); + return new StringToTypeContext(typeConfig, ctorFn, accessors).DeserializeJsv; } - public static object ObjectStringToType(string strType) => ObjectStringToType(new StringSegment(strType)); + internal struct StringToTypeContext + { + private readonly TypeConfig typeConfig; + private readonly EmptyCtorDelegate ctorFn; + private readonly KeyValuePair[] accessors; + + public StringToTypeContext(TypeConfig typeConfig, EmptyCtorDelegate ctorFn, KeyValuePair[] accessors) + { + this.typeConfig = typeConfig; + this.ctorFn = ctorFn; + this.accessors = accessors; + } + + internal object DeserializeJson(ReadOnlySpan value) => DeserializeTypeRefJson.StringToType(value, typeConfig, ctorFn, accessors); + + internal object DeserializeJsv(ReadOnlySpan value) => DeserializeTypeRefJsv.StringToType(value, typeConfig, ctorFn, accessors); + } - public static object ObjectStringToType(StringSegment strType) + public static object ObjectStringToType(ReadOnlySpan strType) { var type = ExtractType(strType); if (type != null) { - var parseFn = Serializer.GetParseStringSegmentFn(type); + var parseFn = Serializer.GetParseStringSpanFn(type); var propertyValue = parseFn(strType); return propertyValue; } - if (JsConfig.ConvertObjectTypesIntoStringDictionary && !strType.IsNullOrEmpty()) + var config = JsConfig.GetConfig(); + + if (config.ConvertObjectTypesIntoStringDictionary && !strType.IsNullOrEmpty()) { - if (strType.GetChar(0) == JsWriter.MapStartChar) + var firstChar = strType[0]; + var endChar = strType[strType.Length - 1]; + if (firstChar == JsWriter.MapStartChar && endChar == JsWriter.MapEndChar) { - var dynamicMatch = DeserializeDictionary.ParseDictionary(strType, null, v => Serializer.UnescapeString(v).Value, v => Serializer.UnescapeString(v).Value); + var dynamicMatch = DeserializeDictionary.ParseDictionary(strType, null, v => Serializer.UnescapeString(v).ToString(), v => Serializer.UnescapeString(v).ToString()); if (dynamicMatch != null && dynamicMatch.Count > 0) { return dynamicMatch; } } - if (strType.GetChar(0) == JsWriter.ListStartChar) + if (firstChar == JsWriter.ListStartChar && endChar == JsWriter.ListEndChar) { - return DeserializeList, TSerializer>.ParseStringSegment(strType); + return DeserializeList, TSerializer>.ParseStringSpan(strType); } } - return (JsConfig.TryToParsePrimitiveTypeValues - ? ParsePrimitive(strType.Value) - : null) ?? Serializer.UnescapeString(strType).Value; + var primitiveType = config.TryToParsePrimitiveTypeValues ? ParsePrimitive(strType) : null; + if (primitiveType != null) + return primitiveType; + + if (Serializer.ObjectDeserializer != null && typeof(TSerializer) == typeof(Json.JsonTypeSerializer)) + return !strType.IsNullOrEmpty() + ? Serializer.ObjectDeserializer(strType) + : strType.Value(); + + return Serializer.UnescapeString(strType).Value(); } - public static Type ExtractType(string strType) => ExtractType(new StringSegment(strType)); + public static Type ExtractType(string strType) => ExtractType(strType.AsSpan()); //TODO: optimize ExtractType - public static Type ExtractType(StringSegment strType) + public static Type ExtractType(ReadOnlySpan strType) { - if (!strType.HasValue || strType.Length <= 1) return null; + if (strType.IsEmpty || strType.Length <= 1) return null; - var hasWhitespace = Json.JsonUtils.WhiteSpaceChars.Contains(strType.GetChar(1)); + var hasWhitespace = Json.JsonUtils.WhiteSpaceChars.Contains(strType[1]); if (hasWhitespace) { var pos = strType.IndexOf('"'); if (pos >= 0) - strType = new StringSegment("{" + strType.Substring(pos, strType.Length - pos)); + strType = ("{" + strType.Substring(pos, strType.Length - pos)).AsSpan(); } var typeAttrInObject = Serializer.TypeAttrInObject; if (strType.Length > typeAttrInObject.Length - && strType.Substring(0, typeAttrInObject.Length) == typeAttrInObject) + && strType.Slice(0, typeAttrInObject.Length).EqualsOrdinal(typeAttrInObject)) { var propIndex = typeAttrInObject.Length; - var typeName = Serializer.UnescapeSafeString(Serializer.EatValue(strType, ref propIndex)).Value; + var typeName = Serializer.UnescapeSafeString(Serializer.EatValue(strType, ref propIndex)).ToString(); var type = JsConfig.TypeFinder(typeName); @@ -111,14 +135,12 @@ public static Type ExtractType(StringSegment strType) return null; } - return PclExport.Instance.UseType(type); + return ReflectionOptimizer.Instance.UseType(type); } return null; } - public static object ParseAbstractType(string value) => ParseAbstractType(new StringSegment(value)); - - public static object ParseAbstractType(StringSegment value) + public static object ParseAbstractType(ReadOnlySpan value) { if (typeof(T).IsAbstract) { @@ -126,7 +148,12 @@ public static object ParseAbstractType(StringSegment value) var concreteType = ExtractType(value); if (concreteType != null) { - return Serializer.GetParseStringSegmentFn(concreteType)(value); + var fn = Serializer.GetParseStringSpanFn(concreteType); + if (fn == ParseAbstractType) + return null; + + var ret = fn(value); + return ret; } Tracer.Instance.WriteWarning( "Could not deserialize Abstract Type with unknown concrete type: " + typeof(T).FullName); @@ -136,7 +163,8 @@ public static object ParseAbstractType(StringSegment value) public static object ParseQuotedPrimitive(string value) { - var fn = JsConfig.ParsePrimitiveFn; + var config = JsConfig.GetConfig(); + var fn = config.ParsePrimitiveFn; var result = fn?.Invoke(value); if (result != null) return result; @@ -164,7 +192,7 @@ public static object ParseQuotedPrimitive(string value) } } - if (JsConfig.DateHandler == DateHandler.RFC1123) + if (config.DateHandler == DateHandler.RFC1123) { // check that we have RFC1123 date: // ddd, dd MMM yyyy HH:mm:ss GMT @@ -177,12 +205,12 @@ public static object ParseQuotedPrimitive(string value) return Serializer.UnescapeString(value); } - public static object ParsePrimitive(string value) => ParsePrimitive(new StringSegment(value)); + public static object ParsePrimitive(string value) => ParsePrimitive(value.AsSpan()); - public static object ParsePrimitive(StringSegment value) + public static object ParsePrimitive(ReadOnlySpan value) { var fn = JsConfig.ParsePrimitiveFn; - var result = fn?.Invoke(value.Value); + var result = fn?.Invoke(value.ToString()); if (result != null) return result; @@ -206,34 +234,74 @@ internal static object ParsePrimitive(string value, char firstChar) return (ParsePrimitive(value) ?? ParseQuotedPrimitive(value)); } } + + internal static class TypeAccessorUtils + { + internal static TypeAccessor Get(this KeyValuePair[] accessors, ReadOnlySpan propertyName, bool lenient) + { + var testValue = FindPropertyAccessor(accessors, propertyName); + if (testValue != null) + return testValue; + + if (lenient) + return FindPropertyAccessor(accessors, + propertyName.ToString().Replace("-", string.Empty).Replace("_", string.Empty).AsSpan()); + + return null; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] //Binary Search + private static TypeAccessor FindPropertyAccessor(KeyValuePair[] accessors, ReadOnlySpan propertyName) + { + var lo = 0; + var hi = accessors.Length - 1; + var mid = (lo + hi + 1) / 2; + + while (lo <= hi) + { + var test = accessors[mid]; + var cmp = propertyName.CompareTo(test.Key.AsSpan(), StringComparison.OrdinalIgnoreCase); + if (cmp == 0) + return test.Value; + if (cmp < 0) + hi = mid - 1; + else + lo = mid + 1; + + mid = (lo + hi + 1) / 2; + } + return null; + } + } + internal class TypeAccessor { - internal ParseStringSegmentDelegate GetProperty; + internal ParseStringSpanDelegate GetProperty; internal SetMemberDelegate SetProperty; internal Type PropertyType; public static Type ExtractType(ITypeSerializer Serializer, string strType) - => ExtractType(Serializer, new StringSegment(strType)); + => ExtractType(Serializer, strType.AsSpan()); - public static Type ExtractType(ITypeSerializer Serializer, StringSegment strType) + public static Type ExtractType(ITypeSerializer Serializer, ReadOnlySpan strType) { - if (!strType.HasValue || strType.Length <= 1) return null; + if (strType.IsEmpty || strType.Length <= 1) return null; - var hasWhitespace = Json.JsonUtils.WhiteSpaceChars.Contains(strType.GetChar(1)); + var hasWhitespace = Json.JsonUtils.WhiteSpaceChars.Contains(strType[1]); if (hasWhitespace) { var pos = strType.IndexOf('"'); if (pos >= 0) - strType = new StringSegment("{" + strType.Substring(pos)); + strType = ("{" + strType.Substring(pos)).AsSpan(); } var typeAttrInObject = Serializer.TypeAttrInObject; if (strType.Length > typeAttrInObject.Length - && strType.Substring(0, typeAttrInObject.Length) == typeAttrInObject) + && strType.Slice(0, typeAttrInObject.Length).EqualsOrdinal(typeAttrInObject)) { var propIndex = typeAttrInObject.Length; - var typeName = Serializer.EatValue(strType, ref propIndex).Value; + var typeName = Serializer.EatValue(strType, ref propIndex).ToString(); var type = JsConfig.TypeFinder(typeName); if (type == null) @@ -254,9 +322,9 @@ public static TypeAccessor Create(ITypeSerializer serializer, TypeConfig typeCon }; } - internal static ParseStringSegmentDelegate GetPropertyMethod(ITypeSerializer serializer, PropertyInfo propertyInfo) + internal static ParseStringSpanDelegate GetPropertyMethod(ITypeSerializer serializer, PropertyInfo propertyInfo) { - var getPropertyFn = serializer.GetParseStringSegmentFn(propertyInfo.PropertyType); + var getPropertyFn = serializer.GetParseStringSpanFn(propertyInfo.PropertyType); if (propertyInfo.PropertyType == typeof(object) || propertyInfo.PropertyType.HasInterface(typeof(IEnumerable))) { @@ -285,7 +353,7 @@ internal static ParseStringSegmentDelegate GetPropertyMethod(ITypeSerializer ser private static SetMemberDelegate GetSetPropertyMethod(TypeConfig typeConfig, PropertyInfo propertyInfo) { if (typeConfig.Type != propertyInfo.DeclaringType) - propertyInfo = propertyInfo.DeclaringType.GetProperty(propertyInfo.Name); + propertyInfo = propertyInfo.DeclaringType.GetProperty(propertyInfo.Name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (!propertyInfo.CanWrite && !typeConfig.EnableAnonymousFieldSetters) return null; @@ -309,8 +377,8 @@ private static SetMemberDelegate GetSetPropertyMethod(TypeConfig typeConfig, Pro } return propertyInfo.CanWrite - ? PclExport.Instance.CreateSetter(propertyInfo) - : PclExport.Instance.CreateSetter(fieldInfo); + ? ReflectionOptimizer.Instance.CreateSetter(propertyInfo) + : ReflectionOptimizer.Instance.CreateSetter(fieldInfo); } public static TypeAccessor Create(ITypeSerializer serializer, TypeConfig typeConfig, FieldInfo fieldInfo) @@ -318,7 +386,7 @@ public static TypeAccessor Create(ITypeSerializer serializer, TypeConfig typeCon return new TypeAccessor { PropertyType = fieldInfo.FieldType, - GetProperty = serializer.GetParseStringSegmentFn(fieldInfo.FieldType), + GetProperty = serializer.GetParseStringSpanFn(fieldInfo.FieldType), SetProperty = GetSetFieldMethod(typeConfig, fieldInfo), }; } @@ -326,9 +394,9 @@ public static TypeAccessor Create(ITypeSerializer serializer, TypeConfig typeCon private static SetMemberDelegate GetSetFieldMethod(TypeConfig typeConfig, FieldInfo fieldInfo) { if (typeConfig.Type != fieldInfo.DeclaringType) - fieldInfo = fieldInfo.DeclaringType.GetField(fieldInfo.Name); + fieldInfo = fieldInfo.DeclaringType.GetField(fieldInfo.Name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); - return PclExport.Instance.CreateSetter(fieldInfo); + return ReflectionOptimizer.Instance.CreateSetter(fieldInfo); } } @@ -339,12 +407,12 @@ public static bool Has(this ParseAsType flags, ParseAsType flag) return (flag & flags) != 0; } - public static object ParseNumber(this StringSegment value) => ParseNumber(value, JsConfig.TryParseIntoBestFit); - public static object ParseNumber(this StringSegment value, bool bestFit) + public static object ParseNumber(this ReadOnlySpan value) => ParseNumber(value, JsConfig.TryParseIntoBestFit); + public static object ParseNumber(this ReadOnlySpan value, bool bestFit) { if (value.Length == 1) { - int singleDigit = value.GetChar(0); + int singleDigit = value[0]; if (singleDigit >= 48 || singleDigit <= 57) // 0 - 9 { var result = singleDigit - 48; @@ -354,15 +422,17 @@ public static object ParseNumber(this StringSegment value, bool bestFit) } } + var config = JsConfig.GetConfig(); + // Parse as decimal - var acceptDecimal = JsConfig.ParsePrimitiveFloatingPointTypes.Has(ParseAsType.Decimal); + var acceptDecimal = config.ParsePrimitiveFloatingPointTypes.Has(ParseAsType.Decimal); var isDecimal = value.TryParseDecimal(out decimal decimalValue); // Check if the number is an Primitive Integer type given that we have a decimal if (isDecimal && decimalValue == decimal.Truncate(decimalValue)) { // Value is a whole number - var parseAs = JsConfig.ParsePrimitiveIntegerTypes; + var parseAs = config.ParsePrimitiveIntegerTypes; if (parseAs.Has(ParseAsType.Byte) && decimalValue <= byte.MaxValue && decimalValue >= byte.MinValue) return (byte)decimalValue; if (parseAs.Has(ParseAsType.SByte) && decimalValue <= sbyte.MaxValue && decimalValue >= sbyte.MinValue) @@ -388,12 +458,12 @@ public static object ParseNumber(this StringSegment value, bool bestFit) if (isDecimal && acceptDecimal) return decimalValue; - var acceptFloat = JsConfig.ParsePrimitiveFloatingPointTypes.HasFlag(ParseAsType.Single); + var acceptFloat = config.ParsePrimitiveFloatingPointTypes.HasFlag(ParseAsType.Single); var isFloat = value.TryParseFloat(out float floatValue); if (acceptFloat && isFloat) return floatValue; - var acceptDouble = JsConfig.ParsePrimitiveFloatingPointTypes.HasFlag(ParseAsType.Double); + var acceptDouble = config.ParsePrimitiveFloatingPointTypes.HasFlag(ParseAsType.Double); var isDouble = value.TryParseDouble(out double doubleValue); if (acceptDouble && isDouble) return doubleValue; diff --git a/src/ServiceStack.Text/Common/DeserializeTypeRef.cs b/src/ServiceStack.Text/Common/DeserializeTypeRef.cs index e04d3c8bd..ea59a9c7a 100644 --- a/src/ServiceStack.Text/Common/DeserializeTypeRef.cs +++ b/src/ServiceStack.Text/Common/DeserializeTypeRef.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Reflection; using System.Runtime.Serialization; +using System.Threading; using ServiceStack.Text.Support; namespace ServiceStack.Text.Common @@ -18,7 +19,7 @@ internal static SerializationException CreateSerializationError(Type type, strin internal static SerializationException GetSerializationException(string propertyName, string propertyValueString, Type propertyType, Exception e) { - var serializationException = new SerializationException(String.Format("Failed to set property '{0}' with '{1}'", propertyName, propertyValueString), e); + var serializationException = new SerializationException($"Failed to set property '{propertyName}' with '{propertyValueString}'", e); if (propertyName != null) { serializationException.Data.Add("propertyName", propertyName); @@ -34,149 +35,77 @@ internal static SerializationException GetSerializationException(string property return serializationException; } - internal static Dictionary GetTypeAccessorMap(TypeConfig typeConfig, ITypeSerializer serializer) + private static Dictionary[]> TypeAccessorsCache = new Dictionary[]>(); + + internal static KeyValuePair[] GetCachedTypeAccessors(Type type, ITypeSerializer serializer) + { + if (TypeAccessorsCache.TryGetValue(type, out var typeAccessors)) + return typeAccessors; + + var typeConfig = new TypeConfig(type); + typeAccessors = GetTypeAccessors(typeConfig, serializer); + + Dictionary[]> snapshot, newCache; + do + { + snapshot = TypeAccessorsCache; + newCache = new Dictionary[]>(TypeAccessorsCache) { + [type] = typeAccessors + }; + } while (!ReferenceEquals( + Interlocked.CompareExchange(ref TypeAccessorsCache, newCache, snapshot), snapshot)); + + return typeAccessors; + } + + internal static KeyValuePair[] GetTypeAccessors(TypeConfig typeConfig, ITypeSerializer serializer) { var type = typeConfig.Type; - var isDataContract = type.IsDto(); var propertyInfos = type.GetSerializableProperties(); var fieldInfos = type.GetSerializableFields(); - if (propertyInfos.Length == 0 && fieldInfos.Length == 0) return null; + if (propertyInfos.Length == 0 && fieldInfos.Length == 0) + return default; - var map = new Dictionary(); + var accessors = new KeyValuePair[propertyInfos.Length + fieldInfos.Length]; + var i = 0; if (propertyInfos.Length != 0) { - foreach (var propertyInfo in propertyInfos) + for (; i < propertyInfos.Length; i++) { + var propertyInfo = propertyInfos[i]; var propertyName = propertyInfo.Name; - if (isDataContract) + var dcsDataMember = propertyInfo.GetDataMember(); + if (dcsDataMember?.Name != null) { - var dcsDataMember = propertyInfo.GetDataMember(); - if (dcsDataMember?.Name != null) - { - propertyName = dcsDataMember.Name; - } + propertyName = dcsDataMember.Name; } - map[new HashedStringSegment(propertyName)] = TypeAccessor.Create(serializer, typeConfig, propertyInfo); + + accessors[i] = new KeyValuePair(propertyName, TypeAccessor.Create(serializer, typeConfig, propertyInfo)); } } if (fieldInfos.Length != 0) { - foreach (var fieldInfo in fieldInfos) + for (var j=0; j < fieldInfos.Length; j++) { + var fieldInfo = fieldInfos[j]; var fieldName = fieldInfo.Name; - if (isDataContract) + var dcsDataMember = fieldInfo.GetDataMember(); + if (dcsDataMember?.Name != null) { - var dcsDataMember = fieldInfo.GetDataMember(); - if (dcsDataMember?.Name != null) - { - fieldName = dcsDataMember.Name; - } + fieldName = dcsDataMember.Name; } - map[new HashedStringSegment(fieldName)] = TypeAccessor.Create(serializer, typeConfig, fieldInfo); - } - } - return map; - } - - /* The old Reference generic implementation - internal static object StringToType( - ITypeSerializer Serializer, - Type type, - string strType, - EmptyCtorDelegate ctorFn, - Dictionary typeAccessorMap) - { - var index = 0; - - if (strType == null) - return null; - - if (!Serializer.EatMapStartChar(strType, ref index)) - throw DeserializeTypeRef.CreateSerializationError(type, strType); - - if (strType == JsWriter.EmptyMap) return ctorFn(); - - object instance = null; - - var strTypeLength = strType.Length; - while (index < strTypeLength) - { - var propertyName = Serializer.EatMapKey(strType, ref index); - - Serializer.EatMapKeySeperator(strType, ref index); - - var propertyValueStr = Serializer.EatValue(strType, ref index); - var possibleTypeInfo = propertyValueStr != null && propertyValueStr.Length > 1 && propertyValueStr[0] == '_'; - if (possibleTypeInfo && propertyName == JsWriter.TypeAttr) - { - var typeName = Serializer.ParseString(propertyValueStr); - instance = ReflectionExtensions.CreateInstance(typeName); - if (instance == null) - { - Tracer.Instance.WriteWarning("Could not find type: " + propertyValueStr); - } - else - { - //If __type info doesn't match, ignore it. - if (!type.IsInstanceOfType(instance)) - instance = null; - } - - Serializer.EatItemSeperatorOrMapEndChar(strType, ref index); - continue; + accessors[i + j] = new KeyValuePair(fieldName, TypeAccessor.Create(serializer, typeConfig, fieldInfo)); } - - if (instance == null) instance = ctorFn(); - - TypeAccessor typeAccessor; - typeAccessorMap.TryGetValue(propertyName, out typeAccessor); - - var propType = possibleTypeInfo ? TypeAccessor.ExtractType(Serializer, propertyValueStr) : null; - if (propType != null) - { - try - { - if (typeAccessor != null) - { - var parseFn = Serializer.GetParseFn(propType); - var propertyValue = parseFn(propertyValueStr); - typeAccessor.SetProperty(instance, propertyValue); - } - - Serializer.EatItemSeperatorOrMapEndChar(strType, ref index); - - continue; - } - catch - { - Tracer.Instance.WriteWarning("WARN: failed to set dynamic property {0} with: {1}", propertyName, propertyValueStr); - } - } - - if (typeAccessor != null && typeAccessor.GetProperty != null && typeAccessor.SetProperty != null) - { - try - { - var propertyValue = typeAccessor.GetProperty(propertyValueStr); - typeAccessor.SetProperty(instance, propertyValue); - } - catch - { - Tracer.Instance.WriteWarning("WARN: failed to set property {0} with: {1}", propertyName, propertyValueStr); - } - } - - Serializer.EatItemSeperatorOrMapEndChar(strType, ref index); } - - return instance; + + Array.Sort(accessors, (x,y) => string.Compare(x.Key, y.Key, StringComparison.OrdinalIgnoreCase)); + return accessors; } - */ - } + } //The same class above but JSON-specific to enable inlining in this hot class. } \ No newline at end of file diff --git a/src/ServiceStack.Text/Common/DeserializeTypeRefJson.cs b/src/ServiceStack.Text/Common/DeserializeTypeRefJson.cs index 40eca52a3..6960ddc2f 100644 --- a/src/ServiceStack.Text/Common/DeserializeTypeRefJson.cs +++ b/src/ServiceStack.Text/Common/DeserializeTypeRefJson.cs @@ -1,102 +1,51 @@ using System; using System.Collections.Generic; -using System.Globalization; -using System.Reflection; using ServiceStack.Text.Json; -#if NETSTANDARD2_0 -using Microsoft.Extensions.Primitives; -#endif -using ServiceStack.Text.Support; namespace ServiceStack.Text.Common { - // Provides a contract for mapping properties to their type accessors - internal interface IPropertyNameResolver - { - TypeAccessor GetTypeAccessorForProperty(StringSegment propertyName, Dictionary typeAccessorMap); - } - // The default behavior is that the target model must match property names exactly - internal class DefaultPropertyNameResolver : IPropertyNameResolver - { - public virtual TypeAccessor GetTypeAccessorForProperty(StringSegment propertyName, Dictionary typeAccessorMap) - { - TypeAccessor typeAccessor; - typeAccessorMap.TryGetValue(new HashedStringSegment(propertyName), out typeAccessor); - return typeAccessor; - } - } - // The lenient behavior is that properties on the target model can be .NET-cased, while the source JSON can differ - internal class LenientPropertyNameResolver : DefaultPropertyNameResolver - { - - public override TypeAccessor GetTypeAccessorForProperty(StringSegment propertyName, Dictionary typeAccessorMap) - { - TypeAccessor typeAccessor; - - // map is case-insensitive by default, so simply remove hyphens and underscores - return typeAccessorMap.TryGetValue(new HashedStringSegment(RemoveSeparators(propertyName)), out typeAccessor) - ? typeAccessor - : base.GetTypeAccessorForProperty(propertyName, typeAccessorMap); - } - - //TODO: optimize - private static string RemoveSeparators(StringSegment propertyName) - { - // "lowercase-hyphen" or "lowercase_underscore" -> lowercaseunderscore - return propertyName.Value.Replace("-", String.Empty).Replace("_", String.Empty); - } - - } - internal static class DeserializeTypeRefJson { private static readonly JsonTypeSerializer Serializer = (JsonTypeSerializer)JsonTypeSerializer.Instance; - internal static object StringToType( + internal static object StringToType(ReadOnlySpan strType, TypeConfig typeConfig, - string strType, EmptyCtorDelegate ctorFn, - Dictionary typeAccessorMap) => - StringToType(typeConfig, new StringSegment(strType), ctorFn, typeAccessorMap); - - static readonly StringSegment typeAttr = new StringSegment(JsWriter.TypeAttr); - - internal static object StringToType( - TypeConfig typeConfig, - StringSegment strType, - EmptyCtorDelegate ctorFn, - Dictionary typeAccessorMap) + KeyValuePair[] typeAccessors) { var index = 0; var type = typeConfig.Type; - if (!strType.HasValue) + if (strType.IsEmpty) return null; - var buffer = strType.Buffer; - var offset = strType.Offset; + var buffer = strType; var strTypeLength = strType.Length; //if (!Serializer.EatMapStartChar(strType, ref index)) - for (; index < strTypeLength; index++) { if (!JsonUtils.IsWhiteSpace(buffer[offset + index])) break; } //Whitespace inline - if (buffer[offset + index] != JsWriter.MapStartChar) - throw DeserializeTypeRef.CreateSerializationError(type, strType.Value); + for (; index < strTypeLength; index++) { if (!JsonUtils.IsWhiteSpace(buffer[index])) break; } //Whitespace inline + if (buffer[index] != JsWriter.MapStartChar) + throw DeserializeTypeRef.CreateSerializationError(type, strType.ToString()); index++; - if (JsonTypeSerializer.IsEmptyMap(strType, index)) return ctorFn(); + if (JsonTypeSerializer.IsEmptyMap(strType, index)) + return ctorFn(); + + var config = JsConfig.GetConfig(); + var typeAttr = config.TypeAttrMemory; object instance = null; + var textCase = typeConfig.TextCase.GetValueOrDefault(config.TextCase); + var lenient = config.PropertyConvention == PropertyConvention.Lenient || textCase == TextCase.SnakeCase; - var propertyResolver = JsConfig.PropertyConvention == PropertyConvention.Lenient - ? ParseUtils.LenientPropertyNameResolver - : ParseUtils.DefaultPropertyNameResolver; + for (; index < strTypeLength; index++) { if (!JsonUtils.IsWhiteSpace(buffer[index])) break; } //Whitespace inline while (index < strTypeLength) { - var propertyName = JsonTypeSerializer.ParseJsonString(strType, ref index); + var propertyName = JsonTypeSerializer.UnescapeJsString(strType, JsonUtils.QuoteChar, removeQuotes:true, ref index); //Serializer.EatMapKeySeperator(strType, ref index); - for (; index < strTypeLength; index++) { if (!JsonUtils.IsWhiteSpace(buffer[offset + index])) break; } //Whitespace inline + for (; index < strTypeLength; index++) { if (!JsonUtils.IsWhiteSpace(buffer[index])) break; } //Whitespace inline if (strTypeLength != index) index++; var propertyValueStr = Serializer.EatValue(strType, ref index); @@ -104,18 +53,18 @@ internal static object StringToType( //if we already have an instance don't check type info, because then we will have a half deserialized object //we could throw here or just use the existing instance. - if (instance == null && possibleTypeInfo && propertyName == typeAttr) + if (instance == null && possibleTypeInfo && propertyName.Equals(typeAttr.Span, StringComparison.OrdinalIgnoreCase)) { var explicitTypeName = Serializer.ParseString(propertyValueStr); - var explicitType = JsConfig.TypeFinder(explicitTypeName); + var explicitType = config.TypeFinder(explicitTypeName); if (explicitType == null || explicitType.IsInterface || explicitType.IsAbstract) { - Tracer.Instance.WriteWarning("Could not find type: " + propertyValueStr); + Tracer.Instance.WriteWarning("Could not find type: " + propertyValueStr.ToString()); } else if (!type.IsAssignableFrom(explicitType)) { - Tracer.Instance.WriteWarning("Could not assign type: " + propertyValueStr); + Tracer.Instance.WriteWarning("Could not assign type: " + propertyValueStr.ToString()); } else { @@ -135,12 +84,9 @@ internal static object StringToType( var derivedType = instance.GetType(); if (derivedType != type) { - var derivedTypeConfig = new TypeConfig(derivedType); - var map = DeserializeTypeRef.GetTypeAccessorMap(derivedTypeConfig, Serializer); + var map = DeserializeTypeRef.GetCachedTypeAccessors(derivedType, Serializer); if (map != null) - { - typeAccessorMap = map; - } + typeAccessors = map; } } } @@ -151,9 +97,9 @@ internal static object StringToType( if (instance == null) instance = ctorFn(); - var typeAccessor = propertyResolver.GetTypeAccessorForProperty(propertyName, typeAccessorMap); + var typeAccessor = typeAccessors.Get(propertyName, lenient); - var propType = possibleTypeInfo && propertyValueStr.GetChar(0) == '_' ? TypeAccessor.ExtractType(Serializer, propertyValueStr) : null; + var propType = possibleTypeInfo && propertyValueStr[0] == '_' ? TypeAccessor.ExtractType(Serializer, propertyValueStr) : null; if (propType != null) { try @@ -161,31 +107,31 @@ internal static object StringToType( if (typeAccessor != null) { //var parseFn = Serializer.GetParseFn(propType); - var parseFn = JsonReader.GetParseStringSegmentFn(propType); + var parseFn = JsonReader.GetParseStringSpanFn(propType); var propertyValue = parseFn(propertyValueStr); if (typeConfig.OnDeserializing != null) - propertyValue = typeConfig.OnDeserializing(instance, propertyName.Value, propertyValue); + propertyValue = typeConfig.OnDeserializing(instance, propertyName.ToString(), propertyValue); typeAccessor.SetProperty(instance, propertyValue); } //Serializer.EatItemSeperatorOrMapEndChar(strType, ref index); - for (; index < strTypeLength; index++) { if (!JsonUtils.IsWhiteSpace(buffer[offset + index])) break; } //Whitespace inline + for (; index < strTypeLength; index++) { if (!JsonUtils.IsWhiteSpace(buffer[index])) break; } //Whitespace inline if (index != strTypeLength) { - var success = buffer[offset + index] == JsWriter.ItemSeperator || buffer[offset + index] == JsWriter.MapEndChar; + var success = buffer[index] == JsWriter.ItemSeperator || buffer[index] == JsWriter.MapEndChar; index++; if (success) - for (; index < strTypeLength; index++) { if (!JsonUtils.IsWhiteSpace(buffer[offset + index])) break; } //Whitespace inline + for (; index < strTypeLength; index++) { if (!JsonUtils.IsWhiteSpace(buffer[index])) break; } //Whitespace inline } continue; } catch (Exception e) { - if (JsConfig.OnDeserializationError != null) JsConfig.OnDeserializationError(instance, propType, propertyName.Value, propertyValueStr.Value, e); - if (JsConfig.ThrowOnDeserializationError) throw DeserializeTypeRef.GetSerializationException(propertyName.Value, propertyValueStr.Value, propType, e); - else Tracer.Instance.WriteWarning("WARN: failed to set dynamic property {0} with: {1}", propertyName, propertyValueStr.Value); + config.OnDeserializationError?.Invoke(instance, propType, propertyName.ToString(), propertyValueStr.ToString(), e); + if (config.ThrowOnError) throw DeserializeTypeRef.GetSerializationException(propertyName.ToString(), propertyValueStr.ToString(), propType, e); + else Tracer.Instance.WriteWarning("WARN: failed to set dynamic property {0} with: {1}", propertyName.ToString(), propertyValueStr.ToString()); } } @@ -195,31 +141,31 @@ internal static object StringToType( { var propertyValue = typeAccessor.GetProperty(propertyValueStr); if (typeConfig.OnDeserializing != null) - propertyValue = typeConfig.OnDeserializing(instance, propertyName.Value, propertyValue); + propertyValue = typeConfig.OnDeserializing(instance, propertyName.ToString(), propertyValue); typeAccessor.SetProperty(instance, propertyValue); } catch (NotSupportedException) { throw; } catch (Exception e) { - if (JsConfig.OnDeserializationError != null) JsConfig.OnDeserializationError(instance, propType ?? typeAccessor.PropertyType, propertyName.Value, propertyValueStr.Value, e); - if (JsConfig.ThrowOnDeserializationError) throw DeserializeTypeRef.GetSerializationException(propertyName.Value, propertyValueStr.Value, typeAccessor.PropertyType, e); - else Tracer.Instance.WriteWarning("WARN: failed to set property {0} with: {1}", propertyName, propertyValueStr.Value); + config.OnDeserializationError?.Invoke(instance, propType ?? typeAccessor.PropertyType, propertyName.ToString(), propertyValueStr.ToString(), e); + if (config.ThrowOnError) throw DeserializeTypeRef.GetSerializationException(propertyName.ToString(), propertyValueStr.ToString(), typeAccessor.PropertyType, e); + else Tracer.Instance.WriteWarning("WARN: failed to set property {0} with: {1}", propertyName.ToString(), propertyValueStr.ToString()); } } else { // the property is not known by the DTO - typeConfig.OnDeserializing?.Invoke(instance, propertyName.Value, propertyValueStr.Value); + typeConfig.OnDeserializing?.Invoke(instance, propertyName.ToString(), propertyValueStr.ToString()); } //Serializer.EatItemSeperatorOrMapEndChar(strType, ref index); - for (; index < strTypeLength; index++) { if (!JsonUtils.IsWhiteSpace(buffer[offset + index])) break; } //Whitespace inline + for (; index < strTypeLength; index++) { if (!JsonUtils.IsWhiteSpace(buffer[index])) break; } //Whitespace inline if (index != strType.Length) { - var success = buffer[offset + index] == JsWriter.ItemSeperator || buffer[offset + index] == JsWriter.MapEndChar; + var success = buffer[index] == JsWriter.ItemSeperator || buffer[index] == JsWriter.MapEndChar; index++; if (success) - for (; index < strTypeLength; index++) { if (!JsonUtils.IsWhiteSpace(buffer[offset + index])) break; } //Whitespace inline + for (; index < strTypeLength; index++) { if (!JsonUtils.IsWhiteSpace(buffer[index])) break; } //Whitespace inline } } diff --git a/src/ServiceStack.Text/Common/DeserializeTypeRefJsv.cs b/src/ServiceStack.Text/Common/DeserializeTypeRefJsv.cs index 7ad586fb2..2f6c57379 100644 --- a/src/ServiceStack.Text/Common/DeserializeTypeRefJsv.cs +++ b/src/ServiceStack.Text/Common/DeserializeTypeRefJsv.cs @@ -2,10 +2,6 @@ using System.Collections.Generic; using ServiceStack.Text.Json; using ServiceStack.Text.Jsv; -using ServiceStack.Text.Support; -#if NETSTANDARD2_0 -using Microsoft.Extensions.Primitives; -#endif namespace ServiceStack.Text.Common { @@ -13,34 +9,35 @@ internal static class DeserializeTypeRefJsv { private static readonly JsvTypeSerializer Serializer = (JsvTypeSerializer)JsvTypeSerializer.Instance; - internal static object StringToType( + static readonly ReadOnlyMemory typeAttr = JsWriter.TypeAttr.AsMemory(); + + internal static object StringToType(ReadOnlySpan strType, TypeConfig typeConfig, - StringSegment strType, EmptyCtorDelegate ctorFn, - Dictionary typeAccessorMap) + KeyValuePair[] typeAccessors) { var index = 0; var type = typeConfig.Type; - if (!strType.HasValue) + if (strType.IsEmpty) return null; //if (!Serializer.EatMapStartChar(strType, ref index)) - if (strType.GetChar(index++) != JsWriter.MapStartChar) - throw DeserializeTypeRef.CreateSerializationError(type, strType.Value); + if (strType[index++] != JsWriter.MapStartChar) + throw DeserializeTypeRef.CreateSerializationError(type, strType.ToString()); - if (JsonTypeSerializer.IsEmptyMap(strType)) return ctorFn(); + if (JsonTypeSerializer.IsEmptyMap(strType)) + return ctorFn(); - object instance = null; + var config = JsConfig.GetConfig(); - var propertyResolver = JsConfig.PropertyConvention == PropertyConvention.Lenient - ? ParseUtils.LenientPropertyNameResolver - : ParseUtils.DefaultPropertyNameResolver; + object instance = null; + var lenient = config.PropertyConvention == PropertyConvention.Lenient || config.TextCase == TextCase.SnakeCase; var strTypeLength = strType.Length; while (index < strTypeLength) { - var propertyName = Serializer.EatMapKey(strType, ref index); + var propertyName = Serializer.EatMapKey(strType, ref index).Trim(); //Serializer.EatMapKeySeperator(strType, ref index); index++; @@ -48,18 +45,18 @@ internal static object StringToType( var propertyValueStr = Serializer.EatValue(strType, ref index); var possibleTypeInfo = propertyValueStr != null && propertyValueStr.Length > 1; - if (possibleTypeInfo && propertyName == new StringSegment(JsWriter.TypeAttr)) + if (possibleTypeInfo && propertyName.Equals(typeAttr.Span, StringComparison.OrdinalIgnoreCase)) { var explicitTypeName = Serializer.ParseString(propertyValueStr); - var explicitType = JsConfig.TypeFinder(explicitTypeName); + var explicitType = config.TypeFinder(explicitTypeName); if (explicitType == null || explicitType.IsInterface || explicitType.IsAbstract) { - Tracer.Instance.WriteWarning("Could not find type: " + propertyValueStr); + Tracer.Instance.WriteWarning("Could not find type: " + propertyValueStr.ToString()); } else if (!type.IsAssignableFrom(explicitType)) { - Tracer.Instance.WriteWarning("Could not assign type: " + propertyValueStr); + Tracer.Instance.WriteWarning("Could not assign type: " + propertyValueStr.ToString()); } else { @@ -79,12 +76,9 @@ internal static object StringToType( var derivedType = instance.GetType(); if (derivedType != type) { - var derivedTypeConfig = new TypeConfig(derivedType); - var map = DeserializeTypeRef.GetTypeAccessorMap(derivedTypeConfig, Serializer); + var map = DeserializeTypeRef.GetCachedTypeAccessors(derivedType, Serializer); if (map != null) - { - typeAccessorMap = map; - } + typeAccessors = map; } } } @@ -97,19 +91,19 @@ internal static object StringToType( if (instance == null) instance = ctorFn(); - var typeAccessor = propertyResolver.GetTypeAccessorForProperty(propertyName, typeAccessorMap); + var typeAccessor = typeAccessors.Get(propertyName, lenient); - var propType = possibleTypeInfo && propertyValueStr.GetChar(0) == '_' ? TypeAccessor.ExtractType(Serializer, propertyValueStr) : null; + var propType = possibleTypeInfo && propertyValueStr[0] == '_' ? TypeAccessor.ExtractType(Serializer, propertyValueStr) : null; if (propType != null) { try { if (typeAccessor != null) { - var parseFn = Serializer.GetParseStringSegmentFn(propType); + var parseFn = Serializer.GetParseStringSpanFn(propType); var propertyValue = parseFn(propertyValueStr); if (typeConfig.OnDeserializing != null) - propertyValue = typeConfig.OnDeserializing(instance, propertyName.Value, propertyValue); + propertyValue = typeConfig.OnDeserializing(instance, propertyName.ToString(), propertyValue); typeAccessor.SetProperty(instance, propertyValue); } @@ -120,9 +114,9 @@ internal static object StringToType( } catch (Exception e) { - if (JsConfig.OnDeserializationError != null) JsConfig.OnDeserializationError(instance, propType, propertyName.Value, propertyValueStr.Value, e); - if (JsConfig.ThrowOnDeserializationError) throw DeserializeTypeRef.GetSerializationException(propertyName.Value, propertyValueStr.Value, propType, e); - else Tracer.Instance.WriteWarning("WARN: failed to set dynamic property {0} with: {1}", propertyName, propertyValueStr); + config.OnDeserializationError?.Invoke(instance, propType, propertyName.ToString(), propertyValueStr.ToString(), e); + if (config.ThrowOnError) throw DeserializeTypeRef.GetSerializationException(propertyName.ToString(), propertyValueStr.ToString(), propType, e); + else Tracer.Instance.WriteWarning("WARN: failed to set dynamic property {0} with: {1}", propertyName.ToString(), propertyValueStr.ToString()); } } @@ -132,21 +126,21 @@ internal static object StringToType( { var propertyValue = typeAccessor.GetProperty(propertyValueStr); if (typeConfig.OnDeserializing != null) - propertyValue = typeConfig.OnDeserializing(instance, propertyName.Value, propertyValue); + propertyValue = typeConfig.OnDeserializing(instance, propertyName.ToString(), propertyValue); typeAccessor.SetProperty(instance, propertyValue); } catch (NotSupportedException) { throw; } catch (Exception e) { - if (JsConfig.OnDeserializationError != null) JsConfig.OnDeserializationError(instance, propType ?? typeAccessor.PropertyType, propertyName.Value, propertyValueStr.Value, e); - if (JsConfig.ThrowOnDeserializationError) throw DeserializeTypeRef.GetSerializationException(propertyName.Value, propertyValueStr.Value, propType, e); - else Tracer.Instance.WriteWarning("WARN: failed to set property {0} with: {1}", propertyName, propertyValueStr); + config.OnDeserializationError?.Invoke(instance, propType ?? typeAccessor.PropertyType, propertyName.ToString(), propertyValueStr.ToString(), e); + if (config.ThrowOnError) throw DeserializeTypeRef.GetSerializationException(propertyName.ToString(), propertyValueStr.ToString(), propType, e); + else Tracer.Instance.WriteWarning("WARN: failed to set property {0} with: {1}", propertyName.ToString(), propertyValueStr.ToString()); } } else { // the property is not known by the DTO - typeConfig.OnDeserializing?.Invoke(instance, propertyName.Value, propertyValueStr); + typeConfig.OnDeserializing?.Invoke(instance, propertyName.ToString(), propertyValueStr.ToString()); } //Serializer.EatItemSeperatorOrMapEndChar(strType, ref index); diff --git a/src/ServiceStack.Text/Common/DeserializeTypeUtils.cs b/src/ServiceStack.Text/Common/DeserializeTypeUtils.cs index 4a80c81e4..9e2a2e490 100644 --- a/src/ServiceStack.Text/Common/DeserializeTypeUtils.cs +++ b/src/ServiceStack.Text/Common/DeserializeTypeUtils.cs @@ -12,24 +12,19 @@ using System; using System.Reflection; -#if NETSTANDARD2_0 -using Microsoft.Extensions.Primitives; -#else -using ServiceStack.Text.Support; -#endif namespace ServiceStack.Text.Common { public class DeserializeTypeUtils { - public static ParseStringDelegate GetParseMethod(Type type) => v => GetParseStringSegmentMethod(type)(new StringSegment(v)); + public static ParseStringDelegate GetParseMethod(Type type) => v => GetParseStringSpanMethod(type)(v.AsSpan()); - public static ParseStringSegmentDelegate GetParseStringSegmentMethod(Type type) + public static ParseStringSpanDelegate GetParseStringSpanMethod(Type type) { var typeConstructor = GetTypeStringConstructor(type); if (typeConstructor != null) { - return value => typeConstructor.Invoke(new object[] { value.Value }); + return value => typeConstructor.Invoke(new object[] { value.ToString() }); } return null; diff --git a/src/ServiceStack.Text/Common/ITypeSerializer.cs b/src/ServiceStack.Text/Common/ITypeSerializer.cs index 2b78843ec..2f25ebaa9 100644 --- a/src/ServiceStack.Text/Common/ITypeSerializer.cs +++ b/src/ServiceStack.Text/Common/ITypeSerializer.cs @@ -1,17 +1,15 @@ using System; using System.IO; using ServiceStack.Text.Json; -#if NETSTANDARD2_0 -using Microsoft.Extensions.Primitives; -#else -using ServiceStack.Text.Support; -#endif - namespace ServiceStack.Text.Common { + public delegate object ObjectDeserializerDelegate(ReadOnlySpan value); + public interface ITypeSerializer { + ObjectDeserializerDelegate ObjectDeserializer { get; set; } + bool IncludeNullValues { get; } bool IncludeNullValuesInDictionaries { get; } string TypeAttrInObject { get; } @@ -32,8 +30,8 @@ public interface ITypeSerializer void WriteNullableDateTime(TextWriter writer, object dateTime); void WriteDateTimeOffset(TextWriter writer, object oDateTimeOffset); void WriteNullableDateTimeOffset(TextWriter writer, object dateTimeOffset); - void WriteTimeSpan(TextWriter writer, object dateTimeOffset); - void WriteNullableTimeSpan(TextWriter writer, object dateTimeOffset); + void WriteTimeSpan(TextWriter writer, object timeSpan); + void WriteNullableTimeSpan(TextWriter writer, object timeSpan); void WriteGuid(TextWriter writer, object oValue); void WriteNullableGuid(TextWriter writer, object oValue); void WriteBytes(TextWriter writer, object oByteValue); @@ -51,35 +49,40 @@ public interface ITypeSerializer void WriteDouble(TextWriter writer, object doubleValue); void WriteDecimal(TextWriter writer, object decimalValue); void WriteEnum(TextWriter writer, object enumValue); - void WriteEnumFlags(TextWriter writer, object enumFlagValue); - //object EncodeMapKey(object value); +#if NET6_0_OR_GREATER + void WriteDateOnly(TextWriter writer, object oDateOnly); + void WriteNullableDateOnly(TextWriter writer, object oDateOnly); + void WriteTimeOnly(TextWriter writer, object oTimeOnly); + void WriteNullableTimeOnly(TextWriter writer, object oTimeOnly); +#endif ParseStringDelegate GetParseFn(); - ParseStringSegmentDelegate GetParseStringSegmentFn(); + ParseStringSpanDelegate GetParseStringSpanFn(); ParseStringDelegate GetParseFn(Type type); - ParseStringSegmentDelegate GetParseStringSegmentFn(Type type); + ParseStringSpanDelegate GetParseStringSpanFn(Type type); string ParseRawString(string value); string ParseString(string value); - string ParseString(StringSegment value); + string ParseString(ReadOnlySpan value); string UnescapeString(string value); - StringSegment UnescapeString(StringSegment value); + ReadOnlySpan UnescapeString(ReadOnlySpan value); + object UnescapeStringAsObject(ReadOnlySpan value); string UnescapeSafeString(string value); - StringSegment UnescapeSafeString(StringSegment value); + ReadOnlySpan UnescapeSafeString(ReadOnlySpan value); string EatTypeValue(string value, ref int i); - StringSegment EatTypeValue(StringSegment value, ref int i); + ReadOnlySpan EatTypeValue(ReadOnlySpan value, ref int i); bool EatMapStartChar(string value, ref int i); - bool EatMapStartChar(StringSegment value, ref int i); + bool EatMapStartChar(ReadOnlySpan value, ref int i); string EatMapKey(string value, ref int i); - StringSegment EatMapKey(StringSegment value, ref int i); + ReadOnlySpan EatMapKey(ReadOnlySpan value, ref int i); bool EatMapKeySeperator(string value, ref int i); - bool EatMapKeySeperator(StringSegment value, ref int i); + bool EatMapKeySeperator(ReadOnlySpan value, ref int i); void EatWhitespace(string value, ref int i); - void EatWhitespace(StringSegment value, ref int i); + void EatWhitespace(ReadOnlySpan value, ref int i); string EatValue(string value, ref int i); - StringSegment EatValue(StringSegment value, ref int i); + ReadOnlySpan EatValue(ReadOnlySpan value, ref int i); bool EatItemSeperatorOrMapEndChar(string value, ref int i); - bool EatItemSeperatorOrMapEndChar(StringSegment value, ref int i); + bool EatItemSeperatorOrMapEndChar(ReadOnlySpan value, ref int i); } } \ No newline at end of file diff --git a/src/ServiceStack.Text/Common/JsDelegates.cs b/src/ServiceStack.Text/Common/JsDelegates.cs index c0a263705..b6bb0cdcc 100644 --- a/src/ServiceStack.Text/Common/JsDelegates.cs +++ b/src/ServiceStack.Text/Common/JsDelegates.cs @@ -13,12 +13,6 @@ using System; using System.Collections.Generic; using System.IO; -#if NETSTANDARD2_0 -using Microsoft.Extensions.Primitives; -#else -using ServiceStack.Text.Support; -#endif - namespace ServiceStack.Text.Common { @@ -28,13 +22,15 @@ namespace ServiceStack.Text.Common internal delegate void WriteDelegate(TextWriter writer, object value); - internal delegate ParseStringSegmentDelegate ParseFactoryDelegate(); + internal delegate ParseStringSpanDelegate ParseFactoryDelegate(); + + public delegate object DeserializeStringSpanDelegate(Type type, ReadOnlySpan source); public delegate void WriteObjectDelegate(TextWriter writer, object obj); public delegate object ParseStringDelegate(string stringValue); - public delegate object ParseStringSegmentDelegate(StringSegment value); + public delegate object ParseStringSpanDelegate(ReadOnlySpan value); public delegate object ConvertObjectDelegate(object fromObject); diff --git a/src/ServiceStack.Text/Common/JsReader.cs b/src/ServiceStack.Text/Common/JsReader.cs index 8e27a050a..bfdcd897c 100644 --- a/src/ServiceStack.Text/Common/JsReader.cs +++ b/src/ServiceStack.Text/Common/JsReader.cs @@ -2,11 +2,7 @@ using System.Collections; using System.Collections.Generic; using System.Reflection; -#if NETSTANDARD2_0 -using Microsoft.Extensions.Primitives; -#else -using ServiceStack.Text.Support; -#endif +using System.Runtime.CompilerServices; namespace ServiceStack.Text.Common { @@ -27,79 +23,79 @@ public ParseStringDelegate GetParseFn() return GetCoreParseFn(); } - public ParseStringSegmentDelegate GetParseStringSegmentFn() + public ParseStringSpanDelegate GetParseStringSpanFn() { var onDeserializedFn = JsConfig.OnDeserializedFn; if (onDeserializedFn != null) { - var parseFn = GetCoreParseStringSegmentFn(); + var parseFn = GetCoreParseStringSpanFn(); return value => onDeserializedFn((T)parseFn(value)); } - return GetCoreParseStringSegmentFn(); + return GetCoreParseStringSpanFn(); } private ParseStringDelegate GetCoreParseFn() { - return v => GetCoreParseStringSegmentFn()(new StringSegment(v)); + return v => GetCoreParseStringSpanFn()(v.AsSpan()); } - private ParseStringSegmentDelegate GetCoreParseStringSegmentFn() + private ParseStringSpanDelegate GetCoreParseStringSpanFn() { var type = Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T); if (JsConfig.HasDeserializeFn) - return value => JsConfig.ParseFn(Serializer, value.Value); + return value => JsConfig.ParseFn(Serializer, value.Value()); if (type.IsEnum) - return x => ParseUtils.TryParseEnum(type, Serializer.UnescapeSafeString(x).Value); + return x => ParseUtils.TryParseEnum(type, Serializer.UnescapeSafeString(x).Value()); if (type == typeof(string)) - return v => Serializer.UnescapeString(v).Value; + return Serializer.UnescapeStringAsObject; if (type == typeof(object)) return DeserializeType.ObjectStringToType; var specialParseFn = ParseUtils.GetSpecialParseMethod(type); if (specialParseFn != null) - return v => specialParseFn(v.Value); + return v => specialParseFn(v.Value()); if (type.IsArray) { - return DeserializeArray.ParseStringSegment; + return DeserializeArray.ParseStringSpan; } - var builtInMethod = DeserializeBuiltin.ParseStringSegment; + var builtInMethod = DeserializeBuiltin.ParseStringSpan; if (builtInMethod != null) return value => builtInMethod(Serializer.UnescapeSafeString(value)); if (type.HasGenericType()) { if (type.IsOrHasGenericInterfaceTypeOf(typeof(IList<>))) - return DeserializeList.ParseStringSegment; + return DeserializeList.ParseStringSpan; if (type.IsOrHasGenericInterfaceTypeOf(typeof(IDictionary<,>))) - return DeserializeDictionary.GetParseStringSegmentMethod(type); + return DeserializeDictionary.GetParseStringSpanMethod(type); if (type.IsOrHasGenericInterfaceTypeOf(typeof(ICollection<>))) - return DeserializeCollection.GetParseStringSegmentMethod(type); + return DeserializeCollection.GetParseStringSpanMethod(type); if (type.HasAnyTypeDefinitionsOf(typeof(Queue<>)) || type.HasAnyTypeDefinitionsOf(typeof(Stack<>))) - return DeserializeSpecializedCollections.ParseStringSegment; + return DeserializeSpecializedCollections.ParseStringSpan; if (type.IsOrHasGenericInterfaceTypeOf(typeof(KeyValuePair<,>))) - return DeserializeKeyValuePair.GetParseStringSegmentMethod(type); + return DeserializeKeyValuePair.GetParseStringSpanMethod(type); if (type.IsOrHasGenericInterfaceTypeOf(typeof(IEnumerable<>))) - return DeserializeEnumerable.ParseStringSegment; + return DeserializeEnumerable.ParseStringSpan; - var customFn = DeserializeCustomGenericType.GetParseStringSegmentMethod(type); + var customFn = DeserializeCustomGenericType.GetParseStringSpanMethod(type); if (customFn != null) return customFn; } - var pclParseFn = PclExport.Instance.GetJsReaderParseStringSegmentMethod(typeof(T)); + var pclParseFn = PclExport.Instance.GetJsReaderParseStringSpanMethod(typeof(T)); if (pclParseFn != null) return pclParseFn; @@ -107,49 +103,61 @@ private ParseStringSegmentDelegate GetCoreParseStringSegmentFn() && (typeof(T).IsAssignableFrom(typeof(IDictionary)) || typeof(T).HasInterface(typeof(IDictionary))); if (isDictionary) { - return DeserializeDictionary.GetParseStringSegmentMethod(type); + return DeserializeDictionary.GetParseStringSpanMethod(type); } var isEnumerable = typeof(T).IsAssignableFrom(typeof(IEnumerable)) || typeof(T).HasInterface(typeof(IEnumerable)); if (isEnumerable) { - var parseFn = DeserializeSpecializedCollections.ParseStringSegment; - if (parseFn != null) return parseFn; + var parseFn = DeserializeSpecializedCollections.ParseStringSpan; + if (parseFn != null) + return parseFn; } if (type.IsValueType) { - //at first try to find more faster `ParseStringSegment` method - var staticParseStringSegmentMethod = StaticParseMethod.ParseStringSegment; - if (staticParseStringSegmentMethod != null) - return value => staticParseStringSegmentMethod(Serializer.UnescapeSafeString(value)); + //at first try to find more faster `ParseStringSpan` method + var staticParseStringSpanMethod = StaticParseMethod.ParseStringSpan; + if (staticParseStringSpanMethod != null) + return value => staticParseStringSpanMethod(Serializer.UnescapeSafeString(value)); //then try to find `Parse` method var staticParseMethod = StaticParseMethod.Parse; if (staticParseMethod != null) - return value => staticParseMethod(Serializer.UnescapeSafeString(value).Value); + return value => staticParseMethod(Serializer.UnescapeSafeString(value).ToString()); } else { - var staticParseStringSegmentMethod = StaticParseRefTypeMethod.ParseStringSegment; - if (staticParseStringSegmentMethod != null) - return value => staticParseStringSegmentMethod(Serializer.UnescapeSafeString(value)); + var staticParseStringSpanMethod = StaticParseRefTypeMethod.ParseStringSpan; + if (staticParseStringSpanMethod != null) + return value => staticParseStringSpanMethod(Serializer.UnescapeSafeString(value)); var staticParseMethod = StaticParseRefTypeMethod.Parse; if (staticParseMethod != null) - return value => staticParseMethod(Serializer.UnescapeSafeString(value).Value); + return value => staticParseMethod(Serializer.UnescapeSafeString(value).ToString()); } - var typeConstructor = DeserializeType.GetParseStringSegmentMethod(TypeConfig.GetState()); + var typeConstructor = DeserializeType.GetParseStringSpanMethod(TypeConfig.GetState()); if (typeConstructor != null) return typeConstructor; - var stringConstructor = DeserializeTypeUtils.GetParseStringSegmentMethod(type); - if (stringConstructor != null) return stringConstructor; + var stringConstructor = DeserializeTypeUtils.GetParseStringSpanMethod(type); + if (stringConstructor != null) + return stringConstructor; return DeserializeType.ParseAbstractType; } + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] + public static void InitAot() + { + var hold = DeserializeBuiltin.Parse; + hold = DeserializeArray.Parse; + DeserializeType.ExtractType(default(ReadOnlySpan)); + DeserializeArrayWithElements.ParseGenericArray(default(ReadOnlySpan), null); + DeserializeCollection.ParseCollection(default(ReadOnlySpan), null, null); + DeserializeListWithElements.ParseGenericList(default(ReadOnlySpan), null, null); + } } } diff --git a/src/ServiceStack.Text/Common/JsState.cs b/src/ServiceStack.Text/Common/JsState.cs index fe9a0cde2..778b1060a 100644 --- a/src/ServiceStack.Text/Common/JsState.cs +++ b/src/ServiceStack.Text/Common/JsState.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Runtime.CompilerServices; namespace ServiceStack.Text.Common { @@ -76,6 +77,20 @@ internal static bool InDeserializer() return InDeserializerFns != null && InDeserializerFns.Contains(typeof(T)); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool Traverse(object value) + { + if (++Depth <= JsConfig.MaxDepth) + return true; + + Tracer.Instance.WriteError( + $"Exceeded MaxDepth limit of {JsConfig.MaxDepth} attempting to serialize {value.GetType().Name}"); + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void UnTraverse() => --Depth; + internal static void Reset() { InSerializerFns = null; diff --git a/src/ServiceStack.Text/Common/JsWriter.cs b/src/ServiceStack.Text/Common/JsWriter.cs index 2e6f851af..e9bb5dd00 100644 --- a/src/ServiceStack.Text/Common/JsWriter.cs +++ b/src/ServiceStack.Text/Common/JsWriter.cs @@ -2,6 +2,7 @@ using System.Collections; using System.Collections.Generic; using System.IO; +using System.Runtime.CompilerServices; using ServiceStack.Text.Json; using ServiceStack.Text.Jsv; @@ -41,11 +42,12 @@ static JsWriter() { EscapeCharFlags[escapeChar] = true; } - var loadConfig = JsConfig.EmitCamelCaseNames; //force load + var loadConfig = JsConfig.TextCase; //force load } public static void WriteDynamic(Action callback) { + var prevState = JsState.IsWritingDynamic; JsState.IsWritingDynamic = true; try { @@ -53,7 +55,7 @@ public static void WriteDynamic(Action callback) } finally { - JsState.IsWritingDynamic = false; + JsState.IsWritingDynamic = prevState; } } @@ -153,24 +155,23 @@ public static void WriteEnumFlags(TextWriter writer, object enumFlagValue) } } - public static void AssertAllowedRuntimeType(Type type) + public static bool ShouldAllowRuntimeType(Type type) { if (!JsState.IsRuntimeType) - return; + return true; if (JsConfig.AllowRuntimeType?.Invoke(type) == true) - return; + return true; var allowAttributesNamed = JsConfig.AllowRuntimeTypeWithAttributesNamed; if (allowAttributesNamed?.Count > 0) { - var OAttrs = type.AllAttributes(); - foreach (var oAttr in OAttrs) + var oAttrs = type.AllAttributes(); + foreach (var oAttr in oAttrs) { - var attr = oAttr as Attribute; - if (attr == null) continue; + if (!(oAttr is Attribute attr)) continue; if (allowAttributesNamed.Contains(attr.GetType().Name)) - return; + return true; } } @@ -181,11 +182,17 @@ public static void AssertAllowedRuntimeType(Type type) foreach (var interfaceType in interfaces) { if (allowInterfacesNamed.Contains(interfaceType.Name)) - return; + return true; } } - throw new NotSupportedException($"{type.Name} is not an allowed Runtime Type. Whitelist Type with [RuntimeSerializable] or IRuntimeSerializable."); + return false; + } + + public static void AssertAllowedRuntimeType(Type type) + { + if (!ShouldAllowRuntimeType(type)) + throw new NotSupportedException($"{type.Name} is not an allowed Runtime Type. Whitelist Type with [RuntimeSerializable] or IRuntimeSerializable."); } } @@ -271,13 +278,31 @@ public WriteObjectDelegate GetValueTypeToStringMethod(Type type) if (type == typeof(Guid?)) return Serializer.WriteNullableGuid; + +#if NET6_0 + if (type == typeof(DateOnly)) + if (isNullable) + return Serializer.WriteNullableDateOnly; + else + return Serializer.WriteDateOnly; + if (type == typeof(DateOnly?)) + return Serializer.WriteDateOnly; + + if (type == typeof(TimeOnly)) + if (isNullable) + return Serializer.WriteNullableTimeOnly; + else + return Serializer.WriteTimeOnly; + if (type == typeof(TimeOnly?)) + return Serializer.WriteTimeOnly; +#endif } else { if (underlyingType.IsEnum) - return type.FirstAttribute() != null - ? (WriteObjectDelegate)Serializer.WriteEnumFlags - : Serializer.WriteEnum; + { + return Serializer.WriteEnum; + } } if (type.HasInterface(typeof(IFormattable))) @@ -334,9 +359,15 @@ public void WriteValue(TextWriter writer, object value) var valueWriter = (IValueWriter)value; valueWriter.WriteTo(Serializer, writer); } + + void ThrowTaskNotSupported(TextWriter writer, object value) => + throw new NotSupportedException("Serializing Task's is not supported. Did you forget to await it?"); private WriteObjectDelegate GetCoreWriteFn() { + if (typeof(T).IsInstanceOf(typeof(System.Threading.Tasks.Task))) + return ThrowTaskNotSupported; + if (typeof(T).IsValueType && !JsConfig.TreatAsRefType(typeof(T)) || JsConfig.HasSerializeFn) { return JsConfig.HasSerializeFn @@ -382,7 +413,7 @@ private WriteObjectDelegate GetCoreWriteFn() mapTypeArgs[0], mapTypeArgs[1]); var keyWriteFn = Serializer.GetWriteFn(mapTypeArgs[0]); - var valueWriteFn = typeof(T) == typeof(JsonObject) + var valueWriteFn = typeof(JsonObject).IsAssignableFrom(typeof(T)) ? JsonObject.WriteValue : Serializer.GetWriteFn(mapTypeArgs[1]); @@ -427,12 +458,11 @@ private WriteObjectDelegate GetCoreWriteFn() return Serializer.WriteBuiltIn; } - public Dictionary SpecialTypes; + public readonly Dictionary SpecialTypes; public WriteObjectDelegate GetSpecialWriteFn(Type type) { - WriteObjectDelegate writeFn = null; - if (SpecialTypes.TryGetValue(type, out writeFn)) + if (SpecialTypes.TryGetValue(type, out var writeFn)) return writeFn; if (type.IsInstanceOfType(typeof(Type))) @@ -448,5 +478,22 @@ public void WriteType(TextWriter writer, object value) { Serializer.WriteRawString(writer, JsConfig.TypeWriter((Type)value)); } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] + public static void InitAot() + { + WriteListsOfElements.WriteList(null, null); + WriteListsOfElements.WriteIList(null, null); + WriteListsOfElements.WriteEnumerable(null, null); + WriteListsOfElements.WriteListValueType(null, null); + WriteListsOfElements.WriteIListValueType(null, null); + WriteListsOfElements.WriteGenericArrayValueType(null, null); + WriteListsOfElements.WriteArray(null, null); + + TranslateListWithElements.LateBoundTranslateToGenericICollection(null, null); + TranslateListWithConvertibleElements.LateBoundTranslateToGenericICollection(null, null); + + QueryStringWriter.WriteObject(null, null); + } } } diff --git a/src/ServiceStack.Text/Common/ParseUtils.cs b/src/ServiceStack.Text/Common/ParseUtils.cs index 63b803dba..bdc4e7363 100644 --- a/src/ServiceStack.Text/Common/ParseUtils.cs +++ b/src/ServiceStack.Text/Common/ParseUtils.cs @@ -11,21 +11,16 @@ // using System; +using System.Runtime.CompilerServices; +using System.Runtime.Serialization; namespace ServiceStack.Text.Common { internal static class ParseUtils { - public static readonly IPropertyNameResolver DefaultPropertyNameResolver = new DefaultPropertyNameResolver(); - public static readonly IPropertyNameResolver LenientPropertyNameResolver = new LenientPropertyNameResolver(); - public static object NullValueType(Type type) { -#if NETFX_CORE - return type.GetTypeInfo().IsValueType ? Activator.CreateInstance(type) : null; -#else return type.GetDefaultValue(); -#endif } public static object ParseObject(string value) @@ -66,14 +61,15 @@ public static object TryParseEnum(Type enumType, string str) if (str == null) return null; - if (JsConfig.EmitLowercaseUnderscoreNames) + if (JsConfig.TextCase == TextCase.SnakeCase) { string[] names = Enum.GetNames(enumType); if (Array.IndexOf(names, str) == -1) // case sensitive ... could use Linq Contains() extension with StringComparer.InvariantCultureIgnoreCase instead for a slight penalty str = str.Replace("_", ""); } - return Enum.Parse(enumType, str, ignoreCase: true); + var enumInfo = CachedTypeInfo.Get(enumType).EnumInfo; + return enumInfo.Parse(str); } } diff --git a/src/ServiceStack.Text/Common/StaticParseMethod.cs b/src/ServiceStack.Text/Common/StaticParseMethod.cs index 7becbe2ee..5b38bd009 100644 --- a/src/ServiceStack.Text/Common/StaticParseMethod.cs +++ b/src/ServiceStack.Text/Common/StaticParseMethod.cs @@ -11,14 +11,7 @@ // using System; -using System.Reflection; -using System.Linq; using ServiceStack.Text.Jsv; -#if NETSTANDARD2_0 -using Microsoft.Extensions.Primitives; -#else -using ServiceStack.Text.Support; -#endif namespace ServiceStack.Text.Common { @@ -58,24 +51,26 @@ public static ParseStringDelegate GetParseFn(string parseMethod) return null; } - public static ParseStringSegmentDelegate GetParseStringSegmentFn(string parseMethod) + delegate T ParseStringSpanGenericDelegate(ReadOnlySpan value); + + public static ParseStringSpanDelegate GetParseStringSpanFn(string parseMethod) { // Get the static Parse(string) method on the type supplied var parseMethodInfo = typeof(T).GetStaticMethod(parseMethod, new[] { typeof(string) }); if (parseMethodInfo == null) return null; - ParseStringSegmentDelegate parseDelegate = null; + ParseStringSpanDelegate parseDelegate = null; try { if (parseMethodInfo.ReturnType != typeof(T)) { - parseDelegate = (ParseStringSegmentDelegate)parseMethodInfo.MakeDelegate(typeof(ParseStringSegmentDelegate), false); + parseDelegate = (ParseStringSpanDelegate)parseMethodInfo.MakeDelegate(typeof(ParseStringSpanDelegate), false); } if (parseDelegate == null) { //Try wrapping strongly-typed return with wrapper fn. - var typedParseDelegate = (Func)parseMethodInfo.MakeDelegate(typeof(Func)); + var typedParseDelegate = (ParseStringSpanGenericDelegate)parseMethodInfo.MakeDelegate(typeof(ParseStringSpanGenericDelegate)); parseDelegate = x => typedParseDelegate(x); } } @@ -85,7 +80,7 @@ public static ParseStringSegmentDelegate GetParseStringSegmentFn(string parse } if (parseDelegate != null) - return value => parseDelegate(new StringSegment(value.Value.FromCsvField())); + return value => parseDelegate(value.ToString().FromCsvField().AsSpan()); return null; } @@ -94,18 +89,18 @@ public static ParseStringSegmentDelegate GetParseStringSegmentFn(string parse public static class StaticParseMethod { const string ParseMethod = "Parse"; - const string ParseStringSegmentMethod = "ParseStringSegment"; + const string ParseStringSpanMethod = "ParseStringSpanMethod"; private static readonly ParseStringDelegate CacheFn; - private static readonly ParseStringSegmentDelegate CacheStringSegmentFn; + private static readonly ParseStringSpanDelegate CacheStringSpanFn; public static ParseStringDelegate Parse => CacheFn; - public static ParseStringSegmentDelegate ParseStringSegment => CacheStringSegmentFn; + public static ParseStringSpanDelegate ParseStringSpan => CacheStringSpanFn; static StaticParseMethod() { CacheFn = ParseMethodUtilities.GetParseFn(ParseMethod); - CacheStringSegmentFn = ParseMethodUtilities.GetParseStringSegmentFn(ParseMethod); + CacheStringSpanFn = ParseMethodUtilities.GetParseStringSpanFn(ParseMethod); } } @@ -117,20 +112,20 @@ internal static class StaticParseRefTypeMethod ? "ParseJsv" : "ParseJson"; - static readonly string ParseStringSegmentMethod = typeof(TSerializer) == typeof(JsvTypeSerializer) - ? "ParseStringSegmentJsv" - : "ParseStringSegmentJson"; + static readonly string ParseStringSpanMethod = typeof(TSerializer) == typeof(JsvTypeSerializer) + ? "ParseStringSpanJsv" + : "ParseStringSpanJson"; private static readonly ParseStringDelegate CacheFn; - private static readonly ParseStringSegmentDelegate CacheStringSegmentFn; + private static readonly ParseStringSpanDelegate CacheStringSpanFn; public static ParseStringDelegate Parse => CacheFn; - public static ParseStringSegmentDelegate ParseStringSegment => CacheStringSegmentFn; + public static ParseStringSpanDelegate ParseStringSpan => CacheStringSpanFn; static StaticParseRefTypeMethod() { CacheFn = ParseMethodUtilities.GetParseFn(ParseMethod); - CacheStringSegmentFn = ParseMethodUtilities.GetParseStringSegmentFn(ParseStringSegmentMethod); + CacheStringSpanFn = ParseMethodUtilities.GetParseStringSpanFn(ParseStringSpanMethod); } } diff --git a/src/ServiceStack.Text/Common/WriteDictionary.cs b/src/ServiceStack.Text/Common/WriteDictionary.cs index a91112353..4041e801b 100644 --- a/src/ServiceStack.Text/Common/WriteDictionary.cs +++ b/src/ServiceStack.Text/Common/WriteDictionary.cs @@ -72,9 +72,8 @@ public override int GetHashCode() public static Action GetWriteGenericDictionary(Type keyType, Type valueType) { - WriteMapDelegate writeFn; var mapKey = new MapKey(keyType, valueType); - if (CacheFns.TryGetValue(mapKey, out writeFn)) return writeFn.Invoke; + if (CacheFns.TryGetValue(mapKey, out var writeFn)) return writeFn.Invoke; var genericType = typeof(ToStringDictionaryMethods<,,>).MakeGenericType(keyType, valueType, typeof(TSerializer)); var mi = genericType.GetStaticMethod("WriteIDictionary"); @@ -206,6 +205,10 @@ public static void WriteGenericIDictionary( writer.Write(JsonUtils.Null); return; } + + if (map is JsonObject jsonObject) + map = (IDictionary) jsonObject.ToUnescapedDictionary(); + writer.Write(JsWriter.MapStartChar); var encodeMapKey = Serializer.GetTypeInfo(typeof(TKey)).EncodeMapKey; @@ -239,7 +242,6 @@ public static void WriteGenericIDictionary( { writeKeyFn(writer, kvp.Key); } - } finally { diff --git a/src/ServiceStack.Text/Common/WriteLists.cs b/src/ServiceStack.Text/Common/WriteLists.cs index fbda8c51a..cc75def01 100644 --- a/src/ServiceStack.Text/Common/WriteLists.cs +++ b/src/ServiceStack.Text/Common/WriteLists.cs @@ -216,7 +216,20 @@ public static class WriteListsOfElements static WriteListsOfElements() { - ElementWriteFn = JsWriter.GetTypeSerializer().GetWriteFn(); + var fn = JsWriter.GetTypeSerializer().GetWriteFn(); + ElementWriteFn = (writer, obj) => { + try + { + if (!JsState.Traverse(obj)) + return; + + fn(writer, obj); + } + finally + { + JsState.UnTraverse(); + } + }; } public static void WriteList(TextWriter writer, object oList) diff --git a/src/ServiceStack.Text/Common/WriteType.cs b/src/ServiceStack.Text/Common/WriteType.cs index 8cdc34ab5..192c45343 100644 --- a/src/ServiceStack.Text/Common/WriteType.cs +++ b/src/ServiceStack.Text/Common/WriteType.cs @@ -73,9 +73,11 @@ private static bool TryWriteSelfType(TextWriter writer) { if (ShouldSkipType()) return false; - Serializer.WriteRawString(writer, JsConfig.TypeAttr); + var config = JsConfig.GetConfig(); + + Serializer.WriteRawString(writer, config.TypeAttr); writer.Write(JsWriter.MapKeySeperator); - Serializer.WriteRawString(writer, JsConfig.TypeWriter(typeof(T))); + Serializer.WriteRawString(writer, config.TypeWriter(typeof(T))); return true; } @@ -83,16 +85,15 @@ private static bool TryWriteTypeInfo(TextWriter writer, object obj) { if (obj == null || ShouldSkipType()) return false; - Serializer.WriteRawString(writer, JsConfig.TypeAttr); + var config = JsConfig.GetConfig(); + + Serializer.WriteRawString(writer, config.TypeAttr); writer.Write(JsWriter.MapKeySeperator); - Serializer.WriteRawString(writer, JsConfig.TypeWriter(obj.GetType())); + Serializer.WriteRawString(writer, config.TypeWriter(obj.GetType())); return true; } - public static WriteObjectDelegate Write - { - get { return CacheFn; } - } + public static WriteObjectDelegate Write => CacheFn; private static WriteObjectDelegate GetWriteFn() { @@ -208,11 +209,8 @@ private static bool Init() && JsConfig.HasSerializeFn.Contains(propertyType) && !JsConfig.HasIncludeDefaultValue.Contains(propertyType); bool propertySuppressDefaultAttribute = false; -#if (NETFX_CORE) - var shouldSerialize = (Func)null; -#else + var shouldSerialize = GetShouldSerializeMethod(fieldInfo); -#endif if (isDataContract) { var dcsDataMember = fieldInfo.GetDataMember(); @@ -260,18 +258,19 @@ private static bool Init() internal struct TypePropertyWriter { - internal string PropertyName + internal string GetPropertyName(Config config) { - get + switch (config.TextCase) { - return JsConfig.EmitCamelCaseNames.GetValueOrDefault(JsConfig.EmitCamelCaseNames) - ? propertyNameCLSFriendly - : JsConfig.EmitLowercaseUnderscoreNames.GetValueOrDefault(JsConfig.EmitLowercaseUnderscoreNames) - ? propertyNameLowercaseUnderscore - : propertyName; + case TextCase.CamelCase: + return propertyNameCLSFriendly; + case TextCase.SnakeCase: + return propertyNameLowercaseUnderscore; + default: + return propertyName; } } - + internal readonly Type PropertyType; internal readonly string propertyName; internal readonly int propertyOrder; @@ -310,21 +309,24 @@ public TypePropertyWriter(Type propertyType, string propertyName, string propert this.isEnum = isEnum; } - public bool ShouldWriteProperty(object propertyValue) + public bool ShouldWriteProperty(object propertyValue, Config config) { - if ((propertySuppressDefaultAttribute || JsConfig.ExcludeDefaultValues) && Equals(DefaultValue, propertyValue)) - return false; - - if (!Serializer.IncludeNullValues - && (propertyValue == null - || (propertySuppressDefaultConfig && Equals(DefaultValue, propertyValue)))) + var isDefaultValue = propertyValue == null || Equals(DefaultValue, propertyValue); + if (isDefaultValue) { - return false; + if (!isEnum) + { + if (propertySuppressDefaultAttribute || config.ExcludeDefaultValues) + return false; + if (!Serializer.IncludeNullValues && (propertyValue == null || propertySuppressDefaultConfig)) + return false; + } + else if (!config.IncludeDefaultEnums) + { + return false; + } } - if (isEnum && !JsConfig.IncludeDefaultEnums && Equals(DefaultValue, propertyValue)) - return false; - return true; } } @@ -366,15 +368,6 @@ public static void WriteAbstractProperties(TextWriter writer, object value) WriteLateboundProperties(writer, value, valueType); } - internal static string GetPropertyName(string propertyName) - { - return JsConfig.EmitCamelCaseNames.GetValueOrDefault(JsConfig.EmitCamelCaseNames) - ? propertyName.ToCamelCase() - : JsConfig.EmitLowercaseUnderscoreNames.GetValueOrDefault(JsConfig.EmitLowercaseUnderscoreNames) - ? propertyName.ToLowercaseUnderscore() - : propertyName; - } - public static void WriteProperties(TextWriter writer, object instance) { if (instance == null) @@ -405,19 +398,21 @@ public static void WriteProperties(TextWriter writer, object instance) if (PropertyWriters != null) { + var config = JsConfig.GetConfig(); + var typedInstance = (T)instance; var len = PropertyWriters.Length; for (int index = 0; index < len; index++) { var propertyWriter = PropertyWriters[index]; - if (propertyWriter.shouldSerialize != null && !propertyWriter.shouldSerialize(typedInstance)) + if (propertyWriter.shouldSerialize?.Invoke(typedInstance) == false) continue; var dontSkipDefault = false; if (propertyWriter.shouldSerializeDynamic != null) { - var shouldSerialize = propertyWriter.shouldSerializeDynamic(typedInstance, propertyWriter.PropertyName); + var shouldSerialize = propertyWriter.shouldSerializeDynamic(typedInstance, propertyWriter.GetPropertyName(config)); if (shouldSerialize.HasValue) { if (shouldSerialize.Value) @@ -431,17 +426,16 @@ public static void WriteProperties(TextWriter writer, object instance) if (!dontSkipDefault) { - if (!propertyWriter.ShouldWriteProperty(propertyValue)) + if (!propertyWriter.ShouldWriteProperty(propertyValue, config)) continue; - if (JsConfig.ExcludePropertyReferences != null - && JsConfig.ExcludePropertyReferences.Contains(propertyWriter.propertyReferenceName)) continue; + if (config.ExcludePropertyReferences?.Contains(propertyWriter.propertyReferenceName) == true) continue; } if (i++ > 0) writer.Write(JsWriter.ItemSeperator); - Serializer.WritePropertyName(writer, propertyWriter.PropertyName); + Serializer.WritePropertyName(writer, propertyWriter.GetPropertyName(config)); writer.Write(JsWriter.MapKeySeperator); if (typeof(TSerializer) == typeof(JsonTypeSerializer)) JsState.IsWritingValue = true; @@ -472,9 +466,23 @@ public static void WriteProperties(TextWriter writer, object instance) private static void WriteLateboundProperties(TextWriter writer, object value, Type valueType) { var writeFn = Serializer.GetWriteFn(valueType); + var prevState = JsState.IsWritingDynamic; if (!JsConfig.ExcludeTypeInfo.GetValueOrDefault()) JsState.IsWritingDynamic = true; writeFn(writer, value); - if (!JsConfig.ExcludeTypeInfo.GetValueOrDefault()) JsState.IsWritingDynamic = false; + if (!JsConfig.ExcludeTypeInfo.GetValueOrDefault()) JsState.IsWritingDynamic = prevState; + } + + internal static string GetPropertyName(string propertyName, Config config) + { + switch (config.TextCase) + { + case TextCase.CamelCase: + return propertyName.ToCamelCase(); + case TextCase.SnakeCase: + return propertyName.ToLowercaseUnderscore(); + default: + return propertyName; + } } private static readonly char[] ArrayBrackets = { '[', ']' }; @@ -484,12 +492,15 @@ public static void WriteComplexQueryStringProperties(string typeName, TextWriter var i = 0; if (PropertyWriters != null) { + var config = JsConfig.GetConfig(); + var typedInstance = (T)instance; var len = PropertyWriters.Length; for (var index = 0; index < len; index++) { var propertyWriter = PropertyWriters[index]; - if (propertyWriter.shouldSerialize != null && !propertyWriter.shouldSerialize(typedInstance)) continue; + if (propertyWriter.shouldSerialize != null && !propertyWriter.shouldSerialize(typedInstance)) + continue; var propertyValue = instance != null ? propertyWriter.GetterFn(typedInstance) : null; if (propertyWriter.propertySuppressDefaultAttribute && Equals(propertyWriter.DefaultValue, propertyValue)) @@ -500,8 +511,8 @@ public static void WriteComplexQueryStringProperties(string typeName, TextWriter && !Serializer.IncludeNullValues) continue; - if (JsConfig.ExcludePropertyReferences != null - && JsConfig.ExcludePropertyReferences.Contains(propertyWriter.propertyReferenceName)) continue; + if (config.ExcludePropertyReferences != null && config.ExcludePropertyReferences.Contains(propertyWriter.propertyReferenceName)) + continue; if (i++ > 0) writer.Write('&'); @@ -512,7 +523,7 @@ public static void WriteComplexQueryStringProperties(string typeName, TextWriter !propertyValueType.HasInterface(typeof(IEnumerable))) { //Nested Complex Type: legal_entity[dob][day]=1 - var prefix = "{0}[{1}]".Fmt(typeName, propertyWriter.PropertyName); + var prefix = $"{typeName}[{propertyWriter.GetPropertyName(config)}]"; var props = propertyValueType.GetSerializableProperties(); for (int j = 0; j < props.Length; j++) { @@ -526,7 +537,7 @@ public static void WriteComplexQueryStringProperties(string typeName, TextWriter writer.Write(prefix); writer.Write('['); - writer.Write(GetPropertyName(pi.Name)); + writer.Write(GetPropertyName(pi.Name, config)); writer.Write("]="); if (pValue == null) @@ -543,7 +554,7 @@ public static void WriteComplexQueryStringProperties(string typeName, TextWriter { writer.Write(typeName); writer.Write('['); - writer.Write(propertyWriter.PropertyName); + writer.Write(propertyWriter.GetPropertyName(config)); writer.Write("]="); if (propertyValue == null) @@ -564,6 +575,8 @@ public static void WriteQueryString(TextWriter writer, object instance) try { JsState.QueryStringMode = true; + var config = JsConfig.GetConfig(); + var i = 0; var typedInstance = (T)instance; foreach (var propertyWriter in PropertyWriters) @@ -585,12 +598,12 @@ public static void WriteQueryString(TextWriter writer, object instance) var nonEnumerableUserType = !isEnumerable && (propertyType.IsUserType() || propertyType.IsInterface); if (nonEnumerableUserType || propertyType.IsOrHasGenericInterfaceTypeOf(typeof(IDictionary<,>))) { - if (QueryStringSerializer.ComplexTypeStrategy(writer, propertyWriter.PropertyName, propertyValue)) + if (QueryStringSerializer.ComplexTypeStrategy(writer, propertyWriter.GetPropertyName(config), propertyValue)) continue; } } - Serializer.WritePropertyName(writer, propertyWriter.PropertyName); + Serializer.WritePropertyName(writer, propertyWriter.GetPropertyName(config)); writer.Write('='); if (strValue != null) @@ -609,7 +622,7 @@ public static void WriteQueryString(TextWriter writer, object instance) var enumerableWriter = new StreamWriter(ms); //ms disposed in using propertyWriter.WriteFn(enumerableWriter, propertyValue); enumerableWriter.Flush(); - var output = ms.ToArray().FromUtf8Bytes(); + var output = ms.ReadToEnd(); output = output.Trim(ArrayBrackets); writer.Write(output); } diff --git a/src/ServiceStack.Text/CsvConfig.cs b/src/ServiceStack.Text/CsvConfig.cs index 4a7ecc6bd..b6f2c8658 100644 --- a/src/ServiceStack.Text/CsvConfig.cs +++ b/src/ServiceStack.Text/CsvConfig.cs @@ -15,8 +15,8 @@ static CsvConfig() private static CultureInfo sRealNumberCultureInfo; public static CultureInfo RealNumberCultureInfo { - get { return sRealNumberCultureInfo ?? CultureInfo.InvariantCulture; } - set { sRealNumberCultureInfo = value; } + get => sRealNumberCultureInfo ?? CultureInfo.InvariantCulture; + set => sRealNumberCultureInfo = value; } [ThreadStatic] @@ -24,10 +24,7 @@ public static CultureInfo RealNumberCultureInfo private static string sItemSeperatorString; public static string ItemSeperatorString { - get - { - return tsItemSeperatorString ?? sItemSeperatorString ?? JsWriter.ItemSeperatorString; - } + get => tsItemSeperatorString ?? sItemSeperatorString ?? JsWriter.ItemSeperatorString; set { tsItemSeperatorString = value; @@ -41,10 +38,7 @@ public static string ItemSeperatorString private static string sItemDelimiterString; public static string ItemDelimiterString { - get - { - return tsItemDelimiterString ?? sItemDelimiterString ?? JsWriter.QuoteString; - } + get => tsItemDelimiterString ?? sItemDelimiterString ?? JsWriter.QuoteString; set { tsItemDelimiterString = value; @@ -61,10 +55,7 @@ public static string ItemDelimiterString private static string sEscapedItemDelimiterString; internal static string EscapedItemDelimiterString { - get - { - return tsEscapedItemDelimiterString ?? sEscapedItemDelimiterString ?? DefaultEscapedItemDelimiterString; - } + get => tsEscapedItemDelimiterString ?? sEscapedItemDelimiterString ?? DefaultEscapedItemDelimiterString; set { tsEscapedItemDelimiterString = value; @@ -79,10 +70,7 @@ internal static string EscapedItemDelimiterString private static string[] sEscapeStrings; public static string[] EscapeStrings { - get - { - return tsEscapeStrings ?? sEscapeStrings ?? defaultEscapeStrings; - } + get => tsEscapeStrings ?? sEscapeStrings ?? defaultEscapeStrings; private set { tsEscapeStrings = value; @@ -105,10 +93,7 @@ private static void ResetEscapeStrings() private static string sRowSeparatorString; public static string RowSeparatorString { - get - { - return tsRowSeparatorString ?? sRowSeparatorString ?? "\r\n"; - } + get => tsRowSeparatorString ?? sRowSeparatorString ?? "\r\n"; set { tsRowSeparatorString = value; @@ -135,10 +120,7 @@ public static class CsvConfig private static Dictionary customHeadersMap; public static Dictionary CustomHeadersMap { - get - { - return customHeadersMap; - } + get => customHeadersMap; set { customHeadersMap = value; diff --git a/src/ServiceStack.Text/CsvReader.cs b/src/ServiceStack.Text/CsvReader.cs index 5cec7ef5d..f468ae62a 100644 --- a/src/ServiceStack.Text/CsvReader.cs +++ b/src/ServiceStack.Text/CsvReader.cs @@ -59,7 +59,8 @@ public static List ParseLines(string csv) return rows; } - public static List ParseFields(string line) + public static List ParseFields(string line) => ParseFields(line, null); + public static List ParseFields(string line, Func parseFn) { var to = new List(); if (string.IsNullOrEmpty(line)) @@ -70,7 +71,7 @@ public static List ParseFields(string line) while (++i <= len) { var value = EatValue(line, ref i); - to.Add(value.FromCsvField()); + to.Add(parseFn != null ? parseFn(value.FromCsvField()) : value.FromCsvField()); } return to; @@ -208,7 +209,6 @@ internal static void Reset() PropertyConverters = new List(); PropertyConvertersMap = new Dictionary(PclExport.Instance.InvariantComparerIgnoreCase); - var isDataContract = typeof(T).IsDto(); foreach (var propertyInfo in TypeConfig.Properties) { if (!propertyInfo.CanWrite || propertyInfo.GetSetMethod(nonPublic:true) == null) continue; @@ -221,12 +221,9 @@ internal static void Reset() var converter = JsvReader.GetParseFn(propertyInfo.PropertyType); PropertyConverters.Add(converter); - if (isDataContract) - { - var dcsDataMemberName = propertyInfo.GetDataMemberName(); - if (dcsDataMemberName != null) - propertyName = dcsDataMemberName; - } + var dcsDataMemberName = propertyInfo.GetDataMemberName(); + if (dcsDataMemberName != null) + propertyName = dcsDataMemberName; Headers.Add(propertyName); PropertySettersMap[propertyName] = setter; @@ -315,6 +312,8 @@ public static object ReadObjectRow(string csv) public static List> ReadStringDictionary(IEnumerable rows) { + if (rows == null) return null; //AOT + var to = new List>(); List headers = null; @@ -357,11 +356,15 @@ public static List Read(List rows) List headers = null; if (!CsvConfig.OmitHeaders || Headers.Count == 0) - headers = CsvReader.ParseFields(rows[0]); + { + headers = CsvReader.ParseFields(rows[0], s => s.Trim()); + } if (typeof(T).IsValueType || typeof(T) == typeof(string)) { - return GetSingleRow(rows, typeof(T)); + return rows.Count == 1 + ? GetSingleRow(CsvReader.ParseFields(rows[0]), typeof(T)) + : GetSingleRow(rows, typeof(T)); } for (var rowIndex = headers == null ? 0 : 1; rowIndex < rows.Count; rowIndex++) diff --git a/src/ServiceStack.Text/CsvSerializer.cs b/src/ServiceStack.Text/CsvSerializer.cs index 3a9416e25..6454916f8 100644 --- a/src/ServiceStack.Text/CsvSerializer.cs +++ b/src/ServiceStack.Text/CsvSerializer.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Reflection; +using System.Runtime.CompilerServices; using System.Text; using System.Threading; using ServiceStack.Text.Common; @@ -16,6 +17,8 @@ public class CsvSerializer //Don't emit UTF8 BOM by default public static Encoding UseEncoding { get; set; } = PclExport.Instance.GetUTF8Encoding(false); + public static Action OnSerialize { get; set; } + private static Dictionary WriteFnCache = new Dictionary(); internal static WriteObjectDelegate GetWriteFn(Type type) { @@ -34,8 +37,7 @@ internal static WriteObjectDelegate GetWriteFn(Type type) do { snapshot = WriteFnCache; - newCache = new Dictionary(WriteFnCache); - newCache[type] = writeFn; + newCache = new Dictionary(WriteFnCache) {[type] = writeFn}; } while (!ReferenceEquals( Interlocked.CompareExchange(ref WriteFnCache, newCache, snapshot), snapshot)); @@ -67,8 +69,7 @@ internal static ParseStringDelegate GetReadFn(Type type) do { snapshot = ReadFnCache; - newCache = new Dictionary(ReadFnCache); - newCache[type] = writeFn; + newCache = new Dictionary(ReadFnCache) {[type] = writeFn}; } while (!ReferenceEquals( Interlocked.CompareExchange(ref ReadFnCache, newCache, snapshot), snapshot)); @@ -152,7 +153,7 @@ public static T DeserializeFromReader(TextReader reader) public static T DeserializeFromString(string text) { - if (string.IsNullOrEmpty(text)) return default(T); + if (string.IsNullOrEmpty(text)) return default; var results = CsvSerializer.ReadObject(text); return ConvertFrom(results); } @@ -160,10 +161,19 @@ public static T DeserializeFromString(string text) public static object DeserializeFromString(Type type, string text) { if (string.IsNullOrEmpty(text)) return null; - var fn = GetReadFn(type); - var result = fn(text); - var converted = ConvertFrom(type, result); - return converted; + var hold = JsState.IsCsv; + JsState.IsCsv = true; + try + { + var fn = GetReadFn(type); + var result = fn(text); + var converted = ConvertFrom(type, result); + return converted; + } + finally + { + JsState.IsCsv = hold; + } } public static void WriteLateBoundObject(TextWriter writer, object value) @@ -223,6 +233,22 @@ internal static object ConvertFrom(Type type, object results) return results.ConvertTo(type); } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] + public static void InitAot() + { + CsvSerializer.WriteFn(); + CsvSerializer.WriteObject(null, null); + CsvWriter.Write(null, default(IEnumerable)); + CsvWriter.WriteRow(null, default(T)); + CsvWriter.WriteObject(null, default(IEnumerable)); + CsvWriter.WriteObjectRow(null, default(IEnumerable)); + + CsvReader.ReadRow(null); + CsvReader.ReadObject(null); + CsvReader.ReadObjectRow(null); + CsvReader.ReadStringDictionary(null); + } } public static class CsvSerializer @@ -255,9 +281,9 @@ private static WriteObjectDelegate GetWriteFn() bestCandidateEnumerableType = typeof(T).GetTypeWithGenericTypeDefinitionOf(typeof(IEnumerable<>)); if (bestCandidateEnumerableType != null) { - var dictionarOrKvps = typeof(T).HasInterface(typeof(IEnumerable>)) - || typeof(T).HasInterface(typeof(IEnumerable>)); - if (dictionarOrKvps) + var dictionaryOrKvps = typeof(T).HasInterface(typeof(IEnumerable>)) + || typeof(T).HasInterface(typeof(IEnumerable>)); + if (dictionaryOrKvps) { return WriteSelf; } @@ -370,6 +396,7 @@ public static void WriteObject(TextWriter writer, object value) JsState.IsCsv = true; try { + CsvSerializer.OnSerialize?.Invoke(value); WriteCacheFn(writer, value); } finally @@ -517,6 +544,8 @@ public static object ReadEnumerableProperty(string row) public static object ReadNonEnumerableType(string row) { + if (row == null) return null; //AOT + var value = readElementFn(row); var to = typeof(T).CreateInstance(); valueSetter(to, value); @@ -525,6 +554,8 @@ public static object ReadNonEnumerableType(string row) public static object ReadObject(string value) { + if (value == null) return null; //AOT + var hold = JsState.IsCsv; JsState.IsCsv = true; try @@ -536,7 +567,5 @@ public static object ReadObject(string value) JsState.IsCsv = hold; } } - - } } \ No newline at end of file diff --git a/src/ServiceStack.Text/CsvWriter.cs b/src/ServiceStack.Text/CsvWriter.cs index 2a80d8db2..1ad3cbfd9 100644 --- a/src/ServiceStack.Text/CsvWriter.cs +++ b/src/ServiceStack.Text/CsvWriter.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Reflection; using ServiceStack.Text.Common; namespace ServiceStack.Text @@ -10,6 +11,8 @@ internal class CsvDictionaryWriter { public static void WriteRow(TextWriter writer, IEnumerable row) { + if (writer == null) return; //AOT + var ranOnce = false; foreach (var field in row) { @@ -22,6 +25,8 @@ public static void WriteRow(TextWriter writer, IEnumerable row) public static void WriteObjectRow(TextWriter writer, IEnumerable row) { + if (writer == null) return; //AOT + var ranOnce = false; foreach (var field in row) { @@ -118,9 +123,10 @@ public static class CsvWriter { public static bool HasAnyEscapeChars(string value) { - return CsvConfig.EscapeStrings.Any(value.Contains) - || value[0] == JsWriter.ListStartChar - || value[0] == JsWriter.MapStartChar; + return !string.IsNullOrEmpty(value) + && (CsvConfig.EscapeStrings.Any(value.Contains) + || value[0] == JsWriter.ListStartChar + || value[0] == JsWriter.MapStartChar); } internal static void WriteItemSeperatorIfRanOnce(TextWriter writer, ref bool ranOnce) @@ -139,6 +145,7 @@ public class CsvWriter public static List Headers { get; set; } internal static List> PropertyGetters; + internal static List PropertyInfos; private static readonly WriteObjectDelegate OptimizedWriter; @@ -158,20 +165,19 @@ internal static void Reset() Headers = new List(); PropertyGetters = new List>(); - var isDataContract = typeof(T).IsDto(); + PropertyInfos = new List(); foreach (var propertyInfo in TypeConfig.Properties) { if (!propertyInfo.CanRead || propertyInfo.GetGetMethod(nonPublic:true) == null) continue; if (!TypeSerializer.CanCreateFromString(propertyInfo.PropertyType)) continue; PropertyGetters.Add(propertyInfo.CreateGetter()); + PropertyInfos.Add(propertyInfo); + var propertyName = propertyInfo.Name; - if (isDataContract) - { - var dcsDataMemberName = propertyInfo.GetDataMemberName(); - if (dcsDataMemberName != null) - propertyName = dcsDataMemberName; - } + var dcsDataMemberName = propertyInfo.GetDataMemberName(); + if (dcsDataMemberName != null) + propertyName = dcsDataMemberName; Headers.Add(propertyName); } } @@ -241,17 +247,22 @@ public static List> GetRows(IEnumerable records) public static void WriteObject(TextWriter writer, object records) { + if (writer == null) return; //AOT + Write(writer, (IEnumerable)records); } public static void WriteObjectRow(TextWriter writer, object record) { + if (writer == null) return; //AOT + WriteRow(writer, (T)record); } public static void Write(TextWriter writer, IEnumerable records) { if (writer == null) return; //AOT + if (records == null) return; if (typeof(T) == typeof(Dictionary) || typeof(T) == typeof(IDictionary)) { @@ -272,10 +283,56 @@ public static void Write(TextWriter writer, IEnumerable records) return; } - if (!CsvConfig.OmitHeaders && Headers.Count > 0) + var recordsList = records.ToList(); + + var headers = Headers; + var propGetters = PropertyGetters; + var treatAsSingleRow = typeof(T).IsValueType || typeof(T) == typeof(string); + + if (!treatAsSingleRow && JsConfig.ExcludeDefaultValues) + { + var hasValues = new bool[headers.Count]; + var defaultValues = new object[headers.Count]; + for (var i = 0; i < PropertyInfos.Count; i++) + { + defaultValues[i] = PropertyInfos[i].PropertyType.GetDefaultValue(); + } + + foreach (var record in recordsList) + { + for (var i = 0; i < propGetters.Count; i++) + { + var propGetter = propGetters[i]; + var value = propGetter(record); + + if (value != null && !value.Equals(defaultValues[i])) + hasValues[i] = true; + } + } + + if (hasValues.Any(x => x == false)) + { + var newHeaders = new List(); + var newGetters = new List>(); + + for (int i = 0; i < hasValues.Length; i++) + { + if (hasValues[i]) + { + newHeaders.Add(headers[i]); + newGetters.Add(propGetters[i]); + } + } + + headers = newHeaders; + propGetters = newGetters; + } + } + + if (!CsvConfig.OmitHeaders && headers.Count > 0) { var ranOnce = false; - foreach (var header in Headers) + foreach (var header in headers) { CsvWriter.WriteItemSeperatorIfRanOnce(writer, ref ranOnce); @@ -284,25 +341,23 @@ public static void Write(TextWriter writer, IEnumerable records) writer.Write(CsvConfig.RowSeparatorString); } - if (records == null) return; - - if (typeof(T).IsValueType || typeof(T) == typeof(string)) + if (treatAsSingleRow) { - var singleRow = GetSingleRow(records, typeof(T)); + var singleRow = GetSingleRow(recordsList, typeof(T)); WriteRow(writer, singleRow); return; } - var row = new string[Headers.Count]; - foreach (var record in records) + var row = new string[headers.Count]; + foreach (var record in recordsList) { - for (var i = 0; i < PropertyGetters.Count; i++) + for (var i = 0; i < propGetters.Count; i++) { - var propertyGetter = PropertyGetters[i]; - var value = propertyGetter(record) ?? ""; + var propGetter = propGetters[i]; + var value = propGetter(record) ?? ""; - var strValue = value is string - ? (string)value + var strValue = value is string s + ? s : TypeSerializer.SerializeToString(value).StripQuotes(); row[i] = strValue; @@ -331,6 +386,8 @@ public static void WriteRow(TextWriter writer, T row) public static void WriteRow(TextWriter writer, IEnumerable row) { + if (writer == null) return; //AOT + var ranOnce = false; foreach (var field in row) { @@ -343,6 +400,8 @@ public static void WriteRow(TextWriter writer, IEnumerable row) public static void Write(TextWriter writer, IEnumerable> rows) { + if (writer == null) return; //AOT + if (Headers.Count > 0) { var ranOnce = false; diff --git a/src/ServiceStack.Text/DateTimeExtensions.cs b/src/ServiceStack.Text/DateTimeExtensions.cs index f564d04fd..23910f6ac 100644 --- a/src/ServiceStack.Text/DateTimeExtensions.cs +++ b/src/ServiceStack.Text/DateTimeExtensions.cs @@ -17,7 +17,7 @@ namespace ServiceStack.Text { /// - /// A fast, standards-based, serialization-issue free DateTime serailizer. + /// A fast, standards-based, serialization-issue free DateTime serializer. /// public static class DateTimeExtensions { @@ -45,6 +45,9 @@ public static long ToUnixTimeMsAlt(this DateTime dateTime) { return (dateTime.ToStableUniversalTime().Ticks - UnixEpoch) / TimeSpan.TicksPerMillisecond; } + + public static long ToUnixTimeMs(this DateTimeOffset dateTimeOffset) => + (long)ToDateTimeSinceUnixEpoch(dateTimeOffset.UtcDateTime).TotalMilliseconds; public static long ToUnixTimeMs(this DateTime dateTime) { @@ -76,6 +79,11 @@ public static long ToUnixTimeMs(this long ticks) return (ticks - UnixEpoch) / TimeSpan.TicksPerMillisecond; } +#if NET6_0 + public static long ToUnixTimeMs(this DateOnly dateOnly) => dateOnly.ToDateTime(default, DateTimeKind.Utc).ToUnixTimeMs(); + public static long ToUnixTime(this DateOnly dateOnly) => dateOnly.ToDateTime(default, DateTimeKind.Utc).ToUnixTime(); +#endif + public static DateTime FromUnixTimeMs(this double msSince1970) { return UnixEpochDateTimeUtc + TimeSpan.FromMilliseconds(msSince1970); diff --git a/src/ServiceStack.Text/DefaultMemory.cs b/src/ServiceStack.Text/DefaultMemory.cs new file mode 100644 index 000000000..cc05301b1 --- /dev/null +++ b/src/ServiceStack.Text/DefaultMemory.cs @@ -0,0 +1,1058 @@ +using System; +using System.Globalization; +using System.IO; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using ServiceStack.Text.Common; +using ServiceStack.Text.Json; +using ServiceStack.Text.Pools; + +namespace ServiceStack.Text +{ + public sealed class DefaultMemory : MemoryProvider + { + private static DefaultMemory provider; + public static DefaultMemory Provider => provider ?? (provider = new DefaultMemory()); + private DefaultMemory() { } + + public static void Configure() => Instance = Provider; + + public override bool ParseBoolean(ReadOnlySpan value) + { + if (!value.TryParseBoolean(out bool result)) + throw new FormatException(BadFormat); + + return result; + } + + public override bool TryParseBoolean(ReadOnlySpan value, out bool result) + { + result = false; + + if (value.CompareIgnoreCase(bool.TrueString.AsSpan())) + { + result = true; + return true; + } + + return value.CompareIgnoreCase(bool.FalseString.AsSpan()); + } + + public override bool TryParseDecimal(ReadOnlySpan value, out decimal result) => + TryParseDecimal(value, allowThousands: true, out result); + + public override decimal ParseDecimal(ReadOnlySpan value) => ParseDecimal(value, allowThousands: true); + + public override decimal ParseDecimal(ReadOnlySpan value, bool allowThousands) + { + if (!TryParseDecimal(value, allowThousands, out var result)) + throw new FormatException(BadFormat); + + return result; + } + + public override bool TryParseFloat(ReadOnlySpan value, out float result) => float.TryParse( + value.ToString(), NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, + out result); + + public override float ParseFloat(ReadOnlySpan value) => float.Parse(value.ToString(), + NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture); + + public override bool TryParseDouble(ReadOnlySpan value, out double result) => double.TryParse( + value.ToString(), NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, + out result); + + public override double ParseDouble(ReadOnlySpan value) => double.Parse(value.ToString(), + NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture); + + public override sbyte ParseSByte(ReadOnlySpan value) => SignedInteger.ParseSByte(value); + + public override byte ParseByte(ReadOnlySpan value) => UnsignedInteger.ParseByte(value); + + public override short ParseInt16(ReadOnlySpan value) => SignedInteger.ParseInt16(value); + + public override ushort ParseUInt16(ReadOnlySpan value) => UnsignedInteger.ParseUInt16(value); + + public override int ParseInt32(ReadOnlySpan value) => SignedInteger.ParseInt32(value); + + public override uint ParseUInt32(ReadOnlySpan value) => UnsignedInteger.ParseUInt32(value); + + public override uint ParseUInt32(ReadOnlySpan value, NumberStyles style) => + uint.Parse(value.ToString(), style); + + public override long ParseInt64(ReadOnlySpan value) => SignedInteger.ParseInt64(value); + + public override ulong ParseUInt64(ReadOnlySpan value) => UnsignedInteger.ParseUInt64(value); + + internal static Exception CreateOverflowException(long maxValue) => + new OverflowException(string.Format(OverflowMessage, SignedMaxValueToIntType(maxValue))); + + internal static Exception CreateOverflowException(ulong maxValue) => + new OverflowException(string.Format(OverflowMessage, UnsignedMaxValueToIntType(maxValue))); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static string SignedMaxValueToIntType(long maxValue) + { + switch (maxValue) + { + case sbyte.MaxValue: + return nameof(SByte); + case short.MaxValue: + return nameof(Int16); + case int.MaxValue: + return nameof(Int32); + case long.MaxValue: + return nameof(Int64); + default: + return "Unknown"; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static string UnsignedMaxValueToIntType(ulong maxValue) + { + switch (maxValue) + { + case byte.MaxValue: + return nameof(Byte); + case ushort.MaxValue: + return nameof(UInt16); + case uint.MaxValue: + return nameof(UInt32); + case ulong.MaxValue: + return nameof(UInt64); + default: + return "Unknown"; + } + } + + public static bool TryParseDecimal(ReadOnlySpan value, bool allowThousands, out decimal result) + { + result = 0; + + if (value.Length == 0) + return false; + + ulong preResult = 0; + bool isLargeNumber = false; + int i = 0; + int end = value.Length; + var state = ParseState.LeadingWhite; + bool negative = false; + bool noIntegerPart = false; + sbyte scale = 0; + + while (i < end) + { + var c = value[i++]; + + switch (state) + { + case ParseState.LeadingWhite: + if (JsonUtils.IsWhiteSpace(c)) + break; + + if (c == '-') + { + negative = true; + state = ParseState.Sign; + } + else if (c == '.') + { + noIntegerPart = true; + state = ParseState.FractionNumber; + + if (i == end) + return false; + } + else if (c == '0') + { + state = ParseState.DecimalPoint; + } + else if (c > '0' && c <= '9') + { + preResult = (ulong) (c - '0'); + state = ParseState.Number; + } + else return false; + + break; + case ParseState.Sign: + if (c == '.') + { + noIntegerPart = true; + state = ParseState.FractionNumber; + + if (i == end) + return false; + } + else if (c == '0') + { + state = ParseState.DecimalPoint; + } + else if (c > '0' && c <= '9') + { + preResult = (ulong) (c - '0'); + state = ParseState.Number; + } + else return false; + + break; + case ParseState.Number: + if (c == '.') + { + state = ParseState.FractionNumber; + } + else if (c >= '0' && c <= '9') + { + if (isLargeNumber) + { + checked + { + result = 10 * result + (c - '0'); + } + } + else + { + preResult = 10 * preResult + (ulong) (c - '0'); + if (preResult > ulong.MaxValue / 10 - 10) + { + isLargeNumber = true; + result = preResult; + } + } + } + else if (JsonUtils.IsWhiteSpace(c)) + { + state = ParseState.TrailingWhite; + } + else if (allowThousands && c == ',') { } + else return false; + + break; + case ParseState.DecimalPoint: + if (c == '.') + { + state = ParseState.FractionNumber; + } + else return false; + + break; + case ParseState.FractionNumber: + if (JsonUtils.IsWhiteSpace(c)) + { + if (noIntegerPart) + return false; + state = ParseState.TrailingWhite; + } + else if (c == 'e' || c == 'E') + { + if (noIntegerPart && scale == 0) + return false; + state = ParseState.Exponent; + } + else if (c >= '0' && c <= '9') + { + if (isLargeNumber) + { + checked + { + result = 10 * result + (c - '0'); + } + } + else + { + preResult = 10 * preResult + (ulong) (c - '0'); + if (preResult > ulong.MaxValue / 10 - 10) + { + isLargeNumber = true; + result = preResult; + } + } + + scale++; + } + else return false; + + break; + case ParseState.Exponent: + bool expNegative = false; + if (c == '-') + { + if (i == end) + return false; + + expNegative = true; + c = value[i++]; + } + else if (c == '+') + { + if (i == end) + return false; + c = value[i++]; + } + + //skip leading zeroes + while (c == '0' && i < end) c = value[i++]; + + if (c > '0' && c <= '9') + { + var exp = SignedInteger.ParseInt64(value.Slice(i - 1, end - i + 1)); + if (exp < sbyte.MinValue || exp > sbyte.MaxValue) + return false; + + if (!expNegative) + { + exp = (sbyte) -exp; + } + + if (exp >= 0 || scale > -exp) + { + scale += (sbyte) exp; + } + else + { + for (int j = 0; j < -exp - scale; j++) + { + if (isLargeNumber) + { + checked + { + result = 10 * result; + } + } + else + { + preResult = 10 * preResult; + if (preResult > ulong.MaxValue / 10) + { + isLargeNumber = true; + result = preResult; + } + } + } + + scale = 0; + } + + //set i to end of string, because ParseInt16 eats number and all trailing whites + i = end; + } + else return false; + + break; + case ParseState.TrailingWhite: + if (!JsonUtils.IsWhiteSpace(c)) + return false; + break; + } + } + + if (!isLargeNumber) + { + var mid = (int) (preResult >> 32); + var lo = (int) (preResult & 0xffffffff); + result = new decimal(lo, mid, 0, negative, (byte) scale); + } + else + { + var bits = decimal.GetBits(result); + result = new decimal(bits[0], bits[1], bits[2], negative, (byte) scale); + } + + return true; + } + + private static readonly byte[] lo16 = { + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 0, 1, + 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, + 255, 255, 255, 255, 255, 10, 11, 12, 13, 14, + 15, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 10, 11, 12, + 13, 14, 15 + }; + + private static readonly byte[] hi16 = { + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 0, 16, + 32, 48, 64, 80, 96, 112, 128, 144, 255, 255, + 255, 255, 255, 255, 255, 160, 176, 192, 208, 224, + 240, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 160, 176, 192, + 208, 224, 240 + }; + + public override Guid ParseGuid(ReadOnlySpan value) + { + if (value.IsEmpty) + throw new FormatException(BadFormat); + + //Guid can be in one of 3 forms: + //1. General `{dddddddd-dddd-dddd-dddd-dddddddddddd}` or `(dddddddd-dddd-dddd-dddd-dddddddddddd)` 8-4-4-4-12 chars + //2. Hex `{0xdddddddd,0xdddd,0xdddd,{0xdd,0xdd,0xdd,0xdd,0xdd,0xdd,0xdd,0xdd}}` 8-4-4-8x2 chars + //3. No style `dddddddddddddddddddddddddddddddd` 32 chars + + int i = 0; + int end = value.Length; + while (i < end && JsonUtils.IsWhiteSpace(value[i])) i++; + + if (i == end) + throw new FormatException(BadFormat); + + var result = ParseGeneralStyleGuid(value.Slice(i, end - i), out var guidLen); + i += guidLen; + + while (i < end && JsonUtils.IsWhiteSpace(value[i])) i++; + + if (i < end) + throw new FormatException(BadFormat); + + return result; + } + + public override void Write(Stream stream, ReadOnlyMemory value) + { + var bytes = BufferPool.GetBuffer(Encoding.UTF8.GetMaxByteCount(value.Length)); + try + { + var chars = value.ToArray(); + int bytesCount = Encoding.UTF8.GetBytes(chars, 0, chars.Length, bytes, 0); + stream.Write(bytes, 0, bytesCount); + } + finally + { + BufferPool.ReleaseBufferToPool(ref bytes); + } + } + + public override void Write(Stream stream, ReadOnlyMemory value) + { + if (MemoryMarshal.TryGetArray(value, out var segment) && segment.Array != null) + { + byte[] bytes = BufferPool.GetBuffer(segment.Count); + try + { + stream.Write(segment.Array, 0, segment.Count); + } + finally + { + BufferPool.ReleaseBufferToPool(ref bytes); + } + } + else + { + var bytes = value.ToArray(); + stream.Write(bytes, 0, value.Length); + } + } + + public override Task WriteAsync(Stream stream, ReadOnlySpan value, CancellationToken token = default) + { + // encode the span into a buffer; this should never fail, so if it does: something + // is very very ill; don't stress about returning to the pool + byte[] bytes = BufferPool.GetBuffer(Encoding.UTF8.GetMaxByteCount(value.Length)); + var chars = value.ToArray(); + int bytesCount = Encoding.UTF8.GetBytes(chars, 0, chars.Length, bytes, 0); + // now do the write async - this returns to the pool + return WriteAsyncAndReturn(stream, bytes, 0, bytesCount, token); + } + + private static async Task WriteAsyncAndReturn(Stream stream, byte[] bytes, int offset, int count, CancellationToken token) + { + try + { + await stream.WriteAsync(bytes, offset, count, token).ConfigAwait(); + } + finally + { + BufferPool.ReleaseBufferToPool(ref bytes); + } + } + + public override Task WriteAsync(Stream stream, ReadOnlyMemory value, CancellationToken token = default) => + WriteAsync(stream, value.Span, token); + + public override async Task WriteAsync(Stream stream, ReadOnlyMemory value, CancellationToken token = default) + { + byte[] bytes = BufferPool.GetBuffer(value.Length); + try + { + value.CopyTo(bytes); + if (stream is MemoryStream ms) + { + // ReSharper disable once MethodHasAsyncOverloadWithCancellation + ms.Write(bytes, 0, value.Length); + } + else + { + await stream.WriteAsync(bytes, 0, value.Length, token).ConfigAwait(); + } + } + finally + { + BufferPool.ReleaseBufferToPool(ref bytes); + } + } + + public override object Deserialize(Stream stream, Type type, DeserializeStringSpanDelegate deserializer) + { + var fromPool = false; + + if (!(stream is MemoryStream ms)) + { + fromPool = true; + + if (stream.CanSeek) + stream.Position = 0; + + ms = stream.CopyToNewMemoryStream(); + } + + return Deserialize(ms, fromPool, type, deserializer); + } + + public override async Task DeserializeAsync(Stream stream, Type type, + DeserializeStringSpanDelegate deserializer) + { + var fromPool = false; + + if (!(stream is MemoryStream ms)) + { + fromPool = true; + + if (stream.CanSeek) + stream.Position = 0; + + ms = await stream.CopyToNewMemoryStreamAsync().ConfigAwait(); + } + + return Deserialize(ms, fromPool, type, deserializer); + } + + private static object Deserialize(MemoryStream ms, bool fromPool, Type type, + DeserializeStringSpanDelegate deserializer) + { + var bytes = ms.GetBufferAsBytes(); + var utf8 = CharPool.GetBuffer(Encoding.UTF8.GetCharCount(bytes, 0, (int) ms.Length)); + try + { + var charsWritten = Encoding.UTF8.GetChars(bytes, 0, (int) ms.Length, utf8, 0); + var ret = deserializer(type, new ReadOnlySpan(utf8, 0, charsWritten).WithoutBom()); + return ret; + } + finally + { + CharPool.ReleaseBufferToPool(ref utf8); + + if (fromPool) + ms.Dispose(); + } + } + + public override byte[] ParseBase64(ReadOnlySpan value) + { + return Convert.FromBase64String(value.ToString()); + } + + public override string ToBase64(ReadOnlyMemory value) + { + return MemoryMarshal.TryGetArray(value, out var segment) + ? Convert.ToBase64String(segment.Array, 0, segment.Count) + : Convert.ToBase64String(value.ToArray()); + } + + public override StringBuilder Append(StringBuilder sb, ReadOnlySpan value) + { + return sb.Append(value.ToArray()); + } + + public override int GetUtf8CharCount(ReadOnlySpan bytes) => + Encoding.UTF8.GetCharCount(bytes.ToArray()); //SLOW + + public override int GetUtf8ByteCount(ReadOnlySpan chars) => + Encoding.UTF8.GetByteCount(chars.ToArray()); //SLOW + + public override ReadOnlyMemory ToUtf8(ReadOnlySpan source) + { + var chars = source.ToArray(); + var bytes = new byte[Encoding.UTF8.GetByteCount(chars)]; + var bytesWritten = Encoding.UTF8.GetBytes(chars, 0, source.Length, bytes, 0); + return new ReadOnlyMemory(bytes, 0, bytesWritten); + } + + public override ReadOnlyMemory FromUtf8(ReadOnlySpan source) + { + var bytes = source.WithoutBom().ToArray(); + var chars = new char[Encoding.UTF8.GetCharCount(bytes)]; + var charsWritten = Encoding.UTF8.GetChars(bytes, 0, bytes.Length, chars, 0); + return new ReadOnlyMemory(chars, 0, charsWritten); + } + + public override int ToUtf8(ReadOnlySpan source, Span destination) + { + var chars = source.ToArray(); + var bytes = destination.ToArray(); + var bytesWritten = Encoding.UTF8.GetBytes(chars, 0, source.Length, bytes, 0); + new ReadOnlySpan(bytes, 0, bytesWritten).CopyTo(destination); + return bytesWritten; + } + + public override int FromUtf8(ReadOnlySpan source, Span destination) + { + var bytes = source.WithoutBom().ToArray(); + var chars = destination.ToArray(); + var charsWritten = Encoding.UTF8.GetChars(bytes, 0, bytes.Length, chars, 0); + new ReadOnlySpan(chars, 0, charsWritten).CopyTo(destination); + return charsWritten; + } + + public override byte[] ToUtf8Bytes(ReadOnlySpan source) => Encoding.UTF8.GetBytes(source.ToArray()); + + public override string FromUtf8Bytes(ReadOnlySpan source) => Encoding.UTF8.GetString(source.WithoutBom().ToArray()); + + public override MemoryStream ToMemoryStream(ReadOnlySpan source) => + MemoryStreamFactory.GetStream(source.ToArray()); + + private static Guid ParseGeneralStyleGuid(ReadOnlySpan value, out int len) + { + var buf = value; + var n = 0; + + int dash = 0; + len = 32; + bool hasParenthesis = false; + + if (value.Length < len) + throw new FormatException(BadFormat); + + var cs = value[0]; + if (cs == '{' || cs == '(') + { + n++; + len += 2; + hasParenthesis = true; + + if (buf[8 + n] != '-') + throw new FormatException(BadFormat); + } + + if (buf[8 + n] == '-') + { + if (buf[13 + n] != '-' + || buf[18 + n] != '-' + || buf[23 + n] != '-') + throw new FormatException(BadFormat); + + len += 4; + dash = 1; + } + + if (value.Length < len) + throw new FormatException(BadFormat); + + if (hasParenthesis) + { + var ce = buf[len - 1]; + + if ((cs != '{' || ce != '}') && (cs != '(' || ce != ')')) + throw new FormatException(BadFormat); + } + + int a; + short b, c; + byte d, e, f, g, h, i, j, k; + + byte a1 = ParseHexByte(buf[n], buf[n + 1]); + n += 2; + byte a2 = ParseHexByte(buf[n], buf[n + 1]); + n += 2; + byte a3 = ParseHexByte(buf[n], buf[n + 1]); + n += 2; + byte a4 = ParseHexByte(buf[n], buf[n + 1]); + a = (a1 << 24) + (a2 << 16) + (a3 << 8) + a4; + n += 2 + dash; + + byte b1 = ParseHexByte(buf[n], buf[n + 1]); + n += 2; + byte b2 = ParseHexByte(buf[n], buf[n + 1]); + b = (short) ((b1 << 8) + b2); + n += 2 + dash; + + byte c1 = ParseHexByte(buf[n], buf[n + 1]); + n += 2; + byte c2 = ParseHexByte(buf[n], buf[n + 1]); + c = (short) ((c1 << 8) + c2); + n += 2 + dash; + + d = ParseHexByte(buf[n], buf[n + 1]); + n += 2; + e = ParseHexByte(buf[n], buf[n + 1]); + n += 2 + dash; + + f = ParseHexByte(buf[n], buf[n + 1]); + n += 2; + g = ParseHexByte(buf[n], buf[n + 1]); + n += 2; + h = ParseHexByte(buf[n], buf[n + 1]); + n += 2; + i = ParseHexByte(buf[n], buf[n + 1]); + n += 2; + j = ParseHexByte(buf[n], buf[n + 1]); + n += 2; + k = ParseHexByte(buf[n], buf[n + 1]); + + return new Guid(a, b, c, d, e, f, g, h, i, j, k); + } + + private static byte ParseHexByte(char c1, char c2) + { + try + { + byte lo = lo16[c2]; + byte hi = hi16[c1]; + + if (lo == 255 || hi == 255) + throw new FormatException(BadFormat); + + return (byte) (hi + lo); + } + catch (IndexOutOfRangeException) + { + throw new FormatException(BadFormat); + } + } + } + + enum ParseState + { + LeadingWhite, + Sign, + Number, + DecimalPoint, + FractionNumber, + Exponent, + ExponentSign, + ExponentValue, + TrailingWhite + } + + internal static class SignedInteger where T : struct, IComparable, IEquatable, IConvertible + { + private static readonly TypeCode typeCode; + private static readonly long minValue; + private static readonly long maxValue; + + static SignedInteger() + { + typeCode = Type.GetTypeCode(typeof(T)); + + switch (typeCode) + { + case TypeCode.SByte: + minValue = sbyte.MinValue; + maxValue = sbyte.MaxValue; + break; + case TypeCode.Int16: + minValue = short.MinValue; + maxValue = short.MaxValue; + break; + case TypeCode.Int32: + minValue = int.MinValue; + maxValue = int.MaxValue; + break; + case TypeCode.Int64: + minValue = long.MinValue; + maxValue = long.MaxValue; + break; + default: + throw new NotSupportedException($"{typeof(T).Name} is not a signed integer"); + } + } + + internal static object ParseNullableObject(ReadOnlySpan value) + { + if (value.IsNullOrEmpty()) + return null; + + return ParseObject(value); + } + + internal static object ParseObject(ReadOnlySpan value) + { + var result = ParseInt64(value); + switch (typeCode) + { + case TypeCode.SByte: + return (sbyte) result; + case TypeCode.Int16: + return (short) result; + case TypeCode.Int32: + return (int) result; + default: + return result; + } + } + + public static sbyte ParseSByte(ReadOnlySpan value) => (sbyte) ParseInt64(value); + public static short ParseInt16(ReadOnlySpan value) => (short) ParseInt64(value); + public static int ParseInt32(ReadOnlySpan value) => (int) ParseInt64(value); + + public static long ParseInt64(ReadOnlySpan value) + { + if (value.IsEmpty) + throw new FormatException(MemoryProvider.BadFormat); + + long result = 0; + int i = 0; + int end = value.Length; + var state = ParseState.LeadingWhite; + bool negative = false; + + //skip leading whitespaces + while (i < end && JsonUtils.IsWhiteSpace(value[i])) i++; + + if (i == end) + throw new FormatException(MemoryProvider.BadFormat); + + //skip leading zeros + while (i < end && value[i] == '0') + { + state = ParseState.Number; + i++; + } + + while (i < end) + { + var c = value[i++]; + + switch (state) + { + case ParseState.LeadingWhite: + if (c == '-') + { + negative = true; + state = ParseState.Sign; + } + else if (c == '0') + { + state = ParseState.TrailingWhite; + } + else if (c > '0' && c <= '9') + { + result = -(c - '0'); + state = ParseState.Number; + } + else throw new FormatException(MemoryProvider.BadFormat); + + break; + case ParseState.Sign: + if (c == '0') + { + state = ParseState.TrailingWhite; + } + else if (c > '0' && c <= '9') + { + result = -(c - '0'); + state = ParseState.Number; + } + else throw new FormatException(MemoryProvider.BadFormat); + + break; + case ParseState.Number: + if (c >= '0' && c <= '9') + { + checked + { + result = 10 * result - (c - '0'); + } + + if (result < minValue + ) //check only minvalue, because in absolute value it's greater than maxvalue + throw DefaultMemory.CreateOverflowException(maxValue); + } + else if (JsonUtils.IsWhiteSpace(c)) + { + state = ParseState.TrailingWhite; + } + else throw new FormatException(MemoryProvider.BadFormat); + + break; + case ParseState.TrailingWhite: + if (JsonUtils.IsWhiteSpace(c)) + { + state = ParseState.TrailingWhite; + } + else throw new FormatException(MemoryProvider.BadFormat); + + break; + } + } + + if (state != ParseState.Number && state != ParseState.TrailingWhite) + throw new FormatException(MemoryProvider.BadFormat); + + if (negative) + return result; + + checked + { + result = -result; + } + + if (result > maxValue) + throw DefaultMemory.CreateOverflowException(maxValue); + + return result; + } + } + + internal static class UnsignedInteger where T : struct, IComparable, IEquatable, IConvertible + { + private static readonly TypeCode typeCode; + private static readonly ulong maxValue; + + static UnsignedInteger() + { + typeCode = Type.GetTypeCode(typeof(T)); + + switch (typeCode) + { + case TypeCode.Byte: + maxValue = byte.MaxValue; + break; + case TypeCode.UInt16: + maxValue = ushort.MaxValue; + break; + case TypeCode.UInt32: + maxValue = uint.MaxValue; + break; + case TypeCode.UInt64: + maxValue = ulong.MaxValue; + break; + default: + throw new NotSupportedException($"{typeof(T).Name} is not a signed integer"); + } + } + + internal static object ParseNullableObject(ReadOnlySpan value) + { + if (value.IsNullOrEmpty()) + return null; + + return ParseObject(value); + } + + internal static object ParseObject(ReadOnlySpan value) + { + var result = ParseUInt64(value); + switch (typeCode) + { + case TypeCode.Byte: + return (byte) result; + case TypeCode.UInt16: + return (ushort) result; + case TypeCode.UInt32: + return (uint) result; + default: + return result; + } + } + + public static byte ParseByte(ReadOnlySpan value) => (byte) ParseUInt64(value); + public static ushort ParseUInt16(ReadOnlySpan value) => (ushort) ParseUInt64(value); + public static uint ParseUInt32(ReadOnlySpan value) => (uint) ParseUInt64(value); + + internal static ulong ParseUInt64(ReadOnlySpan value) + { + if (value.IsEmpty) + throw new FormatException(MemoryProvider.BadFormat); + + ulong result = 0; + int i = 0; + int end = value.Length; + var state = ParseState.LeadingWhite; + + //skip leading whitespaces + while (i < end && JsonUtils.IsWhiteSpace(value[i])) i++; + + if (i == end) + throw new FormatException(MemoryProvider.BadFormat); + + //skip leading zeros + while (i < end && value[i] == '0') + { + state = ParseState.Number; + i++; + } + + while (i < end) + { + var c = value[i++]; + + switch (state) + { + case ParseState.LeadingWhite: + if (JsonUtils.IsWhiteSpace(c)) + break; + if (c == '0') + { + state = ParseState.TrailingWhite; + } + else if (c > '0' && c <= '9') + { + result = (ulong) (c - '0'); + state = ParseState.Number; + } + else throw new FormatException(MemoryProvider.BadFormat); + + + break; + case ParseState.Number: + if (c >= '0' && c <= '9') + { + checked + { + result = 10 * result + (ulong) (c - '0'); + } + + if (result > maxValue + ) //check only minvalue, because in absolute value it's greater than maxvalue + throw DefaultMemory.CreateOverflowException(maxValue); + } + else if (JsonUtils.IsWhiteSpace(c)) + { + state = ParseState.TrailingWhite; + } + else throw new FormatException(MemoryProvider.BadFormat); + + break; + case ParseState.TrailingWhite: + if (JsonUtils.IsWhiteSpace(c)) + { + state = ParseState.TrailingWhite; + } + else throw new FormatException(MemoryProvider.BadFormat); + + break; + } + } + + if (state != ParseState.Number && state != ParseState.TrailingWhite) + throw new FormatException(MemoryProvider.BadFormat); + + return result; + } + } +} \ No newline at end of file diff --git a/src/ServiceStack.Text/Defer.cs b/src/ServiceStack.Text/Defer.cs new file mode 100644 index 000000000..2e29db4e4 --- /dev/null +++ b/src/ServiceStack.Text/Defer.cs @@ -0,0 +1,15 @@ +using System; + +namespace ServiceStack +{ + /// + /// Useful class for C# 8 using declaration to defer action til outside of scope, e.g: + /// using var defer = new Defer(() => response.Close()); + /// + public struct Defer : IDisposable + { + private readonly Action fn; + public Defer(Action fn) => this.fn = fn ?? throw new ArgumentNullException(nameof(fn)); + public void Dispose() => fn(); + } +} \ No newline at end of file diff --git a/src/ServiceStack.Text/DynamicNumber.cs b/src/ServiceStack.Text/DynamicNumber.cs index f751a9fc3..33a637061 100644 --- a/src/ServiceStack.Text/DynamicNumber.cs +++ b/src/ServiceStack.Text/DynamicNumber.cs @@ -4,9 +4,6 @@ using System.Runtime.CompilerServices; using ServiceStack.Text; using ServiceStack.Text.Common; -#if NETSTANDARD2_0 -using Microsoft.Extensions.Primitives; -#endif namespace ServiceStack { @@ -16,11 +13,25 @@ public interface IDynamicNumber object ConvertFrom(object value); bool TryParse(string str, out object result); string ToString(object value); + object DefaultValue { get; } object add(object lhs, object rhs); object sub(object lhs, object rhs); object mul(object lhs, object rhs); object div(object lhs, object rhs); + object mod(object lhs, object rhs); + object pow(object lhs, object rhs); + object log(object lhs, object rhs); + object min(object lhs, object rhs); + object max(object lhs, object rhs); + int compareTo(object lhs, object rhs); + + object bitwiseAnd(object lhs, object rhs); + object bitwiseOr(object lhs, object rhs); + object bitwiseXOr(object lhs, object rhs); + object bitwiseLeftShift(object lhs, object rhs); + object bitwiseRightShift(object lhs, object rhs); + object bitwiseNot(object target); } public class DynamicSByte : IDynamicNumber @@ -46,11 +57,25 @@ public bool TryParse(string str, out object result) } public string ToString(object value) => Convert(value).ToString(); + public object DefaultValue => default(sbyte); public object add(object lhs, object rhs) => Convert(lhs) + Convert(rhs); public object sub(object lhs, object rhs) => Convert(lhs) - Convert(rhs); public object mul(object lhs, object rhs) => Convert(lhs) * Convert(rhs); public object div(object lhs, object rhs) => Convert(lhs) / Convert(rhs); + public object mod(object lhs, object rhs) => Convert(lhs) % Convert(rhs); + public object min(object lhs, object rhs) => Math.Min(Convert(lhs), Convert(rhs)); + public object max(object lhs, object rhs) => Math.Max(Convert(lhs), Convert(rhs)); + public object pow(object lhs, object rhs) => Math.Pow(Convert(lhs), Convert(rhs)); + public object log(object lhs, object rhs) => Math.Log(Convert(lhs), Convert(rhs)); + public int compareTo(object lhs, object rhs) => Convert(lhs).CompareTo(Convert(rhs)); + + public object bitwiseAnd(object lhs, object rhs) => Convert(lhs) & Convert(rhs); + public object bitwiseOr(object lhs, object rhs) => Convert(lhs) | Convert(rhs); + public object bitwiseXOr(object lhs, object rhs) => Convert(lhs) ^ Convert(rhs); + public object bitwiseLeftShift(object lhs, object rhs) => Convert(lhs) << Convert(rhs); + public object bitwiseRightShift(object lhs, object rhs) => Convert(lhs) >> Convert(rhs); + public object bitwiseNot(object target) => ~Convert(target); } public class DynamicByte : IDynamicNumber @@ -76,11 +101,25 @@ public bool TryParse(string str, out object result) } public string ToString(object value) => Convert(value).ToString(); + public object DefaultValue => default(byte); public object add(object lhs, object rhs) => Convert(lhs) + Convert(rhs); public object sub(object lhs, object rhs) => Convert(lhs) - Convert(rhs); public object mul(object lhs, object rhs) => Convert(lhs) * Convert(rhs); public object div(object lhs, object rhs) => Convert(lhs) / Convert(rhs); + public object mod(object lhs, object rhs) => Convert(lhs) % Convert(rhs); + public object min(object lhs, object rhs) => Math.Min(Convert(lhs), Convert(rhs)); + public object max(object lhs, object rhs) => Math.Max(Convert(lhs), Convert(rhs)); + public object pow(object lhs, object rhs) => Math.Pow(Convert(lhs), Convert(rhs)); + public object log(object lhs, object rhs) => Math.Log(Convert(lhs), Convert(rhs)); + public int compareTo(object lhs, object rhs) => Convert(lhs).CompareTo(Convert(rhs)); + + public object bitwiseAnd(object lhs, object rhs) => Convert(lhs) & Convert(rhs); + public object bitwiseOr(object lhs, object rhs) => Convert(lhs) | Convert(rhs); + public object bitwiseXOr(object lhs, object rhs) => Convert(lhs) ^ Convert(rhs); + public object bitwiseLeftShift(object lhs, object rhs) => Convert(lhs) << Convert(rhs); + public object bitwiseRightShift(object lhs, object rhs) => Convert(lhs) >> Convert(rhs); + public object bitwiseNot(object target) => ~Convert(target); } public class DynamicShort : IDynamicNumber @@ -106,11 +145,25 @@ public bool TryParse(string str, out object result) } public string ToString(object value) => Convert(value).ToString(); + public object DefaultValue => default(short); public object add(object lhs, object rhs) => Convert(lhs) + Convert(rhs); public object sub(object lhs, object rhs) => Convert(lhs) - Convert(rhs); public object mul(object lhs, object rhs) => Convert(lhs) * Convert(rhs); public object div(object lhs, object rhs) => Convert(lhs) / Convert(rhs); + public object mod(object lhs, object rhs) => Convert(lhs) % Convert(rhs); + public object min(object lhs, object rhs) => Math.Min(Convert(lhs), Convert(rhs)); + public object max(object lhs, object rhs) => Math.Max(Convert(lhs), Convert(rhs)); + public object pow(object lhs, object rhs) => Math.Pow(Convert(lhs), Convert(rhs)); + public object log(object lhs, object rhs) => Math.Log(Convert(lhs), Convert(rhs)); + public int compareTo(object lhs, object rhs) => Convert(lhs).CompareTo(Convert(rhs)); + + public object bitwiseAnd(object lhs, object rhs) => Convert(lhs) & Convert(rhs); + public object bitwiseOr(object lhs, object rhs) => Convert(lhs) | Convert(rhs); + public object bitwiseXOr(object lhs, object rhs) => Convert(lhs) ^ Convert(rhs); + public object bitwiseLeftShift(object lhs, object rhs) => Convert(lhs) << Convert(rhs); + public object bitwiseRightShift(object lhs, object rhs) => Convert(lhs) >> Convert(rhs); + public object bitwiseNot(object target) => ~Convert(target); } public class DynamicUShort : IDynamicNumber @@ -136,11 +189,25 @@ public bool TryParse(string str, out object result) } public string ToString(object value) => Convert(value).ToString(); + public object DefaultValue => default(ushort); public object add(object lhs, object rhs) => Convert(lhs) + Convert(rhs); public object sub(object lhs, object rhs) => Convert(lhs) - Convert(rhs); public object mul(object lhs, object rhs) => Convert(lhs) * Convert(rhs); public object div(object lhs, object rhs) => Convert(lhs) / Convert(rhs); + public object mod(object lhs, object rhs) => Convert(lhs) % Convert(rhs); + public object min(object lhs, object rhs) => Math.Min(Convert(lhs), Convert(rhs)); + public object max(object lhs, object rhs) => Math.Max(Convert(lhs), Convert(rhs)); + public object pow(object lhs, object rhs) => Math.Pow(Convert(lhs), Convert(rhs)); + public object log(object lhs, object rhs) => Math.Log(Convert(lhs), Convert(rhs)); + public int compareTo(object lhs, object rhs) => Convert(lhs).CompareTo(Convert(rhs)); + + public object bitwiseAnd(object lhs, object rhs) => Convert(lhs) & Convert(rhs); + public object bitwiseOr(object lhs, object rhs) => Convert(lhs) | Convert(rhs); + public object bitwiseXOr(object lhs, object rhs) => Convert(lhs) ^ Convert(rhs); + public object bitwiseLeftShift(object lhs, object rhs) => Convert(lhs) << Convert(rhs); + public object bitwiseRightShift(object lhs, object rhs) => Convert(lhs) >> Convert(rhs); + public object bitwiseNot(object target) => ~Convert(target); } public class DynamicInt : IDynamicNumber @@ -166,11 +233,25 @@ public bool TryParse(string str, out object result) } public string ToString(object value) => Convert(value).ToString(); + public object DefaultValue => default(int); public object add(object lhs, object rhs) => Convert(lhs) + Convert(rhs); public object sub(object lhs, object rhs) => Convert(lhs) - Convert(rhs); public object mul(object lhs, object rhs) => Convert(lhs) * Convert(rhs); public object div(object lhs, object rhs) => Convert(lhs) / Convert(rhs); + public object mod(object lhs, object rhs) => Convert(lhs) % Convert(rhs); + public object min(object lhs, object rhs) => Math.Min(Convert(lhs), Convert(rhs)); + public object max(object lhs, object rhs) => Math.Max(Convert(lhs), Convert(rhs)); + public object pow(object lhs, object rhs) => Math.Pow(Convert(lhs), Convert(rhs)); + public object log(object lhs, object rhs) => Math.Log(Convert(lhs), Convert(rhs)); + public int compareTo(object lhs, object rhs) => Convert(lhs).CompareTo(Convert(rhs)); + + public object bitwiseAnd(object lhs, object rhs) => Convert(lhs) & Convert(rhs); + public object bitwiseOr(object lhs, object rhs) => Convert(lhs) | Convert(rhs); + public object bitwiseXOr(object lhs, object rhs) => Convert(lhs) ^ Convert(rhs); + public object bitwiseLeftShift(object lhs, object rhs) => Convert(lhs) << Convert(rhs); + public object bitwiseRightShift(object lhs, object rhs) => Convert(lhs) >> Convert(rhs); + public object bitwiseNot(object target) => ~Convert(target); } public class DynamicUInt : IDynamicNumber @@ -196,11 +277,25 @@ public bool TryParse(string str, out object result) } public string ToString(object value) => Convert(value).ToString(); + public object DefaultValue => default(uint); public object add(object lhs, object rhs) => Convert(lhs) + Convert(rhs); public object sub(object lhs, object rhs) => Convert(lhs) - Convert(rhs); public object mul(object lhs, object rhs) => Convert(lhs) * Convert(rhs); public object div(object lhs, object rhs) => Convert(lhs) / Convert(rhs); + public object mod(object lhs, object rhs) => Convert(lhs) % Convert(rhs); + public object min(object lhs, object rhs) => Math.Min(Convert(lhs), Convert(rhs)); + public object max(object lhs, object rhs) => Math.Max(Convert(lhs), Convert(rhs)); + public object pow(object lhs, object rhs) => Math.Pow(Convert(lhs), Convert(rhs)); + public object log(object lhs, object rhs) => Math.Log(Convert(lhs), Convert(rhs)); + public int compareTo(object lhs, object rhs) => Convert(lhs).CompareTo(Convert(rhs)); + + public object bitwiseAnd(object lhs, object rhs) => Convert(lhs) & Convert(rhs); + public object bitwiseOr(object lhs, object rhs) => Convert(lhs) | Convert(rhs); + public object bitwiseXOr(object lhs, object rhs) => Convert(lhs) ^ Convert(rhs); + public object bitwiseLeftShift(object lhs, object rhs) => Convert(lhs) << (int)Convert(rhs); + public object bitwiseRightShift(object lhs, object rhs) => Convert(lhs) >> (int)Convert(rhs); + public object bitwiseNot(object target) => ~Convert(target); } public class DynamicLong : IDynamicNumber @@ -226,11 +321,25 @@ public bool TryParse(string str, out object result) } public string ToString(object value) => Convert(value).ToString(); + public object DefaultValue => default(long); public object add(object lhs, object rhs) => Convert(lhs) + Convert(rhs); public object sub(object lhs, object rhs) => Convert(lhs) - Convert(rhs); public object mul(object lhs, object rhs) => Convert(lhs) * Convert(rhs); public object div(object lhs, object rhs) => Convert(lhs) / Convert(rhs); + public object mod(object lhs, object rhs) => Convert(lhs) % Convert(rhs); + public object min(object lhs, object rhs) => Math.Min(Convert(lhs), Convert(rhs)); + public object max(object lhs, object rhs) => Math.Max(Convert(lhs), Convert(rhs)); + public object pow(object lhs, object rhs) => Math.Pow(Convert(lhs), Convert(rhs)); + public object log(object lhs, object rhs) => Math.Log(Convert(lhs), Convert(rhs)); + public int compareTo(object lhs, object rhs) => Convert(lhs).CompareTo(Convert(rhs)); + + public object bitwiseAnd(object lhs, object rhs) => Convert(lhs) & Convert(rhs); + public object bitwiseOr(object lhs, object rhs) => Convert(lhs) | Convert(rhs); + public object bitwiseXOr(object lhs, object rhs) => Convert(lhs) ^ Convert(rhs); + public object bitwiseLeftShift(object lhs, object rhs) => Convert(lhs) << (int)Convert(rhs); + public object bitwiseRightShift(object lhs, object rhs) => Convert(lhs) >> (int)Convert(rhs); + public object bitwiseNot(object target) => ~Convert(target); } public class DynamicULong : IDynamicNumber @@ -256,11 +365,25 @@ public bool TryParse(string str, out object result) } public string ToString(object value) => Convert(value).ToString(); + public object DefaultValue => default(ulong); public object add(object lhs, object rhs) => Convert(lhs) + Convert(rhs); public object sub(object lhs, object rhs) => Convert(lhs) - Convert(rhs); public object mul(object lhs, object rhs) => Convert(lhs) * Convert(rhs); public object div(object lhs, object rhs) => Convert(lhs) / Convert(rhs); + public object mod(object lhs, object rhs) => Convert(lhs) % Convert(rhs); + public object min(object lhs, object rhs) => Math.Min(Convert(lhs), Convert(rhs)); + public object max(object lhs, object rhs) => Math.Max(Convert(lhs), Convert(rhs)); + public object pow(object lhs, object rhs) => Math.Pow(Convert(lhs), Convert(rhs)); + public object log(object lhs, object rhs) => Math.Log(Convert(lhs), Convert(rhs)); + public int compareTo(object lhs, object rhs) => Convert(lhs).CompareTo(Convert(rhs)); + + public object bitwiseAnd(object lhs, object rhs) => Convert(lhs) & Convert(rhs); + public object bitwiseOr(object lhs, object rhs) => Convert(lhs) | Convert(rhs); + public object bitwiseXOr(object lhs, object rhs) => Convert(lhs) ^ Convert(rhs); + public object bitwiseLeftShift(object lhs, object rhs) => Convert(lhs) << (int)Convert(rhs); + public object bitwiseRightShift(object lhs, object rhs) => Convert(lhs) >> (int)Convert(rhs); + public object bitwiseNot(object target) => ~Convert(target); } public class DynamicFloat : IDynamicNumber @@ -276,7 +399,7 @@ public object ConvertFrom(object value) => this.ParseString(value) public bool TryParse(string str, out object result) { - if (new StringSegment(str).TryParseFloat(out float value)) + if (str.AsSpan().TryParseFloat(out float value)) { result = value; return true; @@ -286,11 +409,25 @@ public bool TryParse(string str, out object result) } public string ToString(object value) => Convert(value).ToString("r", CultureInfo.InvariantCulture); + public object DefaultValue => default(float); public object add(object lhs, object rhs) => Convert(lhs) + Convert(rhs); public object sub(object lhs, object rhs) => Convert(lhs) - Convert(rhs); public object mul(object lhs, object rhs) => Convert(lhs) * Convert(rhs); public object div(object lhs, object rhs) => Convert(lhs) / Convert(rhs); + public object mod(object lhs, object rhs) => Convert(lhs) % Convert(rhs); + public object min(object lhs, object rhs) => Math.Min(Convert(lhs), Convert(rhs)); + public object max(object lhs, object rhs) => Math.Max(Convert(lhs), Convert(rhs)); + public object pow(object lhs, object rhs) => Math.Pow(Convert(lhs), Convert(rhs)); + public object log(object lhs, object rhs) => Math.Log(Convert(lhs), Convert(rhs)); + public int compareTo(object lhs, object rhs) => Convert(lhs).CompareTo(Convert(rhs)); + + public object bitwiseAnd(object lhs, object rhs) => throw new NotSupportedException("Bitwise operators only supported on integer types"); + public object bitwiseOr(object lhs, object rhs) => throw new NotSupportedException("Bitwise operators only supported on integer types"); + public object bitwiseXOr(object lhs, object rhs) => throw new NotSupportedException("Bitwise operators only supported on integer types"); + public object bitwiseLeftShift(object lhs, object rhs) => throw new NotSupportedException("Bitwise operators only supported on integer types"); + public object bitwiseRightShift(object lhs, object rhs) => throw new NotSupportedException("Bitwise operators only supported on integer types"); + public object bitwiseNot(object target) => throw new NotSupportedException("Bitwise operators only supported on integer types"); } public class DynamicDouble : IDynamicNumber @@ -306,7 +443,7 @@ public object ConvertFrom(object value) => this.ParseString(value) public bool TryParse(string str, out object result) { - if (new StringSegment(str).TryParseDouble(out double value)) + if (str.AsSpan().TryParseDouble(out double value)) { result = value; return true; @@ -316,11 +453,25 @@ public bool TryParse(string str, out object result) } public string ToString(object value) => Convert(value).ToString("r", CultureInfo.InvariantCulture); + public object DefaultValue => default(double); public object add(object lhs, object rhs) => Convert(lhs) + Convert(rhs); public object sub(object lhs, object rhs) => Convert(lhs) - Convert(rhs); public object mul(object lhs, object rhs) => Convert(lhs) * Convert(rhs); public object div(object lhs, object rhs) => Convert(lhs) / Convert(rhs); + public object mod(object lhs, object rhs) => Convert(lhs) % Convert(rhs); + public object min(object lhs, object rhs) => Math.Min(Convert(lhs), Convert(rhs)); + public object max(object lhs, object rhs) => Math.Max(Convert(lhs), Convert(rhs)); + public object pow(object lhs, object rhs) => Math.Pow(Convert(lhs), Convert(rhs)); + public object log(object lhs, object rhs) => Math.Log(Convert(lhs), Convert(rhs)); + public int compareTo(object lhs, object rhs) => Convert(lhs).CompareTo(Convert(rhs)); + + public object bitwiseAnd(object lhs, object rhs) => throw new NotSupportedException("Bitwise operators only supported on integer types"); + public object bitwiseOr(object lhs, object rhs) => throw new NotSupportedException("Bitwise operators only supported on integer types"); + public object bitwiseXOr(object lhs, object rhs) => throw new NotSupportedException("Bitwise operators only supported on integer types"); + public object bitwiseLeftShift(object lhs, object rhs) => throw new NotSupportedException("Bitwise operators only supported on integer types"); + public object bitwiseRightShift(object lhs, object rhs) => throw new NotSupportedException("Bitwise operators only supported on integer types"); + public object bitwiseNot(object target) => throw new NotSupportedException("Bitwise operators only supported on integer types"); } public class DynamicDecimal : IDynamicNumber @@ -336,7 +487,7 @@ public object ConvertFrom(object value) => this.ParseString(value) public bool TryParse(string str, out object result) { - if (new StringSegment(str).TryParseDecimal(out decimal value)) + if (str.AsSpan().TryParseDecimal(out decimal value)) { result = value; return true; @@ -346,11 +497,25 @@ public bool TryParse(string str, out object result) } public string ToString(object value) => Convert(value).ToString(CultureInfo.InvariantCulture); + public object DefaultValue => default(decimal); public object add(object lhs, object rhs) => Convert(lhs) + Convert(rhs); public object sub(object lhs, object rhs) => Convert(lhs) - Convert(rhs); public object mul(object lhs, object rhs) => Convert(lhs) * Convert(rhs); public object div(object lhs, object rhs) => Convert(lhs) / Convert(rhs); + public object mod(object lhs, object rhs) => Convert(lhs) % Convert(rhs); + public object min(object lhs, object rhs) => Math.Min(Convert(lhs), Convert(rhs)); + public object max(object lhs, object rhs) => Math.Max(Convert(lhs), Convert(rhs)); + public object pow(object lhs, object rhs) => Math.Pow((double) Convert(lhs), (double) Convert(rhs)); + public object log(object lhs, object rhs) => Math.Log((double) Convert(lhs), (double) Convert(rhs)); + public int compareTo(object lhs, object rhs) => Convert(lhs).CompareTo(Convert(rhs)); + + public object bitwiseAnd(object lhs, object rhs) => throw new NotSupportedException("Bitwise operators only supported on integer types"); + public object bitwiseOr(object lhs, object rhs) => throw new NotSupportedException("Bitwise operators only supported on integer types"); + public object bitwiseXOr(object lhs, object rhs) => throw new NotSupportedException("Bitwise operators only supported on integer types"); + public object bitwiseLeftShift(object lhs, object rhs) => throw new NotSupportedException("Bitwise operators only supported on integer types"); + public object bitwiseRightShift(object lhs, object rhs) => throw new NotSupportedException("Bitwise operators only supported on integer types"); + public object bitwiseNot(object target) => throw new NotSupportedException("Bitwise operators only supported on integer types"); } public static class DynamicNumber @@ -425,6 +590,19 @@ public static IDynamicNumber GetNumber(Type type) return maxNumber; } + public static IDynamicNumber Get(object obj) + { + if (obj == null) + return null; + + if (obj is string lhsString && !TryParse(lhsString, out obj)) + return null; + + return TryGetRanking(obj.GetType(), out int lhsRanking) + ? RankNumbers[lhsRanking] + : null; + } + public static IDynamicNumber GetNumber(object lhs, object rhs) { if (lhs == null || rhs == null) @@ -450,8 +628,8 @@ public static IDynamicNumber AssertNumbers(string name, object lhs, object rhs) if (number == null) { throw new ArgumentException($"Invalid numbers passed to {name}: " + - $"({lhs?.GetType().Name ?? "null"} '{lhs?.ToString().SubstringWithElipsis(0, 100)}', " + - $"{rhs?.GetType().Name ?? "null"} '{rhs?.ToString().SubstringWithElipsis(0, 100)}')"); + $"({lhs?.GetType().Name ?? "null"} '{lhs?.ToString().SubstringWithEllipsis(0, 100)}', " + + $"{rhs?.GetType().Name ?? "null"} '{rhs?.ToString().SubstringWithEllipsis(0, 100)}')"); } return number; @@ -478,6 +656,42 @@ public static IDynamicNumber AssertNumbers(string name, object lhs, object rhs) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static object Divide(object lhs, object rhs) => AssertNumbers(nameof(Divide), lhs, rhs).div(lhs, rhs); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static object Mod(object lhs, object rhs) => AssertNumbers(nameof(Mod), lhs, rhs).mod(lhs, rhs); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static object Min(object lhs, object rhs) => AssertNumbers(nameof(Min), lhs, rhs).min(lhs, rhs); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static object Max(object lhs, object rhs) => AssertNumbers(nameof(Max), lhs, rhs).max(lhs, rhs); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static object Pow(object lhs, object rhs) => AssertNumbers(nameof(Pow), lhs, rhs).pow(lhs, rhs); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static object Log(object lhs, object rhs) => AssertNumbers(nameof(Log), lhs, rhs).log(lhs, rhs); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int CompareTo(object lhs, object rhs) => AssertNumbers(nameof(CompareTo), lhs, rhs).compareTo(lhs, rhs); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static object BitwiseAnd(object lhs, object rhs) => AssertNumbers(nameof(BitwiseAnd), lhs, rhs).bitwiseAnd(lhs, rhs); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static object BitwiseOr(object lhs, object rhs) => AssertNumbers(nameof(BitwiseOr), lhs, rhs).bitwiseOr(lhs, rhs); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static object BitwiseXOr(object lhs, object rhs) => AssertNumbers(nameof(BitwiseXOr), lhs, rhs).bitwiseXOr(lhs, rhs); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static object BitwiseLeftShift(object lhs, object rhs) => AssertNumbers(nameof(BitwiseLeftShift), lhs, rhs).bitwiseLeftShift(lhs, rhs); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static object BitwiseRightShift(object lhs, object rhs) => AssertNumbers(nameof(BitwiseRightShift), lhs, rhs).bitwiseRightShift(lhs, rhs); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static object BitwiseNot(object lhs) => Get(lhs).bitwiseNot(lhs); + public static bool TryParse(string strValue, out object result) { if (JsConfig.TryParseIntoBestFit) @@ -490,11 +704,12 @@ public static bool TryParse(string strValue, out object result) if (strValue.Length == 1) { int singleDigit = strValue[0]; - if (singleDigit >= 48 || singleDigit <= 57) // 0 - 9 + if (singleDigit >= '0' && singleDigit <= '9') { result = singleDigit - 48; // 0 return true; } + return false; } var hasDecimal = strValue.IndexOf('.') >= 0; @@ -517,14 +732,14 @@ public static bool TryParse(string strValue, out object result) } } - var segValue = new StringSegment(strValue); - if (segValue.TryParseDouble(out double doubleValue)) + var spanValue = strValue.AsSpan(); + if (spanValue.TryParseDouble(out double doubleValue)) { result = doubleValue; return true; } - if (segValue.TryParseDecimal(out decimal decimalValue)) + if (spanValue.TryParseDecimal(out decimal decimalValue)) { result = decimalValue; return true; @@ -539,7 +754,7 @@ public static bool TryParseIntoBestFit(string strValue, out object result) if (!(strValue?.Length > 0)) return false; - var segValue = new StringSegment(strValue); + var segValue = strValue.AsSpan(); result = segValue.ParseNumber(bestFit:true); return result != null; } diff --git a/src/ServiceStack.Text/Env.cs b/src/ServiceStack.Text/Env.cs index d2684cd47..003d2d40a 100644 --- a/src/ServiceStack.Text/Env.cs +++ b/src/ServiceStack.Text/Env.cs @@ -4,6 +4,9 @@ using System; using System.Globalization; using System.IO; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading.Tasks; namespace ServiceStack.Text { @@ -14,69 +17,105 @@ static Env() if (PclExport.Instance == null) throw new ArgumentException("PclExport.Instance needs to be initialized"); - var platformName = PclExport.Instance.PlatformName; - if (platformName != PclExport.Platforms.Uwp) - { - IsMono = AssemblyUtils.FindType("Mono.Runtime") != null; - - IsIOS = AssemblyUtils.FindType("MonoTouch.Foundation.NSObject") != null - || AssemblyUtils.FindType("Foundation.NSObject") != null; - - IsAndroid = AssemblyUtils.FindType("Android.Manifest") != null; - - try - { - IsOSX = AssemblyUtils.FindType("Mono.AppKit") != null; -#if NET45 - IsWindows = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("windir")); - if (File.Exists(@"/System/Library/CoreServices/SystemVersion.plist")) - IsOSX = true; - string osType = File.Exists(@"/proc/sys/kernel/ostype") ? File.ReadAllText(@"/proc/sys/kernel/ostype") : null; - IsLinux = osType?.IndexOf("Linux", StringComparison.OrdinalIgnoreCase) >= 0; -#endif - } - catch (Exception) {} - } - else - { - IsUWP = true; - } - -#if NETSTANDARD2_0 +#if NETCORE IsNetStandard = true; try { - IsLinux = System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Linux); - IsWindows = System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows); - IsOSX = System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.OSX); + IsLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux); + IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + IsOSX = RuntimeInformation.IsOSPlatform(OSPlatform.OSX); + IsNetCore3 = RuntimeInformation.FrameworkDescription.StartsWith(".NET Core 3"); + + var fxDesc = RuntimeInformation.FrameworkDescription; + IsMono = fxDesc.Contains("Mono"); + IsNetCore = fxDesc.StartsWith(".NET Core", StringComparison.OrdinalIgnoreCase); } catch (Exception) {} //throws PlatformNotSupportedException in AWS lambda IsUnix = IsOSX || IsLinux; HasMultiplePlatformTargets = true; -#elif NET45 + IsUWP = IsRunningAsUwp(); +#elif NETFX IsNetFramework = true; + switch (Environment.OSVersion.Platform) + { + case PlatformID.Win32NT: + case PlatformID.Win32S: + case PlatformID.Win32Windows: + case PlatformID.WinCE: + IsWindows = true; + break; + } + var platform = (int)Environment.OSVersion.Platform; IsUnix = platform == 4 || platform == 6 || platform == 128; - IsLinux = IsUnix; - if (Environment.GetEnvironmentVariable("OS")?.IndexOf("Windows", StringComparison.OrdinalIgnoreCase) >= 0) - IsWindows = true; + + if (File.Exists(@"/System/Library/CoreServices/SystemVersion.plist")) + IsOSX = true; + var osType = File.Exists(@"/proc/sys/kernel/ostype") + ? File.ReadAllText(@"/proc/sys/kernel/ostype") + : null; + IsLinux = osType?.IndexOf("Linux", StringComparison.OrdinalIgnoreCase) >= 0; + try + { + IsMono = AssemblyUtils.FindType("Mono.Runtime") != null; + } + catch (Exception) {} + + SupportsDynamic = true; +#endif + +#if NETCORE + IsNetStandard = false; + IsNetCore = true; + SupportsDynamic = true; + IsNetCore21 = true; +#endif +#if NET6_0 + IsNet6 = true; +#endif +#if NETSTANDARD2_0 + IsNetStandard20 = true; #endif + + if (!IsUWP) + { + try + { + IsAndroid = AssemblyUtils.FindType("Android.Manifest") != null; + if (IsOSX && IsMono) + { + var runtimeDir = RuntimeEnvironment.GetRuntimeDirectory(); + //iOS detection no longer trustworthy so assuming iOS based on some current heuristics. TODO: improve iOS detection + IsIOS = runtimeDir.StartsWith("/private/var") || + runtimeDir.Contains("/CoreSimulator/Devices/"); + } + } + catch (Exception) {} + } + SupportsExpressions = true; - SupportsEmit = !IsIOS; + SupportsEmit = !(IsUWP || IsIOS); - ServerUserAgent = "ServiceStack/" + - ServiceStackVersion + " " - + platformName - + (IsMono ? "/Mono" : "/.NET"); + if (!SupportsEmit) + { + ReflectionOptimizer.Instance = ExpressionReflectionOptimizer.Provider; + } VersionString = ServiceStackVersion.ToString(CultureInfo.InvariantCulture); + ServerUserAgent = "ServiceStack/" + + VersionString + " " + + PclExport.Instance.PlatformName + + (IsLinux ? "/Linux" : IsOSX ? "/macOS" : IsUnix ? "/Unix" : IsWindows ? "/Windows" : "/UnknownOS") + + (IsIOS ? "/iOS" : IsAndroid ? "/Android" : IsUWP ? "/UWP" : "") + + (IsNet6 ? "/net6" : IsNetStandard20 ? "/std2.0" : IsNetFramework ? "/netfx" : "") + (IsMono ? "/Mono" : "") + + $"/{LicenseUtils.Info}"; __releaseDate = new DateTime(2001,01,01); } public static string VersionString { get; set; } - public static decimal ServiceStackVersion = 5.00m; + public static decimal ServiceStackVersion = 6.03m; public static bool IsLinux { get; set; } @@ -92,17 +131,34 @@ static Env() public static bool IsAndroid { get; set; } - public static bool IsUWP { get; set; } + public static bool IsNetNative { get; set; } + + public static bool IsUWP { get; private set; } public static bool IsNetStandard { get; set; } + public static bool IsNetCore21 { get; set; } + public static bool IsNet6 { get; set; } + public static bool IsNetStandard20 { get; set; } + public static bool IsNetFramework { get; set; } - public static bool SupportsExpressions { get; set; } + public static bool IsNetCore { get; set; } + + public static bool IsNetCore3 { get; set; } + + public static bool SupportsExpressions { get; private set; } + + public static bool SupportsEmit { get; private set; } - public static bool SupportsEmit { get; set; } + public static bool SupportsDynamic { get; private set; } - public static bool StrictMode { get; set; } + private static bool strictMode; + public static bool StrictMode + { + get => strictMode; + set => Config.Instance.ThrowOnError = strictMode = value; + } public static string ServerUserAgent { get; set; } @@ -114,23 +170,27 @@ public static DateTime GetReleaseDate() return __releaseDate; } - private static string referenceAssembyPath; - public static string ReferenceAssembyPath + [Obsolete("Use ReferenceAssemblyPath")] + public static string ReferenceAssembyPath => ReferenceAssemblyPath; + + private static string referenceAssemblyPath; + + public static string ReferenceAssemblyPath { get { - if (!IsMono && referenceAssembyPath == null) + if (!IsMono && referenceAssemblyPath == null) { var programFilesPath = PclExport.Instance.GetEnvironmentVariable("ProgramFiles(x86)") ?? @"C:\Program Files (x86)"; var netFxReferenceBasePath = programFilesPath + @"\Reference Assemblies\Microsoft\Framework\.NETFramework\"; if ((netFxReferenceBasePath + @"v4.5.2\").DirectoryExists()) - referenceAssembyPath = netFxReferenceBasePath + @"v4.5.2\"; + referenceAssemblyPath = netFxReferenceBasePath + @"v4.5.2\"; else if ((netFxReferenceBasePath + @"v4.5.1\").DirectoryExists()) - referenceAssembyPath = netFxReferenceBasePath + @"v4.5.1\"; + referenceAssemblyPath = netFxReferenceBasePath + @"v4.5.1\"; else if ((netFxReferenceBasePath + @"v4.5\").DirectoryExists()) - referenceAssembyPath = netFxReferenceBasePath + @"v4.5\"; + referenceAssemblyPath = netFxReferenceBasePath + @"v4.5\"; else if ((netFxReferenceBasePath + @"v4.0\").DirectoryExists()) - referenceAssembyPath = netFxReferenceBasePath + @"v4.0\"; + referenceAssemblyPath = netFxReferenceBasePath + @"v4.0\"; else { var v4Dirs = PclExport.Instance.GetDirectoryNames(netFxReferenceBasePath, "v4*"); @@ -142,19 +202,127 @@ public static string ReferenceAssembyPath } if (v4Dirs.Length > 0) { - referenceAssembyPath = v4Dirs[v4Dirs.Length - 1] + @"\"; //latest v4 + referenceAssemblyPath = v4Dirs[v4Dirs.Length - 1] + @"\"; //latest v4 } else { throw new FileNotFoundException( "Could not infer .NET Reference Assemblies path, e.g '{0}'.\n".Fmt(netFxReferenceBasePath + @"v4.0\") + - "Provide path manually 'Env.ReferenceAssembyPath'."); + "Provide path manually 'Env.ReferenceAssemblyPath'."); } } } - return referenceAssembyPath; + return referenceAssemblyPath; } - set { referenceAssembyPath = value; } + set => referenceAssemblyPath = value; } + +#if NETCORE + private static bool IsRunningAsUwp() + { + try + { + IsNetNative = RuntimeInformation.FrameworkDescription.StartsWith(".NET Native", StringComparison.OrdinalIgnoreCase); + return IsInAppContainer || IsNetNative; + } + catch (Exception) {} + return false; + } + + private static bool IsWindows7OrLower + { + get + { + int versionMajor = Environment.OSVersion.Version.Major; + int versionMinor = Environment.OSVersion.Version.Minor; + double version = versionMajor + (double)versionMinor / 10; + return version <= 6.1; + } + } + + // From: https://github.com/dotnet/corefx/blob/master/src/CoreFx.Private.TestUtilities/src/System/PlatformDetection.Windows.cs + private static int s_isInAppContainer = -1; + private static bool IsInAppContainer + { + // This actually checks whether code is running in a modern app. + // Currently this is the only situation where we run in app container. + // If we want to distinguish the two cases in future, + // EnvironmentHelpers.IsAppContainerProcess in desktop code shows how to check for the AC token. + get + { + if (s_isInAppContainer != -1) + return s_isInAppContainer == 1; + + if (!IsWindows || IsWindows7OrLower) + { + s_isInAppContainer = 0; + return false; + } + + byte[] buffer = TypeConstants.EmptyByteArray; + uint bufferSize = 0; + try + { + int result = GetCurrentApplicationUserModelId(ref bufferSize, buffer); + switch (result) + { + case 15703: // APPMODEL_ERROR_NO_APPLICATION + s_isInAppContainer = 0; + break; + case 0: // ERROR_SUCCESS + case 122: // ERROR_INSUFFICIENT_BUFFER + // Success is actually insufficient buffer as we're really only looking for + // not NO_APPLICATION and we're not actually giving a buffer here. The + // API will always return NO_APPLICATION if we're not running under a + // WinRT process, no matter what size the buffer is. + s_isInAppContainer = 1; + break; + default: + throw new InvalidOperationException($"Failed to get AppId, result was {result}."); + } + } + catch (Exception e) + { + // We could catch this here, being friendly with older portable surface area should we + // desire to use this method elsewhere. + if (e.GetType().FullName.Equals("System.EntryPointNotFoundException", StringComparison.Ordinal)) + { + // API doesn't exist, likely pre Win8 + s_isInAppContainer = 0; + } + else + { + throw; + } + } + + return s_isInAppContainer == 1; + } + } + + [DllImport("kernel32.dll", ExactSpelling = true)] + private static extern int GetCurrentApplicationUserModelId(ref uint applicationUserModelIdLength, byte[] applicationUserModelId); + #endif + + public const bool ContinueOnCapturedContext = false; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ConfiguredTaskAwaitable ConfigAwait(this Task task) => + task.ConfigureAwait(ContinueOnCapturedContext); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ConfiguredTaskAwaitable ConfigAwait(this Task task) => + task.ConfigureAwait(ContinueOnCapturedContext); + +#if NETCORE + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ConfiguredValueTaskAwaitable ConfigAwait(this ValueTask task) => + task.ConfigureAwait(ContinueOnCapturedContext); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ConfiguredValueTaskAwaitable ConfigAwait(this ValueTask task) => + task.ConfigureAwait(ContinueOnCapturedContext); +#endif + } } \ No newline at end of file diff --git a/src/ServiceStack.Text/Extensions/ServiceStackExtensions.cs b/src/ServiceStack.Text/Extensions/ServiceStackExtensions.cs new file mode 100644 index 000000000..14c422526 --- /dev/null +++ b/src/ServiceStack.Text/Extensions/ServiceStackExtensions.cs @@ -0,0 +1,42 @@ +using System; + +namespace ServiceStack.Extensions +{ + /// + /// Move conflicting extension methods into 'ServiceStack.Extensions' namespace + /// + public static class ServiceStackExtensions + { + //Ambiguous definitions in .NET Core 3.0 System MemoryExtensions.cs + public static ReadOnlyMemory Trim(this ReadOnlyMemory span) + { + return span.TrimStart().TrimEnd(); + } + + public static ReadOnlyMemory TrimStart(this ReadOnlyMemory value) + { + if (value.IsEmpty) return TypeConstants.NullStringMemory; + var span = value.Span; + int start = 0; + for (; start < span.Length; start++) + { + if (!char.IsWhiteSpace(span[start])) + break; + } + return value.Slice(start); + } + + public static ReadOnlyMemory TrimEnd(this ReadOnlyMemory value) + { + if (value.IsEmpty) return TypeConstants.NullStringMemory; + var span = value.Span; + int end = span.Length - 1; + for (; end >= 0; end--) + { + if (!char.IsWhiteSpace(span[end])) + break; + } + return value.Slice(0, end + 1); + } + } +} \ No newline at end of file diff --git a/src/ServiceStack.Text/HashSet.cs b/src/ServiceStack.Text/HashSet.cs deleted file mode 100644 index eb0b9271b..000000000 --- a/src/ServiceStack.Text/HashSet.cs +++ /dev/null @@ -1,90 +0,0 @@ -#if (NETFX_CORE || WP) -// -// https://github.com/ServiceStack/ServiceStack.Text -// ServiceStack.Text: .NET C# POCO JSON, JSV and CSV Text Serializers. -// -// Authors: -// Demis Bellot (demis.bellot@gmail.com) -// Mijail Cisneros (cisneros@mijail.ru) -// -// Copyright 2012 Liquidbit Ltd. -// -// Licensed under the same terms of ServiceStack. -// - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; - -namespace ServiceStack.Text -{ - /// - /// A hashset implementation that uses an IDictionary - /// - public class HashSet : ICollection, IEnumerable, IEnumerable - { - private readonly Dictionary _dict; - - public HashSet() - { - _dict = new Dictionary(); - } - - public HashSet(IEnumerable collection) - { - if (collection == null) - throw new ArgumentNullException("collection"); - - _dict = new Dictionary(collection.Count()); - foreach (T item in collection) - Add(item); - } - - public void Add(T item) - { - _dict.Add(item, 0); - } - - public void Clear() - { - _dict.Clear(); - } - - public bool Contains(T item) - { - return _dict.ContainsKey(item); - } - - public void CopyTo(T[] array, int arrayIndex) - { - _dict.Keys.CopyTo(array, arrayIndex); - } - - public bool Remove(T item) - { - return _dict.Remove(item); - } - - public IEnumerator GetEnumerator() - { - return _dict.Keys.GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return _dict.Keys.GetEnumerator(); - } - - public int Count - { - get { return _dict.Keys.Count(); } - } - - public bool IsReadOnly - { - get { return false; } - } - } -} -#endif diff --git a/src/ServiceStack.Text/HttpHeaders.cs b/src/ServiceStack.Text/HttpHeaders.cs new file mode 100644 index 000000000..8cdfeace3 --- /dev/null +++ b/src/ServiceStack.Text/HttpHeaders.cs @@ -0,0 +1,213 @@ +using System; +using System.Collections.Generic; + +namespace ServiceStack; + +public static class HttpHeaders +{ + public const string XParamOverridePrefix = "X-Param-Override-"; + + public const string XHttpMethodOverride = "X-Http-Method-Override"; + + public const string XAutoBatchCompleted = "X-AutoBatch-Completed"; // How many requests were completed before first failure + + public const string XTag = "X-Tag"; + + public const string XUserAuthId = "X-UAId"; + + public const string XTrigger = "X-Trigger"; // Trigger Events on UserAgent + + public const string XForwardedFor = "X-Forwarded-For"; // IP Address + + public const string XForwardedPort = "X-Forwarded-Port"; // 80 + + public const string XForwardedProtocol = "X-Forwarded-Proto"; // http or https + + public const string XRealIp = "X-Real-IP"; + + public const string XLocation = "X-Location"; + + public const string XStatus = "X-Status"; + + public const string XPoweredBy = "X-Powered-By"; + + public const string Referer = "Referer"; + + public const string CacheControl = "Cache-Control"; + + public const string IfModifiedSince = "If-Modified-Since"; + + public const string IfUnmodifiedSince = "If-Unmodified-Since"; + + public const string IfNoneMatch = "If-None-Match"; + + public const string IfMatch = "If-Match"; + + public const string LastModified = "Last-Modified"; + + public const string Accept = "Accept"; + + public const string AcceptEncoding = "Accept-Encoding"; + + public const string ContentType = "Content-Type"; + + public const string ContentEncoding = "Content-Encoding"; + + public const string ContentLength = "Content-Length"; + + public const string ContentDisposition = "Content-Disposition"; + + public const string Location = "Location"; + + public const string SetCookie = "Set-Cookie"; + + public const string ETag = "ETag"; + + public const string Age = "Age"; + + public const string Expires = "Expires"; + + public const string Vary = "Vary"; + + public const string Authorization = "Authorization"; + + public const string WwwAuthenticate = "WWW-Authenticate"; + + public const string AllowOrigin = "Access-Control-Allow-Origin"; + + public const string AllowMethods = "Access-Control-Allow-Methods"; + + public const string AllowHeaders = "Access-Control-Allow-Headers"; + + public const string AllowCredentials = "Access-Control-Allow-Credentials"; + + public const string ExposeHeaders = "Access-Control-Expose-Headers"; + + public const string AccessControlMaxAge = "Access-Control-Max-Age"; + + public const string Origin = "Origin"; + + public const string RequestMethod = "Access-Control-Request-Method"; + + public const string RequestHeaders = "Access-Control-Request-Headers"; + + public const string AcceptRanges = "Accept-Ranges"; + + public const string ContentRange = "Content-Range"; + + public const string Range = "Range"; + + public const string SOAPAction = "SOAPAction"; + + public const string Allow = "Allow"; + + public const string AcceptCharset = "Accept-Charset"; + + public const string AcceptLanguage = "Accept-Language"; + + public const string Connection = "Connection"; + + public const string Cookie = "Cookie"; + + public const string ContentLanguage = "Content-Language"; + + public const string Expect = "Expect"; + + public const string Pragma = "Pragma"; + + public const string ProxyAuthenticate = "Proxy-Authenticate"; + + public const string ProxyAuthorization = "Proxy-Authorization"; + + public const string ProxyConnection = "Proxy-Connection"; + + public const string SetCookie2 = "Set-Cookie2"; + + public const string TE = "TE"; + + public const string Trailer = "Trailer"; + + public const string TransferEncoding = "Transfer-Encoding"; + + public const string Upgrade = "Upgrade"; + + public const string Via = "Via"; + + public const string Warning = "Warning"; + + public const string Date = "Date"; + public const string Host = "Host"; + public const string UserAgent = "User-Agent"; + + public static HashSet RestrictedHeaders = new(StringComparer.OrdinalIgnoreCase) + { + Accept, + Connection, + ContentLength, + ContentType, + Date, + Expect, + Host, + IfModifiedSince, + Range, + Referer, + TransferEncoding, + UserAgent, + ProxyConnection, + }; +} + + +public static class CompressionTypes +{ + public static readonly string[] AllCompressionTypes = + { +#if NET6_0_OR_GREATER + Brotli, +#endif + Deflate, + GZip, + }; + +#if NET6_0_OR_GREATER + public const string Default = Brotli; +#else + public const string Default = Deflate; +#endif + + public const string Brotli = "br"; + public const string Deflate = "deflate"; + public const string GZip = "gzip"; + + public static bool IsValid(string compressionType) + { +#if NET6_0_OR_GREATER + return compressionType is Deflate or GZip or Brotli; +#else + return compressionType is Deflate or GZip; +#endif + } + + public static void AssertIsValid(string compressionType) + { + if (!IsValid(compressionType)) + { + throw new NotSupportedException(compressionType + + " is not a supported compression type. Valid types: " + string.Join(", ", AllCompressionTypes)); + } + } + + public static string GetExtension(string compressionType) + { + switch (compressionType) + { + case Brotli: + case Deflate: + case GZip: + return "." + compressionType; + default: + throw new NotSupportedException( + "Unknown compressionType: " + compressionType); + } + } +} \ No newline at end of file diff --git a/src/ServiceStack.Text/HttpMethods.cs b/src/ServiceStack.Text/HttpMethods.cs new file mode 100644 index 000000000..af2427998 --- /dev/null +++ b/src/ServiceStack.Text/HttpMethods.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; + +namespace ServiceStack; + +public static class HttpMethods +{ + static readonly string[] allVerbs = { + "OPTIONS", "GET", "HEAD", "POST", "PUT", "DELETE", "TRACE", "CONNECT", // RFC 2616 + "PROPFIND", "PROPPATCH", "MKCOL", "COPY", "MOVE", "LOCK", "UNLOCK", // RFC 2518 + "VERSION-CONTROL", "REPORT", "CHECKOUT", "CHECKIN", "UNCHECKOUT", + "MKWORKSPACE", "UPDATE", "LABEL", "MERGE", "BASELINE-CONTROL", "MKACTIVITY", // RFC 3253 + "ORDERPATCH", // RFC 3648 + "ACL", // RFC 3744 + "PATCH", // https://datatracker.ietf.org/doc/draft-dusseault-http-patch/ + "SEARCH", // https://datatracker.ietf.org/doc/draft-reschke-webdav-search/ + "BCOPY", "BDELETE", "BMOVE", "BPROPFIND", "BPROPPATCH", "NOTIFY", + "POLL", "SUBSCRIBE", "UNSUBSCRIBE" //MS Exchange WebDav: http://msdn.microsoft.com/en-us/library/aa142917.aspx + }; + + public static HashSet AllVerbs = new(allVerbs); + + public static bool Exists(string httpMethod) => AllVerbs.Contains(httpMethod.ToUpper()); + public static bool HasVerb(string httpVerb) => Exists(httpVerb); + + public const string Get = "GET"; + public const string Put = "PUT"; + public const string Post = "POST"; + public const string Delete = "DELETE"; + public const string Options = "OPTIONS"; + public const string Head = "HEAD"; + public const string Patch = "PATCH"; +} \ No newline at end of file diff --git a/src/ServiceStack.Text/HttpRequestConfig.cs b/src/ServiceStack.Text/HttpRequestConfig.cs new file mode 100644 index 000000000..0f9ec75e9 --- /dev/null +++ b/src/ServiceStack.Text/HttpRequestConfig.cs @@ -0,0 +1,64 @@ +#nullable enable + +using System; +using System.Collections.Generic; +using System.Text; + +namespace ServiceStack; + +public class HttpRequestConfig +{ + public string? Accept { get; set; } + public string? UserAgent { get; set; } + public string? ContentType { get; set; } + public string? Referer { get; set; } + public string? Expect { get; set; } + public string[]? TransferEncoding { get; set; } + public bool? TransferEncodingChunked { get; set; } + public NameValue? Authorization { get; set; } + public LongRange? Range { get; set; } + public List Headers { get; set; } = new(); + + public void SetAuthBearer(string value) => Authorization = new("Bearer", value); + public void SetAuthBasic(string name, string value) => + Authorization = new("Basic", Convert.ToBase64String(Encoding.UTF8.GetBytes(name + ":" + value))); + public void SetRange(long from, long? to = null) => Range = new(from, to); + + public void AddHeader(string name, string value) => Headers.Add(new(name, value)); +} + +public record NameValue +{ + public NameValue(string name, string value) + { + this.Name = name; + this.Value = value; + } + + public string Name { get; } + public string Value { get; } + + public void Deconstruct(out string name, out string value) + { + name = this.Name; + value = this.Value; + } +} + +public record LongRange +{ + public LongRange(long from, long? to = null) + { + this.From = from; + this.To = to; + } + + public long From { get; } + public long? To { get; } + + public void Deconstruct(out long from, out long? to) + { + from = this.From; + to = this.To; + } +} diff --git a/src/ServiceStack.Text/HttpStatus.cs b/src/ServiceStack.Text/HttpStatus.cs new file mode 100644 index 000000000..a60925b96 --- /dev/null +++ b/src/ServiceStack.Text/HttpStatus.cs @@ -0,0 +1,90 @@ +namespace ServiceStack.Text; + +public static class HttpStatus +{ + public static string GetStatusDescription(int statusCode) + { + if (statusCode is >= 100 and < 600) + { + int i = statusCode / 100; + int j = statusCode % 100; + + if (j < Descriptions[i].Length) + return Descriptions[i][j]; + } + + return string.Empty; + } + + private static readonly string[][] Descriptions = + { + null, + new[] + { + /* 100 */ "Continue", + /* 101 */ "Switching Protocols", + /* 102 */ "Processing" + }, + new[] + { + /* 200 */ "OK", + /* 201 */ "Created", + /* 202 */ "Accepted", + /* 203 */ "Non-Authoritative Information", + /* 204 */ "No Content", + /* 205 */ "Reset Content", + /* 206 */ "Partial Content", + /* 207 */ "Multi-Status" + }, + new[] + { + /* 300 */ "Multiple Choices", + /* 301 */ "Moved Permanently", + /* 302 */ "Found", + /* 303 */ "See Other", + /* 304 */ "Not Modified", + /* 305 */ "Use Proxy", + /* 306 */ string.Empty, + /* 307 */ "Temporary Redirect" + }, + new[] + { + /* 400 */ "Bad Request", + /* 401 */ "Unauthorized", + /* 402 */ "Payment Required", + /* 403 */ "Forbidden", + /* 404 */ "Not Found", + /* 405 */ "Method Not Allowed", + /* 406 */ "Not Acceptable", + /* 407 */ "Proxy Authentication Required", + /* 408 */ "Request Timeout", + /* 409 */ "Conflict", + /* 410 */ "Gone", + /* 411 */ "Length Required", + /* 412 */ "Precondition Failed", + /* 413 */ "Request Entity Too Large", + /* 414 */ "Request-Uri Too Long", + /* 415 */ "Unsupported Media Type", + /* 416 */ "Requested Range Not Satisfiable", + /* 417 */ "Expectation Failed", + /* 418 */ string.Empty, + /* 419 */ string.Empty, + /* 420 */ string.Empty, + /* 421 */ string.Empty, + /* 422 */ "Unprocessable Entity", + /* 423 */ "Locked", + /* 424 */ "Failed Dependency" + }, + new[] + { + /* 500 */ "Internal Server Error", + /* 501 */ "Not Implemented", + /* 502 */ "Bad Gateway", + /* 503 */ "Service Unavailable", + /* 504 */ "Gateway Timeout", + /* 505 */ "Http Version Not Supported", + /* 506 */ string.Empty, + /* 507 */ "Insufficient Storage" + } + }; +} \ No newline at end of file diff --git a/src/ServiceStack.Text/HttpUtils.HttpClient.cs b/src/ServiceStack.Text/HttpUtils.HttpClient.cs new file mode 100644 index 000000000..a90a6d862 --- /dev/null +++ b/src/ServiceStack.Text/HttpUtils.HttpClient.cs @@ -0,0 +1,1172 @@ +#if NET6_0_OR_GREATER +#nullable enable + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading; +using System.Threading.Tasks; +using ServiceStack.Text; + +namespace ServiceStack; + +public static partial class HttpUtils +{ + private class HttpClientFactory + { + private readonly Lazy lazyHandler; + internal HttpClientFactory(Func handler) => + lazyHandler = new Lazy(() => handler(), LazyThreadSafetyMode.ExecutionAndPublication); + public HttpClient CreateClient() => new(lazyHandler.Value, disposeHandler: false); + } + + // Ok to use HttpClientHandler which now uses SocketsHttpHandler + // https://github.com/dotnet/runtime/blob/main/src/libraries/System.Net.Http/src/System/Net/Http/HttpClientHandler.cs#L16 + public static Func HttpClientHandlerFactory { get; set; } = () => new() { + UseDefaultCredentials = true, + AutomaticDecompression = DecompressionMethods.Brotli | DecompressionMethods.Deflate | DecompressionMethods.GZip, + }; + + // This was the least desirable end to this sadness https://github.com/dotnet/aspnetcore/issues/28385 + // Requires + // public static IHttpClientFactory ClientFactory { get; set; } = new ServiceCollection() + // .AddHttpClient() + // .Configure(options => + // options.HttpMessageHandlerBuilderActions.Add(builder => builder.PrimaryHandler = HandlerFactory)) + // .BuildServiceProvider().GetRequiredService(); + + // Escape & BYO IHttpClientFactory + private static HttpClientFactory? clientFactory; + public static Func CreateClient { get; set; } = () => { + try + { + clientFactory ??= new(HttpClientHandlerFactory); + return clientFactory.CreateClient(); + } + catch (Exception ex) + { + Tracer.Instance.WriteError(ex); + return new HttpClient(); + } + }; + + public static HttpClient Create() => CreateClient(); + + public static string GetJsonFromUrl(this string url, + Action? requestFilter = null, Action? responseFilter = null) + { + return url.GetStringFromUrl(accept:MimeTypes.Json, requestFilter, responseFilter); + } + + public static Task GetJsonFromUrlAsync(this string url, + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + return url.GetStringFromUrlAsync(accept:MimeTypes.Json, requestFilter, responseFilter, token: token); + } + + public static string GetXmlFromUrl(this string url, + Action? requestFilter = null, Action? responseFilter = null) + { + return url.GetStringFromUrl(accept:MimeTypes.Xml, requestFilter, responseFilter); + } + + public static Task GetXmlFromUrlAsync(this string url, + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + return url.GetStringFromUrlAsync(accept:MimeTypes.Xml, requestFilter, responseFilter, token: token); + } + + public static string GetCsvFromUrl(this string url, + Action? requestFilter = null, Action? responseFilter = null) + { + return url.GetStringFromUrl(accept:MimeTypes.Csv, requestFilter, responseFilter); + } + + public static Task GetCsvFromUrlAsync(this string url, + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + return url.GetStringFromUrlAsync(accept:MimeTypes.Csv, requestFilter, responseFilter, token: token); + } + + public static string GetStringFromUrl(this string url, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null) + { + return SendStringToUrl(url, method:HttpMethods.Get, accept: accept, + requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task GetStringFromUrlAsync(this string url, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method:HttpMethods.Get, accept: accept, + requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PostStringToUrl(this string url, string? requestBody = null, + string? contentType = null, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null) + { + return SendStringToUrl(url, method:HttpMethods.Post, + requestBody: requestBody, contentType: contentType, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PostStringToUrlAsync(this string url, string? requestBody = null, + string? contentType = null, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method:HttpMethods.Post, + requestBody: requestBody, contentType: contentType, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PostToUrl(this string url, string? formData = null, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null) + { + return SendStringToUrl(url, method:HttpMethods.Post, + contentType: MimeTypes.FormUrlEncoded, requestBody: formData, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PostToUrlAsync(this string url, string? formData = null, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method:HttpMethods.Post, + contentType: MimeTypes.FormUrlEncoded, requestBody: formData, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PostToUrl(this string url, object? formData = null, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null) + { + string? postFormData = formData != null ? QueryStringSerializer.SerializeToString(formData) : null; + + return SendStringToUrl(url, method:HttpMethods.Post, + contentType: MimeTypes.FormUrlEncoded, requestBody: postFormData, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PostToUrlAsync(this string url, object? formData = null, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + string? postFormData = formData != null ? QueryStringSerializer.SerializeToString(formData) : null; + + return SendStringToUrlAsync(url, method:HttpMethods.Post, + contentType: MimeTypes.FormUrlEncoded, requestBody: postFormData, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PostJsonToUrl(this string url, string json, + Action? requestFilter = null, Action? responseFilter = null) + { + return SendStringToUrl(url, method:HttpMethods.Post, requestBody: json, contentType: MimeTypes.Json, + accept: MimeTypes.Json, + requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PostJsonToUrlAsync(this string url, string json, + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method:HttpMethods.Post, requestBody: json, contentType: MimeTypes.Json, + accept: MimeTypes.Json, + requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PostJsonToUrl(this string url, object data, + Action? requestFilter = null, Action? responseFilter = null) + { + return SendStringToUrl(url, method:HttpMethods.Post, requestBody: data.ToJson(), contentType: MimeTypes.Json, + accept: MimeTypes.Json, + requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PostJsonToUrlAsync(this string url, object data, + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method:HttpMethods.Post, requestBody: data.ToJson(), contentType: MimeTypes.Json, + accept: MimeTypes.Json, + requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PostXmlToUrl(this string url, string xml, + Action? requestFilter = null, Action? responseFilter = null) + { + return SendStringToUrl(url, method:HttpMethods.Post, requestBody: xml, contentType: MimeTypes.Xml, accept: MimeTypes.Xml, + requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PostXmlToUrlAsync(this string url, string xml, + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method:HttpMethods.Post, requestBody: xml, contentType: MimeTypes.Xml, + accept: MimeTypes.Xml, + requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PostCsvToUrl(this string url, string csv, + Action? requestFilter = null, Action? responseFilter = null) + { + return SendStringToUrl(url, method:HttpMethods.Post, requestBody: csv, contentType: MimeTypes.Csv, accept: MimeTypes.Csv, + requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PostCsvToUrlAsync(this string url, string csv, + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method:HttpMethods.Post, requestBody: csv, contentType: MimeTypes.Csv, + accept: MimeTypes.Csv, + requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PutStringToUrl(this string url, string? requestBody = null, + string? contentType = null, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null) + { + return SendStringToUrl(url, method:HttpMethods.Put, + requestBody: requestBody, contentType: contentType, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PutStringToUrlAsync(this string url, string? requestBody = null, + string? contentType = null, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method:HttpMethods.Put, + requestBody: requestBody, contentType: contentType, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PutToUrl(this string url, string? formData = null, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null) + { + return SendStringToUrl(url, method:HttpMethods.Put, + contentType: MimeTypes.FormUrlEncoded, requestBody: formData, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PutToUrlAsync(this string url, string? formData = null, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method:HttpMethods.Put, + contentType: MimeTypes.FormUrlEncoded, requestBody: formData, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PutToUrl(this string url, object? formData = null, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null) + { + string? postFormData = formData != null ? QueryStringSerializer.SerializeToString(formData) : null; + + return SendStringToUrl(url, method:HttpMethods.Put, + contentType: MimeTypes.FormUrlEncoded, requestBody: postFormData, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PutToUrlAsync(this string url, object? formData = null, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + string? postFormData = formData != null ? QueryStringSerializer.SerializeToString(formData) : null; + + return SendStringToUrlAsync(url, method:HttpMethods.Put, + contentType: MimeTypes.FormUrlEncoded, requestBody: postFormData, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PutJsonToUrl(this string url, string json, + Action? requestFilter = null, Action? responseFilter = null) + { + return SendStringToUrl(url, method:HttpMethods.Put, requestBody: json, contentType: MimeTypes.Json, + accept: MimeTypes.Json, + requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PutJsonToUrlAsync(this string url, string json, + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method:HttpMethods.Put, requestBody: json, contentType: MimeTypes.Json, + accept: MimeTypes.Json, + requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PutJsonToUrl(this string url, object data, + Action? requestFilter = null, Action? responseFilter = null) + { + return SendStringToUrl(url, method:HttpMethods.Put, requestBody: data.ToJson(), contentType: MimeTypes.Json, + accept: MimeTypes.Json, + requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PutJsonToUrlAsync(this string url, object data, + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method:HttpMethods.Put, requestBody: data.ToJson(), contentType: MimeTypes.Json, + accept: MimeTypes.Json, + requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PutXmlToUrl(this string url, string xml, + Action? requestFilter = null, Action? responseFilter = null) + { + return SendStringToUrl(url, method:HttpMethods.Put, requestBody: xml, contentType: MimeTypes.Xml, accept: MimeTypes.Xml, + requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PutXmlToUrlAsync(this string url, string xml, + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method:HttpMethods.Put, requestBody: xml, contentType: MimeTypes.Xml, + accept: MimeTypes.Xml, + requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PutCsvToUrl(this string url, string csv, + Action? requestFilter = null, Action? responseFilter = null) + { + return SendStringToUrl(url, method:HttpMethods.Put, requestBody: csv, contentType: MimeTypes.Csv, accept: MimeTypes.Csv, + requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PutCsvToUrlAsync(this string url, string csv, + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method:HttpMethods.Put, requestBody: csv, contentType: MimeTypes.Csv, + accept: MimeTypes.Csv, + requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PatchStringToUrl(this string url, string? requestBody = null, + string? contentType = null, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null) + { + return SendStringToUrl(url, method:HttpMethods.Patch, + requestBody: requestBody, contentType: contentType, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PatchStringToUrlAsync(this string url, string? requestBody = null, + string? contentType = null, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method:HttpMethods.Patch, + requestBody: requestBody, contentType: contentType, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PatchToUrl(this string url, string? formData = null, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null) + { + return SendStringToUrl(url, method:HttpMethods.Patch, + contentType: MimeTypes.FormUrlEncoded, requestBody: formData, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PatchToUrlAsync(this string url, string? formData = null, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method:HttpMethods.Patch, + contentType: MimeTypes.FormUrlEncoded, requestBody: formData, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PatchToUrl(this string url, object? formData = null, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null) + { + string? postFormData = formData != null ? QueryStringSerializer.SerializeToString(formData) : null; + + return SendStringToUrl(url, method:HttpMethods.Patch, + contentType: MimeTypes.FormUrlEncoded, requestBody: postFormData, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PatchToUrlAsync(this string url, object? formData = null, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + string? postFormData = formData != null ? QueryStringSerializer.SerializeToString(formData) : null; + + return SendStringToUrlAsync(url, method:HttpMethods.Patch, + contentType: MimeTypes.FormUrlEncoded, requestBody: postFormData, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PatchJsonToUrl(this string url, string json, + Action? requestFilter = null, Action? responseFilter = null) + { + return SendStringToUrl(url, method:HttpMethods.Patch, requestBody: json, contentType: MimeTypes.Json, + accept: MimeTypes.Json, + requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PatchJsonToUrlAsync(this string url, string json, + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method:HttpMethods.Patch, requestBody: json, contentType: MimeTypes.Json, + accept: MimeTypes.Json, + requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PatchJsonToUrl(this string url, object data, + Action? requestFilter = null, Action? responseFilter = null) + { + return SendStringToUrl(url, method:HttpMethods.Patch, requestBody: data.ToJson(), contentType: MimeTypes.Json, + accept: MimeTypes.Json, + requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PatchJsonToUrlAsync(this string url, object data, + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method:HttpMethods.Patch, requestBody: data.ToJson(), contentType: MimeTypes.Json, + accept: MimeTypes.Json, + requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string DeleteFromUrl(this string url, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null) + { + return SendStringToUrl(url, method:HttpMethods.Delete, accept: accept, requestFilter: requestFilter, + responseFilter: responseFilter); + } + + public static Task DeleteFromUrlAsync(this string url, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method:HttpMethods.Delete, accept: accept, requestFilter: requestFilter, + responseFilter: responseFilter, token: token); + } + + public static string OptionsFromUrl(this string url, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null) + { + return SendStringToUrl(url, method:HttpMethods.Options, accept: accept, requestFilter: requestFilter, + responseFilter: responseFilter); + } + + public static Task OptionsFromUrlAsync(this string url, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method:HttpMethods.Options, accept: accept, requestFilter: requestFilter, + responseFilter: responseFilter, token: token); + } + + public static string HeadFromUrl(this string url, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null) + { + return SendStringToUrl(url, method:HttpMethods.Head, accept: accept, requestFilter: requestFilter, + responseFilter: responseFilter); + } + + public static Task HeadFromUrlAsync(this string url, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method:HttpMethods.Head, accept: accept, requestFilter: requestFilter, + responseFilter: responseFilter, token: token); + } + + public static string SendStringToUrl(this string url, string method = HttpMethods.Post, + string? requestBody = null, string? contentType = null, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null) + { + return Create().SendStringToUrl(url, method:method, requestBody:requestBody, + contentType:contentType, accept:accept, requestFilter:requestFilter, responseFilter:responseFilter); + } + + public static string SendStringToUrl(this HttpClient client, string url, string method = HttpMethods.Post, + string? requestBody = null, string? contentType = null, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null) + { + var httpReq = new HttpRequestMessage(new HttpMethod(method), url); + httpReq.Headers.Add(HttpHeaders.Accept, accept); + + if (requestBody != null) + { + httpReq.Content = new StringContent(requestBody, UseEncoding); + if (contentType != null) + httpReq.Content.Headers.ContentType = MediaTypeHeaderValue.Parse(contentType); + } + requestFilter?.Invoke(httpReq); + + var httpRes = client.Send(httpReq); + responseFilter?.Invoke(httpRes); + httpRes.EnsureSuccessStatusCode(); + return httpRes.Content.ReadAsStream().ReadToEnd(UseEncoding); + } + + public static Task SendStringToUrlAsync(this string url, + string method = HttpMethods.Post, string? requestBody = null, + string? contentType = null, string accept = "*/*", Action? requestFilter = null, + Action? responseFilter = null, CancellationToken token = default) + { + return Create().SendStringToUrlAsync(url, method:method, requestBody:requestBody, contentType:contentType, accept:accept, + requestFilter:requestFilter, responseFilter:responseFilter, token); + } + + public static async Task SendStringToUrlAsync(this HttpClient client, string url, string method = HttpMethods.Post, + string? requestBody = null, + string? contentType = null, string accept = "*/*", Action? requestFilter = null, + Action? responseFilter = null, CancellationToken token = default) + { + var httpReq = new HttpRequestMessage(new HttpMethod(method), url); + httpReq.Headers.Add(HttpHeaders.Accept, accept); + + if (requestBody != null) + { + httpReq.Content = new StringContent(requestBody, UseEncoding); + if (contentType != null) + httpReq.Content.Headers.ContentType = MediaTypeHeaderValue.Parse(contentType); + } + requestFilter?.Invoke(httpReq); + + var httpRes = await client.SendAsync(httpReq, token).ConfigAwait(); + responseFilter?.Invoke(httpRes); + httpRes.EnsureSuccessStatusCode(); + return await httpRes.Content.ReadAsStringAsync(token).ConfigAwait(); + } + + public static byte[] GetBytesFromUrl(this string url, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null) + { + return url.SendBytesToUrl(method:HttpMethods.Get, accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task GetBytesFromUrlAsync(this string url, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + return url.SendBytesToUrlAsync(method:HttpMethods.Get, accept: accept, requestFilter: requestFilter, responseFilter: responseFilter, + token: token); + } + + public static byte[] PostBytesToUrl(this string url, byte[]? requestBody = null, string? contentType = null, + string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null) + { + return SendBytesToUrl(url, method:HttpMethods.Post, + contentType: contentType, requestBody: requestBody, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PostBytesToUrlAsync(this string url, byte[]? requestBody = null, + string? contentType = null, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + return SendBytesToUrlAsync(url, method:HttpMethods.Post, + contentType: contentType, requestBody: requestBody, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static byte[] PutBytesToUrl(this string url, byte[]? requestBody = null, string? contentType = null, + string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null) + { + return SendBytesToUrl(url, method:HttpMethods.Put, + contentType: contentType, requestBody: requestBody, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PutBytesToUrlAsync(this string url, byte[]? requestBody = null, string? contentType = null, + string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + return SendBytesToUrlAsync(url, method:HttpMethods.Put, + contentType: contentType, requestBody: requestBody, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static byte[] SendBytesToUrl(this string url, string method = HttpMethods.Post, + byte[]? requestBody = null, string? contentType = null, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null) + { + return Create().SendBytesToUrl(url, method:method, requestBody:requestBody, contentType:contentType, accept:accept, + requestFilter:requestFilter, responseFilter:responseFilter); + } + + public static byte[] SendBytesToUrl(this HttpClient client, string url, string method = HttpMethods.Post, + byte[]? requestBody = null, string? contentType = null, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null) + { + var httpReq = new HttpRequestMessage(new HttpMethod(method), url); + httpReq.Headers.Add(HttpHeaders.Accept, accept); + + if (requestBody != null) + { + httpReq.Content = new ReadOnlyMemoryContent(requestBody); + if (contentType != null) + httpReq.Content.Headers.ContentType = MediaTypeHeaderValue.Parse(contentType); + } + requestFilter?.Invoke(httpReq); + + var httpRes = client.Send(httpReq); + responseFilter?.Invoke(httpRes); + httpRes.EnsureSuccessStatusCode(); + return httpRes.Content.ReadAsStream().ReadFully(); + } + + public static Task SendBytesToUrlAsync(this string url, string method = HttpMethods.Post, + byte[]? requestBody = null, string? contentType = null, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + return Create().SendBytesToUrlAsync(url, method, requestBody, contentType, accept, + requestFilter, responseFilter, token); + } + + public static async Task SendBytesToUrlAsync(this HttpClient client, string url, string method = HttpMethods.Post, + byte[]? requestBody = null, string? contentType = null, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + var httpReq = new HttpRequestMessage(new HttpMethod(method), url); + httpReq.Headers.Add(HttpHeaders.Accept, accept); + + if (requestBody != null) + { + httpReq.Content = new ReadOnlyMemoryContent(requestBody); + if (contentType != null) + httpReq.Content.Headers.ContentType = MediaTypeHeaderValue.Parse(contentType); + } + requestFilter?.Invoke(httpReq); + + var httpRes = await client.SendAsync(httpReq, token).ConfigAwait(); + responseFilter?.Invoke(httpRes); + httpRes.EnsureSuccessStatusCode(); + return await (await httpRes.Content.ReadAsStreamAsync(token).ConfigAwait()).ReadFullyAsync(token).ConfigAwait(); + } + + public static Stream GetStreamFromUrl(this string url, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null) + { + return url.SendStreamToUrl(method:HttpMethods.Get, accept: accept, + requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task GetStreamFromUrlAsync(this string url, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + return url.SendStreamToUrlAsync(method:HttpMethods.Get, accept: accept, + requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static Stream PostStreamToUrl(this string url, Stream? requestBody = null, string? contentType = null, + string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null) + { + return SendStreamToUrl(url, method:HttpMethods.Post, + contentType: contentType, requestBody: requestBody, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PostStreamToUrlAsync(this string url, Stream? requestBody = null, + string? contentType = null, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + return SendStreamToUrlAsync(url, method:HttpMethods.Post, + contentType: contentType, requestBody: requestBody, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static Stream PutStreamToUrl(this string url, Stream? requestBody = null, string? contentType = null, + string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null) + { + return SendStreamToUrl(url, method:HttpMethods.Put, + contentType: contentType, requestBody: requestBody, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PutStreamToUrlAsync(this string url, Stream? requestBody = null, + string? contentType = null, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + return SendStreamToUrlAsync(url, method:HttpMethods.Put, + contentType: contentType, requestBody: requestBody, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static Stream SendStreamToUrl(this string url, string method = HttpMethods.Post, + Stream? requestBody = null, string? contentType = null, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null) + { + return Create().SendStreamToUrl(url, method:method, requestBody:requestBody, contentType:contentType, accept:accept, + requestFilter:requestFilter, responseFilter:responseFilter); + } + + public static Stream SendStreamToUrl(this HttpClient client, string url, string method = HttpMethods.Post, + Stream? requestBody = null, string? contentType = null, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null) + { + var httpReq = new HttpRequestMessage(new HttpMethod(method), url); + httpReq.Headers.Add(HttpHeaders.Accept, accept); + + if (requestBody != null) + { + httpReq.Content = new StreamContent(requestBody); + if (contentType != null) + httpReq.Content.Headers.ContentType = MediaTypeHeaderValue.Parse(contentType); + } + requestFilter?.Invoke(httpReq); + + var httpRes = client.Send(httpReq); + responseFilter?.Invoke(httpRes); + httpRes.EnsureSuccessStatusCode(); + return httpRes.Content.ReadAsStream(); + } + + public static Task SendStreamToUrlAsync(this string url, string method = HttpMethods.Post, + Stream? requestBody = null, string? contentType = null, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + return Create().SendStreamToUrlAsync(url, method:method, requestBody:requestBody, contentType:contentType, accept:accept, + requestFilter:requestFilter, responseFilter:responseFilter, token); + } + + public static async Task SendStreamToUrlAsync(this HttpClient client, string url, string method = HttpMethods.Post, + Stream? requestBody = null, string? contentType = null, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + var httpReq = new HttpRequestMessage(new HttpMethod(method), url); + httpReq.Headers.Add(HttpHeaders.Accept, accept); + + if (requestBody != null) + { + httpReq.Content = new StreamContent(requestBody); + if (contentType != null) + httpReq.Content.Headers.ContentType = MediaTypeHeaderValue.Parse(contentType); + } + requestFilter?.Invoke(httpReq); + + var httpRes = await client.SendAsync(httpReq, token).ConfigAwait(); + responseFilter?.Invoke(httpRes); + httpRes.EnsureSuccessStatusCode(); + return await httpRes.Content.ReadAsStreamAsync(token).ConfigAwait(); + } + + public static HttpStatusCode? GetResponseStatus(this string url) + { + try + { + var client = Create(); + var httpReq = new HttpRequestMessage(new HttpMethod(HttpMethods.Get), url); + httpReq.Headers.Add(HttpHeaders.Accept, "*/*"); + var httpRes = client.Send(httpReq); + return httpRes.StatusCode; + } + catch (Exception ex) + { + return ex.GetStatus(); + } + } + + public static HttpResponseMessage? GetErrorResponse(this string url) + { + var client = Create(); + var httpReq = new HttpRequestMessage(new HttpMethod(HttpMethods.Get), url); + httpReq.Headers.Add(HttpHeaders.Accept, "*/*"); + var httpRes = client.Send(httpReq); + return httpRes.IsSuccessStatusCode + ? null + : httpRes; + } + + public static async Task GetErrorResponseAsync(this string url, CancellationToken token=default) + { + var client = Create(); + var httpReq = new HttpRequestMessage(new HttpMethod(HttpMethods.Get), url); + httpReq.Headers.Add(HttpHeaders.Accept, "*/*"); + var httpRes = await client.SendAsync(httpReq, token).ConfigAwait(); + return httpRes.IsSuccessStatusCode + ? null + : httpRes; + } + + public static string ReadToEnd(this HttpResponseMessage webRes) + { + using var stream = webRes.Content.ReadAsStream(); + return stream.ReadToEnd(UseEncoding); + } + + public static Task ReadToEndAsync(this HttpResponseMessage webRes) + { + using var stream = webRes.Content.ReadAsStream(); + return stream.ReadToEndAsync(UseEncoding); + } + + public static IEnumerable ReadLines(this HttpResponseMessage webRes) + { + using var stream = webRes.Content.ReadAsStream(); + using var reader = new StreamReader(stream, UseEncoding, true, 1024, leaveOpen: true); + string? line; + while ((line = reader.ReadLine()) != null) + { + yield return line; + } + } + + public static HttpResponseMessage UploadFile(this HttpRequestMessage httpReq, Stream fileStream, + string fileName, string mimeType, string accept = "*/*", string method = HttpMethods.Post, string field = "file", + Action? requestFilter = null, Action? responseFilter = null) + { + return Create().UploadFile(httpReq, fileStream, fileName, mimeType, accept, method, field, + requestFilter, responseFilter); + } + + + public static HttpResponseMessage UploadFile(this HttpClient client, HttpRequestMessage httpReq, Stream fileStream, + string fileName, string? mimeType = null, string accept = "*/*", string method = HttpMethods.Post, string field = "file", + Action? requestFilter = null, Action? responseFilter = null) + { + if (httpReq.RequestUri == null) + throw new ArgumentException(nameof(httpReq.RequestUri)); + + httpReq.Method = new HttpMethod(method); + httpReq.Headers.Add(HttpHeaders.Accept, accept); + requestFilter?.Invoke(httpReq); + + using var content = new MultipartFormDataContent(); + var fileBytes = fileStream.ReadFully(); + using var fileContent = new ByteArrayContent(fileBytes, 0, fileBytes.Length); + fileContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data") + { + Name = "file", + FileName = fileName + }; + fileContent.Headers.ContentType = MediaTypeHeaderValue.Parse(mimeType ?? MimeTypes.GetMimeType(fileName)); + content.Add(fileContent, "file", fileName); + + var httpRes = client.Send(httpReq); + responseFilter?.Invoke(httpRes); + httpRes.EnsureSuccessStatusCode(); + return httpRes; + } + + public static Task UploadFileAsync(this HttpRequestMessage httpReq, Stream fileStream, + string fileName, + string? mimeType = null, string accept = "*/*", string method = HttpMethods.Post, string field = "file", + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + return Create().UploadFileAsync(httpReq, fileStream, fileName, mimeType, accept, method, field, + requestFilter, responseFilter, token); + } + + public static async Task UploadFileAsync(this HttpClient client, + HttpRequestMessage httpReq, Stream fileStream, string fileName, + string? mimeType = null, string accept = "*/*", string method = HttpMethods.Post, string field = "file", + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + if (httpReq.RequestUri == null) + throw new ArgumentException(nameof(httpReq.RequestUri)); + + httpReq.Method = new HttpMethod(method); + httpReq.Headers.Add(HttpHeaders.Accept, accept); + requestFilter?.Invoke(httpReq); + + using var content = new MultipartFormDataContent(); + var fileBytes = await fileStream.ReadFullyAsync(token).ConfigAwait(); + using var fileContent = new ByteArrayContent(fileBytes, 0, fileBytes.Length); + fileContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data") + { + Name = "file", + FileName = fileName + }; + fileContent.Headers.ContentType = MediaTypeHeaderValue.Parse(mimeType ?? MimeTypes.GetMimeType(fileName)); + content.Add(fileContent, "file", fileName); + + var httpRes = await client.SendAsync(httpReq, token).ConfigAwait(); + responseFilter?.Invoke(httpRes); + httpRes.EnsureSuccessStatusCode(); + return httpRes; + } + + public static void UploadFile(this HttpRequestMessage httpReq, Stream fileStream, string fileName) + { + if (fileName == null) + throw new ArgumentNullException(nameof(fileName)); + var mimeType = MimeTypes.GetMimeType(fileName); + if (mimeType == null) + throw new ArgumentException("Mime-type not found for file: " + fileName); + + UploadFile(httpReq, fileStream, fileName, mimeType); + } + + public static async Task UploadFileAsync(this HttpRequestMessage webRequest, Stream fileStream, string fileName, + CancellationToken token = default) + { + if (fileName == null) + throw new ArgumentNullException(nameof(fileName)); + var mimeType = MimeTypes.GetMimeType(fileName); + if (mimeType == null) + throw new ArgumentException("Mime-type not found for file: " + fileName); + + await UploadFileAsync(webRequest, fileStream, fileName, mimeType, token: token).ConfigAwait(); + } + + public static string PostXmlToUrl(this string url, object data, + Action? requestFilter = null, Action? responseFilter = null) + { + return SendStringToUrl(url, method:HttpMethods.Post, requestBody: data.ToXml(), contentType: MimeTypes.Xml, + accept: MimeTypes.Xml, + requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static string PostCsvToUrl(this string url, object data, + Action? requestFilter = null, Action? responseFilter = null) + { + return SendStringToUrl(url, method:HttpMethods.Post, requestBody: data.ToCsv(), contentType: MimeTypes.Csv, + accept: MimeTypes.Csv, + requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static string PutXmlToUrl(this string url, object data, + Action? requestFilter = null, Action? responseFilter = null) + { + return SendStringToUrl(url, method:HttpMethods.Put, requestBody: data.ToXml(), contentType: MimeTypes.Xml, + accept: MimeTypes.Xml, + requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static string PutCsvToUrl(this string url, object data, + Action? requestFilter = null, Action? responseFilter = null) + { + return SendStringToUrl(url, method:HttpMethods.Put, requestBody: data.ToCsv(), contentType: MimeTypes.Csv, + accept: MimeTypes.Csv, + requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static HttpResponseMessage PostFileToUrl(this string url, + FileInfo uploadFileInfo, string uploadFileMimeType, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null) + { + var webReq = new HttpRequestMessage(HttpMethod.Post, url); + using var fileStream = uploadFileInfo.OpenRead(); + var fileName = uploadFileInfo.Name; + + return webReq.UploadFile(fileStream, fileName, uploadFileMimeType, accept: accept, + method: HttpMethods.Post, requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static async Task PostFileToUrlAsync(this string url, + FileInfo uploadFileInfo, string uploadFileMimeType, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + var webReq = new HttpRequestMessage(HttpMethod.Post, url); + await using var fileStream = uploadFileInfo.OpenRead(); + var fileName = uploadFileInfo.Name; + + return await webReq.UploadFileAsync(fileStream, fileName, uploadFileMimeType, accept: accept, + method: HttpMethods.Post, requestFilter: requestFilter, responseFilter: responseFilter, token: token).ConfigAwait(); + } + + public static HttpResponseMessage PutFileToUrl(this string url, + FileInfo uploadFileInfo, string uploadFileMimeType, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null) + { + var webReq = new HttpRequestMessage(HttpMethod.Put, url); + using var fileStream = uploadFileInfo.OpenRead(); + var fileName = uploadFileInfo.Name; + + return webReq.UploadFile(fileStream, fileName, uploadFileMimeType, accept: accept, + method: HttpMethods.Post, requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static async Task PutFileToUrlAsync(this string url, + FileInfo uploadFileInfo, string uploadFileMimeType, string accept = "*/*", + Action? requestFilter = null, Action? responseFilter = null, + CancellationToken token = default) + { + var webReq = new HttpRequestMessage(HttpMethod.Put, url); + await using var fileStream = uploadFileInfo.OpenRead(); + var fileName = uploadFileInfo.Name; + + return await webReq.UploadFileAsync(fileStream, fileName, uploadFileMimeType, accept: accept, + method: HttpMethods.Post, requestFilter: requestFilter, responseFilter: responseFilter, token: token).ConfigAwait(); + } + + public static void AddHeader(this HttpRequestMessage res, string name, string value) => + res.WithHeader(name, value); + + /// + /// Returns first Request Header in HttpRequestMessage Headers and Content.Headers + /// + public static string? GetHeader(this HttpRequestMessage req, string name) + { + if (RequestHeadersResolver.TryGetValue(name, out var fn)) + return fn(req); + + return req.Headers.TryGetValues(name, out var headers) + ? headers.FirstOrDefault() + : req.Content?.Headers.TryGetValues(name, out var contentHeaders) == true + ? contentHeaders!.FirstOrDefault() + : null; + } + + public static Dictionary> RequestHeadersResolver { get; set; } = new(StringComparer.OrdinalIgnoreCase) { + }; + public static Dictionary> ResponseHeadersResolver { get; set; } = new(StringComparer.OrdinalIgnoreCase) { + }; + + /// + /// Returns first Response Header in HttpResponseMessage Headers and Content.Headers + /// + public static string? GetHeader(this HttpResponseMessage res, string name) + { + if (ResponseHeadersResolver.TryGetValue(name, out var fn)) + return fn(res); + + return res.Headers.TryGetValues(name, out var headers) + ? headers.FirstOrDefault() + : res.Content?.Headers.TryGetValues(name, out var contentHeaders) == true + ? contentHeaders!.FirstOrDefault() + : null; + } + + public static HttpRequestMessage WithHeader(this HttpRequestMessage httpReq, string name, string value) + { + if (name.Equals(HttpHeaders.Authorization, StringComparison.OrdinalIgnoreCase)) + { + httpReq.Headers.Authorization = + new AuthenticationHeaderValue(value.LeftPart(' '), value.RightPart(' ')); + } + else if (name.Equals(HttpHeaders.ContentType, StringComparison.OrdinalIgnoreCase)) + { + if (httpReq.Content == null) + throw new NotSupportedException("Can't set ContentType before Content is populated"); + httpReq.Content.Headers.ContentType = MediaTypeHeaderValue.Parse(value); + } + else if (name.Equals(HttpHeaders.Referer, StringComparison.OrdinalIgnoreCase)) + { + httpReq.Headers.Referrer = new Uri(value); + } + else if (name.Equals(HttpHeaders.UserAgent, StringComparison.OrdinalIgnoreCase)) + { + httpReq.Headers.UserAgent.ParseAdd(value); + } + else + { + httpReq.Headers.Add(name, value); + } + return httpReq; + } + + /// + /// Populate HttpRequestMessage with a simpler, untyped API + /// Syntax compatible with HttpWebRequest + /// + public static HttpRequestMessage With(this HttpRequestMessage httpReq, Action configure) + { + var config = new HttpRequestConfig(); + configure(config); + + var headers = config.Headers; + + if (config.Accept != null) + { + httpReq.Headers.Accept.Clear(); //override or consistent behavior + httpReq.Headers.Accept.Add(new(config.Accept)); + } + if (config.UserAgent != null) + headers.Add(new(HttpHeaders.UserAgent, config.UserAgent)); + if (config.ContentType != null) + { + if (httpReq.Content == null) + throw new NotSupportedException("Can't set ContentType before Content is populated"); + httpReq.Content.Headers.ContentType = MediaTypeHeaderValue.Parse(config.ContentType); + } + if (config.Referer != null) + httpReq.Headers.Referrer = new Uri(config.Referer); + if (config.Authorization != null) + httpReq.Headers.Authorization = + new AuthenticationHeaderValue(config.Authorization.Name, config.Authorization.Value); + if (config.Range != null) + httpReq.Headers.Range = new RangeHeaderValue(config.Range.From, config.Range.To); + if (config.Expect != null) + httpReq.Headers.Expect.Add(new(config.Expect)); + + if (config.TransferEncodingChunked != null) + httpReq.Headers.TransferEncodingChunked = config.TransferEncodingChunked.Value; + else if (config.TransferEncoding?.Length > 0) + { + foreach (var enc in config.TransferEncoding) + { + httpReq.Headers.TransferEncoding.Add(new(enc)); + } + } + + foreach (var entry in headers) + { + httpReq.WithHeader(entry.Name, entry.Value); + } + + return httpReq; + } + + public static void DownloadFileTo(this string downloadUrl, string fileName, + List? headers = null) + { + var client = Create(); + var httpReq = new HttpRequestMessage(HttpMethod.Get, downloadUrl) + .With(c => { + c.Accept = "*/*"; + if (headers != null) c.Headers = headers; + }); + + var httpRes = client.Send(httpReq); + httpRes.EnsureSuccessStatusCode(); + + using var fs = new FileStream(fileName, FileMode.CreateNew); + var bytes = httpRes.Content.ReadAsStream().ReadFully(); + fs.Write(bytes); + } +} + +public static class HttpClientExt +{ + /// + /// Case-insensitive, trimmed compare of two content types from start to ';', i.e. without charset suffix + /// + public static bool MatchesContentType(this HttpResponseMessage res, string matchesContentType) => + MimeTypes.MatchesContentType(res.GetHeader(HttpHeaders.ContentType), matchesContentType); + + public static long? GetContentLength(this HttpResponseMessage res) => + res.Content.Headers.ContentLength; +} + +#endif diff --git a/src/ServiceStack.Text/HttpUtils.WebRequest.cs b/src/ServiceStack.Text/HttpUtils.WebRequest.cs new file mode 100644 index 000000000..0f776e07b --- /dev/null +++ b/src/ServiceStack.Text/HttpUtils.WebRequest.cs @@ -0,0 +1,1246 @@ +#if !NET6_0_OR_GREATER +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using ServiceStack.Text; + +namespace ServiceStack; + +public static partial class HttpUtils +{ + [ThreadStatic] + public static IHttpResultsFilter ResultsFilter; + + public static string GetJsonFromUrl(this string url, + Action requestFilter = null, Action responseFilter = null) + { + return url.GetStringFromUrl(MimeTypes.Json, requestFilter, responseFilter); + } + + public static Task GetJsonFromUrlAsync(this string url, + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + return url.GetStringFromUrlAsync(MimeTypes.Json, requestFilter, responseFilter, token: token); + } + + public static string GetXmlFromUrl(this string url, + Action requestFilter = null, Action responseFilter = null) + { + return url.GetStringFromUrl(MimeTypes.Xml, requestFilter, responseFilter); + } + + public static Task GetXmlFromUrlAsync(this string url, + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + return url.GetStringFromUrlAsync(MimeTypes.Xml, requestFilter, responseFilter, token: token); + } + + public static string GetCsvFromUrl(this string url, + Action requestFilter = null, Action responseFilter = null) + { + return url.GetStringFromUrl(MimeTypes.Csv, requestFilter, responseFilter); + } + + public static Task GetCsvFromUrlAsync(this string url, + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + return url.GetStringFromUrlAsync(MimeTypes.Csv, requestFilter, responseFilter, token: token); + } + + public static string GetStringFromUrl(this string url, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null) + { + return SendStringToUrl(url, accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task GetStringFromUrlAsync(this string url, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, accept: accept, requestFilter: requestFilter, responseFilter: responseFilter, + token: token); + } + + public static string PostStringToUrl(this string url, string requestBody = null, + string contentType = null, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null) + { + return SendStringToUrl(url, method: "POST", + requestBody: requestBody, contentType: contentType, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PostStringToUrlAsync(this string url, string requestBody = null, + string contentType = null, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method: "POST", + requestBody: requestBody, contentType: contentType, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PostToUrl(this string url, string formData = null, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null) + { + return SendStringToUrl(url, method: "POST", + contentType: MimeTypes.FormUrlEncoded, requestBody: formData, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PostToUrlAsync(this string url, string formData = null, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method: "POST", + contentType: MimeTypes.FormUrlEncoded, requestBody: formData, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PostToUrl(this string url, object formData = null, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null) + { + string postFormData = formData != null ? QueryStringSerializer.SerializeToString(formData) : null; + + return SendStringToUrl(url, method: "POST", + contentType: MimeTypes.FormUrlEncoded, requestBody: postFormData, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PostToUrlAsync(this string url, object formData = null, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + string postFormData = formData != null ? QueryStringSerializer.SerializeToString(formData) : null; + + return SendStringToUrlAsync(url, method: "POST", + contentType: MimeTypes.FormUrlEncoded, requestBody: postFormData, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PostJsonToUrl(this string url, string json, + Action requestFilter = null, Action responseFilter = null) + { + return SendStringToUrl(url, method: "POST", requestBody: json, contentType: MimeTypes.Json, + accept: MimeTypes.Json, + requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PostJsonToUrlAsync(this string url, string json, + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method: "POST", requestBody: json, contentType: MimeTypes.Json, + accept: MimeTypes.Json, + requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PostJsonToUrl(this string url, object data, + Action requestFilter = null, Action responseFilter = null) + { + return SendStringToUrl(url, method: "POST", requestBody: data.ToJson(), contentType: MimeTypes.Json, + accept: MimeTypes.Json, + requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PostJsonToUrlAsync(this string url, object data, + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method: "POST", requestBody: data.ToJson(), contentType: MimeTypes.Json, + accept: MimeTypes.Json, + requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PostXmlToUrl(this string url, string xml, + Action requestFilter = null, Action responseFilter = null) + { + return SendStringToUrl(url, method: "POST", requestBody: xml, contentType: MimeTypes.Xml, accept: MimeTypes.Xml, + requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PostXmlToUrlAsync(this string url, string xml, + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method: "POST", requestBody: xml, contentType: MimeTypes.Xml, + accept: MimeTypes.Xml, + requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PostCsvToUrl(this string url, string csv, + Action requestFilter = null, Action responseFilter = null) + { + return SendStringToUrl(url, method: "POST", requestBody: csv, contentType: MimeTypes.Csv, accept: MimeTypes.Csv, + requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PostCsvToUrlAsync(this string url, string csv, + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method: "POST", requestBody: csv, contentType: MimeTypes.Csv, + accept: MimeTypes.Csv, + requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PutStringToUrl(this string url, string requestBody = null, + string contentType = null, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null) + { + return SendStringToUrl(url, method: "PUT", + requestBody: requestBody, contentType: contentType, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PutStringToUrlAsync(this string url, string requestBody = null, + string contentType = null, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method: "PUT", + requestBody: requestBody, contentType: contentType, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PutToUrl(this string url, string formData = null, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null) + { + return SendStringToUrl(url, method: "PUT", + contentType: MimeTypes.FormUrlEncoded, requestBody: formData, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PutToUrlAsync(this string url, string formData = null, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method: "PUT", + contentType: MimeTypes.FormUrlEncoded, requestBody: formData, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PutToUrl(this string url, object formData = null, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null) + { + string postFormData = formData != null ? QueryStringSerializer.SerializeToString(formData) : null; + + return SendStringToUrl(url, method: "PUT", + contentType: MimeTypes.FormUrlEncoded, requestBody: postFormData, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PutToUrlAsync(this string url, object formData = null, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + string postFormData = formData != null ? QueryStringSerializer.SerializeToString(formData) : null; + + return SendStringToUrlAsync(url, method: "PUT", + contentType: MimeTypes.FormUrlEncoded, requestBody: postFormData, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PutJsonToUrl(this string url, string json, + Action requestFilter = null, Action responseFilter = null) + { + return SendStringToUrl(url, method: "PUT", requestBody: json, contentType: MimeTypes.Json, + accept: MimeTypes.Json, + requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PutJsonToUrlAsync(this string url, string json, + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method: "PUT", requestBody: json, contentType: MimeTypes.Json, + accept: MimeTypes.Json, + requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PutJsonToUrl(this string url, object data, + Action requestFilter = null, Action responseFilter = null) + { + return SendStringToUrl(url, method: "PUT", requestBody: data.ToJson(), contentType: MimeTypes.Json, + accept: MimeTypes.Json, + requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PutJsonToUrlAsync(this string url, object data, + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method: "PUT", requestBody: data.ToJson(), contentType: MimeTypes.Json, + accept: MimeTypes.Json, + requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PutXmlToUrl(this string url, string xml, + Action requestFilter = null, Action responseFilter = null) + { + return SendStringToUrl(url, method: "PUT", requestBody: xml, contentType: MimeTypes.Xml, accept: MimeTypes.Xml, + requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PutXmlToUrlAsync(this string url, string xml, + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method: "PUT", requestBody: xml, contentType: MimeTypes.Xml, + accept: MimeTypes.Xml, + requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PutCsvToUrl(this string url, string csv, + Action requestFilter = null, Action responseFilter = null) + { + return SendStringToUrl(url, method: "PUT", requestBody: csv, contentType: MimeTypes.Csv, accept: MimeTypes.Csv, + requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PutCsvToUrlAsync(this string url, string csv, + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method: "PUT", requestBody: csv, contentType: MimeTypes.Csv, + accept: MimeTypes.Csv, + requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PatchStringToUrl(this string url, string requestBody = null, + string contentType = null, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null) + { + return SendStringToUrl(url, method: "PATCH", + requestBody: requestBody, contentType: contentType, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PatchStringToUrlAsync(this string url, string requestBody = null, + string contentType = null, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method: "PATCH", + requestBody: requestBody, contentType: contentType, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PatchToUrl(this string url, string formData = null, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null) + { + return SendStringToUrl(url, method: "PATCH", + contentType: MimeTypes.FormUrlEncoded, requestBody: formData, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PatchToUrlAsync(this string url, string formData = null, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method: "PATCH", + contentType: MimeTypes.FormUrlEncoded, requestBody: formData, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PatchToUrl(this string url, object formData = null, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null) + { + string postFormData = formData != null ? QueryStringSerializer.SerializeToString(formData) : null; + + return SendStringToUrl(url, method: "PATCH", + contentType: MimeTypes.FormUrlEncoded, requestBody: postFormData, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PatchToUrlAsync(this string url, object formData = null, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + string postFormData = formData != null ? QueryStringSerializer.SerializeToString(formData) : null; + + return SendStringToUrlAsync(url, method: "PATCH", + contentType: MimeTypes.FormUrlEncoded, requestBody: postFormData, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PatchJsonToUrl(this string url, string json, + Action requestFilter = null, Action responseFilter = null) + { + return SendStringToUrl(url, method: "PATCH", requestBody: json, contentType: MimeTypes.Json, + accept: MimeTypes.Json, + requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PatchJsonToUrlAsync(this string url, string json, + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method: "PATCH", requestBody: json, contentType: MimeTypes.Json, + accept: MimeTypes.Json, + requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string PatchJsonToUrl(this string url, object data, + Action requestFilter = null, Action responseFilter = null) + { + return SendStringToUrl(url, method: "PATCH", requestBody: data.ToJson(), contentType: MimeTypes.Json, + accept: MimeTypes.Json, + requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PatchJsonToUrlAsync(this string url, object data, + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method: "PATCH", requestBody: data.ToJson(), contentType: MimeTypes.Json, + accept: MimeTypes.Json, + requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static string DeleteFromUrl(this string url, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null) + { + return SendStringToUrl(url, method: "DELETE", accept: accept, requestFilter: requestFilter, + responseFilter: responseFilter); + } + + public static Task DeleteFromUrlAsync(this string url, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method: "DELETE", accept: accept, requestFilter: requestFilter, + responseFilter: responseFilter, token: token); + } + + public static string OptionsFromUrl(this string url, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null) + { + return SendStringToUrl(url, method: "OPTIONS", accept: accept, requestFilter: requestFilter, + responseFilter: responseFilter); + } + + public static Task OptionsFromUrlAsync(this string url, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method: "OPTIONS", accept: accept, requestFilter: requestFilter, + responseFilter: responseFilter, token: token); + } + + public static string HeadFromUrl(this string url, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null) + { + return SendStringToUrl(url, method: "HEAD", accept: accept, requestFilter: requestFilter, + responseFilter: responseFilter); + } + + public static Task HeadFromUrlAsync(this string url, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + return SendStringToUrlAsync(url, method: "HEAD", accept: accept, requestFilter: requestFilter, + responseFilter: responseFilter, token: token); + } + + public static string SendStringToUrl(this string url, string method = null, + string requestBody = null, string contentType = null, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null) + { + var webReq = WebRequest.CreateHttp(url); + return SendStringToUrl(webReq, method, requestBody, contentType, accept, requestFilter, responseFilter); + } + + public static async Task SendStringToUrlAsync(this string url, string method = null, + string requestBody = null, + string contentType = null, string accept = "*/*", Action requestFilter = null, + Action responseFilter = null, CancellationToken token = default) + { + var webReq = WebRequest.CreateHttp(url); + return await SendStringToUrlAsync(webReq, method, requestBody, contentType, accept, requestFilter, responseFilter); + } + + public static string SendStringToUrl(this HttpWebRequest webReq, string method, string requestBody, string contentType, + string accept, Action requestFilter, Action responseFilter) + { + if (method != null) + webReq.Method = method; + if (contentType != null) + webReq.ContentType = contentType; + + webReq.Accept = accept; + PclExport.Instance.AddCompression(webReq); + + requestFilter?.Invoke(webReq); + + if (ResultsFilter != null) + { + return ResultsFilter.GetString(webReq, requestBody); + } + + if (requestBody != null) + { + using var reqStream = PclExport.Instance.GetRequestStream(webReq); + using var writer = new StreamWriter(reqStream, UseEncoding); + writer.Write(requestBody); + } + else if (method != null && HasRequestBody(method)) + { + webReq.ContentLength = 0; + } + + using var webRes = webReq.GetResponse(); + using var stream = webRes.GetResponseStream(); + responseFilter?.Invoke((HttpWebResponse)webRes); + return stream.ReadToEnd(UseEncoding); + } + + public static async Task SendStringToUrlAsync(this HttpWebRequest webReq, + string method, string requestBody, string contentType, string accept, + Action requestFilter, Action responseFilter) + { + if (method != null) + webReq.Method = method; + if (contentType != null) + webReq.ContentType = contentType; + + webReq.Accept = accept; + PclExport.Instance.AddCompression(webReq); + + requestFilter?.Invoke(webReq); + + if (ResultsFilter != null) + { + var result = ResultsFilter.GetString(webReq, requestBody); + return result; + } + + if (requestBody != null) + { + using var reqStream = PclExport.Instance.GetRequestStream(webReq); + using var writer = new StreamWriter(reqStream, UseEncoding); + await writer.WriteAsync(requestBody).ConfigAwait(); + } + else if (method != null && HasRequestBody(method)) + { + webReq.ContentLength = 0; + } + + using var webRes = await webReq.GetResponseAsync().ConfigAwait(); + responseFilter?.Invoke((HttpWebResponse)webRes); + using var stream = webRes.GetResponseStream(); + return await stream.ReadToEndAsync().ConfigAwait(); + } + + public static byte[] GetBytesFromUrl(this string url, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null) + { + return url.SendBytesToUrl(accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task GetBytesFromUrlAsync(this string url, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + return url.SendBytesToUrlAsync(accept: accept, requestFilter: requestFilter, responseFilter: responseFilter, + token: token); + } + + public static byte[] PostBytesToUrl(this string url, byte[] requestBody = null, string contentType = null, + string accept = "*/*", + Action requestFilter = null, Action responseFilter = null) + { + return SendBytesToUrl(url, method: "POST", + contentType: contentType, requestBody: requestBody, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PostBytesToUrlAsync(this string url, byte[] requestBody = null, + string contentType = null, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + return SendBytesToUrlAsync(url, method: "POST", + contentType: contentType, requestBody: requestBody, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static byte[] PutBytesToUrl(this string url, byte[] requestBody = null, string contentType = null, + string accept = "*/*", + Action requestFilter = null, Action responseFilter = null) + { + return SendBytesToUrl(url, method: "PUT", + contentType: contentType, requestBody: requestBody, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PutBytesToUrlAsync(this string url, byte[] requestBody = null, string contentType = null, + string accept = "*/*", + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + return SendBytesToUrlAsync(url, method: "PUT", + contentType: contentType, requestBody: requestBody, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static byte[] SendBytesToUrl(this string url, string method = null, + byte[] requestBody = null, string contentType = null, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null) + { + var webReq = WebRequest.CreateHttp(url); + return SendBytesToUrl(webReq, method, requestBody, contentType, accept, requestFilter, responseFilter); + } + + public static async Task SendBytesToUrlAsync(this string url, string method = null, + byte[] requestBody = null, string contentType = null, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + var webReq = WebRequest.CreateHttp(url); + return await SendBytesToUrlAsync(webReq, method, requestBody, contentType, accept, requestFilter, responseFilter, token); + } + + public static byte[] SendBytesToUrl(this HttpWebRequest webReq, string method, byte[] requestBody, string contentType, + string accept, Action requestFilter, Action responseFilter) + { + if (method != null) + webReq.Method = method; + + if (contentType != null) + webReq.ContentType = contentType; + + webReq.Accept = accept; + PclExport.Instance.AddCompression(webReq); + + requestFilter?.Invoke(webReq); + + if (ResultsFilter != null) + { + return ResultsFilter.GetBytes(webReq, requestBody); + } + + if (requestBody != null) + { + using var req = PclExport.Instance.GetRequestStream(webReq); + req.Write(requestBody, 0, requestBody.Length); + } + + using var webRes = PclExport.Instance.GetResponse(webReq); + responseFilter?.Invoke((HttpWebResponse)webRes); + + using var stream = webRes.GetResponseStream(); + return stream.ReadFully(); + } + + public static async Task SendBytesToUrlAsync(this HttpWebRequest webReq, string method, byte[] requestBody, + string contentType, string accept, Action requestFilter, Action responseFilter, CancellationToken token) + { + if (method != null) + webReq.Method = method; + if (contentType != null) + webReq.ContentType = contentType; + + webReq.Accept = accept; + PclExport.Instance.AddCompression(webReq); + + requestFilter?.Invoke(webReq); + + if (ResultsFilter != null) + { + var result = ResultsFilter.GetBytes(webReq, requestBody); + return result; + } + + if (requestBody != null) + { + using var req = PclExport.Instance.GetRequestStream(webReq); + await req.WriteAsync(requestBody, 0, requestBody.Length, token).ConfigAwait(); + } + + var webRes = await webReq.GetResponseAsync().ConfigAwait(); + responseFilter?.Invoke((HttpWebResponse)webRes); + + using var stream = webRes.GetResponseStream(); + return await stream.ReadFullyAsync(token).ConfigAwait(); + } + + public static Stream GetStreamFromUrl(this string url, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null) + { + return url.SendStreamToUrl(accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task GetStreamFromUrlAsync(this string url, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + return url.SendStreamToUrlAsync(accept: accept, requestFilter: requestFilter, responseFilter: responseFilter, + token: token); + } + + public static Stream PostStreamToUrl(this string url, Stream requestBody = null, string contentType = null, + string accept = "*/*", + Action requestFilter = null, Action responseFilter = null) + { + return SendStreamToUrl(url, method: "POST", + contentType: contentType, requestBody: requestBody, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PostStreamToUrlAsync(this string url, Stream requestBody = null, + string contentType = null, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + return SendStreamToUrlAsync(url, method: "POST", + contentType: contentType, requestBody: requestBody, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + public static Stream PutStreamToUrl(this string url, Stream requestBody = null, string contentType = null, + string accept = "*/*", + Action requestFilter = null, Action responseFilter = null) + { + return SendStreamToUrl(url, method: "PUT", + contentType: contentType, requestBody: requestBody, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static Task PutStreamToUrlAsync(this string url, Stream requestBody = null, + string contentType = null, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + return SendStreamToUrlAsync(url, method: "PUT", + contentType: contentType, requestBody: requestBody, + accept: accept, requestFilter: requestFilter, responseFilter: responseFilter, token: token); + } + + /// + /// Returns HttpWebResponse Stream which must be disposed + /// + public static Stream SendStreamToUrl(this string url, string method = null, + Stream requestBody = null, string contentType = null, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null) + { + var webReq = WebRequest.CreateHttp(url); + if (method != null) + webReq.Method = method; + + if (contentType != null) + webReq.ContentType = contentType; + + webReq.Accept = accept; + PclExport.Instance.AddCompression(webReq); + + requestFilter?.Invoke(webReq); + + if (ResultsFilter != null) + { + return new MemoryStream(ResultsFilter.GetBytes(webReq, requestBody.ReadFully())); + } + + if (requestBody != null) + { + using var req = PclExport.Instance.GetRequestStream(webReq); + requestBody.CopyTo(req); + } + + var webRes = PclExport.Instance.GetResponse(webReq); + responseFilter?.Invoke((HttpWebResponse)webRes); + + var stream = webRes.GetResponseStream(); + return stream; + } + + /// + /// Returns HttpWebResponse Stream which must be disposed + /// + public static async Task SendStreamToUrlAsync(this string url, string method = null, + Stream requestBody = null, string contentType = null, string accept = "*/*", + Action requestFilter = null, Action responseFilter = null, + CancellationToken token = default) + { + var webReq = WebRequest.CreateHttp(url); + if (method != null) + webReq.Method = method; + if (contentType != null) + webReq.ContentType = contentType; + + webReq.Accept = accept; + PclExport.Instance.AddCompression(webReq); + + requestFilter?.Invoke(webReq); + + if (ResultsFilter != null) + { + return new MemoryStream(ResultsFilter.GetBytes(webReq, + await requestBody.ReadFullyAsync(token).ConfigAwait())); + } + + if (requestBody != null) + { + using var req = PclExport.Instance.GetRequestStream(webReq); + await requestBody.CopyToAsync(req, token).ConfigAwait(); + } + + var webRes = await webReq.GetResponseAsync().ConfigAwait(); + responseFilter?.Invoke((HttpWebResponse)webRes); + + var stream = webRes.GetResponseStream(); + return stream; + } + + public static HttpStatusCode? GetResponseStatus(this string url) + { + try + { + var webReq = WebRequest.CreateHttp(url); + using var webRes = PclExport.Instance.GetResponse(webReq); + var httpRes = webRes as HttpWebResponse; + return httpRes?.StatusCode; + } + catch (Exception ex) + { + return ex.GetStatus(); + } + } + + public static HttpWebResponse GetErrorResponse(this string url) + { + try + { + var webReq = WebRequest.Create(url); + using var webRes = PclExport.Instance.GetResponse(webReq); + webRes.ReadToEnd(); + return null; + } + catch (WebException webEx) + { + return (HttpWebResponse)webEx.Response; + } + } + + public static async Task GetErrorResponseAsync(this string url) + { + try + { + var webReq = WebRequest.Create(url); + using var webRes = await webReq.GetResponseAsync().ConfigAwait(); + await webRes.ReadToEndAsync().ConfigAwait(); + return null; + } + catch (WebException webEx) + { + return (HttpWebResponse)webEx.Response; + } + } + + public static void UploadFile(this WebRequest webRequest, Stream fileStream, string fileName, string mimeType, + string accept = null, Action requestFilter = null, string method = "POST", + string field = "file") + { + var httpReq = (HttpWebRequest)webRequest; + httpReq.Method = method; + + if (accept != null) + httpReq.Accept = accept; + + requestFilter?.Invoke(httpReq); + + var boundary = Guid.NewGuid().ToString("N"); + + httpReq.ContentType = "multipart/form-data; boundary=\"" + boundary + "\""; + + var boundaryBytes = ("\r\n--" + boundary + "--\r\n").ToAsciiBytes(); + + var headerBytes = GetHeaderBytes(fileName, mimeType, field, boundary); + + var contentLength = fileStream.Length + headerBytes.Length + boundaryBytes.Length; + PclExport.Instance.InitHttpWebRequest(httpReq, + contentLength: contentLength, allowAutoRedirect: false, keepAlive: false); + + if (ResultsFilter != null) + { + ResultsFilter.UploadStream(httpReq, fileStream, fileName); + return; + } + + using var outputStream = PclExport.Instance.GetRequestStream(httpReq); + outputStream.Write(headerBytes, 0, headerBytes.Length); + fileStream.CopyTo(outputStream, 4096); + outputStream.Write(boundaryBytes, 0, boundaryBytes.Length); + PclExport.Instance.CloseStream(outputStream); + } + + public static async Task UploadFileAsync(this WebRequest webRequest, Stream fileStream, string fileName, + string mimeType, + string accept = null, Action requestFilter = null, string method = "POST", + string field = "file", + CancellationToken token = default) + { + var httpReq = (HttpWebRequest)webRequest; + httpReq.Method = method; + + if (accept != null) + httpReq.Accept = accept; + + requestFilter?.Invoke(httpReq); + + var boundary = Guid.NewGuid().ToString("N"); + + httpReq.ContentType = "multipart/form-data; boundary=\"" + boundary + "\""; + + var boundaryBytes = ("\r\n--" + boundary + "--\r\n").ToAsciiBytes(); + + var headerBytes = GetHeaderBytes(fileName, mimeType, field, boundary); + + var contentLength = fileStream.Length + headerBytes.Length + boundaryBytes.Length; + PclExport.Instance.InitHttpWebRequest(httpReq, + contentLength: contentLength, allowAutoRedirect: false, keepAlive: false); + + if (ResultsFilter != null) + { + ResultsFilter.UploadStream(httpReq, fileStream, fileName); + return; + } + + using var outputStream = PclExport.Instance.GetRequestStream(httpReq); + await outputStream.WriteAsync(headerBytes, 0, headerBytes.Length, token).ConfigAwait(); + await fileStream.CopyToAsync(outputStream, 4096, token).ConfigAwait(); + await outputStream.WriteAsync(boundaryBytes, 0, boundaryBytes.Length, token).ConfigAwait(); + PclExport.Instance.CloseStream(outputStream); + } + + public static void UploadFile(this WebRequest webRequest, Stream fileStream, string fileName) + { + if (fileName == null) + throw new ArgumentNullException(nameof(fileName)); + var mimeType = MimeTypes.GetMimeType(fileName); + if (mimeType == null) + throw new ArgumentException("Mime-type not found for file: " + fileName); + + UploadFile(webRequest, fileStream, fileName, mimeType); + } + + public static async Task UploadFileAsync(this WebRequest webRequest, Stream fileStream, string fileName, + CancellationToken token = default) + { + if (fileName == null) + throw new ArgumentNullException(nameof(fileName)); + var mimeType = MimeTypes.GetMimeType(fileName); + if (mimeType == null) + throw new ArgumentException("Mime-type not found for file: " + fileName); + + await UploadFileAsync(webRequest, fileStream, fileName, mimeType, token: token).ConfigAwait(); + } + + public static string PostXmlToUrl(this string url, object data, + Action requestFilter = null, Action responseFilter = null) + { + return SendStringToUrl(url, method: "POST", requestBody: data.ToXml(), contentType: MimeTypes.Xml, + accept: MimeTypes.Xml, + requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static string PostCsvToUrl(this string url, object data, + Action requestFilter = null, Action responseFilter = null) + { + return SendStringToUrl(url, method: "POST", requestBody: data.ToCsv(), contentType: MimeTypes.Csv, + accept: MimeTypes.Csv, + requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static string PutXmlToUrl(this string url, object data, + Action requestFilter = null, Action responseFilter = null) + { + return SendStringToUrl(url, method: "PUT", requestBody: data.ToXml(), contentType: MimeTypes.Xml, + accept: MimeTypes.Xml, + requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static string PutCsvToUrl(this string url, object data, + Action requestFilter = null, Action responseFilter = null) + { + return SendStringToUrl(url, method: "PUT", requestBody: data.ToCsv(), contentType: MimeTypes.Csv, + accept: MimeTypes.Csv, + requestFilter: requestFilter, responseFilter: responseFilter); + } + + public static WebResponse PostFileToUrl(this string url, + FileInfo uploadFileInfo, string uploadFileMimeType, + string accept = null, + Action requestFilter = null) + { + var webReq = WebRequest.CreateHttp(url); + using (var fileStream = uploadFileInfo.OpenRead()) + { + var fileName = uploadFileInfo.Name; + + webReq.UploadFile(fileStream, fileName, uploadFileMimeType, accept: accept, requestFilter: requestFilter, + method: "POST"); + } + + if (ResultsFilter != null) + return null; + + return webReq.GetResponse(); + } + + public static async Task PostFileToUrlAsync(this string url, + FileInfo uploadFileInfo, string uploadFileMimeType, + string accept = null, + Action requestFilter = null, CancellationToken token = default) + { + var webReq = WebRequest.CreateHttp(url); + using (var fileStream = uploadFileInfo.OpenRead()) + { + var fileName = uploadFileInfo.Name; + + await webReq.UploadFileAsync(fileStream, fileName, uploadFileMimeType, accept: accept, + requestFilter: requestFilter, method: "POST", token: token).ConfigAwait(); + } + + if (ResultsFilter != null) + return null; + + return await webReq.GetResponseAsync().ConfigAwait(); + } + + public static WebResponse PutFileToUrl(this string url, + FileInfo uploadFileInfo, string uploadFileMimeType, + string accept = null, + Action requestFilter = null) + { + var webReq = WebRequest.CreateHttp(url); + using (var fileStream = uploadFileInfo.OpenRead()) + { + var fileName = uploadFileInfo.Name; + + webReq.UploadFile(fileStream, fileName, uploadFileMimeType, accept: accept, requestFilter: requestFilter, + method: "PUT"); + } + + if (ResultsFilter != null) + return null; + + return webReq.GetResponse(); + } + + public static async Task PutFileToUrlAsync(this string url, + FileInfo uploadFileInfo, string uploadFileMimeType, + string accept = null, + Action requestFilter = null, CancellationToken token = default) + { + var webReq = WebRequest.CreateHttp(url); + using (var fileStream = uploadFileInfo.OpenRead()) + { + var fileName = uploadFileInfo.Name; + + await webReq.UploadFileAsync(fileStream, fileName, uploadFileMimeType, accept: accept, + requestFilter: requestFilter, method: "PUT", token: token).ConfigAwait(); + } + + if (ResultsFilter != null) + return null; + + return await webReq.GetResponseAsync().ConfigAwait(); + } + + public static WebResponse UploadFile(this WebRequest webRequest, + FileInfo uploadFileInfo, string uploadFileMimeType) + { + using (var fileStream = uploadFileInfo.OpenRead()) + { + var fileName = uploadFileInfo.Name; + + webRequest.UploadFile(fileStream, fileName, uploadFileMimeType); + } + + if (ResultsFilter != null) + return null; + + return webRequest.GetResponse(); + } + + public static async Task UploadFileAsync(this WebRequest webRequest, + FileInfo uploadFileInfo, string uploadFileMimeType) + { + using (var fileStream = uploadFileInfo.OpenRead()) + { + var fileName = uploadFileInfo.Name; + + await webRequest.UploadFileAsync(fileStream, fileName, uploadFileMimeType).ConfigAwait(); + } + + if (ResultsFilter != null) + return null; + + return await webRequest.GetResponseAsync().ConfigAwait(); + } + + private static byte[] GetHeaderBytes(string fileName, string mimeType, string field, string boundary) + { + var header = "\r\n--" + boundary + + $"\r\nContent-Disposition: form-data; name=\"{field}\"; filename=\"{fileName}\"\r\nContent-Type: {mimeType}\r\n\r\n"; + + var headerBytes = header.ToAsciiBytes(); + return headerBytes; + } + + public static void DownloadFileTo(this string downloadUrl, string fileName, + List headers = null) + { + var webClient = new WebClient(); + if (headers != null) + { + foreach (var header in headers) + { + webClient.Headers[header.Name] = header.Value; + } + } + webClient.DownloadFile(downloadUrl, fileName); + } + + public static void SetRange(this HttpWebRequest request, long from, long? to) + { + if (to != null) + request.AddRange(from, to.Value); + else + request.AddRange(from); + } + + public static void AddHeader(this HttpWebRequest res, string name, string value) => + res.Headers[name] = value; + public static string GetHeader(this HttpWebRequest res, string name) => + res.Headers.Get(name); + public static string GetHeader(this HttpWebResponse res, string name) => + res.Headers.Get(name); + + public static HttpWebRequest WithHeader(this HttpWebRequest httpReq, string name, string value) + { + httpReq.Headers[name] = value; + return httpReq; + } + + /// + /// Populate HttpRequestMessage with a simpler, untyped API + /// Syntax compatible with HttpClient's HttpRequestMessage + /// + public static HttpWebRequest With(this HttpWebRequest httpReq, Action configure) + { + var config = new HttpRequestConfig(); + configure(config); + + if (config.Accept != null) + httpReq.Accept = config.Accept; + + if (config.UserAgent != null) + httpReq.UserAgent = config.UserAgent; + + if (config.ContentType != null) + httpReq.ContentType = config.ContentType; + + if (config.Referer != null) + httpReq.Referer = config.Referer; + + if (config.Authorization != null) + httpReq.Headers[HttpHeaders.Authorization] = + config.Authorization.Name + " " + config.Authorization.Value; + + if (config.Range != null) + httpReq.SetRange(config.Range.From, config.Range.To); + + if (config.Expect != null) + httpReq.Expect = config.Expect; + + if (config.TransferEncodingChunked != null) + httpReq.TransferEncoding = "chunked"; + else if (config.TransferEncoding?.Length > 0) + httpReq.TransferEncoding = string.Join(", ", config.TransferEncoding); + + foreach (var entry in config.Headers) + { + httpReq.Headers[entry.Name] = entry.Value; + } + + return httpReq; + } +} + +public interface IHttpResultsFilter : IDisposable +{ + string GetString(HttpWebRequest webReq, string reqBody); + byte[] GetBytes(HttpWebRequest webReq, byte[] reqBody); + void UploadStream(HttpWebRequest webRequest, Stream fileStream, string fileName); +} + +public class HttpResultsFilter : IHttpResultsFilter +{ + private readonly IHttpResultsFilter previousFilter; + + public string StringResult { get; set; } + public byte[] BytesResult { get; set; } + + public Func StringResultFn { get; set; } + public Func BytesResultFn { get; set; } + public Action UploadFileFn { get; set; } + + public HttpResultsFilter(string stringResult = null, byte[] bytesResult = null) + { + StringResult = stringResult; + BytesResult = bytesResult; + + previousFilter = HttpUtils.ResultsFilter; + HttpUtils.ResultsFilter = this; + } + + public void Dispose() + { + HttpUtils.ResultsFilter = previousFilter; + } + + public string GetString(HttpWebRequest webReq, string reqBody) + { + return StringResultFn != null + ? StringResultFn(webReq, reqBody) + : StringResult; + } + + public byte[] GetBytes(HttpWebRequest webReq, byte[] reqBody) + { + return BytesResultFn != null + ? BytesResultFn(webReq, reqBody) + : BytesResult; + } + + public void UploadStream(HttpWebRequest webRequest, Stream fileStream, string fileName) + { + UploadFileFn?.Invoke(webRequest, fileStream, fileName); + } +} + +public static class HttpClientExt +{ + /// + /// Case-insensitive, trimmed compare of two content types from start to ';', i.e. without charset suffix + /// + public static bool MatchesContentType(this HttpWebResponse res, string matchesContentType) => + MimeTypes.MatchesContentType(res.Headers[HttpHeaders.ContentType], matchesContentType); + + /// + /// Returns null for unknown Content-length + /// Syntax + Behavior compatible with HttpClient HttpResponseMessage + /// + public static long? GetContentLength(this HttpWebResponse res) => + res.ContentLength == -1 ? null : res.ContentLength; +} +#endif diff --git a/src/ServiceStack.Text/HttpUtils.cs b/src/ServiceStack.Text/HttpUtils.cs index 3d451a9fd..d96edc574 100644 --- a/src/ServiceStack.Text/HttpUtils.cs +++ b/src/ServiceStack.Text/HttpUtils.cs @@ -1,4 +1,4 @@ -//Copyright (c) ServiceStack, Inc. All Rights Reserved. +//Copyright (c) ServiceStack, Inc. All Rights Reserved. //License: https://raw.github.com/ServiceStack/ServiceStack/master/license.txt using System; @@ -6,1574 +6,352 @@ using System.IO; using System.Net; using System.Text; +using System.Threading; using System.Threading.Tasks; +using ServiceStack.Text; -namespace ServiceStack -{ - public static class HttpUtils - { - public static string UserAgent = "ServiceStack.Text"; - - public static Encoding UseEncoding { get; set; } = PclExport.Instance.GetUTF8Encoding(false); - - [ThreadStatic] - public static IHttpResultsFilter ResultsFilter; - - public static string AddQueryParam(this string url, string key, object val, bool encode = true) - { - return url.AddQueryParam(key, val.ToString(), encode); - } - - public static string AddQueryParam(this string url, object key, string val, bool encode = true) - { - return AddQueryParam(url, (key ?? "").ToString(), val, encode); - } - - public static string AddQueryParam(this string url, string key, string val, bool encode = true) - { - if (string.IsNullOrEmpty(url)) return null; - var prefix = string.Empty; - if (!url.EndsWith("?") && !url.EndsWith("&")) - { - prefix = url.IndexOf('?') == -1 ? "?" : "&"; - } - return url + prefix + key + "=" + (encode ? val.UrlEncode() : val); - } - - public static string SetQueryParam(this string url, string key, string val) - { - if (string.IsNullOrEmpty(url)) return null; - var qsPos = url.IndexOf('?'); - if (qsPos != -1) - { - var existingKeyPos = qsPos + 1 == url.IndexOf(key, qsPos, PclExport.Instance.InvariantComparison) - ? qsPos - : url.IndexOf("&" + key, qsPos, PclExport.Instance.InvariantComparison); - - if (existingKeyPos != -1) - { - var endPos = url.IndexOf('&', existingKeyPos + 1); - if (endPos == -1) - endPos = url.Length; - - var newUrl = url.Substring(0, existingKeyPos + key.Length + 1) - + "=" - + val.UrlEncode() - + url.Substring(endPos); - return newUrl; - } - } - var prefix = qsPos == -1 ? "?" : "&"; - return url + prefix + key + "=" + val.UrlEncode(); - } - - public static string AddHashParam(this string url, string key, object val) - { - return url.AddHashParam(key, val.ToString()); - } - - public static string AddHashParam(this string url, string key, string val) - { - if (string.IsNullOrEmpty(url)) return null; - var prefix = url.IndexOf('#') == -1 ? "#" : "/"; - return url + prefix + key + "=" + val.UrlEncode(); - } - - public static string SetHashParam(this string url, string key, string val) - { - if (string.IsNullOrEmpty(url)) return null; - var hPos = url.IndexOf('#'); - if (hPos != -1) - { - var existingKeyPos = hPos + 1 == url.IndexOf(key, hPos, PclExport.Instance.InvariantComparison) - ? hPos - : url.IndexOf("/" + key, hPos, PclExport.Instance.InvariantComparison); - - if (existingKeyPos != -1) - { - var endPos = url.IndexOf('/', existingKeyPos + 1); - if (endPos == -1) - endPos = url.Length; - - var newUrl = url.Substring(0, existingKeyPos + key.Length + 1) - + "=" - + val.UrlEncode() - + url.Substring(endPos); - return newUrl; - } - } - var prefix = url.IndexOf('#') == -1 ? "#" : "/"; - return url + prefix + key + "=" + val.UrlEncode(); - } - - public static string GetJsonFromUrl(this string url, - Action requestFilter = null, Action responseFilter = null) - { - return url.GetStringFromUrl(MimeTypes.Json, requestFilter, responseFilter); - } - - public static Task GetJsonFromUrlAsync(this string url, - Action requestFilter = null, Action responseFilter = null) - { - return url.GetStringFromUrlAsync(MimeTypes.Json, requestFilter, responseFilter); - } - - public static string GetXmlFromUrl(this string url, - Action requestFilter = null, Action responseFilter = null) - { - return url.GetStringFromUrl(MimeTypes.Xml, requestFilter, responseFilter); - } - - public static Task GetXmlFromUrlAsync(this string url, - Action requestFilter = null, Action responseFilter = null) - { - return url.GetStringFromUrlAsync(MimeTypes.Xml, requestFilter, responseFilter); - } - - public static string GetCsvFromUrl(this string url, - Action requestFilter = null, Action responseFilter = null) - { - return url.GetStringFromUrl(MimeTypes.Csv, requestFilter, responseFilter); - } - - public static Task GetCsvFromUrlAsync(this string url, - Action requestFilter = null, Action responseFilter = null) - { - return url.GetStringFromUrlAsync(MimeTypes.Csv, requestFilter, responseFilter); - } - - public static string GetStringFromUrl(this string url, string accept = "*/*", - Action requestFilter = null, Action responseFilter = null) - { - return SendStringToUrl(url, accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); - } - - public static Task GetStringFromUrlAsync(this string url, string accept = "*/*", - Action requestFilter = null, Action responseFilter = null) - { - return SendStringToUrlAsync(url, accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); - } - - public static string PostStringToUrl(this string url, string requestBody = null, - string contentType = null, string accept = "*/*", - Action requestFilter = null, Action responseFilter = null) - { - return SendStringToUrl(url, method: "POST", - requestBody: requestBody, contentType: contentType, - accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); - } - - public static Task PostStringToUrlAsync(this string url, string requestBody = null, - string contentType = null, string accept = "*/*", - Action requestFilter = null, Action responseFilter = null) - { - return SendStringToUrlAsync(url, method: "POST", - requestBody: requestBody, contentType: contentType, - accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); - } - - public static string PostToUrl(this string url, string formData = null, string accept = "*/*", - Action requestFilter = null, Action responseFilter = null) - { - return SendStringToUrl(url, method: "POST", - contentType: MimeTypes.FormUrlEncoded, requestBody: formData, - accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); - } - - public static Task PostToUrlAsync(this string url, string formData = null, string accept = "*/*", - Action requestFilter = null, Action responseFilter = null) - { - return SendStringToUrlAsync(url, method: "POST", - contentType: MimeTypes.FormUrlEncoded, requestBody: formData, - accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); - } - - public static string PostToUrl(this string url, object formData = null, string accept = "*/*", - Action requestFilter = null, Action responseFilter = null) - { - string postFormData = formData != null ? QueryStringSerializer.SerializeToString(formData) : null; - - return SendStringToUrl(url, method: "POST", - contentType: MimeTypes.FormUrlEncoded, requestBody: postFormData, - accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); - } - - public static Task PostToUrlAsync(this string url, object formData = null, string accept = "*/*", - Action requestFilter = null, Action responseFilter = null) - { - string postFormData = formData != null ? QueryStringSerializer.SerializeToString(formData) : null; - - return SendStringToUrlAsync(url, method: "POST", - contentType: MimeTypes.FormUrlEncoded, requestBody: postFormData, - accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); - } - - public static string PostJsonToUrl(this string url, string json, - Action requestFilter = null, Action responseFilter = null) - { - return SendStringToUrl(url, method: "POST", requestBody: json, contentType: MimeTypes.Json, accept: MimeTypes.Json, - requestFilter: requestFilter, responseFilter: responseFilter); - } - - public static Task PostJsonToUrlAsync(this string url, string json, - Action requestFilter = null, Action responseFilter = null) - { - return SendStringToUrlAsync(url, method: "POST", requestBody: json, contentType: MimeTypes.Json, accept: MimeTypes.Json, - requestFilter: requestFilter, responseFilter: responseFilter); - } - - public static string PostJsonToUrl(this string url, object data, - Action requestFilter = null, Action responseFilter = null) - { - return SendStringToUrl(url, method: "POST", requestBody: data.ToJson(), contentType: MimeTypes.Json, accept: MimeTypes.Json, - requestFilter: requestFilter, responseFilter: responseFilter); - } - - public static Task PostJsonToUrlAsync(this string url, object data, - Action requestFilter = null, Action responseFilter = null) - { - return SendStringToUrlAsync(url, method: "POST", requestBody: data.ToJson(), contentType: MimeTypes.Json, accept: MimeTypes.Json, - requestFilter: requestFilter, responseFilter: responseFilter); - } - - public static string PostXmlToUrl(this string url, string xml, - Action requestFilter = null, Action responseFilter = null) - { - return SendStringToUrl(url, method: "POST", requestBody: xml, contentType: MimeTypes.Xml, accept: MimeTypes.Xml, - requestFilter: requestFilter, responseFilter: responseFilter); - } - - public static Task PostXmlToUrlAsync(this string url, string xml, - Action requestFilter = null, Action responseFilter = null) - { - return SendStringToUrlAsync(url, method: "POST", requestBody: xml, contentType: MimeTypes.Xml, accept: MimeTypes.Xml, - requestFilter: requestFilter, responseFilter: responseFilter); - } - - public static string PostCsvToUrl(this string url, string csv, - Action requestFilter = null, Action responseFilter = null) - { - return SendStringToUrl(url, method: "POST", requestBody: csv, contentType: MimeTypes.Csv, accept: MimeTypes.Csv, - requestFilter: requestFilter, responseFilter: responseFilter); - } - - public static Task PostCsvToUrlAsync(this string url, string csv, - Action requestFilter = null, Action responseFilter = null) - { - return SendStringToUrlAsync(url, method: "POST", requestBody: csv, contentType: MimeTypes.Csv, accept: MimeTypes.Csv, - requestFilter: requestFilter, responseFilter: responseFilter); - } - - public static string PutStringToUrl(this string url, string requestBody = null, - string contentType = null, string accept = "*/*", - Action requestFilter = null, Action responseFilter = null) - { - return SendStringToUrl(url, method: "PUT", - requestBody: requestBody, contentType: contentType, - accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); - } - - public static Task PutStringToUrlAsync(this string url, string requestBody = null, - string contentType = null, string accept = "*/*", - Action requestFilter = null, Action responseFilter = null) - { - return SendStringToUrlAsync(url, method: "PUT", - requestBody: requestBody, contentType: contentType, - accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); - } - - public static string PutToUrl(this string url, string formData = null, string accept = "*/*", - Action requestFilter = null, Action responseFilter = null) - { - return SendStringToUrl(url, method: "PUT", - contentType: MimeTypes.FormUrlEncoded, requestBody: formData, - accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); - } - - public static Task PutToUrlAsync(this string url, string formData = null, string accept = "*/*", - Action requestFilter = null, Action responseFilter = null) - { - return SendStringToUrlAsync(url, method: "PUT", - contentType: MimeTypes.FormUrlEncoded, requestBody: formData, - accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); - } - - public static string PutToUrl(this string url, object formData = null, string accept = "*/*", - Action requestFilter = null, Action responseFilter = null) - { - string postFormData = formData != null ? QueryStringSerializer.SerializeToString(formData) : null; - - return SendStringToUrl(url, method: "PUT", - contentType: MimeTypes.FormUrlEncoded, requestBody: postFormData, - accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); - } - - public static Task PutToUrlAsync(this string url, object formData = null, string accept = "*/*", - Action requestFilter = null, Action responseFilter = null) - { - string postFormData = formData != null ? QueryStringSerializer.SerializeToString(formData) : null; - - return SendStringToUrlAsync(url, method: "PUT", - contentType: MimeTypes.FormUrlEncoded, requestBody: postFormData, - accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); - } - - public static string PutJsonToUrl(this string url, string json, - Action requestFilter = null, Action responseFilter = null) - { - return SendStringToUrl(url, method: "PUT", requestBody: json, contentType: MimeTypes.Json, accept: MimeTypes.Json, - requestFilter: requestFilter, responseFilter: responseFilter); - } - - public static Task PutJsonToUrlAsync(this string url, string json, - Action requestFilter = null, Action responseFilter = null) - { - return SendStringToUrlAsync(url, method: "PUT", requestBody: json, contentType: MimeTypes.Json, accept: MimeTypes.Json, - requestFilter: requestFilter, responseFilter: responseFilter); - } - - public static string PutJsonToUrl(this string url, object data, - Action requestFilter = null, Action responseFilter = null) - { - return SendStringToUrl(url, method: "PUT", requestBody: data.ToJson(), contentType: MimeTypes.Json, accept: MimeTypes.Json, - requestFilter: requestFilter, responseFilter: responseFilter); - } - - public static Task PutJsonToUrlAsync(this string url, object data, - Action requestFilter = null, Action responseFilter = null) - { - return SendStringToUrlAsync(url, method: "PUT", requestBody: data.ToJson(), contentType: MimeTypes.Json, accept: MimeTypes.Json, - requestFilter: requestFilter, responseFilter: responseFilter); - } - - public static string PutXmlToUrl(this string url, string xml, - Action requestFilter = null, Action responseFilter = null) - { - return SendStringToUrl(url, method: "PUT", requestBody: xml, contentType: MimeTypes.Xml, accept: MimeTypes.Xml, - requestFilter: requestFilter, responseFilter: responseFilter); - } - - public static Task PutXmlToUrlAsync(this string url, string xml, - Action requestFilter = null, Action responseFilter = null) - { - return SendStringToUrlAsync(url, method: "PUT", requestBody: xml, contentType: MimeTypes.Xml, accept: MimeTypes.Xml, - requestFilter: requestFilter, responseFilter: responseFilter); - } - - public static string PutCsvToUrl(this string url, string csv, - Action requestFilter = null, Action responseFilter = null) - { - return SendStringToUrl(url, method: "PUT", requestBody: csv, contentType: MimeTypes.Csv, accept: MimeTypes.Csv, - requestFilter: requestFilter, responseFilter: responseFilter); - } - - public static Task PutCsvToUrlAsync(this string url, string csv, - Action requestFilter = null, Action responseFilter = null) - { - return SendStringToUrlAsync(url, method: "PUT", requestBody: csv, contentType: MimeTypes.Csv, accept: MimeTypes.Csv, - requestFilter: requestFilter, responseFilter: responseFilter); - } - - public static string PatchStringToUrl(this string url, string requestBody = null, - string contentType = null, string accept = "*/*", - Action requestFilter = null, Action responseFilter = null) - { - return SendStringToUrl(url, method: "PATCH", - requestBody: requestBody, contentType: contentType, - accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); - } - - public static Task PatchStringToUrlAsync(this string url, string requestBody = null, - string contentType = null, string accept = "*/*", - Action requestFilter = null, Action responseFilter = null) - { - return SendStringToUrlAsync(url, method: "PATCH", - requestBody: requestBody, contentType: contentType, - accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); - } - - public static string PatchToUrl(this string url, string formData = null, string accept = "*/*", - Action requestFilter = null, Action responseFilter = null) - { - return SendStringToUrl(url, method: "PATCH", - contentType: MimeTypes.FormUrlEncoded, requestBody: formData, - accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); - } - - public static Task PatchToUrlAsync(this string url, string formData = null, string accept = "*/*", - Action requestFilter = null, Action responseFilter = null) - { - return SendStringToUrlAsync(url, method: "PATCH", - contentType: MimeTypes.FormUrlEncoded, requestBody: formData, - accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); - } - - public static string PatchToUrl(this string url, object formData = null, string accept = "*/*", - Action requestFilter = null, Action responseFilter = null) - { - string postFormData = formData != null ? QueryStringSerializer.SerializeToString(formData) : null; - - return SendStringToUrl(url, method: "PATCH", - contentType: MimeTypes.FormUrlEncoded, requestBody: postFormData, - accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); - } - - public static Task PatchToUrlAsync(this string url, object formData = null, string accept = "*/*", - Action requestFilter = null, Action responseFilter = null) - { - string postFormData = formData != null ? QueryStringSerializer.SerializeToString(formData) : null; - - return SendStringToUrlAsync(url, method: "PATCH", - contentType: MimeTypes.FormUrlEncoded, requestBody: postFormData, - accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); - } - - public static string PatchJsonToUrl(this string url, string json, - Action requestFilter = null, Action responseFilter = null) - { - return SendStringToUrl(url, method: "PATCH", requestBody: json, contentType: MimeTypes.Json, accept: MimeTypes.Json, - requestFilter: requestFilter, responseFilter: responseFilter); - } - - public static Task PatchJsonToUrlAsync(this string url, string json, - Action requestFilter = null, Action responseFilter = null) - { - return SendStringToUrlAsync(url, method: "PATCH", requestBody: json, contentType: MimeTypes.Json, accept: MimeTypes.Json, - requestFilter: requestFilter, responseFilter: responseFilter); - } - - public static string PatchJsonToUrl(this string url, object data, - Action requestFilter = null, Action responseFilter = null) - { - return SendStringToUrl(url, method: "PATCH", requestBody: data.ToJson(), contentType: MimeTypes.Json, accept: MimeTypes.Json, - requestFilter: requestFilter, responseFilter: responseFilter); - } - - public static Task PatchJsonToUrlAsync(this string url, object data, - Action requestFilter = null, Action responseFilter = null) - { - return SendStringToUrlAsync(url, method: "PATCH", requestBody: data.ToJson(), contentType: MimeTypes.Json, accept: MimeTypes.Json, - requestFilter: requestFilter, responseFilter: responseFilter); - } - - public static string DeleteFromUrl(this string url, string accept = "*/*", - Action requestFilter = null, Action responseFilter = null) - { - return SendStringToUrl(url, method: "DELETE", accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); - } - - public static Task DeleteFromUrlAsync(this string url, string accept = "*/*", - Action requestFilter = null, Action responseFilter = null) - { - return SendStringToUrlAsync(url, method: "DELETE", accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); - } - - public static string OptionsFromUrl(this string url, string accept = "*/*", - Action requestFilter = null, Action responseFilter = null) - { - return SendStringToUrl(url, method: "OPTIONS", accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); - } - - public static Task OptionsFromUrlAsync(this string url, string accept = "*/*", - Action requestFilter = null, Action responseFilter = null) - { - return SendStringToUrlAsync(url, method: "OPTIONS", accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); - } - - public static string HeadFromUrl(this string url, string accept = "*/*", - Action requestFilter = null, Action responseFilter = null) - { - return SendStringToUrl(url, method: "HEAD", accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); - } - - public static Task HeadFromUrlAsync(this string url, string accept = "*/*", - Action requestFilter = null, Action responseFilter = null) - { - return SendStringToUrlAsync(url, method: "HEAD", accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); - } - - public static string SendStringToUrl(this string url, string method = null, - string requestBody = null, string contentType = null, string accept = "*/*", - Action requestFilter = null, Action responseFilter = null) - { - var webReq = (HttpWebRequest)WebRequest.Create(url); - if (method != null) - webReq.Method = method; - if (contentType != null) - webReq.ContentType = contentType; - - webReq.Accept = accept; - PclExport.Instance.AddCompression(webReq); - - requestFilter?.Invoke(webReq); - - if (ResultsFilter != null) - { - return ResultsFilter.GetString(webReq, requestBody); - } - - if (requestBody != null) - { - using (var reqStream = PclExport.Instance.GetRequestStream(webReq)) - using (var writer = new StreamWriter(reqStream, UseEncoding)) - { - writer.Write(requestBody); - } - } - - using (var webRes = PclExport.Instance.GetResponse(webReq)) - using (var stream = webRes.GetResponseStream()) - using (var reader = new StreamReader(stream, UseEncoding)) - { - responseFilter?.Invoke((HttpWebResponse)webRes); - - return reader.ReadToEnd(); - } - } - - public static Task SendStringToUrlAsync(this string url, string method = null, string requestBody = null, - string contentType = null, string accept = "*/*", Action requestFilter = null, - Action responseFilter = null) - { - var webReq = (HttpWebRequest)WebRequest.Create(url); - if (method != null) - webReq.Method = method; - if (contentType != null) - webReq.ContentType = contentType; - - webReq.Accept = accept; - PclExport.Instance.AddCompression(webReq); - - requestFilter?.Invoke(webReq); - - if (ResultsFilter != null) - { - var result = ResultsFilter.GetString(webReq, requestBody); - var tcsResult = new TaskCompletionSource(); - tcsResult.SetResult(result); - return tcsResult.Task; - } - - if (requestBody != null) - { - using (var reqStream = PclExport.Instance.GetRequestStream(webReq)) - using (var writer = new StreamWriter(reqStream, UseEncoding)) - { - writer.Write(requestBody); - } - } - - var taskWebRes = webReq.GetResponseAsync(); - var tcs = new TaskCompletionSource(); - - taskWebRes.ContinueWith(task => - { - if (task.Exception != null) - { - tcs.SetException(task.Exception); - return; - } - if (task.IsCanceled) - { - tcs.SetCanceled(); - return; - } - - var webRes = task.Result; - responseFilter?.Invoke((HttpWebResponse)webRes); - - using (var stream = webRes.GetResponseStream()) - using (var reader = new StreamReader(stream, UseEncoding)) - { - tcs.SetResult(reader.ReadToEnd()); - } - }); - - return tcs.Task; - } - - public static byte[] GetBytesFromUrl(this string url, string accept = "*/*", - Action requestFilter = null, Action responseFilter = null) - { - return url.SendBytesToUrl(accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); - } - - public static Task GetBytesFromUrlAsync(this string url, string accept = "*/*", - Action requestFilter = null, Action responseFilter = null) - { - return url.SendBytesToUrlAsync(accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); - } - - public static byte[] PostBytesToUrl(this string url, byte[] requestBody = null, string contentType = null, string accept = "*/*", - Action requestFilter = null, Action responseFilter = null) - { - return SendBytesToUrl(url, method: "POST", - contentType: contentType, requestBody: requestBody, - accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); - } - - public static Task PostBytesToUrlAsync(this string url, byte[] requestBody = null, string contentType = null, string accept = "*/*", - Action requestFilter = null, Action responseFilter = null) - { - return SendBytesToUrlAsync(url, method: "POST", - contentType: contentType, requestBody: requestBody, - accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); - } - - public static byte[] PutBytesToUrl(this string url, byte[] requestBody = null, string contentType = null, string accept = "*/*", - Action requestFilter = null, Action responseFilter = null) - { - return SendBytesToUrl(url, method: "PUT", - contentType: contentType, requestBody: requestBody, - accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); - } - - public static Task PutBytesToUrlAsync(this string url, byte[] requestBody = null, string contentType = null, string accept = "*/*", - Action requestFilter = null, Action responseFilter = null) - { - return SendBytesToUrlAsync(url, method: "PUT", - contentType: contentType, requestBody: requestBody, - accept: accept, requestFilter: requestFilter, responseFilter: responseFilter); - } - - public static byte[] SendBytesToUrl(this string url, string method = null, - byte[] requestBody = null, string contentType = null, string accept = "*/*", - Action requestFilter = null, Action responseFilter = null) - { - var webReq = (HttpWebRequest)WebRequest.Create(url); - if (method != null) - webReq.Method = method; - - if (contentType != null) - webReq.ContentType = contentType; - - webReq.Accept = accept; - PclExport.Instance.AddCompression(webReq); - - requestFilter?.Invoke(webReq); - - if (ResultsFilter != null) - { - return ResultsFilter.GetBytes(webReq, requestBody); - } - - if (requestBody != null) - { - using (var req = PclExport.Instance.GetRequestStream(webReq)) - { - req.Write(requestBody, 0, requestBody.Length); - } - } - - using (var webRes = PclExport.Instance.GetResponse(webReq)) - { - responseFilter?.Invoke((HttpWebResponse)webRes); - - using (var stream = webRes.GetResponseStream()) - { - return stream.ReadFully(); - } - } - } - - public static Task SendBytesToUrlAsync(this string url, string method = null, - byte[] requestBody = null, string contentType = null, string accept = "*/*", - Action requestFilter = null, Action responseFilter = null) - { - var webReq = (HttpWebRequest)WebRequest.Create(url); - if (method != null) - webReq.Method = method; - if (contentType != null) - webReq.ContentType = contentType; - - webReq.Accept = accept; - PclExport.Instance.AddCompression(webReq); - - requestFilter?.Invoke(webReq); - - if (ResultsFilter != null) - { - var result = ResultsFilter.GetBytes(webReq, requestBody); - var tcsResult = new TaskCompletionSource(); - tcsResult.SetResult(result); - return tcsResult.Task; - } - - if (requestBody != null) - { - using (var req = PclExport.Instance.GetRequestStream(webReq)) - { - req.Write(requestBody, 0, requestBody.Length); - } - } - - var taskWebRes = webReq.GetResponseAsync(); - var tcs = new TaskCompletionSource(); - - taskWebRes.ContinueWith(task => - { - if (task.Exception != null) - { - tcs.SetException(task.Exception); - return; - } - if (task.IsCanceled) - { - tcs.SetCanceled(); - return; - } - - var webRes = task.Result; - responseFilter?.Invoke((HttpWebResponse)webRes); - - using (var stream = webRes.GetResponseStream()) - { - tcs.SetResult(stream.ReadFully()); - } - }); - - return tcs.Task; - } - - public static bool IsAny300(this Exception ex) - { - var status = ex.GetStatus(); - return status >= HttpStatusCode.MultipleChoices && status < HttpStatusCode.BadRequest; - } - - public static bool IsAny400(this Exception ex) - { - var status = ex.GetStatus(); - return status >= HttpStatusCode.BadRequest && status < HttpStatusCode.InternalServerError; - } - - public static bool IsAny500(this Exception ex) - { - var status = ex.GetStatus(); - return status >= HttpStatusCode.InternalServerError && (int)status < 600; - } - - public static bool IsNotModified(this Exception ex) - { - return GetStatus(ex) == HttpStatusCode.NotModified; - } - - public static bool IsBadRequest(this Exception ex) - { - return GetStatus(ex) == HttpStatusCode.BadRequest; - } - - public static bool IsNotFound(this Exception ex) - { - return GetStatus(ex) == HttpStatusCode.NotFound; - } - - public static bool IsUnauthorized(this Exception ex) - { - return GetStatus(ex) == HttpStatusCode.Unauthorized; - } - - public static bool IsForbidden(this Exception ex) - { - return GetStatus(ex) == HttpStatusCode.Forbidden; - } - - public static bool IsInternalServerError(this Exception ex) - { - return GetStatus(ex) == HttpStatusCode.InternalServerError; - } - - public static HttpStatusCode? GetResponseStatus(this string url) - { - try - { - var webReq = (HttpWebRequest)WebRequest.Create(url); - using (var webRes = PclExport.Instance.GetResponse(webReq)) - { - var httpRes = webRes as HttpWebResponse; - return httpRes?.StatusCode; - } - } - catch (Exception ex) - { - return ex.GetStatus(); - } - } - - public static HttpStatusCode? GetStatus(this Exception ex) - { - if (ex == null) - return null; - - if (ex is WebException webEx) - return GetStatus(webEx); - - if (ex is IHasStatusCode hasStatus) - return (HttpStatusCode)hasStatus.StatusCode; - - return null; - } - - public static HttpStatusCode? GetStatus(this WebException webEx) - { - var httpRes = webEx?.Response as HttpWebResponse; - return httpRes?.StatusCode; - } - - public static bool HasStatus(this Exception ex, HttpStatusCode statusCode) - { - return GetStatus(ex) == statusCode; - } - - public static string GetResponseBody(this Exception ex) - { - if (!(ex is WebException webEx) || webEx.Response == null || webEx.Status != WebExceptionStatus.ProtocolError) - return null; - - var errorResponse = (HttpWebResponse)webEx.Response; - using (var reader = new StreamReader(errorResponse.GetResponseStream(), UseEncoding)) - { - return reader.ReadToEnd(); - } - } - - public static string ReadToEnd(this WebResponse webRes) - { - using (var stream = webRes.GetResponseStream()) - using (var reader = new StreamReader(stream, UseEncoding)) - { - return reader.ReadToEnd(); - } - } - - public static IEnumerable ReadLines(this WebResponse webRes) - { - using (var stream = webRes.GetResponseStream()) - using (var reader = new StreamReader(stream, UseEncoding)) - { - string line; - while ((line = reader.ReadLine()) != null) - { - yield return line; - } - } - } - - public static HttpWebResponse GetErrorResponse(this string url) - { - try - { - var webReq = WebRequest.Create(url); - using (var webRes = PclExport.Instance.GetResponse(webReq)) - { - webRes.ReadToEnd(); - return null; - } - } - catch (WebException webEx) - { - return (HttpWebResponse)webEx.Response; - } - } - - public static Task GetRequestStreamAsync(this WebRequest request) - { - return GetRequestStreamAsync((HttpWebRequest)request); - } - - public static Task GetRequestStreamAsync(this HttpWebRequest request) - { - var tcs = new TaskCompletionSource(); - - try - { - request.BeginGetRequestStream(iar => - { - try - { - var response = request.EndGetRequestStream(iar); - tcs.SetResult(response); - } - catch (Exception exc) - { - tcs.SetException(exc); - } - }, null); - } - catch (Exception exc) - { - tcs.SetException(exc); - } - - return tcs.Task; - } - - public static Task ConvertTo(this Task task) where TDerived : TBase - { - var tcs = new TaskCompletionSource(); - task.ContinueWith(t => tcs.SetResult(t.Result), TaskContinuationOptions.OnlyOnRanToCompletion); - task.ContinueWith(t => tcs.SetException(t.Exception.InnerExceptions), TaskContinuationOptions.OnlyOnFaulted); - task.ContinueWith(t => tcs.SetCanceled(), TaskContinuationOptions.OnlyOnCanceled); - return tcs.Task; - } - - public static Task GetResponseAsync(this WebRequest request) - { - return GetResponseAsync((HttpWebRequest)request).ConvertTo(); - } - - public static Task GetResponseAsync(this HttpWebRequest request) - { - var tcs = new TaskCompletionSource(); +namespace ServiceStack; - try - { - request.BeginGetResponse(iar => - { - try - { - var response = (HttpWebResponse)request.EndGetResponse(iar); - tcs.SetResult(response); - } - catch (Exception exc) - { - tcs.SetException(exc); - } - }, null); - } - catch (Exception exc) - { - tcs.SetException(exc); - } - - return tcs.Task; - } - - public static void UploadFile(this WebRequest webRequest, Stream fileStream, string fileName, string mimeType, - string accept = null, Action requestFilter = null, string method = "POST") - { - var httpReq = (HttpWebRequest)webRequest; - httpReq.Method = method; - - if (accept != null) - httpReq.Accept = accept; - - requestFilter?.Invoke(httpReq); - - var boundary = Guid.NewGuid().ToString("N"); - - httpReq.ContentType = "multipart/form-data; boundary=\"" + boundary + "\""; - - var boundarybytes = ("\r\n--" + boundary + "--\r\n").ToAsciiBytes(); - - var headerTemplate = "\r\n--" + boundary + - "\r\nContent-Disposition: form-data; name=\"file\"; filename=\"{0}\"\r\nContent-Type: {1}\r\n\r\n"; - - var header = string.Format(headerTemplate, fileName, mimeType); - - var headerbytes = header.ToAsciiBytes(); - - var contentLength = fileStream.Length + headerbytes.Length + boundarybytes.Length; - PclExport.Instance.InitHttpWebRequest(httpReq, - contentLength: contentLength, allowAutoRedirect: false, keepAlive: false); - - if (ResultsFilter != null) - { - ResultsFilter.UploadStream(httpReq, fileStream, fileName); - return; - } - - using (var outputStream = PclExport.Instance.GetRequestStream(httpReq)) - { - outputStream.Write(headerbytes, 0, headerbytes.Length); - - fileStream.CopyTo(outputStream, 4096); - - outputStream.Write(boundarybytes, 0, boundarybytes.Length); - - PclExport.Instance.CloseStream(outputStream); - } - } - - public static void UploadFile(this WebRequest webRequest, Stream fileStream, string fileName) - { - if (fileName == null) - throw new ArgumentNullException(nameof(fileName)); - var mimeType = MimeTypes.GetMimeType(fileName); - if (mimeType == null) - throw new ArgumentException("Mime-type not found for file: " + fileName); +public static partial class HttpUtils +{ + public static string UserAgent = "ServiceStack.Text" + +#if NET6_0_OR_GREATER + "/net6" +#elif NETSTANDARD2_0 + "/std2.0" +#elif NETFX + "/net472" +#else + "/unknown" +#endif +; - UploadFile(webRequest, fileStream, fileName, mimeType); - } + public static Encoding UseEncoding { get; set; } = new UTF8Encoding(false); - public static string PostXmlToUrl(this string url, object data, - Action requestFilter = null, Action responseFilter = null) - { - return SendStringToUrl(url, method: "POST", requestBody: data.ToXml(), contentType: MimeTypes.Xml, accept: MimeTypes.Xml, - requestFilter: requestFilter, responseFilter: responseFilter); - } + public static string AddQueryParam(this string url, string key, object val, bool encode = true) + { + return url.AddQueryParam(key, val?.ToString(), encode); + } - public static string PostCsvToUrl(this string url, object data, - Action requestFilter = null, Action responseFilter = null) - { - return SendStringToUrl(url, method: "POST", requestBody: data.ToCsv(), contentType: MimeTypes.Csv, accept: MimeTypes.Csv, - requestFilter: requestFilter, responseFilter: responseFilter); - } + public static string AddQueryParam(this string url, object key, string val, bool encode = true) + { + return AddQueryParam(url, key?.ToString(), val, encode); + } - public static string PutXmlToUrl(this string url, object data, - Action requestFilter = null, Action responseFilter = null) - { - return SendStringToUrl(url, method: "PUT", requestBody: data.ToXml(), contentType: MimeTypes.Xml, accept: MimeTypes.Xml, - requestFilter: requestFilter, responseFilter: responseFilter); - } + public static string AddQueryParam(this string url, string key, string val, bool encode = true) + { + if (url == null) + url = ""; - public static string PutCsvToUrl(this string url, object data, - Action requestFilter = null, Action responseFilter = null) + if (key == null || val == null) + return url; + + var prefix = string.Empty; + if (!url.EndsWith("?") && !url.EndsWith("&")) { - return SendStringToUrl(url, method: "PUT", requestBody: data.ToCsv(), contentType: MimeTypes.Csv, accept: MimeTypes.Csv, - requestFilter: requestFilter, responseFilter: responseFilter); + prefix = url.IndexOf('?') == -1 ? "?" : "&"; } + return url + prefix + key + "=" + (encode ? val.UrlEncode() : val); } - //Allow Exceptions to Customize HTTP StatusCode and StatusDescription returned - public interface IHasStatusCode + public static string SetQueryParam(this string url, string key, string val) { - int StatusCode { get; } + if (url == null) + url = ""; + + if (key == null || val == null) + return url; + + var qsPos = url.IndexOf('?'); + if (qsPos != -1) + { + var existingKeyPos = qsPos + 1 == url.IndexOf(key + "=", qsPos, StringComparison.Ordinal) + ? qsPos + : url.IndexOf("&" + key, qsPos, StringComparison.Ordinal); + + if (existingKeyPos != -1) + { + var endPos = url.IndexOf('&', existingKeyPos + 1); + if (endPos == -1) + endPos = url.Length; + + var newUrl = url.Substring(0, existingKeyPos + key.Length + 1) + + "=" + + val.UrlEncode() + + url.Substring(endPos); + return newUrl; + } + } + var prefix = qsPos == -1 ? "?" : "&"; + return url + prefix + key + "=" + val.UrlEncode(); } - public interface IHasStatusDescription + public static string AddHashParam(this string url, string key, object val) { - string StatusDescription { get; } + return url.AddHashParam(key, val?.ToString()); } - public interface IHttpResultsFilter : IDisposable + public static string AddHashParam(this string url, string key, string val) { - string GetString(HttpWebRequest webReq, string reqBody); - byte[] GetBytes(HttpWebRequest webReq, byte[] reqBody); - void UploadStream(HttpWebRequest webRequest, Stream fileStream, string fileName); + if (url == null) + url = ""; + + if (key == null || val == null) + return url; + + var prefix = url.IndexOf('#') == -1 ? "#" : "/"; + return url + prefix + key + "=" + val.UrlEncode(); } - public class HttpResultsFilter : IHttpResultsFilter + public static string SetHashParam(this string url, string key, string val) { - private readonly IHttpResultsFilter previousFilter; - - public string StringResult { get; set; } - public byte[] BytesResult { get; set; } - - public Func StringResultFn { get; set; } - public Func BytesResultFn { get; set; } - public Action UploadFileFn { get; set; } + if (url == null) + url = ""; + + if (key == null || val == null) + return url; + + var hPos = url.IndexOf('#'); + if (hPos != -1) + { + var existingKeyPos = hPos + 1 == url.IndexOf(key + "=", hPos, PclExport.Instance.InvariantComparison) + ? hPos + : url.IndexOf("/" + key, hPos, PclExport.Instance.InvariantComparison); + + if (existingKeyPos != -1) + { + var endPos = url.IndexOf('/', existingKeyPos + 1); + if (endPos == -1) + endPos = url.Length; + + var newUrl = url.Substring(0, existingKeyPos + key.Length + 1) + + "=" + + val.UrlEncode() + + url.Substring(endPos); + return newUrl; + } + } + var prefix = url.IndexOf('#') == -1 ? "#" : "/"; + return url + prefix + key + "=" + val.UrlEncode(); + } - public HttpResultsFilter(string stringResult = null, byte[] bytesResult = null) + public static bool HasRequestBody(string httpMethod) + { + switch (httpMethod) { - StringResult = stringResult; - BytesResult = bytesResult; - - previousFilter = HttpUtils.ResultsFilter; - HttpUtils.ResultsFilter = this; + case HttpMethods.Get: + case HttpMethods.Delete: + case HttpMethods.Head: + case HttpMethods.Options: + return false; } - public void Dispose() - { - HttpUtils.ResultsFilter = previousFilter; - } + return true; + } + + public static Task GetRequestStreamAsync(this WebRequest request) + { + return GetRequestStreamAsync((HttpWebRequest)request); + } - public string GetString(HttpWebRequest webReq, string reqBody) - { - return StringResultFn != null - ? StringResultFn(webReq, reqBody) - : StringResult; - } + public static Task GetRequestStreamAsync(this HttpWebRequest request) + { + var tcs = new TaskCompletionSource(); - public byte[] GetBytes(HttpWebRequest webReq, byte[] reqBody) + try { - return BytesResultFn != null - ? BytesResultFn(webReq, reqBody) - : BytesResult; + request.BeginGetRequestStream(iar => + { + try + { + var response = request.EndGetRequestStream(iar); + tcs.SetResult(response); + } + catch (Exception exc) + { + tcs.SetException(exc); + } + }, null); } - - public void UploadStream(HttpWebRequest webRequest, Stream fileStream, string fileName) + catch (Exception exc) { - UploadFileFn?.Invoke(webRequest, fileStream, fileName); + tcs.SetException(exc); } + + return tcs.Task; } -} -namespace ServiceStack -{ - public static class MimeTypes + public static Task ConvertTo(this Task task) where TDerived : TBase { - public static Dictionary ExtensionMimeTypes = new Dictionary(); - - public const string Html = "text/html"; - public const string Xml = "application/xml"; - public const string XmlText = "text/xml"; - public const string Json = "application/json"; - public const string JsonText = "text/json"; - public const string Jsv = "application/jsv"; - public const string JsvText = "text/jsv"; - public const string Csv = "text/csv"; - public const string ProtoBuf = "application/x-protobuf"; - public const string JavaScript = "text/javascript"; - - public const string FormUrlEncoded = "application/x-www-form-urlencoded"; - public const string MultiPartFormData = "multipart/form-data"; - public const string JsonReport = "text/jsonreport"; - public const string Soap11 = "text/xml; charset=utf-8"; - public const string Soap12 = "application/soap+xml"; - public const string Yaml = "application/yaml"; - public const string YamlText = "text/yaml"; - public const string PlainText = "text/plain"; - public const string MarkdownText = "text/markdown"; - public const string MsgPack = "application/x-msgpack"; - public const string Wire = "application/x-wire"; - public const string NetSerializer = "application/x-netserializer"; + var tcs = new TaskCompletionSource(); + task.ContinueWith(t => tcs.SetResult(t.Result), TaskContinuationOptions.OnlyOnRanToCompletion); + task.ContinueWith(t => tcs.SetException(t.Exception.InnerExceptions), TaskContinuationOptions.OnlyOnFaulted); + task.ContinueWith(t => tcs.SetCanceled(), TaskContinuationOptions.OnlyOnCanceled); + return tcs.Task; + } - public const string ImagePng = "image/png"; - public const string ImageGif = "image/gif"; - public const string ImageJpg = "image/jpeg"; + public static Task GetResponseAsync(this WebRequest request) + { + return GetResponseAsync((HttpWebRequest)request).ConvertTo(); + } - public const string Bson = "application/bson"; - public const string Binary = "application/octet-stream"; - public const string ServerSentEvents = "text/event-stream"; + public static Task GetResponseAsync(this HttpWebRequest request) + { + var tcs = new TaskCompletionSource(); - public static string GetExtension(string mimeType) + try { - switch (mimeType) + request.BeginGetResponse(iar => { - case ProtoBuf: - return ".pbuf"; - } - - var parts = mimeType.Split('/'); - if (parts.Length == 1) return "." + parts[0]; - if (parts.Length == 2) return "." + parts[1]; - - throw new NotSupportedException("Unknown mimeType: " + mimeType); + try + { + var response = (HttpWebResponse)request.EndGetResponse(iar); + tcs.SetResult(response); + } + catch (Exception exc) + { + tcs.SetException(exc); + } + }, null); } - - public static string GetMimeType(string fileNameOrExt) + catch (Exception exc) { - if (string.IsNullOrEmpty(fileNameOrExt)) - throw new ArgumentNullException("fileNameOrExt"); - - var parts = fileNameOrExt.Split('.'); - var fileExt = parts[parts.Length - 1]; - - string mimeType; - if (ExtensionMimeTypes.TryGetValue(fileExt, out mimeType)) - { - return mimeType; - } - - switch (fileExt) - { - case "jpeg": - case "gif": - case "png": - case "tiff": - case "bmp": - case "webp": - return "image/" + fileExt; - - case "jpg": - return "image/jpeg"; - - case "tif": - return "image/tiff"; - - case "svg": - return "image/svg+xml"; - - case "htm": - case "html": - case "shtml": - return "text/html"; - - case "js": - return "text/javascript"; - - case "ts": - return "text/x.typescript"; - - case "jsx": - return "text/jsx"; - - case "csv": - case "css": - case "sgml": - return "text/" + fileExt; - - case "txt": - return "text/plain"; - - case "wav": - return "audio/wav"; - - case "mp3": - return "audio/mpeg3"; - - case "mid": - return "audio/midi"; - - case "qt": - case "mov": - return "video/quicktime"; - - case "mpg": - return "video/mpeg"; - - case "avi": - case "mp4": - case "ogg": - case "webm": - return "video/" + fileExt; - - case "ogv": - return "video/ogg"; - - case "rtf": - return "application/" + fileExt; - - case "xls": - return "application/x-excel"; - - case "doc": - return "application/msword"; - - case "ppt": - return "application/powerpoint"; - - case "gz": - case "tgz": - return "application/x-compressed"; - - case "eot": - return "application/vnd.ms-fontobject"; - - case "ttf": - return "application/octet-stream"; - - case "woff": - return "application/font-woff"; - case "woff2": - return "application/font-woff2"; - - default: - return "application/" + fileExt; - } + tcs.SetException(exc); } - } - public static class HttpHeaders + return tcs.Task; + } + + public static bool IsAny300(this Exception ex) { - public const string XParamOverridePrefix = "X-Param-Override-"; - - public const string XHttpMethodOverride = "X-Http-Method-Override"; - - public const string XAutoBatchCompleted = "X-AutoBatch-Completed"; // How many requests were completed before first failure - - public const string XTag = "X-Tag"; - - public const string XUserAuthId = "X-UAId"; - - public const string XTrigger = "X-Trigger"; // Trigger Events on UserAgent - - public const string XForwardedFor = "X-Forwarded-For"; // IP Address - - public const string XForwardedPort = "X-Forwarded-Port"; // 80 - - public const string XForwardedProtocol = "X-Forwarded-Proto"; // http or https - - public const string XRealIp = "X-Real-IP"; - - public const string XLocation = "X-Location"; - - public const string XStatus = "X-Status"; - - public const string Referer = "Referer"; - - public const string CacheControl = "Cache-Control"; - - public const string IfModifiedSince = "If-Modified-Since"; - - public const string IfUnmodifiedSince = "If-Unmodified-Since"; - - public const string IfNoneMatch = "If-None-Match"; - - public const string IfMatch = "If-Match"; - - public const string LastModified = "Last-Modified"; - - public const string Accept = "Accept"; - - public const string AcceptEncoding = "Accept-Encoding"; - - public const string ContentType = "Content-Type"; - - public const string ContentEncoding = "Content-Encoding"; - - public const string ContentLength = "Content-Length"; - - public const string ContentDisposition = "Content-Disposition"; - - public const string Location = "Location"; - - public const string SetCookie = "Set-Cookie"; - - public const string ETag = "ETag"; - - public const string Age = "Age"; - - public const string Expires = "Expires"; - - public const string Vary = "Vary"; - - public const string Authorization = "Authorization"; - - public const string WwwAuthenticate = "WWW-Authenticate"; - - public const string AllowOrigin = "Access-Control-Allow-Origin"; - - public const string AllowMethods = "Access-Control-Allow-Methods"; - - public const string AllowHeaders = "Access-Control-Allow-Headers"; - - public const string AllowCredentials = "Access-Control-Allow-Credentials"; - - public const string ExposeHeaders = "Access-Control-Expose-Headers"; - - public const string AccessControlMaxAge = "Access-Control-Max-Age"; - - public const string Origin = "Origin"; - - public const string RequestMethod = "Access-Control-Request-Method"; - - public const string RequestHeaders = "Access-Control-Request-Headers"; - - public const string AcceptRanges = "Accept-Ranges"; - - public const string ContentRange = "Content-Range"; - - public const string Range = "Range"; - - public const string SOAPAction = "SOAPAction"; - - public const string Allow = "Allow"; - - public const string AcceptCharset = "Accept-Charset"; - - public const string AcceptLanguage = "Accept-Language"; - - public const string Connection = "Connection"; - - public const string Cookie = "Cookie"; + var status = ex.GetStatus(); + return status is >= HttpStatusCode.MultipleChoices and < HttpStatusCode.BadRequest; + } - public const string ContentLanguage = "Content-Language"; + public static bool IsAny400(this Exception ex) + { + var status = ex.GetStatus(); + return status is >= HttpStatusCode.BadRequest and < HttpStatusCode.InternalServerError; + } - public const string Expect = "Expect"; + public static bool IsAny500(this Exception ex) + { + var status = ex.GetStatus(); + return status >= HttpStatusCode.InternalServerError && (int)status < 600; + } - public const string Pragma = "Pragma"; - - public const string ProxyAuthenticate = "Proxy-Authenticate"; + public static bool IsNotModified(this Exception ex) + { + return GetStatus(ex) == HttpStatusCode.NotModified; + } - public const string ProxyAuthorization = "Proxy-Authorization"; + public static bool IsBadRequest(this Exception ex) + { + return GetStatus(ex) == HttpStatusCode.BadRequest; + } - public const string ProxyConnection = "Proxy-Connection"; + public static bool IsNotFound(this Exception ex) + { + return GetStatus(ex) == HttpStatusCode.NotFound; + } - public const string SetCookie2 = "Set-Cookie2"; + public static bool IsUnauthorized(this Exception ex) + { + return GetStatus(ex) == HttpStatusCode.Unauthorized; + } - public const string TE = "TE"; + public static bool IsForbidden(this Exception ex) + { + return GetStatus(ex) == HttpStatusCode.Forbidden; + } - public const string Trailer = "Trailer"; + public static bool IsInternalServerError(this Exception ex) + { + return GetStatus(ex) == HttpStatusCode.InternalServerError; + } - public const string TransferEncoding = "Transfer-Encoding"; + public static HttpStatusCode? GetStatus(this Exception ex) + { +#if NET6_0_OR_GREATER + if (ex is System.Net.Http.HttpRequestException httpEx) + return GetStatus(httpEx); +#endif - public const string Upgrade = "Upgrade"; + if (ex is WebException webEx) + return GetStatus(webEx); - public const string Via = "Via"; + if (ex is IHasStatusCode hasStatus) + return (HttpStatusCode)hasStatus.StatusCode; - public const string Warning = "Warning"; + return null; + } - public const string Date = "Date"; - public const string Host = "Host"; - public const string UserAgent = "User-Agent"; +#if NET6_0_OR_GREATER + public static HttpStatusCode? GetStatus(this System.Net.Http.HttpRequestException ex) => ex.StatusCode; +#endif - public static HashSet RestrictedHeaders = new HashSet(StringComparer.OrdinalIgnoreCase) - { - Accept, - Connection, - ContentLength, - ContentType, - Date, - Expect, - Host, - IfModifiedSince, - Range, - Referer, - TransferEncoding, - UserAgent, - ProxyConnection, - }; + public static HttpStatusCode? GetStatus(this WebException webEx) + { + var httpRes = webEx?.Response as HttpWebResponse; + return httpRes?.StatusCode; } - public static class HttpMethods + public static bool HasStatus(this Exception ex, HttpStatusCode statusCode) { - static readonly string[] allVerbs = new[] { - "OPTIONS", "GET", "HEAD", "POST", "PUT", "DELETE", "TRACE", "CONNECT", // RFC 2616 - "PROPFIND", "PROPPATCH", "MKCOL", "COPY", "MOVE", "LOCK", "UNLOCK", // RFC 2518 - "VERSION-CONTROL", "REPORT", "CHECKOUT", "CHECKIN", "UNCHECKOUT", - "MKWORKSPACE", "UPDATE", "LABEL", "MERGE", "BASELINE-CONTROL", "MKACTIVITY", // RFC 3253 - "ORDERPATCH", // RFC 3648 - "ACL", // RFC 3744 - "PATCH", // https://datatracker.ietf.org/doc/draft-dusseault-http-patch/ - "SEARCH", // https://datatracker.ietf.org/doc/draft-reschke-webdav-search/ - "BCOPY", "BDELETE", "BMOVE", "BPROPFIND", "BPROPPATCH", "NOTIFY", - "POLL", "SUBSCRIBE", "UNSUBSCRIBE" //MS Exchange WebDav: http://msdn.microsoft.com/en-us/library/aa142917.aspx - }; - - public static HashSet AllVerbs = new HashSet(allVerbs); + return GetStatus(ex) == statusCode; + } - public static bool HasVerb(string httpVerb) - { -#if NETFX_CORE - return allVerbs.Any(p => p.Equals(httpVerb.ToUpper())); -#else - return AllVerbs.Contains(httpVerb.ToUpper()); -#endif - } + public static string GetResponseBody(this Exception ex) + { + if (!(ex is WebException webEx) || webEx.Response == null || webEx.Status != WebExceptionStatus.ProtocolError) + return null; - public const string Get = "GET"; - public const string Put = "PUT"; - public const string Post = "POST"; - public const string Delete = "DELETE"; - public const string Options = "OPTIONS"; - public const string Head = "HEAD"; - public const string Patch = "PATCH"; + var errorResponse = (HttpWebResponse)webEx.Response; + using var responseStream = errorResponse.GetResponseStream(); + return responseStream.ReadToEnd(UseEncoding); } - public static class CompressionTypes + public static async Task GetResponseBodyAsync(this Exception ex, CancellationToken token = default) { - public static readonly string[] AllCompressionTypes = new[] { Deflate, GZip }; - - public const string Default = Deflate; - public const string Deflate = "deflate"; - public const string GZip = "gzip"; + if (!(ex is WebException webEx) || webEx.Response == null || webEx.Status != WebExceptionStatus.ProtocolError) + return null; - public static bool IsValid(string compressionType) - { - return compressionType == Deflate || compressionType == GZip; - } + var errorResponse = (HttpWebResponse)webEx.Response; + using var responseStream = errorResponse.GetResponseStream(); + return await responseStream.ReadToEndAsync(UseEncoding).ConfigAwait(); + } - public static void AssertIsValid(string compressionType) - { - if (!IsValid(compressionType)) - { - throw new NotSupportedException(compressionType - + " is not a supported compression type. Valid types: gzip, deflate."); - } - } + public static string ReadToEnd(this WebResponse webRes) + { + using var stream = webRes.GetResponseStream(); + return stream.ReadToEnd(UseEncoding); + } - public static string GetExtension(string compressionType) - { - switch (compressionType) - { - case Deflate: - case GZip: - return "." + compressionType; - default: - throw new NotSupportedException( - "Unknown compressionType: " + compressionType); - } - } + public static Task ReadToEndAsync(this WebResponse webRes) + { + using var stream = webRes.GetResponseStream(); + return stream.ReadToEndAsync(UseEncoding); } - public static class HttpStatus + public static IEnumerable ReadLines(this WebResponse webRes) { - public static string GetStatusDescription(int statusCode) + using var stream = webRes.GetResponseStream(); + using var reader = new StreamReader(stream, UseEncoding, true, 1024, leaveOpen: true); + string line; + while ((line = reader.ReadLine()) != null) { - if (statusCode >= 100 && statusCode < 600) - { - int i = statusCode / 100; - int j = statusCode % 100; - - if (j < Descriptions[i].Length) - return Descriptions[i][j]; - } - - return string.Empty; + yield return line; } - - private static readonly string[][] Descriptions = new string[][] - { - null, - new[] - { - /* 100 */ "Continue", - /* 101 */ "Switching Protocols", - /* 102 */ "Processing" - }, - new[] - { - /* 200 */ "OK", - /* 201 */ "Created", - /* 202 */ "Accepted", - /* 203 */ "Non-Authoritative Information", - /* 204 */ "No Content", - /* 205 */ "Reset Content", - /* 206 */ "Partial Content", - /* 207 */ "Multi-Status" - }, - new[] - { - /* 300 */ "Multiple Choices", - /* 301 */ "Moved Permanently", - /* 302 */ "Found", - /* 303 */ "See Other", - /* 304 */ "Not Modified", - /* 305 */ "Use Proxy", - /* 306 */ string.Empty, - /* 307 */ "Temporary Redirect" - }, - new[] - { - /* 400 */ "Bad Request", - /* 401 */ "Unauthorized", - /* 402 */ "Payment Required", - /* 403 */ "Forbidden", - /* 404 */ "Not Found", - /* 405 */ "Method Not Allowed", - /* 406 */ "Not Acceptable", - /* 407 */ "Proxy Authentication Required", - /* 408 */ "Request Timeout", - /* 409 */ "Conflict", - /* 410 */ "Gone", - /* 411 */ "Length Required", - /* 412 */ "Precondition Failed", - /* 413 */ "Request Entity Too Large", - /* 414 */ "Request-Uri Too Long", - /* 415 */ "Unsupported Media Type", - /* 416 */ "Requested Range Not Satisfiable", - /* 417 */ "Expectation Failed", - /* 418 */ string.Empty, - /* 419 */ string.Empty, - /* 420 */ string.Empty, - /* 421 */ string.Empty, - /* 422 */ "Unprocessable Entity", - /* 423 */ "Locked", - /* 424 */ "Failed Dependency" - }, - new[] - { - /* 500 */ "Internal Server Error", - /* 501 */ "Not Implemented", - /* 502 */ "Bad Gateway", - /* 503 */ "Service Unavailable", - /* 504 */ "Gateway Timeout", - /* 505 */ "Http Version Not Supported", - /* 506 */ string.Empty, - /* 507 */ "Insufficient Storage" - } - }; } } + +//Allow Exceptions to Customize HTTP StatusCode and StatusDescription returned +public interface IHasStatusCode +{ + int StatusCode { get; } +} + +public interface IHasStatusDescription +{ + string StatusDescription { get; } +} \ No newline at end of file diff --git a/src/ServiceStack.Text/JsAot.cs b/src/ServiceStack.Text/JsAot.cs new file mode 100644 index 000000000..103360426 --- /dev/null +++ b/src/ServiceStack.Text/JsAot.cs @@ -0,0 +1,225 @@ +#if __IOS__ || __ANDROID__ || WINDOWS_UWP || __MOBILE__ + +// When Linking "SDK and User Assemblies" in Xamarin you can copy this class to your project and call `JsAot.Run()` on Startup +// Alternative solution is to add 'ServiceStack.Text' to your "Skip linking assemblies" list which should contain: +// ServiceStack.Text;ServiceStack.Client;{Your}.ServiceModel + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; +using ServiceStack.Text; +using ServiceStack.Text.Common; +using Xamarin.Forms.Internals; + +namespace ServiceStack +{ + public static class JsAot + { + [Preserve] + public static void Init() {} + + /// + /// Provide hint to IOS AOT compiler to pre-compile generic classes for all your DTOs. + /// Just needs to be called once in a static constructor. + /// + [Preserve] + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] + public static void Run() + { + try + { + RegisterForAot(); + } + catch (Exception ex) + { + Console.WriteLine(ex); + throw; + } + } + + [Preserve(AllMembers = true)] + internal class Poco + { + public string Dummy { get; set; } + } + + [Preserve] + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] + internal static void RegisterForAot() + { + RegisterTypeForAot(); + + RegisterElement(); + + RegisterElement(); + RegisterElement(); + RegisterElement(); + RegisterElement(); + RegisterElement(); + RegisterElement(); + RegisterElement(); + RegisterElement(); + + RegisterElement(); + RegisterElement(); + RegisterElement(); + RegisterElement(); + RegisterElement(); + + RegisterElement(); + RegisterElement(); + RegisterElement(); + RegisterElement(); + RegisterElement(); + RegisterElement(); + RegisterElement(); + RegisterElement(); + RegisterElement(); + RegisterElement(); + RegisterElement(); + RegisterElement(); + RegisterElement(); + + RegisterElement(); + RegisterTypeForAot(); // used by DateTime + + // register built in structs + RegisterTypeForAot(); + RegisterTypeForAot(); + RegisterTypeForAot(); + RegisterTypeForAot(); + + RegisterTypeForAot(); + RegisterTypeForAot(); + RegisterTypeForAot(); + RegisterTypeForAot(); + } + + [Preserve] + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] + public static void RegisterTypeForAot() + { + AotConfig.RegisterSerializers(); + } + + [Preserve] + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] + public static void RegisterElement() + { + AotConfig.RegisterSerializers(); + AotConfig.RegisterElement(); + AotConfig.RegisterElement(); + } + + [Preserve(AllMembers = true)] + internal class AotConfig + { + internal static JsReader jsonReader; + internal static JsWriter jsonWriter; + internal static JsReader jsvReader; + internal static JsWriter jsvWriter; + internal static Text.Json.JsonTypeSerializer jsonSerializer; + internal static Text.Jsv.JsvTypeSerializer jsvSerializer; + + [Preserve] + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] + static AotConfig() + { + jsonSerializer = new Text.Json.JsonTypeSerializer(); + jsvSerializer = new Text.Jsv.JsvTypeSerializer(); + jsonReader = new JsReader(); + jsonWriter = new JsWriter(); + jsvReader = new JsReader(); + jsvWriter = new JsWriter(); + } + + [Preserve] + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] + internal static void RegisterSerializers() + { + Register(); + jsonSerializer.GetParseFn(); + jsonSerializer.GetWriteFn(); + jsonReader.GetParseFn(); + jsonWriter.GetWriteFn(); + + Register(); + jsvSerializer.GetParseFn(); + jsvSerializer.GetWriteFn(); + jsvReader.GetParseFn(); + jsvWriter.GetWriteFn(); + + CsvSerializer.InitAot(); + QueryStringWriter.WriteFn(); + } + + [Preserve] + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] + public static ParseStringDelegate GetParseFn(Type type) + { + var parseFn = Text.Json.JsonTypeSerializer.Instance.GetParseFn(type); + return parseFn; + } + + [Preserve] + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] + internal static void Register() where TSerializer : ITypeSerializer + { + Text.Json.JsonReader.InitAot(); + Text.Json.JsonWriter.InitAot(); + + Text.Jsv.JsvReader.InitAot(); + Text.Jsv.JsvWriter.InitAot(); + + var hold = new object[] + { + new List(), + new T[0], + new Dictionary(), + new Dictionary(), + new HashSet(), + }; + + JsConfig.ExcludeTypeInfo = false; + + var r1 = JsConfig.OnDeserializedFn; + var r2 = JsConfig.HasDeserializeFn; + var r3 = JsConfig.SerializeFn; + var r4 = JsConfig.DeSerializeFn; + var r5 = TypeConfig.Properties; + + JsReader.InitAot(); + JsWriter.InitAot(); + } + + [Preserve] + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] + internal static void RegisterElement() where TSerializer : ITypeSerializer + { + DeserializeDictionary.ParseDictionary(null, null, null, null); + DeserializeDictionary.ParseDictionary(null, null, null, null); + + ToStringDictionaryMethods.WriteIDictionary(null, null, null, null); + ToStringDictionaryMethods.WriteIDictionary(null, null, null, null); + + // Include List deserialisations from the Register<> method above. This solves issue where List properties on responses deserialise to null. + // No idea why this is happening because there is no visible exception raised. Suspect IOS is swallowing an AOT exception somewhere. + DeserializeArrayWithElements.ParseGenericArray(null, null); + DeserializeListWithElements.ParseGenericList(null, null, null); + + // Cannot use the line below for some unknown reason - when trying to compile to run on device, mtouch bombs during native code compile. + // Something about this line or its inner workings is offensive to mtouch. Luckily this was not needed for my List issue. + // DeserializeCollection.ParseCollection(null, null, null); + + TranslateListWithElements.LateBoundTranslateToGenericICollection(null, typeof(List)); + TranslateListWithConvertibleElements.LateBoundTranslateToGenericICollection(null, typeof(List)); + } + } + } + +} + +#endif diff --git a/src/ServiceStack.Text/JsConfig.cs b/src/ServiceStack.Text/JsConfig.cs index 9611064a2..455ac33ec 100644 --- a/src/ServiceStack.Text/JsConfig.cs +++ b/src/ServiceStack.Text/JsConfig.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; using System.IO; +using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Serialization; +using System.Text; using System.Threading; using ServiceStack.Text.Common; using ServiceStack.Text.Json; @@ -10,14 +12,10 @@ namespace ServiceStack.Text { - public static class - JsConfig + public static class JsConfig { static JsConfig() { - //In-built default serialization, to Deserialize Color struct do: - //JsConfig.SerializeFn = c => c.ToString().Replace("Color ", "").Replace("[", "").Replace("]", ""); - //JsConfig.DeSerializeFn = System.Drawing.Color.FromName; Reset(); LicenseUtils.Init(); } @@ -25,9 +23,23 @@ static JsConfig() // force deterministic initialization of static constructor public static void InitStatics() { } + /// + /// Mark JsConfig global config as initialized and assert it's no longer mutated + /// + /// + public static void Init() => Config.Init(); + + /// + /// Initialize global config and assert that it's no longer mutated + /// + /// + public static void Init(Config config) => Config.Init(config); + + public static bool HasInit => Config.HasInit; + public static JsConfigScope BeginScope() { - return new JsConfigScope(); + return new JsConfigScope(); //Populated with Config.Instance } public static JsConfigScope CreateScope(string config, JsConfigScope scope = null) @@ -84,13 +96,18 @@ public static JsConfigScope CreateScope(string config, JsConfigScope scope = nul case "includetypeinfo": scope.IncludeTypeInfo = boolValue; break; + case "i": + case "pp": //pretty-print + case "indent": + scope.Indent = boolValue; + break; case "eccn": case "emitcamelcasenames": - scope.EmitCamelCaseNames = boolValue; + scope.TextCase = boolValue ? TextCase.CamelCase : scope.TextCase; break; case "elun": case "emitlowercaseunderscorenames": - scope.EmitLowercaseUnderscoreNames = boolValue; + scope.TextCase = boolValue ? TextCase.SnakeCase : scope.TextCase; break; case "pi": case "preferinterfaces": @@ -98,7 +115,9 @@ public static JsConfigScope CreateScope(string config, JsConfigScope scope = nul break; case "tode": case "throwondeserializationerror": - scope.ThrowOnDeserializationError = boolValue; + case "toe": + case "throwonerror": + scope.ThrowOnError = boolValue; break; case "teai": case "treatenumasinteger": @@ -120,14 +139,6 @@ public static JsConfigScope CreateScope(string config, JsConfigScope scope = nul case "appendutcoffset": scope.AppendUtcOffset = boolValue; break; - case "eu": - case "escapeunicode": - scope.EscapeUnicode = boolValue; - break; - case "ehc": - case "escapehtmlchars": - scope.EscapeHtmlChars = boolValue; - break; case "ipf": case "includepublicfields": scope.IncludePublicFields = boolValue; @@ -196,12 +207,39 @@ public static JsConfigScope CreateScope(string config, JsConfigScope scope = nul break; } break; + case "tc": + case "textcase": + switch (value) + { + case "d": + case "default": + scope.TextCase = TextCase.Default; + break; + case "pc": + case "pascalcase": + scope.TextCase = TextCase.PascalCase; + break; + case "cc": + case "camelcase": + scope.TextCase = TextCase.CamelCase; + break; + case "sc": + case "snakecase": + scope.TextCase = TextCase.SnakeCase; + break; + } + break; } } return scope; } + public static UTF8Encoding UTF8Encoding { get; set; } = new UTF8Encoding(false); + + public static JsConfigScope With(Config config) => (JsConfigScope)new JsConfigScope().Populate(config); + + [Obsolete("Use JsConfig.With(new Config { })")] public static JsConfigScope With( bool? convertObjectTypesIntoStringDictionary = null, bool? tryToParsePrimitiveTypeValues = null, @@ -222,6 +260,7 @@ public static JsConfigScope With( bool? preferInterfaces = null, bool? throwOnDeserializationError = null, string typeAttr = null, + string dateTimeFormat = null, Func typeWriter = null, Func typeFinder = null, bool? treatEnumAsInteger = null, @@ -229,473 +268,269 @@ public static JsConfigScope With( bool? alwaysUseUtc = null, bool? assumeUtc = null, bool? appendUtcOffset = null, - bool? escapeUnicode = null, + bool? escapeUnicode = null, bool? includePublicFields = null, int? maxDepth = null, EmptyCtorFactoryDelegate modelFactory = null, string[] excludePropertyReferences = null, - bool? useSystemParseMethods = null) + bool? useSystemParseMethods = null) //Unused { return new JsConfigScope { - ConvertObjectTypesIntoStringDictionary = convertObjectTypesIntoStringDictionary ?? sConvertObjectTypesIntoStringDictionary, - TryToParsePrimitiveTypeValues = tryToParsePrimitiveTypeValues ?? sTryToParsePrimitiveTypeValues, - TryToParseNumericType = tryToParseNumericType ?? sTryToParseNumericType, - - ParsePrimitiveFloatingPointTypes = parsePrimitiveFloatingPointTypes ?? sParsePrimitiveFloatingPointTypes, - ParsePrimitiveIntegerTypes = parsePrimitiveIntegerTypes ?? sParsePrimitiveIntegerTypes, - - ExcludeDefaultValues = excludeDefaultValues ?? sExcludeDefaultValues, - IncludeNullValues = includeNullValues ?? sIncludeNullValues, - IncludeNullValuesInDictionaries = includeNullValuesInDictionaries ?? sIncludeNullValuesInDictionaries, - IncludeDefaultEnums = includeDefaultEnums ?? sIncludeDefaultEnums, - ExcludeTypeInfo = excludeTypeInfo ?? sExcludeTypeInfo, - IncludeTypeInfo = includeTypeInfo ?? sIncludeTypeInfo, - EmitCamelCaseNames = emitCamelCaseNames ?? sEmitCamelCaseNames, - EmitLowercaseUnderscoreNames = emitLowercaseUnderscoreNames ?? sEmitLowercaseUnderscoreNames, - DateHandler = dateHandler ?? sDateHandler, - TimeSpanHandler = timeSpanHandler ?? sTimeSpanHandler, - PropertyConvention = propertyConvention ?? sPropertyConvention, - PreferInterfaces = preferInterfaces ?? sPreferInterfaces, - ThrowOnDeserializationError = throwOnDeserializationError ?? sThrowOnDeserializationError, - TypeAttr = typeAttr ?? sTypeAttr, - TypeWriter = typeWriter ?? sTypeWriter, - TypeFinder = typeFinder ?? sTypeFinder, - TreatEnumAsInteger = treatEnumAsInteger ?? sTreatEnumAsInteger, - SkipDateTimeConversion = skipDateTimeConversion ?? sSkipDateTimeConversion, - AlwaysUseUtc = alwaysUseUtc ?? sAlwaysUseUtc, - AssumeUtc = assumeUtc ?? sAssumeUtc, - AppendUtcOffset = appendUtcOffset ?? sAppendUtcOffset, - EscapeUnicode = escapeUnicode ?? sEscapeUnicode, - IncludePublicFields = includePublicFields ?? sIncludePublicFields, - MaxDepth = maxDepth ?? sMaxDepth, - ModelFactory = modelFactory ?? ModelFactory, - ExcludePropertyReferences = excludePropertyReferences ?? sExcludePropertyReferences, + ConvertObjectTypesIntoStringDictionary = convertObjectTypesIntoStringDictionary ?? Config.Instance.ConvertObjectTypesIntoStringDictionary, + TryToParsePrimitiveTypeValues = tryToParsePrimitiveTypeValues ?? Config.Instance.TryToParsePrimitiveTypeValues, + TryToParseNumericType = tryToParseNumericType ?? Config.Instance.TryToParseNumericType, + + ParsePrimitiveFloatingPointTypes = parsePrimitiveFloatingPointTypes ?? Config.Instance.ParsePrimitiveFloatingPointTypes, + ParsePrimitiveIntegerTypes = parsePrimitiveIntegerTypes ?? Config.Instance.ParsePrimitiveIntegerTypes, + + ExcludeDefaultValues = excludeDefaultValues ?? Config.Instance.ExcludeDefaultValues, + IncludeNullValues = includeNullValues ?? Config.Instance.IncludeNullValues, + IncludeNullValuesInDictionaries = includeNullValuesInDictionaries ?? Config.Instance.IncludeNullValuesInDictionaries, + IncludeDefaultEnums = includeDefaultEnums ?? Config.Instance.IncludeDefaultEnums, + ExcludeTypeInfo = excludeTypeInfo ?? Config.Instance.ExcludeTypeInfo, + IncludeTypeInfo = includeTypeInfo ?? Config.Instance.IncludeTypeInfo, + EmitCamelCaseNames = emitCamelCaseNames ?? Config.Instance.EmitCamelCaseNames, + EmitLowercaseUnderscoreNames = emitLowercaseUnderscoreNames ?? Config.Instance.EmitLowercaseUnderscoreNames, + DateHandler = dateHandler ?? Config.Instance.DateHandler, + TimeSpanHandler = timeSpanHandler ?? Config.Instance.TimeSpanHandler, + PropertyConvention = propertyConvention ?? Config.Instance.PropertyConvention, + PreferInterfaces = preferInterfaces ?? Config.Instance.PreferInterfaces, + ThrowOnError = throwOnDeserializationError ?? Config.Instance.ThrowOnError, + DateTimeFormat = dateTimeFormat ?? Config.Instance.DateTimeFormat, + TypeAttr = typeAttr ?? Config.Instance.TypeAttr, + TypeWriter = typeWriter ?? Config.Instance.TypeWriter, + TypeFinder = typeFinder ?? Config.Instance.TypeFinder, + TreatEnumAsInteger = treatEnumAsInteger ?? Config.Instance.TreatEnumAsInteger, + SkipDateTimeConversion = skipDateTimeConversion ?? Config.Instance.SkipDateTimeConversion, + AlwaysUseUtc = alwaysUseUtc ?? Config.Instance.AlwaysUseUtc, + AssumeUtc = assumeUtc ?? Config.Instance.AssumeUtc, + AppendUtcOffset = appendUtcOffset ?? Config.Instance.AppendUtcOffset, + EscapeUnicode = escapeUnicode ?? Config.Instance.EscapeUnicode, + IncludePublicFields = includePublicFields ?? Config.Instance.IncludePublicFields, + MaxDepth = maxDepth ?? Config.Instance.MaxDepth, + ModelFactory = modelFactory ?? Config.Instance.ModelFactory, + ExcludePropertyReferences = excludePropertyReferences ?? Config.Instance.ExcludePropertyReferences, }; } - private static bool? sConvertObjectTypesIntoStringDictionary; + public static string DateTimeFormat + { + get => JsConfigScope.Current != null ? JsConfigScope.Current.DateTimeFormat : Config.Instance.DateTimeFormat; + set => Config.AssertNotInit().DateTimeFormat = value; + } + public static bool ConvertObjectTypesIntoStringDictionary { - get - { - return (JsConfigScope.Current != null ? JsConfigScope.Current.ConvertObjectTypesIntoStringDictionary : null) - ?? sConvertObjectTypesIntoStringDictionary - ?? false; - } - set - { - if (!sConvertObjectTypesIntoStringDictionary.HasValue) sConvertObjectTypesIntoStringDictionary = value; - } + get => JsConfigScope.Current != null ? JsConfigScope.Current.ConvertObjectTypesIntoStringDictionary : Config.Instance.ConvertObjectTypesIntoStringDictionary; + set => Config.AssertNotInit().ConvertObjectTypesIntoStringDictionary = value; } - private static bool? sTryToParsePrimitiveTypeValues; public static bool TryToParsePrimitiveTypeValues { - get - { - return (JsConfigScope.Current != null ? JsConfigScope.Current.TryToParsePrimitiveTypeValues : null) - ?? sTryToParsePrimitiveTypeValues - ?? false; - } - set - { - if (!sTryToParsePrimitiveTypeValues.HasValue) sTryToParsePrimitiveTypeValues = value; - } + get => JsConfigScope.Current != null ? JsConfigScope.Current.TryToParsePrimitiveTypeValues : Config.Instance.TryToParsePrimitiveTypeValues; + set => Config.AssertNotInit().TryToParsePrimitiveTypeValues = value; } - private static bool? sTryToParseNumericType; public static bool TryToParseNumericType { - get - { - return (JsConfigScope.Current != null ? JsConfigScope.Current.TryToParseNumericType : null) - ?? sTryToParseNumericType - ?? false; - } - set - { - if (!sTryToParseNumericType.HasValue) sTryToParseNumericType = value; - } + get => JsConfigScope.Current != null ? JsConfigScope.Current.TryToParseNumericType : Config.Instance.TryToParseNumericType; + set => Config.AssertNotInit().TryToParseNumericType = value; } - private static bool? sTryParseIntoBestFit; public static bool TryParseIntoBestFit { - get - { - return (JsConfigScope.Current != null ? JsConfigScope.Current.TryParseIntoBestFit : null) - ?? sTryParseIntoBestFit - ?? false; - } - set - { - if (!sTryParseIntoBestFit.HasValue) sTryParseIntoBestFit = value; - } + get => JsConfigScope.Current != null ? JsConfigScope.Current.TryParseIntoBestFit : Config.Instance.TryParseIntoBestFit; + set => Config.AssertNotInit().TryParseIntoBestFit = value; } - private static ParseAsType? sParsePrimitiveFloatingPointTypes; public static ParseAsType ParsePrimitiveFloatingPointTypes { - get - { - return (JsConfigScope.Current != null ? JsConfigScope.Current.ParsePrimitiveFloatingPointTypes : null) - ?? sParsePrimitiveFloatingPointTypes - ?? ParseAsType.Decimal; - } - set - { - if (sParsePrimitiveFloatingPointTypes == null) sParsePrimitiveFloatingPointTypes = value; - } + get => JsConfigScope.Current != null ? JsConfigScope.Current.ParsePrimitiveFloatingPointTypes : Config.Instance.ParsePrimitiveFloatingPointTypes; + set => Config.AssertNotInit().ParsePrimitiveFloatingPointTypes = value; } - private static ParseAsType? sParsePrimitiveIntegerTypes; public static ParseAsType ParsePrimitiveIntegerTypes { - get - { - return (JsConfigScope.Current != null ? JsConfigScope.Current.ParsePrimitiveIntegerTypes : null) - ?? sParsePrimitiveIntegerTypes - ?? ParseAsType.Byte | ParseAsType.SByte | ParseAsType.Int16 | ParseAsType.UInt16 | ParseAsType.Int32 | ParseAsType.UInt32 | ParseAsType.Int64 | ParseAsType.UInt64; - } - set - { - if (!sParsePrimitiveIntegerTypes.HasValue) sParsePrimitiveIntegerTypes = value; - } + get => JsConfigScope.Current != null ? JsConfigScope.Current.ParsePrimitiveIntegerTypes : Config.Instance.ParsePrimitiveIntegerTypes; + set => Config.AssertNotInit().ParsePrimitiveIntegerTypes = value; + } + + public static string[] ExcludePropertyReferences + { + get => JsConfigScope.Current != null ? JsConfigScope.Current.ExcludePropertyReferences : Config.Instance.ExcludePropertyReferences; + set => Config.AssertNotInit().ExcludePropertyReferences = value; } - private static bool? sExcludeDefaultValues; public static bool ExcludeDefaultValues { - get - { - return (JsConfigScope.Current != null ? JsConfigScope.Current.ExcludeDefaultValues : null) - ?? sExcludeDefaultValues - ?? false; - } - set - { - if (!sExcludeDefaultValues.HasValue) sExcludeDefaultValues = value; - } + get => JsConfigScope.Current != null ? JsConfigScope.Current.ExcludeDefaultValues : Config.Instance.ExcludeDefaultValues; + set => Config.AssertNotInit().ExcludeDefaultValues = value; } - private static bool? sIncludeNullValues; public static bool IncludeNullValues { - get - { - return (JsConfigScope.Current != null ? JsConfigScope.Current.IncludeNullValues : null) - ?? sIncludeNullValues - ?? false; - } - set - { - if (!sIncludeNullValues.HasValue) sIncludeNullValues = value; - } + get => JsConfigScope.Current != null ? JsConfigScope.Current.IncludeNullValues : Config.Instance.IncludeNullValues; + set => Config.AssertNotInit().IncludeNullValues = value; } - private static bool? sIncludeNullValuesInDictionaries; public static bool IncludeNullValuesInDictionaries { - get - { - return (JsConfigScope.Current != null ? JsConfigScope.Current.IncludeNullValuesInDictionaries : null) - ?? sIncludeNullValuesInDictionaries - ?? false; - } - set - { - if (!sIncludeNullValuesInDictionaries.HasValue) sIncludeNullValuesInDictionaries = value; - } + get => JsConfigScope.Current != null ? JsConfigScope.Current.IncludeNullValuesInDictionaries : Config.Instance.IncludeNullValuesInDictionaries; + set => Config.AssertNotInit().IncludeNullValuesInDictionaries = value; } - private static bool? sIncludeDefaultEnums; public static bool IncludeDefaultEnums { - get - { - return (JsConfigScope.Current != null ? JsConfigScope.Current.IncludeDefaultEnums : null) - ?? sIncludeDefaultEnums - ?? true; - } - set - { - if (!sIncludeDefaultEnums.HasValue) sIncludeDefaultEnums = value; - } + get => JsConfigScope.Current != null ? JsConfigScope.Current.IncludeDefaultEnums : Config.Instance.IncludeDefaultEnums; + set => Config.AssertNotInit().IncludeDefaultEnums = value; } - private static bool? sTreatEnumAsInteger; public static bool TreatEnumAsInteger { - get - { - return (JsConfigScope.Current != null ? JsConfigScope.Current.TreatEnumAsInteger : null) - ?? sTreatEnumAsInteger - ?? false; - } - set - { - if (!sTreatEnumAsInteger.HasValue) sTreatEnumAsInteger = value; - } + get => JsConfigScope.Current != null ? JsConfigScope.Current.TreatEnumAsInteger : Config.Instance.TreatEnumAsInteger; + set => Config.AssertNotInit().TreatEnumAsInteger = value; } - private static bool? sExcludeTypeInfo; public static bool ExcludeTypeInfo { - get - { - return (JsConfigScope.Current != null ? JsConfigScope.Current.ExcludeTypeInfo : null) - ?? sExcludeTypeInfo - ?? false; - } - set - { - if (!sExcludeTypeInfo.HasValue) sExcludeTypeInfo = value; - } + get => JsConfigScope.Current != null ? JsConfigScope.Current.ExcludeTypeInfo : Config.Instance.ExcludeTypeInfo; + set => Config.AssertNotInit().ExcludeTypeInfo = value; } - private static bool? sIncludeTypeInfo; public static bool IncludeTypeInfo { - get - { - return (JsConfigScope.Current != null ? JsConfigScope.Current.IncludeTypeInfo : null) - ?? sIncludeTypeInfo - ?? false; - } - set - { - if (!sIncludeTypeInfo.HasValue) sIncludeTypeInfo = value; - } + get => JsConfigScope.Current != null ? JsConfigScope.Current.IncludeTypeInfo : Config.Instance.IncludeTypeInfo; + set => Config.AssertNotInit().IncludeTypeInfo = value; + } + + public static bool Indent + { + get => JsConfigScope.Current != null ? JsConfigScope.Current.Indent : Config.Instance.Indent; + set => Config.AssertNotInit().Indent = value; } - private static string sTypeAttr; public static string TypeAttr { - get - { - return (JsConfigScope.Current != null ? JsConfigScope.Current.TypeAttr : null) - ?? sTypeAttr - ?? JsWriter.TypeAttr; - } + get => JsConfigScope.Current != null ? JsConfigScope.Current.TypeAttr : Config.Instance.TypeAttr; set { - if (sTypeAttr == null) sTypeAttr = value; - JsonTypeAttrInObject = JsonTypeSerializer.GetTypeAttrInObject(value); - JsvTypeAttrInObject = JsvTypeSerializer.GetTypeAttrInObject(value); + var config = Config.AssertNotInit(); + config.TypeAttr = value; } } - private static string sJsonTypeAttrInObject; - private static readonly string defaultJsonTypeAttrInObject = JsonTypeSerializer.GetTypeAttrInObject(TypeAttr); internal static string JsonTypeAttrInObject { - get - { - return (JsConfigScope.Current != null ? JsConfigScope.Current.JsonTypeAttrInObject : null) - ?? sJsonTypeAttrInObject - ?? defaultJsonTypeAttrInObject; - } - set - { - if (sJsonTypeAttrInObject == null) sJsonTypeAttrInObject = value; - } + get => JsConfigScope.Current != null ? JsConfigScope.Current.JsonTypeAttrInObject : Config.Instance.JsonTypeAttrInObject; } - private static string sJsvTypeAttrInObject; - private static readonly string defaultJsvTypeAttrInObject = JsvTypeSerializer.GetTypeAttrInObject(TypeAttr); internal static string JsvTypeAttrInObject { - get - { - return (JsConfigScope.Current != null ? JsConfigScope.Current.JsvTypeAttrInObject : null) - ?? sJsvTypeAttrInObject - ?? defaultJsvTypeAttrInObject; - } - set - { - if (sJsvTypeAttrInObject == null) sJsvTypeAttrInObject = value; - } + get => JsConfigScope.Current != null ? JsConfigScope.Current.JsvTypeAttrInObject : Config.Instance.JsvTypeAttrInObject; } - private static Func sTypeWriter; public static Func TypeWriter { - get - { - return (JsConfigScope.Current != null ? JsConfigScope.Current.TypeWriter : null) - ?? sTypeWriter - ?? AssemblyUtils.WriteType; - } - set - { - if (sTypeWriter == null) sTypeWriter = value; - } + get => JsConfigScope.Current != null ? JsConfigScope.Current.TypeWriter : Config.Instance.TypeWriter; + set => Config.AssertNotInit().TypeWriter = value; } - private static Func sTypeFinder; public static Func TypeFinder { - get - { - return (JsConfigScope.Current != null ? JsConfigScope.Current.TypeFinder : null) - ?? sTypeFinder - ?? AssemblyUtils.FindType; - } - set - { - if (sTypeFinder == null) sTypeFinder = value; - } + get => JsConfigScope.Current != null ? JsConfigScope.Current.TypeFinder : Config.Instance.TypeFinder; + set => Config.AssertNotInit().TypeFinder = value; } - private static Func sParsePrimitiveFn; public static Func ParsePrimitiveFn { - get - { - return (JsConfigScope.Current != null ? JsConfigScope.Current.ParsePrimitiveFn : null) - ?? sParsePrimitiveFn - ?? null; - } - set - { - if (sParsePrimitiveFn == null) sParsePrimitiveFn = value; - } + get => JsConfigScope.Current != null ? JsConfigScope.Current.ParsePrimitiveFn : Config.Instance.ParsePrimitiveFn; + set => Config.AssertNotInit().ParsePrimitiveFn = value; } - private static DateHandler? sDateHandler; public static DateHandler DateHandler { - get - { - return (JsConfigScope.Current != null ? JsConfigScope.Current.DateHandler : null) - ?? sDateHandler - ?? DateHandler.TimestampOffset; - } - set - { - if (!sDateHandler.HasValue) sDateHandler = value; - } + get => JsConfigScope.Current != null ? JsConfigScope.Current.DateHandler : Config.Instance.DateHandler; + set => Config.AssertNotInit().DateHandler = value; } /// /// Sets which format to use when serializing TimeSpans /// - private static TimeSpanHandler? sTimeSpanHandler; public static TimeSpanHandler TimeSpanHandler { - get - { - return (JsConfigScope.Current != null ? JsConfigScope.Current.TimeSpanHandler : null) - ?? sTimeSpanHandler - ?? TimeSpanHandler.DurationFormat; - } - set - { - if (!sTimeSpanHandler.HasValue) sTimeSpanHandler = value; - } + get => JsConfigScope.Current != null ? JsConfigScope.Current.TimeSpanHandler : Config.Instance.TimeSpanHandler; + set => Config.AssertNotInit().TimeSpanHandler = value; } + /// + /// Text case to use for property names (Default = PascalCase) + /// + public static TextCase TextCase + { + get => JsConfigScope.Current != null ? JsConfigScope.Current.TextCase : Config.Instance.TextCase; + set => Config.AssertNotInit().TextCase = value; + } /// - /// if the is configured - /// to take advantage of specification, - /// to support user-friendly serialized formats, ie emitting camelCasing for JSON - /// and parsing member names and enum values in a case-insensitive manner. + /// Emitting camelCase for property names /// - private static bool? sEmitCamelCaseNames; + [Obsolete("Use TextCase = TextCase.CamelCase")] public static bool EmitCamelCaseNames { - // obeying the use of ThreadStatic, but allowing for setting JsConfig once as is the normal case - get - { - return (JsConfigScope.Current != null ? JsConfigScope.Current.EmitCamelCaseNames : null) - ?? sEmitCamelCaseNames - ?? false; - } - set - { - if (!sEmitCamelCaseNames.HasValue) sEmitCamelCaseNames = value; - } + get => JsConfigScope.Current != null ? JsConfigScope.Current.EmitCamelCaseNames : Config.Instance.EmitCamelCaseNames; + set => Config.AssertNotInit().EmitCamelCaseNames = value; } /// - /// if the is configured - /// to support web-friendly serialized formats, ie emitting lowercase_underscore_casing for JSON + /// Emitting lowercase_underscore_casing for property names /// - private static bool? sEmitLowercaseUnderscoreNames; + [Obsolete("Use TextCase = TextCase.SnakeCase")] public static bool EmitLowercaseUnderscoreNames { // obeying the use of ThreadStatic, but allowing for setting JsConfig once as is the normal case - get - { - return (JsConfigScope.Current != null ? JsConfigScope.Current.EmitLowercaseUnderscoreNames : null) - ?? sEmitLowercaseUnderscoreNames - ?? false; - } - set - { - if (!sEmitLowercaseUnderscoreNames.HasValue) sEmitLowercaseUnderscoreNames = value; - } + get => JsConfigScope.Current != null ? JsConfigScope.Current.EmitLowercaseUnderscoreNames : Config.Instance.EmitLowercaseUnderscoreNames; + set => Config.AssertNotInit().EmitLowercaseUnderscoreNames = value; } + //Avoid multiple static property checks by getting snapshot of active config + public static Config GetConfig() => JsConfigScope.Current != null ? JsConfigScope.Current : Config.Instance; + /// /// Define how property names are mapped during deserialization /// - private static PropertyConvention? sPropertyConvention; public static PropertyConvention PropertyConvention { - get - { - return (JsConfigScope.Current != null ? JsConfigScope.Current.PropertyConvention : null) - ?? sPropertyConvention - ?? PropertyConvention.Strict; - } - set - { - if (!sPropertyConvention.HasValue) sPropertyConvention = value; - } + get => JsConfigScope.Current != null ? JsConfigScope.Current.PropertyConvention : Config.Instance.PropertyConvention; + set => Config.AssertNotInit().PropertyConvention = value; } - /// /// Gets or sets a value indicating if the framework should throw serialization exceptions - /// or continue regardless of deserialization errors. If the framework + /// or continue regardless of serialization errors. If the framework /// will throw; otherwise, it will parse as many fields as possible. The default is . /// - private static bool? sThrowOnDeserializationError; - public static bool ThrowOnDeserializationError + public static bool ThrowOnError { // obeying the use of ThreadStatic, but allowing for setting JsConfig once as is the normal case - get - { - return (JsConfigScope.Current != null ? JsConfigScope.Current.ThrowOnDeserializationError : null) - ?? sThrowOnDeserializationError - ?? false; - } - set - { - if (!sThrowOnDeserializationError.HasValue) sThrowOnDeserializationError = value; - } + get => JsConfigScope.Current != null ? JsConfigScope.Current.ThrowOnError : Config.Instance.ThrowOnError; + set => Config.AssertNotInit().ThrowOnError = value; } + [Obsolete("Renamed to ThrowOnError")] + public static bool ThrowOnDeserializationError + { + get => ThrowOnError; + set => ThrowOnError = value; + } + /// /// Gets or sets a value indicating if the framework should always convert to UTC format instead of local time. /// - private static bool? sAlwaysUseUtc; public static bool AlwaysUseUtc { - // obeying the use of ThreadStatic, but allowing for setting JsConfig once as is the normal case - get - { - return (JsConfigScope.Current != null ? JsConfigScope.Current.AlwaysUseUtc : null) - ?? sAlwaysUseUtc - ?? false; - } - set - { - if (!sAlwaysUseUtc.HasValue) sAlwaysUseUtc = value; - } + get => JsConfigScope.Current != null ? JsConfigScope.Current.AlwaysUseUtc : Config.Instance.AlwaysUseUtc; + set => Config.AssertNotInit().AlwaysUseUtc = value; } /// @@ -705,96 +540,50 @@ public static bool AlwaysUseUtc /// This will take precedence over other flags like AlwaysUseUtc /// JsConfig.DateHandler = DateHandler.ISO8601 should be used when set true for consistent de/serialization. /// - private static bool? sSkipDateTimeConversion; public static bool SkipDateTimeConversion { // obeying the use of ThreadStatic, but allowing for setting JsConfig once as is the normal case - get - { - return (JsConfigScope.Current != null ? JsConfigScope.Current.SkipDateTimeConversion : null) - ?? sSkipDateTimeConversion - ?? false; - } - set - { - if (!sSkipDateTimeConversion.HasValue) sSkipDateTimeConversion = value; - } + get => JsConfigScope.Current != null ? JsConfigScope.Current.SkipDateTimeConversion : Config.Instance.SkipDateTimeConversion; + set => Config.AssertNotInit().SkipDateTimeConversion = value; } + /// /// Gets or sets a value indicating if the framework should always assume is in UTC format if Kind is Unspecified. /// - private static bool? sAssumeUtc; public static bool AssumeUtc { // obeying the use of ThreadStatic, but allowing for setting JsConfig once as is the normal case - get - { - return (JsConfigScope.Current != null ? JsConfigScope.Current.AssumeUtc : null) - ?? sAssumeUtc - ?? false; - } - set - { - if (!sAssumeUtc.HasValue) sAssumeUtc = value; - } + get => JsConfigScope.Current != null ? JsConfigScope.Current.AssumeUtc : Config.Instance.AssumeUtc; + set => Config.AssertNotInit().AssumeUtc = value; } /// /// Gets or sets whether we should append the Utc offset when we serialize Utc dates. Defaults to no. /// Only supported for when the JsConfig.DateHandler == JsonDateHandler.TimestampOffset /// - private static bool? sAppendUtcOffset; - public static bool? AppendUtcOffset + public static bool AppendUtcOffset { // obeying the use of ThreadStatic, but allowing for setting JsConfig once as is the normal case - get - { - return (JsConfigScope.Current != null ? JsConfigScope.Current.AppendUtcOffset : null) - ?? sAppendUtcOffset - ?? null; - } - set - { - if (sAppendUtcOffset == null) sAppendUtcOffset = value; - } + get => JsConfigScope.Current != null ? JsConfigScope.Current.AppendUtcOffset : Config.Instance.AppendUtcOffset; + set => Config.AssertNotInit().AppendUtcOffset = value; } /// /// Gets or sets a value indicating if unicode symbols should be serialized as "\uXXXX". /// - private static bool? sEscapeUnicode; public static bool EscapeUnicode { - // obeying the use of ThreadStatic, but allowing for setting JsConfig once as is the normal case - get - { - return (JsConfigScope.Current != null ? JsConfigScope.Current.EscapeUnicode : null) - ?? sEscapeUnicode - ?? false; - } - set - { - if (!sEscapeUnicode.HasValue) sEscapeUnicode = value; - } + get => JsConfigScope.Current != null ? JsConfigScope.Current.EscapeUnicode : Config.Instance.EscapeUnicode; + set => Config.AssertNotInit().EscapeUnicode = value; } /// /// Gets or sets a value indicating if HTML entity chars [> < & = '] should be escaped as "\uXXXX". /// - private static bool? sEscapeHtmlChars; public static bool EscapeHtmlChars { - // obeying the use of ThreadStatic, but allowing for setting JsConfig once as is the normal case - get - { - return (JsConfigScope.Current != null ? JsConfigScope.Current.EscapeHtmlChars : null) - ?? sEscapeHtmlChars - ?? false; - } - set - { - if (!sEscapeHtmlChars.HasValue) sEscapeHtmlChars = value; - } + get => JsConfigScope.Current != null ? JsConfigScope.Current.EscapeHtmlChars : Config.Instance.EscapeHtmlChars; + set => Config.AssertNotInit().EscapeHtmlChars = value; } /// @@ -802,15 +591,10 @@ public static bool EscapeHtmlChars /// an exception happens during the deserialization. /// /// Parameters have following meaning in order: deserialized entity, property name, parsed value, property type, caught exception. - private static DeserializationErrorDelegate sOnDeserializationError; public static DeserializationErrorDelegate OnDeserializationError { - get - { - return (JsConfigScope.Current != null ? JsConfigScope.Current.OnDeserializationError : null) - ?? sOnDeserializationError; - } - set { sOnDeserializationError = value; } + get => JsConfigScope.Current != null ? JsConfigScope.Current.OnDeserializationError : Config.Instance.OnDeserializationError; + set => Config.AssertNotInit().OnDeserializationError = value; } internal static HashSet HasSerializeFn = new HashSet(); @@ -819,22 +603,13 @@ public static DeserializationErrorDelegate OnDeserializationError public static HashSet TreatValueAsRefTypes = new HashSet(); - private static bool? sPreferInterfaces; /// - /// If set to true, Interface types will be prefered over concrete types when serializing. + /// If set to true, Interface types will be preferred over concrete types when serializing. /// public static bool PreferInterfaces { - get - { - return (JsConfigScope.Current != null ? JsConfigScope.Current.PreferInterfaces : null) - ?? sPreferInterfaces - ?? false; - } - set - { - if (!sPreferInterfaces.HasValue) sPreferInterfaces = value; - } + get => JsConfigScope.Current != null ? JsConfigScope.Current.PreferInterfaces : Config.Instance.PreferInterfaces; + set => Config.AssertNotInit().PreferInterfaces = value; } internal static bool TreatAsRefType(Type valueType) @@ -842,41 +617,22 @@ internal static bool TreatAsRefType(Type valueType) return TreatValueAsRefTypes.Contains(valueType.IsGenericType ? valueType.GetGenericTypeDefinition() : valueType); } - /// - /// If set to true, Interface types will be prefered over concrete types when serializing. + /// If set to true, Interface types will be preferred over concrete types when serializing. /// - private static bool? sIncludePublicFields; public static bool IncludePublicFields { - get - { - return (JsConfigScope.Current != null ? JsConfigScope.Current.IncludePublicFields : null) - ?? sIncludePublicFields - ?? false; - } - set - { - if (!sIncludePublicFields.HasValue) sIncludePublicFields = value; - } + get => JsConfigScope.Current != null ? JsConfigScope.Current.IncludePublicFields : Config.Instance.IncludePublicFields; + set => Config.AssertNotInit().IncludePublicFields = value; } /// /// Sets the maximum depth to avoid circular dependencies /// - private static int? sMaxDepth; public static int MaxDepth { - get - { - return (JsConfigScope.Current != null ? JsConfigScope.Current.MaxDepth : null) - ?? sMaxDepth - ?? int.MaxValue; - } - set - { - if (!sMaxDepth.HasValue) sMaxDepth = value; - } + get => JsConfigScope.Current != null ? JsConfigScope.Current.MaxDepth : Config.Instance.MaxDepth; + set => Config.AssertNotInit().MaxDepth = value; } /// @@ -884,53 +640,28 @@ public static int MaxDepth /// This is helpful for integration with IoC containers where you need to call the container constructor. /// Return null if you don't know how to construct the type and the parameterless constructor will be used. /// - private static EmptyCtorFactoryDelegate sModelFactory; public static EmptyCtorFactoryDelegate ModelFactory { - get - { - return (JsConfigScope.Current != null ? JsConfigScope.Current.ModelFactory : null) - ?? sModelFactory - ?? null; - } - set - { - if (sModelFactory != null) sModelFactory = value; - } + get => JsConfigScope.Current != null ? JsConfigScope.Current.ModelFactory : Config.Instance.ModelFactory; + set => Config.AssertNotInit().ModelFactory = value; } - private static string[] sExcludePropertyReferences; - public static string[] ExcludePropertyReferences + public static HashSet ExcludeTypes { - get - { - return (JsConfigScope.Current != null ? JsConfigScope.Current.ExcludePropertyReferences : null) - ?? sExcludePropertyReferences; - } - set - { - if (sExcludePropertyReferences != null) sExcludePropertyReferences = value; - } + get => JsConfigScope.Current != null ? JsConfigScope.Current.ExcludeTypes : Config.Instance.ExcludeTypes; + set => Config.AssertNotInit().ExcludeTypes = value; } - private static HashSet sExcludeTypes; - public static HashSet ExcludeTypes + public static HashSet ExcludeTypeNames { - get - { - return (JsConfigScope.Current != null ? JsConfigScope.Current.ExcludeTypes : null) - ?? sExcludeTypes; - } - set - { - if (sExcludePropertyReferences != null) sExcludeTypes = value; - } + get => JsConfigScope.Current != null ? JsConfigScope.Current.ExcludeTypeNames : Config.Instance.ExcludeTypeNames; + set => Config.AssertNotInit().ExcludeTypeNames = value; } public static string[] IgnoreAttributesNamed { - set { ReflectionExtensions.IgnoreAttributesNamed = value; } - get { return ReflectionExtensions.IgnoreAttributesNamed; } + set => ReflectionExtensions.IgnoreAttributesNamed = value; + get => ReflectionExtensions.IgnoreAttributesNamed; } public static HashSet AllowRuntimeTypeWithAttributesNamed { get; set; } @@ -958,46 +689,16 @@ public static void Reset() Reset(uniqueType); } - sModelFactory = ReflectionExtensions.GetConstructorMethodToCache; - sTryToParsePrimitiveTypeValues = null; - sTryToParseNumericType = null; - sTryParseIntoBestFit = null; - sConvertObjectTypesIntoStringDictionary = null; - sExcludeDefaultValues = null; - sIncludeNullValues = null; - sIncludeNullValuesInDictionaries = null; - sExcludeTypeInfo = null; - sEmitCamelCaseNames = null; - sEmitLowercaseUnderscoreNames = null; - sDateHandler = null; - sTimeSpanHandler = null; - sPreferInterfaces = null; - sThrowOnDeserializationError = null; - sTypeAttr = null; - sJsonTypeAttrInObject = null; - sJsvTypeAttrInObject = null; - sTypeWriter = null; - sTypeFinder = null; - sParsePrimitiveFn = null; - sTreatEnumAsInteger = null; - sAlwaysUseUtc = null; - sAssumeUtc = null; - sSkipDateTimeConversion = null; - sAppendUtcOffset = null; - sEscapeUnicode = null; - sEscapeHtmlChars = null; - sOnDeserializationError = null; - sIncludePublicFields = null; + Env.StrictMode = false; + Config.Reset(); + AutoMappingUtils.Reset(); HasSerializeFn = new HashSet(); HasIncludeDefaultValue = new HashSet(); TreatValueAsRefTypes = new HashSet { typeof(KeyValuePair<,>) }; - sPropertyConvention = null; - sExcludePropertyReferences = null; - sExcludeTypes = new HashSet { typeof(Stream) }; __uniqueTypes = new HashSet(); - sMaxDepth = 50; - sParsePrimitiveIntegerTypes = null; - sParsePrimitiveFloatingPointTypes = null; + + //Called when writing each string, too expensive to maintain as scoped config + AllowRuntimeType = null; AllowRuntimeTypeWithAttributesNamed = new HashSet { @@ -1066,6 +767,14 @@ static JsConfig() RuntimeHelpers.RunClassConstructor(typeof(T).TypeHandle); } + internal static Config GetConfig() + { + var config = new Config().Populate(JsConfig.GetConfig()); + if (TextCase != TextCase.Default) + config.TextCase = TextCase; + return config; + } + /// /// Always emit type info for this type. Takes precedence over ExcludeTypeInfo /// @@ -1077,18 +786,33 @@ static JsConfig() public static bool? ExcludeTypeInfo = null; /// - /// if the is configured - /// to take advantage of specification, - /// to support user-friendly serialized formats, ie emitting camelCasing for JSON - /// and parsing member names and enum values in a case-insensitive manner. + /// Text case to use for property names (Default = PascalCase) + /// + public static TextCase TextCase { get; set; } + + /// + /// Emitting camelCase for property names /// - public static bool? EmitCamelCaseNames = null; + [Obsolete("Use TextCase = TextCase.CamelCase")] + public static bool? EmitCamelCaseNames + { + get => TextCase == TextCase.CamelCase; + set => TextCase = value == true ? TextCase.CamelCase : TextCase; + } - public static bool? EmitLowercaseUnderscoreNames = null; + /// + /// Emitting lowercase_underscore_casing for property names + /// + [Obsolete("Use TextCase = TextCase.SnakeCase")] + public static bool? EmitLowercaseUnderscoreNames + { + get => TextCase == TextCase.SnakeCase; + set => TextCase = value == true ? TextCase.SnakeCase : TextCase; + } public static bool IncludeDefaultValue { - get { return JsConfig.HasIncludeDefaultValue.Contains(typeof(T)); } + get => JsConfig.HasIncludeDefaultValue.Contains(typeof(T)); set { if (value) @@ -1106,7 +830,7 @@ public static bool IncludeDefaultValue private static Func serializeFn; public static Func SerializeFn { - get { return serializeFn; } + get => serializeFn; set { serializeFn = value; @@ -1124,7 +848,7 @@ public static Func SerializeFn /// public static bool TreatValueAsRefType { - get { return JsConfig.TreatValueAsRefTypes.Contains(typeof(T)); } + get => JsConfig.TreatValueAsRefTypes.Contains(typeof(T)); set { if (value) @@ -1137,10 +861,7 @@ public static bool TreatValueAsRefType /// /// Whether there is a fn (raw or otherwise) /// - public static bool HasSerializeFn - { - get { return !JsState.InSerializer() && (serializeFn != null || rawSerializeFn != null); } - } + public static bool HasSerializeFn => !JsState.InSerializer() && (serializeFn != null || rawSerializeFn != null); /// /// Define custom raw serialization fn @@ -1148,7 +869,7 @@ public static bool HasSerializeFn private static Func rawSerializeFn; public static Func RawSerializeFn { - get { return rawSerializeFn; } + get => rawSerializeFn; set { rawSerializeFn = value; @@ -1167,7 +888,7 @@ public static Func RawSerializeFn private static Func onSerializingFn; public static Func OnSerializingFn { - get { return onSerializingFn; } + get => onSerializingFn; set { onSerializingFn = value; RefreshWrite(); } } @@ -1177,7 +898,7 @@ public static Func OnSerializingFn private static Action onSerializedFn; public static Action OnSerializedFn { - get { return onSerializedFn; } + get => onSerializedFn; set { onSerializedFn = value; RefreshWrite(); } } @@ -1187,7 +908,7 @@ public static Action OnSerializedFn private static Func deSerializeFn; public static Func DeSerializeFn { - get { return deSerializeFn; } + get => deSerializeFn; set { deSerializeFn = value; RefreshRead(); } } @@ -1197,31 +918,25 @@ public static Func DeSerializeFn private static Func rawDeserializeFn; public static Func RawDeserializeFn { - get { return rawDeserializeFn; } + get => rawDeserializeFn; set { rawDeserializeFn = value; RefreshRead(); } } - public static bool HasDeserializeFn - { - get { return !JsState.InDeserializer() && (DeSerializeFn != null || RawDeserializeFn != null); } - } + public static bool HasDeserializeFn => !JsState.InDeserializer() && (DeSerializeFn != null || RawDeserializeFn != null); private static Func onDeserializedFn; public static Func OnDeserializedFn { - get { return onDeserializedFn; } + get => onDeserializedFn; set { onDeserializedFn = value; RefreshRead(); } } - public static bool HasDeserialingFn - { - get { return OnDeserializingFn != null; } - } + public static bool HasDeserializingFn => OnDeserializingFn != null; private static Func onDeserializingFn; public static Func OnDeserializingFn { - get { return onDeserializingFn; } + get => onDeserializingFn; set { onDeserializingFn = value; RefreshRead(); } } @@ -1313,7 +1028,8 @@ public static void Reset() RawSerializeFn = null; DeSerializeFn = null; ExcludePropertyNames = null; - EmitCamelCaseNames = EmitLowercaseUnderscoreNames = IncludeTypeInfo = ExcludeTypeInfo = null; + TextCase = TextCase.Default; + IncludeTypeInfo = ExcludeTypeInfo = null; } public static void RefreshRead() @@ -1364,5 +1080,26 @@ public enum TimeSpanHandler /// StandardFormat } + + public enum TextCase + { + /// + /// If unspecified uses PascalCase + /// + Default, + /// + /// PascalCase + /// + PascalCase, + /// + /// camelCase + /// + CamelCase, + /// + /// snake_case + /// + SnakeCase, + } + } diff --git a/src/ServiceStack.Text/JsConfigScope.cs b/src/ServiceStack.Text/JsConfigScope.cs index 55f767d23..81603de19 100644 --- a/src/ServiceStack.Text/JsConfigScope.cs +++ b/src/ServiceStack.Text/JsConfigScope.cs @@ -1,89 +1,274 @@ using System; +using System.Threading; using System.Collections.Generic; +using System.Reflection; +using ServiceStack.Text.Json; +using ServiceStack.Text.Jsv; using ServiceStack.Text.Common; namespace ServiceStack.Text { - public sealed class JsConfigScope : IDisposable + public sealed class JsConfigScope : Config, IDisposable { bool disposed; - JsConfigScope parent; + readonly JsConfigScope parent; - [ThreadStatic] - private static JsConfigScope head; +#if NETCORE + private static AsyncLocal head = new AsyncLocal(); +#else + [ThreadStatic] private static JsConfigScope head; +#endif internal JsConfigScope() { PclExport.Instance.BeginThreadAffinity(); +#if NETCORE + parent = head.Value; + head.Value = this; +#else parent = head; head = this; +#endif } - internal static JsConfigScope Current - { - get - { - return head; - } - } - - public static void DisposeCurrent() - { - if (head != null) - { - head.Dispose(); - } - } + internal static JsConfigScope Current => +#if NETCORE + head.Value; +#else + head; +#endif public void Dispose() { if (!disposed) { disposed = true; +#if NETCORE + head.Value = parent; +#else head = parent; +#endif PclExport.Instance.EndThreadAffinity(); } } + } + + public class Config + { + private static Config instance; + internal static Config Instance => instance ??= new Config(Defaults); + internal static bool HasInit = false; + + public static Config AssertNotInit() => HasInit + ? throw new NotSupportedException("JsConfig can't be mutated after JsConfig.Init(). Use BeginScope() or CreateScope() to use custom config after Init().") + : Instance; + + private static string InitStackTrace = null; + + public static void Init() => Init(null); + public static void Init(Config config) + { + if (HasInit && Env.StrictMode) + throw new NotSupportedException($"JsConfig has already been initialized at: {InitStackTrace}"); + + if (config != null) + instance = config; + + HasInit = true; + InitStackTrace = Environment.StackTrace; + } + + /// + /// Bypass Init checks. Only call on Startup. + /// + /// + public static void UnsafeInit(Config config) + { + if (config != null) + instance = config; + } + + internal static void Reset() + { + HasInit = false; + Instance.Populate(Defaults); + } - public bool? ConvertObjectTypesIntoStringDictionary { get; set; } - public bool? TryToParsePrimitiveTypeValues { get; set; } - public bool? TryToParseNumericType { get; set; } - public bool? TryParseIntoBestFit { get; set; } - public ParseAsType? ParsePrimitiveFloatingPointTypes { get; set; } - public ParseAsType? ParsePrimitiveIntegerTypes { get; set; } - public bool? ExcludeDefaultValues { get; set; } - public bool? IncludeNullValues { get; set; } - public bool? IncludeNullValuesInDictionaries { get; set; } - public bool? IncludeDefaultEnums { get; set; } - public bool? TreatEnumAsInteger { get; set; } - public bool? ExcludeTypeInfo { get; set; } - public bool? IncludeTypeInfo { get; set; } - public string TypeAttr { get; set; } - internal string JsonTypeAttrInObject { get; set; } - internal string JsvTypeAttrInObject { get; set; } + public Config() + { + Populate(Instance); + } + + private Config(Config config) + { + if (config != null) //Defaults=null, instance=Defaults + Populate(config); + } + + public bool ConvertObjectTypesIntoStringDictionary { get; set; } + public bool TryToParsePrimitiveTypeValues { get; set; } + public bool TryToParseNumericType { get; set; } + public bool TryParseIntoBestFit { get; set; } + public ParseAsType ParsePrimitiveFloatingPointTypes { get; set; } + public ParseAsType ParsePrimitiveIntegerTypes { get; set; } + public bool ExcludeDefaultValues { get; set; } + public bool IncludeNullValues { get; set; } + public bool IncludeNullValuesInDictionaries { get; set; } + public bool IncludeDefaultEnums { get; set; } + public bool TreatEnumAsInteger { get; set; } + public bool ExcludeTypeInfo { get; set; } + public bool IncludeTypeInfo { get; set; } + public bool Indent { get; set; } + + private string typeAttr; + public string TypeAttr + { + get => typeAttr; + set + { + typeAttrSpan = null; + jsonTypeAttrInObject = null; + jsvTypeAttrInObject = null; + typeAttr = value; + } + } + ReadOnlyMemory? typeAttrSpan = null; + public ReadOnlyMemory TypeAttrMemory => typeAttrSpan ??= TypeAttr.AsMemory(); + public string DateTimeFormat { get; set; } + private string jsonTypeAttrInObject; + internal string JsonTypeAttrInObject => jsonTypeAttrInObject ??= JsonTypeSerializer.GetTypeAttrInObject(TypeAttr); + private string jsvTypeAttrInObject; + internal string JsvTypeAttrInObject => jsvTypeAttrInObject ??= JsvTypeSerializer.GetTypeAttrInObject(TypeAttr); + public Func TypeWriter { get; set; } public Func TypeFinder { get; set; } public Func ParsePrimitiveFn { get; set; } - public DateHandler? DateHandler { get; set; } - public TimeSpanHandler? TimeSpanHandler { get; set; } - public PropertyConvention? PropertyConvention { get; set; } - public bool? EmitCamelCaseNames { get; set; } - public bool? EmitLowercaseUnderscoreNames { get; set; } - public bool? ThrowOnDeserializationError { get; set; } - public bool? SkipDateTimeConversion { get; set; } - public bool? AlwaysUseUtc { get; set; } - public bool? AssumeUtc { get; set; } - public bool? AppendUtcOffset { get; set; } - public bool? EscapeUnicode { get; set; } - public bool? EscapeHtmlChars { get; set; } - public bool? PreferInterfaces { get; set; } - public bool? IncludePublicFields { get; set; } - public int? MaxDepth { get; set; } + public DateHandler DateHandler { get; set; } + public TimeSpanHandler TimeSpanHandler { get; set; } + public PropertyConvention PropertyConvention { get; set; } + + public TextCase TextCase { get; set; } + + [Obsolete("Use TextCase = TextCase.CamelCase")] + public bool EmitCamelCaseNames + { + get => TextCase == TextCase.CamelCase; + set => TextCase = value ? TextCase.CamelCase : TextCase; + } + + [Obsolete("Use TextCase = TextCase.SnakeCase")] + public bool EmitLowercaseUnderscoreNames + { + get => TextCase == TextCase.SnakeCase; + set => TextCase = value ? TextCase.SnakeCase : TextCase.Default; + } + + public bool ThrowOnError { get; set; } + public bool SkipDateTimeConversion { get; set; } + public bool AlwaysUseUtc { get; set; } + public bool AssumeUtc { get; set; } + public bool AppendUtcOffset { get; set; } + public bool PreferInterfaces { get; set; } + public bool IncludePublicFields { get; set; } + public int MaxDepth { get; set; } public DeserializationErrorDelegate OnDeserializationError { get; set; } public EmptyCtorFactoryDelegate ModelFactory { get; set; } public string[] ExcludePropertyReferences { get; set; } public HashSet ExcludeTypes { get; set; } + public HashSet ExcludeTypeNames { get; set; } + public bool EscapeUnicode { get; set; } + public bool EscapeHtmlChars { get; set; } + + public static Config Defaults => new Config(null) { + ConvertObjectTypesIntoStringDictionary = false, + TryToParsePrimitiveTypeValues = false, + TryToParseNumericType = false, + TryParseIntoBestFit = false, + ParsePrimitiveFloatingPointTypes = ParseAsType.Decimal, + ParsePrimitiveIntegerTypes = ParseAsType.Byte | ParseAsType.SByte | ParseAsType.Int16 | ParseAsType.UInt16 | + ParseAsType.Int32 | ParseAsType.UInt32 | ParseAsType.Int64 | ParseAsType.UInt64, + ExcludeDefaultValues = false, + ExcludePropertyReferences = null, + IncludeNullValues = false, + IncludeNullValuesInDictionaries = false, + IncludeDefaultEnums = true, + TreatEnumAsInteger = false, + ExcludeTypeInfo = false, + IncludeTypeInfo = false, + Indent = false, + TypeAttr = JsWriter.TypeAttr, + DateTimeFormat = null, + TypeWriter = AssemblyUtils.WriteType, + TypeFinder = AssemblyUtils.FindType, + ParsePrimitiveFn = null, + DateHandler = Text.DateHandler.TimestampOffset, + TimeSpanHandler = Text.TimeSpanHandler.DurationFormat, + TextCase = TextCase.Default, + PropertyConvention = Text.PropertyConvention.Strict, + ThrowOnError = Env.StrictMode, + SkipDateTimeConversion = false, + AlwaysUseUtc = false, + AssumeUtc = false, + AppendUtcOffset = false, + EscapeUnicode = false, + EscapeHtmlChars = false, + PreferInterfaces = false, + IncludePublicFields = false, + MaxDepth = 50, + OnDeserializationError = null, + ModelFactory = ReflectionExtensions.GetConstructorMethodToCache, + ExcludeTypes = new HashSet { + typeof(System.IO.Stream), + typeof(System.Reflection.MethodBase), + }, + ExcludeTypeNames = new HashSet {} + }; + + public Config Populate(Config config) + { + ConvertObjectTypesIntoStringDictionary = config.ConvertObjectTypesIntoStringDictionary; + TryToParsePrimitiveTypeValues = config.TryToParsePrimitiveTypeValues; + TryToParseNumericType = config.TryToParseNumericType; + TryParseIntoBestFit = config.TryParseIntoBestFit; + ParsePrimitiveFloatingPointTypes = config.ParsePrimitiveFloatingPointTypes; + ParsePrimitiveIntegerTypes = config.ParsePrimitiveIntegerTypes; + ExcludeDefaultValues = config.ExcludeDefaultValues; + ExcludePropertyReferences = config.ExcludePropertyReferences; + IncludeNullValues = config.IncludeNullValues; + IncludeNullValuesInDictionaries = config.IncludeNullValuesInDictionaries; + IncludeDefaultEnums = config.IncludeDefaultEnums; + TreatEnumAsInteger = config.TreatEnumAsInteger; + ExcludeTypeInfo = config.ExcludeTypeInfo; + IncludeTypeInfo = config.IncludeTypeInfo; + Indent = config.Indent; + TypeAttr = config.TypeAttr; + DateTimeFormat = config.DateTimeFormat; + TypeWriter = config.TypeWriter; + TypeFinder = config.TypeFinder; + ParsePrimitiveFn = config.ParsePrimitiveFn; + DateHandler = config.DateHandler; + TimeSpanHandler = config.TimeSpanHandler; + TextCase = config.TextCase; + PropertyConvention = config.PropertyConvention; + ThrowOnError = config.ThrowOnError; + SkipDateTimeConversion = config.SkipDateTimeConversion; + AlwaysUseUtc = config.AlwaysUseUtc; + AssumeUtc = config.AssumeUtc; + AppendUtcOffset = config.AppendUtcOffset; + EscapeUnicode = config.EscapeUnicode; + EscapeHtmlChars = config.EscapeHtmlChars; + PreferInterfaces = config.PreferInterfaces; + IncludePublicFields = config.IncludePublicFields; + MaxDepth = config.MaxDepth; + OnDeserializationError = config.OnDeserializationError; + ModelFactory = config.ModelFactory; + ExcludeTypes = config.ExcludeTypes; + ExcludeTypeNames = config.ExcludeTypeNames; + return this; + } } + } + diff --git a/src/ServiceStack.Text/Json/JsonReader.Generic.cs b/src/ServiceStack.Text/Json/JsonReader.Generic.cs index b92b2fb94..11faa59d3 100644 --- a/src/ServiceStack.Text/Json/JsonReader.Generic.cs +++ b/src/ServiceStack.Text/Json/JsonReader.Generic.cs @@ -3,12 +3,9 @@ using System; using System.Collections.Generic; +using System.Runtime.CompilerServices; using System.Threading; using ServiceStack.Text.Common; -#if NETSTANDARD2_0 -using Microsoft.Extensions.Primitives; -#endif -using ServiceStack.Text.Support; namespace ServiceStack.Text.Json { @@ -18,36 +15,49 @@ public static class JsonReader private static Dictionary ParseFnCache = new Dictionary(); - internal static ParseStringDelegate GetParseFn(Type type) => v => GetParseStringSegmentFn(type)(new StringSegment(v)); + internal static ParseStringDelegate GetParseFn(Type type) => v => GetParseStringSpanFn(type)(v.AsSpan()); - internal static ParseStringSegmentDelegate GetParseStringSegmentFn(Type type) + internal static ParseStringSpanDelegate GetParseSpanFn(Type type) => v => GetParseStringSpanFn(type)(v); + + internal static ParseStringSpanDelegate GetParseStringSpanFn(Type type) { - ParseFactoryDelegate parseFactoryFn; - ParseFnCache.TryGetValue(type, out parseFactoryFn); + ParseFnCache.TryGetValue(type, out var parseFactoryFn); - if (parseFactoryFn != null) return parseFactoryFn(); + if (parseFactoryFn != null) + return parseFactoryFn(); var genericType = typeof(JsonReader<>).MakeGenericType(type); - var mi = genericType.GetStaticMethod("GetParseStringSegmentFn"); + var mi = genericType.GetStaticMethod(nameof(GetParseStringSpanFn)); parseFactoryFn = (ParseFactoryDelegate)mi.MakeDelegate(typeof(ParseFactoryDelegate)); Dictionary snapshot, newCache; do { snapshot = ParseFnCache; - newCache = new Dictionary(ParseFnCache); - newCache[type] = parseFactoryFn; + newCache = new Dictionary(ParseFnCache) + { + [type] = parseFactoryFn + }; } while (!ReferenceEquals( Interlocked.CompareExchange(ref ParseFnCache, newCache, snapshot), snapshot)); return parseFactoryFn(); } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] + public static void InitAot() + { + Text.Json.JsonReader.Instance.GetParseFn(); + Text.Json.JsonReader.Parse(TypeConstants.NullStringSpan); + Text.Json.JsonReader.GetParseFn(); + Text.Json.JsonReader.GetParseStringSpanFn(); + } } - public static class JsonReader + internal static class JsonReader { - private static ParseStringSegmentDelegate ReadFn; + private static ParseStringSpanDelegate ReadFn; static JsonReader() { @@ -61,22 +71,26 @@ public static void Refresh() if (JsonReader.Instance == null) return; - ReadFn = JsonReader.Instance.GetParseStringSegmentFn(); + ReadFn = JsonReader.Instance.GetParseStringSpanFn(); + JsConfig.AddUniqueType(typeof(T)); } - public static ParseStringDelegate GetParseFn() => - ReadFn != null - ? (ParseStringDelegate)(v => ReadFn(new StringSegment(v))) + public static ParseStringDelegate GetParseFn() => ReadFn != null + ? (ParseStringDelegate)(v => ReadFn(v.AsSpan())) : Parse; - public static ParseStringSegmentDelegate GetParseStringSegmentFn() => ReadFn ?? Parse; + public static ParseStringSpanDelegate GetParseStringSpanFn() => ReadFn ?? Parse; - public static object Parse(string value) => Parse(new StringSegment(value)); + public static object Parse(string value) => value != null + ? Parse(value.AsSpan()) + : null; - public static object Parse(StringSegment value) + public static object Parse(ReadOnlySpan value) { TypeConfig.Init(); + value = value.WithoutBom(); + if (ReadFn == null) { if (typeof(T).IsAbstract || typeof(T).IsInterface) @@ -85,7 +99,7 @@ public static object Parse(StringSegment value) var concreteType = DeserializeType.ExtractType(value); if (concreteType != null) { - return JsonReader.GetParseStringSegmentFn(concreteType)(value); + return JsonReader.GetParseStringSpanFn(concreteType)(value); } throw new NotSupportedException("Can not deserialize interface type: " + typeof(T).Name); @@ -94,9 +108,9 @@ public static object Parse(StringSegment value) Refresh(); } - return value.HasValue - ? ReadFn(value) - : null; + return !value.IsEmpty + ? ReadFn(value) + : null; } } } \ No newline at end of file diff --git a/src/ServiceStack.Text/Json/JsonTypeSerializer.cs b/src/ServiceStack.Text/Json/JsonTypeSerializer.cs index 737e4e98f..4d38b238d 100644 --- a/src/ServiceStack.Text/Json/JsonTypeSerializer.cs +++ b/src/ServiceStack.Text/Json/JsonTypeSerializer.cs @@ -5,56 +5,31 @@ using System.Globalization; using System.IO; using System.Runtime.CompilerServices; -using System.Text; +using System.Runtime.Serialization; using ServiceStack.Text.Common; -using ServiceStack.Text.Pools; -#if NETSTANDARD2_0 -using Microsoft.Extensions.Primitives; -#endif -using ServiceStack.Text.Support; - namespace ServiceStack.Text.Json { - public class JsonTypeSerializer + public struct JsonTypeSerializer : ITypeSerializer { public static ITypeSerializer Instance = new JsonTypeSerializer(); - public bool IncludeNullValues - { - get { return JsConfig.IncludeNullValues; } - } + public ObjectDeserializerDelegate ObjectDeserializer { get; set; } - public bool IncludeNullValuesInDictionaries - { - get { return JsConfig.IncludeNullValuesInDictionaries; } - } + public bool IncludeNullValues => JsConfig.IncludeNullValues; - public string TypeAttrInObject - { - get { return JsConfig.JsonTypeAttrInObject; } - } + public bool IncludeNullValuesInDictionaries => JsConfig.IncludeNullValuesInDictionaries; - internal static string GetTypeAttrInObject(string typeAttr) - { - return string.Format("{{\"{0}\":", typeAttr); - } + public string TypeAttrInObject => JsConfig.JsonTypeAttrInObject; - public WriteObjectDelegate GetWriteFn() - { - return JsonWriter.WriteFn(); - } + internal static string GetTypeAttrInObject(string typeAttr) => $"{{\"{typeAttr}\":"; - public WriteObjectDelegate GetWriteFn(Type type) - { - return JsonWriter.GetWriteFn(type); - } + public WriteObjectDelegate GetWriteFn() => JsonWriter.WriteFn(); - public TypeInfo GetTypeInfo(Type type) - { - return JsonWriter.GetTypeInfo(type); - } + public WriteObjectDelegate GetWriteFn(Type type) => JsonWriter.GetWriteFn(type); + + public TypeInfo GetTypeInfo(Type type) => JsonWriter.GetTypeInfo(type); /// /// Shortcut escape when we're sure value doesn't contain any escaped chars @@ -98,13 +73,13 @@ public void WriteBuiltIn(TextWriter writer, object value) public void WriteObjectString(TextWriter writer, object value) { - JsonUtils.WriteString(writer, value != null ? value.ToString() : null); + JsonUtils.WriteString(writer, value?.ToString()); } public void WriteFormattableObjectString(TextWriter writer, object value) { var formattable = value as IFormattable; - JsonUtils.WriteString(writer, formattable != null ? formattable.ToString(null, CultureInfo.InvariantCulture) : null); + JsonUtils.WriteString(writer, formattable?.ToString(null, CultureInfo.InvariantCulture)); } public void WriteException(TextWriter writer, object value) @@ -163,7 +138,6 @@ public void WriteTimeSpan(TextWriter writer, object oTimeSpan) public void WriteNullableTimeSpan(TextWriter writer, object oTimeSpan) { - if (oTimeSpan == null) return; WriteTimeSpan(writer, ((TimeSpan?)oTimeSpan).Value); } @@ -305,185 +279,259 @@ public void WriteDecimal(TextWriter writer, object decimalValue) public void WriteEnum(TextWriter writer, object enumValue) { - if (enumValue == null) return; - if (GetTypeInfo(enumValue.GetType()).IsNumeric) + if (enumValue == null) + return; + var serializedValue = CachedTypeInfo.Get(enumValue.GetType()).EnumInfo.GetSerializedValue(enumValue); + if (serializedValue is string strEnum) + WriteRawString(writer, strEnum); + else JsWriter.WriteEnumFlags(writer, enumValue); + } + + +#if NET6_0 + public void WriteDateOnly(TextWriter writer, object oDateOnly) + { + var dateOnly = (DateOnly)oDateOnly; + switch (JsConfig.DateHandler) + { + case DateHandler.UnixTime: + writer.Write(dateOnly.ToUnixTime()); + break; + case DateHandler.UnixTimeMs: + writer.Write(dateOnly.ToUnixTimeMs()); + break; + default: + writer.Write(JsWriter.QuoteString); + writer.Write(dateOnly.ToString("O")); + writer.Write(JsWriter.QuoteString); + break; + } + } + + public void WriteNullableDateOnly(TextWriter writer, object oDateOnly) + { + if (oDateOnly == null) + writer.Write(JsonUtils.Null); else - WriteRawString(writer, enumValue.ToString()); + WriteDateOnly(writer, oDateOnly); } - public void WriteEnumFlags(TextWriter writer, object enumFlagValue) + public void WriteTimeOnly(TextWriter writer, object oTimeOnly) { - JsWriter.WriteEnumFlags(writer, enumFlagValue); + var stringValue = JsConfig.TimeSpanHandler == TimeSpanHandler.StandardFormat + ? oTimeOnly.ToString() + : DateTimeSerializer.ToXsdTimeSpanString(((TimeOnly)oTimeOnly).ToTimeSpan()); + WriteRawString(writer, stringValue); + } + + public void WriteNullableTimeOnly(TextWriter writer, object oTimeOnly) + { + if (oTimeOnly == null) return; + WriteTimeSpan(writer, ((TimeOnly?)oTimeOnly).Value.ToTimeSpan()); } +#endif + [MethodImpl(MethodImplOptions.AggressiveInlining)] public ParseStringDelegate GetParseFn() { return JsonReader.Instance.GetParseFn(); } - public ParseStringSegmentDelegate GetParseStringSegmentFn() + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ParseStringSpanDelegate GetParseStringSpanFn() { - return JsonReader.Instance.GetParseStringSegmentFn(); + return JsonReader.Instance.GetParseStringSpanFn(); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public ParseStringDelegate GetParseFn(Type type) { return JsonReader.GetParseFn(type); } - public ParseStringSegmentDelegate GetParseStringSegmentFn(Type type) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ParseStringSpanDelegate GetParseStringSpanFn(Type type) { - return JsonReader.GetParseStringSegmentFn(type); + return JsonReader.GetParseStringSpanFn(type); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public string ParseRawString(string value) { return value; } - public string ParseString(StringSegment value) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string ParseString(ReadOnlySpan value) { - return value.IsNullOrEmpty() ? value.Value : ParseRawString(value.Value); + return value.IsNullOrEmpty() ? null : ParseRawString(value.ToString()); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public string ParseString(string value) { return string.IsNullOrEmpty(value) ? value : ParseRawString(value); } - public static bool IsEmptyMap(string value, int i = 1) => IsEmptyMap(new StringSegment(value)); - - public static bool IsEmptyMap(StringSegment value, int i = 1) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsEmptyMap(ReadOnlySpan value, int i = 1) { - for (; i < value.Length; i++) { var c = value.GetChar(i); if (!JsonUtils.IsWhiteSpace(c)) break; } //Whitespace inline + for (; i < value.Length; i++) { var c = value[i]; if (!JsonUtils.IsWhiteSpace(c)) break; } //Whitespace inline if (value.Length == i) return true; - return value.GetChar(i++) == JsWriter.MapEndChar; + return value[i++] == JsWriter.MapEndChar; } - internal static string ParseString(string json, ref int index) - { - return ParseString(new StringSegment(json), ref index).Value; - } - - internal static StringSegment ParseString(StringSegment json, ref int index) + internal static ReadOnlySpan ParseString(ReadOnlySpan json, ref int index) { var jsonLength = json.Length; - var buffer = json.Buffer; - var offset = json.Offset; - if (buffer[offset + index] != JsonUtils.QuoteChar) - throw new Exception("Invalid unquoted string starting with: " + json.Value.SafeSubstring(50)); + if (json[index] != JsonUtils.QuoteChar) + throw new Exception("Invalid unquoted string starting with: " + json.SafeSubstring(50).ToString()); var startIndex = ++index; do { - char c = buffer[offset + index]; - if (c == JsonUtils.QuoteChar) break; - if (c != JsonUtils.EscapeChar) continue; - c = buffer[offset + index]; - index++; - if (c == 'u') + var c = json[index]; + + if (c == JsonUtils.QuoteChar) + break; + + if (c == JsonUtils.EscapeChar) { - index += 4; + index++; + if (json[index] == 'u') + index += 4; } + } while (index++ < jsonLength); + if (index == jsonLength) - throw new Exception("Invalid unquoted string ending with: " + json.Value.SafeSubstring(json.Length - 50, 50)); + throw new Exception("Invalid unquoted string ending with: " + json.SafeSubstring(json.Length - 50, 50).ToString()); + index++; - return new StringSegment(buffer, offset + startIndex, Math.Min(index, jsonLength) - startIndex - 1); + var str = json.Slice(startIndex, Math.Min(index, jsonLength) - startIndex - 1); + if (str.Length == 0) + return TypeConstants.EmptyStringSpan; + + return str; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public string UnescapeString(string value) { var i = 0; - return UnEscapeJsonString(value, ref i); + return UnescapeJsonString(value, ref i); } - public StringSegment UnescapeString(StringSegment value) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlySpan UnescapeString(ReadOnlySpan value) { var i = 0; - return UnEscapeJsonString(value, ref i); + return UnescapeJsonString(value, ref i); } - public string UnescapeSafeString(string value) => UnescapeSafeString(new StringSegment(value)).Value; - - public StringSegment UnescapeSafeString(StringSegment value) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public object UnescapeStringAsObject(ReadOnlySpan value) { - if (value.IsNullOrEmpty()) return value; - return value.GetChar(0) == JsonUtils.QuoteChar && value.GetChar(value.Length - 1) == JsonUtils.QuoteChar - ? value.Subsegment(1, value.Length - 2) - : value; + var ignore = 0; + return UnescapeJsString(value, JsonUtils.QuoteChar, removeQuotes: true, ref ignore).Value(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string UnescapeSafeString(string value) => UnescapeSafeString(value.AsSpan()).ToString(); - //if (value[0] != JsonUtils.QuoteChar) - // throw new Exception("Invalid unquoted string starting with: " + value.SafeSubstring(50)); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlySpan UnescapeSafeString(ReadOnlySpan value) + { + if (value.IsEmpty) + return value; - //return value.Substring(1, value.Length - 2); + if (value[0] == JsonUtils.QuoteChar && value[value.Length - 1] == JsonUtils.QuoteChar) + return value.Slice(1, value.Length - 2); + + return value; } - static readonly char[] IsSafeJsonChars = new[] { JsonUtils.QuoteChar, JsonUtils.EscapeChar }; + static readonly char[] IsSafeJsonChars = { JsonUtils.QuoteChar, JsonUtils.EscapeChar }; - internal static StringSegment ParseJsonString(StringSegment json, ref int index) + internal static ReadOnlySpan ParseJsonString(ReadOnlySpan json, ref int index) { - for (; index < json.Length; index++) { var ch = json.GetChar(index); if (!JsonUtils.IsWhiteSpace(ch)) break; } //Whitespace inline + for (; index < json.Length; index++) { var ch = json[index]; if (!JsonUtils.IsWhiteSpace(ch)) break; } //Whitespace inline - return UnEscapeJsonString(json, ref index); + return UnescapeJsonString(json, ref index); } - private static string UnEscapeJsonString(string json, ref int index) + private static string UnescapeJsonString(string json, ref int index) { - return UnEscapeJsonString(new StringSegment(json), ref index).Value; + return json != null + ? UnescapeJsonString(json.AsSpan(), ref index).ToString() + : null; } - private static StringSegment UnEscapeJsonString(StringSegment json, ref int index) + private static ReadOnlySpan UnescapeJsonString(ReadOnlySpan json, ref int index) => + UnescapeJsString(json, JsonUtils.QuoteChar, removeQuotes:true, ref index); + + public static ReadOnlySpan UnescapeJsString(ReadOnlySpan json, char quoteChar) + { + var ignore = 0; + return UnescapeJsString(json, quoteChar, removeQuotes:false, ref ignore); + } + + public static ReadOnlySpan UnescapeJsString(ReadOnlySpan json, char quoteChar, bool removeQuotes, ref int index) { if (json.IsNullOrEmpty()) return json; var jsonLength = json.Length; - var buffer = json.Buffer; - var offset = json.Offset; + var buffer = json; - var firstChar = buffer[offset + index]; - if (firstChar == JsonUtils.QuoteChar) + var firstChar = buffer[index]; + if (firstChar == quoteChar) { index++; //MicroOp: See if we can short-circuit evaluation (to avoid StringBuilder) - var strEndPos = json.IndexOfAny(IsSafeJsonChars, index); - if (strEndPos == -1) return json.Subsegment(index, jsonLength - index); + var jsonAtIndex = json.Slice(index); + var strEndPos = jsonAtIndex.IndexOfAny(IsSafeJsonChars); + if (strEndPos == -1) + return jsonAtIndex.Slice(0, jsonLength); - if (json.GetChar(strEndPos) == JsonUtils.QuoteChar) + if (jsonAtIndex[strEndPos] == quoteChar) { - var potentialValue = json.Subsegment(index, strEndPos - index); - index = strEndPos + 1; - return potentialValue; + var potentialValue = jsonAtIndex.Slice(0, strEndPos); + index += strEndPos + 1; + return potentialValue.Length > 0 + ? potentialValue + : TypeConstants.EmptyStringSpan; } } else { - var i = index + offset; - var end = offset + jsonLength; + var i = index; + var end = jsonLength; while (i < end) { var c = buffer[i]; - if (c == JsonUtils.QuoteChar || c == JsonUtils.EscapeChar) + if (c == quoteChar || c == JsonUtils.EscapeChar) break; i++; } - if (i == end) return new StringSegment(buffer, offset + index, jsonLength - index); + if (i == end) + return buffer.Slice(index, jsonLength - index); } - return Unescape(json); + return Unescape(json, removeQuotes:removeQuotes, quoteChar:quoteChar); } - + public static string Unescape(string input) => Unescape(input, true); - public static string Unescape(string input, bool removeQuotes) - { - return Unescape(new StringSegment(input), removeQuotes).Value; - } + public static string Unescape(string input, bool removeQuotes) => Unescape(input.AsSpan(), removeQuotes).ToString(); + + public static ReadOnlySpan Unescape(ReadOnlySpan input) => Unescape(input, true); - public static StringSegment Unescape(StringSegment input) => Unescape(input, true); - public static StringSegment Unescape(StringSegment input, bool removeQuotes) + public static ReadOnlySpan Unescape(ReadOnlySpan input, bool removeQuotes) => + Unescape(input, removeQuotes, JsonUtils.QuoteChar); + + public static ReadOnlySpan Unescape(ReadOnlySpan input, bool removeQuotes, char quoteChar) { var length = input.Length; int start = 0; @@ -491,13 +539,14 @@ public static StringSegment Unescape(StringSegment input, bool removeQuotes) var output = StringBuilderThreadStatic.Allocate(); for (; count < length;) { + var c = input[count]; if (removeQuotes) { - if (input.GetChar(count) == JsonUtils.QuoteChar) + if (c == quoteChar) { if (start != count) { - output.Append(input.Buffer, input.Offset + start, count - start); + output.Append(input.Slice(start, count - start)); } count++; start = count; @@ -505,18 +554,18 @@ public static StringSegment Unescape(StringSegment input, bool removeQuotes) } } - if (input.GetChar(count) == JsonUtils.EscapeChar) + if (c == JsonUtils.EscapeChar) { if (start != count) { - output.Append(input.Buffer, input.Offset + start, count - start); + output.Append(input.Slice(start, count - start)); } start = count; count++; if (count >= length) continue; //we will always be parsing an escaped char here - var c = input.GetChar(count); + c = input[count]; switch (c) { @@ -551,8 +600,8 @@ public static StringSegment Unescape(StringSegment input, bool removeQuotes) case 'u': if (count + 4 < length) { - var unicodeString = input.Substring(count + 1, 4); - var unicodeIntVal = UInt32.Parse(unicodeString, NumberStyles.HexNumber); + var unicodeString = input.Slice(count + 1, 4); + var unicodeIntVal = MemoryProvider.Instance.ParseUInt32(unicodeString, NumberStyles.HexNumber); output.Append(ConvertFromUtf32((int)unicodeIntVal)); count += 5; } @@ -564,22 +613,22 @@ public static StringSegment Unescape(StringSegment input, bool removeQuotes) case 'x': if (count + 4 < length) { - var unicodeString = input.Substring(count + 1, 4); - var unicodeIntVal = uint.Parse(unicodeString, NumberStyles.HexNumber); + var unicodeString = input.Slice(count + 1, 4); + var unicodeIntVal = MemoryProvider.Instance.ParseUInt32(unicodeString, NumberStyles.HexNumber); output.Append(ConvertFromUtf32((int)unicodeIntVal)); count += 5; } else if (count + 2 < length) { - var unicodeString = input.Substring(count + 1, 2); - var unicodeIntVal = uint.Parse(unicodeString, NumberStyles.HexNumber); + var unicodeString = input.Slice(count + 1, 2); + var unicodeIntVal = MemoryProvider.Instance.ParseUInt32(unicodeString, NumberStyles.HexNumber); output.Append(ConvertFromUtf32((int)unicodeIntVal)); count += 3; } else { - output.Append(input.Buffer, input.Offset + start, count - start); + output.Append(input.Slice(start, count - start)); } break; default: @@ -594,8 +643,8 @@ public static StringSegment Unescape(StringSegment input, bool removeQuotes) count++; } } - output.Append(input.Buffer, input.Offset + start, length - start); - return new StringSegment(StringBuilderThreadStatic.ReturnAndFree(output)); + output.Append(input.Slice(start, length - start)); + return StringBuilderThreadStatic.ReturnAndFree(output).AsSpan(); } /// @@ -607,12 +656,11 @@ public static StringSegment Unescape(StringSegment input, bool removeQuotes) public static string ConvertFromUtf32(int utf32) { if (utf32 < 0 || utf32 > 0x10FFFF) - throw new ArgumentOutOfRangeException("utf32", "The argument must be from 0 to 0x10FFFF."); + throw new ArgumentOutOfRangeException(nameof(utf32), "The argument must be from 0 to 0x10FFFF."); if (utf32 < 0x10000) return new string((char)utf32, 1); utf32 -= 0x10000; - return new string(new[] {(char) ((utf32 >> 10) + 0xD800), - (char) (utf32 % 0x0400 + 0xDC00)}); + return new string(new[] {(char) ((utf32 >> 10) + 0xD800), (char) (utf32 % 0x0400 + 0xDC00)}); } public string EatTypeValue(string value, ref int i) @@ -620,35 +668,35 @@ public string EatTypeValue(string value, ref int i) return EatValue(value, ref i); } - public StringSegment EatTypeValue(StringSegment value, ref int i) + public ReadOnlySpan EatTypeValue(ReadOnlySpan value, ref int i) { return EatValue(value, ref i); } - public bool EatMapStartChar(string value, ref int i) => EatMapStartChar(new StringSegment(value), ref i); + public bool EatMapStartChar(string value, ref int i) => EatMapStartChar(value.AsSpan(), ref i); - public bool EatMapStartChar(StringSegment value, ref int i) + public bool EatMapStartChar(ReadOnlySpan value, ref int i) { - for (; i < value.Length; i++) { var c = value.GetChar(i); if (!JsonUtils.IsWhiteSpace(c)) break; } //Whitespace inline - return value.GetChar(i++) == JsWriter.MapStartChar; + for (; i < value.Length; i++) { var c = value[i]; if (!JsonUtils.IsWhiteSpace(c)) break; } //Whitespace inline + return value[i++] == JsWriter.MapStartChar; } - public string EatMapKey(string value, ref int i) => EatMapKey(new StringSegment(value), ref i).Value; + public string EatMapKey(string value, ref int i) => EatMapKey(value.AsSpan(), ref i).ToString(); - public StringSegment EatMapKey(StringSegment value, ref int i) + public ReadOnlySpan EatMapKey(ReadOnlySpan value, ref int i) { var valueLength = value.Length; - for (; i < value.Length; i++) { var c = value.GetChar(i); if (!JsonUtils.IsWhiteSpace(c)) break; } //Whitespace inline + for (; i < value.Length; i++) { var c = value[i]; if (!JsonUtils.IsWhiteSpace(c)) break; } //Whitespace inline var tokenStartPos = i; - var valueChar = value.GetChar(i); + var valueChar = value[i]; switch (valueChar) { //If we are at the end, return. case JsWriter.ItemSeperator: case JsWriter.MapEndChar: - return default(StringSegment); + return default(ReadOnlySpan); //Is Within Quotes, i.e. "..." case JsWriter.QuoteChar: @@ -658,7 +706,7 @@ public StringSegment EatMapKey(StringSegment value, ref int i) //Is Value while (++i < valueLength) { - valueChar = value.GetChar(i); + valueChar = value[i]; if (valueChar == JsWriter.ItemSeperator //If it doesn't have quotes it's either a keyword or number so also has a ws boundary @@ -669,46 +717,47 @@ public StringSegment EatMapKey(StringSegment value, ref int i) } } - return value.Subsegment(tokenStartPos, i - tokenStartPos); + return value.Slice(tokenStartPos, i - tokenStartPos); } - public bool EatMapKeySeperator(string value, ref int i) => EatMapKeySeperator(new StringSegment(value), ref i); + public bool EatMapKeySeperator(string value, ref int i) => EatMapKeySeperator(value.AsSpan(), ref i); - public bool EatMapKeySeperator(StringSegment value, ref int i) + public bool EatMapKeySeperator(ReadOnlySpan value, ref int i) { - for (; i < value.Length; i++) { var c = value.GetChar(i); if (!JsonUtils.IsWhiteSpace(c)) break; } //Whitespace inline + for (; i < value.Length; i++) { var c = value[i]; if (!JsonUtils.IsWhiteSpace(c)) break; } //Whitespace inline if (value.Length == i) return false; - return value.GetChar(i++) == JsWriter.MapKeySeperator; + return value[i++] == JsWriter.MapKeySeperator; } public bool EatItemSeperatorOrMapEndChar(string value, ref int i) { - return EatItemSeperatorOrMapEndChar(new StringSegment(value), ref i); + return EatItemSeperatorOrMapEndChar(value.AsSpan(), ref i); } - public bool EatItemSeperatorOrMapEndChar(StringSegment value, ref int i) + public bool EatItemSeperatorOrMapEndChar(ReadOnlySpan value, ref int i) { - for (; i < value.Length; i++) { var c = value.GetChar(i); if (!JsonUtils.IsWhiteSpace(c)) break; } //Whitespace inline + for (; i < value.Length; i++) { var c = value[i]; if (!JsonUtils.IsWhiteSpace(c)) break; } //Whitespace inline if (i == value.Length) return false; - var success = value.GetChar(i) == JsWriter.ItemSeperator - || value.GetChar(i) == JsWriter.MapEndChar; - - i++; + var success = value[i] == JsWriter.ItemSeperator || value[i] == JsWriter.MapEndChar; if (success) { - for (; i < value.Length; i++) { var c = value.GetChar(i); if (!JsonUtils.IsWhiteSpace(c)) break; } //Whitespace inline + i++; + + for (; i < value.Length; i++) { var c = value[i]; if (!JsonUtils.IsWhiteSpace(c)) break; } //Whitespace inline } + else if (Env.StrictMode) throw new Exception( + $"Expected '{JsWriter.ItemSeperator}' or '{JsWriter.MapEndChar}'"); return success; } - public void EatWhitespace(StringSegment value, ref int i) + public void EatWhitespace(ReadOnlySpan value, ref int i) { - for (; i < value.Length; i++) { var c = value.GetChar(i); if (!JsonUtils.IsWhiteSpace(c)) break; } //Whitespace inline + for (; i < value.Length; i++) { var c = value[i]; if (!JsonUtils.IsWhiteSpace(c)) break; } //Whitespace inline } public void EatWhitespace(string value, ref int i) @@ -718,21 +767,20 @@ public void EatWhitespace(string value, ref int i) public string EatValue(string value, ref int i) { - return EatValue(new StringSegment(value), ref i).Value; + return EatValue(value.AsSpan(), ref i).ToString(); } - public StringSegment EatValue(StringSegment value, ref int i) + public ReadOnlySpan EatValue(ReadOnlySpan value, ref int i) { - var buf = value.Buffer; + var buf = value; var valueLength = value.Length; - var offset = value.Offset; - if (i == valueLength) return default(StringSegment); + if (i == valueLength) return default; - while (i < valueLength && JsonUtils.IsWhiteSpace(buf[offset + i])) i++; //Whitespace inline - if (i == valueLength) return default(StringSegment); + while (i < valueLength && JsonUtils.IsWhiteSpace(buf[i])) i++; //Whitespace inline + if (i == valueLength) return default; var tokenStartPos = i; - var valueChar = buf[offset + i]; + var valueChar = buf[i]; var withinQuotes = false; var endsToEat = 1; @@ -741,7 +789,7 @@ public StringSegment EatValue(StringSegment value, ref int i) //If we are at the end, return. case JsWriter.ItemSeperator: case JsWriter.MapEndChar: - return default(StringSegment); + return default; //Is Within Quotes, i.e. "..." case JsWriter.QuoteChar: @@ -751,7 +799,7 @@ public StringSegment EatValue(StringSegment value, ref int i) case JsWriter.MapStartChar: while (++i < valueLength) { - valueChar = buf[offset +i]; + valueChar = buf[i]; if (valueChar == JsonUtils.EscapeChar) { @@ -774,13 +822,13 @@ public StringSegment EatValue(StringSegment value, ref int i) break; } } - return value.Subsegment(tokenStartPos, i - tokenStartPos); + return value.Slice(tokenStartPos, i - tokenStartPos); //Is List, i.e. [...] case JsWriter.ListStartChar: while (++i < valueLength) { - valueChar = buf[offset + i]; + valueChar = buf[i]; if (valueChar == JsonUtils.EscapeChar) { @@ -803,13 +851,13 @@ public StringSegment EatValue(StringSegment value, ref int i) break; } } - return value.Subsegment(tokenStartPos, i - tokenStartPos); + return value.Slice(tokenStartPos, i - tokenStartPos); } //Is Value while (++i < valueLength) { - valueChar = buf[offset + i]; + valueChar = buf[i]; if (valueChar == JsWriter.ItemSeperator || valueChar == JsWriter.MapEndChar @@ -821,8 +869,11 @@ public StringSegment EatValue(StringSegment value, ref int i) } } - var strValue = value.Subsegment(tokenStartPos, i - tokenStartPos); - return strValue == new StringSegment(JsonUtils.Null) ? default(StringSegment) : strValue; + var strValue = value.Slice(tokenStartPos, i - tokenStartPos); + + return strValue.Equals(JsonUtils.Null.AsSpan(), StringComparison.Ordinal) + ? default + : strValue; } } diff --git a/src/ServiceStack.Text/Json/JsonUtils.cs b/src/ServiceStack.Text/Json/JsonUtils.cs index 54dfca2a3..91eb96a70 100644 --- a/src/ServiceStack.Text/Json/JsonUtils.cs +++ b/src/ServiceStack.Text/Json/JsonUtils.cs @@ -4,11 +4,6 @@ using System; using System.IO; using System.Runtime.CompilerServices; -#if NETSTANDARD2_0 -using Microsoft.Extensions.Primitives; -#endif -using ServiceStack.Text.Support; - namespace ServiceStack.Text.Json { @@ -47,7 +42,7 @@ public static class JsonUtils [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsWhiteSpace(char c) { - return c == ' ' || (c >= '\x0009' && c <= '\x000d') || c == '\x00a0' || c == '\x0085'; + return c == ' ' || (c >= '\x0009' && c <= '\x000d') || c == '\x00a0' || c == '\x0085' || c == TypeConstants.NonWidthWhiteSpace; } public static void WriteString(TextWriter writer, string value) @@ -58,8 +53,9 @@ public static void WriteString(TextWriter writer, string value) return; } - var escapeHtmlChars = JsConfig.EscapeHtmlChars; - var escapeUnicode = JsConfig.EscapeUnicode; + var config = JsConfig.GetConfig(); + var escapeHtmlChars = config.EscapeHtmlChars; + var escapeUnicode = config.EscapeUnicode; if (!HasAnyEscapeChars(value, escapeHtmlChars)) { @@ -216,11 +212,11 @@ public static bool IsJsObject(string value) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsJsObject(StringSegment value) + public static bool IsJsObject(ReadOnlySpan value) { return !value.IsNullOrEmpty() - && value.GetChar(0) == '{' - && value.GetChar(value.Length - 1) == '}'; + && value[0] == '{' + && value[value.Length - 1] == '}'; } @@ -233,11 +229,11 @@ public static bool IsJsArray(string value) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsJsArray(StringSegment value) + public static bool IsJsArray(ReadOnlySpan value) { return !value.IsNullOrEmpty() - && value.GetChar(0) == '[' - && value.GetChar(value.Length - 1) == ']'; + && value[0] == '[' + && value[value.Length - 1] == ']'; } } diff --git a/src/ServiceStack.Text/Json/JsonWriter.Generic.cs b/src/ServiceStack.Text/Json/JsonWriter.Generic.cs index 2929c80b3..c5a9cc865 100644 --- a/src/ServiceStack.Text/Json/JsonWriter.Generic.cs +++ b/src/ServiceStack.Text/Json/JsonWriter.Generic.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Runtime.CompilerServices; using System.Threading; using ServiceStack.Text.Common; @@ -32,8 +33,7 @@ internal static WriteObjectDelegate GetWriteFn(Type type) { try { - WriteObjectDelegate writeFn; - if (WriteFnCache.TryGetValue(type, out writeFn)) return writeFn; + if (WriteFnCache.TryGetValue(type, out var writeFn)) return writeFn; var genericType = typeof(JsonWriter<>).MakeGenericType(type); var mi = genericType.GetStaticMethod("WriteFn"); @@ -67,8 +67,7 @@ internal static TypeInfo GetTypeInfo(Type type) { try { - TypeInfo writeFn; - if (JsonTypeInfoCache.TryGetValue(type, out writeFn)) return writeFn; + if (JsonTypeInfoCache.TryGetValue(type, out var writeFn)) return writeFn; var genericType = typeof(JsonWriter<>).MakeGenericType(type); var mi = genericType.GetStaticMethod("GetTypeInfo"); @@ -106,12 +105,8 @@ internal static void WriteLateBoundObject(TextWriter writer, object value) try { - if (++JsState.Depth > JsConfig.MaxDepth) - { - Tracer.Instance.WriteError("Exceeded MaxDepth limit of {0} attempting to serialize {1}" - .Fmt(JsConfig.MaxDepth, value.GetType().Name)); + if (!JsState.Traverse(value)) return; - } var type = value.GetType(); var writeFn = type == typeof(object) @@ -125,7 +120,7 @@ internal static void WriteLateBoundObject(TextWriter writer, object value) } finally { - JsState.Depth--; + JsState.UnTraverse(); } } @@ -133,12 +128,20 @@ internal static WriteObjectDelegate GetValueTypeToStringMethod(Type type) { return Instance.GetValueTypeToStringMethod(type); } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] + public static void InitAot() + { + Text.Json.JsonWriter.WriteFn(); + Text.Json.JsonWriter.Instance.GetWriteFn(); + Text.Json.JsonWriter.Instance.GetValueTypeToStringMethod(typeof(T)); + JsWriter.GetTypeSerializer().GetWriteFn(); + } } public class TypeInfo { internal bool EncodeMapKey; - internal bool IsNumeric; } /// @@ -164,6 +167,7 @@ public static void Refresh() CacheFn = typeof(T) == typeof(object) ? JsonWriter.WriteLateBoundObject : JsonWriter.Instance.GetWriteFn(); + JsConfig.AddUniqueType(typeof(T)); } public static WriteObjectDelegate WriteFn() @@ -185,7 +189,6 @@ static JsonWriter() TypeInfo = new TypeInfo { EncodeMapKey = typeof(T) == typeof(bool) || isNumeric, - IsNumeric = isNumeric }; CacheFn = typeof(T) == typeof(object) @@ -195,37 +198,33 @@ static JsonWriter() public static void WriteObject(TextWriter writer, object value) { -#if __IOS__ - if (writer == null) return; -#endif TypeConfig.Init(); try { - if (++JsState.Depth > JsConfig.MaxDepth) - { - Tracer.Instance.WriteError("Exceeded MaxDepth limit of {0} attempting to serialize {1}" - .Fmt(JsConfig.MaxDepth, value.GetType().Name)); + if (!JsState.Traverse(value)) return; - } CacheFn(writer, value); } finally { - JsState.Depth--; + JsState.UnTraverse(); } } public static void WriteRootObject(TextWriter writer, object value) { -#if __IOS__ - if (writer == null) return; -#endif + GetRootObjectWriteFn(value)(writer, value); + } + + public static WriteObjectDelegate GetRootObjectWriteFn(object value) + { TypeConfig.Init(); + JsonSerializer.OnSerialize?.Invoke(value); JsState.Depth = 0; - CacheFn(writer, value); + return CacheFn; } } diff --git a/src/ServiceStack.Text/JsonObject.cs b/src/ServiceStack.Text/JsonObject.cs index 7d0e47501..196a13d44 100644 --- a/src/ServiceStack.Text/JsonObject.cs +++ b/src/ServiceStack.Text/JsonObject.cs @@ -1,5 +1,7 @@ using System; +using System.Collections; using System.Collections.Generic; +using System.Globalization; using System.IO; using ServiceStack.Text.Common; using ServiceStack.Text.Json; @@ -16,18 +18,19 @@ public static T JsonTo(this Dictionary map, string key) /// /// Get JSON string value converted to T /// - public static T Get(this Dictionary map, string key, T defaultValue = default(T)) + public static T Get(this Dictionary map, string key, T defaultValue = default) { - string strVal; - return map.TryGetValue(key, out strVal) ? JsonSerializer.DeserializeFromString(strVal) : defaultValue; + if (map == null) + return default; + return map.TryGetValue(key, out var strVal) ? JsonSerializer.DeserializeFromString(strVal) : defaultValue; } public static T[] GetArray(this Dictionary map, string key) { - var obj = map as JsonObject; - string value; - return map.TryGetValue(key, out value) - ? (obj != null ? value.FromJson() : value.FromJsv()) + if (map == null) + return TypeConstants.EmptyArray; + return map.TryGetValue(key, out var value) + ? (map is JsonObject obj ? value.FromJson() : value.FromJsv()) : TypeConstants.EmptyArray; } @@ -36,8 +39,11 @@ public static T[] GetArray(this Dictionary map, string key) /// public static string Get(this Dictionary map, string key) { - string strVal; - return map.TryGetValue(key, out strVal) ? JsonTypeSerializer.Instance.UnescapeString(strVal) : null; + if (map == null) + return null; + return map.TryGetValue(key, out var strVal) + ? JsonTypeSerializer.Instance.UnescapeString(strVal) + : null; } public static JsonArrayObjects ArrayObjects(this string json) @@ -57,11 +63,11 @@ public static List ConvertAll(this JsonArrayObjects jsonArrayObjects, Func return results; } - public static T ConvertTo(this JsonObject jsonObject, Func converFn) + public static T ConvertTo(this JsonObject jsonObject, Func convertFn) { return jsonObject == null - ? default(T) - : converFn(jsonObject); + ? default + : convertFn(jsonObject); } public static Dictionary ToDictionary(this JsonObject jsonObject) @@ -72,15 +78,39 @@ public static Dictionary ToDictionary(this JsonObject jsonObject } } - public class JsonObject : Dictionary + public class JsonObject : Dictionary, IEnumerable> { /// /// Get JSON string value /// public new string this[string key] { - get { return this.Get(key); } - set { base[key] = value; } + get => this.Get(key); + set => base[key] = value; + } + + public new Enumerator GetEnumerator() + { + var to = new Dictionary(); + foreach (var key in Keys) + { + to[key] = this[key]; + } + return to.GetEnumerator(); + } + + IEnumerator> IEnumerable>.GetEnumerator() + => GetEnumerator(); + + public Dictionary ToUnescapedDictionary() + { + var to = new Dictionary(); + var enumerateAsConcreteDict = (Dictionary)this; + foreach (var entry in enumerateAsConcreteDict) + { + to[entry.Key] = entry.Value; + } + return to; } public static JsonObject Parse(string json) @@ -95,16 +125,14 @@ public static JsonArrayObjects ParseArray(string json) public JsonArrayObjects ArrayObjects(string propertyName) { - string strValue; - return this.TryGetValue(propertyName, out strValue) + return this.TryGetValue(propertyName, out var strValue) ? JsonArrayObjects.Parse(strValue) : null; } public JsonObject Object(string propertyName) { - string strValue; - return this.TryGetValue(propertyName, out strValue) + return this.TryGetValue(propertyName, out var strValue) ? Parse(strValue) : null; } @@ -161,16 +189,14 @@ private static bool IsJavaScriptNumber(string strValue) if (!strValue.Contains(".")) { - long longValue; - if (long.TryParse(strValue, out longValue)) + if (long.TryParse(strValue, out var longValue)) { return longValue < JsonUtils.MaxInteger && longValue > JsonUtils.MinInteger; } return false; } - double doubleValue; - if (double.TryParse(strValue, out doubleValue)) + if (double.TryParse(strValue, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out var doubleValue)) { return doubleValue < JsonUtils.MaxInteger && doubleValue > JsonUtils.MinInteger; } diff --git a/src/ServiceStack.Text/JsonSerializer.Generic.cs b/src/ServiceStack.Text/JsonSerializer.Generic.cs index 16fa3809e..269757c98 100644 --- a/src/ServiceStack.Text/JsonSerializer.Generic.cs +++ b/src/ServiceStack.Text/JsonSerializer.Generic.cs @@ -46,12 +46,12 @@ public T DeserializeFromReader(TextReader reader) public string SerializeToString(T value) { if (value == null) return null; - if (typeof(T) == typeof(string)) return value as string; if (typeof(T) == typeof(object) || typeof(T).IsAbstract || typeof(T).IsInterface) { + var prevState = JsState.IsWritingDynamic; if (typeof(T).IsAbstract || typeof(T).IsInterface) JsState.IsWritingDynamic = true; var result = JsonSerializer.SerializeToString(value, value.GetType()); - if (typeof(T).IsAbstract || typeof(T).IsInterface) JsState.IsWritingDynamic = false; + if (typeof(T).IsAbstract || typeof(T).IsInterface) JsState.IsWritingDynamic = prevState; return result; } @@ -63,16 +63,12 @@ public string SerializeToString(T value) public void SerializeToWriter(T value, TextWriter writer) { if (value == null) return; - if (typeof(T) == typeof(string)) - { - writer.Write(value); - return; - } if (typeof(T) == typeof(object) || typeof(T).IsAbstract || typeof(T).IsInterface) { + var prevState = JsState.IsWritingDynamic; if (typeof(T).IsAbstract || typeof(T).IsInterface) JsState.IsWritingDynamic = true; JsonSerializer.SerializeToWriter(value, value.GetType(), writer); - if (typeof(T).IsAbstract || typeof(T).IsInterface) JsState.IsWritingDynamic = false; + if (typeof(T).IsAbstract || typeof(T).IsInterface) JsState.IsWritingDynamic = prevState; return; } diff --git a/src/ServiceStack.Text/JsonSerializer.cs b/src/ServiceStack.Text/JsonSerializer.cs index 66292e0d9..d3468fb74 100644 --- a/src/ServiceStack.Text/JsonSerializer.cs +++ b/src/ServiceStack.Text/JsonSerializer.cs @@ -14,6 +14,8 @@ using System.IO; using System.Net; using System.Text; +using System.Threading; +using System.Threading.Tasks; using ServiceStack.Text.Common; using ServiceStack.Text.Json; @@ -29,12 +31,25 @@ static JsonSerializer() JsConfig.InitStatics(); } - public static Encoding UTF8Encoding = PclExport.Instance.GetUTF8Encoding(false); + public static int BufferSize = 1024; + + [Obsolete("Use JsConfig.UTF8Encoding")] + public static UTF8Encoding UTF8Encoding + { + get => JsConfig.UTF8Encoding; + set => JsConfig.UTF8Encoding = value; + } + + public static Action OnSerialize { get; set; } public static T DeserializeFromString(string value) { - if (string.IsNullOrEmpty(value)) return default(T); - return (T)JsonReader.Parse(value); + return JsonReader.Parse(value) is T obj ? obj : default(T); + } + + public static T DeserializeFromSpan(ReadOnlySpan value) + { + return JsonReader.Parse(value) is T obj ? obj : default(T); } public static T DeserializeFromReader(TextReader reader) @@ -42,6 +57,13 @@ public static T DeserializeFromReader(TextReader reader) return DeserializeFromString(reader.ReadToEnd()); } + public static object DeserializeFromSpan(Type type, ReadOnlySpan value) + { + return value.IsEmpty + ? null + : JsonReader.GetParseSpanFn(type)(value); + } + public static object DeserializeFromString(string value, Type type) { return string.IsNullOrEmpty(value) @@ -63,9 +85,10 @@ public static string SerializeToString(T value) } if (typeof(T).IsAbstract || typeof(T).IsInterface) { + var prevState = JsState.IsWritingDynamic; JsState.IsWritingDynamic = true; var result = SerializeToString(value, value.GetType()); - JsState.IsWritingDynamic = false; + JsState.IsWritingDynamic = prevState; return result; } @@ -76,7 +99,7 @@ public static string SerializeToString(T value) } else { - JsonWriter.WriteRootObject(writer, value); + WriteObjectToWriter(value, JsonWriter.GetRootObjectWriteFn(value), writer); } return StringWriterThreadStatic.ReturnAndFree(writer); } @@ -92,7 +115,8 @@ public static string SerializeToString(object value, Type type) } else { - JsonWriter.GetWriteFn(type)(writer, value); + OnSerialize?.Invoke(value); + WriteObjectToWriter(value, JsonWriter.GetWriteFn(type), writer); } return StringWriterThreadStatic.ReturnAndFree(writer); } @@ -110,13 +134,14 @@ public static void SerializeToWriter(T value, TextWriter writer) } else if (typeof(T).IsAbstract || typeof(T).IsInterface) { + var prevState = JsState.IsWritingDynamic; JsState.IsWritingDynamic = false; SerializeToWriter(value, value.GetType(), writer); - JsState.IsWritingDynamic = true; + JsState.IsWritingDynamic = prevState; } else { - JsonWriter.WriteRootObject(writer, value); + WriteObjectToWriter(value, JsonWriter.GetRootObjectWriteFn(value), writer); } } @@ -129,7 +154,8 @@ public static void SerializeToWriter(object value, Type type, TextWriter writer) return; } - JsonWriter.GetWriteFn(type)(writer, value); + OnSerialize?.Invoke(value); + WriteObjectToWriter(value, JsonWriter.GetWriteFn(type), writer); } public static void SerializeToStream(T value, Stream stream) @@ -141,60 +167,80 @@ public static void SerializeToStream(T value, Stream stream) } else if (typeof(T).IsAbstract || typeof(T).IsInterface) { + var prevState = JsState.IsWritingDynamic; JsState.IsWritingDynamic = false; SerializeToStream(value, value.GetType(), stream); - JsState.IsWritingDynamic = true; + JsState.IsWritingDynamic = prevState; } else { - var writer = new StreamWriter(stream, UTF8Encoding); - JsonWriter.WriteRootObject(writer, value); + var writer = new StreamWriter(stream, JsConfig.UTF8Encoding, BufferSize, leaveOpen:true); + WriteObjectToWriter(value, JsonWriter.GetRootObjectWriteFn(value), writer); writer.Flush(); } } public static void SerializeToStream(object value, Type type, Stream stream) { - var writer = new StreamWriter(stream, UTF8Encoding); - JsonWriter.GetWriteFn(type)(writer, value); + OnSerialize?.Invoke(value); + var writer = new StreamWriter(stream, JsConfig.UTF8Encoding, BufferSize, leaveOpen:true); + WriteObjectToWriter(value, JsonWriter.GetWriteFn(type), writer); writer.Flush(); } - public static T DeserializeFromStream(Stream stream) + private static void WriteObjectToWriter(object value, WriteObjectDelegate serializeFn, TextWriter writer) { - using (var reader = new StreamReader(stream, UTF8Encoding)) + if (!JsConfig.Indent) { - return DeserializeFromString(reader.ReadToEnd()); + serializeFn(writer, value); } + else + { + var sb = StringBuilderCache.Allocate(); + using var captureJson = new StringWriter(sb); + serializeFn(captureJson, value); + captureJson.Flush(); + var json = StringBuilderCache.ReturnAndFree(sb); + var indentJson = json.IndentJson(); + writer.Write(indentJson); + } + } + public static T DeserializeFromStream(Stream stream) + { + return (T)MemoryProvider.Instance.Deserialize(stream, typeof(T), DeserializeFromSpan); } public static object DeserializeFromStream(Type type, Stream stream) { - using (var reader = new StreamReader(stream, UTF8Encoding)) - { - return DeserializeFromString(reader.ReadToEnd(), type); - } + return MemoryProvider.Instance.Deserialize(stream, type, DeserializeFromSpan); + } + + public static Task DeserializeFromStreamAsync(Type type, Stream stream) + { + return MemoryProvider.Instance.DeserializeAsync(stream, type, DeserializeFromSpan); + } + + public static async Task DeserializeFromStreamAsync(Stream stream) + { + var obj = await MemoryProvider.Instance.DeserializeAsync(stream, typeof(T), DeserializeFromSpan).ConfigAwait(); + return (T)obj; } public static T DeserializeResponse(WebRequest webRequest) { using (var webRes = PclExport.Instance.GetResponse(webRequest)) + using (var stream = webRes.GetResponseStream()) { - using (var stream = webRes.GetResponseStream()) - { - return DeserializeFromStream(stream); - } + return DeserializeFromStream(stream); } } public static object DeserializeResponse(Type type, WebRequest webRequest) { using (var webRes = PclExport.Instance.GetResponse(webRequest)) + using (var stream = webRes.GetResponseStream()) { - using (var stream = webRes.GetResponseStream()) - { - return DeserializeFromStream(type, stream); - } + return DeserializeFromStream(type, stream); } } diff --git a/src/ServiceStack.Text/Jsv/JsvReader.Generic.cs b/src/ServiceStack.Text/Jsv/JsvReader.Generic.cs index a7de7d295..df21cd9ad 100644 --- a/src/ServiceStack.Text/Jsv/JsvReader.Generic.cs +++ b/src/ServiceStack.Text/Jsv/JsvReader.Generic.cs @@ -4,12 +4,8 @@ using System; using System.Collections.Generic; using System.Threading; +using System.Runtime.CompilerServices; using ServiceStack.Text.Common; -#if NETSTANDARD2_0 -using Microsoft.Extensions.Primitives; -#else -using ServiceStack.Text.Support; -#endif namespace ServiceStack.Text.Jsv { @@ -19,36 +15,47 @@ public static class JsvReader private static Dictionary ParseFnCache = new Dictionary(); - public static ParseStringDelegate GetParseFn(Type type) => v => GetParseStringSegmentFn(type)(new StringSegment(v)); + public static ParseStringDelegate GetParseFn(Type type) => v => GetParseStringSpanFn(type)(v.AsSpan()); - public static ParseStringSegmentDelegate GetParseStringSegmentFn(Type type) + public static ParseStringSpanDelegate GetParseSpanFn(Type type) => v => GetParseStringSpanFn(type)(v); + + public static ParseStringSpanDelegate GetParseStringSpanFn(Type type) { - ParseFactoryDelegate parseFactoryFn; - ParseFnCache.TryGetValue(type, out parseFactoryFn); + ParseFnCache.TryGetValue(type, out var parseFactoryFn); if (parseFactoryFn != null) return parseFactoryFn(); var genericType = typeof(JsvReader<>).MakeGenericType(type); - var mi = genericType.GetStaticMethod("GetParseStringSegmentFn"); + var mi = genericType.GetStaticMethod(nameof(GetParseStringSpanFn)); parseFactoryFn = (ParseFactoryDelegate)mi.MakeDelegate(typeof(ParseFactoryDelegate)); Dictionary snapshot, newCache; do { snapshot = ParseFnCache; - newCache = new Dictionary(ParseFnCache); - newCache[type] = parseFactoryFn; + newCache = new Dictionary(ParseFnCache) { + [type] = parseFactoryFn + }; } while (!ReferenceEquals( Interlocked.CompareExchange(ref ParseFnCache, newCache, snapshot), snapshot)); return parseFactoryFn(); } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] + public static void InitAot() + { + Text.Jsv.JsvReader.Instance.GetParseFn(); + Text.Jsv.JsvReader.Parse(default(ReadOnlySpan)); + Text.Jsv.JsvReader.GetParseFn(); + Text.Jsv.JsvReader.GetParseStringSpanFn(); + } } internal static class JsvReader { - private static ParseStringSegmentDelegate ReadFn; + private static ParseStringSpanDelegate ReadFn; static JsvReader() { @@ -62,22 +69,26 @@ public static void Refresh() if (JsvReader.Instance == null) return; - ReadFn = JsvReader.Instance.GetParseStringSegmentFn(); + ReadFn = JsvReader.Instance.GetParseStringSpanFn(); + JsConfig.AddUniqueType(typeof(T)); } - public static ParseStringDelegate GetParseFn() - { - return ReadFn != null ? (ParseStringDelegate)(v => ReadFn(new StringSegment(v))) : Parse; - } + public static ParseStringDelegate GetParseFn() => ReadFn != null + ? (ParseStringDelegate)(v => ReadFn(v.AsSpan())) + : Parse; - public static ParseStringSegmentDelegate GetParseStringSegmentFn() => ReadFn ?? ParseStringSegment; + public static ParseStringSpanDelegate GetParseStringSpanFn() => ReadFn ?? Parse; - public static object Parse(string value) => ParseStringSegment(new StringSegment(value)); + public static object Parse(string value) => value != null + ? Parse(value.AsSpan()) + : null; - public static object ParseStringSegment(StringSegment value) + public static object Parse(ReadOnlySpan value) { TypeConfig.Init(); + value = value.WithoutBom(); + if (ReadFn == null) { if (typeof(T).IsInterface) @@ -89,7 +100,9 @@ public static object ParseStringSegment(StringSegment value) Refresh(); } - return value.HasValue ? ReadFn(value) : null; + return !value.IsEmpty + ? ReadFn(value) + : null; } } } \ No newline at end of file diff --git a/src/ServiceStack.Text/Jsv/JsvSerializer.Generic.cs b/src/ServiceStack.Text/Jsv/JsvSerializer.Generic.cs index 9c181da7a..4bf59a4ea 100644 --- a/src/ServiceStack.Text/Jsv/JsvSerializer.Generic.cs +++ b/src/ServiceStack.Text/Jsv/JsvSerializer.Generic.cs @@ -16,8 +16,7 @@ internal class JsvSerializer public T DeserializeFromString(string value, Type type) { - ParseStringDelegate parseFn; - if (DeserializerCache.TryGetValue(type, out parseFn)) return (T)parseFn(value); + if (DeserializerCache.TryGetValue(type, out var parseFn)) return (T)parseFn(value); var genericType = typeof(T).MakeGenericType(type); var mi = genericType.GetMethodInfo("DeserializeFromString", new[] { typeof(string) }); diff --git a/src/ServiceStack.Text/Jsv/JsvTypeSerializer.cs b/src/ServiceStack.Text/Jsv/JsvTypeSerializer.cs index 29e5bb83f..4c7a48306 100644 --- a/src/ServiceStack.Text/Jsv/JsvTypeSerializer.cs +++ b/src/ServiceStack.Text/Jsv/JsvTypeSerializer.cs @@ -4,20 +4,19 @@ using System; using System.Globalization; using System.IO; +using System.Runtime.CompilerServices; using ServiceStack.Text.Common; using ServiceStack.Text.Json; -#if NETSTANDARD2_0 -using Microsoft.Extensions.Primitives; -#endif -using ServiceStack.Text.Support; namespace ServiceStack.Text.Jsv { - public class JsvTypeSerializer + public struct JsvTypeSerializer : ITypeSerializer { public static ITypeSerializer Instance = new JsvTypeSerializer(); + public ObjectDeserializerDelegate ObjectDeserializer { get; set; } + public bool IncludeNullValues => false; public bool IncludeNullValuesInDictionaries => false; @@ -53,8 +52,7 @@ public void WriteObjectString(TextWriter writer, object value) { if (value != null) { - var strValue = value as string; - if (strValue != null) + if (value is string strValue) { WriteString(writer, strValue); } @@ -241,84 +239,125 @@ public void WriteDecimal(TextWriter writer, object decimalValue) public void WriteEnum(TextWriter writer, object enumValue) { - if (enumValue == null) return; - if (JsConfig.TreatEnumAsInteger) - JsWriter.WriteEnumFlags(writer, enumValue); + if (enumValue == null) + return; + var serializedValue = CachedTypeInfo.Get(enumValue.GetType()).EnumInfo.GetSerializedValue(enumValue); + if (serializedValue is string strEnum) + writer.Write(strEnum); else - writer.Write(enumValue.ToString()); + JsWriter.WriteEnumFlags(writer, enumValue); + } + +#if NET6_0 + public void WriteDateOnly(TextWriter writer, object oDateOnly) + { + var dateOnly = (DateOnly)oDateOnly; + switch (JsConfig.DateHandler) + { + case DateHandler.UnixTime: + writer.Write(dateOnly.ToUnixTime()); + break; + case DateHandler.UnixTimeMs: + writer.Write(dateOnly.ToUnixTimeMs()); + break; + default: + writer.Write(dateOnly.ToString("O")); + break; + } } - public void WriteEnumFlags(TextWriter writer, object enumFlagValue) + public void WriteNullableDateOnly(TextWriter writer, object oDateOnly) { - JsWriter.WriteEnumFlags(writer, enumFlagValue); + if (oDateOnly == null) return; + WriteDateOnly(writer, oDateOnly); } - public object EncodeMapKey(object value) + public void WriteTimeOnly(TextWriter writer, object oTimeOnly) { - return value; + var stringValue = JsConfig.TimeSpanHandler == TimeSpanHandler.StandardFormat + ? oTimeOnly.ToString() + : DateTimeSerializer.ToXsdTimeSpanString(((TimeOnly)oTimeOnly).ToTimeSpan()); + WriteRawString(writer, stringValue); } + public void WriteNullableTimeOnly(TextWriter writer, object oTimeOnly) + { + if (oTimeOnly == null) return; + WriteTimeSpan(writer, ((TimeOnly?)oTimeOnly).Value.ToTimeSpan()); + } +#endif + public ParseStringDelegate GetParseFn() => JsvReader.Instance.GetParseFn(); public ParseStringDelegate GetParseFn(Type type) => JsvReader.GetParseFn(type); - public ParseStringSegmentDelegate GetParseStringSegmentFn() => JsvReader.Instance.GetParseStringSegmentFn(); + public ParseStringSpanDelegate GetParseStringSpanFn() => JsvReader.Instance.GetParseStringSpanFn(); - public ParseStringSegmentDelegate GetParseStringSegmentFn(Type type) => JsvReader.GetParseStringSegmentFn(type); + public ParseStringSpanDelegate GetParseStringSpanFn(Type type) => JsvReader.GetParseStringSpanFn(type); - public string UnescapeSafeString(string value) => value.FromCsvField(); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public object UnescapeStringAsObject(ReadOnlySpan value) + { + return UnescapeSafeString(value).Value(); + } + + public string UnescapeSafeString(string value) => JsState.IsCsv + ? value + : value.FromCsvField(); - public StringSegment UnescapeSafeString(StringSegment value) => value.FromCsvField(); + public ReadOnlySpan UnescapeSafeString(ReadOnlySpan value) => JsState.IsCsv + ? value // already unescaped in CsvReader.ParseFields() + : value.FromCsvField(); public string ParseRawString(string value) => value; public string ParseString(string value) => value.FromCsvField(); - public string ParseString(StringSegment value) => value.Value.FromCsvField(); + public string ParseString(ReadOnlySpan value) => value.ToString().FromCsvField(); public string UnescapeString(string value) => value.FromCsvField(); - public StringSegment UnescapeString(StringSegment value) => new StringSegment(value.Value.FromCsvField()); + public ReadOnlySpan UnescapeString(ReadOnlySpan value) => value.FromCsvField(); - public string EatTypeValue(string value, ref int i) => EatValue(new StringSegment(value), ref i).Value; + public string EatTypeValue(string value, ref int i) => EatValue(value, ref i); - public StringSegment EatTypeValue(StringSegment value, ref int i) => EatValue(value, ref i); + public ReadOnlySpan EatTypeValue(ReadOnlySpan value, ref int i) => EatValue(value, ref i); - public bool EatMapStartChar(string value, ref int i) => EatMapStartChar(new StringSegment(value), ref i); + public bool EatMapStartChar(string value, ref int i) => EatMapStartChar(value.AsSpan(), ref i); - public bool EatMapStartChar(StringSegment value, ref int i) + public bool EatMapStartChar(ReadOnlySpan value, ref int i) { - var success = value.GetChar(i) == JsWriter.MapStartChar; + var success = value[i] == JsWriter.MapStartChar; if (success) i++; return success; } - public string EatMapKey(string value, ref int i) => EatMapKey(new StringSegment(value), ref i).Value; + public string EatMapKey(string value, ref int i) => EatMapKey(value.AsSpan(), ref i).ToString(); - public StringSegment EatMapKey(StringSegment value, ref int i) + public ReadOnlySpan EatMapKey(ReadOnlySpan value, ref int i) { var tokenStartPos = i; var valueLength = value.Length; - var valueChar = value.GetChar(tokenStartPos); + var valueChar = value[tokenStartPos]; switch (valueChar) { case JsWriter.QuoteChar: while (++i < valueLength) { - valueChar = value.GetChar(i); + valueChar = value[i]; if (valueChar != JsWriter.QuoteChar) continue; - var isLiteralQuote = i + 1 < valueLength && value.GetChar(i + 1) == JsWriter.QuoteChar; + var isLiteralQuote = i + 1 < valueLength && value[i + 1] == JsWriter.QuoteChar; i++; //skip quote if (!isLiteralQuote) break; } - return value.Subsegment(tokenStartPos, i - tokenStartPos); + return value.Slice(tokenStartPos, i - tokenStartPos); //Is Type/Map, i.e. {...} case JsWriter.MapStartChar: @@ -326,7 +365,7 @@ public StringSegment EatMapKey(StringSegment value, ref int i) var withinQuotes = false; while (++i < valueLength && endsToEat > 0) { - valueChar = value.GetChar(i); + valueChar = value[i]; if (valueChar == JsWriter.QuoteChar) withinQuotes = !withinQuotes; @@ -340,11 +379,11 @@ public StringSegment EatMapKey(StringSegment value, ref int i) if (valueChar == JsWriter.MapEndChar) endsToEat--; } - return value.Subsegment(tokenStartPos, i - tokenStartPos); + return value.Slice(tokenStartPos, i - tokenStartPos); } - while (value.GetChar(++i) != JsWriter.MapKeySeperator) { } - return value.Subsegment(tokenStartPos, i - tokenStartPos); + while (value[++i] != JsWriter.MapKeySeperator) { } + return value.Slice(tokenStartPos, i - tokenStartPos); } public bool EatMapKeySeperator(string value, ref int i) @@ -352,49 +391,57 @@ public bool EatMapKeySeperator(string value, ref int i) return value[i++] == JsWriter.MapKeySeperator; } - public bool EatMapKeySeperator(StringSegment value, ref int i) + public bool EatMapKeySeperator(ReadOnlySpan value, ref int i) { - return value.GetChar(i++) == JsWriter.MapKeySeperator; + return value[i++] == JsWriter.MapKeySeperator; } - public bool EatItemSeperatorOrMapEndChar(string value, ref int i) { if (i == value.Length) return false; var success = value[i] == JsWriter.ItemSeperator || value[i] == JsWriter.MapEndChar; - i++; + + if (success) + i++; + else if (Env.StrictMode) throw new Exception( + $"Expected '{JsWriter.ItemSeperator}' or '{JsWriter.MapEndChar}'"); + return success; } - public bool EatItemSeperatorOrMapEndChar(StringSegment value, ref int i) + public bool EatItemSeperatorOrMapEndChar(ReadOnlySpan value, ref int i) { if (i == value.Length) return false; - var success = value.GetChar(i) == JsWriter.ItemSeperator - || value.GetChar(i) == JsWriter.MapEndChar; - i++; + var success = value[i] == JsWriter.ItemSeperator + || value[i] == JsWriter.MapEndChar; + + if (success) + i++; + else if (Env.StrictMode) throw new Exception( + $"Expected '{JsWriter.ItemSeperator}' or '{JsWriter.MapEndChar}'"); + return success; } - public void EatWhitespace(string value, ref int i) {} - public void EatWhitespace(StringSegment value, ref int i) { } + public void EatWhitespace(ReadOnlySpan value, ref int i) { } public string EatValue(string value, ref int i) { - return EatValue(new StringSegment(value), ref i).Value; + return EatValue(value.AsSpan(), ref i).ToString(); } - public StringSegment EatValue(StringSegment value, ref int i) + public ReadOnlySpan EatValue(ReadOnlySpan value, ref int i) { var tokenStartPos = i; var valueLength = value.Length; - if (i == valueLength) return default(StringSegment); + if (i == valueLength) return default; - var valueChar = value.GetChar(i); + var valueChar = value[i]; var withinQuotes = false; var endsToEat = 1; @@ -403,29 +450,29 @@ public StringSegment EatValue(StringSegment value, ref int i) //If we are at the end, return. case JsWriter.ItemSeperator: case JsWriter.MapEndChar: - return default(StringSegment); + return default; //Is Within Quotes, i.e. "..." case JsWriter.QuoteChar: while (++i < valueLength) { - valueChar = value.GetChar(i); + valueChar = value[i]; if (valueChar != JsWriter.QuoteChar) continue; - var isLiteralQuote = i + 1 < valueLength && value.GetChar(i + 1) == JsWriter.QuoteChar; + var isLiteralQuote = i + 1 < valueLength && value[i + 1] == JsWriter.QuoteChar; i++; //skip quote if (!isLiteralQuote) break; } - return value.Subsegment(tokenStartPos, i - tokenStartPos); + return value.Slice(tokenStartPos, i - tokenStartPos); //Is Type/Map, i.e. {...} case JsWriter.MapStartChar: while (++i < valueLength && endsToEat > 0) { - valueChar = value.GetChar(i); + valueChar = value[i]; if (valueChar == JsWriter.QuoteChar) withinQuotes = !withinQuotes; @@ -439,13 +486,13 @@ public StringSegment EatValue(StringSegment value, ref int i) if (valueChar == JsWriter.MapEndChar) endsToEat--; } - return value.Subsegment(tokenStartPos, i - tokenStartPos); + return value.Slice(tokenStartPos, i - tokenStartPos); //Is List, i.e. [...] case JsWriter.ListStartChar: while (++i < valueLength && endsToEat > 0) { - valueChar = value.GetChar(i); + valueChar = value[i]; if (valueChar == JsWriter.QuoteChar) withinQuotes = !withinQuotes; @@ -459,13 +506,13 @@ public StringSegment EatValue(StringSegment value, ref int i) if (valueChar == JsWriter.ListEndChar) endsToEat--; } - return value.Subsegment(tokenStartPos, i - tokenStartPos); + return value.Slice(tokenStartPos, i - tokenStartPos); } //Is Value while (++i < valueLength) { - valueChar = value.GetChar(i); + valueChar = value[i]; if (valueChar == JsWriter.ItemSeperator || valueChar == JsWriter.MapEndChar) @@ -474,7 +521,7 @@ public StringSegment EatValue(StringSegment value, ref int i) } } - return value.Subsegment(tokenStartPos, i - tokenStartPos); + return value.Slice(tokenStartPos, i - tokenStartPos); } } } \ No newline at end of file diff --git a/src/ServiceStack.Text/Jsv/JsvWriter.Generic.cs b/src/ServiceStack.Text/Jsv/JsvWriter.Generic.cs index b633f312e..7cb2cce8a 100644 --- a/src/ServiceStack.Text/Jsv/JsvWriter.Generic.cs +++ b/src/ServiceStack.Text/Jsv/JsvWriter.Generic.cs @@ -4,12 +4,13 @@ using System; using System.Collections.Generic; using System.IO; +using System.Runtime.CompilerServices; using System.Threading; using ServiceStack.Text.Common; namespace ServiceStack.Text.Jsv { - internal static class JsvWriter + public static class JsvWriter { public static readonly JsWriter Instance = new JsWriter(); @@ -32,8 +33,8 @@ public static WriteObjectDelegate GetWriteFn(Type type) { try { - WriteObjectDelegate writeFn; - if (WriteFnCache.TryGetValue(type, out writeFn)) return writeFn; + if (WriteFnCache.TryGetValue(type, out var writeFn)) + return writeFn; var genericType = typeof(JsvWriter<>).MakeGenericType(type); var mi = genericType.GetStaticMethod("WriteFn"); @@ -67,12 +68,8 @@ public static void WriteLateBoundObject(TextWriter writer, object value) try { - if (++JsState.Depth > JsConfig.MaxDepth) - { - Tracer.Instance.WriteError("Exceeded MaxDepth limit of {0} attempting to serialize {1}" - .Fmt(JsConfig.MaxDepth, value.GetType().Name)); + if (!JsState.Traverse(value)) return; - } var type = value.GetType(); var writeFn = type == typeof(object) @@ -86,7 +83,7 @@ public static void WriteLateBoundObject(TextWriter writer, object value) } finally { - JsState.Depth--; + JsState.UnTraverse(); } } @@ -94,6 +91,15 @@ public static WriteObjectDelegate GetValueTypeToStringMethod(Type type) { return Instance.GetValueTypeToStringMethod(type); } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] + public static void InitAot() + { + Text.Jsv.JsvWriter.WriteFn(); + Text.Jsv.JsvWriter.Instance.GetWriteFn(); + Text.Jsv.JsvWriter.Instance.GetValueTypeToStringMethod(typeof(T)); + JsWriter.GetTypeSerializer().GetWriteFn(); + } } /// @@ -118,6 +124,7 @@ public static void Refresh() CacheFn = typeof(T) == typeof(object) ? JsvWriter.WriteLateBoundObject : JsvWriter.Instance.GetWriteFn(); + JsConfig.AddUniqueType(typeof(T)); } public static WriteObjectDelegate WriteFn() @@ -134,34 +141,29 @@ static JsvWriter() public static void WriteObject(TextWriter writer, object value) { -#if __IOS__ - if (writer == null) return; -#endif + if (writer == null) return; //AOT + TypeConfig.Init(); try { - if (++JsState.Depth > JsConfig.MaxDepth) - { - Tracer.Instance.WriteError("Exceeded MaxDepth limit of {0} attempting to serialize {1}" - .Fmt(JsConfig.MaxDepth, value.GetType().Name)); + if (!JsState.Traverse(value)) return; - } CacheFn(writer, value); } finally { - JsState.Depth--; + JsState.UnTraverse(); } } public static void WriteRootObject(TextWriter writer, object value) { -#if __IOS__ - if (writer == null) return; -#endif + if (writer == null) return; //AOT + TypeConfig.Init(); + TypeSerializer.OnSerialize?.Invoke(value); JsState.Depth = 0; CacheFn(writer, value); diff --git a/src/ServiceStack.Text/LicenseUtils.cs b/src/ServiceStack.Text/LicenseUtils.cs index 89c01c08e..2fb47aec7 100644 --- a/src/ServiceStack.Text/LicenseUtils.cs +++ b/src/ServiceStack.Text/LicenseUtils.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Globalization; using System.IO; using System.Linq; @@ -22,6 +23,8 @@ public LicenseException(string message, Exception innerException) : base(message public enum LicenseType { Free, + FreeIndividual, + FreeOpenSource, Indie, Business, Enterprise, @@ -62,6 +65,14 @@ public enum LicenseFeature : long Aws = 1 << 10, } + [Flags] + public enum LicenseMeta : long + { + None = 0, + Subscription = 1 << 0, + Cores = 1 << 1, + } + public enum QuotaType { Operations, //ServiceStack @@ -106,6 +117,7 @@ public class LicenseKey public string Ref { get; set; } public string Name { get; set; } public LicenseType Type { get; set; } + public long Meta { get; set; } public string Hash { get; set; } public DateTime Expiry { get; set; } } @@ -125,10 +137,10 @@ static LicenseUtils() PclExport.Instance.RegisterLicenseFromConfig(); } - private static bool hasInit; + public static bool HasInit { get; private set; } public static void Init() { - hasInit = true; //Dummy method to init static constructor + HasInit = true; //Dummy method to init static constructor } public static class ErrorMessages @@ -164,12 +176,36 @@ public static void AssertEvaluationLicense() private static readonly int[] revokedSubs = { 4018, 4019, 4041, 4331, 4581 }; - private static LicenseKey __activatedLicense; + private class __ActivatedLicense + { + internal readonly LicenseKey LicenseKey; + internal __ActivatedLicense(LicenseKey licenseKey) => LicenseKey = licenseKey; + } + + public static string LicenseWarningMessage { get; private set; } + + private static string GetLicenseWarningMessage() + { + var key = __activatedLicense?.LicenseKey; + if (key == null) + return null; + + if (DateTime.UtcNow > key.Expiry) + { + var licenseMeta = key.Meta; + if ((licenseMeta & (long)LicenseMeta.Subscription) != 0) + return $"This Annual Subscription expired on '{key.Expiry:d}', please update your License Key with this years subscription."; + } + + return null; + } + + private static __ActivatedLicense __activatedLicense; public static void RegisterLicense(string licenseKeyText) { JsConfig.InitStatics(); - if (__activatedLicense != null) //Skip multple license registrations. Use RemoveLicense() to reset. + if (__activatedLicense != null) //Skip multiple license registrations. Use RemoveLicense() to reset. return; string subId = null; @@ -177,17 +213,32 @@ public static void RegisterLicense(string licenseKeyText) Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture; try { + if (IsFreeLicenseKey(licenseKeyText)) + { + ValidateFreeLicenseKey(licenseKeyText); + return; + } + var parts = licenseKeyText.SplitOnFirst('-'); subId = parts[0]; if (int.TryParse(subId, out var subIdInt) && revokedSubs.Contains(subIdInt)) throw new LicenseException("This subscription has been revoked. " + ContactDetails); - var key = PclExport.Instance.VerifyLicenseKeyText(licenseKeyText); + var key = VerifyLicenseKeyText(licenseKeyText); ValidateLicenseKey(key); } + catch (PlatformNotSupportedException) + { + // Allow usage in environments like dotnet script + __activatedLicense = new __ActivatedLicense(new LicenseKey { Type = LicenseType.Indie }); + } catch (Exception ex) { + //bubble unrelated project Exceptions + if (ex is FileNotFoundException || ex is FileLoadException || ex is BadImageFormatException || ex is NotSupportedException) + throw; + if (ex is LicenseException) throw; @@ -204,6 +255,9 @@ public static void RegisterLicense(string licenseKeyText) } catch (Exception exFallback) { + if (exFallback is FileNotFoundException || exFallback is FileLoadException || exFallback is BadImageFormatException) + throw; + throw new LicenseException(msg, exFallback).Trace(); } } @@ -226,7 +280,167 @@ private static void ValidateLicenseKey(LicenseKey key) if (key.Type == LicenseType.Trial && DateTime.UtcNow > key.Expiry) throw new LicenseException($"This trial license has expired on {key.Expiry:d}." + ContactDetails).Trace(); - __activatedLicense = key; + __activatedLicense = new __ActivatedLicense(key); + + LicenseWarningMessage = GetLicenseWarningMessage(); + if (LicenseWarningMessage != null) + Console.WriteLine(LicenseWarningMessage); + } + + private const string IndividualPrefix = "Individual (c) "; + private const string OpenSourcePrefix = "OSS "; + + private static bool IsFreeLicenseKey(string licenseText) => + licenseText.StartsWith(IndividualPrefix) || licenseText.StartsWith(OpenSourcePrefix); + + private static void ValidateFreeLicenseKey(string licenseText) + { + if (!IsFreeLicenseKey(licenseText)) + throw new NotSupportedException("Not a free License Key"); + + var envKey = Environment.GetEnvironmentVariable("SERVICESTACK_LICENSE"); + if (envKey == licenseText) + throw new LicenseException("Cannot use SERVICESTACK_LICENSE Environment variable with free License Keys, " + + "please use Licensing.RegisterLicense() in source code."); + + LicenseKey key = null; + if (licenseText.StartsWith(IndividualPrefix)) + { + key = VerifyIndividualLicense(licenseText); + if (key == null) + throw new LicenseException("Individual License Key is invalid."); + } + else if (licenseText.StartsWith(OpenSourcePrefix)) + { + key = VerifyOpenSourceLicense(licenseText); + if (key == null) + throw new LicenseException("Open Source License Key is invalid."); + } + else throw new NotSupportedException("Not a free License Key"); + + var releaseDate = Env.GetReleaseDate(); + if (releaseDate > key.Expiry) + throw new LicenseException($"This license has expired on {key.Expiry:d} and is not valid for use with this release.\n" + + "Check https://servicestack.net/free for eligible renewals.").Trace(); + + __activatedLicense = new __ActivatedLicense(key); + } + + internal static string Info => __activatedLicense?.LicenseKey == null + ? "NO" + : __activatedLicense.LicenseKey.Type switch { + LicenseType.Free => "FR", + LicenseType.FreeIndividual => "FI", + LicenseType.FreeOpenSource => "FO", + LicenseType.Indie => "IN", + LicenseType.Business => "BU", + LicenseType.Enterprise => "EN", + LicenseType.TextIndie => "TI", + LicenseType.TextBusiness => "TB", + LicenseType.OrmLiteIndie => "OI", + LicenseType.OrmLiteBusiness => "OB", + LicenseType.RedisIndie => "RI", + LicenseType.RedisBusiness => "RB", + LicenseType.AwsIndie => "AI", + LicenseType.AwsBusiness => "AB", + LicenseType.Trial => "TR", + LicenseType.Site => "SI", + LicenseType.TextSite => "TS", + LicenseType.RedisSite => "RS", + LicenseType.OrmLiteSite => "OS", + _ => "UN", + }; + + private static LicenseKey VerifyIndividualLicense(string licenseKey) + { + if (licenseKey == null) + return null; + if (licenseKey.Length < 100) + return null; + if (!licenseKey.StartsWith(IndividualPrefix)) + return null; + var keyText = licenseKey.LastLeftPart(' '); + var keySign = licenseKey.LastRightPart(' '); + if (keySign.Length < 48) + return null; + + try + { + var rsa = System.Security.Cryptography.RSA.Create(); + rsa.FromXml(LicensePublicKey); + +#if !NETCORE + var verified = ((System.Security.Cryptography.RSACryptoServiceProvider)rsa) + .VerifyData(keyText.ToUtf8Bytes(), "SHA256", Convert.FromBase64String(keySign)); +#else + var verified = rsa.VerifyData(keyText.ToUtf8Bytes(), + Convert.FromBase64String(keySign), + System.Security.Cryptography.HashAlgorithmName.SHA256, + System.Security.Cryptography.RSASignaturePadding.Pkcs1); +#endif + if (verified) + { + var yearStr = keyText.Substring(IndividualPrefix.Length).LeftPart(' '); + if (yearStr.Length == 4 && int.TryParse(yearStr, out var year)) + { + return new LicenseKey { + Expiry = new DateTime(year + 1, 1, 1, 0, 0, 0, DateTimeKind.Utc), + Hash = keySign, + Name = keyText, + Type = LicenseType.FreeIndividual, + }; + } + } + } + catch { } + + return null; + } + + private static LicenseKey VerifyOpenSourceLicense(string licenseKey) + { + if (licenseKey == null) + return null; + if (licenseKey.Length < 100) + return null; + if (!licenseKey.StartsWith(OpenSourcePrefix)) + return null; + var keyText = licenseKey.LastLeftPart(' '); + var keySign = licenseKey.LastRightPart(' '); + if (keySign.Length < 48) + return null; + + try + { + var rsa = System.Security.Cryptography.RSA.Create(); + rsa.FromXml(LicensePublicKey); + +#if !NETCORE + var verified = ((System.Security.Cryptography.RSACryptoServiceProvider)rsa) + .VerifyData(keyText.ToUtf8Bytes(), "SHA256", Convert.FromBase64String(keySign)); +#else + var verified = rsa.VerifyData(keyText.ToUtf8Bytes(), + Convert.FromBase64String(keySign), + System.Security.Cryptography.HashAlgorithmName.SHA256, + System.Security.Cryptography.RSASignaturePadding.Pkcs1); +#endif + if (verified) + { + var yearStr = keyText.Substring(OpenSourcePrefix.Length).RightPart(' ').LeftPart(' '); + if (yearStr.Length == 4 && int.TryParse(yearStr, out var year)) + { + return new LicenseKey { + Expiry = new DateTime(year + 1, 1, 1, 0, 0, 0, DateTimeKind.Utc), + Hash = keySign, + Name = keyText, + Type = LicenseType.FreeOpenSource, + }; + } + } + } + catch { } + + return null; } public static void RemoveLicense() @@ -236,7 +450,7 @@ public static void RemoveLicense() public static LicenseFeature ActivatedLicenseFeatures() { - return __activatedLicense != null ? __activatedLicense.GetLicensedFeatures() : LicenseFeature.None; + return __activatedLicense?.LicenseKey.GetLicensedFeatures() ?? LicenseFeature.None; } public static void ApprovedUsage(LicenseFeature licenseFeature, LicenseFeature requestedFeature, @@ -333,6 +547,8 @@ public static LicenseFeature GetLicensedFeatures(this LicenseKey key) case LicenseType.Free: return LicenseFeature.Free; + case LicenseType.FreeIndividual: + case LicenseType.FreeOpenSource: case LicenseType.Indie: case LicenseType.Business: case LicenseType.Enterprise: @@ -423,12 +639,190 @@ public static string GetHashKeyToSign(this LicenseKey key) public static Exception GetInnerMostException(this Exception ex) { - //Extract true exception from static intializers (e.g. LicenseException) + //Extract true exception from static initializers (e.g. LicenseException) while (ex.InnerException != null) { ex = ex.InnerException; } return ex; } + + //License Utils + public static bool VerifySignedHash(byte[] DataToVerify, byte[] SignedData, System.Security.Cryptography.RSAParameters Key) + { + try + { + var RSAalg = new System.Security.Cryptography.RSACryptoServiceProvider(); + RSAalg.ImportParameters(Key); + return RSAalg.VerifySha1Data(DataToVerify, SignedData); + + } + catch (System.Security.Cryptography.CryptographicException ex) + { + Tracer.Instance.WriteError(ex); + return false; + } + } + + public static LicenseKey VerifyLicenseKeyText(string licenseKeyText) + { +#if NETFX || NETCORE + LicenseKey key; + try + { + if (!licenseKeyText.VerifyLicenseKeyText(out key)) + throw new ArgumentException("licenseKeyText"); + } + catch (Exception) + { + if (!VerifyLicenseKeyTextFallback(licenseKeyText, out key)) + throw; + } + return key; +#else + return licenseKeyText.ToLicenseKey(); +#endif + } + + private static void FromXml(this System.Security.Cryptography.RSA rsa, string xml) + { +#if NETFX + rsa.FromXmlString(xml); +#else + //throws PlatformNotSupportedException + var csp = ExtractFromXml(xml); + rsa.ImportParameters(csp); +#endif + } + +#if !NET45 + private static System.Security.Cryptography.RSAParameters ExtractFromXml(string xml) + { + var csp = new System.Security.Cryptography.RSAParameters(); + using (var reader = System.Xml.XmlReader.Create(new StringReader(xml))) + { + while (reader.Read()) + { + if (reader.NodeType != System.Xml.XmlNodeType.Element) + continue; + + var elName = reader.Name; + if (elName == "RSAKeyValue") + continue; + + do { + reader.Read(); + } while (reader.NodeType != System.Xml.XmlNodeType.Text && reader.NodeType != System.Xml.XmlNodeType.EndElement); + + if (reader.NodeType == System.Xml.XmlNodeType.EndElement) + continue; + + var value = reader.Value; + switch (elName) + { + case "Modulus": + csp.Modulus = Convert.FromBase64String(value); + break; + case "Exponent": + csp.Exponent = Convert.FromBase64String(value); + break; + case "P": + csp.P = Convert.FromBase64String(value); + break; + case "Q": + csp.Q = Convert.FromBase64String(value); + break; + case "DP": + csp.DP = Convert.FromBase64String(value); + break; + case "DQ": + csp.DQ = Convert.FromBase64String(value); + break; + case "InverseQ": + csp.InverseQ = Convert.FromBase64String(value); + break; + case "D": + csp.D = Convert.FromBase64String(value); + break; + } + } + + return csp; + } + } +#endif + + public static bool VerifyLicenseKeyText(this string licenseKeyText, out LicenseKey key) + { + var publicRsaProvider = new System.Security.Cryptography.RSACryptoServiceProvider(); + publicRsaProvider.FromXml(LicenseUtils.LicensePublicKey); + var publicKeyParams = publicRsaProvider.ExportParameters(false); + + key = licenseKeyText.ToLicenseKey(); + var originalData = key.GetHashKeyToSign().ToUtf8Bytes(); + var signedData = Convert.FromBase64String(key.Hash); + + return VerifySignedHash(originalData, signedData, publicKeyParams); + } + + public static bool VerifyLicenseKeyTextFallback(this string licenseKeyText, out LicenseKey key) + { + System.Security.Cryptography.RSAParameters publicKeyParams; + try + { + var publicRsaProvider = new System.Security.Cryptography.RSACryptoServiceProvider(); + publicRsaProvider.FromXml(LicenseUtils.LicensePublicKey); + publicKeyParams = publicRsaProvider.ExportParameters(false); + } + catch (Exception ex) + { + throw new Exception("Could not import LicensePublicKey", ex); + } + + try + { + key = licenseKeyText.ToLicenseKeyFallback(); + } + catch (Exception ex) + { + throw new Exception("Could not deserialize LicenseKeyText Manually", ex); + } + + byte[] originalData; + byte[] signedData; + + try + { + originalData = key.GetHashKeyToSign().ToUtf8Bytes(); + } + catch (Exception ex) + { + throw new Exception("Could not convert HashKey to UTF-8", ex); + } + + try + { + signedData = Convert.FromBase64String(key.Hash); + } + catch (Exception ex) + { + throw new Exception("Could not convert key.Hash from Base64", ex); + } + + try + { + return VerifySignedHash(originalData, signedData, publicKeyParams); + } + catch (Exception ex) + { + throw new Exception($"Could not Verify License Key ({originalData.Length}, {signedData.Length})", ex); + } + } + + public static bool VerifySha1Data(this System.Security.Cryptography.RSACryptoServiceProvider RSAalg, byte[] unsignedData, byte[] encryptedData) + { + using var sha = System.Security.Cryptography.SHA1.Create(); + return RSAalg.VerifyData(unsignedData, sha, encryptedData); + } } } \ No newline at end of file diff --git a/src/ServiceStack.Text/MemoryProvider.cs b/src/ServiceStack.Text/MemoryProvider.cs new file mode 100644 index 000000000..4876ebc20 --- /dev/null +++ b/src/ServiceStack.Text/MemoryProvider.cs @@ -0,0 +1,81 @@ +using System; +using System.Globalization; +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using ServiceStack.Text.Common; +using ServiceStack.Text.Json; + +namespace ServiceStack.Text +{ + public abstract class MemoryProvider + { + public static MemoryProvider Instance = +#if NETCORE && !NETSTANDARD2_0 + NetCoreMemory.Provider; +#else + DefaultMemory.Provider; +#endif + + internal const string BadFormat = "Input string was not in a correct format."; + internal const string OverflowMessage = "Value was either too large or too small for an {0}."; + + public abstract bool TryParseBoolean(ReadOnlySpan value, out bool result); + public abstract bool ParseBoolean(ReadOnlySpan value); + + public abstract bool TryParseDecimal(ReadOnlySpan value, out decimal result); + public abstract decimal ParseDecimal(ReadOnlySpan value); + public abstract decimal ParseDecimal(ReadOnlySpan value, bool allowThousands); + + public abstract bool TryParseFloat(ReadOnlySpan value, out float result); + public abstract float ParseFloat(ReadOnlySpan value); + + public abstract bool TryParseDouble(ReadOnlySpan value, out double result); + public abstract double ParseDouble(ReadOnlySpan value); + + public abstract sbyte ParseSByte(ReadOnlySpan value); + public abstract byte ParseByte(ReadOnlySpan value); + public abstract short ParseInt16(ReadOnlySpan value); + public abstract ushort ParseUInt16(ReadOnlySpan value); + public abstract int ParseInt32(ReadOnlySpan value); + public abstract uint ParseUInt32(ReadOnlySpan value); + public abstract uint ParseUInt32(ReadOnlySpan value, NumberStyles style); + public abstract long ParseInt64(ReadOnlySpan value); + public abstract ulong ParseUInt64(ReadOnlySpan value); + + public abstract Guid ParseGuid(ReadOnlySpan value); + + public abstract byte[] ParseBase64(ReadOnlySpan value); + + public abstract string ToBase64(ReadOnlyMemory value); + + public abstract void Write(Stream stream, ReadOnlyMemory value); + public abstract void Write(Stream stream, ReadOnlyMemory value); + + public abstract Task WriteAsync(Stream stream, ReadOnlyMemory value, CancellationToken token = default); + public abstract Task WriteAsync(Stream stream, ReadOnlyMemory value, CancellationToken token = default); + + public abstract Task WriteAsync(Stream stream, ReadOnlySpan value, CancellationToken token = default); + + public abstract object Deserialize(Stream stream, Type type, DeserializeStringSpanDelegate deserializer); + + public abstract Task DeserializeAsync(Stream stream, Type type, + DeserializeStringSpanDelegate deserializer); + + public abstract StringBuilder Append(StringBuilder sb, ReadOnlySpan value); + + public abstract int GetUtf8CharCount(ReadOnlySpan bytes); + public abstract int GetUtf8ByteCount(ReadOnlySpan chars); + + public abstract ReadOnlyMemory ToUtf8(ReadOnlySpan source); + public abstract ReadOnlyMemory FromUtf8(ReadOnlySpan source); + + public abstract int ToUtf8(ReadOnlySpan source, Span destination); + public abstract int FromUtf8(ReadOnlySpan source, Span destination); + + public abstract byte[] ToUtf8Bytes(ReadOnlySpan source); + public abstract string FromUtf8Bytes(ReadOnlySpan source); + public abstract MemoryStream ToMemoryStream(ReadOnlySpan source); + } +} \ No newline at end of file diff --git a/src/ServiceStack.Text/MimeTypes.cs b/src/ServiceStack.Text/MimeTypes.cs new file mode 100644 index 000000000..4dfe3b122 --- /dev/null +++ b/src/ServiceStack.Text/MimeTypes.cs @@ -0,0 +1,453 @@ +using System; +using System.Collections.Generic; + +namespace ServiceStack; + +public static class MimeTypes +{ + public static Dictionary ExtensionMimeTypes = new(); + public const string Utf8Suffix = "; charset=utf-8"; + + public const string Html = "text/html"; + public const string HtmlUtf8 = Html + Utf8Suffix; + public const string Css = "text/css"; + public const string Xml = "application/xml"; + public const string XmlText = "text/xml"; + public const string Json = "application/json"; + public const string ProblemJson = "application/problem+json"; + public const string JsonText = "text/json"; + public const string Jsv = "application/jsv"; + public const string JsvText = "text/jsv"; + public const string Csv = "text/csv"; + public const string ProtoBuf = "application/x-protobuf"; + public const string JavaScript = "text/javascript"; + public const string WebAssembly = "application/wasm"; + public const string Jar = "application/java-archive"; + public const string Dmg = "application/x-apple-diskimage"; + public const string Pkg = "application/x-newton-compatible-pkg"; + + public const string FormUrlEncoded = "application/x-www-form-urlencoded"; + public const string MultiPartFormData = "multipart/form-data"; + public const string JsonReport = "text/jsonreport"; + public const string Soap11 = "text/xml; charset=utf-8"; + public const string Soap12 = "application/soap+xml"; + public const string Yaml = "application/yaml"; + public const string YamlText = "text/yaml"; + public const string PlainText = "text/plain"; + public const string MarkdownText = "text/markdown"; + public const string MsgPack = "application/x-msgpack"; + public const string Wire = "application/x-wire"; + public const string Compressed = "application/x-compressed"; + public const string NetSerializer = "application/x-netserializer"; + public const string Excel = "application/excel"; + public const string MsWord = "application/msword"; + public const string Cert = "application/x-x509-ca-cert"; + + public const string ImagePng = "image/png"; + public const string ImageGif = "image/gif"; + public const string ImageJpg = "image/jpeg"; + public const string ImageSvg = "image/svg+xml"; + + public const string Bson = "application/bson"; + public const string Binary = "application/octet-stream"; + public const string ServerSentEvents = "text/event-stream"; + + public static string GetExtension(string mimeType) + { + switch (mimeType) + { + case ProtoBuf: + return ".pbuf"; + } + + var parts = mimeType.Split('/'); + if (parts.Length == 1) return "." + parts[0].LeftPart('+').LeftPart(';'); + if (parts.Length == 2) return "." + parts[1].LeftPart('+').LeftPart(';'); + + throw new NotSupportedException("Unknown mimeType: " + mimeType); + } + + //Lower cases and trims left part of content-type prior ';' + public static string GetRealContentType(string contentType) + { + if (contentType == null) + return null; + + int start = -1, end = -1; + + for(int i=0; i < contentType.Length; i++) + { + if (!char.IsWhiteSpace(contentType[i])) + { + if (contentType[i] == ';') + break; + if (start == -1) + { + start = i; + } + end = i; + } + } + + return start != -1 + ? contentType.Substring(start, end - start + 1).ToLowerInvariant() + : null; + } + + /// + /// Case-insensitive, trimmed compare of two content types from start to ';', i.e. without charset suffix + /// + public static bool MatchesContentType(string contentType, string matchesContentType) + { + if (contentType == null || matchesContentType == null) + return false; + + int start = -1, matchStart = -1, matchEnd = -1; + + for (var i=0; i < contentType.Length; i++) + { + if (char.IsWhiteSpace(contentType[i])) + continue; + start = i; + break; + } + + for (var i=0; i < matchesContentType.Length; i++) + { + if (char.IsWhiteSpace(matchesContentType[i])) + continue; + if (matchesContentType[i] == ';') + break; + if (matchStart == -1) + matchStart = i; + matchEnd = i; + } + + return start != -1 && matchStart != -1 && matchEnd != -1 + && string.Compare(contentType, start, + matchesContentType, matchStart, matchEnd - matchStart + 1, + StringComparison.OrdinalIgnoreCase) == 0; + } + + public static Func IsBinaryFilter { get; set; } + + public static bool IsBinary(string contentType) + { + var userFilter = IsBinaryFilter?.Invoke(contentType); + if (userFilter != null) + return userFilter.Value; + + var realContentType = GetRealContentType(contentType); + switch (realContentType) + { + case ProtoBuf: + case MsgPack: + case Binary: + case Bson: + case Wire: + case Cert: + case Excel: + case MsWord: + case Compressed: + case WebAssembly: + case Jar: + case Dmg: + case Pkg: + return true; + } + + // Text format exceptions to below heuristics + switch (realContentType) + { + case ImageSvg: + return false; + } + + var primaryType = realContentType.LeftPart('/'); + var secondaryType = realContentType.RightPart('/'); + switch (primaryType) + { + case "image": + case "audio": + case "video": + return true; + } + + if (secondaryType.StartsWith("pkc") + || secondaryType.StartsWith("x-pkc") + || secondaryType.StartsWith("font") + || secondaryType.StartsWith("vnd.ms-")) + return true; + + return false; + } + + public static string GetMimeType(string fileNameOrExt) + { + if (string.IsNullOrEmpty(fileNameOrExt)) + throw new ArgumentNullException(nameof(fileNameOrExt)); + + var fileExt = fileNameOrExt.LastRightPart('.').ToLower(); + if (ExtensionMimeTypes.TryGetValue(fileExt, out var mimeType)) + { + return mimeType; + } + + switch (fileExt) + { + case "jpeg": + return "image/jpeg"; + case "gif": + return "image/gif"; + case "png": + return "image/png"; + case "tiff": + return "image/tiff"; + case "bmp": + return "image/bmp"; + case "webp": + return "image/webp"; + + case "jpg": + return "image/jpeg"; + + case "tif": + return "image/tiff"; + + case "svg": + return ImageSvg; + + case "ico": + return "image/x-icon"; + + case "htm": + case "html": + case "shtml": + return "text/html"; + + case "js": + return "text/javascript"; + case "ts": + return "text/typescript"; + case "jsx": + return "text/jsx"; + + case "csv": + return Csv; + case "css": + return Css; + + case "cs": + return "text/x-csharp"; + case "fs": + return "text/x-fsharp"; + case "vb": + return "text/x-vb"; + case "dart": + return "application/dart"; + case "go": + return "text/x-go"; + case "kt": + case "kts": + return "text/x-kotlin"; + case "java": + return "text/x-java"; + case "py": + return "text/x-python"; + case "groovy": + case "gradle": + return "text/x-groovy"; + + case "yml": + case "yaml": + return YamlText; + + case "sh": + return "text/x-sh"; + case "bat": + case "cmd": + return "application/bat"; + + case "xml": + case "csproj": + case "fsproj": + case "vbproj": + return "text/xml"; + + case "txt": + case "ps1": + return "text/plain"; + + case "sgml": + return "text/sgml"; + + case "mp3": + return "audio/mpeg3"; + + case "au": + case "snd": + return "audio/basic"; + + case "aac": + case "ac3": + case "aiff": + case "m4a": + case "m4b": + case "m4p": + case "mid": + case "midi": + case "wav": + return "audio/" + fileExt; + + case "qt": + case "mov": + return "video/quicktime"; + + case "mpg": + return "video/mpeg"; + + case "ogv": + return "video/ogg"; + + case "3gpp": + case "avi": + case "dv": + case "divx": + case "ogg": + case "mp4": + case "webm": + return "video/" + fileExt; + + case "rtf": + return "application/" + fileExt; + + case "xls": + case "xlt": + case "xla": + return Excel; + + case "xlsx": + return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; + case "xltx": + return "application/vnd.openxmlformats-officedocument.spreadsheetml.template"; + + case "doc": + case "dot": + return MsWord; + + case "docx": + return "application/vnd.openxmlformats-officedocument.wordprocessingml.document"; + case "dotx": + return "application/vnd.openxmlformats-officedocument.wordprocessingml.template"; + + case "ppt": + case "oit": + case "pps": + case "ppa": + return "application/vnd.ms-powerpoint"; + + case "pptx": + return "application/vnd.openxmlformats-officedocument.presentationml.presentation"; + case "potx": + return "application/vnd.openxmlformats-officedocument.presentationml.template"; + case "ppsx": + return "application/vnd.openxmlformats-officedocument.presentationml.slideshow"; + + case "mdb": + return "application/vnd.ms-access"; + + case "cer": + case "crt": + case "der": + return Cert; + + case "p10": + return "application/pkcs10"; + case "p12": + return "application/x-pkcs12"; + case "p7b": + case "spc": + return "application/x-pkcs7-certificates"; + case "p7c": + case "p7m": + return "application/pkcs7-mime"; + case "p7r": + return "application/x-pkcs7-certreqresp"; + case "p7s": + return "application/pkcs7-signature"; + case "sst": + return "application/vnd.ms-pki.certstore"; + + case "gz": + case "tgz": + case "zip": + case "rar": + case "lzh": + case "z": + return Compressed; + + case "eot": + return "application/vnd.ms-fontobject"; + + case "ttf": + return "application/octet-stream"; + + case "woff": + return "application/font-woff"; + case "woff2": + return "application/font-woff2"; + + case "jar": + return Jar; + + case "aaf": + case "aca": + case "asd": + case "bin": + case "cab": + case "chm": + case "class": + case "cur": + case "db": + case "dat": + case "deploy": + case "dll": + case "dsp": + case "exe": + case "fla": + case "ics": + case "inf": + case "mix": + case "msi": + case "mso": + case "obj": + case "ocx": + case "prm": + case "prx": + case "psd": + case "psp": + case "qxd": + case "sea": + case "snp": + case "so": + case "sqlite": + case "toc": + case "u32": + case "xmp": + case "xsn": + case "xtp": + return Binary; + + case "wasm": + return WebAssembly; + + case "dmg": + return Dmg; + case "pkg": + return Pkg; + + default: + return "application/" + fileExt; + } + } +} \ No newline at end of file diff --git a/src/ServiceStack.Text/MurmurHash2.cs b/src/ServiceStack.Text/MurmurHash2.cs new file mode 100644 index 000000000..807f51b3f --- /dev/null +++ b/src/ServiceStack.Text/MurmurHash2.cs @@ -0,0 +1,65 @@ +using System; + +namespace ServiceStack.Text +{ + // https://softwareengineering.stackexchange.com/questions/49550/which-hashing-algorithm-is-best-for-uniqueness-and-speed#answer-145633 + // https://github.com/jitbit/MurmurHash.net + public class MurmurHash2 + { + public static uint Hash(string data) + { + return Hash(System.Text.Encoding.UTF8.GetBytes(data)); + } + + public static uint Hash(byte[] data) + { + return Hash(data, 0xc58f1a7a); + } + const uint m = 0x5bd1e995; + const int r = 24; + + public static uint Hash(byte[] data, uint seed) + { + int length = data.Length; + if (length == 0) + return 0; + uint h = seed ^ (uint)length; + int currentIndex = 0; + while (length >= 4) + { + uint k = (uint)(data[currentIndex++] | data[currentIndex++] << 8 | data[currentIndex++] << 16 | data[currentIndex++] << 24); + k *= m; + k ^= k >> r; + k *= m; + + h *= m; + h ^= k; + length -= 4; + } + switch (length) + { + case 3: + h ^= (UInt16)(data[currentIndex++] | data[currentIndex++] << 8); + h ^= (uint)(data[currentIndex] << 16); + h *= m; + break; + case 2: + h ^= (UInt16)(data[currentIndex++] | data[currentIndex] << 8); + h *= m; + break; + case 1: + h ^= data[currentIndex]; + h *= m; + break; + default: + break; + } + + h ^= h >> 13; + h *= m; + h ^= h >> 15; + + return h; + } + } +} \ No newline at end of file diff --git a/src/ServiceStack.Text/NetCoreMemory.cs b/src/ServiceStack.Text/NetCoreMemory.cs new file mode 100644 index 000000000..7a2aaed51 --- /dev/null +++ b/src/ServiceStack.Text/NetCoreMemory.cs @@ -0,0 +1,231 @@ +#if NETCORE && !NETSTANDARD2_0 + +using System; +using System.Buffers.Text; +using System.Globalization; +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using ServiceStack.Text.Common; +using ServiceStack.Text.Pools; + +namespace ServiceStack.Text +{ + public sealed class NetCoreMemory : MemoryProvider + { + private static NetCoreMemory provider; + public static NetCoreMemory Provider => provider ??= new NetCoreMemory(); + private NetCoreMemory() { } + + public static void Configure() => Instance = Provider; + + public override bool ParseBoolean(ReadOnlySpan value) => bool.Parse(value); + + public override bool TryParseBoolean(ReadOnlySpan value, out bool result) => + bool.TryParse(value, out result); + + public override bool TryParseDecimal(ReadOnlySpan value, out decimal result) => + decimal.TryParse(value, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out result); + + public override decimal ParseDecimal(ReadOnlySpan value, bool allowThousands) => + decimal.Parse(value, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture); + + public override bool TryParseFloat(ReadOnlySpan value, out float result) => + float.TryParse(value, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out result); + + public override bool TryParseDouble(ReadOnlySpan value, out double result) => + double.TryParse(value, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out result); + + public override decimal ParseDecimal(ReadOnlySpan value) => + decimal.Parse(value, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture); + + public override float ParseFloat(ReadOnlySpan value) => + float.Parse(value, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture); + + public override double ParseDouble(ReadOnlySpan value) => + double.Parse(value, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture); + + public override sbyte ParseSByte(ReadOnlySpan value) => sbyte.Parse(value); + + public override byte ParseByte(ReadOnlySpan value) => byte.Parse(value); + + public override short ParseInt16(ReadOnlySpan value) => short.Parse(value); + + public override ushort ParseUInt16(ReadOnlySpan value) => ushort.Parse(value); + + public override int ParseInt32(ReadOnlySpan value) => int.Parse(value); + + public override uint ParseUInt32(ReadOnlySpan value) => uint.Parse(value); + + public override uint ParseUInt32(ReadOnlySpan value, NumberStyles style) => uint.Parse(value.ToString(), NumberStyles.HexNumber); + + public override long ParseInt64(ReadOnlySpan value) => long.Parse(value); + + public override ulong ParseUInt64(ReadOnlySpan value) => ulong.Parse(value); + + public override Guid ParseGuid(ReadOnlySpan value) => Guid.Parse(value); + + public override byte[] ParseBase64(ReadOnlySpan value) + { + byte[] bytes = BufferPool.GetBuffer(Base64.GetMaxDecodedFromUtf8Length(value.Length)); + try + { + if (Convert.TryFromBase64Chars(value, bytes, out var bytesWritten)) + { + var ret = new byte[bytesWritten]; + Buffer.BlockCopy(bytes, 0, ret, 0, bytesWritten); + return ret; + } + else + { + var chars = value.ToArray(); + return Convert.FromBase64CharArray(chars, 0, chars.Length); + } + } + finally + { + BufferPool.ReleaseBufferToPool(ref bytes); + } + } + + public override string ToBase64(ReadOnlyMemory value) + { + return Convert.ToBase64String(value.Span); + } + + public override void Write(Stream stream, ReadOnlyMemory value) + { + var utf8 = ToUtf8(value.Span); + if (stream is MemoryStream ms) + ms.Write(utf8.Span); + else + stream.Write(utf8.Span); + } + + public override void Write(Stream stream, ReadOnlyMemory value) + { + if (stream is MemoryStream ms) + ms.Write(value.Span); + else + stream.Write(value.Span); + } + + public override Task WriteAsync(Stream stream, ReadOnlyMemory value, CancellationToken token = default) => + WriteAsync(stream, value.Span, token); + + public override Task WriteAsync(Stream stream, ReadOnlySpan value, CancellationToken token=default) + { + var utf8 = ToUtf8(value); + if (stream is MemoryStream ms) + ms.Write(utf8.Span); + else + return Task.FromResult(stream.WriteAsync(utf8, token)); + + return TypeConstants.EmptyTask; + } + + public override async Task WriteAsync(Stream stream, ReadOnlyMemory value, CancellationToken token = default) + { + if (stream is MemoryStream ms) + ms.Write(value.Span); + else + await stream.WriteAsync(value, token).ConfigAwait(); + } + + public override object Deserialize(Stream stream, Type type, DeserializeStringSpanDelegate deserializer) + { + var fromPool = false; + + if (!(stream is MemoryStream ms)) + { + fromPool = true; + + if (stream.CanSeek) + stream.Position = 0; + + ms = stream.CopyToNewMemoryStream(); + } + + return Deserialize(ms, fromPool, type, deserializer); + } + + public override async Task DeserializeAsync(Stream stream, Type type, DeserializeStringSpanDelegate deserializer) + { + var fromPool = false; + + if (!(stream is MemoryStream ms)) + { + fromPool = true; + + if (stream.CanSeek) + stream.Position = 0; + + ms = await stream.CopyToNewMemoryStreamAsync().ConfigAwait(); + } + + return Deserialize(ms, fromPool, type, deserializer); + } + + private static object Deserialize(MemoryStream memoryStream, bool fromPool, Type type, DeserializeStringSpanDelegate deserializer) + { + var bytes = memoryStream.GetBufferAsSpan().WithoutBom(); + var chars = CharPool.GetBuffer(Encoding.UTF8.GetCharCount(bytes)); + try + { + var charsWritten = Encoding.UTF8.GetChars(bytes, chars); + ReadOnlySpan charsSpan = chars; + var ret = deserializer(type, charsSpan.Slice(0, charsWritten)); + return ret; + } + finally + { + CharPool.ReleaseBufferToPool(ref chars); + + if (fromPool) + memoryStream.Dispose(); + } + } + + public override StringBuilder Append(StringBuilder sb, ReadOnlySpan value) + { + return sb.Append(value); + } + + public override int GetUtf8CharCount(ReadOnlySpan bytes) => Encoding.UTF8.GetCharCount(bytes); + + public override int GetUtf8ByteCount(ReadOnlySpan chars) => Encoding.UTF8.GetByteCount(chars); + + public override ReadOnlyMemory ToUtf8(ReadOnlySpan source) + { + Memory bytes = new byte[Encoding.UTF8.GetByteCount(source)]; + var bytesWritten = Encoding.UTF8.GetBytes(source, bytes.Span); + return bytes.Slice(0, bytesWritten); + } + + public override ReadOnlyMemory FromUtf8(ReadOnlySpan source) + { + source = source.WithoutBom(); + Memory chars = new char[Encoding.UTF8.GetCharCount(source)]; + var charsWritten = Encoding.UTF8.GetChars(source, chars.Span); + return chars.Slice(0, charsWritten); + } + + public override int ToUtf8(ReadOnlySpan source, Span destination) => Encoding.UTF8.GetBytes(source, destination); + + public override int FromUtf8(ReadOnlySpan source, Span destination) => Encoding.UTF8.GetChars(source.WithoutBom(), destination); + + public override byte[] ToUtf8Bytes(ReadOnlySpan source) => ToUtf8(source).ToArray(); + + public override string FromUtf8Bytes(ReadOnlySpan source) => FromUtf8(source.WithoutBom()).ToString(); + + public override MemoryStream ToMemoryStream(ReadOnlySpan source) + { + var ms = MemoryStreamFactory.GetStream(source.Length); + ms.Write(source); + return ms; + } + } +} + +#endif diff --git a/src/ServiceStack.Text/ObjectDictionary.cs b/src/ServiceStack.Text/ObjectDictionary.cs new file mode 100644 index 000000000..94b08b4fa --- /dev/null +++ b/src/ServiceStack.Text/ObjectDictionary.cs @@ -0,0 +1,59 @@ +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace ServiceStack +{ + /// + /// UX friendly alternative alias of Dictionary<string, object> + /// + public class ObjectDictionary : Dictionary + { + public ObjectDictionary() { } + public ObjectDictionary(int capacity) : base(capacity) { } + public ObjectDictionary(IEqualityComparer comparer) : base(comparer) { } + public ObjectDictionary(int capacity, IEqualityComparer comparer) : base(capacity, comparer) { } + public ObjectDictionary(IDictionary dictionary) : base(dictionary) { } + public ObjectDictionary(IDictionary dictionary, IEqualityComparer comparer) : base(dictionary, comparer) { } + protected ObjectDictionary(SerializationInfo info, StreamingContext context) : base(info, context) { } + } + + /// + /// UX friendly alternative alias of Dictionary<string, string> + /// + public class StringDictionary : Dictionary + { + public StringDictionary() { } + public StringDictionary(int capacity) : base(capacity) { } + public StringDictionary(IEqualityComparer comparer) : base(comparer) { } + public StringDictionary(int capacity, IEqualityComparer comparer) : base(capacity, comparer) { } + public StringDictionary(IDictionary dictionary) : base(dictionary) { } + public StringDictionary(IDictionary dictionary, IEqualityComparer comparer) : base(dictionary, comparer) { } + protected StringDictionary(SerializationInfo info, StreamingContext context) : base(info, context) { } + } + + /// + /// UX friendly alternative alias of List<KeyValuePair<string, object>gt; + /// + public class KeyValuePairs : List> + { + public KeyValuePairs() { } + public KeyValuePairs(int capacity) : base(capacity) { } + public KeyValuePairs(IEnumerable> collection) : base(collection) { } + + public static KeyValuePair Create(string key, object value) => + new KeyValuePair(key, value); + } + + /// + /// UX friendly alternative alias of List<KeyValuePair<string, string>gt; + /// + public class KeyValueStrings : List> + { + public KeyValueStrings() { } + public KeyValueStrings(int capacity) : base(capacity) { } + public KeyValueStrings(IEnumerable> collection) : base(collection) { } + + public static KeyValuePair Create(string key, string value) => + new KeyValuePair(key, value); + } +} \ No newline at end of file diff --git a/src/ServiceStack.Text/PathUtils.cs b/src/ServiceStack.Text/PathUtils.cs index 708180bac..73a7fe027 100644 --- a/src/ServiceStack.Text/PathUtils.cs +++ b/src/ServiceStack.Text/PathUtils.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Runtime.CompilerServices; using System.Text; using ServiceStack.Text; @@ -65,7 +66,7 @@ public static string MapAbsolutePath(this string relativePath) public static string MapHostAbsolutePath(this string relativePath) { var sep = PclExport.Instance.DirSep; -#if !NETSTANDARD2_0 +#if !NETCORE return PclExport.Instance.MapAbsolutePath(relativePath, $"{sep}.."); #else return PclExport.Instance.MapAbsolutePath(relativePath, $"{sep}..{sep}..{sep}.."); @@ -106,13 +107,40 @@ public static string AssertDir(this string dirPath) return dirPath; } + private static readonly char[] Slashes = { '/', '\\' }; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] //only trim/allocate if need to + private static string TrimEndIf(this string path, char[] chars) + { + if (string.IsNullOrEmpty(path) || chars == null || chars.Length == 0) + return path; + + var lastChar = path[path.Length - 1]; + foreach (var c in chars) + { + if (c == lastChar) + return path.TrimEnd(chars); + } + return path; + } + + public static string CombineWith(this string path, string withPath) + { + if (path == null) + path = ""; + if (string.IsNullOrEmpty(withPath)) + return path; + var startPath = path.TrimEndIf(Slashes); + return startPath + (withPath[0] == '/' ? withPath : "/" + withPath); + } + public static string CombineWith(this string path, params string[] thesePaths) { if (path == null) path = ""; if (thesePaths.Length == 1 && thesePaths[0] == null) return path; - var startPath = path.Length > 1 ? path.TrimEnd('/', '\\') : path; + var startPath = path.TrimEndIf(Slashes); var sb = StringBuilderThreadStatic.Allocate(); sb.Append(startPath); @@ -125,7 +153,7 @@ public static string CombineWith(this string path, params object[] thesePaths) if (thesePaths.Length == 1 && thesePaths[0] == null) return path; var sb = StringBuilderThreadStatic.Allocate(); - sb.Append(path.TrimEnd('/', '\\')); + sb.Append(path.TrimEndIf(Slashes)); AppendPaths(sb, ToStrings(thesePaths)); return StringBuilderThreadStatic.ReturnAndFree(sb); } @@ -155,10 +183,10 @@ public static string ResolvePaths(this string path) var resolvedPath = string.Join("/", combinedPaths); if (path[0] == '/' && prefix.Length == 0) - resolvedPath = '/' + resolvedPath; + resolvedPath = "/" + resolvedPath; return path[path.Length - 1] == '/' && resolvedPath.Length > 0 - ? prefix + resolvedPath + '/' + ? prefix + resolvedPath + "/" : prefix + resolvedPath; } diff --git a/src/ServiceStack.Text/Pcl.Dynamic.cs b/src/ServiceStack.Text/Pcl.Dynamic.cs index 06193ac69..0a462ab61 100644 --- a/src/ServiceStack.Text/Pcl.Dynamic.cs +++ b/src/ServiceStack.Text/Pcl.Dynamic.cs @@ -1,8 +1,6 @@ //Copyright (c) ServiceStack, Inc. All Rights Reserved. //License: https://raw.github.com/ServiceStack/ServiceStack/master/license.txt -#if !(PCL || LITE || NO_DYNAMIC) - using System; using System.Collections.Generic; using System.Dynamic; @@ -10,17 +8,9 @@ using ServiceStack.Text.Common; using ServiceStack.Text.Json; using System.Linq; -using System.Text; -#if NETSTANDARD2_0 -using Microsoft.Extensions.Primitives; -#else -using ServiceStack.Text.Support; -#endif -#if !(SL5 || __IOS__ || NETFX_CORE) using System.Reflection; using System.Reflection.Emit; -#endif namespace ServiceStack { @@ -29,19 +19,19 @@ public static class DeserializeDynamic { private static readonly ITypeSerializer Serializer = JsWriter.GetTypeSerializer(); - private static readonly ParseStringSegmentDelegate CachedParseFn; + private static readonly ParseStringSpanDelegate CachedParseFn; static DeserializeDynamic() { CachedParseFn = ParseDynamic; } - public static ParseStringDelegate Parse => v => CachedParseFn(new StringSegment(v)); + public static ParseStringDelegate Parse => v => CachedParseFn(v.AsSpan()); - public static ParseStringSegmentDelegate ParseStringSegment => CachedParseFn; + public static ParseStringSpanDelegate ParseStringSpan => CachedParseFn; - public static IDynamicMetaObjectProvider ParseDynamic(string value) => ParseDynamic(new StringSegment(value)); + public static IDynamicMetaObjectProvider ParseDynamic(string value) => ParseDynamic(value.AsSpan()); - public static IDynamicMetaObjectProvider ParseDynamic(StringSegment value) + public static IDynamicMetaObjectProvider ParseDynamic(ReadOnlySpan value) { var index = VerifyAndGetStartIndex(value, typeof(ExpandoObject)); @@ -60,7 +50,7 @@ public static IDynamicMetaObjectProvider ParseDynamic(StringSegment value) Serializer.EatMapKeySeperator(value, ref index); var elementValue = Serializer.EatValue(value, ref index); - var mapKey = Serializer.UnescapeString(keyValue).Value; + var mapKey = Serializer.UnescapeString(keyValue).ToString(); if (JsonUtils.IsJsObject(elementValue)) { @@ -68,15 +58,15 @@ public static IDynamicMetaObjectProvider ParseDynamic(StringSegment value) } else if (JsonUtils.IsJsArray(elementValue)) { - container[mapKey] = DeserializeList, TSerializer>.ParseStringSegment(elementValue); + container[mapKey] = DeserializeList, TSerializer>.ParseStringSpan(elementValue); } else if (tryToParsePrimitiveTypes) { - container[mapKey] = DeserializeType.ParsePrimitive(elementValue) ?? Serializer.UnescapeString(elementValue); + container[mapKey] = DeserializeType.ParsePrimitive(elementValue) ?? Serializer.UnescapeString(elementValue).Value(); } else { - container[mapKey] = Serializer.UnescapeString(elementValue); + container[mapKey] = Serializer.UnescapeString(elementValue).Value(); } Serializer.EatItemSeperatorOrMapEndChar(value, ref index); @@ -85,7 +75,7 @@ public static IDynamicMetaObjectProvider ParseDynamic(StringSegment value) return result; } - private static int VerifyAndGetStartIndex(StringSegment value, Type createMapType) + private static int VerifyAndGetStartIndex(ReadOnlySpan value, Type createMapType) { var index = 0; if (!Serializer.EatMapStartChar(value, ref index)) @@ -98,8 +88,6 @@ private static int VerifyAndGetStartIndex(StringSegment value, Type createMapTyp } } -//TODO: Workout how to fix broken CoreCLR SL5 build that uses dynamic - public class DynamicJson : DynamicObject { private readonly IDictionary _hash = new Dictionary(); @@ -114,7 +102,7 @@ public static dynamic Deserialize(string json) { // Support arbitrary nesting by using JsonObject var deserialized = JsonSerializer.DeserializeFromString(json); - var hash = deserialized.ToDictionary, string, object>(entry => entry.Key, entry => entry.Value); + var hash = deserialized.ToUnescapedDictionary().ToObjectDictionary(); return new DynamicJson(hash); } @@ -192,158 +180,4 @@ internal static string Underscored(IEnumerable pascalCase) return StringBuilderCache.ReturnAndFree(sb).ToLowerInvariant(); } } - -#if !(SL5 || __IOS__ || NETFX_CORE) - public static class DynamicProxy - { - public static T GetInstanceFor() - { - return (T)GetInstanceFor(typeof(T)); - } - - static readonly ModuleBuilder ModuleBuilder; - static readonly AssemblyBuilder DynamicAssembly; - static readonly Type[] EmptyTypes = new Type[0]; - - public static object GetInstanceFor(Type targetType) - { - lock (DynamicAssembly) - { - var constructedType = DynamicAssembly.GetType(ProxyName(targetType)) ?? GetConstructedType(targetType); - var instance = Activator.CreateInstance(constructedType); - return instance; - } - } - - static string ProxyName(Type targetType) - { - return targetType.Name + "Proxy"; - } - - static DynamicProxy() - { - var assemblyName = new AssemblyName("DynImpl"); -#if NETSTANDARD2_0 - DynamicAssembly = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run); -#else - DynamicAssembly = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave); -#endif - ModuleBuilder = DynamicAssembly.DefineDynamicModule("DynImplModule"); - } - - static Type GetConstructedType(Type targetType) - { - var typeBuilder = ModuleBuilder.DefineType(targetType.Name + "Proxy", TypeAttributes.Public); - - var ctorBuilder = typeBuilder.DefineConstructor( - MethodAttributes.Public, - CallingConventions.Standard, - new Type[] { }); - var ilGenerator = ctorBuilder.GetILGenerator(); - ilGenerator.Emit(OpCodes.Ret); - - IncludeType(targetType, typeBuilder); - - foreach (var face in targetType.GetInterfaces()) - IncludeType(face, typeBuilder); - -#if NETSTANDARD2_0 - return typeBuilder.CreateTypeInfo().AsType(); -#else - return typeBuilder.CreateType(); -#endif - } - - static void IncludeType(Type typeOfT, TypeBuilder typeBuilder) - { - var methodInfos = typeOfT.GetMethods(); - foreach (var methodInfo in methodInfos) - { - if (methodInfo.Name.StartsWith("set_", StringComparison.Ordinal)) continue; // we always add a set for a get. - - if (methodInfo.Name.StartsWith("get_", StringComparison.Ordinal)) - { - BindProperty(typeBuilder, methodInfo); - } - else - { - BindMethod(typeBuilder, methodInfo); - } - } - - typeBuilder.AddInterfaceImplementation(typeOfT); - } - - static void BindMethod(TypeBuilder typeBuilder, MethodInfo methodInfo) - { - var methodBuilder = typeBuilder.DefineMethod( - methodInfo.Name, - MethodAttributes.Public | MethodAttributes.Virtual, - methodInfo.ReturnType, - methodInfo.GetParameters().Select(p => p.GetType()).ToArray() - ); - var methodILGen = methodBuilder.GetILGenerator(); - if (methodInfo.ReturnType == typeof(void)) - { - methodILGen.Emit(OpCodes.Ret); - } - else - { - if (methodInfo.ReturnType.IsValueType || methodInfo.ReturnType.IsEnum) - { - MethodInfo getMethod = typeof(Activator).GetMethod("CreateInstance", new[] { typeof(Type) }); - LocalBuilder lb = methodILGen.DeclareLocal(methodInfo.ReturnType); - methodILGen.Emit(OpCodes.Ldtoken, lb.LocalType); - methodILGen.Emit(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle")); - methodILGen.Emit(OpCodes.Callvirt, getMethod); - methodILGen.Emit(OpCodes.Unbox_Any, lb.LocalType); - } - else - { - methodILGen.Emit(OpCodes.Ldnull); - } - methodILGen.Emit(OpCodes.Ret); - } - typeBuilder.DefineMethodOverride(methodBuilder, methodInfo); - } - - public static void BindProperty(TypeBuilder typeBuilder, MethodInfo methodInfo) - { - // Backing Field - string propertyName = methodInfo.Name.Replace("get_", ""); - Type propertyType = methodInfo.ReturnType; - FieldBuilder backingField = typeBuilder.DefineField("_" + propertyName, propertyType, FieldAttributes.Private); - - //Getter - MethodBuilder backingGet = typeBuilder.DefineMethod("get_" + propertyName, MethodAttributes.Public | - MethodAttributes.SpecialName | MethodAttributes.Virtual | - MethodAttributes.HideBySig, propertyType, EmptyTypes); - ILGenerator getIl = backingGet.GetILGenerator(); - - getIl.Emit(OpCodes.Ldarg_0); - getIl.Emit(OpCodes.Ldfld, backingField); - getIl.Emit(OpCodes.Ret); - - - //Setter - MethodBuilder backingSet = typeBuilder.DefineMethod("set_" + propertyName, MethodAttributes.Public | - MethodAttributes.SpecialName | MethodAttributes.Virtual | - MethodAttributes.HideBySig, null, new[] { propertyType }); - - ILGenerator setIl = backingSet.GetILGenerator(); - - setIl.Emit(OpCodes.Ldarg_0); - setIl.Emit(OpCodes.Ldarg_1); - setIl.Emit(OpCodes.Stfld, backingField); - setIl.Emit(OpCodes.Ret); - - // Property - PropertyBuilder propertyBuilder = typeBuilder.DefineProperty(propertyName, PropertyAttributes.None, propertyType, null); - propertyBuilder.SetGetMethod(backingGet); - propertyBuilder.SetSetMethod(backingSet); - } - } -#endif - } -#endif diff --git a/src/ServiceStack.Text/PclExport.NetCore.cs b/src/ServiceStack.Text/PclExport.NetCore.cs new file mode 100644 index 000000000..7cf1fc8f3 --- /dev/null +++ b/src/ServiceStack.Text/PclExport.NetCore.cs @@ -0,0 +1,41 @@ +#if (NETCORE || NET6_0_OR_GREATER) && !NETSTANDARD2_0 + +using System; +using ServiceStack.Text; +using ServiceStack.Text.Common; + +namespace ServiceStack +{ + public class Net6PclExport : NetStandardPclExport + { + public Net6PclExport() + { + this.PlatformName = Platforms.Net6; + ReflectionOptimizer.Instance = EmitReflectionOptimizer.Provider; + } + + public override ParseStringDelegate GetJsReaderParseMethod(Type type) + { + if (type.IsAssignableFrom(typeof(System.Dynamic.IDynamicMetaObjectProvider)) || + type.HasInterface(typeof(System.Dynamic.IDynamicMetaObjectProvider))) + { + return DeserializeDynamic.Parse; + } + + return null; + } + + public override ParseStringSpanDelegate GetJsReaderParseStringSpanMethod(Type type) + { + if (type.IsAssignableFrom(typeof(System.Dynamic.IDynamicMetaObjectProvider)) || + type.HasInterface(typeof(System.Dynamic.IDynamicMetaObjectProvider))) + { + return DeserializeDynamic.ParseStringSpan; + } + + return null; + } + } +} + +#endif \ No newline at end of file diff --git a/src/ServiceStack.Text/PclExport.Net40.cs b/src/ServiceStack.Text/PclExport.NetFx.cs similarity index 62% rename from src/ServiceStack.Text/PclExport.Net40.cs rename to src/ServiceStack.Text/PclExport.NetFx.cs index af79a86c5..9df18991b 100644 --- a/src/ServiceStack.Text/PclExport.Net40.cs +++ b/src/ServiceStack.Text/PclExport.NetFx.cs @@ -1,4 +1,4 @@ -#if !NETSTANDARD2_0 +#if NETFX using System; using System.Collections; using System.Collections.Concurrent; @@ -7,7 +7,6 @@ using System.Globalization; using System.IO; using System.Linq; -using System.Linq.Expressions; using System.Net; using System.Reflection; using System.Runtime.CompilerServices; @@ -20,42 +19,26 @@ using ServiceStack.Text; using ServiceStack.Text.Common; using ServiceStack.Text.Json; -using ServiceStack.Text.Support; - -#if !__IOS__ && !NETSTANDARD2_0 using System.Reflection.Emit; -using FastMember = ServiceStack.Text.FastMember; -#endif - -#if __UNIFIED__ -using Preserve = Foundation.PreserveAttribute; -#elif __IOS__ -using Preserve = MonoTouch.Foundation.PreserveAttribute; -#endif namespace ServiceStack { - public class Net40PclExport : PclExport + public class NetFxPclExport : PclExport { - public static Net40PclExport Provider = new Net40PclExport(); + public static NetFxPclExport Provider = new NetFxPclExport(); - public Net40PclExport() + public NetFxPclExport() { - this.SupportsEmit = SupportsExpression = true; this.DirSep = Path.DirectorySeparatorChar; this.AltDirSep = Path.DirectorySeparatorChar == '/' ? '\\' : '/'; this.RegexOptions = RegexOptions.Compiled; -#if DNXCORE50 - this.InvariantComparison = CultureInfo.InvariantCulture.CompareInfo.GetStringComparer(); - this.InvariantComparisonIgnoreCase = CultureInfo.InvariantCultureIgnoreCase.CompareInfo.GetStringComparer(); -#else this.InvariantComparison = StringComparison.InvariantCulture; this.InvariantComparisonIgnoreCase = StringComparison.InvariantCultureIgnoreCase; -#endif this.InvariantComparer = StringComparer.InvariantCulture; this.InvariantComparerIgnoreCase = StringComparer.InvariantCultureIgnoreCase; - this.PlatformName = Environment.OSVersion.Platform.ToString(); + this.PlatformName = Platforms.NetFX; + ReflectionOptimizer.Instance = EmitReflectionOptimizer.Provider; } public static PclExport Configure() @@ -69,11 +52,6 @@ public override string ReadAllText(string filePath) return File.ReadAllText(filePath); } - public override string ToTitleCase(string value) - { - return TextInfo.ToTitleCase(value).Replace("_", String.Empty); - } - public override string ToInvariantUpper(char value) { return value.ToString(CultureInfo.InvariantCulture).ToUpper(); @@ -127,11 +105,6 @@ public override string[] GetDirectoryNames(string dirPath, string searchPattern public override void RegisterLicenseFromConfig() { -#if ANDROID -#elif __IOS__ -#elif __MAC__ -#elif NETSTANDARD2_0 -#else string licenceKeyText; try { @@ -143,6 +116,7 @@ public override void RegisterLicenseFromConfig() return; } } + catch (NotSupportedException) { return; } // Ignore Unity/IL2CPP Exception catch (Exception ex) { licenceKeyText = Environment.GetEnvironmentVariable(EnvironmentKey)?.Trim(); @@ -164,7 +138,6 @@ public override void RegisterLicenseFromConfig() { LicenseUtils.RegisterLicense(licenceKeyText); } -#endif } public override string GetEnvironmentVariable(string name) @@ -182,6 +155,12 @@ public override void WriteLine(string format, params object[] args) Console.WriteLine(format, args); } + public override async Task WriteAndFlushAsync(Stream stream, byte[] bytes) + { + await stream.WriteAsync(bytes, 0, bytes.Length).ConfigAwait(); + await stream.FlushAsync().ConfigAwait(); + } + public override void AddCompression(WebRequest webReq) { var httpReq = (HttpWebRequest)webReq; @@ -290,86 +269,6 @@ public override Type GetGenericCollectionType(Type type) && t.GetGenericTypeDefinition() == typeof(ICollection<>), null).FirstOrDefault(); } - public override GetMemberDelegate CreateGetter(PropertyInfo propertyInfo) - { - return -#if NET45 - SupportsEmit ? PropertyInvoker.GetEmit(propertyInfo) : -#endif - SupportsExpression - ? PropertyInvoker.GetExpression(propertyInfo) - : base.CreateGetter(propertyInfo); - } - - public override GetMemberDelegate CreateGetter(PropertyInfo propertyInfo) - { - return -#if NET45 - SupportsEmit ? PropertyInvoker.GetEmit(propertyInfo) : -#endif - SupportsExpression - ? PropertyInvoker.GetExpression(propertyInfo) - : base.CreateGetter(propertyInfo); - } - - public override SetMemberDelegate CreateSetter(PropertyInfo propertyInfo) - { - return -#if NET45 - SupportsEmit ? PropertyInvoker.SetEmit(propertyInfo) : -#endif - SupportsExpression - ? PropertyInvoker.SetExpression(propertyInfo) - : base.CreateSetter(propertyInfo); - } - - public override SetMemberDelegate CreateSetter(PropertyInfo propertyInfo) - { - return SupportsExpression - ? PropertyInvoker.SetExpression(propertyInfo) - : base.CreateSetter(propertyInfo); - } - - public override GetMemberDelegate CreateGetter(FieldInfo fieldInfo) - { - return -#if NET45 - SupportsEmit ? FieldInvoker.GetEmit(fieldInfo) : -#endif - SupportsExpression - ? FieldInvoker.GetExpression(fieldInfo) - : base.CreateGetter(fieldInfo); - } - - public override GetMemberDelegate CreateGetter(FieldInfo fieldInfo) - { - return -#if NET45 - SupportsEmit ? FieldInvoker.GetEmit(fieldInfo) : -#endif - SupportsExpression - ? FieldInvoker.GetExpression(fieldInfo) - : base.CreateGetter(fieldInfo); - } - - public override SetMemberDelegate CreateSetter(FieldInfo fieldInfo) - { - return -#if NET45 - SupportsEmit ? FieldInvoker.SetEmit(fieldInfo) : -#endif - SupportsExpression - ? FieldInvoker.SetExpression(fieldInfo) - : base.CreateSetter(fieldInfo); - } - - public override SetMemberDelegate CreateSetter(FieldInfo fieldInfo) - { - return SupportsExpression - ? FieldInvoker.SetExpression(fieldInfo) - : base.CreateSetter(fieldInfo); - } - public override string ToXsdDateTimeString(DateTime dateTime) { #if !LITE @@ -420,7 +319,7 @@ public override ParseStringDelegate GetDictionaryParseMethod(Type t return null; } - public override ParseStringSegmentDelegate GetDictionaryParseStringSegmentMethod(Type type) + public override ParseStringSpanDelegate GetDictionaryParseStringSpanMethod(Type type) { if (type == typeof(Hashtable)) { @@ -438,7 +337,7 @@ public override ParseStringDelegate GetSpecializedCollectionParseMethod(Type type) + public override ParseStringSpanDelegate GetSpecializedCollectionParseStringSpanMethod(Type type) { if (type == typeof(StringCollection)) { @@ -450,7 +349,7 @@ public override ParseStringSegmentDelegate GetSpecializedCollectionParseStringSe public override ParseStringDelegate GetJsReaderParseMethod(Type type) { -#if !(__IOS__ || LITE) +#if !LITE if (type.IsAssignableFrom(typeof(System.Dynamic.IDynamicMetaObjectProvider)) || type.HasInterface(typeof(System.Dynamic.IDynamicMetaObjectProvider))) { @@ -460,13 +359,13 @@ public override ParseStringDelegate GetJsReaderParseMethod(Type typ return null; } - public override ParseStringSegmentDelegate GetJsReaderParseStringSegmentMethod(Type type) + public override ParseStringSpanDelegate GetJsReaderParseStringSpanMethod(Type type) { -#if !(__IOS__ || LITE) +#if !LITE if (type.IsAssignableFrom(typeof(System.Dynamic.IDynamicMetaObjectProvider)) || type.HasInterface(typeof(System.Dynamic.IDynamicMetaObjectProvider))) { - return DeserializeDynamic.ParseStringSegment; + return DeserializeDynamic.ParseStringSpan; } #endif return null; @@ -492,8 +391,7 @@ public override void CloseStream(Stream stream) public override LicenseKey VerifyLicenseKeyText(string licenseKeyText) { - LicenseKey key; - if (!licenseKeyText.VerifyLicenseKeyText(out key)) + if (!licenseKeyText.VerifyLicenseKeyText(out LicenseKey key)) throw new ArgumentException("licenseKeyText"); return key; @@ -549,16 +447,6 @@ public override string GetStackTrace() return Environment.StackTrace; } -#if !__IOS__ - public override Type UseType(Type type) - { - if (type.IsInterface || type.IsAbstract) - { - return DynamicProxy.GetInstanceFor(type).GetType(); - } - return type; - } - public override DataContractAttribute GetWeakDataContract(Type type) { return type.GetWeakDataContract(); @@ -573,320 +461,14 @@ public override DataMemberAttribute GetWeakDataMember(FieldInfo pi) { return pi.GetWeakDataMember(); } -#endif - } - -#if __MAC__ - public class MacPclExport : IosPclExport - { - public static new MacPclExport Provider = new MacPclExport(); - - public MacPclExport() - { - PlatformName = "MAC"; - SupportsEmit = SupportsExpression = true; - } - - public new static void Configure() - { - Configure(Provider); - } - } -#endif - -#if NET45 || NETFX_CORE - public class Net45PclExport : Net40PclExport - { - public static new Net45PclExport Provider = new Net45PclExport(); - - public Net45PclExport() - { - PlatformName = "NET45 " + Environment.OSVersion.Platform.ToString(); - } - - public new static void Configure() - { - Configure(Provider); - } - - public override Task WriteAndFlushAsync(Stream stream, byte[] bytes) - { - return stream.WriteAsync(bytes, 0, bytes.Length) - .ContinueWith(t => stream.FlushAsync()); - } } -#endif - -#if __IOS__ || __MAC__ - [Preserve(AllMembers = true)] - internal class Poco - { - public string Dummy { get; set; } - } - - public class IosPclExport : Net40PclExport - { - public static new IosPclExport Provider = new IosPclExport(); - - public IosPclExport() - { - PlatformName = "IOS"; - SupportsEmit = SupportsExpression = false; - } - - public new static void Configure() - { - Configure(Provider); - } - - public override void ResetStream(Stream stream) - { - // MonoTouch throws NotSupportedException when setting System.Net.WebConnectionStream.Position - // Not sure if the stream is used later though, so may have to copy to MemoryStream and - // pass that around instead after this point? - } - - /// - /// Provide hint to IOS AOT compiler to pre-compile generic classes for all your DTOs. - /// Just needs to be called once in a static constructor. - /// - [Preserve] - public static void InitForAot() - { - } - - [Preserve] - public override void RegisterForAot() - { - RegisterTypeForAot(); - - RegisterElement(); - - RegisterElement(); - RegisterElement(); - RegisterElement(); - RegisterElement(); - RegisterElement(); - RegisterElement(); - RegisterElement(); - RegisterElement(); - - RegisterElement(); - RegisterElement(); - RegisterElement(); - RegisterElement(); - RegisterElement(); - - RegisterElement(); - RegisterElement(); - RegisterElement(); - RegisterElement(); - RegisterElement(); - RegisterElement(); - RegisterElement(); - RegisterElement(); - RegisterElement(); - RegisterElement(); - RegisterElement(); - RegisterElement(); - RegisterElement(); - - //RegisterElement(); - - RegisterTypeForAot(); // used by DateTime - - // register built in structs - RegisterTypeForAot(); - RegisterTypeForAot(); - RegisterTypeForAot(); - RegisterTypeForAot(); - - RegisterTypeForAot(); - RegisterTypeForAot(); - RegisterTypeForAot(); - RegisterTypeForAot(); - } - - [Preserve] - public static void RegisterTypeForAot() - { - AotConfig.RegisterSerializers(); - } - - [Preserve] - public static void RegisterQueryStringWriter() - { - var i = 0; - if (QueryStringWriter.WriteFn() != null) i++; - } - - [Preserve] - public static int RegisterElement() - { - var i = 0; - i += AotConfig.RegisterSerializers(); - AotConfig.RegisterElement(); - AotConfig.RegisterElement(); - return i; - } - - /// - /// Class contains Ahead-of-Time (AOT) explicit class declarations which is used only to workaround "-aot-only" exceptions occured on device only. - /// - [Preserve(AllMembers = true)] - internal class AotConfig - { - internal static JsReader jsonReader; - internal static JsWriter jsonWriter; - internal static JsReader jsvReader; - internal static JsWriter jsvWriter; - internal static JsonTypeSerializer jsonSerializer; - internal static Text.Jsv.JsvTypeSerializer jsvSerializer; - - static AotConfig() - { - jsonSerializer = new JsonTypeSerializer(); - jsvSerializer = new Text.Jsv.JsvTypeSerializer(); - jsonReader = new JsReader(); - jsonWriter = new JsWriter(); - jsvReader = new JsReader(); - jsvWriter = new JsWriter(); - } - - internal static int RegisterSerializers() - { - var i = 0; - i += Register(); - if (jsonSerializer.GetParseFn() != null) i++; - if (jsonSerializer.GetWriteFn() != null) i++; - if (jsonReader.GetParseFn() != null) i++; - if (jsonWriter.GetWriteFn() != null) i++; - - i += Register(); - if (jsvSerializer.GetParseFn() != null) i++; - if (jsvSerializer.GetWriteFn() != null) i++; - if (jsvReader.GetParseFn() != null) i++; - if (jsvWriter.GetWriteFn() != null) i++; - - //RegisterCsvSerializer(); - RegisterQueryStringWriter(); - return i; - } - - internal static void RegisterCsvSerializer() - { - CsvSerializer.WriteFn(); - CsvSerializer.WriteObject(null, null); - CsvWriter.Write(null, default(IEnumerable)); - CsvWriter.WriteRow(null, default(T)); - } - - public static ParseStringDelegate GetParseFn(Type type) - { - var parseFn = JsonTypeSerializer.Instance.GetParseFn(type); - return parseFn; - } - - internal static int Register() where TSerializer : ITypeSerializer - { - var i = 0; - - if (JsonWriter.WriteFn() != null) i++; - if (JsonWriter.Instance.GetWriteFn() != null) i++; - if (JsonReader.Instance.GetParseFn() != null) i++; - if (JsonReader.Parse(null) != null) i++; - if (JsonReader.GetParseFn() != null) i++; - //if (JsWriter.GetTypeSerializer().GetWriteFn() != null) i++; - if (new List() != null) i++; - if (new T[0] != null) i++; - - JsConfig.ExcludeTypeInfo = false; - - if (JsConfig.OnDeserializedFn != null) i++; - if (JsConfig.HasDeserializeFn) i++; - if (JsConfig.SerializeFn != null) i++; - if (JsConfig.DeSerializeFn != null) i++; - //JsConfig.SerializeFn = arg => ""; - //JsConfig.DeSerializeFn = arg => default(T); - if (TypeConfig.Properties != null) i++; - -/* - if (WriteType.Write != null) i++; - if (WriteType.Write != null) i++; - - if (DeserializeBuiltin.Parse != null) i++; - if (DeserializeArray.Parse != null) i++; - DeserializeType.ExtractType(null); - DeserializeArrayWithElements.ParseGenericArray(null, null); - DeserializeCollection.ParseCollection(null, null, null); - DeserializeListWithElements.ParseGenericList(null, null, null); - - SpecializedQueueElements.ConvertToQueue(null); - SpecializedQueueElements.ConvertToStack(null); -*/ - - WriteListsOfElements.WriteList(null, null); - WriteListsOfElements.WriteIList(null, null); - WriteListsOfElements.WriteEnumerable(null, null); - WriteListsOfElements.WriteListValueType(null, null); - WriteListsOfElements.WriteIListValueType(null, null); - WriteListsOfElements.WriteGenericArrayValueType(null, null); - WriteListsOfElements.WriteArray(null, null); - - TranslateListWithElements.LateBoundTranslateToGenericICollection(null, null); - TranslateListWithConvertibleElements.LateBoundTranslateToGenericICollection(null, null); - - QueryStringWriter.WriteObject(null, null); - return i; - } - - internal static void RegisterElement() where TSerializer : ITypeSerializer - { - DeserializeDictionary.ParseDictionary(null, null, null, null); - DeserializeDictionary.ParseDictionary(null, null, null, null); - - ToStringDictionaryMethods.WriteIDictionary(null, null, null, null); - ToStringDictionaryMethods.WriteIDictionary(null, null, null, null); - - // Include List deserialisations from the Register<> method above. This solves issue where List properties on responses deserialise to null. - // No idea why this is happening because there is no visible exception raised. Suspect IOS is swallowing an AOT exception somewhere. - DeserializeArrayWithElements.ParseGenericArray(null, null); - DeserializeListWithElements.ParseGenericList(null, null, null); - - // Cannot use the line below for some unknown reason - when trying to compile to run on device, mtouch bombs during native code compile. - // Something about this line or its inner workings is offensive to mtouch. Luckily this was not needed for my List issue. - // DeserializeCollection.ParseCollection(null, null, null); - - TranslateListWithElements.LateBoundTranslateToGenericICollection(null, typeof(List)); - TranslateListWithConvertibleElements.LateBoundTranslateToGenericICollection(null, typeof(List)); - } - } - } -#endif - -#if ANDROID - public class AndroidPclExport : Net40PclExport - { - public static new AndroidPclExport Provider = new AndroidPclExport(); - - public AndroidPclExport() - { - PlatformName = "Android"; - } - - public new static void Configure() - { - Configure(Provider); - } - } -#endif internal class SerializerUtils where TSerializer : ITypeSerializer { private static readonly ITypeSerializer Serializer = JsWriter.GetTypeSerializer(); - private static int VerifyAndGetStartIndex(StringSegment value, Type createMapType) + private static int VerifyAndGetStartIndex(ReadOnlySpan value, Type createMapType) { var index = 0; if (!Serializer.EatMapStartChar(value, ref index)) @@ -898,11 +480,11 @@ private static int VerifyAndGetStartIndex(StringSegment value, Type createMapTyp return index; } - public static Hashtable ParseHashtable(string value) => ParseHashtable(new StringSegment(value)); + public static Hashtable ParseHashtable(string value) => ParseHashtable(value.AsSpan()); - public static Hashtable ParseHashtable(StringSegment value) + public static Hashtable ParseHashtable(ReadOnlySpan value) { - if (!value.HasValue) + if (value.IsEmpty) return null; var index = VerifyAndGetStartIndex(value, typeof(Hashtable)); @@ -917,10 +499,10 @@ public static Hashtable ParseHashtable(StringSegment value) var keyValue = Serializer.EatMapKey(value, ref index); Serializer.EatMapKeySeperator(value, ref index); var elementValue = Serializer.EatValue(value, ref index); - if (!keyValue.HasValue) continue; + if (keyValue.IsEmpty) continue; - var mapKey = keyValue.Value; - var mapValue = elementValue.Value; + var mapKey = keyValue.ToString(); + var mapValue = elementValue.Value(); result[mapKey] = mapValue; @@ -930,15 +512,15 @@ public static Hashtable ParseHashtable(StringSegment value) return result; } - public static StringCollection ParseStringCollection(string value) where TS : ITypeSerializer => ParseStringCollection(new StringSegment(value)); + public static StringCollection ParseStringCollection(string value) where TS : ITypeSerializer => ParseStringCollection(value.AsSpan()); - public static StringCollection ParseStringCollection(StringSegment value) where TS : ITypeSerializer + public static StringCollection ParseStringCollection(ReadOnlySpan value) where TS : ITypeSerializer { - if ((value = DeserializeListWithElements.StripList(value)) == null) return null; - return value.Length == 0 - ? new StringCollection() - : ToStringCollection(DeserializeListWithElements.ParseStringList(value)); + if ((value = DeserializeListWithElements.StripList(value)).IsNullOrEmpty()) + return value.IsEmpty ? null : new StringCollection(); + + return ToStringCollection(DeserializeListWithElements.ParseStringList(value)); } public static StringCollection ToStringCollection(List items) @@ -954,67 +536,9 @@ public static StringCollection ToStringCollection(List items) public static class PclExportExt { - //HttpUtils - public static WebResponse PostFileToUrl(this string url, - FileInfo uploadFileInfo, string uploadFileMimeType, - string accept = null, - Action requestFilter = null) - { - var webReq = (HttpWebRequest)WebRequest.Create(url); - using (var fileStream = uploadFileInfo.OpenRead()) - { - var fileName = uploadFileInfo.Name; - - webReq.UploadFile(fileStream, fileName, uploadFileMimeType, accept: accept, requestFilter: requestFilter, method: "POST"); - } - - if (HttpUtils.ResultsFilter != null) - return null; - - return webReq.GetResponse(); - } - - public static WebResponse PutFileToUrl(this string url, - FileInfo uploadFileInfo, string uploadFileMimeType, - string accept = null, - Action requestFilter = null) - { - var webReq = (HttpWebRequest)WebRequest.Create(url); - using (var fileStream = uploadFileInfo.OpenRead()) - { - var fileName = uploadFileInfo.Name; - - webReq.UploadFile(fileStream, fileName, uploadFileMimeType, accept: accept, requestFilter: requestFilter, method: "PUT"); - } - - if (HttpUtils.ResultsFilter != null) - return null; - - return webReq.GetResponse(); - } - - public static WebResponse UploadFile(this WebRequest webRequest, - FileInfo uploadFileInfo, string uploadFileMimeType) - { - using (var fileStream = uploadFileInfo.OpenRead()) - { - var fileName = uploadFileInfo.Name; - - webRequest.UploadFile(fileStream, fileName, uploadFileMimeType); - } - - if (HttpUtils.ResultsFilter != null) - return null; - - return webRequest.GetResponse(); - } - //XmlSerializer public static void CompressToStream(TXmlDto from, Stream stream) { -#if __IOS__ || ANDROID - throw new NotImplementedException("Compression is not supported on this platform"); -#else using (var deflateStream = new System.IO.Compression.DeflateStream(stream, System.IO.Compression.CompressionMode.Compress)) using (var xw = new System.Xml.XmlTextWriter(deflateStream, Encoding.UTF8)) { @@ -1022,7 +546,6 @@ public static void CompressToStream(TXmlDto from, Stream stream) serializer.WriteObject(xw, from); xw.Flush(); } -#endif } public static byte[] Compress(TXmlDto from) @@ -1035,99 +558,6 @@ public static byte[] Compress(TXmlDto from) } } - //License Utils - public static bool VerifySignedHash(byte[] DataToVerify, byte[] SignedData, RSAParameters Key) - { - try - { - var RSAalg = new RSACryptoServiceProvider(); - RSAalg.ImportParameters(Key); - return RSAalg.VerifySha1Data(DataToVerify, SignedData); - - } - catch (CryptographicException ex) - { - Tracer.Instance.WriteError(ex); - return false; - } - } - - public static bool VerifyLicenseKeyText(this string licenseKeyText, out LicenseKey key) - { - var publicRsaProvider = new RSACryptoServiceProvider(); - publicRsaProvider.FromXmlString(LicenseUtils.LicensePublicKey); - var publicKeyParams = publicRsaProvider.ExportParameters(false); - - key = licenseKeyText.ToLicenseKey(); - var originalData = key.GetHashKeyToSign().ToUtf8Bytes(); - var signedData = Convert.FromBase64String(key.Hash); - - return VerifySignedHash(originalData, signedData, publicKeyParams); - } - - public static bool VerifyLicenseKeyTextFallback(this string licenseKeyText, out LicenseKey key) - { - RSAParameters publicKeyParams; - try - { - var publicRsaProvider = new RSACryptoServiceProvider(); - publicRsaProvider.FromXmlString(LicenseUtils.LicensePublicKey); - publicKeyParams = publicRsaProvider.ExportParameters(false); - } - catch (Exception ex) - { - throw new Exception("Could not import LicensePublicKey", ex); - } - - try - { - key = licenseKeyText.ToLicenseKeyFallback(); - } - catch (Exception ex) - { - throw new Exception("Could not deserialize LicenseKeyText Manually", ex); - } - - byte[] originalData; - byte[] signedData; - - try - { - originalData = key.GetHashKeyToSign().ToUtf8Bytes(); - } - catch (Exception ex) - { - throw new Exception("Could not convert HashKey to UTF-8", ex); - } - - try - { - signedData = Convert.FromBase64String(key.Hash); - } - catch (Exception ex) - { - throw new Exception("Could not convert key.Hash from Base64", ex); - } - - try - { - return VerifySignedHash(originalData, signedData, publicKeyParams); - } - catch (Exception ex) - { - throw new Exception($"Could not Verify License Key ({originalData.Length}, {signedData.Length})", ex); - } - } - - public static bool VerifySha1Data(this RSACryptoServiceProvider RSAalg, byte[] unsignedData, byte[] encryptedData) - { - using (var sha = new SHA1CryptoServiceProvider()) - { - return RSAalg.VerifyData(unsignedData, sha, encryptedData); - } - } - -#if !__IOS__ //ReflectionExtensions const string DataContract = "DataContractAttribute"; @@ -1198,12 +628,9 @@ public static DataMemberAttribute GetWeakDataMember(this FieldInfo pi) } return null; } -#endif } } -#if !__IOS__ && !NETSTANDARD2_0 - //Not using it here, but @marcgravell's stuff is too good not to include // http://code.google.com/p/fast-member/ Apache License 2.0 namespace ServiceStack.Text.FastMember @@ -1594,6 +1021,5 @@ private static void Cast(ILGenerator il, Type type, LocalBuilder addr) } } } -#endif #endif diff --git a/src/ServiceStack.Text/PclExport.NetStandard.cs b/src/ServiceStack.Text/PclExport.NetStandard.cs index 00ed12814..e536fec6f 100644 --- a/src/ServiceStack.Text/PclExport.NetStandard.cs +++ b/src/ServiceStack.Text/PclExport.NetStandard.cs @@ -1,7 +1,7 @@ //Copyright (c) ServiceStack, Inc. All Rights Reserved. //License: https://raw.github.com/ServiceStack/ServiceStack/master/license.txt -#if NETSTANDARD2_0 +#if NETCORE using System; using System.Collections.Generic; using System.IO; @@ -11,22 +11,17 @@ using ServiceStack.Text.Json; using System.Globalization; using System.Reflection; -using System.Reflection.Emit; -using System.Runtime.InteropServices; using System.Net; using System.Collections.Specialized; -using System.Linq.Expressions; -using Microsoft.Extensions.Primitives; namespace ServiceStack { public class NetStandardPclExport : PclExport { - public static NetStandardPclExport Provider = new NetStandardPclExport(); + public static NetStandardPclExport Provider = new(); - static string[] allDateTimeFormats = new string[] - { + static string[] allDateTimeFormats = { "yyyy-MM-ddTHH:mm:ss.FFFFFFFzzzzzz", "yyyy-MM-ddTHH:mm:ss.FFFFFFF", "yyyy-MM-ddTHH:mm:ss.FFFFFFFZ", @@ -53,61 +48,20 @@ public class NetStandardPclExport : PclExport "--MM--zzzzzz", }; - static readonly Action SetUserAgentDelegate = - (Action)typeof(HttpWebRequest) - .GetProperty("UserAgent") - ?.GetSetMethod(nonPublic:true)?.CreateDelegate(typeof(Action)); - - static readonly Action SetAllowAutoRedirectDelegate = - (Action)typeof(HttpWebRequest) - .GetProperty("AllowAutoRedirect") - ?.GetSetMethod(nonPublic:true)?.CreateDelegate(typeof(Action)); - - static readonly Action SetKeepAliveDelegate = - (Action)typeof(HttpWebRequest) - .GetProperty("KeepAlive") - ?.GetSetMethod(nonPublic:true)?.CreateDelegate(typeof(Action)); - - static readonly Action SetContentLengthDelegate = - (Action)typeof(HttpWebRequest) - .GetProperty("ContentLength") - ?.GetSetMethod(nonPublic:true)?.CreateDelegate(typeof(Action)); - - private bool allowToChangeRestrictedHeaders; - public NetStandardPclExport() { this.PlatformName = Platforms.NetStandard; -#if NETSTANDARD2_0 this.DirSep = Path.DirectorySeparatorChar; -#else - this.DirSep = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? '\\' : '/'; -#endif - var req = HttpWebRequest.Create("http://servicestack.net"); - try - { - req.Headers[HttpRequestHeader.UserAgent] = "ServiceStack"; - allowToChangeRestrictedHeaders = true; - } catch (ArgumentException) - { - allowToChangeRestrictedHeaders = false; - } } public override string ReadAllText(string filePath) { - //NET Standard 1.1 does not supported Stream Reader with string constructor -#if NETSTANDARD2_0 - using (StreamReader rdr = File.OpenText(filePath)) + using (var reader = File.OpenText(filePath)) { - return rdr.ReadToEnd(); + return reader.ReadToEnd(); } -#else - return String.Empty; -#endif } -#if NETSTANDARD2_0 public override bool FileExists(string filePath) { return File.Exists(filePath); @@ -172,78 +126,42 @@ public override string MapAbsolutePath(string relativePath, string appendPartial return Path.GetFullPath(relativePath.Replace("~", hostDirectoryPath)); } return relativePath; - } - -#elif NETSTANDARD1_1 - public string BinPath = null; + } - public override string MapAbsolutePath(string relativePath, string appendPartialPathModifier) - { - if (BinPath == null) - { - var codeBase = GetAssemblyCodeBase(typeof(PclExport).GetTypeInfo().Assembly); - if (codeBase == null) - throw new Exception("NetStandardPclExport.BinPath must be initialized"); - - BinPath = Path.GetDirectoryName(codeBase.Replace("file:///", "")); - } - - return relativePath.StartsWith("~") - ? relativePath.Replace("~", BinPath) - : relativePath; - } -#endif public static PclExport Configure() { Configure(Provider); return Provider; } - public override string GetEnvironmentVariable(string name) - { -#if NETSTANDARD2_0 - return Environment.GetEnvironmentVariable(name); -#else - return null; -#endif - } + public override string GetEnvironmentVariable(string name) => Environment.GetEnvironmentVariable(name); - public override void WriteLine(string line) - { -#if NETSTANDARD2_0 - Console.WriteLine(line); -#else - System.Diagnostics.Debug.WriteLine(line); -#endif - } + public override void WriteLine(string line) => Console.WriteLine(line); - public override void WriteLine(string format, params object[] args) - { -#if NETSTANDARD2_0 - Console.WriteLine(format, args); -#else - System.Diagnostics.Debug.WriteLine(format, args); -#endif - } + public override void WriteLine(string format, params object[] args) => Console.WriteLine(format, args); -#if NETSTANDARD2_0 public override void AddCompression(WebRequest webReq) { - var httpReq = (HttpWebRequest)webReq; - //TODO: Restore when AutomaticDecompression added to WebRequest - //httpReq.Headers[HttpRequestHeader.AcceptEncoding] = "gzip,deflate"; - //httpReq.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate; + try + { + var httpReq = (HttpWebRequest)webReq; + httpReq.Headers[HttpRequestHeader.AcceptEncoding] = "gzip,deflate"; + httpReq.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate; + } + catch (Exception ex) + { + Tracer.Instance.WriteError(ex); + } } public override void AddHeader(WebRequest webReq, string name, string value) { webReq.Headers[name] = value; } -#endif public override Assembly[] GetAllAssemblies() { - return new Assembly[0]; + return AppDomain.CurrentDomain.GetAssemblies(); } public override string GetAssemblyCodeBase(Assembly assembly) @@ -254,7 +172,6 @@ public override string GetAssemblyCodeBase(Assembly assembly) return codeBase; } -#if NETSTANDARD2_0 public override string GetAssemblyPath(Type source) { var codeBase = GetAssemblyCodeBase(source.GetTypeInfo().Assembly); @@ -274,7 +191,6 @@ public override byte[] GetAsciiBytes(string str) { return System.Text.Encoding.ASCII.GetBytes(str); } -#endif public override bool InSameAssembly(Type t1, Type t2) { @@ -288,86 +204,6 @@ public override Type GetGenericCollectionType(Type type) && t.GetGenericTypeDefinition() == typeof(ICollection<>)); } - public override GetMemberDelegate CreateGetter(PropertyInfo propertyInfo) - { - return -#if NETSTANDARD2_0 - SupportsEmit ? PropertyInvoker.GetEmit(propertyInfo) : -#endif - SupportsExpression - ? PropertyInvoker.GetExpression(propertyInfo) - : base.CreateGetter(propertyInfo); - } - - public override GetMemberDelegate CreateGetter(PropertyInfo propertyInfo) - { - return -#if NETSTANDARD2_0 - SupportsEmit ? PropertyInvoker.GetEmit(propertyInfo) : -#endif - SupportsExpression - ? PropertyInvoker.GetExpression(propertyInfo) - : base.CreateGetter(propertyInfo); - } - - public override SetMemberDelegate CreateSetter(PropertyInfo propertyInfo) - { - return -#if NETSTANDARD2_0 - SupportsEmit ? PropertyInvoker.SetEmit(propertyInfo) : -#endif - SupportsExpression - ? PropertyInvoker.SetExpression(propertyInfo) - : base.CreateSetter(propertyInfo); - } - - public override SetMemberDelegate CreateSetter(PropertyInfo propertyInfo) - { - return SupportsExpression - ? PropertyInvoker.SetExpression(propertyInfo) - : base.CreateSetter(propertyInfo); - } - - public override GetMemberDelegate CreateGetter(FieldInfo fieldInfo) - { - return -#if NETSTANDARD2_0 - SupportsEmit ? FieldInvoker.GetEmit(fieldInfo) : -#endif - SupportsExpression - ? FieldInvoker.GetExpression(fieldInfo) - : base.CreateGetter(fieldInfo); - } - - public override GetMemberDelegate CreateGetter(FieldInfo fieldInfo) - { - return -#if NETSTANDARD2_0 - SupportsEmit ? FieldInvoker.GetEmit(fieldInfo) : -#endif - SupportsExpression - ? FieldInvoker.GetExpression(fieldInfo) - : base.CreateGetter(fieldInfo); - } - - public override SetMemberDelegate CreateSetter(FieldInfo fieldInfo) - { - return -#if NETSTANDARD2_0 - SupportsEmit ? FieldInvoker.SetEmit(fieldInfo) : -#endif - SupportsExpression - ? FieldInvoker.SetExpression(fieldInfo) - : base.CreateSetter(fieldInfo); - } - - public override SetMemberDelegate CreateSetter(FieldInfo fieldInfo) - { - return SupportsExpression - ? FieldInvoker.SetExpression(fieldInfo) - : base.CreateSetter(fieldInfo); - } - public override DateTime ParseXsdDateTimeAsUtc(string dateTimeStr) { return DateTime.ParseExact(dateTimeStr, allDateTimeFormats, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.AllowLeadingWhite|DateTimeStyles.AllowTrailingWhite|DateTimeStyles.AdjustToUniversal) @@ -381,17 +217,16 @@ public override DateTime ParseXsdDateTimeAsUtc(string dateTimeStr) // return TimeZoneInfo.ConvertTimeToUtc(dateTime); //} -#if NETSTANDARD2_0 public override ParseStringDelegate GetSpecializedCollectionParseMethod(Type type) { if (type == typeof(StringCollection)) { - return v => ParseStringCollection(new StringSegment(v)); + return v => ParseStringCollection(v.AsSpan()); } return null; } - public override ParseStringSegmentDelegate GetSpecializedCollectionParseStringSegmentMethod(Type type) + public override ParseStringSpanDelegate GetSpecializedCollectionParseStringSpanMethod(Type type) { if (type == typeof(StringCollection)) { @@ -400,9 +235,10 @@ public override ParseStringSegmentDelegate GetSpecializedCollectionParseStringSe return null; } - private static StringCollection ParseStringCollection(StringSegment value) where TSerializer : ITypeSerializer + private static StringCollection ParseStringCollection(ReadOnlySpan value) where TSerializer : ITypeSerializer { - if (!(value = DeserializeListWithElements.StripList(value)).HasValue) return null; + if ((value = DeserializeListWithElements.StripList(value)).IsNullOrEmpty()) + return value.IsEmpty ? null : new StringCollection(); var result = new StringCollection(); @@ -416,69 +252,60 @@ private static StringCollection ParseStringCollection(StringSegment return result; } -#endif - public override ParseStringDelegate GetJsReaderParseMethod(Type type) + public override void SetUserAgent(HttpWebRequest httpReq, string value) { - if (type.IsAssignableFrom(typeof(System.Dynamic.IDynamicMetaObjectProvider)) || - type.HasInterface(typeof(System.Dynamic.IDynamicMetaObjectProvider))) + try { - return DeserializeDynamic.Parse; + httpReq.UserAgent = value; } - - return null; - } - - public override ParseStringSegmentDelegate GetJsReaderParseStringSegmentMethod(Type type) - { - if (type.IsAssignableFrom(typeof(System.Dynamic.IDynamicMetaObjectProvider)) || - type.HasInterface(typeof(System.Dynamic.IDynamicMetaObjectProvider))) + catch (Exception e) // API may have been removed by Xamarin's Linker { - return DeserializeDynamic.ParseStringSegment; + Tracer.Instance.WriteError(e); } - - return null; } - public override void SetUserAgent(HttpWebRequest httpReq, string value) + public override void SetContentLength(HttpWebRequest httpReq, long value) { - if (SetUserAgentDelegate != null) + try { - SetUserAgentDelegate(httpReq, value); - } else + httpReq.ContentLength = value; + } + catch (Exception e) // API may have been removed by Xamarin's Linker { - if (allowToChangeRestrictedHeaders) - httpReq.Headers[HttpRequestHeader.UserAgent] = value; + Tracer.Instance.WriteError(e); } } - public override void SetContentLength(HttpWebRequest httpReq, long value) + public override void SetAllowAutoRedirect(HttpWebRequest httpReq, bool value) { - if (SetContentLengthDelegate != null) + try { - SetContentLengthDelegate(httpReq, value); - } else + httpReq.AllowAutoRedirect = value; + } + catch (Exception e) // API may have been removed by Xamarin's Linker { - if (allowToChangeRestrictedHeaders) - httpReq.Headers[HttpRequestHeader.ContentLength] = value.ToString(); + Tracer.Instance.WriteError(e); } } - public override void SetAllowAutoRedirect(HttpWebRequest httpReq, bool value) - { - SetAllowAutoRedirectDelegate?.Invoke(httpReq, value); - } - public override void SetKeepAlive(HttpWebRequest httpReq, bool value) { - SetKeepAliveDelegate?.Invoke(httpReq, value); + try + { + httpReq.KeepAlive = value; + } + catch (Exception e) // API may have been removed by Xamarin's Linker + { + Tracer.Instance.WriteError(e); + } } public override void InitHttpWebRequest(HttpWebRequest httpReq, long? contentLength = null, bool allowAutoRedirect = true, bool keepAlive = true) { - SetUserAgent(httpReq, Env.ServerUserAgent); - SetAllowAutoRedirect(httpReq, allowAutoRedirect); - SetKeepAlive(httpReq, keepAlive); + httpReq.UserAgent = Env.ServerUserAgent; + httpReq.AllowAutoRedirect = allowAutoRedirect; + httpReq.KeepAlive = keepAlive; if (contentLength != null) { @@ -493,29 +320,28 @@ public override void Config(HttpWebRequest req, string userAgent = null, bool? preAuthenticate = null) { - //req.MaximumResponseHeadersLength = int.MaxValue; //throws "The message length limit was exceeded" exception - if (allowAutoRedirect.HasValue) SetAllowAutoRedirect(req, allowAutoRedirect.Value); - //if (readWriteTimeout.HasValue) req.ReadWriteTimeout = (int)readWriteTimeout.Value.TotalMilliseconds; - //if (timeout.HasValue) req.Timeout = (int)timeout.Value.TotalMilliseconds; - if (userAgent != null) SetUserAgent(req, userAgent); - //if (preAuthenticate.HasValue) req.PreAuthenticate = preAuthenticate.Value; - } - -#if NETSTANDARD2_0 - public override string GetStackTrace() - { - return Environment.StackTrace; - } -#endif + try + { + //req.MaximumResponseHeadersLength = int.MaxValue; //throws "The message length limit was exceeded" exception + if (allowAutoRedirect.HasValue) + req.AllowAutoRedirect = allowAutoRedirect.Value; - public override Type UseType(Type type) - { - if (type.IsInterface || type.IsAbstract) + if (userAgent != null) + req.UserAgent = userAgent; + + if (readWriteTimeout.HasValue) req.ReadWriteTimeout = (int) readWriteTimeout.Value.TotalMilliseconds; + if (timeout.HasValue) req.Timeout = (int) timeout.Value.TotalMilliseconds; + + if (preAuthenticate.HasValue) + req.PreAuthenticate = preAuthenticate.Value; + } + catch (Exception ex) { - return DynamicProxy.GetInstanceFor(type).GetType(); + Tracer.Instance.WriteError(ex); } - return type; } + + public override string GetStackTrace() => Environment.StackTrace; public static void InitForAot() { @@ -657,7 +483,7 @@ internal static int Register() where TSerializer : ITypeSerializ if (JsonWriter.WriteFn() != null) i++; if (JsonWriter.Instance.GetWriteFn() != null) i++; if (JsonReader.Instance.GetParseFn() != null) i++; - if (JsonReader.Parse(null) != null) i++; + if (JsonReader.Parse(default(ReadOnlySpan)) != null) i++; if (JsonReader.GetParseFn() != null) i++; //if (JsWriter.GetTypeSerializer().GetWriteFn() != null) i++; if (new List() != null) i++; @@ -690,16 +516,16 @@ internal static int Register() where TSerializer : ITypeSerializ internal static void RegisterElement() where TSerializer : ITypeSerializer { - DeserializeDictionary.ParseDictionary(null, null, null, null); - DeserializeDictionary.ParseDictionary(null, null, null, null); + DeserializeDictionary.ParseDictionary(default(ReadOnlySpan), null, null, null); + DeserializeDictionary.ParseDictionary(default(ReadOnlySpan), null, null, null); ToStringDictionaryMethods.WriteIDictionary(null, null, null, null); ToStringDictionaryMethods.WriteIDictionary(null, null, null, null); // Include List deserialisations from the Register<> method above. This solves issue where List properties on responses deserialise to null. // No idea why this is happening because there is no visible exception raised. Suspect IOS is swallowing an AOT exception somewhere. - DeserializeArrayWithElements.ParseGenericArray(null, null); - DeserializeListWithElements.ParseGenericList(null, null, null); + DeserializeArrayWithElements.ParseGenericArray(default(ReadOnlySpan), null); + DeserializeListWithElements.ParseGenericList(default(ReadOnlySpan), null, null); // Cannot use the line below for some unknown reason - when trying to compile to run on device, mtouch bombs during native code compile. // Something about this line or its inner workings is offensive to mtouch. Luckily this was not needed for my List issue. diff --git a/src/ServiceStack.Text/PclExport.WinStore.cs b/src/ServiceStack.Text/PclExport.WinStore.cs deleted file mode 100644 index ba0c7d51e..000000000 --- a/src/ServiceStack.Text/PclExport.WinStore.cs +++ /dev/null @@ -1,328 +0,0 @@ -//Copyright (c) ServiceStack, Inc. All Rights Reserved. -//License: https://raw.github.com/ServiceStack/ServiceStack/master/license.txt - -#if NETFX_CORE -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using ServiceStack.Text; -using ServiceStack.Text.Common; -using ServiceStack.Text.Json; - -namespace ServiceStack -{ - public class WinStorePclExport : PclExport - { - public new static WinStorePclExport Provider = new WinStorePclExport(); - - public WinStorePclExport() - { - this.PlatformName = Platforms.WindowsStore; - } - - public static PclExport Configure() - { - Configure(Provider); - return Provider; - } - - public override string ReadAllText(string filePath) - { - var task = Windows.Storage.StorageFile.GetFileFromPathAsync(filePath); - task.AsTask().Wait(); - - var file = task.GetResults(); - - var streamTask = file.OpenStreamForReadAsync(); - streamTask.Wait(); - - var fileStream = streamTask.Result; - - return new StreamReader(fileStream).ReadToEnd(); - } - - public override bool FileExists(string filePath) - { - try - { - var task = Windows.Storage.StorageFile.GetFileFromPathAsync(filePath); - //no exception means file exists - return true; - } - catch (Exception ex) - { - //find out through exception - return false; - } - } - - public override void WriteLine(string line) - { - System.Diagnostics.Debug.WriteLine(line); - } - - public override void WriteLine(string format, params object[] args) - { - System.Diagnostics.Debug.WriteLine(format, args); - } - - public override Assembly[] GetAllAssemblies() - { - return AppDomain.CurrentDomain.GetAssemblies(); - } - - private sealed class AppDomain - { - public static AppDomain CurrentDomain { get; private set; } - public static Assembly[] cacheObj = null; - - static AppDomain() - { - CurrentDomain = new AppDomain(); - } - - public Assembly[] GetAssemblies() - { - return cacheObj ?? GetAssemblyListAsync().Result.ToArray(); - } - - private async System.Threading.Tasks.Task> GetAssemblyListAsync() - { - var folder = Windows.ApplicationModel.Package.Current.InstalledLocation; - - var assemblies = new List(); - foreach (Windows.Storage.StorageFile file in await folder.GetFilesAsync()) - { - if (file.FileType == ".dll" || file.FileType == ".exe") - { - try - { - var filename = file.Name.Substring(0, file.Name.Length - file.FileType.Length); - AssemblyName name = new AssemblyName() {Name = filename}; - Assembly asm = Assembly.Load(name); - assemblies.Add(asm); - } - catch (Exception) - { - // Invalid WinRT assembly! - } - } - } - - cacheObj = assemblies.ToArray(); - - return cacheObj; - } - } - - public override string GetAssemblyCodeBase(Assembly assembly) - { - return assembly.GetName().FullName; - } - - //public override DateTime ToStableUniversalTime(DateTime dateTime) - //{ - // // .Net 2.0 - 3.5 has an issue with DateTime.ToUniversalTime, but works ok with TimeZoneInfo.ConvertTimeToUtc. - // // .Net 4.0+ does this under the hood anyway. - // return TimeZoneInfo.ConvertTimeToUtc(dateTime); - //} - - public static void InitForAot() - { - } - - internal class Poco - { - public string Dummy { get; set; } - } - - public override void RegisterForAot() - { - RegisterTypeForAot(); - - RegisterElement(); - - RegisterElement(); - RegisterElement(); - RegisterElement(); - RegisterElement(); - RegisterElement(); - RegisterElement(); - RegisterElement(); - RegisterElement(); - - RegisterElement(); - RegisterElement(); - RegisterElement(); - RegisterElement(); - RegisterElement(); - - RegisterElement(); - RegisterElement(); - RegisterElement(); - RegisterElement(); - RegisterElement(); - RegisterElement(); - RegisterElement(); - RegisterElement(); - RegisterElement(); - RegisterElement(); - RegisterElement(); - RegisterElement(); - RegisterElement(); - - //RegisterElement(); - - RegisterTypeForAot(); // used by DateTime - - // register built in structs - RegisterTypeForAot(); - RegisterTypeForAot(); - RegisterTypeForAot(); - RegisterTypeForAot(); - - RegisterTypeForAot(); - RegisterTypeForAot(); - RegisterTypeForAot(); - RegisterTypeForAot(); - } - - public static void RegisterTypeForAot() - { - AotConfig.RegisterSerializers(); - } - - public static void RegisterQueryStringWriter() - { - var i = 0; - if (QueryStringWriter.WriteFn() != null) i++; - } - - public static int RegisterElement() - { - var i = 0; - i += AotConfig.RegisterSerializers(); - AotConfig.RegisterElement(); - AotConfig.RegisterElement(); - return i; - } - - internal class AotConfig - { - internal static JsReader jsonReader; - internal static JsWriter jsonWriter; - internal static JsReader jsvReader; - internal static JsWriter jsvWriter; - internal static JsonTypeSerializer jsonSerializer; - internal static Text.Jsv.JsvTypeSerializer jsvSerializer; - - static AotConfig() - { - jsonSerializer = new JsonTypeSerializer(); - jsvSerializer = new Text.Jsv.JsvTypeSerializer(); - jsonReader = new JsReader(); - jsonWriter = new JsWriter(); - jsvReader = new JsReader(); - jsvWriter = new JsWriter(); - } - - internal static int RegisterSerializers() - { - var i = 0; - i += Register(); - if (jsonSerializer.GetParseFn() != null) i++; - if (jsonSerializer.GetWriteFn() != null) i++; - if (jsonReader.GetParseFn() != null) i++; - if (jsonWriter.GetWriteFn() != null) i++; - - i += Register(); - if (jsvSerializer.GetParseFn() != null) i++; - if (jsvSerializer.GetWriteFn() != null) i++; - if (jsvReader.GetParseFn() != null) i++; - if (jsvWriter.GetWriteFn() != null) i++; - - //RegisterCsvSerializer(); - RegisterQueryStringWriter(); - return i; - } - - internal static void RegisterCsvSerializer() - { - CsvSerializer.WriteFn(); - CsvSerializer.WriteObject(null, null); - CsvWriter.Write(null, default(IEnumerable)); - CsvWriter.WriteRow(null, default(T)); - } - - public static ParseStringDelegate GetParseFn(Type type) - { - var parseFn = JsonTypeSerializer.Instance.GetParseFn(type); - return parseFn; - } - - internal static int Register() where TSerializer : ITypeSerializer - { - var i = 0; - - if (JsonWriter.WriteFn() != null) i++; - if (JsonWriter.Instance.GetWriteFn() != null) i++; - if (JsonReader.Instance.GetParseFn() != null) i++; - if (JsonReader.Parse(null) != null) i++; - if (JsonReader.GetParseFn() != null) i++; - //if (JsWriter.GetTypeSerializer().GetWriteFn() != null) i++; - if (new List() != null) i++; - if (new T[0] != null) i++; - - JsConfig.ExcludeTypeInfo = false; - - if (JsConfig.OnDeserializedFn != null) i++; - if (JsConfig.HasDeserializeFn) i++; - if (JsConfig.SerializeFn != null) i++; - if (JsConfig.DeSerializeFn != null) i++; - //JsConfig.SerializeFn = arg => ""; - //JsConfig.DeSerializeFn = arg => default(T); - if (TypeConfig.Properties != null) i++; - - WriteListsOfElements.WriteList(null, null); - WriteListsOfElements.WriteIList(null, null); - WriteListsOfElements.WriteEnumerable(null, null); - WriteListsOfElements.WriteListValueType(null, null); - WriteListsOfElements.WriteIListValueType(null, null); - WriteListsOfElements.WriteGenericArrayValueType(null, null); - WriteListsOfElements.WriteArray(null, null); - - TranslateListWithElements.LateBoundTranslateToGenericICollection(null, null); - TranslateListWithConvertibleElements.LateBoundTranslateToGenericICollection(null, null); - - QueryStringWriter.WriteObject(null, null); - return i; - } - - internal static void RegisterElement() where TSerializer : ITypeSerializer - { - DeserializeDictionary.ParseDictionary(null, null, null, null); - DeserializeDictionary.ParseDictionary(null, null, null, null); - - ToStringDictionaryMethods.WriteIDictionary(null, null, null, null); - ToStringDictionaryMethods.WriteIDictionary(null, null, null, null); - - // Include List deserialisations from the Register<> method above. This solves issue where List properties on responses deserialise to null. - // No idea why this is happening because there is no visible exception raised. Suspect IOS is swallowing an AOT exception somewhere. - DeserializeArrayWithElements.ParseGenericArray(null, null); - DeserializeListWithElements.ParseGenericList(null, null, null); - - // Cannot use the line below for some unknown reason - when trying to compile to run on device, mtouch bombs during native code compile. - // Something about this line or its inner workings is offensive to mtouch. Luckily this was not needed for my List issue. - // DeserializeCollection.ParseCollection(null, null, null); - - TranslateListWithElements.LateBoundTranslateToGenericICollection(null, typeof(List)); - TranslateListWithConvertibleElements.LateBoundTranslateToGenericICollection(null, typeof(List)); - } - } - - } -} - -#endif diff --git a/src/ServiceStack.Text/PclExport.cs b/src/ServiceStack.Text/PclExport.cs index 5b92312da..9ba3c9cd1 100644 --- a/src/ServiceStack.Text/PclExport.cs +++ b/src/ServiceStack.Text/PclExport.cs @@ -21,21 +21,23 @@ public abstract class PclExport { public static class Platforms { - public const string Uwp = "UWP"; - public const string Android = "Android"; - public const string IOS = "IOS"; - public const string Mac = "MAC"; - public const string NetStandard = "NETStandard"; - } - - public static PclExport Instance -#if NET45 - = new Net45PclExport() -#else - = new NetStandardPclExport() + public const string NetStandard = "NETStd"; + public const string Net6 = "NET6"; + public const string NetFX = "NETFX"; + } + + public static PclExport Instance = +#if NETFX + new NetFxPclExport() +#elif NETSTANDARD2_0 + new NetStandardPclExport() +#elif NETCORE || NET6_0_OR_GREATER + new Net6PclExport() #endif ; + public static ReflectionOptimizer Reflection => ReflectionOptimizer.Instance; + static PclExport() {} public static bool ConfigureProvider(string typeName) @@ -67,10 +69,6 @@ public static void Configure(PclExport instance) public Task EmptyTask; - public bool SupportsExpression; - - public bool SupportsEmit; - public char DirSep = '\\'; public char AltDirSep = '/'; @@ -79,8 +77,6 @@ public static void Configure(PclExport instance) public string PlatformName = "Unknown"; - public TextInfo TextInfo = CultureInfo.InvariantCulture.TextInfo; - public RegexOptions RegexOptions = RegexOptions.None; public StringComparison InvariantComparison = StringComparison.Ordinal; @@ -93,23 +89,6 @@ public static void Configure(PclExport instance) public abstract string ReadAllText(string filePath); - public virtual string ToTitleCase(string value) - { - string[] words = value.Split('_'); - - for (int i = 0; i <= words.Length - 1; i++) - { - if ((!object.ReferenceEquals(words[i], string.Empty))) - { - string firstLetter = words[i].Substring(0, 1); - string rest = words[i].Substring(1); - string result = firstLetter.ToUpper() + rest.ToLower(); - words[i] = result; - } - } - return string.Join("", words); - } - // HACK: The only way to detect anonymous types right now. public virtual bool IsAnonymousType(Type type) { @@ -163,11 +142,6 @@ public virtual void WriteLine(string line, params object[] args) { } - public virtual HttpWebRequest CreateWebRequest(string requestUri, bool? emulateHttpViaPost = null) - { - return (HttpWebRequest)WebRequest.Create(requestUri); - } - public virtual void Config(HttpWebRequest req, bool? allowAutoRedirect = null, TimeSpan? timeout = null, @@ -202,6 +176,11 @@ public virtual WebResponse GetResponse(WebRequest webRequest) } } + public virtual Task GetResponseAsync(WebRequest webRequest) + { + return webRequest.GetResponseAsync(); + } + public virtual bool IsDebugBuild(Assembly assembly) { return assembly.AllAttributes() @@ -281,31 +260,33 @@ public virtual Encoding GetUTF8Encoding(bool emitBom=false) return new UTF8Encoding(emitBom); } - public virtual SetMemberDelegate CreateSetter(FieldInfo fieldInfo) - { - return fieldInfo.SetValue; - } + + [Obsolete("ReflectionOptimizer.CreateGetter")] + public GetMemberDelegate CreateGetter(PropertyInfo propertyInfo) => ReflectionOptimizer.Instance.CreateGetter(propertyInfo); - public virtual SetMemberDelegate CreateSetter(FieldInfo fieldInfo) - { - return (o,x) => fieldInfo.SetValue(o,x); - } + [Obsolete("ReflectionOptimizer.CreateGetter")] + public GetMemberDelegate CreateGetter(PropertyInfo propertyInfo) => ReflectionOptimizer.Instance.CreateGetter(propertyInfo); - public virtual GetMemberDelegate CreateGetter(FieldInfo fieldInfo) - { - return fieldInfo.GetValue; - } + [Obsolete("ReflectionOptimizer.CreateSetter")] + public SetMemberDelegate CreateSetter(PropertyInfo propertyInfo) => ReflectionOptimizer.Instance.CreateSetter(propertyInfo); - public virtual GetMemberDelegate CreateGetter(FieldInfo fieldInfo) - { - return x => fieldInfo.GetValue(x); - } + [Obsolete("ReflectionOptimizer.CreateSetter")] + public SetMemberDelegate CreateSetter(PropertyInfo propertyInfo) => ReflectionOptimizer.Instance.CreateSetter(propertyInfo); + - public virtual Type UseType(Type type) - { - return type; - } + [Obsolete("ReflectionOptimizer.CreateGetter")] + public virtual GetMemberDelegate CreateGetter(FieldInfo fieldInfo) => ReflectionOptimizer.Instance.CreateGetter(fieldInfo); + + [Obsolete("ReflectionOptimizer.CreateGetter")] + public virtual GetMemberDelegate CreateGetter(FieldInfo fieldInfo) => ReflectionOptimizer.Instance.CreateGetter(fieldInfo); + + [Obsolete("ReflectionOptimizer.CreateSetter")] + public virtual SetMemberDelegate CreateSetter(FieldInfo fieldInfo) => ReflectionOptimizer.Instance.CreateSetter(fieldInfo); + [Obsolete("ReflectionOptimizer.CreateSetter")] + public virtual SetMemberDelegate CreateSetter(FieldInfo fieldInfo) => ReflectionOptimizer.Instance.CreateSetter(fieldInfo); + + public virtual bool InSameAssembly(Type t1, Type t2) { return t1.AssemblyQualifiedName != null && t1.AssemblyQualifiedName.Equals(t2.AssemblyQualifiedName); @@ -318,40 +299,6 @@ public virtual Type GetGenericCollectionType(Type type) && t.GetGenericTypeDefinition() == typeof(ICollection<>)); } - public virtual SetMemberDelegate CreateSetter(PropertyInfo propertyInfo) - { - var propertySetMethod = propertyInfo.GetSetMethod(nonPublic:true); - if (propertySetMethod == null) return null; - - return (o, convertedValue) => - propertySetMethod.Invoke(o, new[] { convertedValue }); - } - - public virtual SetMemberDelegate CreateSetter(PropertyInfo propertyInfo) - { - var propertySetMethod = propertyInfo.GetSetMethod(nonPublic:true); - if (propertySetMethod == null) return null; - - return (o, convertedValue) => - propertySetMethod.Invoke(o, new[] { convertedValue }); - } - - public virtual GetMemberDelegate CreateGetter(PropertyInfo propertyInfo) - { - var getMethodInfo = propertyInfo.GetGetMethod(nonPublic:true); - if (getMethodInfo == null) return null; - - return o => propertyInfo.GetGetMethod(nonPublic:true).Invoke(o, TypeConstants.EmptyObjectArray); - } - - public virtual GetMemberDelegate CreateGetter(PropertyInfo propertyInfo) - { - var getMethodInfo = propertyInfo.GetGetMethod(nonPublic:true); - if (getMethodInfo == null) return null; - - return o => propertyInfo.GetGetMethod(nonPublic:true).Invoke(o, TypeConstants.EmptyObjectArray); - } - public virtual string ToXsdDateTimeString(DateTime dateTime) { return System.Xml.XmlConvert.ToString(dateTime.ToStableUniversalTime(), DateTimeSerializer.XsdDateTimeFormat); @@ -380,46 +327,26 @@ public virtual DateTime ToStableUniversalTime(DateTime dateTime) } public virtual ParseStringDelegate GetDictionaryParseMethod(Type type) - where TSerializer : ITypeSerializer - { - return null; - } + where TSerializer : ITypeSerializer => null; - public virtual ParseStringSegmentDelegate GetDictionaryParseStringSegmentMethod(Type type) - where TSerializer : ITypeSerializer - { - return null; - } + public virtual ParseStringSpanDelegate GetDictionaryParseStringSpanMethod(Type type) + where TSerializer : ITypeSerializer => null; public virtual ParseStringDelegate GetSpecializedCollectionParseMethod(Type type) - where TSerializer : ITypeSerializer - { - return null; - } + where TSerializer : ITypeSerializer => null; - public virtual ParseStringSegmentDelegate GetSpecializedCollectionParseStringSegmentMethod(Type type) - where TSerializer : ITypeSerializer - { - return null; - } + public virtual ParseStringSpanDelegate GetSpecializedCollectionParseStringSpanMethod(Type type) + where TSerializer : ITypeSerializer => null; public virtual ParseStringDelegate GetJsReaderParseMethod(Type type) - where TSerializer : ITypeSerializer - { - return null; - } + where TSerializer : ITypeSerializer => null; - public virtual ParseStringSegmentDelegate GetJsReaderParseStringSegmentMethod(Type type) - where TSerializer : ITypeSerializer - { - return null; - } + public virtual ParseStringSpanDelegate GetJsReaderParseStringSpanMethod(Type type) + where TSerializer : ITypeSerializer => null; public virtual void InitHttpWebRequest(HttpWebRequest httpReq, - long? contentLength = null, bool allowAutoRedirect = true, bool keepAlive = true) - { - } + long? contentLength = null, bool allowAutoRedirect = true, bool keepAlive = true) {} public virtual void CloseStream(Stream stream) { @@ -441,37 +368,15 @@ public virtual LicenseKey VerifyLicenseKeyTextFallback(string licenseKeyText) return licenseKeyText.ToLicenseKeyFallback(); } - public virtual void BeginThreadAffinity() - { - } - - public virtual void EndThreadAffinity() - { - } + public virtual void BeginThreadAffinity() {} + public virtual void EndThreadAffinity() {} - public virtual DataContractAttribute GetWeakDataContract(Type type) - { - return null; - } - - public virtual DataMemberAttribute GetWeakDataMember(PropertyInfo pi) - { - return null; - } + public virtual DataContractAttribute GetWeakDataContract(Type type) => null; + public virtual DataMemberAttribute GetWeakDataMember(PropertyInfo pi) => null; + public virtual DataMemberAttribute GetWeakDataMember(FieldInfo pi) => null; - public virtual DataMemberAttribute GetWeakDataMember(FieldInfo pi) - { - return null; - } - - public virtual void RegisterForAot() - { - } - - public virtual string GetStackTrace() - { - return null; - } + public virtual void RegisterForAot() {} + public virtual string GetStackTrace() => null; public virtual Task WriteAndFlushAsync(Stream stream, byte[] bytes) { diff --git a/src/ServiceStack.Text/PlatformExtensions.cs b/src/ServiceStack.Text/PlatformExtensions.cs index 32416ee9e..aabf7f366 100644 --- a/src/ServiceStack.Text/PlatformExtensions.cs +++ b/src/ServiceStack.Text/PlatformExtensions.cs @@ -2,11 +2,13 @@ using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Collections.Specialized; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Serialization; using System.Threading; +using System.Xml; using ServiceStack.Text; namespace ServiceStack @@ -122,15 +124,71 @@ public static MethodInfo GetInstanceMethod(this Type type, string methodName) => [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool HasAttribute(this Type type) => type.AllAttributes().Any(x => x.GetType() == typeof(T)); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool HasAttributeOf(this Type type) => type.AllAttributes().Any(x => x is T); + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool HasAttribute(this PropertyInfo pi) => pi.AllAttributes().Any(x => x.GetType() == typeof(T)); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool HasAttributeOf(this PropertyInfo pi) => pi.AllAttributesLazy().Any(x => x is T); + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool HasAttribute(this FieldInfo fi) => fi.AllAttributes().Any(x => x.GetType() == typeof(T)); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool HasAttributeOf(this FieldInfo fi) => fi.AllAttributes().Any(x => x is T); + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool HasAttribute(this MethodInfo mi) => mi.AllAttributes().Any(x => x.GetType() == typeof(T)); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool HasAttributeOf(this MethodInfo mi) => mi.AllAttributes().Any(x => x is T); + + private static readonly ConcurrentDictionary, bool> hasAttributeCache = new(); + public static bool HasAttributeCached(this MemberInfo memberInfo) + { + var key = new Tuple(memberInfo, typeof(T)); + if (hasAttributeCache.TryGetValue(key , out var hasAttr)) + return hasAttr; + + hasAttr = memberInfo is Type t + ? t.AllAttributes().Any(x => x.GetType() == typeof(T)) + : memberInfo is PropertyInfo pi + ? pi.AllAttributes().Any(x => x.GetType() == typeof(T)) + : memberInfo is FieldInfo fi + ? fi.AllAttributes().Any(x => x.GetType() == typeof(T)) + : memberInfo is MethodInfo mi + ? mi.AllAttributes().Any(x => x.GetType() == typeof(T)) + : throw new NotSupportedException(memberInfo.GetType().Name); + + hasAttributeCache[key] = hasAttr; + + return hasAttr; + } + + private static readonly ConcurrentDictionary, bool> hasAttributeOfCache = new ConcurrentDictionary, bool>(); + public static bool HasAttributeOfCached(this MemberInfo memberInfo) + { + var key = new Tuple(memberInfo, typeof(T)); + if (hasAttributeOfCache.TryGetValue(key , out var hasAttr)) + return hasAttr; + + hasAttr = memberInfo is Type t + ? t.AllAttributes().Any(x => x is T) + : memberInfo is PropertyInfo pi + ? pi.AllAttributes().Any(x => x is T) + : memberInfo is FieldInfo fi + ? fi.AllAttributes().Any(x => x is T) + : memberInfo is MethodInfo mi + ? mi.AllAttributes().Any(x => x is T) + : throw new NotSupportedException(memberInfo.GetType().Name); + + hasAttributeOfCache[key] = hasAttr; + + return hasAttr; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool HasAttributeNamed(this Type type, string name) { @@ -142,7 +200,7 @@ public static bool HasAttributeNamed(this Type type, string name) public static bool HasAttributeNamed(this PropertyInfo pi, string name) { var normalizedAttr = name.Replace("Attribute", "").ToLower(); - return pi.AllAttributes().Any(x => x.GetType().Name.Replace("Attribute", "").ToLower() == normalizedAttr); + return pi.AllAttributesLazy().Any(x => x.GetType().Name.Replace("Attribute", "").ToLower() == normalizedAttr); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -183,11 +241,9 @@ public static PropertyInfo[] AllProperties(this Type type) => type.GetProperties(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); //Should only register Runtime Attributes on StartUp, So using non-ThreadSafe Dictionary is OK - static Dictionary> propertyAttributesMap - = new Dictionary>(); + static Dictionary> propertyAttributesMap = new(); - static Dictionary> typeAttributesMap - = new Dictionary>(); + static Dictionary> typeAttributesMap = new(); public static void ClearRuntimeAttributes() { @@ -277,6 +333,20 @@ public static object[] AllAttributes(this PropertyInfo propertyInfo) return runtimeAttrs.Cast().ToArray(); } + public static IEnumerable AllAttributesLazy(this PropertyInfo propertyInfo) + { + var attrs = propertyInfo.GetCustomAttributes(true); + var runtimeAttrs = propertyInfo.GetAttributes(); + foreach (var attr in runtimeAttrs) + { + yield return attr; + } + foreach (var attr in attrs) + { + yield return attr; + } + } + public static object[] AllAttributes(this PropertyInfo propertyInfo, Type attrType) { var attrs = propertyInfo.GetCustomAttributes(attrType, true); @@ -288,6 +358,18 @@ public static object[] AllAttributes(this PropertyInfo propertyInfo, Type attrTy return runtimeAttrs.Cast().ToArray(); } + public static IEnumerable AllAttributesLazy(this PropertyInfo propertyInfo, Type attrType) + { + foreach (var attr in propertyInfo.GetAttributes(attrType)) + { + yield return attr; + } + foreach (var attr in propertyInfo.GetCustomAttributes(attrType, true)) + { + yield return attr; + } + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static object[] AllAttributes(this ParameterInfo paramInfo) => paramInfo.GetCustomAttributes(true); @@ -315,6 +397,15 @@ public static object[] AllAttributes(this MemberInfo memberInfo, Type attrType) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static object[] AllAttributes(this Type type) => type.GetCustomAttributes(true).Union(type.GetRuntimeAttributes()).ToArray(); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static IEnumerable AllAttributesLazy(this Type type) + { + foreach (var attr in type.GetRuntimeAttributes()) + yield return attr; + foreach (var attr in type.GetCustomAttributes(true)) + yield return attr; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static object[] AllAttributes(this Type type, Type attrType) => type.GetCustomAttributes(attrType, true).Union(type.GetRuntimeAttributes(attrType)).ToArray(); @@ -334,6 +425,9 @@ public static object[] AllAttributes(this Type type, Type attrType) => [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TAttr[] AllAttributes(this PropertyInfo pi) => pi.AllAttributes(typeof(TAttr)).Cast().ToArray(); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static IEnumerable AllAttributesLazy(this PropertyInfo pi) => pi.AllAttributesLazy(typeof(TAttr)).Cast(); + [MethodImpl(MethodImplOptions.AggressiveInlining)] static IEnumerable GetRuntimeAttributes(this Type type) => typeAttributesMap.TryGetValue(type, out var attrs) ? attrs.OfType() @@ -353,6 +447,19 @@ public static TAttr[] AllAttributes(this Type type) .ToArray(); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static IEnumerable AllAttributesLazy(this Type type) + { + foreach (var attr in type.GetCustomAttributes(typeof(TAttr), true).OfType()) + { + yield return attr; + } + foreach (var attr in type.GetRuntimeAttributes()) + { + yield return attr; + } + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TAttr FirstAttribute(this Type type) where TAttr : class { @@ -374,10 +481,8 @@ public static TAttribute FirstAttribute(this ParameterInfo paramInfo } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TAttribute FirstAttribute(this PropertyInfo propertyInfo) - { - return propertyInfo.AllAttributes().FirstOrDefault(); - } + public static TAttribute FirstAttribute(this PropertyInfo propertyInfo) => + propertyInfo.AllAttributesLazy().FirstOrDefault(); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Type FirstGenericTypeDefinition(this Type type) @@ -387,20 +492,7 @@ public static Type FirstGenericTypeDefinition(this Type type) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsDynamic(this Assembly assembly) - { - try - { - var isDyanmic = assembly is System.Reflection.Emit.AssemblyBuilder - || string.IsNullOrEmpty(assembly.Location); - return isDyanmic; - } - catch (NotSupportedException) - { - //Ignore assembly.Location not supported in a dynamic assembly. - return true; - } - } + public static bool IsDynamic(this Assembly assembly) => ReflectionOptimizer.Instance.IsDynamic(assembly); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static MethodInfo GetStaticMethod(this Type type, string methodName, Type[] types) @@ -535,8 +627,9 @@ public static Delegate CreateDelegate(this MethodInfo methodInfo, Type delegateT public static Type GetCollectionType(this Type type) { - return type.GetElementType() - ?? type.GetGenericArguments().LastOrDefault(); //new[] { str }.Select(x => new Type()) => WhereSelectArrayIterator + return type.GetElementType() + ?? type.GetGenericArguments().LastOrDefault() //new[] { str }.Select(x => new Type()) => WhereSelectArrayIterator + ?? (type.BaseType != null && type.BaseType != typeof(object) ? type.BaseType.GetCollectionType() : null); //e.g. ArrayOfString : List } static Dictionary GenericTypeCache = new Dictionary(); @@ -569,8 +662,9 @@ public static Type GetCachedGenericType(this Type type, params Type[] argTypes) do { snapshot = GenericTypeCache; - newCache = new Dictionary(GenericTypeCache); - newCache[key] = genericType; + newCache = new Dictionary(GenericTypeCache) { + [key] = genericType + }; } while (!ReferenceEquals( Interlocked.CompareExchange(ref GenericTypeCache, newCache, snapshot), snapshot)); @@ -610,26 +704,34 @@ public void SetValue(object instance, object value) if (SetValueFn == null) return; - if (!Type.IsInstanceOfType(value)) + if (Type != typeof(object)) { - lock (this) + if (value is IEnumerable> dictionary) { - //Only caches object converter used on first use - if (ConvertType == null) - { - ConvertType = value.GetType(); - ConvertValueFn = TypeConverter.CreateTypeConverter(ConvertType, Type); - } + value = dictionary.FromObjectDictionary(Type); } - if (ConvertType.IsInstanceOfType(value)) + if (!Type.IsInstanceOfType(value)) { - value = ConvertValueFn(value); - } - else - { - var tempConvertFn = TypeConverter.CreateTypeConverter(value.GetType(), Type); - value = tempConvertFn(value); + lock (this) + { + //Only caches object converter used on first use + if (ConvertType == null) + { + ConvertType = value.GetType(); + ConvertValueFn = TypeConverter.CreateTypeConverter(ConvertType, Type); + } + } + + if (ConvertType.IsInstanceOfType(value)) + { + value = ConvertValueFn(value); + } + else + { + var tempConvertFn = TypeConverter.CreateTypeConverter(value.GetType(), Type); + value = tempConvertFn(value); + } } } @@ -637,66 +739,312 @@ public void SetValue(object instance, object value) } } + private static Dictionary ConvertToDictionary( + IEnumerable> collection, + Func mapper = null) + { + if (mapper != null) + { + return mapper.MapToDictionary(collection); + } + + var to = new Dictionary(); + foreach (var entry in collection) + { + string key = entry.Key; + object value = entry.Value; + to[key] = value; + } + return to; + } + + private static Dictionary MapToDictionary( + this Func mapper, + IEnumerable> collection) + { + return collection.ToDictionary( + pair => pair.Key, + pair => mapper(pair.Key, pair.Value)); + } + public static Dictionary ToObjectDictionary(this object obj) + { + return ToObjectDictionary(obj, null); + } + + public static Dictionary ToObjectDictionary( + this object obj, + Func mapper) { if (obj == null) return null; if (obj is Dictionary alreadyDict) + { + if (mapper != null) + return mapper.MapToDictionary(alreadyDict); return alreadyDict; + } if (obj is IDictionary interfaceDict) + { + if (mapper != null) + return mapper.MapToDictionary(interfaceDict); return new Dictionary(interfaceDict); + } + var to = new Dictionary(); if (obj is Dictionary stringDict) { - var to = new Dictionary(); - foreach (var entry in stringDict) + return ConvertToDictionary(stringDict, mapper); + } + + if (obj is IDictionary d) + { + foreach (var key in d.Keys) { - to[entry.Key] = entry.Value; + string k = key.ToString(); + object v = d[key]; + v = mapper?.Invoke(k, v) ?? v; + to[k] = v; } return to; } + if (obj is NameValueCollection nvc) + { + for (var i = 0; i < nvc.Count; i++) + { + string k = nvc.GetKey(i); + object v = nvc.Get(i); + v = mapper?.Invoke(k, v) ?? v; + to[k] = v; + } + return to; + } + + if (obj is IEnumerable> objKvps) + { + return ConvertToDictionary(objKvps, mapper); + } + if (obj is IEnumerable> strKvps) + { + return ConvertToDictionary(strKvps, mapper); + } + var type = obj.GetType(); + if (type.GetKeyValuePairsTypes(out var keyType, out var valueType, out var kvpType) && obj is IEnumerable e) + { + var keyGetter = TypeProperties.Get(kvpType).GetPublicGetter("Key"); + var valueGetter = TypeProperties.Get(kvpType).GetPublicGetter("Value"); + + foreach (var entry in e) + { + var key = keyGetter(entry); + var value = valueGetter(entry); + string k = key.ConvertTo(); + value = mapper?.Invoke(k, value) ?? value; + to[k] = value; + } + return to; + } - ObjectDictionaryDefinition def; - if (!toObjectMapCache.TryGetValue(type, out def)) - toObjectMapCache[type] = def = CreateObjectDictionaryDefinition(type); - var dict = new Dictionary(); + if (obj is KeyValuePair objKvp) + { + string kk = nameof(objKvp.Key); + object kv = objKvp.Key; + kv = mapper?.Invoke(kk, kv) ?? kv; + + string vk = nameof(objKvp.Value); + object vv = objKvp.Value; + vv = mapper?.Invoke(vk, vv) ?? vv; + + return new Dictionary + { + [kk] = kv, + [vk] = vv + }; + } + if (obj is KeyValuePair strKvp) + { + string kk = nameof(objKvp.Key); + object kv = strKvp.Key; + kv = mapper?.Invoke(kk, kv) ?? kv; + + string vk = nameof(strKvp.Value); + object vv = strKvp.Value; + vv = mapper?.Invoke(vk, vv) ?? vv; + + return new Dictionary + { + [kk] = kv, + [vk] = vv + }; + } + if (type.GetKeyValuePairTypes(out _, out var _)) + { + string kk = "Key"; + object kv = TypeProperties.Get(type).GetPublicGetter("Key")(obj).ConvertTo(); + kv = mapper?.Invoke(kk, kv) ?? kv; + + string vk = "Value"; + object vv = TypeProperties.Get(type).GetPublicGetter("Value")(obj); + vv = mapper?.Invoke(vk, vv) ?? vv; + + return new Dictionary + { + [kk] = kv, + [vk] = vv + }; + } + + if (!toObjectMapCache.TryGetValue(type, out var def)) + toObjectMapCache[type] = def = CreateObjectDictionaryDefinition(type); foreach (var fieldDef in def.Fields) { - dict[fieldDef.Name] = fieldDef.GetValueFn(obj); + string k = fieldDef.Name; + object v = fieldDef.GetValueFn(obj); + v = mapper?.Invoke(k, v) ?? v; + to[k] = v; } - return dict; + return to; } - public static object FromObjectDictionary(this Dictionary values, Type type) + public static Type GetKeyValuePairsTypeDef(this Type dictType) { - var alreadyDict = type == typeof(Dictionary); - if (alreadyDict) + //matches IDictionary<,>, IReadOnlyDictionary<,>, List> + var genericDef = dictType.GetTypeWithGenericTypeDefinitionOf(typeof(IEnumerable<>)); + if (genericDef == null) + return null; + + var genericEnumType = genericDef.GetGenericArguments()[0]; + return GetKeyValuePairTypeDef(genericEnumType); + } + + public static Type GetKeyValuePairTypeDef(this Type genericEnumType) => genericEnumType.GetTypeWithGenericTypeDefinitionOf(typeof(KeyValuePair<,>)); + + public static bool GetKeyValuePairsTypes(this Type dictType, out Type keyType, out Type valueType) => + dictType.GetKeyValuePairsTypes(out keyType, out valueType, out _); + + public static bool GetKeyValuePairsTypes(this Type dictType, out Type keyType, out Type valueType, out Type kvpType) + { + //matches IDictionary<,>, IReadOnlyDictionary<,>, List> + var genericDef = dictType.GetTypeWithGenericTypeDefinitionOf(typeof(IEnumerable<>)); + if (genericDef != null) + { + kvpType = genericDef.GetGenericArguments()[0]; + if (GetKeyValuePairTypes(kvpType, out keyType, out valueType)) + return true; + } + kvpType = keyType = valueType = null; + return false; + } + + public static bool GetKeyValuePairTypes(this Type kvpType, out Type keyType, out Type valueType) + { + var genericKvps = kvpType.GetTypeWithGenericTypeDefinitionOf(typeof(KeyValuePair<,>)); + if (genericKvps != null) + { + var genericArgs = kvpType.GetGenericArguments(); + keyType = genericArgs[0]; + valueType = genericArgs[1]; return true; + } + + keyType = valueType = null; + return false; + } + + public static object FromObjectDictionary(this IEnumerable> values, Type type) + { + if (values == null) + return null; + + var alreadyDict = typeof(IEnumerable>).IsAssignableFrom(type); + if (alreadyDict) + return values; + var to = type.CreateInstance(); + if (to is IDictionary d) + { + if (type.GetKeyValuePairsTypes(out var toKeyType, out var toValueType)) + { + foreach (var entry in values) + { + var toKey = entry.Key.ConvertTo(toKeyType); + var toValue = entry.Value.ConvertTo(toValueType); + d[toKey] = toValue; + } + } + else + { + foreach (var entry in values) + { + d[entry.Key] = entry.Value; + } + } + } + else + { + PopulateInstanceInternal(values, to, type); + } + + return to; + } + + public static void PopulateInstance(this IEnumerable> values, object instance) + { + if (values == null || instance == null) + return; + + PopulateInstanceInternal(values, instance, instance.GetType()); + } + + private static void PopulateInstanceInternal(IEnumerable> values, object to, Type type) + { if (!toObjectMapCache.TryGetValue(type, out var def)) toObjectMapCache[type] = def = CreateObjectDictionaryDefinition(type); - var to = type.CreateInstance(); foreach (var entry in values) { if (!def.FieldsMap.TryGetValue(entry.Key, out var fieldDef) && !def.FieldsMap.TryGetValue(entry.Key.ToPascalCase(), out fieldDef) - || entry.Value == null) + || entry.Value == null + || entry.Value == DBNull.Value) continue; fieldDef.SetValue(to, entry.Value); } - return to; } - public static T FromObjectDictionary(this Dictionary values) + public static void PopulateInstance(this IEnumerable> values, object instance) + { + if (values == null || instance == null) + return; + + PopulateInstanceInternal(values, instance, instance.GetType()); + } + + private static void PopulateInstanceInternal(IEnumerable> values, object to, Type type) + { + if (!toObjectMapCache.TryGetValue(type, out var def)) + toObjectMapCache[type] = def = CreateObjectDictionaryDefinition(type); + + foreach (var entry in values) + { + if (!def.FieldsMap.TryGetValue(entry.Key, out var fieldDef) && + !def.FieldsMap.TryGetValue(entry.Key.ToPascalCase(), out fieldDef) + || entry.Value == null) + continue; + + fieldDef.SetValue(to, entry.Value); + } + } + + public static T FromObjectDictionary(this IEnumerable> values) { return (T)values.FromObjectDictionary(typeof(T)); } @@ -745,28 +1093,68 @@ public static Dictionary ToSafePartialObjectDictionary(this T { var valueType = entry.Value?.GetType(); - if (valueType == null || !valueType.IsClass || valueType == typeof(string)) + try { - to[entry.Key] = entry.Value; - } - else if (!TypeSerializer.HasCircularReferences(entry.Value)) - { - if (entry.Value is IEnumerable enumerable) + if (valueType == null || !valueType.IsClass || valueType == typeof(string)) { to[entry.Key] = entry.Value; } + else if (!TypeSerializer.HasCircularReferences(entry.Value)) + { + if (entry.Value is IEnumerable enumerable) + { + to[entry.Key] = entry.Value; + } + else + { + to[entry.Key] = entry.Value.ToSafePartialObjectDictionary(); + } + } else { - to[entry.Key] = entry.Value.ToSafePartialObjectDictionary(); + to[entry.Key] = entry.Value.ToString(); } + } - else + catch (Exception ignore) { - to[entry.Key] = entry.Value.ToString(); + Tracer.Instance.WriteDebug($"Could not retrieve value from '{valueType?.GetType().Name}': ${ignore.Message}"); } } } return to; } + + public static Dictionary MergeIntoObjectDictionary(this object obj, params object[] sources) + { + var to = obj.ToObjectDictionary(); + foreach (var source in sources) + foreach (var entry in source.ToObjectDictionary()) + { + to[entry.Key] = entry.Value; + } + return to; + } + + public static Dictionary ToStringDictionary(this IEnumerable> from) => ToStringDictionary(from, null); + + public static Dictionary ToStringDictionary(this IEnumerable> from, IEqualityComparer comparer) + { + var to = comparer != null + ? new Dictionary(comparer) + : new Dictionary(); + + if (from != null) + { + foreach (var entry in from) + { + to[entry.Key] = entry.Value is string s + ? s + : entry.Value.ConvertTo(); + } + } + + return to; + } } } \ No newline at end of file diff --git a/src/ServiceStack.Text/Pools/BufferPool.cs b/src/ServiceStack.Text/Pools/BufferPool.cs new file mode 100644 index 000000000..08a4e5f92 --- /dev/null +++ b/src/ServiceStack.Text/Pools/BufferPool.cs @@ -0,0 +1,172 @@ +using System; + +namespace ServiceStack.Text.Pools +{ + /// + /// Courtesy of @marcgravell + /// https://github.com/mgravell/protobuf-net/blob/master/src/protobuf-net/BufferPool.cs + /// + public sealed class BufferPool + { + public static void Flush() + { + lock (Pool) + { + for (var i = 0; i < Pool.Length; i++) + Pool[i] = null; + } + } + + private BufferPool() { } + private const int POOL_SIZE = 20; + public const int BUFFER_LENGTH = 1450; //<= MTU - DJB + private static readonly CachedBuffer[] Pool = new CachedBuffer[POOL_SIZE]; + + public static byte[] GetBuffer() + { + return GetBuffer(BUFFER_LENGTH); + } + + public static byte[] GetBuffer(int minSize) + { + byte[] cachedBuff = GetCachedBuffer(minSize); + return cachedBuff ?? new byte[minSize]; + } + + public static byte[] GetCachedBuffer(int minSize) + { + lock (Pool) + { + var bestIndex = -1; + byte[] bestMatch = null; + for (var i = 0; i < Pool.Length; i++) + { + var buffer = Pool[i]; + if (buffer == null || buffer.Size < minSize) + { + continue; + } + if (bestMatch != null && bestMatch.Length < buffer.Size) + { + continue; + } + + var tmp = buffer.Buffer; + if (tmp == null) + { + Pool[i] = null; + } + else + { + bestMatch = tmp; + bestIndex = i; + } + } + + if (bestIndex >= 0) + { + Pool[bestIndex] = null; + } + + return bestMatch; + } + } + + /// + /// https://docs.microsoft.com/en-us/dotnet/framework/configure-apps/file-schema/runtime/gcallowverylargeobjects-element + /// + private const int MaxByteArraySize = int.MaxValue - 56; + + public static void ResizeAndFlushLeft(ref byte[] buffer, int toFitAtLeastBytes, int copyFromIndex, int copyBytes) + { + Helpers.DebugAssert(buffer != null); + Helpers.DebugAssert(toFitAtLeastBytes > buffer.Length); + Helpers.DebugAssert(copyFromIndex >= 0); + Helpers.DebugAssert(copyBytes >= 0); + + int newLength = buffer.Length * 2; + if (newLength < 0) + { + newLength = MaxByteArraySize; + } + + if (newLength < toFitAtLeastBytes) newLength = toFitAtLeastBytes; + + if (copyBytes == 0) + { + ReleaseBufferToPool(ref buffer); + } + + var newBuffer = GetCachedBuffer(toFitAtLeastBytes) ?? new byte[newLength]; + + if (copyBytes > 0) + { + Buffer.BlockCopy(buffer, copyFromIndex, newBuffer, 0, copyBytes); + ReleaseBufferToPool(ref buffer); + } + + buffer = newBuffer; + } + + public static void ReleaseBufferToPool(ref byte[] buffer) + { + if (buffer == null) return; + + lock (Pool) + { + var minIndex = 0; + var minSize = int.MaxValue; + for (var i = 0; i < Pool.Length; i++) + { + var tmp = Pool[i]; + if (tmp == null || !tmp.IsAlive) + { + minIndex = 0; + break; + } + if (tmp.Size < minSize) + { + minIndex = i; + minSize = tmp.Size; + } + } + + Pool[minIndex] = new CachedBuffer(buffer); + } + + buffer = null; + } + + private class CachedBuffer + { + private readonly WeakReference _reference; + + public int Size { get; } + + public bool IsAlive => _reference.IsAlive; + public byte[] Buffer => (byte[])_reference.Target; + + public CachedBuffer(byte[] buffer) + { + Size = buffer.Length; + _reference = new WeakReference(buffer); + } + } + } + + internal sealed class Helpers + { + private Helpers() { } + + [System.Diagnostics.Conditional("DEBUG")] + internal static void DebugAssert(bool condition) + { +#if DEBUG + if (!condition && System.Diagnostics.Debugger.IsAttached) + System.Diagnostics.Debugger.Break(); + System.Diagnostics.Debug.Assert(condition); +#endif + } + + } +} \ No newline at end of file diff --git a/src/ServiceStack.Text/Pools/CharPool .cs b/src/ServiceStack.Text/Pools/CharPool .cs new file mode 100644 index 000000000..2bfeb1adc --- /dev/null +++ b/src/ServiceStack.Text/Pools/CharPool .cs @@ -0,0 +1,152 @@ +using System; + +namespace ServiceStack.Text.Pools +{ + public sealed class CharPool + { + public static void Flush() + { + lock (Pool) + { + for (var i = 0; i < Pool.Length; i++) + Pool[i] = null; + } + } + + private CharPool() { } + private const int POOL_SIZE = 20; + public const int BUFFER_LENGTH = 1450; //<= MTU - DJB + private static readonly CachedBuffer[] Pool = new CachedBuffer[POOL_SIZE]; + + public static char[] GetBuffer() + { + return GetBuffer(BUFFER_LENGTH); + } + + public static char[] GetBuffer(int minSize) + { + char[] cachedBuff = GetCachedBuffer(minSize); + return cachedBuff ?? new char[minSize]; + } + + public static char[] GetCachedBuffer(int minSize) + { + lock (Pool) + { + var bestIndex = -1; + char[] bestMatch = null; + for (var i = 0; i < Pool.Length; i++) + { + var buffer = Pool[i]; + if (buffer == null || buffer.Size < minSize) + { + continue; + } + if (bestMatch != null && bestMatch.Length < buffer.Size) + { + continue; + } + + var tmp = buffer.Buffer; + if (tmp == null) + { + Pool[i] = null; + } + else + { + bestMatch = tmp; + bestIndex = i; + } + } + + if (bestIndex >= 0) + { + Pool[bestIndex] = null; + } + + return bestMatch; + } + } + + /// + /// https://docs.microsoft.com/en-us/dotnet/framework/configure-apps/file-schema/runtime/gcallowverylargeobjects-element + /// + private const int MaxcharArraySize = int.MaxValue - 56; + + public static void ResizeAndFlushLeft(ref char[] buffer, int toFitAtLeastchars, int copyFromIndex, int copychars) + { + Helpers.DebugAssert(buffer != null); + Helpers.DebugAssert(toFitAtLeastchars > buffer.Length); + Helpers.DebugAssert(copyFromIndex >= 0); + Helpers.DebugAssert(copychars >= 0); + + int newLength = buffer.Length * 2; + if (newLength < 0) + { + newLength = MaxcharArraySize; + } + + if (newLength < toFitAtLeastchars) newLength = toFitAtLeastchars; + + if (copychars == 0) + { + ReleaseBufferToPool(ref buffer); + } + + var newBuffer = GetCachedBuffer(toFitAtLeastchars) ?? new char[newLength]; + + if (copychars > 0) + { + Buffer.BlockCopy(buffer, copyFromIndex, newBuffer, 0, copychars); + ReleaseBufferToPool(ref buffer); + } + + buffer = newBuffer; + } + + public static void ReleaseBufferToPool(ref char[] buffer) + { + if (buffer == null) return; + + lock (Pool) + { + var minIndex = 0; + var minSize = int.MaxValue; + for (var i = 0; i < Pool.Length; i++) + { + var tmp = Pool[i]; + if (tmp == null || !tmp.IsAlive) + { + minIndex = 0; + break; + } + if (tmp.Size < minSize) + { + minIndex = i; + minSize = tmp.Size; + } + } + + Pool[minIndex] = new CachedBuffer(buffer); + } + + buffer = null; + } + + private class CachedBuffer + { + private readonly WeakReference _reference; + + public int Size { get; } + + public bool IsAlive => _reference.IsAlive; + public char[] Buffer => (char[])_reference.Target; + + public CachedBuffer(char[] buffer) + { + Size = buffer.Length; + _reference = new WeakReference(buffer); + } + } + } +} \ No newline at end of file diff --git a/src/ServiceStack.Text/Properties/AssemblyInfo.cs b/src/ServiceStack.Text/Properties/AssemblyInfo.cs index 85d1ea7ef..b64975d01 100644 --- a/src/ServiceStack.Text/Properties/AssemblyInfo.cs +++ b/src/ServiceStack.Text/Properties/AssemblyInfo.cs @@ -1,37 +1,5 @@ -using System.Reflection; using System.Runtime.InteropServices; -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("ServiceStack.Text")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("ServiceStack")] -[assembly: AssemblyProduct("ServiceStack.Text")] -[assembly: AssemblyCopyright("Copyright (c) ServiceStack 2017")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. [assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -#if !PCL [assembly: Guid("a352d4d3-df2a-4c78-b646-67181a6333a6")] -#endif - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("5.0.0.0")] -[assembly: AssemblyFileVersion("5.0.0.0")] +[assembly: System.Reflection.AssemblyVersion("6.0.0.0")] diff --git a/src/ServiceStack.Text/QueryStringSerializer.cs b/src/ServiceStack.Text/QueryStringSerializer.cs index d183d7443..58c4c593a 100644 --- a/src/ServiceStack.Text/QueryStringSerializer.cs +++ b/src/ServiceStack.Text/QueryStringSerializer.cs @@ -17,6 +17,7 @@ using System.Globalization; using System.IO; using System.Linq; +using System.Runtime.CompilerServices; using System.Text; using System.Threading; using ServiceStack.Text; @@ -44,8 +45,7 @@ internal static WriteObjectDelegate GetWriteFn(Type type) { try { - WriteObjectDelegate writeFn; - if (WriteFnCache.TryGetValue(type, out writeFn)) return writeFn; + if (WriteFnCache.TryGetValue(type, out var writeFn)) return writeFn; var genericType = typeof(QueryStringWriter<>).MakeGenericType(type); var mi = genericType.GetStaticMethod("WriteFn"); @@ -91,6 +91,12 @@ public static string SerializeToString(T value) GetWriteFn(value.GetType())(writer, value); return StringWriterThreadStatic.ReturnAndFree(writer); } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] + public static void InitAot() + { + QueryStringWriter.WriteFn(); + } } /// @@ -159,7 +165,8 @@ public static void WriteIDictionary(TextWriter writer, object oMap) foreach (var key in map.Keys) { var dictionaryValue = map[key]; - if (dictionaryValue == null) continue; + if (dictionaryValue == null) + continue; if (writeKeyFn == null) { @@ -168,7 +175,11 @@ public static void WriteIDictionary(TextWriter writer, object oMap) } if (writeValueFn == null || isObjectDictionary) - writeValueFn = Serializer.GetWriteFn(dictionaryValue.GetType()); + { + writeValueFn = dictionaryValue is string + ? (w,x) => w.Write(((string)x).UrlEncode()) + : Serializer.GetWriteFn(dictionaryValue.GetType()); + } if (ranOnce) writer.Write("&"); diff --git a/src/ServiceStack.Text/RecyclableMemoryStream.cs b/src/ServiceStack.Text/RecyclableMemoryStream.cs index 20233df15..13ee0e6c9 100644 --- a/src/ServiceStack.Text/RecyclableMemoryStream.cs +++ b/src/ServiceStack.Text/RecyclableMemoryStream.cs @@ -24,18 +24,19 @@ using System.IO; using System.Collections.Generic; using System.Collections.Concurrent; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Tracing; using System.Linq; +using System.Runtime.CompilerServices; using System.Threading; namespace ServiceStack.Text //Internalize to avoid conflicts { - using Events = RecyclableMemoryStreamManager.Events; - public static class MemoryStreamFactory { public static bool UseRecyclableMemoryStream { get; set; } - public static RecyclableMemoryStreamManager RecyclableInstance = new RecyclableMemoryStreamManager(); + public static RecyclableMemoryStreamManager RecyclableInstance = new(); public static MemoryStream GetStream() { @@ -47,22 +48,291 @@ public static MemoryStream GetStream() public static MemoryStream GetStream(int capacity) { return UseRecyclableMemoryStream - ? RecyclableInstance.GetStream(typeof(MemoryStreamFactory).Name, capacity) + ? RecyclableInstance.GetStream(nameof(MemoryStreamFactory), capacity) : new MemoryStream(capacity); } public static MemoryStream GetStream(byte[] bytes) { return UseRecyclableMemoryStream - ? RecyclableInstance.GetStream(typeof(MemoryStreamFactory).Name, bytes, 0, bytes.Length) - : new MemoryStream(bytes); + ? RecyclableInstance.GetStream(nameof(MemoryStreamFactory), bytes, 0, bytes.Length) + : new MemoryStream(bytes, 0, bytes.Length, writable:true, publiclyVisible:true); } public static MemoryStream GetStream(byte[] bytes, int index, int count) { return UseRecyclableMemoryStream - ? RecyclableInstance.GetStream(typeof(MemoryStreamFactory).Name, bytes, index, count) - : new MemoryStream(bytes, index, count); + ? RecyclableInstance.GetStream(nameof(MemoryStreamFactory), bytes, index, count) + : new MemoryStream(bytes, index, count, writable:true, publiclyVisible:true); + } + } + +#if !NETCORE + public enum EventLevel + { + LogAlways = 0, + Critical, + Error, + Warning, + Informational, + Verbose, + } + + public enum EventKeywords : long + { + None = 0x0, + } + + [AttributeUsage(AttributeTargets.Class)] + public sealed class EventSourceAttribute : Attribute + { + public string Name { get; set; } + public string Guid { get; set; } + } + + [AttributeUsage(AttributeTargets.Method)] + public sealed class EventAttribute : Attribute + { + public EventAttribute(int id) { } + + public EventLevel Level { get; set; } + } + + public class EventSource + { + public void WriteEvent(params object[] unused) + { + return; + } + + public bool IsEnabled() + { + return false; + } + + public bool IsEnabled(EventLevel level, EventKeywords keywords) + { + return false; + } + } +#endif + + public sealed partial class RecyclableMemoryStreamManager + { + /// + /// ETW events for RecyclableMemoryStream + /// + [EventSource(Name = "Microsoft-IO-RecyclableMemoryStream", Guid = "{B80CD4E4-890E-468D-9CBA-90EB7C82DFC7}")] + public sealed class Events : EventSource + { + /// + /// Static log object, through which all events are written. + /// + public static Events Writer = new Events(); + + /// + /// Type of buffer + /// + public enum MemoryStreamBufferType + { + /// + /// Small block buffer + /// + Small, + /// + /// Large pool buffer + /// + Large + } + + /// + /// The possible reasons for discarding a buffer + /// + public enum MemoryStreamDiscardReason + { + /// + /// Buffer was too large to be re-pooled + /// + TooLarge, + /// + /// There are enough free bytes in the pool + /// + EnoughFree + } + + /// + /// Logged when a stream object is created. + /// + /// A unique ID for this stream. + /// A temporary ID for this stream, usually indicates current usage. + /// Requested size of the stream + [Event(1, Level = EventLevel.Verbose)] + public void MemoryStreamCreated(Guid guid, string tag, int requestedSize) + { + if (this.IsEnabled(EventLevel.Verbose, EventKeywords.None)) + { + WriteEvent(1, guid, tag ?? string.Empty, requestedSize); + } + } + + /// + /// Logged when the stream is disposed + /// + /// A unique ID for this stream. + /// A temporary ID for this stream, usually indicates current usage. + [Event(2, Level = EventLevel.Verbose)] + public void MemoryStreamDisposed(Guid guid, string tag) + { + if (this.IsEnabled(EventLevel.Verbose, EventKeywords.None)) + { + WriteEvent(2, guid, tag ?? string.Empty); + } + } + + /// + /// Logged when the stream is disposed for the second time. + /// + /// A unique ID for this stream. + /// A temporary ID for this stream, usually indicates current usage. + /// Call stack of initial allocation. + /// Call stack of the first dispose. + /// Call stack of the second dispose. + /// Note: Stacks will only be populated if RecyclableMemoryStreamManager.GenerateCallStacks is true. + [Event(3, Level = EventLevel.Critical)] + public void MemoryStreamDoubleDispose(Guid guid, string tag, string allocationStack, string disposeStack1, + string disposeStack2) + { + if (this.IsEnabled()) + { + this.WriteEvent(3, guid, tag ?? string.Empty, allocationStack ?? string.Empty, + disposeStack1 ?? string.Empty, disposeStack2 ?? string.Empty); + } + } + + /// + /// Logged when a stream is finalized. + /// + /// A unique ID for this stream. + /// A temporary ID for this stream, usually indicates current usage. + /// Call stack of initial allocation. + /// Note: Stacks will only be populated if RecyclableMemoryStreamManager.GenerateCallStacks is true. + [Event(4, Level = EventLevel.Error)] + public void MemoryStreamFinalized(Guid guid, string tag, string allocationStack) + { + if (this.IsEnabled()) + { + WriteEvent(4, guid, tag ?? string.Empty, allocationStack ?? string.Empty); + } + } + + /// + /// Logged when ToArray is called on a stream. + /// + /// A unique ID for this stream. + /// A temporary ID for this stream, usually indicates current usage. + /// Call stack of the ToArray call. + /// Length of stream + /// Note: Stacks will only be populated if RecyclableMemoryStreamManager.GenerateCallStacks is true. + [Event(5, Level = EventLevel.Verbose)] + public void MemoryStreamToArray(Guid guid, string tag, string stack, int size) + { + if (this.IsEnabled(EventLevel.Verbose, EventKeywords.None)) + { + WriteEvent(5, guid, tag ?? string.Empty, stack ?? string.Empty, size); + } + } + + /// + /// Logged when the RecyclableMemoryStreamManager is initialized. + /// + /// Size of blocks, in bytes. + /// Size of the large buffer multiple, in bytes. + /// Maximum buffer size, in bytes. + [Event(6, Level = EventLevel.Informational)] + public void MemoryStreamManagerInitialized(int blockSize, int largeBufferMultiple, int maximumBufferSize) + { + if (this.IsEnabled()) + { + WriteEvent(6, blockSize, largeBufferMultiple, maximumBufferSize); + } + } + + /// + /// Logged when a new block is created. + /// + /// Number of bytes in the small pool currently in use. + [Event(7, Level = EventLevel.Verbose)] + public void MemoryStreamNewBlockCreated(long smallPoolInUseBytes) + { + if (this.IsEnabled(EventLevel.Verbose, EventKeywords.None)) + { + WriteEvent(7, smallPoolInUseBytes); + } + } + + /// + /// Logged when a new large buffer is created. + /// + /// Requested size + /// Number of bytes in the large pool in use. + [Event(8, Level = EventLevel.Verbose)] + public void MemoryStreamNewLargeBufferCreated(int requiredSize, long largePoolInUseBytes) + { + if (this.IsEnabled(EventLevel.Verbose, EventKeywords.None)) + { + WriteEvent(8, requiredSize, largePoolInUseBytes); + } + } + + /// + /// Logged when a buffer is created that is too large to pool. + /// + /// Size requested by the caller + /// A temporary ID for this stream, usually indicates current usage. + /// Call stack of the requested stream. + /// Note: Stacks will only be populated if RecyclableMemoryStreamManager.GenerateCallStacks is true. + [Event(9, Level = EventLevel.Verbose)] + public void MemoryStreamNonPooledLargeBufferCreated(int requiredSize, string tag, string allocationStack) + { + if (this.IsEnabled(EventLevel.Verbose, EventKeywords.None)) + { + WriteEvent(9, requiredSize, tag ?? string.Empty, allocationStack ?? string.Empty); + } + } + + /// + /// Logged when a buffer is discarded (not put back in the pool, but given to GC to clean up). + /// + /// Type of the buffer being discarded. + /// A temporary ID for this stream, usually indicates current usage. + /// Reason for the discard. + [Event(10, Level = EventLevel.Warning)] + public void MemoryStreamDiscardBuffer(MemoryStreamBufferType bufferType, string tag, + MemoryStreamDiscardReason reason) + { + if (this.IsEnabled()) + { + WriteEvent(10, bufferType, tag ?? string.Empty, reason); + } + } + + /// + /// Logged when a stream grows beyond the maximum capacity. + /// + /// The requested capacity. + /// Maximum capacity, as configured by RecyclableMemoryStreamManager. + /// A temporary ID for this stream, usually indicates current usage. + /// Call stack for the capacity request. + /// Note: Stacks will only be populated if RecyclableMemoryStreamManager.GenerateCallStacks is true. + [Event(11, Level = EventLevel.Error)] + public void MemoryStreamOverCapacity(int requestedCapacity, long maxCapacity, string tag, + string allocationStack) + { + if (this.IsEnabled()) + { + WriteEvent(11, requestedCapacity, maxCapacity, tag ?? string.Empty, allocationStack ?? string.Empty); + } + } } } @@ -72,9 +342,9 @@ public static MemoryStream GetStream(byte[] bytes, int index, int count) /// /// There are two pools managed in here. The small pool contains same-sized buffers that are handed to streams /// as they write more data. - /// + /// /// For scenarios that need to call GetBuffer(), the large pool contains buffers of various sizes, all - /// multiples of LargeBufferMultiple (1 MB by default). They are split by size to avoid overly-wasteful buffer + /// multiples/exponentials of LargeBufferMultiple (1 MB by default). They are split by size to avoid overly-wasteful buffer /// usage. There should be far fewer 8 MB buffers than 1 MB buffers, for example. /// public partial class RecyclableMemoryStreamManager @@ -106,24 +376,25 @@ public partial class RecyclableMemoryStreamManager public delegate void UsageReportEventHandler( long smallPoolInUseBytes, long smallPoolFreeBytes, long largePoolInUseBytes, long largePoolFreeBytes); + /// + /// Default block size, in bytes + /// public const int DefaultBlockSize = 128 * 1024; + /// + /// Default large buffer multiple, in bytes + /// public const int DefaultLargeBufferMultiple = 1024 * 1024; + /// + /// Default maximum buffer size, in bytes + /// public const int DefaultMaximumBufferSize = 128 * 1024 * 1024; - private readonly int blockSize; private readonly long[] largeBufferFreeSize; private readonly long[] largeBufferInUseSize; - private readonly int largeBufferMultiple; - /// - /// pools[0] = 1x largeBufferMultiple buffers - /// pools[1] = 2x largeBufferMultiple buffers - /// etc., up to maximumBufferSize - /// private readonly ConcurrentStack[] largePools; - private readonly int maximumBufferSize; private readonly ConcurrentStack smallPool; private long smallPoolFreeSize; @@ -133,8 +404,7 @@ public delegate void UsageReportEventHandler( /// Initializes the memory manager with the default block/buffer specifications. /// public RecyclableMemoryStreamManager() - : this(DefaultBlockSize, DefaultLargeBufferMultiple, DefaultMaximumBufferSize) - { } + : this(DefaultBlockSize, DefaultLargeBufferMultiple, DefaultMaximumBufferSize, false) { } /// /// Initializes the memory manager with the given block requiredSize. @@ -145,36 +415,52 @@ public RecyclableMemoryStreamManager() /// blockSize is not a positive number, or largeBufferMultiple is not a positive number, or maximumBufferSize is less than blockSize. /// maximumBufferSize is not a multiple of largeBufferMultiple public RecyclableMemoryStreamManager(int blockSize, int largeBufferMultiple, int maximumBufferSize) + : this(blockSize, largeBufferMultiple, maximumBufferSize, false) { } + + /// + /// Initializes the memory manager with the given block requiredSize. + /// + /// Size of each block that is pooled. Must be > 0. + /// Each large buffer will be a multiple/exponential of this value. + /// Buffers larger than this are not pooled + /// Switch to exponential large buffer allocation strategy + /// blockSize is not a positive number, or largeBufferMultiple is not a positive number, or maximumBufferSize is less than blockSize. + /// maximumBufferSize is not a multiple/exponential of largeBufferMultiple + public RecyclableMemoryStreamManager(int blockSize, int largeBufferMultiple, int maximumBufferSize, bool useExponentialLargeBuffer) { if (blockSize <= 0) { - throw new ArgumentOutOfRangeException("blockSize", blockSize, "blockSize must be a positive number"); + throw new ArgumentOutOfRangeException(nameof(blockSize), blockSize, "blockSize must be a positive number"); } if (largeBufferMultiple <= 0) { - throw new ArgumentOutOfRangeException("largeBufferMultiple", + throw new ArgumentOutOfRangeException(nameof(largeBufferMultiple), "largeBufferMultiple must be a positive number"); } if (maximumBufferSize < blockSize) { - throw new ArgumentOutOfRangeException("maximumBufferSize", + throw new ArgumentOutOfRangeException(nameof(maximumBufferSize), "maximumBufferSize must be at least blockSize"); } - this.blockSize = blockSize; - this.largeBufferMultiple = largeBufferMultiple; - this.maximumBufferSize = maximumBufferSize; + this.BlockSize = blockSize; + this.LargeBufferMultiple = largeBufferMultiple; + this.MaximumBufferSize = maximumBufferSize; + this.UseExponentialLargeBuffer = useExponentialLargeBuffer; - if (!this.IsLargeBufferMultiple(maximumBufferSize)) + if (!this.IsLargeBufferSize(maximumBufferSize)) { - throw new ArgumentException("maximumBufferSize is not a multiple of largeBufferMultiple", - "maximumBufferSize"); + throw new ArgumentException(String.Format("maximumBufferSize is not {0} of largeBufferMultiple", + this.UseExponentialLargeBuffer ? "an exponential" : "a multiple"), + nameof(maximumBufferSize)); } this.smallPool = new ConcurrentStack(); - var numLargePools = maximumBufferSize / largeBufferMultiple; + var numLargePools = useExponentialLargeBuffer + ? ((int)Math.Log(maximumBufferSize / largeBufferMultiple, 2) + 1) + : (maximumBufferSize / largeBufferMultiple); // +1 to store size of bytes in use that are too large to be pooled this.largeBufferInUseSize = new long[numLargePools + 1]; @@ -187,57 +473,61 @@ public RecyclableMemoryStreamManager(int blockSize, int largeBufferMultiple, int this.largePools[i] = new ConcurrentStack(); } - Events.Write.MemoryStreamManagerInitialized(blockSize, largeBufferMultiple, maximumBufferSize); + Events.Writer.MemoryStreamManagerInitialized(blockSize, largeBufferMultiple, maximumBufferSize); } /// /// The size of each block. It must be set at creation and cannot be changed. /// - public int BlockSize - { - get { return this.blockSize; } - } + public int BlockSize { get; } /// - /// All buffers are multiples of this number. It must be set at creation and cannot be changed. + /// All buffers are multiples/exponentials of this number. It must be set at creation and cannot be changed. /// - public int LargeBufferMultiple - { - get { return this.largeBufferMultiple; } - } + public int LargeBufferMultiple { get; } + + /// + /// Use multiple large buffer allocation strategy. It must be set at creation and cannot be changed. + /// + public bool UseMultipleLargeBuffer => !this.UseExponentialLargeBuffer; /// - /// Gets or sets the maximum buffer size. + /// Use exponential large buffer allocation strategy. It must be set at creation and cannot be changed. + /// + public bool UseExponentialLargeBuffer { get; } + + /// + /// Gets the maximum buffer size. /// /// Any buffer that is returned to the pool that is larger than this will be /// discarded and garbage collected. - public int MaximumBufferSize - { - get { return this.maximumBufferSize; } - } + public int MaximumBufferSize { get; } /// /// Number of bytes in small pool not currently in use /// - public long SmallPoolFreeSize - { - get { return this.smallPoolFreeSize; } - } + public long SmallPoolFreeSize => this.smallPoolFreeSize; /// /// Number of bytes currently in use by stream from the small pool /// - public long SmallPoolInUseSize - { - get { return this.smallPoolInUseSize; } - } + public long SmallPoolInUseSize => this.smallPoolInUseSize; /// /// Number of bytes in large pool not currently in use /// public long LargePoolFreeSize { - get { return this.largeBufferFreeSize.Sum(); } + get + { + long sum = 0; + foreach (long freeSize in this.largeBufferFreeSize) + { + sum += freeSize; + } + + return sum; + } } /// @@ -245,16 +535,22 @@ public long LargePoolFreeSize /// public long LargePoolInUseSize { - get { return this.largeBufferInUseSize.Sum(); } + get + { + long sum = 0; + foreach (long inUseSize in this.largeBufferInUseSize) + { + sum += inUseSize; + } + + return sum; + } } /// /// How many blocks are in the small pool /// - public long SmallBlocksFree - { - get { return this.smallPool.Count; } - } + public long SmallBlocksFree => this.smallPool.Count; /// /// How many buffers are in the large pool @@ -307,6 +603,13 @@ public long LargeBuffersFree /// public bool AggressiveBufferReturn { get; set; } + /// + /// Causes an exception to be thrown if ToArray is ever called. + /// + /// Calling ToArray defeats the purpose of a pooled buffer. Use this property to discover code that is calling ToArray. If this is + /// set and stream.ToArray() is called, a NotSupportedException will be thrown. + public bool ThrowExceptionOnToArray { get; set; } + /// /// Removes and returns a single block from the pool. /// @@ -319,12 +622,8 @@ internal byte[] GetBlock() // We'll add this back to the pool when the stream is disposed // (unless our free pool is too large) block = new byte[this.BlockSize]; - Events.Write.MemoryStreamNewBlockCreated(this.smallPoolInUseSize); - - if (this.BlockCreated != null) - { - this.BlockCreated(); - } + Events.Writer.MemoryStreamNewBlockCreated(this.smallPoolInUseSize); + ReportBlockCreated(); } else { @@ -337,16 +636,16 @@ internal byte[] GetBlock() /// /// Returns a buffer of arbitrary size from the large buffer pool. This buffer - /// will be at least the requiredSize and always be a multiple of largeBufferMultiple. + /// will be at least the requiredSize and always be a multiple/exponential of largeBufferMultiple. /// /// The minimum length of the buffer /// The tag of the stream returning this buffer, for logging if necessary. /// A buffer of at least the required size. internal byte[] GetLargeBuffer(int requiredSize, string tag) { - requiredSize = this.RoundToLargeBufferMultiple(requiredSize); + requiredSize = this.RoundToLargeBufferSize(requiredSize); - var poolIndex = requiredSize / this.largeBufferMultiple - 1; + var poolIndex = this.GetPoolIndex(requiredSize); byte[] buffer; if (poolIndex < this.largePools.Length) @@ -355,11 +654,8 @@ internal byte[] GetLargeBuffer(int requiredSize, string tag) { buffer = new byte[requiredSize]; - Events.Write.MemoryStreamNewLargeBufferCreated(requiredSize, this.LargePoolInUseSize); - if (this.LargeBufferCreated != null) - { - this.LargeBufferCreated(); - } + Events.Writer.MemoryStreamNewLargeBufferCreated(requiredSize, this.LargePoolInUseSize); + ReportLargeBufferCreated(); } else { @@ -380,14 +676,10 @@ internal byte[] GetLargeBuffer(int requiredSize, string tag) if (this.GenerateCallStacks) { // Grab the stack -- we want to know who requires such large buffers - callStack = PclExport.Instance.GetStackTrace(); - } - Events.Write.MemoryStreamNonPooledLargeBufferCreated(requiredSize, tag, callStack); - - if (this.LargeBufferCreated != null) - { - this.LargeBufferCreated(); + callStack = Environment.StackTrace; } + Events.Writer.MemoryStreamNonPooledLargeBufferCreated(requiredSize, tag, callStack); + ReportLargeBufferCreated(); } Interlocked.Add(ref this.largeBufferInUseSize[poolIndex], buffer.Length); @@ -395,14 +687,45 @@ internal byte[] GetLargeBuffer(int requiredSize, string tag) return buffer; } - private int RoundToLargeBufferMultiple(int requiredSize) + private int RoundToLargeBufferSize(int requiredSize) { - return ((requiredSize + this.LargeBufferMultiple - 1) / this.LargeBufferMultiple) * this.LargeBufferMultiple; + if (this.UseExponentialLargeBuffer) + { + int pow = 1; + while (this.LargeBufferMultiple * pow < requiredSize) + { + pow <<= 1; + } + return this.LargeBufferMultiple * pow; + } + else + { + return ((requiredSize + this.LargeBufferMultiple - 1) / this.LargeBufferMultiple) * this.LargeBufferMultiple; + } } - private bool IsLargeBufferMultiple(int value) + private bool IsLargeBufferSize(int value) { - return (value != 0) && (value % this.LargeBufferMultiple) == 0; + return (value != 0) && (this.UseExponentialLargeBuffer + ? (value == RoundToLargeBufferSize(value)) + : (value % this.LargeBufferMultiple) == 0); + } + + private int GetPoolIndex(int length) + { + if (this.UseExponentialLargeBuffer) + { + int index = 0; + while ((this.LargeBufferMultiple << index) < length) + { + ++index; + } + return index; + } + else + { + return length / this.LargeBufferMultiple - 1; + } } /// @@ -411,22 +734,23 @@ private bool IsLargeBufferMultiple(int value) /// The buffer to return. /// The tag of the stream returning this buffer, for logging if necessary. /// buffer is null - /// buffer.Length is not a multiple of LargeBufferMultiple (it did not originate from this pool) + /// buffer.Length is not a multiple/exponential of LargeBufferMultiple (it did not originate from this pool) internal void ReturnLargeBuffer(byte[] buffer, string tag) { if (buffer == null) { - throw new ArgumentNullException("buffer"); + throw new ArgumentNullException(nameof(buffer)); } - if (!this.IsLargeBufferMultiple(buffer.Length)) + if (!this.IsLargeBufferSize(buffer.Length)) { throw new ArgumentException( - "buffer did not originate from this memory manager. The size is not a multiple of " + + String.Format("buffer did not originate from this memory manager. The size is not {0} of ", + this.UseExponentialLargeBuffer ? "an exponential" : "a multiple") + this.LargeBufferMultiple); } - var poolIndex = buffer.Length / this.largeBufferMultiple - 1; + var poolIndex = this.GetPoolIndex(buffer.Length); if (poolIndex < this.largePools.Length) { @@ -438,13 +762,9 @@ internal void ReturnLargeBuffer(byte[] buffer, string tag) } else { - Events.Write.MemoryStreamDiscardBuffer(Events.MemoryStreamBufferType.Large, tag, + Events.Writer.MemoryStreamDiscardBuffer(Events.MemoryStreamBufferType.Large, tag, Events.MemoryStreamDiscardReason.EnoughFree); - - if (this.LargeBufferDiscarded != null) - { - this.LargeBufferDiscarded(Events.MemoryStreamDiscardReason.EnoughFree); - } + ReportLargeBufferDiscarded(Events.MemoryStreamDiscardReason.EnoughFree); } } else @@ -453,21 +773,15 @@ internal void ReturnLargeBuffer(byte[] buffer, string tag) // analysis. We have space in the inuse array for this. poolIndex = this.largeBufferInUseSize.Length - 1; - Events.Write.MemoryStreamDiscardBuffer(Events.MemoryStreamBufferType.Large, tag, + Events.Writer.MemoryStreamDiscardBuffer(Events.MemoryStreamBufferType.Large, tag, Events.MemoryStreamDiscardReason.TooLarge); - if (this.LargeBufferDiscarded != null) - { - this.LargeBufferDiscarded(Events.MemoryStreamDiscardReason.TooLarge); - } + ReportLargeBufferDiscarded(Events.MemoryStreamDiscardReason.TooLarge); } Interlocked.Add(ref this.largeBufferInUseSize[poolIndex], -buffer.Length); - if (this.UsageReport != null) - { - this.UsageReport(this.smallPoolInUseSize, this.smallPoolFreeSize, this.LargePoolInUseSize, - this.LargePoolFreeSize); - } + ReportUsageReport(this.smallPoolInUseSize, this.smallPoolFreeSize, this.LargePoolInUseSize, + this.LargePoolFreeSize); } /// @@ -481,7 +795,7 @@ internal void ReturnBlocks(ICollection blocks, string tag) { if (blocks == null) { - throw new ArgumentNullException("blocks"); + throw new ArgumentNullException(nameof(blocks)); } var bytesToReturn = blocks.Count * this.BlockSize; @@ -504,61 +818,66 @@ internal void ReturnBlocks(ICollection blocks, string tag) } else { - Events.Write.MemoryStreamDiscardBuffer(Events.MemoryStreamBufferType.Small, tag, + Events.Writer.MemoryStreamDiscardBuffer(Events.MemoryStreamBufferType.Small, tag, Events.MemoryStreamDiscardReason.EnoughFree); - if (this.BlockDiscarded != null) - { - this.BlockDiscarded(); - } + ReportBlockDiscarded(); break; } } - if (this.UsageReport != null) - { - this.UsageReport(this.smallPoolInUseSize, this.smallPoolFreeSize, this.LargePoolInUseSize, - this.LargePoolFreeSize); - } + ReportUsageReport(this.smallPoolInUseSize, this.smallPoolFreeSize, this.LargePoolInUseSize, + this.LargePoolFreeSize); + } + + internal void ReportBlockCreated() + { + this.BlockCreated?.Invoke(); + } + + internal void ReportBlockDiscarded() + { + this.BlockDiscarded?.Invoke(); + } + + internal void ReportLargeBufferCreated() + { + this.LargeBufferCreated?.Invoke(); + } + + internal void ReportLargeBufferDiscarded(Events.MemoryStreamDiscardReason reason) + { + this.LargeBufferDiscarded?.Invoke(reason); } internal void ReportStreamCreated() { - if (this.StreamCreated != null) - { - this.StreamCreated(); - } + this.StreamCreated?.Invoke(); } internal void ReportStreamDisposed() { - if (this.StreamDisposed != null) - { - this.StreamDisposed(); - } + this.StreamDisposed?.Invoke(); } internal void ReportStreamFinalized() { - if (this.StreamFinalized != null) - { - this.StreamFinalized(); - } + this.StreamFinalized?.Invoke(); } internal void ReportStreamLength(long bytes) { - if (this.StreamLength != null) - { - this.StreamLength(bytes); - } + this.StreamLength?.Invoke(bytes); } internal void ReportStreamToArray() { - if (this.StreamConvertedToArray != null) - { - this.StreamConvertedToArray(); - } + this.StreamConvertedToArray?.Invoke(); + } + + internal void ReportUsageReport( + long smallPoolInUseBytes, long smallPoolFreeBytes, long largePoolInUseBytes, long largePoolFreeBytes) + { + this.UsageReport?.Invoke(smallPoolInUseBytes, smallPoolFreeBytes, largePoolInUseBytes, largePoolFreeBytes); } /// @@ -570,6 +889,16 @@ public MemoryStream GetStream() return new RecyclableMemoryStream(this); } + /// + /// Retrieve a new MemoryStream object with no tag and a default initial capacity. + /// + /// A unique identifier which can be used to trace usages of the stream. + /// A MemoryStream. + public MemoryStream GetStream(Guid id) + { + return new RecyclableMemoryStream(this, id); + } + /// /// Retrieve a new MemoryStream object with the given tag and a default initial capacity. /// @@ -580,6 +909,17 @@ public MemoryStream GetStream(string tag) return new RecyclableMemoryStream(this, tag); } + /// + /// Retrieve a new MemoryStream object with the given tag and a default initial capacity. + /// + /// A unique identifier which can be used to trace usages of the stream. + /// A tag which can be used to track the source of the stream. + /// A MemoryStream. + public MemoryStream GetStream(Guid id, string tag) + { + return new RecyclableMemoryStream(this, id, tag); + } + /// /// Retrieve a new MemoryStream object with the given tag and at least the given capacity. /// @@ -591,28 +931,99 @@ public MemoryStream GetStream(string tag, int requiredSize) return new RecyclableMemoryStream(this, tag, requiredSize); } + /// + /// Retrieve a new MemoryStream object with the given tag and at least the given capacity. + /// + /// A unique identifier which can be used to trace usages of the stream. + /// A tag which can be used to track the source of the stream. + /// The minimum desired capacity for the stream. + /// A MemoryStream. + public MemoryStream GetStream(Guid id, string tag, int requiredSize) + { + return new RecyclableMemoryStream(this, id, tag, requiredSize); + } + /// /// Retrieve a new MemoryStream object with the given tag and at least the given capacity, possibly using - /// a single continugous underlying buffer. + /// a single contiguous underlying buffer. /// /// Retrieving a MemoryStream which provides a single contiguous buffer can be useful in situations /// where the initial size is known and it is desirable to avoid copying data between the smaller underlying /// buffers to a single large one. This is most helpful when you know that you will always call GetBuffer /// on the underlying stream. + /// A unique identifier which can be used to trace usages of the stream. /// A tag which can be used to track the source of the stream. /// The minimum desired capacity for the stream. /// Whether to attempt to use a single contiguous buffer. /// A MemoryStream. - public MemoryStream GetStream(string tag, int requiredSize, bool asContiguousBuffer) + public MemoryStream GetStream(Guid id, string tag, int requiredSize, bool asContiguousBuffer) { if (!asContiguousBuffer || requiredSize <= this.BlockSize) { - return this.GetStream(tag, requiredSize); + return this.GetStream(id, tag, requiredSize); } - return new RecyclableMemoryStream(this, tag, requiredSize, this.GetLargeBuffer(requiredSize, tag)); + return new RecyclableMemoryStream(this, id, tag, requiredSize, this.GetLargeBuffer(requiredSize, tag)); + } + + /// + /// Retrieve a new MemoryStream object with the given tag and at least the given capacity, possibly using + /// a single contiguous underlying buffer. + /// + /// Retrieving a MemoryStream which provides a single contiguous buffer can be useful in situations + /// where the initial size is known and it is desirable to avoid copying data between the smaller underlying + /// buffers to a single large one. This is most helpful when you know that you will always call GetBuffer + /// on the underlying stream. + /// A tag which can be used to track the source of the stream. + /// The minimum desired capacity for the stream. + /// Whether to attempt to use a single contiguous buffer. + /// A MemoryStream. + public MemoryStream GetStream(string tag, int requiredSize, bool asContiguousBuffer) + { + return GetStream(Guid.NewGuid(), tag, requiredSize, asContiguousBuffer); } + /// + /// Retrieve a new MemoryStream object with the given tag and with contents copied from the provided + /// buffer. The provided buffer is not wrapped or used after construction. + /// + /// The new stream's position is set to the beginning of the stream when returned. + /// A unique identifier which can be used to trace usages of the stream. + /// A tag which can be used to track the source of the stream. + /// The byte buffer to copy data from. + /// The offset from the start of the buffer to copy from. + /// The number of bytes to copy from the buffer. + /// A MemoryStream. + public MemoryStream GetStream(Guid id, string tag, byte[] buffer, int offset, int count) + { + RecyclableMemoryStream stream = null; + try + { + stream = new RecyclableMemoryStream(this, id, tag, count); + stream.Write(buffer, offset, count); + stream.Position = 0; + return stream; + } + catch + { + stream?.Dispose(); + throw; + } + } + + /// + /// Retrieve a new MemoryStream object with the contents copied from the provided + /// buffer. The provided buffer is not wrapped or used after construction. + /// + /// The new stream's position is set to the beginning of the stream when returned. + /// The byte buffer to copy data from. + /// A MemoryStream. + public MemoryStream GetStream(byte[] buffer) + { + return GetStream(null, buffer, 0, buffer.Length); + } + + /// /// Retrieve a new MemoryStream object with the given tag and with contents copied from the provided /// buffer. The provided buffer is not wrapped or used after construction. @@ -623,15 +1034,63 @@ public MemoryStream GetStream(string tag, int requiredSize, bool asContiguousBuf /// The offset from the start of the buffer to copy from. /// The number of bytes to copy from the buffer. /// A MemoryStream. - //[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")] public MemoryStream GetStream(string tag, byte[] buffer, int offset, int count) { - var stream = new RecyclableMemoryStream(this, tag, count); - stream.Write(buffer, offset, count); - stream.Position = 0; - return stream; + return GetStream(Guid.NewGuid(), tag, buffer, offset, count); + } + +#if NETCORE && !NETSTANDARD2_0 + /// + /// Retrieve a new MemoryStream object with the given tag and with contents copied from the provided + /// buffer. The provided buffer is not wrapped or used after construction. + /// + /// The new stream's position is set to the beginning of the stream when returned. + /// A unique identifier which can be used to trace usages of the stream. + /// A tag which can be used to track the source of the stream. + /// The byte buffer to copy data from. + /// A MemoryStream. + public MemoryStream GetStream(Guid id, string tag, Memory buffer) + { + RecyclableMemoryStream stream = null; + try + { + stream = new RecyclableMemoryStream(this, id, tag, buffer.Length); + stream.Write(buffer.Span); + stream.Position = 0; + return stream; + } + catch + { + stream?.Dispose(); + throw; + } + } + + /// + /// Retrieve a new MemoryStream object with the contents copied from the provided + /// buffer. The provided buffer is not wrapped or used after construction. + /// + /// The new stream's position is set to the beginning of the stream when returned. + /// The byte buffer to copy data from. + /// A MemoryStream. + public MemoryStream GetStream(Memory buffer) + { + return GetStream(null, buffer); } + /// + /// Retrieve a new MemoryStream object with the given tag and with contents copied from the provided + /// buffer. The provided buffer is not wrapped or used after construction. + /// + /// The new stream's position is set to the beginning of the stream when returned. + /// A tag which can be used to track the source of the stream. + /// The byte buffer to copy data from. + /// A MemoryStream. + public MemoryStream GetStream(string tag, Memory buffer) + { + return GetStream(Guid.NewGuid(), tag, buffer); + } +#endif /// /// Triggered when a new block is created. /// @@ -689,7 +1148,7 @@ public MemoryStream GetStream(string tag, byte[] buffer, int offset, int count) /// buffers. /// /// - /// This class works in tandem with the RecylableMemoryStreamManager to supply MemoryStream + /// This class works in tandem with the RecyclableMemoryStreamManager to supply MemoryStream /// objects to callers, while avoiding these specific problems: /// 1. LOH allocations - since all large buffers are pooled, they will never incur a Gen2 GC /// 2. Memory waste - A standard memory stream doubles its size when it runs out of room. This @@ -706,9 +1165,9 @@ public MemoryStream GetStream(string tag, byte[] buffer, int offset, int count) /// The biggest wrinkle in this implementation is when GetBuffer() is called. This requires a single /// contiguous buffer. If only a single block is in use, then that block is returned. If multiple blocks /// are in use, we retrieve a larger buffer from the memory manager. These large buffers are also pooled, - /// split by size--they are multiples of a chunk size (1 MB by default). + /// split by size--they are multiples/exponentials of a chunk size (1 MB by default). /// - /// Once a large buffer is assigned to the stream the blocks are NEVER again used for this stream. All operations take place on the + /// Once a large buffer is assigned to the stream the small blocks are NEVER again used for this stream. All operations take place on the /// large buffer. The large buffer can be replaced by a larger buffer from the pool as needed. All blocks and large buffers /// are maintained in the stream until the stream is disposed (unless AggressiveBufferReturn is enabled in the stream manager). /// @@ -717,11 +1176,29 @@ public sealed class RecyclableMemoryStream : MemoryStream { private const long MaxStreamLength = Int32.MaxValue; + private static readonly byte[] emptyArray = new byte[0]; + /// /// All of these blocks must be the same size /// private readonly List blocks = new List(1); + private readonly Guid id; + + private readonly RecyclableMemoryStreamManager memoryManager; + + private readonly string tag; + + /// + /// This list is used to store buffers once they're replaced by something larger. + /// This is for the cases where you have users of this class that may hold onto the buffers longer + /// than they should and you want to prevent race conditions which could corrupt the data. + /// + private List dirtyBuffers; + + // long to allow Interlocked.Read (for .NET Standard 1.4 compat) + private long disposedState; + /// /// This is only set by GetBuffer() if the necessary buffer is larger than a single block size, or on /// construction if the caller immediately requests a single large buffer. @@ -732,27 +1209,30 @@ public sealed class RecyclableMemoryStream : MemoryStream private byte[] largeBuffer; /// - /// This list is used to store buffers once they're replaced by something larger. - /// This is for the cases where you have users of this class that may hold onto the buffers longer - /// than they should and you want to prevent race conditions which could corrupt the data. - /// - private List dirtyBuffers; - - private readonly Guid id; - /// - /// Unique identifier for this stream across it's entire lifetime + /// Unique identifier for this stream across its entire lifetime /// /// Object has been disposed - internal Guid Id { get { this.CheckDisposed(); return this.id; } } + internal Guid Id + { + get + { + this.CheckDisposed(); + return this.id; + } + } - private readonly string tag; /// /// A temporary identifier for the current usage of this stream. /// /// Object has been disposed - internal string Tag { get { this.CheckDisposed(); return this.tag; } } - - private readonly RecyclableMemoryStreamManager memoryManager; + internal string Tag + { + get + { + this.CheckDisposed(); + return this.tag; + } + } /// /// Gets the memory manager being used by this stream. @@ -767,72 +1247,83 @@ internal RecyclableMemoryStreamManager MemoryManager } } - private bool disposed; - - private readonly string allocationStack; - private string disposeStack; - /// /// Callstack of the constructor. It is only set if MemoryManager.GenerateCallStacks is true, /// which should only be in debugging situations. /// - internal string AllocationStack { get { return this.allocationStack; } } + internal string AllocationStack { get; } /// /// Callstack of the Dispose call. It is only set if MemoryManager.GenerateCallStacks is true, /// which should only be in debugging situations. /// - internal string DisposeStack { get { return this.disposeStack; } } + internal string DisposeStack { get; private set; } + #region Constructors /// - /// This buffer exists so that WriteByte can forward all of its calls to Write - /// without creating a new byte[] buffer on every call. + /// Allocate a new RecyclableMemoryStream object. /// - private readonly byte[] byteBuffer = new byte[1]; + /// The memory manager + public RecyclableMemoryStream(RecyclableMemoryStreamManager memoryManager) + : this(memoryManager, Guid.NewGuid(), null, 0, null) { } - #region Constructors /// /// Allocate a new RecyclableMemoryStream object. /// /// The memory manager - public RecyclableMemoryStream(RecyclableMemoryStreamManager memoryManager) - : this(memoryManager, null, 0, null) - { - } + /// A unique identifier which can be used to trace usages of the stream. + public RecyclableMemoryStream(RecyclableMemoryStreamManager memoryManager, Guid id) + : this(memoryManager, id, null, 0, null) { } + + /// + /// Allocate a new RecyclableMemoryStream object + /// + /// The memory manager + /// A string identifying this stream for logging and debugging purposes + public RecyclableMemoryStream(RecyclableMemoryStreamManager memoryManager, string tag) + : this(memoryManager, Guid.NewGuid(), tag, 0, null) { } + + /// + /// Allocate a new RecyclableMemoryStream object + /// + /// The memory manager + /// A unique identifier which can be used to trace usages of the stream. + /// A string identifying this stream for logging and debugging purposes + public RecyclableMemoryStream(RecyclableMemoryStreamManager memoryManager, Guid id, string tag) + : this(memoryManager, id, tag, 0, null) { } /// /// Allocate a new RecyclableMemoryStream object /// /// The memory manager /// A string identifying this stream for logging and debugging purposes - public RecyclableMemoryStream(RecyclableMemoryStreamManager memoryManager, string tag) - : this(memoryManager, tag, 0, null) - { - } + /// The initial requested size to prevent future allocations + public RecyclableMemoryStream(RecyclableMemoryStreamManager memoryManager, string tag, int requestedSize) + : this(memoryManager, Guid.NewGuid(), tag, requestedSize, null) { } /// /// Allocate a new RecyclableMemoryStream object /// /// The memory manager + /// A unique identifier which can be used to trace usages of the stream. /// A string identifying this stream for logging and debugging purposes /// The initial requested size to prevent future allocations - public RecyclableMemoryStream(RecyclableMemoryStreamManager memoryManager, string tag, int requestedSize) - : this(memoryManager, tag, requestedSize, null) - { - } + public RecyclableMemoryStream(RecyclableMemoryStreamManager memoryManager, Guid id, string tag, int requestedSize) + : this(memoryManager, id, tag, requestedSize, null) { } /// /// Allocate a new RecyclableMemoryStream object /// /// The memory manager + /// A unique identifier which can be used to trace usages of the stream. /// A string identifying this stream for logging and debugging purposes /// The initial requested size to prevent future allocations /// An initial buffer to use. This buffer will be owned by the stream and returned to the memory manager upon Dispose. - internal RecyclableMemoryStream(RecyclableMemoryStreamManager memoryManager, string tag, int requestedSize, - byte[] initialLargeBuffer) + internal RecyclableMemoryStream(RecyclableMemoryStreamManager memoryManager, Guid id, string tag, int requestedSize, byte[] initialLargeBuffer) + : base(emptyArray) { this.memoryManager = memoryManager; - this.id = Guid.NewGuid(); + this.id = id; this.tag = tag; if (requestedSize < memoryManager.BlockSize) @@ -849,19 +1340,21 @@ internal RecyclableMemoryStream(RecyclableMemoryStreamManager memoryManager, str this.largeBuffer = initialLargeBuffer; } - this.disposed = false; - if (this.memoryManager.GenerateCallStacks) { - this.allocationStack = PclExport.Instance.GetStackTrace(); + this.AllocationStack = Environment.StackTrace; } - Events.Write.MemoryStreamCreated(this.id, this.tag, requestedSize); + RecyclableMemoryStreamManager.Events.Writer.MemoryStreamCreated(this.id, this.tag, requestedSize); this.memoryManager.ReportStreamCreated(); } #endregion #region Dispose and Finalize + /// + /// The finalizer will be called when a stream is not disposed properly. + /// + /// Failing to dispose indicates a bug in the code using streams. Care should be taken to properly account for stream lifetime. ~RecyclableMemoryStream() { this.Dispose(false); @@ -871,34 +1364,32 @@ internal RecyclableMemoryStream(RecyclableMemoryStreamManager memoryManager, str /// Returns the memory used by this stream back to the pool. /// /// Whether we're disposing (true), or being called by the finalizer (false) - /// This method is not thread safe and it may not be called more than once. - //[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1816:CallGCSuppressFinalizeCorrectly", Justification = "We have different disposal semantics, so SuppressFinalize is in a different spot.")] + [SuppressMessage("Microsoft.Usage", "CA1816:CallGCSuppressFinalizeCorrectly", + Justification = "We have different disposal semantics, so SuppressFinalize is in a different spot.")] protected override void Dispose(bool disposing) { - if (this.disposed) + if (Interlocked.CompareExchange(ref this.disposedState, 1, 0) != 0) { string doubleDisposeStack = null; if (this.memoryManager.GenerateCallStacks) { - doubleDisposeStack = PclExport.Instance.GetStackTrace(); + doubleDisposeStack = Environment.StackTrace; } - Events.Write.MemoryStreamDoubleDispose(this.id, this.tag, this.allocationStack, this.disposeStack, doubleDisposeStack); + RecyclableMemoryStreamManager.Events.Writer.MemoryStreamDoubleDispose(this.id, this.tag, + this.AllocationStack, this.DisposeStack, doubleDisposeStack); return; } - Events.Write.MemoryStreamDisposed(this.id, this.tag); + RecyclableMemoryStreamManager.Events.Writer.MemoryStreamDisposed(this.id, this.tag); if (this.memoryManager.GenerateCallStacks) { - this.disposeStack = PclExport.Instance.GetStackTrace(); + this.DisposeStack = Environment.StackTrace; } if (disposing) { - // Once this flag is set, we can't access any properties -- use fields directly - this.disposed = true; - this.memoryManager.ReportStreamDisposed(); GC.SuppressFinalize(this); @@ -907,8 +1398,9 @@ protected override void Dispose(bool disposing) { // We're being finalized. - Events.Write.MemoryStreamFinalized(this.id, this.tag, this.allocationStack); + RecyclableMemoryStreamManager.Events.Writer.MemoryStreamFinalized(this.id, this.tag, this.AllocationStack); +#if !NETSTANDARD1_4 if (AppDomain.CurrentDomain.IsFinalizingForUnload()) { // If we're being finalized because of a shutdown, don't go any further. @@ -917,6 +1409,7 @@ protected override void Dispose(bool disposing) base.Dispose(disposing); return; } +#endif this.memoryManager.ReportStreamFinalized(); } @@ -937,6 +1430,7 @@ protected override void Dispose(bool disposing) } this.memoryManager.ReturnBlocks(this.blocks, this.tag); + this.blocks.Clear(); base.Dispose(disposing); } @@ -944,7 +1438,11 @@ protected override void Dispose(bool disposing) /// /// Equivalent to Dispose /// +#if NETSTANDARD1_4 + public void Close() +#else public override void Close() +#endif { this.Dispose(true); } @@ -959,6 +1457,8 @@ public override void Close() /// Explicitly setting the capacity to a lower value than the current value will have no effect. /// This is because the buffers are all pooled by chunks and there's little reason to /// allow stream truncation. + /// + /// Writing past the current capacity will cause Capacity to automatically increase, until MaximumStreamCapacity is reached. /// /// Object has been disposed public override int Capacity @@ -971,11 +1471,8 @@ public override int Capacity return this.largeBuffer.Length; } - if (this.blocks.Count > 0) - { - return this.blocks.Count * this.memoryManager.BlockSize; - } - return 0; + long size = (long)this.blocks.Count * this.memoryManager.BlockSize; + return (int)Math.Min(int.MaxValue, size); } set { @@ -1032,34 +1529,22 @@ public override long Position /// /// Whether the stream can currently read /// - public override bool CanRead - { - get { return !this.disposed; } - } + public override bool CanRead => !this.Disposed; /// /// Whether the stream can currently seek /// - public override bool CanSeek - { - get { return !this.disposed; } - } + public override bool CanSeek => !this.Disposed; /// /// Always false /// - public override bool CanTimeout - { - get { return false; } - } + public override bool CanTimeout => false; /// /// Whether the stream can currently write /// - public override bool CanWrite - { - get { return !this.disposed; } - } + public override bool CanWrite => !this.Disposed; /// /// Returns a single buffer containing the contents of the stream. @@ -1069,7 +1554,11 @@ public override bool CanWrite /// IMPORTANT: Doing a Write() after calling GetBuffer() invalidates the buffer. The old buffer is held onto /// until Dispose is called, but the next time GetBuffer() is called, a new buffer from the pool will be required. /// Object has been disposed +#if NETSTANDARD1_4 + public byte[] GetBuffer() +#else public override byte[] GetBuffer() +#endif { this.CheckDisposed(); @@ -1103,25 +1592,54 @@ public override byte[] GetBuffer() return this.largeBuffer; } + /// + /// Returns an ArraySegment that wraps a single buffer containing the contents of the stream. + /// + /// An ArraySegment containing a reference to the underlying bytes. + /// Always returns true. + /// GetBuffer has no failure modes (it always returns something, even if it's an empty buffer), therefore this method + /// always returns a valid ArraySegment to the same buffer returned by GetBuffer. +#if NET40 || NET45 + public bool TryGetBuffer(out ArraySegment buffer) +#else + public override bool TryGetBuffer(out ArraySegment buffer) +#endif + { + this.CheckDisposed(); + buffer = new ArraySegment(this.GetBuffer(), 0, (int)this.Length); + // GetBuffer has no failure modes, so this should always succeed + return true; + } + /// /// Returns a new array with a copy of the buffer's contents. You should almost certainly be using GetBuffer combined with the Length to /// access the bytes in this stream. Calling ToArray will destroy the benefits of pooled buffers, but it is included /// for the sake of completeness. /// /// Object has been disposed + /// The current RecyclableStreamManager object disallows ToArray calls. +#pragma warning disable CS0809 [Obsolete("This method has degraded performance vs. GetBuffer and should be avoided.")] public override byte[] ToArray() { this.CheckDisposed(); + + string stack = this.memoryManager.GenerateCallStacks ? Environment.StackTrace : null; + RecyclableMemoryStreamManager.Events.Writer.MemoryStreamToArray(this.id, this.tag, stack, this.length); + + if (this.memoryManager.ThrowExceptionOnToArray) + { + throw new NotSupportedException("The underlying RecyclableMemoryStreamManager is configured to not allow calls to ToArray."); + } + var newBuffer = new byte[this.Length]; this.InternalRead(newBuffer, 0, this.length, 0); - string stack = this.memoryManager.GenerateCallStacks ? PclExport.Instance.GetStackTrace() : null; - Events.Write.MemoryStreamToArray(this.id, this.tag, stack, 0); this.memoryManager.ReportStreamToArray(); return newBuffer; } +#pragma warning restore CS0809 /// /// Reads from the current position into the provided buffer @@ -1135,21 +1653,38 @@ public override byte[] ToArray() /// offset subtracted from the buffer length is less than count /// Object has been disposed public override int Read(byte[] buffer, int offset, int count) + { + return this.SafeRead(buffer, offset, count, ref this.position); + } + + /// + /// Reads from the specified position into the provided buffer + /// + /// Destination buffer + /// Offset into buffer at which to start placing the read bytes. + /// Number of bytes to read. + /// Position in the stream to start reading from + /// The number of bytes read + /// buffer is null + /// offset or count is less than 0 + /// offset subtracted from the buffer length is less than count + /// Object has been disposed + public int SafeRead(byte[] buffer, int offset, int count, ref int streamPosition) { this.CheckDisposed(); if (buffer == null) { - throw new ArgumentNullException("buffer"); + throw new ArgumentNullException(nameof(buffer)); } if (offset < 0) { - throw new ArgumentOutOfRangeException("offset", "offset cannot be negative"); + throw new ArgumentOutOfRangeException(nameof(offset), "offset cannot be negative"); } if (count < 0) { - throw new ArgumentOutOfRangeException("count", "count cannot be negative"); + throw new ArgumentOutOfRangeException(nameof(count), "count cannot be negative"); } if (offset + count > buffer.Length) @@ -1157,10 +1692,39 @@ public override int Read(byte[] buffer, int offset, int count) throw new ArgumentException("buffer length must be at least offset + count"); } - int amountRead = this.InternalRead(buffer, offset, count, this.position); - this.position += amountRead; + int amountRead = this.InternalRead(buffer, offset, count, streamPosition); + streamPosition += amountRead; + return amountRead; + } + +#if !NETSTANDARD2_0 && NETCORE + /// + /// Reads from the current position into the provided buffer + /// + /// Destination buffer + /// The number of bytes read + /// Object has been disposed + public override int Read(Span buffer) + { + return this.SafeRead(buffer, ref this.position); + } + + /// + /// Reads from the specified position into the provided buffer + /// + /// Destination buffer + /// Position in the stream to start reading from + /// The number of bytes read + /// Object has been disposed + public int SafeRead(Span buffer, ref int streamPosition) + { + this.CheckDisposed(); + + int amountRead = this.InternalRead(buffer, streamPosition); + streamPosition += amountRead; return amountRead; } +#endif /// /// Writes the buffer to the stream @@ -1177,17 +1741,18 @@ public override void Write(byte[] buffer, int offset, int count) this.CheckDisposed(); if (buffer == null) { - throw new ArgumentNullException("buffer"); + throw new ArgumentNullException(nameof(buffer)); } if (offset < 0) { - throw new ArgumentOutOfRangeException("offset", offset, "Offset must be in the range of 0 - buffer.Length-1"); + throw new ArgumentOutOfRangeException(nameof(offset), offset, + "Offset must be in the range of 0 - buffer.Length-1"); } if (count < 0) { - throw new ArgumentOutOfRangeException("count", count, "count must be non-negative"); + throw new ArgumentOutOfRangeException(nameof(count), count, "count must be non-negative"); } if (count + offset > buffer.Length) @@ -1203,13 +1768,6 @@ public override void Write(byte[] buffer, int offset, int count) throw new IOException("Maximum capacity exceeded"); } - long requiredBuffers = (end + blockSize - 1) / blockSize; - - if (requiredBuffers * blockSize > MaxStreamLength) - { - throw new IOException("Maximum capacity exceeded"); - } - this.EnsureCapacity((int)end); if (this.largeBuffer == null) @@ -1224,7 +1782,8 @@ public override void Write(byte[] buffer, int offset, int count) int remainingInBlock = blockSize - blockAndOffset.Offset; int amountToWriteInBlock = Math.Min(remainingInBlock, bytesRemaining); - Buffer.BlockCopy(buffer, offset + bytesWritten, currentBlock, blockAndOffset.Offset, amountToWriteInBlock); + Buffer.BlockCopy(buffer, offset + bytesWritten, currentBlock, blockAndOffset.Offset, + amountToWriteInBlock); bytesRemaining -= amountToWriteInBlock; bytesWritten += amountToWriteInBlock; @@ -1241,12 +1800,61 @@ public override void Write(byte[] buffer, int offset, int count) this.length = Math.Max(this.position, this.length); } +#if !NETSTANDARD2_0 && NETCORE + /// + /// Writes the buffer to the stream + /// + /// Source buffer + /// buffer is null + /// Object has been disposed + public override void Write(ReadOnlySpan source) + { + this.CheckDisposed(); + + int blockSize = this.memoryManager.BlockSize; + long end = (long)this.position + source.Length; + // Check for overflow + if (end > MaxStreamLength) + { + throw new IOException("Maximum capacity exceeded"); + } + + this.EnsureCapacity((int)end); + + if (this.largeBuffer == null) + { + var blockAndOffset = this.GetBlockAndRelativeOffset(this.position); + + while (source.Length > 0) + { + byte[] currentBlock = this.blocks[blockAndOffset.Block]; + int remainingInBlock = blockSize - blockAndOffset.Offset; + int amountToWriteInBlock = Math.Min(remainingInBlock, source.Length); + + source.Slice(0, amountToWriteInBlock) + .CopyTo(currentBlock.AsSpan(blockAndOffset.Offset)); + + source = source.Slice(amountToWriteInBlock); + + ++blockAndOffset.Block; + blockAndOffset.Offset = 0; + } + } + else + { + source.CopyTo(this.largeBuffer.AsSpan(this.position)); + } + this.position = (int)end; + this.length = Math.Max(this.position, this.length); + } +#endif + /// /// Returns a useful string for debugging. This should not normally be called in actual production code. /// public override string ToString() { - return string.Format("Id = {0}, Tag = {1}, Length = {2:N0} bytes", this.Id, this.Tag, this.Length); + return $"Id = {this.Id}, Tag = {this.Tag}, Length = {this.Length:N0} bytes"; } /// @@ -1257,8 +1865,44 @@ public override string ToString() public override void WriteByte(byte value) { this.CheckDisposed(); - this.byteBuffer[0] = value; - this.Write(this.byteBuffer, 0, 1); + + long end = (long)this.position + 1; + + // Check for overflow + if (end > MaxStreamLength) + { + throw new IOException("Maximum capacity exceeded"); + } + + if (this.largeBuffer == null) + { + var blockSize = this.memoryManager.BlockSize; + + var block = this.position / blockSize; + + if (block >= this.blocks.Count) + { + this.EnsureCapacity((int)end); + } + + this.blocks[block][this.position % blockSize] = value; + } + else + { + if (this.position >= this.largeBuffer.Length) + { + this.EnsureCapacity((int)end); + } + + this.largeBuffer[this.position] = value; + } + + this.position = (int)end; + + if (this.position > this.length) + { + this.length = this.position; + } } /// @@ -1267,23 +1911,34 @@ public override void WriteByte(byte value) /// The byte at the current position, or -1 if the position is at the end of the stream. /// Object has been disposed public override int ReadByte() + { + return this.SafeReadByte(ref this.position); + } + + /// + /// Reads a single byte from the specified position in the stream. + /// + /// The position in the stream to read from + /// The byte at the current position, or -1 if the position is at the end of the stream. + /// Object has been disposed + public int SafeReadByte(ref int streamPosition) { this.CheckDisposed(); - if (this.position == this.length) + if (streamPosition == this.length) { return -1; } - byte value = 0; + byte value; if (this.largeBuffer == null) { - var blockAndOffset = this.GetBlockAndRelativeOffset(this.position); + var blockAndOffset = this.GetBlockAndRelativeOffset(streamPosition); value = this.blocks[blockAndOffset.Block][blockAndOffset.Offset]; } else { - value = this.largeBuffer[position]; + value = this.largeBuffer[streamPosition]; } - this.position++; + streamPosition++; return value; } @@ -1297,7 +1952,8 @@ public override void SetLength(long value) this.CheckDisposed(); if (value < 0 || value > MaxStreamLength) { - throw new ArgumentOutOfRangeException("value", "value must be non-negative and at most " + MaxStreamLength); + throw new ArgumentOutOfRangeException(nameof(value), + "value must be non-negative and at most " + MaxStreamLength); } this.EnsureCapacity((int)value); @@ -1324,23 +1980,23 @@ public override long Seek(long offset, SeekOrigin loc) this.CheckDisposed(); if (offset > MaxStreamLength) { - throw new ArgumentOutOfRangeException("offset", "offset cannot be larger than " + MaxStreamLength); + throw new ArgumentOutOfRangeException(nameof(offset), "offset cannot be larger than " + MaxStreamLength); } int newPosition; switch (loc) { - case SeekOrigin.Begin: - newPosition = (int)offset; - break; - case SeekOrigin.Current: - newPosition = (int)offset + this.position; - break; - case SeekOrigin.End: - newPosition = (int)offset + this.length; - break; - default: - throw new ArgumentException("Invalid seek origin", "loc"); + case SeekOrigin.Begin: + newPosition = (int)offset; + break; + case SeekOrigin.Current: + newPosition = (int)offset + this.position; + break; + case SeekOrigin.End: + newPosition = (int)offset + this.length; + break; + default: + throw new ArgumentException("Invalid seek origin", nameof(loc)); } if (newPosition < 0) { @@ -1351,55 +2007,90 @@ public override long Seek(long offset, SeekOrigin loc) } /// - /// Synchronously writes this stream's bytes to the parameter stream. + /// Synchronously writes this stream's bytes to the argument stream. /// /// Destination stream /// Important: This does a synchronous write, which may not be desired in some situations + /// stream is null public override void WriteTo(Stream stream) + { + this.WriteTo(stream, 0, this.length); + } + + /// + /// Synchronously writes this stream's bytes, starting at offset, for count bytes, to the argument stream. + /// + /// Destination stream + /// Offset in source + /// Number of bytes to write + /// stream is null + /// Offset is less than 0, or offset + count is beyond this stream's length. + public void WriteTo(Stream stream, int offset, int count) { this.CheckDisposed(); if (stream == null) { - throw new ArgumentNullException("stream"); + throw new ArgumentNullException(nameof(stream)); + } + + if (offset < 0 || offset + count > this.length) + { + throw new ArgumentOutOfRangeException(message: "offset must not be negative and offset + count must not exceed the length of the stream", innerException: null); } if (this.largeBuffer == null) { - int currentBlock = 0; - int bytesRemaining = this.length; + var blockAndOffset = GetBlockAndRelativeOffset(offset); + int bytesRemaining = count; + int currentBlock = blockAndOffset.Block; + int currentOffset = blockAndOffset.Offset; while (bytesRemaining > 0) { - int amountToCopy = Math.Min(this.blocks[currentBlock].Length, bytesRemaining); - stream.Write(this.blocks[currentBlock], 0, amountToCopy); + int amountToCopy = Math.Min(this.blocks[currentBlock].Length - currentOffset, bytesRemaining); + stream.Write(this.blocks[currentBlock], currentOffset, amountToCopy); bytesRemaining -= amountToCopy; ++currentBlock; + currentOffset = 0; } } else { - stream.Write(this.largeBuffer, 0, this.length); + stream.Write(this.largeBuffer, offset, count); } + } - #endregion +#endregion - #region Helper Methods +#region Helper Methods + private bool Disposed => Interlocked.Read(ref this.disposedState) != 0; + + [MethodImpl((MethodImplOptions)256)] private void CheckDisposed() { - if (this.disposed) + if (this.Disposed) { - throw new ObjectDisposedException(string.Format("The stream with Id {0} and Tag {1} is disposed.", this.id, this.tag)); + this.ThrowDisposedException(); } } + [MethodImpl(MethodImplOptions.NoInlining)] + private void ThrowDisposedException() + { + throw new ObjectDisposedException($"The stream with Id {this.id} and Tag {this.tag} is disposed."); + } + private int InternalRead(byte[] buffer, int offset, int count, int fromPosition) { if (this.length - fromPosition <= 0) { return 0; } + + int amountToCopy; + if (this.largeBuffer == null) { var blockAndOffset = this.GetBlockAndRelativeOffset(fromPosition); @@ -1408,8 +2099,10 @@ private int InternalRead(byte[] buffer, int offset, int count, int fromPosition) while (bytesRemaining > 0) { - int amountToCopy = Math.Min(this.blocks[blockAndOffset.Block].Length - blockAndOffset.Offset, bytesRemaining); - Buffer.BlockCopy(this.blocks[blockAndOffset.Block], blockAndOffset.Offset, buffer, bytesWritten + offset, amountToCopy); + amountToCopy = Math.Min(this.blocks[blockAndOffset.Block].Length - blockAndOffset.Offset, + bytesRemaining); + Buffer.BlockCopy(this.blocks[blockAndOffset.Block], blockAndOffset.Offset, buffer, + bytesWritten + offset, amountToCopy); bytesWritten += amountToCopy; bytesRemaining -= amountToCopy; @@ -1419,13 +2112,47 @@ private int InternalRead(byte[] buffer, int offset, int count, int fromPosition) } return bytesWritten; } - else + amountToCopy = Math.Min(count, this.length - fromPosition); + Buffer.BlockCopy(this.largeBuffer, fromPosition, buffer, offset, amountToCopy); + return amountToCopy; + } + +#if NETCORE + private int InternalRead(Span buffer, int fromPosition) + { + if (this.length - fromPosition <= 0) + { + return 0; + } + + int amountToCopy; + + if (this.largeBuffer == null) { - int amountToCopy = Math.Min(count, this.length - fromPosition); - Buffer.BlockCopy(this.largeBuffer, fromPosition, buffer, offset, amountToCopy); - return amountToCopy; + var blockAndOffset = this.GetBlockAndRelativeOffset(fromPosition); + int bytesWritten = 0; + int bytesRemaining = Math.Min(buffer.Length, this.length - fromPosition); + + while (bytesRemaining > 0) + { + amountToCopy = Math.Min(this.blocks[blockAndOffset.Block].Length - blockAndOffset.Offset, + bytesRemaining); + this.blocks[blockAndOffset.Block].AsSpan(blockAndOffset.Offset, amountToCopy) + .CopyTo(buffer.Slice(bytesWritten)); + + bytesWritten += amountToCopy; + bytesRemaining -= amountToCopy; + + ++blockAndOffset.Block; + blockAndOffset.Offset = 0; + } + return bytesWritten; } + amountToCopy = Math.Min(buffer.Length, this.length - fromPosition); + this.largeBuffer.AsSpan(fromPosition, amountToCopy).CopyTo(buffer); + return amountToCopy; } +#endif private struct BlockAndOffset { @@ -1439,6 +2166,7 @@ public BlockAndOffset(int block, int offset) } } + [MethodImpl((MethodImplOptions)256)] private BlockAndOffset GetBlockAndRelativeOffset(int offset) { var blockSize = this.memoryManager.BlockSize; @@ -1449,8 +2177,10 @@ private void EnsureCapacity(int newCapacity) { if (newCapacity > this.memoryManager.MaximumStreamCapacity && this.memoryManager.MaximumStreamCapacity > 0) { - Events.Write.MemoryStreamOverCapacity(newCapacity, this.memoryManager.MaximumStreamCapacity, this.tag, this.allocationStack); - throw new InvalidOperationException("Requested capacity is too large: " + newCapacity + ". Limit is " + this.memoryManager.MaximumStreamCapacity); + RecyclableMemoryStreamManager.Events.Writer.MemoryStreamOverCapacity(newCapacity, + this.memoryManager.MaximumStreamCapacity, this.tag, this.AllocationStack); + throw new InvalidOperationException("Requested capacity is too large: " + newCapacity + ". Limit is " + + this.memoryManager.MaximumStreamCapacity); } if (this.largeBuffer != null) @@ -1493,131 +2223,6 @@ private void ReleaseLargeBuffer() this.largeBuffer = null; } - #endregion - } - - //Avoid taking on an extra dep - public sealed partial class RecyclableMemoryStreamManager - { - //[EventSource(Name = "Microsoft-IO-RecyclableMemoryStream", Guid = "{B80CD4E4-890E-468D-9CBA-90EB7C82DFC7}")] - public sealed class Events// : EventSource - { - public static Events Write = new Events(); - - public enum MemoryStreamBufferType - { - Small, - Large - } - - public enum MemoryStreamDiscardReason - { - TooLarge, - EnoughFree - } - - //[Event(1, Level = EventLevel.Verbose)] - public void MemoryStreamCreated(Guid guid, string tag, int requestedSize) - { - //if (this.IsEnabled(EventLevel.Verbose, EventKeywords.None)) - //{ - // WriteEvent(1, guid, tag ?? string.Empty, requestedSize); - //} - } - - //[Event(2, Level = EventLevel.Verbose)] - public void MemoryStreamDisposed(Guid guid, string tag) - { - //if (this.IsEnabled(EventLevel.Verbose, EventKeywords.None)) - //{ - // WriteEvent(2, guid, tag ?? string.Empty); - //} - } - - //[Event(3, Level = EventLevel.Critical)] - public void MemoryStreamDoubleDispose(Guid guid, string tag, string allocationStack, string disposeStack1, - string disposeStack2) - { - //if (this.IsEnabled()) - //{ - // this.WriteEvent(3, guid, tag ?? string.Empty, allocationStack ?? string.Empty, - // disposeStack1 ?? string.Empty, disposeStack2 ?? string.Empty); - //} - } - - //[Event(4, Level = EventLevel.Error)] - public void MemoryStreamFinalized(Guid guid, string tag, string allocationStack) - { - //if (this.IsEnabled()) - //{ - // WriteEvent(4, guid, tag ?? string.Empty, allocationStack ?? string.Empty); - //} - } - - //[Event(5, Level = EventLevel.Verbose)] - public void MemoryStreamToArray(Guid guid, string tag, string stack, int size) - { - //if (this.IsEnabled(EventLevel.Verbose, EventKeywords.None)) - //{ - // WriteEvent(5, guid, tag ?? string.Empty, stack ?? string.Empty, size); - //} - } - - //[Event(6, Level = EventLevel.Informational)] - public void MemoryStreamManagerInitialized(int blockSize, int largeBufferMultiple, int maximumBufferSize) - { - //if (this.IsEnabled()) - //{ - // WriteEvent(6, blockSize, largeBufferMultiple, maximumBufferSize); - //} - } - - //[Event(7, Level = EventLevel.Verbose)] - public void MemoryStreamNewBlockCreated(long smallPoolInUseBytes) - { - //if (this.IsEnabled(EventLevel.Verbose, EventKeywords.None)) - //{ - // WriteEvent(7, smallPoolInUseBytes); - //} - } - - //[Event(8, Level = EventLevel.Verbose)] - public void MemoryStreamNewLargeBufferCreated(int requiredSize, long largePoolInUseBytes) - { - //if (this.IsEnabled(EventLevel.Verbose, EventKeywords.None)) - //{ - // WriteEvent(8, requiredSize, largePoolInUseBytes); - //} - } - - //[Event(9, Level = EventLevel.Verbose)] - public void MemoryStreamNonPooledLargeBufferCreated(int requiredSize, string tag, string allocationStack) - { - //if (this.IsEnabled(EventLevel.Verbose, EventKeywords.None)) - //{ - // WriteEvent(9, requiredSize, tag ?? string.Empty, allocationStack ?? string.Empty); - //} - } - - //[Event(10, Level = EventLevel.Warning)] - public void MemoryStreamDiscardBuffer(MemoryStreamBufferType bufferType, string tag, - MemoryStreamDiscardReason reason) - { - //if (this.IsEnabled()) - //{ - // WriteEvent(10, bufferType, tag ?? string.Empty, reason); - //} - } - - //[Event(11, Level = EventLevel.Error)] - public void MemoryStreamOverCapacity(int requestedCapacity, long maxCapacity, string tag, - string allocationStack) - { - //if (this.IsEnabled()) - //{ - // WriteEvent(11, requestedCapacity, maxCapacity, tag ?? string.Empty, allocationStack ?? string.Empty); - //} - } - } +#endregion } } diff --git a/src/ServiceStack.Text/ReflectionExtensions.cs b/src/ServiceStack.Text/ReflectionExtensions.cs index 8dc9191ec..4f3aeae96 100644 --- a/src/ServiceStack.Text/ReflectionExtensions.cs +++ b/src/ServiceStack.Text/ReflectionExtensions.cs @@ -334,8 +334,8 @@ public static bool AreAllStringOrValueTypes(params Type[] types) static Dictionary ConstructorMethods = new Dictionary(); public static EmptyCtorDelegate GetConstructorMethod(Type type) { - EmptyCtorDelegate emptyCtorFn; - if (ConstructorMethods.TryGetValue(type, out emptyCtorFn)) return emptyCtorFn; + if (ConstructorMethods.TryGetValue(type, out var emptyCtorFn)) + return emptyCtorFn; emptyCtorFn = GetConstructorMethodToCache(type); @@ -355,8 +355,8 @@ public static EmptyCtorDelegate GetConstructorMethod(Type type) static Dictionary TypeNamesMap = new Dictionary(); public static EmptyCtorDelegate GetConstructorMethod(string typeName) { - EmptyCtorDelegate emptyCtorFn; - if (TypeNamesMap.TryGetValue(typeName, out emptyCtorFn)) return emptyCtorFn; + if (TypeNamesMap.TryGetValue(typeName, out var emptyCtorFn)) + return emptyCtorFn; var type = JsConfig.TypeFinder(typeName); if (type == null) return null; @@ -379,10 +379,9 @@ public static EmptyCtorDelegate GetConstructorMethod(string typeName) public static EmptyCtorDelegate GetConstructorMethodToCache(Type type) { if (type == typeof(string)) - { - return () => String.Empty; - } - else if (type.IsInterface) + return () => string.Empty; + + if (type.IsInterface) { if (type.HasGenericType()) { @@ -424,26 +423,7 @@ public static EmptyCtorDelegate GetConstructorMethodToCache(Type type) return realizedType.CreateInstance; } - var emptyCtor = type.GetConstructor(Type.EmptyTypes); - if (emptyCtor != null) - { - if (PclExport.Instance.SupportsEmit) - { - var dm = new System.Reflection.Emit.DynamicMethod("MyCtor", type, Type.EmptyTypes, - typeof(ReflectionExtensions).Module, true); - var ilgen = dm.GetILGenerator(); - ilgen.Emit(System.Reflection.Emit.OpCodes.Nop); - ilgen.Emit(System.Reflection.Emit.OpCodes.Newobj, emptyCtor); - ilgen.Emit(System.Reflection.Emit.OpCodes.Ret); - - return (EmptyCtorDelegate) dm.CreateDelegate(typeof(EmptyCtorDelegate)); - } - - return () => Activator.CreateInstance(type); - } - - //Anonymous types don't have empty constructors - return () => FormatterServices.GetUninitializedObject(type); + return ReflectionOptimizer.Instance.CreateConstructor(type); } private static class TypeMeta @@ -614,6 +594,20 @@ internal static void Reset() "IgnoreDataMemberAttribute", "JsonIgnoreAttribute" }; + + try + { + JsConfig.SerializeFn = x => x?.ToString(); + JsConfig.SerializeFn = x => x?.ToString(); + JsConfig.SerializeFn = x => x?.ToString(); + JsConfig.SerializeFn = x => x?.ToString(); + JsConfig.SerializeFn = x => x?.ToString(); + JsConfig.SerializeFn = x => x?.ToString(); + } + catch (Exception e) + { + Tracer.Instance.WriteError("ReflectionExtensions JsConfig", e); + } } public static PropertyInfo[] GetSerializableProperties(this Type type) @@ -643,7 +637,8 @@ public static PropertyInfo[] OnlySerializableProperties(this PropertyInfo[] prop var name = attr.GetType().Name; return !IgnoreAttributesNamed.Contains(name); })) - .Where(prop => !JsConfig.ExcludeTypes.Contains(prop.PropertyType)) + .Where(prop => !(JsConfig.ExcludeTypes.Contains(prop.PropertyType) || + JsConfig.ExcludeTypeNames.Contains(prop.PropertyType.FullName))) .ToArray(); } @@ -664,7 +659,9 @@ public static FieldInfo[] GetSerializableFields(this Type type) f.HasAttribute()).ToArray(); } - if (!JsConfig.IncludePublicFields) + var config = JsConfig.GetConfig(); + + if (!config.IncludePublicFields) return TypeConstants.EmptyFieldInfoArray; var publicFields = type.GetPublicFields(); @@ -673,7 +670,7 @@ public static FieldInfo[] GetSerializableFields(this Type type) return publicFields .Where(prop => prop.AllAttributes() .All(attr => !IgnoreAttributesNamed.Contains(attr.GetType().Name))) - .Where(prop => !JsConfig.ExcludeTypes.Contains(prop.FieldType)) + .Where(prop => !config.ExcludeTypes.Contains(prop.FieldType)) .ToArray(); } diff --git a/src/ServiceStack.Text/ReflectionOptimizer.Emit.cs b/src/ServiceStack.Text/ReflectionOptimizer.Emit.cs new file mode 100644 index 000000000..cac57f78f --- /dev/null +++ b/src/ServiceStack.Text/ReflectionOptimizer.Emit.cs @@ -0,0 +1,450 @@ +#if NETFX || (NETCORE && !NETSTANDARD2_0) + +using System; +using System.Linq; +using System.Reflection; +using System.Reflection.Emit; +using System.Runtime.Serialization; + +namespace ServiceStack.Text +{ + public sealed class EmitReflectionOptimizer : ReflectionOptimizer + { + private static EmitReflectionOptimizer provider; + public static EmitReflectionOptimizer Provider => provider ??= new EmitReflectionOptimizer(); + private EmitReflectionOptimizer() { } + + public override Type UseType(Type type) + { + if (type.IsInterface || type.IsAbstract) + { + return DynamicProxy.GetInstanceFor(type).GetType(); + } + + return type; + } + + internal static DynamicMethod CreateDynamicGetMethod(MemberInfo memberInfo) + { + var memberType = memberInfo is FieldInfo ? "Field" : "Property"; + var name = $"_Get{memberType}[T]_{memberInfo.Name}_"; + var returnType = typeof(object); + + return !memberInfo.DeclaringType.IsInterface + ? new DynamicMethod(name, returnType, new[] {typeof(T)}, memberInfo.DeclaringType, true) + : new DynamicMethod(name, returnType, new[] {typeof(T)}, memberInfo.Module, true); + } + + public override GetMemberDelegate CreateGetter(PropertyInfo propertyInfo) + { + var getter = CreateDynamicGetMethod(propertyInfo); + + var gen = getter.GetILGenerator(); + gen.Emit(OpCodes.Ldarg_0); + + if (propertyInfo.DeclaringType.IsValueType) + { + gen.Emit(OpCodes.Unbox, propertyInfo.DeclaringType); + } + else + { + gen.Emit(OpCodes.Castclass, propertyInfo.DeclaringType); + } + + var mi = propertyInfo.GetGetMethod(true); + if (mi == null) + return null; + gen.Emit(mi.IsFinal ? OpCodes.Call : OpCodes.Callvirt, mi); + + if (propertyInfo.PropertyType.IsValueType) + { + gen.Emit(OpCodes.Box, propertyInfo.PropertyType); + } + + gen.Emit(OpCodes.Ret); + + return (GetMemberDelegate) getter.CreateDelegate(typeof(GetMemberDelegate)); + } + + public override GetMemberDelegate CreateGetter(PropertyInfo propertyInfo) + { + var getter = CreateDynamicGetMethod(propertyInfo); + + var gen = getter.GetILGenerator(); + var mi = propertyInfo.GetGetMethod(true); + if (mi == null) + return null; + + if (typeof(T).IsValueType) + { + gen.Emit(OpCodes.Ldarga_S, 0); + + if (typeof(T) != propertyInfo.DeclaringType) + { + gen.Emit(OpCodes.Unbox, propertyInfo.DeclaringType); + } + } + else + { + gen.Emit(OpCodes.Ldarg_0); + + if (typeof(T) != propertyInfo.DeclaringType) + { + gen.Emit(OpCodes.Castclass, propertyInfo.DeclaringType); + } + } + + gen.Emit(mi.IsFinal ? OpCodes.Call : OpCodes.Callvirt, mi); + + if (propertyInfo.PropertyType.IsValueType) + { + gen.Emit(OpCodes.Box, propertyInfo.PropertyType); + } + + gen.Emit(OpCodes.Isinst, typeof(object)); + + gen.Emit(OpCodes.Ret); + + return (GetMemberDelegate) getter.CreateDelegate(typeof(GetMemberDelegate)); + } + + public override SetMemberDelegate CreateSetter(PropertyInfo propertyInfo) + { + var mi = propertyInfo.GetSetMethod(true); + if (mi == null) + return null; + + var setter = CreateDynamicSetMethod(propertyInfo); + + var gen = setter.GetILGenerator(); + gen.Emit(OpCodes.Ldarg_0); + + if (propertyInfo.DeclaringType.IsValueType) + { + gen.Emit(OpCodes.Unbox, propertyInfo.DeclaringType); + } + else + { + gen.Emit(OpCodes.Castclass, propertyInfo.DeclaringType); + } + + gen.Emit(OpCodes.Ldarg_1); + + if (propertyInfo.PropertyType.IsValueType) + { + gen.Emit(OpCodes.Unbox_Any, propertyInfo.PropertyType); + } + else + { + gen.Emit(OpCodes.Castclass, propertyInfo.PropertyType); + } + + gen.EmitCall(mi.IsFinal ? OpCodes.Call : OpCodes.Callvirt, mi, (Type[]) null); + + gen.Emit(OpCodes.Ret); + + return (SetMemberDelegate) setter.CreateDelegate(typeof(SetMemberDelegate)); + } + + public override SetMemberDelegate CreateSetter(PropertyInfo propertyInfo) => + ExpressionReflectionOptimizer.Provider.CreateSetter(propertyInfo); + + + public override GetMemberDelegate CreateGetter(FieldInfo fieldInfo) + { + var getter = CreateDynamicGetMethod(fieldInfo); + + var gen = getter.GetILGenerator(); + + gen.Emit(OpCodes.Ldarg_0); + + if (fieldInfo.DeclaringType.IsValueType) + { + gen.Emit(OpCodes.Unbox, fieldInfo.DeclaringType); + } + else + { + gen.Emit(OpCodes.Castclass, fieldInfo.DeclaringType); + } + + gen.Emit(OpCodes.Ldfld, fieldInfo); + + if (fieldInfo.FieldType.IsValueType) + { + gen.Emit(OpCodes.Box, fieldInfo.FieldType); + } + + gen.Emit(OpCodes.Ret); + + return (GetMemberDelegate) getter.CreateDelegate(typeof(GetMemberDelegate)); + } + + public override GetMemberDelegate CreateGetter(FieldInfo fieldInfo) + { + var getter = CreateDynamicGetMethod(fieldInfo); + + var gen = getter.GetILGenerator(); + + gen.Emit(OpCodes.Ldarg_0); + + gen.Emit(OpCodes.Ldfld, fieldInfo); + + if (fieldInfo.FieldType.IsValueType) + { + gen.Emit(OpCodes.Box, fieldInfo.FieldType); + } + + gen.Emit(OpCodes.Ret); + + return (GetMemberDelegate) getter.CreateDelegate(typeof(GetMemberDelegate)); + } + + public override SetMemberDelegate CreateSetter(FieldInfo fieldInfo) + { + var setter = CreateDynamicSetMethod(fieldInfo); + + var gen = setter.GetILGenerator(); + gen.Emit(OpCodes.Ldarg_0); + + if (fieldInfo.DeclaringType.IsValueType) + { + gen.Emit(OpCodes.Unbox, fieldInfo.DeclaringType); + } + else + { + gen.Emit(OpCodes.Castclass, fieldInfo.DeclaringType); + } + + gen.Emit(OpCodes.Ldarg_1); + + gen.Emit(fieldInfo.FieldType.IsClass + ? OpCodes.Castclass + : OpCodes.Unbox_Any, + fieldInfo.FieldType); + + gen.Emit(OpCodes.Stfld, fieldInfo); + gen.Emit(OpCodes.Ret); + + return (SetMemberDelegate) setter.CreateDelegate(typeof(SetMemberDelegate)); + } + + static readonly Type[] DynamicGetMethodArgs = {typeof(object)}; + + internal static DynamicMethod CreateDynamicGetMethod(MemberInfo memberInfo) + { + var memberType = memberInfo is FieldInfo ? "Field" : "Property"; + var name = $"_Get{memberType}_{memberInfo.Name}_"; + var returnType = typeof(object); + + return !memberInfo.DeclaringType.IsInterface + ? new DynamicMethod(name, returnType, DynamicGetMethodArgs, memberInfo.DeclaringType, true) + : new DynamicMethod(name, returnType, DynamicGetMethodArgs, memberInfo.Module, true); + } + + public override SetMemberDelegate CreateSetter(FieldInfo fieldInfo) => + ExpressionReflectionOptimizer.Provider.CreateSetter(fieldInfo); + + public override SetMemberRefDelegate CreateSetterRef(FieldInfo fieldInfo) => + ExpressionReflectionOptimizer.Provider.CreateSetterRef(fieldInfo); + + public override bool IsDynamic(Assembly assembly) + { + try + { + var isDynamic = assembly is AssemblyBuilder + || string.IsNullOrEmpty(assembly.Location); + return isDynamic; + } + catch (NotSupportedException) + { + //Ignore assembly.Location not supported in a dynamic assembly. + return true; + } + } + + public override EmptyCtorDelegate CreateConstructor(Type type) + { + var emptyCtor = type.GetConstructor(Type.EmptyTypes); + if (emptyCtor != null) + { + var dm = new DynamicMethod("MyCtor", type, Type.EmptyTypes, typeof(ReflectionExtensions).Module, true); + var ilgen = dm.GetILGenerator(); + ilgen.Emit(OpCodes.Nop); + ilgen.Emit(OpCodes.Newobj, emptyCtor); + ilgen.Emit(OpCodes.Ret); + + return (EmptyCtorDelegate) dm.CreateDelegate(typeof(EmptyCtorDelegate)); + } + + //Anonymous types don't have empty constructors + return () => FormatterServices.GetUninitializedObject(type); + } + + static readonly Type[] DynamicSetMethodArgs = {typeof(object), typeof(object)}; + + internal static DynamicMethod CreateDynamicSetMethod(MemberInfo memberInfo) + { + var memberType = memberInfo is FieldInfo ? "Field" : "Property"; + var name = $"_Set{memberType}_{memberInfo.Name}_"; + var returnType = typeof(void); + + return !memberInfo.DeclaringType.IsInterface + ? new DynamicMethod(name, returnType, DynamicSetMethodArgs, memberInfo.DeclaringType, true) + : new DynamicMethod(name, returnType, DynamicSetMethodArgs, memberInfo.Module, true); + } + } + + + public static class DynamicProxy + { + public static T GetInstanceFor() + { + return (T)GetInstanceFor(typeof(T)); + } + + static readonly ModuleBuilder ModuleBuilder; + static readonly AssemblyBuilder DynamicAssembly; + static readonly Type[] EmptyTypes = new Type[0]; + + public static object GetInstanceFor(Type targetType) + { + lock (DynamicAssembly) + { + var constructedType = DynamicAssembly.GetType(ProxyName(targetType)) ?? GetConstructedType(targetType); + var instance = Activator.CreateInstance(constructedType); + return instance; + } + } + + static string ProxyName(Type targetType) + { + return targetType.Name + "Proxy"; + } + + static DynamicProxy() + { + var assemblyName = new AssemblyName("DynImpl"); +#if NETCORE + DynamicAssembly = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run); +#else + DynamicAssembly = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave); +#endif + ModuleBuilder = DynamicAssembly.DefineDynamicModule("DynImplModule"); + } + + static Type GetConstructedType(Type targetType) + { + var typeBuilder = ModuleBuilder.DefineType(targetType.Name + "Proxy", TypeAttributes.Public); + + var ctorBuilder = typeBuilder.DefineConstructor( + MethodAttributes.Public, + CallingConventions.Standard, + new Type[] { }); + var ilGenerator = ctorBuilder.GetILGenerator(); + ilGenerator.Emit(OpCodes.Ret); + + IncludeType(targetType, typeBuilder); + + foreach (var face in targetType.GetInterfaces()) + IncludeType(face, typeBuilder); + +#if NETCORE + return typeBuilder.CreateTypeInfo().AsType(); +#else + return typeBuilder.CreateType(); +#endif + } + + static void IncludeType(Type typeOfT, TypeBuilder typeBuilder) + { + var methodInfos = typeOfT.GetMethods(); + foreach (var methodInfo in methodInfos) + { + if (methodInfo.Name.StartsWith("set_", StringComparison.Ordinal)) continue; // we always add a set for a get. + + if (methodInfo.Name.StartsWith("get_", StringComparison.Ordinal)) + { + BindProperty(typeBuilder, methodInfo); + } + else + { + BindMethod(typeBuilder, methodInfo); + } + } + + typeBuilder.AddInterfaceImplementation(typeOfT); + } + + static void BindMethod(TypeBuilder typeBuilder, MethodInfo methodInfo) + { + var methodBuilder = typeBuilder.DefineMethod( + methodInfo.Name, + MethodAttributes.Public | MethodAttributes.Virtual, + methodInfo.ReturnType, + methodInfo.GetParameters().Select(p => p.GetType()).ToArray() + ); + var methodILGen = methodBuilder.GetILGenerator(); + if (methodInfo.ReturnType == typeof(void)) + { + methodILGen.Emit(OpCodes.Ret); + } + else + { + if (methodInfo.ReturnType.IsValueType || methodInfo.ReturnType.IsEnum) + { + MethodInfo getMethod = typeof(Activator).GetMethod("CreateInstance", new[] { typeof(Type) }); + LocalBuilder lb = methodILGen.DeclareLocal(methodInfo.ReturnType); + methodILGen.Emit(OpCodes.Ldtoken, lb.LocalType); + methodILGen.Emit(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle")); + methodILGen.Emit(OpCodes.Callvirt, getMethod); + methodILGen.Emit(OpCodes.Unbox_Any, lb.LocalType); + } + else + { + methodILGen.Emit(OpCodes.Ldnull); + } + methodILGen.Emit(OpCodes.Ret); + } + typeBuilder.DefineMethodOverride(methodBuilder, methodInfo); + } + + public static void BindProperty(TypeBuilder typeBuilder, MethodInfo methodInfo) + { + // Backing Field + string propertyName = methodInfo.Name.Replace("get_", ""); + Type propertyType = methodInfo.ReturnType; + FieldBuilder backingField = typeBuilder.DefineField("_" + propertyName, propertyType, FieldAttributes.Private); + + //Getter + MethodBuilder backingGet = typeBuilder.DefineMethod("get_" + propertyName, MethodAttributes.Public | + MethodAttributes.SpecialName | MethodAttributes.Virtual | + MethodAttributes.HideBySig, propertyType, EmptyTypes); + ILGenerator getIl = backingGet.GetILGenerator(); + + getIl.Emit(OpCodes.Ldarg_0); + getIl.Emit(OpCodes.Ldfld, backingField); + getIl.Emit(OpCodes.Ret); + + + //Setter + MethodBuilder backingSet = typeBuilder.DefineMethod("set_" + propertyName, MethodAttributes.Public | + MethodAttributes.SpecialName | MethodAttributes.Virtual | + MethodAttributes.HideBySig, null, new[] { propertyType }); + + ILGenerator setIl = backingSet.GetILGenerator(); + + setIl.Emit(OpCodes.Ldarg_0); + setIl.Emit(OpCodes.Ldarg_1); + setIl.Emit(OpCodes.Stfld, backingField); + setIl.Emit(OpCodes.Ret); + + // Property + PropertyBuilder propertyBuilder = typeBuilder.DefineProperty(propertyName, PropertyAttributes.None, propertyType, null); + propertyBuilder.SetGetMethod(backingGet); + propertyBuilder.SetSetMethod(backingSet); + } + } + +} + +#endif \ No newline at end of file diff --git a/src/ServiceStack.Text/ReflectionOptimizer.cs b/src/ServiceStack.Text/ReflectionOptimizer.cs new file mode 100644 index 000000000..0042abcbc --- /dev/null +++ b/src/ServiceStack.Text/ReflectionOptimizer.cs @@ -0,0 +1,355 @@ +using System; +using System.Linq.Expressions; +using System.Reflection; +using System.Runtime.Serialization; + +namespace ServiceStack.Text +{ + public abstract class ReflectionOptimizer + { + public static ReflectionOptimizer Instance = +#if NETFX || (NETCORE && !NETSTANDARD2_0) + EmitReflectionOptimizer.Provider +#else + ExpressionReflectionOptimizer.Provider +#endif + ; + + public abstract Type UseType(Type type); + + public abstract GetMemberDelegate CreateGetter(PropertyInfo propertyInfo); + public abstract GetMemberDelegate CreateGetter(PropertyInfo propertyInfo); + public abstract SetMemberDelegate CreateSetter(PropertyInfo propertyInfo); + public abstract SetMemberDelegate CreateSetter(PropertyInfo propertyInfo); + + public abstract GetMemberDelegate CreateGetter(FieldInfo fieldInfo); + public abstract GetMemberDelegate CreateGetter(FieldInfo fieldInfo); + public abstract SetMemberDelegate CreateSetter(FieldInfo fieldInfo); + public abstract SetMemberDelegate CreateSetter(FieldInfo fieldInfo); + + public abstract SetMemberRefDelegate CreateSetterRef(FieldInfo fieldInfo); + + public abstract bool IsDynamic(Assembly assembly); + public abstract EmptyCtorDelegate CreateConstructor(Type type); + } + + public sealed class RuntimeReflectionOptimizer : ReflectionOptimizer + { + private static RuntimeReflectionOptimizer provider; + public static RuntimeReflectionOptimizer Provider => provider ??= new RuntimeReflectionOptimizer(); + private RuntimeReflectionOptimizer(){} + + public override Type UseType(Type type) => type; + + public override GetMemberDelegate CreateGetter(PropertyInfo propertyInfo) + { + var getMethodInfo = propertyInfo.GetGetMethod(nonPublic:true); + if (getMethodInfo == null) return null; + + return o => propertyInfo.GetGetMethod(nonPublic:true).Invoke(o, TypeConstants.EmptyObjectArray); + } + + public override GetMemberDelegate CreateGetter(PropertyInfo propertyInfo) + { + var getMethodInfo = propertyInfo.GetGetMethod(nonPublic:true); + if (getMethodInfo == null) return null; + + return o => propertyInfo.GetGetMethod(nonPublic:true).Invoke(o, TypeConstants.EmptyObjectArray); + } + + public override SetMemberDelegate CreateSetter(PropertyInfo propertyInfo) + { + var propertySetMethod = propertyInfo.GetSetMethod(nonPublic:true); + if (propertySetMethod == null) return null; + + return (o, convertedValue) => + propertySetMethod.Invoke(o, new[] { convertedValue }); + } + + public override SetMemberDelegate CreateSetter(PropertyInfo propertyInfo) + { + var propertySetMethod = propertyInfo.GetSetMethod(nonPublic:true); + if (propertySetMethod == null) return null; + + return (o, convertedValue) => + propertySetMethod.Invoke(o, new[] { convertedValue }); + } + + + public override GetMemberDelegate CreateGetter(FieldInfo fieldInfo) => fieldInfo.GetValue; + public override GetMemberDelegate CreateGetter(FieldInfo fieldInfo) => x => fieldInfo.GetValue(x); + public override SetMemberDelegate CreateSetter(FieldInfo fieldInfo) => fieldInfo.SetValue; + public override SetMemberDelegate CreateSetter(FieldInfo fieldInfo) => (o,x) => fieldInfo.SetValue(o,x); + + public override SetMemberRefDelegate CreateSetterRef(FieldInfo fieldInfo) => + ExpressionReflectionOptimizer.Provider.CreateSetterRef(fieldInfo); + + public override bool IsDynamic(Assembly assembly) + { + try + { + var isDyanmic = string.IsNullOrEmpty(assembly.Location); + return isDyanmic; + } + catch (NotSupportedException) + { + //Ignore assembly.Location not supported in a dynamic assembly. + return true; + } + } + + public override EmptyCtorDelegate CreateConstructor(Type type) + { + var emptyCtor = type.GetConstructor(Type.EmptyTypes); + if (emptyCtor != null) + return () => Activator.CreateInstance(type); + + //Anonymous types don't have empty constructors + return () => FormatterServices.GetUninitializedObject(type); + } + } + + public sealed class ExpressionReflectionOptimizer : ReflectionOptimizer + { + private static ExpressionReflectionOptimizer provider; + public static ExpressionReflectionOptimizer Provider => provider ?? (provider = new ExpressionReflectionOptimizer()); + private ExpressionReflectionOptimizer(){} + + public override Type UseType(Type type) => type; + + public override GetMemberDelegate CreateGetter(PropertyInfo propertyInfo) + { + var lambda = GetExpressionLambda(propertyInfo); + var propertyGetFn = lambda.Compile(); + return propertyGetFn; + } + + public static Expression GetExpressionLambda(PropertyInfo propertyInfo) + { + var getMethodInfo = propertyInfo.GetGetMethod(nonPublic:true); + if (getMethodInfo == null) return null; + + var oInstanceParam = Expression.Parameter(typeof(object), "oInstanceParam"); + var instanceParam = Expression.Convert(oInstanceParam, propertyInfo.ReflectedType); //propertyInfo.DeclaringType doesn't work on Proxy types + + var exprCallPropertyGetFn = Expression.Call(instanceParam, getMethodInfo); + var oExprCallPropertyGetFn = Expression.Convert(exprCallPropertyGetFn, typeof(object)); + + return Expression.Lambda + ( + oExprCallPropertyGetFn, + oInstanceParam + ); + } + + public override GetMemberDelegate CreateGetter(PropertyInfo propertyInfo) + { + var expr = GetExpressionLambda(propertyInfo); + return expr.Compile(); + } + + public static Expression> GetExpressionLambda(PropertyInfo propertyInfo) + { + var instance = Expression.Parameter(typeof(T), "i"); + var property = typeof(T) != propertyInfo.DeclaringType + ? Expression.Property(Expression.TypeAs(instance, propertyInfo.DeclaringType), propertyInfo) + : Expression.Property(instance, propertyInfo); + var convertProperty = Expression.TypeAs(property, typeof(object)); + return Expression.Lambda>(convertProperty, instance); + } + + public override SetMemberDelegate CreateSetter(PropertyInfo propertyInfo) + { + var propertySetMethod = propertyInfo.GetSetMethod(nonPublic:true); + if (propertySetMethod == null) return null; + + try + { + var declaringType = propertyInfo.ReflectedType; + + var instance = Expression.Parameter(typeof(object), "i"); + var argument = Expression.Parameter(typeof(object), "a"); + + var instanceParam = declaringType.IsValueType && !declaringType.IsNullableType() + ? Expression.Unbox(instance, declaringType) + : Expression.Convert(instance, declaringType); + + var valueParam = Expression.Convert(argument, propertyInfo.PropertyType); + + var setterCall = Expression.Call(instanceParam, propertySetMethod, valueParam); + + return Expression.Lambda(setterCall, instance, argument).Compile(); + } + catch //fallback for Android + { + return (o, convertedValue) => + propertySetMethod.Invoke(o, new[] { convertedValue }); + } + } + + public override SetMemberDelegate CreateSetter(PropertyInfo propertyInfo) + { + try + { + var lambda = SetExpressionLambda(propertyInfo); + return lambda?.Compile(); + } + catch //fallback for Android + { + var mi = propertyInfo.GetSetMethod(nonPublic: true); + return (o, convertedValue) => + mi.Invoke(o, new[] { convertedValue }); + } + } + + public static Expression> SetExpressionLambda(PropertyInfo propertyInfo) + { + var mi = propertyInfo.GetSetMethod(nonPublic: true); + if (mi == null) return null; + + var instance = Expression.Parameter(typeof(T), "i"); + var argument = Expression.Parameter(typeof(object), "a"); + + var instanceType = typeof(T) != propertyInfo.DeclaringType + ? (Expression)Expression.TypeAs(instance, propertyInfo.DeclaringType) + : instance; + + var setterCall = Expression.Call( + instanceType, + mi, + Expression.Convert(argument, propertyInfo.PropertyType)); + + return Expression.Lambda> + ( + setterCall, instance, argument + ); + } + + + public override GetMemberDelegate CreateGetter(FieldInfo fieldInfo) + { + var fieldDeclaringType = fieldInfo.DeclaringType; + + var oInstanceParam = Expression.Parameter(typeof(object), "source"); + var instanceParam = GetCastOrConvertExpression(oInstanceParam, fieldDeclaringType); + + var exprCallFieldGetFn = Expression.Field(instanceParam, fieldInfo); + var oExprCallFieldGetFn = Expression.Convert(exprCallFieldGetFn, typeof(object)); + + var fieldGetterFn = Expression.Lambda + ( + oExprCallFieldGetFn, + oInstanceParam + ) + .Compile(); + + return fieldGetterFn; + } + + private static Expression GetCastOrConvertExpression(Expression expression, Type targetType) + { + Expression result; + var expressionType = expression.Type; + + if (targetType.IsAssignableFrom(expressionType)) + { + result = expression; + } + else + { + // Check if we can use the as operator for casting or if we must use the convert method + if (targetType.IsValueType && !targetType.IsNullableType()) + { + result = Expression.Convert(expression, targetType); + } + else + { + result = Expression.TypeAs(expression, targetType); + } + } + + return result; + } + + public override GetMemberDelegate CreateGetter(FieldInfo fieldInfo) + { + var instance = Expression.Parameter(typeof(T), "i"); + var field = typeof(T) != fieldInfo.DeclaringType + ? Expression.Field(Expression.TypeAs(instance, fieldInfo.DeclaringType), fieldInfo) + : Expression.Field(instance, fieldInfo); + var convertField = Expression.TypeAs(field, typeof(object)); + return Expression.Lambda>(convertField, instance).Compile(); + } + + private static readonly MethodInfo setFieldMethod = typeof(ExpressionReflectionOptimizer).GetStaticMethod(nameof(SetField)); + internal static void SetField(ref TValue field, TValue newValue) => field = newValue; + + public override SetMemberDelegate CreateSetter(FieldInfo fieldInfo) + { + var declaringType = fieldInfo.DeclaringType; + + var sourceParameter = Expression.Parameter(typeof(object), "source"); + var valueParameter = Expression.Parameter(typeof(object), "value"); + + var sourceExpression = declaringType.IsValueType && !declaringType.IsNullableType() + ? Expression.Unbox(sourceParameter, declaringType) + : GetCastOrConvertExpression(sourceParameter, declaringType); + + var fieldExpression = Expression.Field(sourceExpression, fieldInfo); + + var valueExpression = GetCastOrConvertExpression(valueParameter, fieldExpression.Type); + + var genericSetFieldMethodInfo = setFieldMethod.MakeGenericMethod(fieldExpression.Type); + + var setFieldMethodCallExpression = Expression.Call( + null, genericSetFieldMethodInfo, fieldExpression, valueExpression); + + var setterFn = Expression.Lambda( + setFieldMethodCallExpression, sourceParameter, valueParameter).Compile(); + + return setterFn; + } + + public override SetMemberDelegate CreateSetter(FieldInfo fieldInfo) + { + var instance = Expression.Parameter(typeof(T), "i"); + var argument = Expression.Parameter(typeof(object), "a"); + + var field = typeof(T) != fieldInfo.DeclaringType + ? Expression.Field(Expression.TypeAs(instance, fieldInfo.DeclaringType), fieldInfo) + : Expression.Field(instance, fieldInfo); + + var setterCall = Expression.Assign( + field, + Expression.Convert(argument, fieldInfo.FieldType)); + + return Expression.Lambda> + ( + setterCall, instance, argument + ).Compile(); + } + + public override SetMemberRefDelegate CreateSetterRef(FieldInfo fieldInfo) + { + var instance = Expression.Parameter(typeof(T).MakeByRefType(), "i"); + var argument = Expression.Parameter(typeof(object), "a"); + + var field = typeof(T) != fieldInfo.DeclaringType + ? Expression.Field(Expression.TypeAs(instance, fieldInfo.DeclaringType), fieldInfo) + : Expression.Field(instance, fieldInfo); + + var setterCall = Expression.Assign( + field, + Expression.Convert(argument, fieldInfo.FieldType)); + + return Expression.Lambda> + ( + setterCall, instance, argument + ).Compile(); + } + + public override bool IsDynamic(Assembly assembly) => RuntimeReflectionOptimizer.Provider.IsDynamic(assembly); + + public override EmptyCtorDelegate CreateConstructor(Type type) => RuntimeReflectionOptimizer.Provider.CreateConstructor(type); + } +} \ No newline at end of file diff --git a/src/ServiceStack.Text/RuntimeSerializableAttribute.cs b/src/ServiceStack.Text/RuntimeSerializableAttribute.cs index 165653781..ccd155802 100644 --- a/src/ServiceStack.Text/RuntimeSerializableAttribute.cs +++ b/src/ServiceStack.Text/RuntimeSerializableAttribute.cs @@ -3,13 +3,13 @@ namespace ServiceStack.Text { /// - /// Allow Type to be deserialized into late-bould object Types using __type info + /// Allow Type to be deserialized into late-bound object Types using __type info /// [AttributeUsage(AttributeTargets.Class)] public class RuntimeSerializableAttribute : Attribute {} /// - /// Allow Type to be deserialized into late-bould object Types using __type info + /// Allow Type to be deserialized into late-bound object Types using __type info /// public interface IRuntimeSerializable { } } \ No newline at end of file diff --git a/src/ServiceStack.Text/ServiceStack.Text.Core.csproj b/src/ServiceStack.Text/ServiceStack.Text.Core.csproj new file mode 100644 index 000000000..14f46e2db --- /dev/null +++ b/src/ServiceStack.Text/ServiceStack.Text.Core.csproj @@ -0,0 +1,29 @@ + + + ServiceStack.Text.Core + ServiceStack.Text + ServiceStack.Text + netstandard2.0;net6.0 + ServiceStack.Text .NET Standard 2.0 + + .NET's fastest JSON, JSV and CSV Text Serializers. Fast, Light, Resilient. + Contains ServiceStack's high-performance text-processing powers, for more info see: + https://github.com/ServiceStack/ServiceStack.Text + + JSON;Text;Serializer;CSV;JSV;HTTP;Auto Mapping;Dump;Reflection;JS;Utils;Fast + 1591 + + + $(DefineConstants);NETSTANDARD;NET6_0 + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ServiceStack.Text/ServiceStack.Text.csproj b/src/ServiceStack.Text/ServiceStack.Text.csproj index 7ad3b6c32..23980e788 100644 --- a/src/ServiceStack.Text/ServiceStack.Text.csproj +++ b/src/ServiceStack.Text/ServiceStack.Text.csproj @@ -1,54 +1,37 @@  - - net45;netstandard2.0 - ServiceStack.Text ServiceStack.Text - false - false - false - false - false - false - false - false - false - - - - true - true - - - - portable + ServiceStack.Text + net472;netstandard2.0;net6.0 + .NET's fastest JSON Serializer by ServiceStack + + .NET's fastest JSON, JSV and CSV Text Serializers. Fast, Light, Resilient. + Contains ServiceStack's high-performance text-processing powers, for more info see: + https://github.com/ServiceStack/ServiceStack.Text + + JSON;Text;Serializer;CSV;JSV;HTTP;Auto Mapping;Dump;Reflection;JS;Utils;Fast + 1591 - - - $(DefineConstants);NET45 - True - False - ../servicestack.snk - true + + $(DefineConstants);NETFX;NET472 - - - - - - - - - $(DefineConstants);NETSTANDARD2_0 + $(DefineConstants);NETCORE;NETSTANDARD;NETSTANDARD2_0 + + + $(DefineConstants);NETCORE;NET6_0;NET6_0_OR_GREATER - + + + + + + + - - - - - + + + + - - + \ No newline at end of file diff --git a/src/ServiceStack.Text/StreamExtensions.cs b/src/ServiceStack.Text/StreamExtensions.cs index 18cd4e8df..6ea626c7d 100644 --- a/src/ServiceStack.Text/StreamExtensions.cs +++ b/src/ServiceStack.Text/StreamExtensions.cs @@ -2,13 +2,16 @@ //License: https://raw.github.com/ServiceStack/ServiceStack/master/license.txt using System; +using System.Buffers.Text; using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.CompilerServices; +using System.Text; using System.Threading; using System.Threading.Tasks; using ServiceStack.Text; +using ServiceStack.Text.Pools; namespace ServiceStack { @@ -16,8 +19,7 @@ public static class StreamExtensions { public static long WriteTo(this Stream inStream, Stream outStream) { - var memoryStream = inStream as MemoryStream; - if (memoryStream != null) + if (inStream is MemoryStream memoryStream) { memoryStream.WriteTo(outStream); return memoryStream.Position; @@ -41,13 +43,11 @@ public static IEnumerable ReadLines(this Stream stream) if (stream == null) throw new ArgumentNullException(nameof(stream)); - using (var reader = new StreamReader(stream)) + using var reader = new StreamReader(stream); + string line; + while ((line = reader.ReadLine()) != null) { - string line; - while ((line = reader.ReadLine()) != null) - { - yield return line; - } + yield return line; } } @@ -55,27 +55,87 @@ public static IEnumerable ReadLines(this Stream stream) /// @jonskeet: Collection of utility methods which operate on streams. /// r285, February 26th 2009: http://www.yoda.arachsys.com/csharp/miscutil/ /// - const int DefaultBufferSize = 8 * 1024; + public const int DefaultBufferSize = 8 * 1024; + + /// + /// Reads the given stream up to the end, returning the data as a byte array. + /// + public static byte[] ReadFully(this Stream input) => ReadFully(input, DefaultBufferSize); /// /// Reads the given stream up to the end, returning the data as a byte - /// array. + /// array, using the given buffer size. /// - public static byte[] ReadFully(this Stream input) + public static byte[] ReadFully(this Stream input, int bufferSize) { - return ReadFully(input, DefaultBufferSize); + if (bufferSize < 1) + throw new ArgumentOutOfRangeException(nameof(bufferSize)); + + byte[] buffer = BufferPool.GetBuffer(bufferSize); + try + { + return ReadFully(input, buffer); + } + finally + { + BufferPool.ReleaseBufferToPool(ref buffer); + } + } + + /// + /// Reads the given stream up to the end, returning the data as a byte + /// array, using the given buffer for transferring data. Note that the + /// current contents of the buffer is ignored, so the buffer needn't + /// be cleared beforehand. + /// + public static byte[] ReadFully(this Stream input, byte[] buffer) + { + if (buffer == null) + throw new ArgumentNullException(nameof(buffer)); + + if (input == null) + throw new ArgumentNullException(nameof(input)); + + if (buffer.Length == 0) + throw new ArgumentException("Buffer has length of 0"); + + // We could do all our own work here, but using MemoryStream is easier + // and likely to be just as efficient. + using var tempStream = MemoryStreamFactory.GetStream(); + CopyTo(input, tempStream, buffer); + // No need to copy the buffer if it's the right size + if (tempStream.Length == tempStream.GetBuffer().Length) + { + return tempStream.GetBuffer(); + } + // Okay, make a copy that's the right size + return tempStream.ToArray(); } + /// + /// Reads the given stream up to the end, returning the data as a byte array. + /// + public static Task ReadFullyAsync(this Stream input, CancellationToken token=default) => + ReadFullyAsync(input, DefaultBufferSize, token); + /// /// Reads the given stream up to the end, returning the data as a byte /// array, using the given buffer size. /// - public static byte[] ReadFully(this Stream input, int bufferSize) + public static async Task ReadFullyAsync(this Stream input, int bufferSize, CancellationToken token=default) { if (bufferSize < 1) throw new ArgumentOutOfRangeException(nameof(bufferSize)); - return ReadFully(input, new byte[bufferSize]); + byte[] buffer = BufferPool.GetBuffer(bufferSize); + try + { + return await ReadFullyAsync(input, buffer, token); + } + finally + { + BufferPool.ReleaseBufferToPool(ref buffer); + } } /// @@ -84,7 +144,7 @@ public static byte[] ReadFully(this Stream input, int bufferSize) /// current contents of the buffer is ignored, so the buffer needn't /// be cleared beforehand. /// - public static byte[] ReadFully(this Stream input, byte[] buffer) + public static async Task ReadFullyAsync(this Stream input, byte[] buffer, CancellationToken token=default) { if (buffer == null) throw new ArgumentNullException(nameof(buffer)); @@ -97,19 +157,94 @@ public static byte[] ReadFully(this Stream input, byte[] buffer) // We could do all our own work here, but using MemoryStream is easier // and likely to be just as efficient. - using (var tempStream = MemoryStreamFactory.GetStream()) + using var tempStream = MemoryStreamFactory.GetStream(); + await CopyToAsync(input, tempStream, buffer, token); + // No need to copy the buffer if it's the right size + if (tempStream.Length == tempStream.GetBuffer().Length) { - CopyTo(input, tempStream, buffer); - // No need to copy the buffer if it's the right size - if (tempStream.Length == tempStream.GetBuffer().Length) - { - return tempStream.GetBuffer(); - } - // Okay, make a copy that's the right size - return tempStream.ToArray(); + return tempStream.GetBuffer(); + } + // Okay, make a copy that's the right size + return tempStream.ToArray(); + } + + /// + /// Reads the given stream up to the end, returning the MemoryStream Buffer as ReadOnlyMemory<byte>. + /// + public static ReadOnlyMemory ReadFullyAsMemory(this Stream input) => + ReadFullyAsMemory(input, DefaultBufferSize); + + /// + /// Reads the given stream up to the end, returning the MemoryStream Buffer as ReadOnlyMemory<byte>. + /// + public static ReadOnlyMemory ReadFullyAsMemory(this Stream input, int bufferSize) + { + byte[] buffer = BufferPool.GetBuffer(bufferSize); + try + { + return ReadFullyAsMemory(input, buffer); + } + finally + { + BufferPool.ReleaseBufferToPool(ref buffer); + } + } + + public static ReadOnlyMemory ReadFullyAsMemory(this Stream input, byte[] buffer) + { + if (buffer == null) + throw new ArgumentNullException(nameof(buffer)); + + if (input == null) + throw new ArgumentNullException(nameof(input)); + + if (buffer.Length == 0) + throw new ArgumentException("Buffer has length of 0"); + + var ms = new MemoryStream(); + CopyTo(input, ms, buffer); + return ms.GetBufferAsMemory(); + } + + /// + /// Reads the given stream up to the end, returning the MemoryStream Buffer as ReadOnlyMemory<byte>. + /// + public static Task> ReadFullyAsMemoryAsync(this Stream input, CancellationToken token=default) => + ReadFullyAsMemoryAsync(input, DefaultBufferSize, token); + + /// + /// Reads the given stream up to the end, returning the MemoryStream Buffer as ReadOnlyMemory<byte>. + /// + public static async Task> ReadFullyAsMemoryAsync(this Stream input, int bufferSize, CancellationToken token=default) + { + byte[] buffer = BufferPool.GetBuffer(bufferSize); + try + { + return await ReadFullyAsMemoryAsync(input, buffer, token); + } + finally + { + BufferPool.ReleaseBufferToPool(ref buffer); } } + public static async Task> ReadFullyAsMemoryAsync(this Stream input, byte[] buffer, CancellationToken token=default) + { + if (buffer == null) + throw new ArgumentNullException(nameof(buffer)); + + if (input == null) + throw new ArgumentNullException(nameof(input)); + + if (buffer.Length == 0) + throw new ArgumentException("Buffer has length of 0"); + + var ms = new MemoryStream(); + await CopyToAsync(input, ms, buffer, token); + return ms.GetBufferAsMemory(); + } + + /// /// Copies all the data from one stream into another. /// @@ -131,8 +266,8 @@ public static long CopyTo(this Stream input, Stream output, int bufferSize) } /// - /// Copies all the data from one stream into another, using the given - /// buffer for transferring data. Note that the current contents of + /// Copies all the data from one stream into another, using the given + /// buffer for transferring data. Note that the current contents of /// the buffer is ignored, so the buffer needn't be cleared beforehand. /// public static long CopyTo(this Stream input, Stream output, byte[] buffer) @@ -159,6 +294,35 @@ public static long CopyTo(this Stream input, Stream output, byte[] buffer) return total; } + /// + /// Copies all the data from one stream into another, using the given + /// buffer for transferring data. Note that the current contents of + /// the buffer is ignored, so the buffer needn't be cleared beforehand. + /// + public static async Task CopyToAsync(this Stream input, Stream output, byte[] buffer, CancellationToken token=default) + { + if (buffer == null) + throw new ArgumentNullException(nameof(buffer)); + + if (input == null) + throw new ArgumentNullException(nameof(input)); + + if (output == null) + throw new ArgumentNullException(nameof(output)); + + if (buffer.Length == 0) + throw new ArgumentException("Buffer has length of 0"); + + long total = 0; + int read; + while ((read = await input.ReadAsync(buffer, 0, buffer.Length, token)) > 0) + { + await output.WriteAsync(buffer, 0, read, token); + total += read; + } + return total; + } + /// /// Reads exactly the given number of bytes from the specified stream. /// If the end of the stream is reached before the specified amount @@ -268,35 +432,244 @@ public static byte[] Combine(this byte[] bytes, params byte[][] withBytes) public static int AsyncBufferSize = 81920; // CopyToAsync() default value [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Task WriteAsync(this Stream stream, byte[] bytes, CancellationToken token = default(CancellationToken)) => stream.WriteAsync(bytes, 0, bytes.Length, token); + public static Task WriteAsync(this Stream stream, ReadOnlyMemory value, CancellationToken token = default) => + MemoryProvider.Instance.WriteAsync(stream, value, token); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Task CopyToAsync(this Stream input, Stream output, CancellationToken token = default(CancellationToken)) => input.CopyToAsync(output, AsyncBufferSize, token); + public static Task WriteAsync(this Stream stream, byte[] bytes, CancellationToken token = default) => + MemoryProvider.Instance.WriteAsync(stream, bytes, token); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Task WriteAsync(this Stream stream, string text, CancellationToken token = default(CancellationToken)) => stream.WriteAsync(text.ToUtf8Bytes(), token); + public static Task CopyToAsync(this Stream input, Stream output, CancellationToken token = default) => input.CopyToAsync(output, AsyncBufferSize, token); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Task WriteAsync(this Stream stream, string text, CancellationToken token = default) => + MemoryProvider.Instance.WriteAsync(stream, text.AsSpan(), token); + + public static byte[] ToMd5Bytes(this Stream stream) + { +#if NET6_0_OR_GREATER + if (stream is MemoryStream ms) + return System.Security.Cryptography.MD5.HashData(ms.GetBufferAsSpan()); +#endif + if (stream.CanSeek) + { + stream.Position = 0; + } + return System.Security.Cryptography.MD5.Create().ComputeHash(stream); + } + + public static string ToMd5Hash(this Stream stream) => ToMd5Bytes(stream).ToHex(); + + public static string ToMd5Hash(this byte[] bytes) => + System.Security.Cryptography.MD5.Create().ComputeHash(bytes).ToHex(); + + /// + /// Returns bytes in publiclyVisible MemoryStream + /// + public static MemoryStream InMemoryStream(this byte[] bytes) + { + return new MemoryStream(bytes, 0, bytes.Length, writable: true, publiclyVisible: true); + } + + public static string ReadToEnd(this MemoryStream ms) => ReadToEnd(ms, JsConfig.UTF8Encoding); + public static string ReadToEnd(this MemoryStream ms, Encoding encoding) + { + ms.Position = 0; + +#if NETCORE + if (ms.TryGetBuffer(out var buffer)) + { + return encoding.GetString(buffer.Array, buffer.Offset, buffer.Count); + } +#else + try + { + return encoding.GetString(ms.GetBuffer(), 0, (int) ms.Length); + } + catch (UnauthorizedAccessException) + { + } +#endif + + Tracer.Instance.WriteWarning("MemoryStream wasn't created with a publiclyVisible:true byte[] buffer, falling back to slow impl"); + + using var reader = new StreamReader(ms, encoding, true, DefaultBufferSize, leaveOpen: true); + return reader.ReadToEnd(); + } + + public static ReadOnlyMemory GetBufferAsMemory(this MemoryStream ms) + { +#if NETCORE + if (ms.TryGetBuffer(out var buffer)) + { + return new ReadOnlyMemory(buffer.Array, buffer.Offset, buffer.Count); + } +#else + try + { + return new ReadOnlyMemory(ms.GetBuffer(), 0, (int) ms.Length); + } + catch (UnauthorizedAccessException) + { + } +#endif + + Tracer.Instance.WriteWarning("MemoryStream in GetBufferAsSpan() wasn't created with a publiclyVisible:true byte[] buffer, falling back to slow impl"); + return new ReadOnlyMemory(ms.ToArray()); + } + + public static ReadOnlySpan GetBufferAsSpan(this MemoryStream ms) + { +#if NETCORE + if (ms.TryGetBuffer(out var buffer)) + { + return new ReadOnlySpan(buffer.Array, buffer.Offset, buffer.Count); + } +#else + try + { + return new ReadOnlySpan(ms.GetBuffer(), 0, (int) ms.Length); + } + catch (UnauthorizedAccessException) + { + } +#endif + + Tracer.Instance.WriteWarning("MemoryStream in GetBufferAsSpan() wasn't created with a publiclyVisible:true byte[] buffer, falling back to slow impl"); + return new ReadOnlySpan(ms.ToArray()); + } + + public static byte[] GetBufferAsBytes(this MemoryStream ms) + { +#if NETCORE + if (ms.TryGetBuffer(out var buffer)) + { + return buffer.Array; + } +#else + try + { + return ms.GetBuffer(); + } + catch (UnauthorizedAccessException) + { + } +#endif + + Tracer.Instance.WriteWarning("MemoryStream in GetBufferAsBytes() wasn't created with a publiclyVisible:true byte[] buffer, falling back to slow impl"); + return ms.ToArray(); + } + + public static Task ReadToEndAsync(this MemoryStream ms) => ReadToEndAsync(ms, JsConfig.UTF8Encoding); + public static Task ReadToEndAsync(this MemoryStream ms, Encoding encoding) + { + ms.Position = 0; + +#if NETCORE + if (ms.TryGetBuffer(out var buffer)) + { + return encoding.GetString(buffer.Array, buffer.Offset, buffer.Count).InTask(); + } +#else + try + { + return encoding.GetString(ms.GetBuffer(), 0, (int) ms.Length).InTask(); + } + catch (UnauthorizedAccessException) + { + } +#endif + + Tracer.Instance.WriteWarning("MemoryStream in ReadToEndAsync() wasn't created with a publiclyVisible:true byte[] buffer, falling back to slow impl"); + + using var reader = new StreamReader(ms, encoding, true, DefaultBufferSize, leaveOpen: true); + return reader.ReadToEndAsync(); + } - public static string ToMd5Hash(this Stream stream) + public static string ReadToEnd(this Stream stream) => ReadToEnd(stream, JsConfig.UTF8Encoding); + public static string ReadToEnd(this Stream stream, Encoding encoding) { - var hash = System.Security.Cryptography.MD5.Create().ComputeHash(stream); - var sb = StringBuilderCache.Allocate(); - foreach (byte b in hash) + if (stream is MemoryStream ms) + return ms.ReadToEnd(); + + if (stream.CanSeek) + { + stream.Position = 0; + } + + using var reader = new StreamReader(stream, encoding, true, DefaultBufferSize, leaveOpen:true); + return reader.ReadToEnd(); + } + + public static Task ReadToEndAsync(this Stream stream) => ReadToEndAsync(stream, JsConfig.UTF8Encoding); + public static Task ReadToEndAsync(this Stream stream, Encoding encoding) + { + if (stream is MemoryStream ms) + return ms.ReadToEndAsync(encoding); + + if (stream.CanSeek) { - sb.Append(b.ToString("x2")); + stream.Position = 0; } - return StringBuilderCache.ReturnAndFree(sb); + + using var reader = new StreamReader(stream, encoding, true, DefaultBufferSize, leaveOpen:true); + return reader.ReadToEndAsync(); } - public static string ToMd5Hash(this byte[] bytes) + public static Task WriteToAsync(this MemoryStream stream, Stream output, CancellationToken token=default(CancellationToken)) => + WriteToAsync(stream, output, JsConfig.UTF8Encoding, token); + + public static async Task WriteToAsync(this MemoryStream stream, Stream output, Encoding encoding, CancellationToken token) { - var hash = System.Security.Cryptography.MD5.Create().ComputeHash(bytes); - var sb = StringBuilderCache.Allocate(); - foreach (byte b in hash) +#if NETCORE + if (stream.TryGetBuffer(out var buffer)) + { + await output.WriteAsync(buffer.Array, buffer.Offset, buffer.Count, token).ConfigAwait(); + return; + } +#else + try + { + await output.WriteAsync(stream.GetBuffer(), 0, (int) stream.Length, token).ConfigAwait(); + return; + } + catch (UnauthorizedAccessException) { - sb.Append(b.ToString("x2")); } - return StringBuilderCache.ReturnAndFree(sb); +#endif + Tracer.Instance.WriteWarning("MemoryStream in WriteToAsync() wasn't created with a publiclyVisible:true byte[] bufffer, falling back to slow impl"); + + var bytes = stream.ToArray(); + await output.WriteAsync(bytes, 0, bytes.Length, token).ConfigAwait(); + } + + public static Task WriteToAsync(this Stream stream, Stream output, CancellationToken token=default(CancellationToken)) => + WriteToAsync(stream, output, JsConfig.UTF8Encoding, token); + + + public static Task WriteToAsync(this Stream stream, Stream output, Encoding encoding, CancellationToken token) + { + if (stream is MemoryStream ms) + return ms.WriteToAsync(output, encoding, token); + + return stream.CopyToAsync(output, token); + } + + public static MemoryStream CopyToNewMemoryStream(this Stream stream) + { + var ms = MemoryStreamFactory.GetStream(); + stream.CopyTo(ms); + ms.Position = 0; + return ms; + } + + public static async Task CopyToNewMemoryStreamAsync(this Stream stream) + { + var ms = MemoryStreamFactory.GetStream(); + await stream.CopyToAsync(ms).ConfigAwait(); + ms.Position = 0; + return ms; } - } -} \ No newline at end of file +} diff --git a/src/ServiceStack.Text/StringBuilderCache.cs b/src/ServiceStack.Text/StringBuilderCache.cs index bfa501022..27d5712ea 100644 --- a/src/ServiceStack.Text/StringBuilderCache.cs +++ b/src/ServiceStack.Text/StringBuilderCache.cs @@ -67,7 +67,7 @@ public static string ReturnAndFree(StringBuilder sb) } } - //Use separate cache internally to avoid reallocations and cache misses + //Use separate cache internally to avoid re-allocations and cache misses internal static class StringBuilderThreadStatic { [ThreadStatic] diff --git a/src/ServiceStack.Text/StringExtensions.cs b/src/ServiceStack.Text/StringExtensions.cs index 782bf5984..79206d0b0 100644 --- a/src/ServiceStack.Text/StringExtensions.cs +++ b/src/ServiceStack.Text/StringExtensions.cs @@ -26,26 +26,6 @@ namespace ServiceStack { public static class StringExtensions { - public static T To(this string value) - { - return TypeSerializer.DeserializeFromString(value); - } - - public static T To(this string value, T defaultValue) - { - return String.IsNullOrEmpty(value) ? defaultValue : TypeSerializer.DeserializeFromString(value); - } - - public static T ToOrDefaultValue(this string value) - { - return String.IsNullOrEmpty(value) ? default(T) : TypeSerializer.DeserializeFromString(value); - } - - public static object To(this string value, Type type) - { - return TypeSerializer.DeserializeFromString(value, type); - } - /// /// Converts from base: 0 - 62 /// @@ -58,19 +38,19 @@ public static string BaseConvert(this string source, int from, int to) var chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; var len = source.Length; if (len == 0) - throw new Exception(Format("Parameter: '{0}' is not valid integer (in base {1}).", source, from)); + throw new Exception($"Parameter: '{source}' is not valid integer (in base {@from})."); var minus = source[0] == '-' ? "-" : ""; var src = minus == "" ? source : source.Substring(1); len = src.Length; if (len == 0) - throw new Exception(Format("Parameter: '{0}' is not valid integer (in base {1}).", source, from)); + throw new Exception($"Parameter: '{source}' is not valid integer (in base {@from})."); var d = 0; for (int i = 0; i < len; i++) // Convert to decimal { int c = chars.IndexOf(src[i]); if (c >= from) - throw new Exception(Format("Parameter: '{0}' is not valid integer (in base {1}).", source, from)); + throw new Exception($"Parameter: '{source}' is not valid integer (in base {@from})."); d = d * from + c; } if (to == 10 || d == 0) @@ -127,7 +107,8 @@ public static string DecodeJsv(this string value) public static string UrlEncode(this string text, bool upperCase=false) { - if (String.IsNullOrEmpty(text)) return text; + if (string.IsNullOrEmpty(text)) + return text; var sb = StringBuilderThreadStatic.Allocate(); var fmt = upperCase ? "X2" : "x2"; @@ -243,16 +224,24 @@ public static string ToRot13(this string value) return new string(array); } + private static char[] UrlPathDelims = new[] {'?', '#'}; + + public static string UrlWithTrailingSlash(this string url) + { + var endPos = url?.IndexOfAny(UrlPathDelims) ?? -1; + return endPos >= 0 + ? url.Substring(0, endPos).WithTrailingSlash() + url.Substring(endPos) + : url.WithTrailingSlash(); + } + public static string WithTrailingSlash(this string path) { - if (String.IsNullOrEmpty(path)) - throw new ArgumentNullException("path"); + if (path == null) + throw new ArgumentNullException(nameof(path)); + if (path == "") + return "/"; - if (path[path.Length - 1] != '/') - { - return path + "/"; - } - return path; + return path[path.Length - 1] != '/' ? path + "/" : path; } public static string AppendPath(this string uri, params string[] uriComponents) @@ -289,7 +278,9 @@ public static string AppendUrlPathsRaw(this string uri, params string[] uriCompo public static string FromUtf8Bytes(this byte[] bytes) { return bytes == null ? null - : Encoding.UTF8.GetString(bytes, 0, bytes.Length); + : bytes.Length > 3 && bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF + ? Encoding.UTF8.GetString(bytes, 3, bytes.Length - 3) + : Encoding.UTF8.GetString(bytes, 0, bytes.Length); } public static byte[] ToUtf8Bytes(this string value) @@ -322,6 +313,13 @@ public static byte[] ToUtf8Bytes(this double doubleVal) return FastToUtf8Bytes(doubleStr); } + public static string WithoutBom(this string value) + { + return value.Length > 0 && value[0] == 65279 + ? value.Substring(1) + : value; + } + // from JWT spec public static string ToBase64UrlSafe(this byte[] input) { @@ -332,6 +330,15 @@ public static string ToBase64UrlSafe(this byte[] input) return output; } + public static string ToBase64UrlSafe(this MemoryStream ms) + { + var output = Convert.ToBase64String(ms.GetBuffer(), 0, (int)ms.Length); + output = output.LeftPart('='); // Remove any trailing '='s + output = output.Replace('+', '-'); // 62nd char of encoding + output = output.Replace('/', '_'); // 63rd char of encoding + return output; + } + // from JWT spec public static byte[] FromBase64UrlSafe(this string input) { @@ -510,6 +517,16 @@ public static string ToJsv(this T obj) return TypeSerializer.SerializeToString(obj); } + public static string ToJsv(this T obj, Action configure) + { + var config = new Config(); + configure(config); + using (JsConfig.With(config)) + { + return ToJsv(obj); + } + } + public static string ToSafeJsv(this T obj) { return TypeSerializer.HasCircularReferences(obj) @@ -522,6 +539,21 @@ public static T FromJsv(this string jsv) return TypeSerializer.DeserializeFromString(jsv); } + public static T FromJsvSpan(this ReadOnlySpan jsv) + { + return TypeSerializer.DeserializeFromSpan(jsv); + } + + public static string ToJson(this T obj, Action configure) + { + var config = new Config(); + configure(config); + using (JsConfig.With(config)) + { + return ToJson(obj); + } + } + public static string ToJson(this T obj) { return JsConfig.PreferInterfaces @@ -541,11 +573,26 @@ public static T FromJson(this string json) return JsonSerializer.DeserializeFromString(json); } + public static T FromJsonSpan(this ReadOnlySpan json) + { + return JsonSerializer.DeserializeFromSpan(json); + } + public static string ToCsv(this T obj) { return CsvSerializer.SerializeToString(obj); } + public static string ToCsv(this T obj, Action configure) + { + var config = new Config(); + configure(config); + using (JsConfig.With(config)) + { + return ToCsv(obj); + } + } + public static T FromCsv(this string csv) { return CsvSerializer.DeserializeFromString(csv); @@ -560,6 +607,10 @@ public static string Fmt(this string text, params object[] args) { return Format(text, args); } + public static string Fmt(this string text, IFormatProvider provider, params object[] args) + { + return Format(provider, text, args); + } public static string Fmt(this string text, object arg1) { @@ -637,22 +688,22 @@ public static string ExtractContents(this string fromText, string startAfter, st public static string ExtractContents(this string fromText, string uniqueMarker, string startAfter, string endAt) { if (String.IsNullOrEmpty(uniqueMarker)) - throw new ArgumentNullException("uniqueMarker"); + throw new ArgumentNullException(nameof(uniqueMarker)); if (String.IsNullOrEmpty(startAfter)) - throw new ArgumentNullException("startAfter"); + throw new ArgumentNullException(nameof(startAfter)); if (String.IsNullOrEmpty(endAt)) - throw new ArgumentNullException("endAt"); + throw new ArgumentNullException(nameof(endAt)); if (String.IsNullOrEmpty(fromText)) return null; - var markerPos = fromText.IndexOf(uniqueMarker); + var markerPos = fromText.IndexOf(uniqueMarker, StringComparison.Ordinal); if (markerPos == -1) return null; - var startPos = fromText.IndexOf(startAfter, markerPos); + var startPos = fromText.IndexOf(startAfter, markerPos, StringComparison.Ordinal); if (startPos == -1) return null; startPos += startAfter.Length; - var endPos = fromText.IndexOf(endAt, startPos); + var endPos = fromText.IndexOf(endAt, startPos, StringComparison.Ordinal); if (endPos == -1) endPos = fromText.Length; return fromText.Substring(startPos, endPos - startPos); @@ -674,9 +725,11 @@ public static string Quoted(this string text) public static string StripQuotes(this string text) { - return String.IsNullOrEmpty(text) || text.Length < 2 + return string.IsNullOrEmpty(text) || text.Length < 2 ? text - : text[0] == '"' && text[text.Length - 1] == '"' + : (text[0] == '"' && text[text.Length - 1] == '"') || + (text[0] == '\'' && text[text.Length - 1] == '\'') || + (text[0] == '`' && text[text.Length - 1] == '`') ? text.Substring(1, text.Length - 2) : text; } @@ -702,7 +755,8 @@ public static string StripMarkdownMarkup(this string markdown) private const int LowerCaseOffset = 'a' - 'A'; public static string ToCamelCase(this string value) { - if (String.IsNullOrEmpty(value)) return value; + if (string.IsNullOrEmpty(value)) + return value; var len = value.Length; var newValue = new char[len]; @@ -728,7 +782,8 @@ public static string ToCamelCase(this string value) public static string ToPascalCase(this string value) { - if (String.IsNullOrEmpty(value)) return value; + if (string.IsNullOrEmpty(value)) + return value; if (value.IndexOf('_') >= 0) { @@ -736,6 +791,8 @@ public static string ToPascalCase(this string value) var sb = StringBuilderThreadStatic.Allocate(); foreach (var part in parts) { + if (string.IsNullOrEmpty(part)) + continue; var str = part.ToCamelCase(); sb.Append(char.ToUpper(str[0]) + str.SafeSubstring(1, str.Length)); } @@ -748,7 +805,7 @@ public static string ToPascalCase(this string value) public static string ToTitleCase(this string value) { - return PclExport.Instance.ToTitleCase(value); + return CultureInfo.InvariantCulture.TextInfo.ToTitleCase(value).Replace("_", String.Empty); } public static string ToLowercaseUnderscore(this string value) @@ -766,7 +823,7 @@ public static string ToLowercaseUnderscore(this string value) else { sb.Append("_"); - sb.Append(char.ToLowerInvariant(t)); + sb.Append(char.ToLower(t)); } } return StringBuilderThreadStatic.ReturnAndFree(sb); @@ -784,12 +841,13 @@ public static string ToUpperSafe(this string value) public static string SafeSubstring(this string value, int startIndex) { + if (String.IsNullOrEmpty(value)) return Empty; return SafeSubstring(value, startIndex, value.Length); } public static string SafeSubstring(this string value, int startIndex, int length) { - if (String.IsNullOrEmpty(value)) return Empty; + if (String.IsNullOrEmpty(value) || length <= 0) return Empty; if (startIndex < 0) startIndex = 0; if (value.Length >= (startIndex + length)) return value.Substring(startIndex, length); @@ -797,7 +855,10 @@ public static string SafeSubstring(this string value, int startIndex, int length return value.Length > startIndex ? value.Substring(startIndex) : Empty; } - public static string SubstringWithElipsis(this string value, int startIndex, int length) + [Obsolete("typo")] + public static string SubstringWithElipsis(this string value, int startIndex, int length) => SubstringWithEllipsis(value, startIndex, length); + + public static string SubstringWithEllipsis(this string value, int startIndex, int length) { var str = value.SafeSubstring(startIndex, length); return str.Length == length @@ -823,9 +884,13 @@ public static bool EndsWithInvariant(this string str, string endsWith) return str.EndsWith(endsWith, PclExport.Instance.InvariantComparison); } - private static readonly Regex InvalidVarCharsRegex = new Regex(@"[^A-Za-z0-9]", PclExport.Instance.RegexOptions); - private static readonly Regex SplitCamelCaseRegex = new Regex("([A-Z]|[0-9]+)", PclExport.Instance.RegexOptions); - private static readonly Regex HttpRegex = new Regex(@"^http://", + private static readonly Regex InvalidVarCharsRegex = new(@"[^A-Za-z0-9_]", RegexOptions.Compiled); + private static readonly Regex ValidVarCharsRegex = new(@"^[A-Za-z0-9_]+$", RegexOptions.Compiled); + private static readonly Regex InvalidVarRefCharsRegex = new(@"[^A-Za-z0-9._]", RegexOptions.Compiled); + private static readonly Regex ValidVarRefCharsRegex = new(@"^[A-Za-z0-9._]+$", RegexOptions.Compiled); + + private static readonly Regex SplitCamelCaseRegex = new("([A-Z]|[0-9]+)", RegexOptions.Compiled); + private static readonly Regex HttpRegex = new(@"^http://", PclExport.Instance.RegexOptions | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase); public static T ToEnum(this string value) @@ -859,7 +924,7 @@ public static string ToHttps(this string url) { if (url == null) { - throw new ArgumentNullException("url"); + throw new ArgumentNullException(nameof(url)); } return HttpRegex.Replace(url.Trim(), "https://"); } @@ -876,7 +941,7 @@ public static bool IsNullOrEmpty(this string value) public static bool EqualsIgnoreCase(this string value, string other) { - return String.Equals(value, other, StringComparison.CurrentCultureIgnoreCase); + return String.Equals(value, other, StringComparison.OrdinalIgnoreCase); } public static string ReplaceFirst(this string haystack, string needle, string replacement) @@ -887,19 +952,9 @@ public static string ReplaceFirst(this string haystack, string needle, string re return haystack.Substring(0, pos) + replacement + haystack.Substring(pos + needle.Length); } - public static string ReplaceAll(this string haystack, string needle, string replacement) - { - int pos; - // Avoid a possible infinite loop - if (needle == replacement) return haystack; - while ((pos = haystack.IndexOf(needle, StringComparison.Ordinal)) > 0) - { - haystack = haystack.Substring(0, pos) - + replacement - + haystack.Substring(pos + needle.Length); - } - return haystack; - } + [Obsolete("Use built-in string.Replace()")] + public static string ReplaceAll(this string haystack, string needle, string replacement) => + haystack.Replace(needle, replacement); public static bool ContainsAny(this string text, params string[] testMatches) { @@ -910,11 +965,23 @@ public static bool ContainsAny(this string text, params string[] testMatches) return false; } - public static string SafeVarName(this string text) + public static bool ContainsAny(this string text, string[] testMatches, StringComparison comparisonType) { - if (string.IsNullOrEmpty(text)) return null; - return InvalidVarCharsRegex.Replace(text, "_"); + foreach (var testMatch in testMatches) + { + if (text.IndexOf(testMatch, comparisonType) >= 0) return true; + } + return false; } + + public static bool IsValidVarName(this string name) => ValidVarCharsRegex.IsMatch(name); + public static bool IsValidVarRef(this string name) => ValidVarRefCharsRegex.IsMatch(name); + + public static string SafeVarName(this string text) => !string.IsNullOrEmpty(text) + ? InvalidVarCharsRegex.Replace(text, "_") : null; + + public static string SafeVarRef(this string text) => !string.IsNullOrEmpty(text) + ? InvalidVarRefCharsRegex.Replace(text, "_") : null; public static string Join(this List items) { @@ -980,87 +1047,37 @@ public static bool IsSystemType(this Type type) public static bool IsTuple(this Type type) => type.Name.StartsWith("Tuple`"); - public static bool IsInt(this string text) - { - if (string.IsNullOrEmpty(text)) return false; - int ret; - return int.TryParse(text, out ret); - } + public static bool IsInt(this string text) => !string.IsNullOrEmpty(text) && int.TryParse(text, out _); - public static int ToInt(this string text) - { - return text == null ? default(int) : Int32.Parse(text); - } + public static int ToInt(this string text) => text == null ? default(int) : int.Parse(text); - public static int ToInt(this string text, int defaultValue) - { - int ret; - return int.TryParse(text, out ret) ? ret : defaultValue; - } + public static int ToInt(this string text, int defaultValue) => int.TryParse(text, out var ret) ? ret : defaultValue; - public static long ToInt64(this string text) - { - return long.Parse(text); - } + public static long ToLong(this string text) => long.Parse(text); + public static long ToInt64(this string text) => long.Parse(text); - public static long ToInt64(this string text, long defaultValue) - { - long ret; - return long.TryParse(text, out ret) ? ret : defaultValue; - } + public static long ToLong(this string text, long defaultValue) => long.TryParse(text, out var ret) ? ret : defaultValue; + public static long ToInt64(this string text, long defaultValue) => long.TryParse(text, out var ret) ? ret : defaultValue; - public static float ToFloat(this string text) - { - return text == null ? default(float) : float.Parse(text); - } + public static float ToFloat(this string text) => text == null ? default(float) : float.Parse(text); - public static float ToFloatInvariant(this string text) - { - return text == null ? default(float) : float.Parse(text, CultureInfo.InvariantCulture); - } + public static float ToFloatInvariant(this string text) => text == null ? default(float) : float.Parse(text, CultureInfo.InvariantCulture); - public static float ToFloat(this string text, float defaultValue) - { - float ret; - return float.TryParse(text, out ret) ? ret : defaultValue; - } + public static float ToFloat(this string text, float defaultValue) => float.TryParse(text, out var ret) ? ret : defaultValue; - public static double ToDouble(this string text) - { - return text == null ? default(double) : double.Parse(text); - } + public static double ToDouble(this string text) => text == null ? default(double) : double.Parse(text); - public static double ToDoubleInvariant(this string text) - { - return text == null ? default(double) : double.Parse(text, CultureInfo.InvariantCulture); - } + public static double ToDoubleInvariant(this string text) => text == null ? default(double) : double.Parse(text, CultureInfo.InvariantCulture); - public static double ToDouble(this string text, double defaultValue) - { - double ret; - return double.TryParse(text, out ret) ? ret : defaultValue; - } + public static double ToDouble(this string text, double defaultValue) => double.TryParse(text, out var ret) ? ret : defaultValue; - public static decimal ToDecimal(this string text) - { - return text == null ? default(decimal) : decimal.Parse(text); - } + public static decimal ToDecimal(this string text) => text == null ? default(decimal) : decimal.Parse(text); - public static decimal ToDecimalInvariant(this string text) - { - return text == null ? default(decimal) : decimal.Parse(text, CultureInfo.InvariantCulture); - } + public static decimal ToDecimalInvariant(this string text) => text == null ? default(decimal) : decimal.Parse(text, CultureInfo.InvariantCulture); - public static decimal ToDecimal(this string text, decimal defaultValue) - { - decimal ret; - return decimal.TryParse(text, out ret) ? ret : defaultValue; - } + public static decimal ToDecimal(this string text, decimal defaultValue) => decimal.TryParse(text, out var ret) ? ret : defaultValue; - public static bool Matches(this string value, string pattern) - { - return value.Glob(pattern); - } + public static bool Matches(this string value, string pattern) => value.Glob(pattern); public static bool Glob(this string value, string pattern) { @@ -1172,7 +1189,7 @@ public static Dictionary ParseKeyValueText(this string text, stri var to = new Dictionary(); if (text == null) return to; - foreach (var parts in text.ReadLines().Select(line => line.SplitOnFirst(delimiter))) + foreach (var parts in text.ReadLines().Select(line => line.Trim().SplitOnFirst(delimiter))) { var key = parts[0].Trim(); if (key.Length == 0 || key.StartsWith("#")) continue; @@ -1182,6 +1199,21 @@ public static Dictionary ParseKeyValueText(this string text, stri return to; } + public static List> ParseAsKeyValues(this string text, string delimiter=" ") + { + var to = new List>(); + if (text == null) return to; + + foreach (var parts in text.ReadLines().Select(line => line.Trim().SplitOnFirst(delimiter))) + { + var key = parts[0].Trim(); + if (key.Length == 0 || key.StartsWith("#")) continue; + to.Add(new KeyValuePair(key, parts.Length == 2 ? parts[1].Trim() : null)); + } + + return to; + } + public static IEnumerable ReadLines(this string text) { string line; @@ -1192,18 +1224,8 @@ public static IEnumerable ReadLines(this string text) } } - public static int CountOccurrencesOf(this string text, char needle) - { - var chars = text.ToCharArray(); - var count = 0; - var length = chars.Length; - for (var n = length - 1; n >= 0; n--) - { - if (chars[n] == needle) - count++; - } - return count; - } + public static int CountOccurrencesOf(this string text, char needle) => + text.AsSpan().CountOccurrencesOf(needle); public static string NormalizeNewLines(this string text) { @@ -1246,5 +1268,60 @@ public static T FromXml(this string json) } #endif + public static string ToHex(this byte[] hashBytes, bool upper=false) + { + var len = hashBytes.Length * 2; + var chars = new char[len]; + var i = 0; + var index = 0; + for (i = 0; i < len; i += 2) + { + var b = hashBytes[index++]; + chars[i] = GetHexValue(b / 16, upper); + chars[i + 1] = GetHexValue(b % 16, upper); + } + return new string(chars); + } + + private static char GetHexValue(int i, bool upper) + { + if (i < 0 || i > 15) + throw new ArgumentOutOfRangeException(nameof(i), "must be between 0 and 15"); + + return i < 10 + ? (char) (i + '0') + : (char) (i - 10 + (upper ? 'A' : 'a')); + } + + } +} + +namespace ServiceStack.Text +{ + public static class StringTextExtensions + { + [Obsolete("Use ConvertTo")] + public static T To(this string value) + { + return TypeSerializer.DeserializeFromString(value); + } + + [Obsolete("Use ConvertTo")] + public static T To(this string value, T defaultValue) + { + return String.IsNullOrEmpty(value) ? defaultValue : TypeSerializer.DeserializeFromString(value); + } + + [Obsolete("Use ConvertTo")] + public static T ToOrDefaultValue(this string value) + { + return String.IsNullOrEmpty(value) ? default(T) : TypeSerializer.DeserializeFromString(value); + } + + [Obsolete("Use ConvertTo")] + public static object To(this string value, Type type) + { + return TypeSerializer.DeserializeFromString(value, type); + } } } diff --git a/src/ServiceStack.Text/StringSegment.cs b/src/ServiceStack.Text/StringSegment.cs deleted file mode 100644 index 501fb7c1f..000000000 --- a/src/ServiceStack.Text/StringSegment.cs +++ /dev/null @@ -1,439 +0,0 @@ -#if !NETSTANDARD2_0 - -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Runtime.CompilerServices; - -namespace ServiceStack.Text -{ - /// - /// An optimized representation of a substring. - /// - public struct StringSegment : IEquatable, IEquatable - { - /// - /// Initializes an instance of the struct. - /// - /// - /// The original . The includes the whole . - /// - public StringSegment(string buffer) - { - Buffer = buffer; - Offset = 0; - Length = buffer?.Length ?? 0; - } - - /// - /// Initializes an instance of the struct. - /// - /// The original used as buffer. - /// The offset of the segment within the . - /// The length of the segment. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public StringSegment(string buffer, int offset, int length) - { - // Validate arguments, check is minimal instructions with reduced branching for inlinable fast-path - // Negative values discovered though conversion to high values when converted to unsigned - // Failure should be rare and location determination and message is delegated to failure functions - if (buffer == null || (uint)offset > (uint)buffer.Length || (uint)length > (uint)(buffer.Length - offset)) - { - ThrowInvalidArguments(buffer, offset, length); - } - - Buffer = buffer; - Offset = offset; - Length = length; - } - - private static void ThrowInvalidArguments(string buffer, int offset, int length) - { - // Only have single throw in method so is marked as "does not return" and isn't inlined to caller - throw GetInvalidArgumentException(buffer, offset, length); - } - - private static Exception GetInvalidArgumentException(string buffer, int offset, int length) - { - if (buffer == null) - { - return new ArgumentNullException(nameof(buffer)); - } - - if (offset < 0) - { - return new ArgumentOutOfRangeException(nameof(offset)); - } - - if (length < 0) - { - return new ArgumentOutOfRangeException(nameof(length)); - } - - return new ArgumentException("invalid offset or length"); - } - - /// - /// Gets the buffer for this . - /// - public string Buffer { get; } - - /// - /// Gets the offset within the buffer for this . - /// - public int Offset { get; } - - /// - /// Gets the length of this . - /// - public int Length { get; } - - /// - /// Gets the value of this segment as a . - /// - public string Value - { - get - { - if (!HasValue) - { - return null; - } - else - { - return Buffer.Substring(Offset, Length); - } - } - } - - /// - /// Gets whether or not this contains a valid value. - /// - public bool HasValue - { - get { return Buffer != null; } - } - - /// - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - return obj is StringSegment && Equals((StringSegment)obj); - } - - /// - /// Indicates whether the current object is equal to another object of the same type. - /// - /// An object to compare with this object. - /// true if the current object is equal to the other parameter; otherwise, false. - public bool Equals(StringSegment other) - { - return Equals(other, StringComparison.Ordinal); - } - - - /// - /// Indicates whether the current object is equal to another object of the same type. - /// - /// An object to compare with this object. - /// One of the enumeration values that specifies the rules to use in the comparison. - /// true if the current object is equal to the other parameter; otherwise, false. - public bool Equals(StringSegment other, StringComparison comparisonType) - { - int textLength = other.Length; - if (Length != textLength) - { - return false; - } - - return string.Compare(Buffer, Offset, other.Buffer, other.Offset, textLength, comparisonType) == 0; - } - - /// - /// Checks if the specified is equal to the current . - /// - /// The to compare with the current . - /// true if the specified is equal to the current ; otherwise, false. - public bool Equals(string text) - { - return Equals(text, StringComparison.Ordinal); - } - - /// - /// Checks if the specified is equal to the current . - /// - /// The to compare with the current . - /// One of the enumeration values that specifies the rules to use in the comparison. - /// true if the specified is equal to the current ; otherwise, false. - public bool Equals(string text, StringComparison comparisonType) - { - if (text == null) - { - throw new ArgumentNullException(nameof(text)); - } - - int textLength = text.Length; - if (!HasValue || Length != textLength) - { - return false; - } - - return string.Compare(Buffer, Offset, text, 0, textLength, comparisonType) == 0; - } - - /// - /// - /// This GetHashCode is expensive since it allocates on every call. - /// However this is required to ensure we retain any behavior (such as hash code randomization) that - /// string.GetHashCode has. - /// - public override int GetHashCode() - { - if (!HasValue) - { - return 0; - } - else - { - return Value.GetHashCode(); - } - } - - /// - /// Checks if two specified have the same value. - /// - /// The first to compare, or null. - /// The second to compare, or null. - /// true if the value of is the same as the value of ; otherwise, false. - public static bool operator ==(StringSegment left, StringSegment right) - { - return left.Equals(right); - } - - /// - /// Checks if two specified have different values. - /// - /// The first to compare, or null. - /// The second to compare, or null. - /// true if the value of is different from the value of ; otherwise, false. - public static bool operator !=(StringSegment left, StringSegment right) - { - return !left.Equals(right); - } - - /// - /// Checks if the beginning of this matches the specified when compared using the specified . - /// - /// The to compare. - /// One of the enumeration values that specifies the rules to use in the comparison. - /// true if matches the beginning of this ; otherwise, false. - public bool StartsWith(string text, StringComparison comparisonType) - { - if (text == null) - { - throw new ArgumentNullException(nameof(text)); - } - - var textLength = text.Length; - if (!HasValue || Length < textLength) - { - return false; - } - - return string.Compare(Buffer, Offset, text, 0, textLength, comparisonType) == 0; - } - - /// - /// Checks if the end of this matches the specified when compared using the specified . - /// - /// The to compare. - /// One of the enumeration values that specifies the rules to use in the comparison. - /// true if matches the end of this ; otherwise, false. - public bool EndsWith(string text, StringComparison comparisonType) - { - if (text == null) - { - throw new ArgumentNullException(nameof(text)); - } - - var textLength = text.Length; - if (!HasValue || Length < textLength) - { - return false; - } - - return string.Compare(Buffer, Offset + Length - textLength, text, 0, textLength, comparisonType) == 0; - } - - /// - /// Retrieves a substring from this . - /// The substring starts at the position specified by and has the specified . - /// - /// The zero-based starting character position of a substring in this . - /// The number of characters in the substring. - /// A that is equivalent to the substring of length that begins at in this - public string Substring(int offset, int length) - { - if (!HasValue) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } - - if (offset < 0 || offset + length > Length) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } - - if (length < 0 || Offset + offset + length > Buffer.Length) - { - throw new ArgumentOutOfRangeException(nameof(length)); - } - - return Buffer.Substring(Offset + offset, length); - } - - /// - /// Retrieves a that represents a substring from this . - /// The starts at the position specified by and has the specified . - /// - /// The zero-based starting character position of a substring in this . - /// The number of characters in the substring. - /// A that is equivalent to the substring of length that begins at in this - public StringSegment Subsegment(int offset, int length) - { - if (!HasValue) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } - - if (offset < 0 || offset + length > Length) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } - - if (length < 0 || Offset + offset + length > Buffer.Length) - { - throw new ArgumentOutOfRangeException(nameof(length)); - } - - return new StringSegment(Buffer, Offset + offset, length); - } - - /// - /// Gets the zero-based index of the first occurrence of the character in this . - /// The search starts at and examines a specified number of character positions. - /// - /// The Unicode character to seek. - /// The zero-based index position at which the search starts. - /// The number of characters to examine. - /// The zero-based index position of from the beginning of the if that character is found, or -1 if it is not. - public int IndexOf(char c, int start, int count) - { - if (start < 0 || Offset + start > Buffer.Length) - { - throw new ArgumentOutOfRangeException(nameof(start)); - } - - if (count < 0 || Offset + start + count > Buffer.Length) - { - throw new ArgumentOutOfRangeException(nameof(count)); - } - var index = Buffer.IndexOf(c, start + Offset, count); - if (index != -1) - { - return index - Offset; - } - else - { - return index; - } - } - - /// - /// Gets the zero-based index of the first occurrence of the character in this . - /// The search starts at . - /// - /// The Unicode character to seek. - /// The zero-based index position at which the search starts. - /// The zero-based index position of from the beginning of the if that character is found, or -1 if it is not. - public int IndexOf(char c, int start) - { - return IndexOf(c, start, Length - start); - } - - /// - /// Gets the zero-based index of the first occurrence of the character in this . - /// - /// The Unicode character to seek. - /// The zero-based index position of from the beginning of the if that character is found, or -1 if it is not. - public int IndexOf(char c) - { - return IndexOf(c, 0, Length); - } - - /// - /// Removes all leading and trailing whitespaces. - /// - /// The trimmed . - public StringSegment Trim() - { - return TrimStart().TrimEnd(); - } - - /// - /// Removes all leading whitespaces. - /// - /// The trimmed . - public StringSegment TrimStart() - { - var trimmedStart = Offset; - while (trimmedStart < Offset + Length) - { - if (!char.IsWhiteSpace(Buffer, trimmedStart)) - { - break; - } - - trimmedStart++; - } - - return new StringSegment(Buffer, trimmedStart, Offset + Length - trimmedStart); - } - - /// - /// Removes all trailing whitespaces. - /// - /// The trimmed . - public StringSegment TrimEnd() - { - var trimmedEnd = Offset + Length - 1; - while (trimmedEnd >= Offset) - { - if (!char.IsWhiteSpace(Buffer, trimmedEnd)) - { - break; - } - - trimmedEnd--; - } - - return new StringSegment(Buffer, Offset, trimmedEnd - Offset + 1); - } - - /// - /// Returns the represented by this or String.Empty if the does not contain a value. - /// - /// The represented by this or String.Empty if the does not contain a value. - public override string ToString() - { - return Value ?? string.Empty; - } - } -} - -#endif \ No newline at end of file diff --git a/src/ServiceStack.Text/StringSegmentExtensions.cs b/src/ServiceStack.Text/StringSegmentExtensions.cs deleted file mode 100644 index 84a5c3225..000000000 --- a/src/ServiceStack.Text/StringSegmentExtensions.cs +++ /dev/null @@ -1,1209 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Runtime.CompilerServices; -using System.Globalization; -using System.IO; -using System.Security; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using ServiceStack.Text.Json; - -#if NETSTANDARD2_0 -using Microsoft.Extensions.Primitives; -#endif - -namespace ServiceStack.Text -{ - public static class StringSegmentExtensions - { - const string BadFormat = "Input string was not in a correct format."; - const string OverflowMessage = "Value was either too large or too small for an {0}."; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static StringSegment ToStringSegment(this string value) => new StringSegment(value); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsNullOrEmpty(this StringSegment value) => value.Buffer == null || value.Length == 0; - - public static bool IsNullOrWhiteSpace(this StringSegment value) - { - if (!value.HasValue) - return true; - for (int index = 0; index < value.Length; ++index) - { - if (!char.IsWhiteSpace(value.GetChar(index))) - return false; - } - return true; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Char GetChar(this StringSegment value, int index) => value.Buffer[value.Offset + index]; - - public static int IndexOfAny(this StringSegment value, char[] chars, int start, int count) - { - if (start < 0 || value.Offset + start > value.Buffer.Length) - throw new ArgumentOutOfRangeException(nameof(start)); - - if (count < 0 || value.Offset + start + count > value.Buffer.Length) - throw new ArgumentOutOfRangeException(nameof(count)); - - var index = value.Buffer.IndexOfAny(chars, start + value.Offset, count); - if (index != -1) - return index - value.Offset; - - return index; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int LastIndexOfAny(this StringSegment value, char[] anyOf) => value.LastIndexOfAny(anyOf, value.Length - 1, value.Length); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int LastIndexOfAny(this StringSegment value, char[] anyOf, int startIndex) => value.LastIndexOfAny(anyOf, startIndex, startIndex + 1); - - public static int LastIndexOfAny(this StringSegment value, char[] anyOf, int start, int count) - { - if (start < 0 || value.Offset - start > value.Buffer.Length) - throw new ArgumentOutOfRangeException(nameof(start)); - - if (count < 0 || value.Offset - start - count > value.Buffer.Length) - throw new ArgumentOutOfRangeException(nameof(count)); - - var index = value.Buffer.LastIndexOfAny(anyOf, start - value.Offset, count); - if (index != -1) - return index - value.Offset; - - return index; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int IndexOfAny(this StringSegment value, char[] chars) => value.IndexOfAny(chars, 0, value.Length); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int IndexOfAny(this StringSegment value, char[] chars, int start) => value.IndexOfAny(chars, start, value.Length - start); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string Substring(this StringSegment value, int pos) => value.Substring(pos, value.Length - pos); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool CompareIgnoreCase(this StringSegment value, string text) => value.Equals(text, StringComparison.OrdinalIgnoreCase); - - public static StringSegment FromCsvField(this StringSegment text) - { - return text.IsNullOrEmpty() || !text.StartsWith(CsvConfig.ItemDelimiterString, StringComparison.Ordinal) - ? text - : new StringSegment( - text.Subsegment(CsvConfig.ItemDelimiterString.Length, text.Length - CsvConfig.EscapedItemDelimiterString.Length) - .Value - .Replace(CsvConfig.EscapedItemDelimiterString, CsvConfig.ItemDelimiterString)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool ParseBoolean(this StringSegment value) - { - if (!value.TryParseBoolean(out bool result)) - throw new FormatException(BadFormat); - - return result; - } - - public static bool TryParseBoolean(this StringSegment value, out bool result) - { - result = false; - - if (value.CompareIgnoreCase(bool.TrueString)) - { - result = true; - return true; - } - - return value.CompareIgnoreCase(bool.FalseString); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool TryParseDecimal(this StringSegment value, out decimal result) - { - return decimal.TryParse(value.Value, NumberStyles.Float, CultureInfo.InvariantCulture, out result); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool TryParseFloat(this StringSegment value, out float result) - { - return float.TryParse(value.Value, NumberStyles.Float, CultureInfo.InvariantCulture, out result); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool TryParseDouble(this StringSegment value, out double result) - { - return double.TryParse(value.Value, NumberStyles.Float, CultureInfo.InvariantCulture, out result); - } - - enum ParseState - { - LeadingWhite, - Sign, - Number, - DecimalPoint, - FractionNumber, - Exponent, - ExponentSign, - ExponentValue, - TrailingWhite - } - - private static Exception CreateOverflowException(long maxValue) => - new OverflowException(string.Format(OverflowMessage, SignedMaxValueToIntType(maxValue))); - private static Exception CreateOverflowException(ulong maxValue) => - new OverflowException(string.Format(OverflowMessage, UnsignedMaxValueToIntType(maxValue))); - - private static string SignedMaxValueToIntType(long maxValue) - { - switch (maxValue) - { - case SByte.MaxValue: - return nameof(SByte); - case Int16.MaxValue: - return nameof(Int16); - case Int32.MaxValue: - return nameof(Int32); - case Int64.MaxValue: - return nameof(Int64); - default: - return "Unknown"; - } - } - - private static string UnsignedMaxValueToIntType(ulong maxValue) - { - switch (maxValue) - { - case Byte.MaxValue: - return nameof(Byte); - case UInt16.MaxValue: - return nameof(UInt16); - case UInt32.MaxValue: - return nameof(UInt32); - case UInt64.MaxValue: - return nameof(UInt64); - default: - return "Unknown"; - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static sbyte ParseSByte(this StringSegment value) => (sbyte)ParseSignedInteger(value, sbyte.MaxValue, sbyte.MinValue); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static byte ParseByte(this StringSegment value) => (byte)ParseUnsignedInteger(value, byte.MaxValue); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static short ParseInt16(this StringSegment value) => (short)ParseSignedInteger(value, short.MaxValue, short.MinValue); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ushort ParseUInt16(this StringSegment value) => (ushort)ParseUnsignedInteger(value, ushort.MaxValue); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int ParseInt32(this StringSegment value) => (int)ParseSignedInteger(value, Int32.MaxValue, Int32.MinValue); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static uint ParseUInt32(this StringSegment value) => (uint)ParseUnsignedInteger(value, UInt32.MaxValue); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static long ParseInt64(this StringSegment value) => ParseSignedInteger(value, Int64.MaxValue, Int64.MinValue); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ulong ParseUInt64(this StringSegment value) => ParseUnsignedInteger(value, UInt64.MaxValue); - - public static ulong ParseUnsignedInteger(this StringSegment value, ulong maxValue) - { - if (value.Length == 0) - throw new FormatException(BadFormat); - - ulong result = 0; - int i = 0; - var state = ParseState.LeadingWhite; - - while (i < value.Length) - { - var c = value.GetChar(i++); - - switch (state) - { - case ParseState.LeadingWhite: - if (JsonUtils.IsWhiteSpace(c)) - break; - if (c == '0') - { - state = ParseState.TrailingWhite; - } - else if (c > '0' && c <= '9') - { - result = (ulong)(c - '0'); - state = ParseState.Number; - } - else throw new FormatException(BadFormat); - break; - case ParseState.Number: - if (c >= '0' && c <= '9') - { - checked - { - result = 10 * result + (ulong)(c - '0'); - } - if (result > maxValue) //check only minvalue, because in absolute value it's greater than maxvalue - throw CreateOverflowException(maxValue); - } - else if (JsonUtils.IsWhiteSpace(c)) - { - state = ParseState.TrailingWhite; - } - else throw new FormatException(BadFormat); - break; - case ParseState.TrailingWhite: - if (JsonUtils.IsWhiteSpace(c)) - { - state = ParseState.TrailingWhite; - } - else throw new FormatException(BadFormat); - break; - } - } - - if (state != ParseState.Number && state != ParseState.TrailingWhite) - throw new FormatException(BadFormat); - - return result; - } - - public static long ParseSignedInteger(this StringSegment value, long maxValue, long minValue) - { - if (value.Buffer == null) - throw new ArgumentNullException(nameof(value)); - - if (value.Length == 0) - throw new FormatException(BadFormat); - - long result = 0; - int i = value.Offset; - int end = value.Offset + value.Length; - var state = ParseState.LeadingWhite; - bool negative = false; - - //skip leading whitespaces - while (i < end && JsonUtils.IsWhiteSpace(value.Buffer[i])) i++; - if (i == end) - throw new FormatException(BadFormat); - - while (i < end) - { - var c = value.Buffer[i++]; - - switch (state) - { - case ParseState.LeadingWhite: - if (c == '-') - { - negative = true; - state = ParseState.Sign; - } - else if (c == '0') - { - state = ParseState.TrailingWhite; - } - else if (c > '0' && c <= '9') - { - result = -(c - '0'); - state = ParseState.Number; - } - else throw new FormatException(BadFormat); - break; - case ParseState.Sign: - if (c == '0') - { - state = ParseState.TrailingWhite; - } - else if (c > '0' && c <= '9') - { - result = -(c - '0'); - state = ParseState.Number; - } - else throw new FormatException(BadFormat); - break; - case ParseState.Number: - if (c >= '0' && c <= '9') - { - checked - { - result = 10 * result - (c - '0'); - } - if (result < minValue) //check only minvalue, because in absolute value it's greater than maxvalue - throw CreateOverflowException(maxValue); - } - else if (JsonUtils.IsWhiteSpace(c)) - { - state = ParseState.TrailingWhite; - } - else throw new FormatException(BadFormat); - break; - case ParseState.TrailingWhite: - if (JsonUtils.IsWhiteSpace(c)) - { - state = ParseState.TrailingWhite; - } - else throw new FormatException(BadFormat); - break; - } - } - - if (state != ParseState.Number && state != ParseState.TrailingWhite) - throw new FormatException(BadFormat); - - if (negative) - return result; - - checked - { - result = -result; - } - - if (result > maxValue) - throw CreateOverflowException(maxValue); - - return result; - } - - public static object ParseSignedInteger(this StringSegment value) - { - var longValue = value.ParseInt64(); - if (longValue >= int.MinValue && longValue <= int.MaxValue) - return (int)longValue; - return longValue; - } - - public static decimal ParseDecimal(this StringSegment value, bool allowThousands = false) - { - if (value.Length == 0) - throw new FormatException(BadFormat); - - decimal result = 0; - ulong preResult = 0; - bool isLargeNumber = false; - int i = value.Offset; - int end = i + value.Length; - var state = ParseState.LeadingWhite; - bool negative = false; - bool noIntegerPart = false; - sbyte scale = 0; - - while (i < end) - { - var c = value.Buffer[i++]; - - switch (state) - { - case ParseState.LeadingWhite: - if (JsonUtils.IsWhiteSpace(c)) - break; - - if (c == '-') - { - negative = true; - state = ParseState.Sign; - } - else if (c == '.') - { - noIntegerPart = true; - state = ParseState.FractionNumber; - - if (i == end) - throw new FormatException(BadFormat); - } - else if (c == '0') - { - state = ParseState.DecimalPoint; - } - else if (c > '0' && c <= '9') - { - preResult = (ulong)(c - '0'); - state = ParseState.Number; - } - else throw new FormatException(BadFormat); - break; - case ParseState.Sign: - if (c == '.') - { - noIntegerPart = true; - state = ParseState.FractionNumber; - - if (i == end) - throw new FormatException(BadFormat); - } - else if (c == '0') - { - state = ParseState.DecimalPoint; - } - else if (c > '0' && c <= '9') - { - preResult = (ulong)(c - '0'); - state = ParseState.Number; - } - else throw new FormatException(BadFormat); - break; - case ParseState.Number: - if (c == '.') - { - state = ParseState.FractionNumber; - } - else if (c >= '0' && c <= '9') - { - if (isLargeNumber) - { - checked - { - result = 10 * result + (c - '0'); - } - } - else - { - preResult = 10 * preResult + (ulong)(c - '0'); - if (preResult > ulong.MaxValue / 10 - 10) - { - isLargeNumber = true; - result = preResult; - } - } - } - else if (JsonUtils.IsWhiteSpace(c)) - { - state = ParseState.TrailingWhite; - } - else if (allowThousands && c == ',') - { - } - else throw new FormatException(BadFormat); - break; - case ParseState.DecimalPoint: - if (c == '.') - { - state = ParseState.FractionNumber; - } - else throw new FormatException(BadFormat); - break; - case ParseState.FractionNumber: - if (JsonUtils.IsWhiteSpace(c)) - { - if (noIntegerPart) - throw new FormatException(BadFormat); - state = ParseState.TrailingWhite; - } - else if (c == 'e' || c == 'E') - { - if (noIntegerPart && scale == 0) - throw new FormatException(BadFormat); - state = ParseState.Exponent; - } - else if (c >= '0' && c <= '9') - { - if (isLargeNumber) - { - checked - { - result = 10 * result + (c - '0'); - } - } - else - { - preResult = 10 * preResult + (ulong)(c - '0'); - if (preResult > ulong.MaxValue / 10 - 10) - { - isLargeNumber = true; - result = preResult; - } - } - scale++; - } - else throw new FormatException(BadFormat); - break; - case ParseState.Exponent: - bool expNegative = false; - if (c == '-') - { - if (i == end) - throw new FormatException(BadFormat); - - expNegative = true; - c = value.Buffer[i++]; - } - else if (c == '+') - { - if (i == end) - throw new FormatException(BadFormat); - c = value.Buffer[i++]; - } - - //skip leading zeroes - while (c == '0' && i < end) c = value.Buffer[i++]; - - if (c > '0' && c <= '9') - { - var exp = new StringSegment(value.Buffer, i - 1, end - i + 1).ParseSByte(); - if (!expNegative) - { - exp = (sbyte)-exp; - } - - if (exp >= 0 || scale > -exp) - { - scale += exp; - } - else - { - for (int j = 0; j < -exp - scale; j++) - { - if (isLargeNumber) - { - checked - { - result = 10 * result; - } - } - else - { - preResult = 10 * preResult; - if (preResult > ulong.MaxValue / 10) - { - isLargeNumber = true; - result = preResult; - } - } - } - scale = 0; - } - - //set i to end of string, because ParseInt16 eats number and all trailing whites - i = end; - } - else throw new FormatException(BadFormat); - break; - case ParseState.TrailingWhite: - if (!JsonUtils.IsWhiteSpace(c)) - throw new FormatException(BadFormat); - break; - } - } - - if (!isLargeNumber) - { - var mid = (int)(preResult >> 32); - var lo = (int)(preResult & 0xffffffff); - result = new decimal(lo, mid, 0, negative, (byte)scale); - } - else - { - var bits = decimal.GetBits(result); - result = new decimal(bits[0], bits[1], bits[2], negative, (byte)scale); - } - - return result; - } - private static readonly byte[] lo16 = { - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 0, 1, - 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, - 255, 255, 255, 255, 255, 10, 11, 12, 13, 14, - 15, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 10, 11, 12, - 13, 14, 15 - }; - - private static readonly byte[] hi16 = { - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 0, 16, - 32, 48, 64, 80, 96, 112, 128, 144, 255, 255, - 255, 255, 255, 255, 255, 160, 176, 192, 208, 224, - 240, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 160, 176, 192, - 208, 224, 240 - }; - - public static Guid ParseGuid(this StringSegment value) - { - if (value.Buffer == null) - throw new ArgumentNullException(); - - if (value.Length == 0) - throw new FormatException(BadFormat); - - //Guid can be in one of 3 forms: - //1. General `{dddddddd-dddd-dddd-dddd-dddddddddddd}` or `(dddddddd-dddd-dddd-dddd-dddddddddddd)` 8-4-4-4-12 chars - //2. Hex `{0xdddddddd,0xdddd,0xdddd,{0xdd,0xdd,0xdd,0xdd,0xdd,0xdd,0xdd,0xdd}}` 8-4-4-8x2 chars - //3. No style `dddddddddddddddddddddddddddddddd` 32 chars - - int i = value.Offset; - int end = value.Offset + value.Length; - while (i < end && JsonUtils.IsWhiteSpace(value.Buffer[i])) i++; - - if (i == end) - throw new FormatException(BadFormat); - - var result = ParseGeneralStyleGuid(new StringSegment(value.Buffer, i, end - i), out var guidLen); - i += guidLen; - - while (i < end && JsonUtils.IsWhiteSpace(value.Buffer[i])) i++; - - if (i < end) - throw new FormatException(BadFormat); - - return result; - } - - private static Guid ParseGeneralStyleGuid(StringSegment value, out int len) - { - var buf = value.Buffer; - var n = value.Offset; - - int dash = 0; - len = 32; - bool hasParentesis = false; - - if (value.Length < len) - throw new FormatException(BadFormat); - - var cs = value.GetChar(0); - if (cs == '{' || cs == '(') - { - n++; - len += 2; - hasParentesis = true; - - if (buf[8 + n] != '-') - throw new FormatException(BadFormat); - } - - if (buf[8 + n] == '-') - { - if (buf[13 + n] != '-' - || buf[18 + n] != '-' - || buf[23 + n] != '-') - throw new FormatException(BadFormat); - - len += 4; - dash = 1; - } - - if (value.Length < len) - throw new FormatException(BadFormat); - - if (hasParentesis) - { - var ce = buf[value.Offset + len - 1]; - - if ((cs != '{' || ce != '}') && (cs != '(' || ce != ')')) - throw new FormatException(BadFormat); - } - - int a; - short b, c; - byte d, e, f, g, h, i, j, k; - - byte a1 = ParseHexByte(buf[n], buf[n + 1]); - n += 2; - byte a2 = ParseHexByte(buf[n], buf[n + 1]); - n += 2; - byte a3 = ParseHexByte(buf[n], buf[n + 1]); - n += 2; - byte a4 = ParseHexByte(buf[n], buf[n + 1]); - a = (a1 << 24) + (a2 << 16) + (a3 << 8) + a4; - n += 2 + dash; - - byte b1 = ParseHexByte(buf[n], buf[n + 1]); - n += 2; - byte b2 = ParseHexByte(buf[n], buf[n + 1]); - b = (short)((b1 << 8) + b2); - n += 2 + dash; - - byte c1 = ParseHexByte(buf[n], buf[n + 1]); - n += 2; - byte c2 = ParseHexByte(buf[n], buf[n + 1]); - c = (short)((c1 << 8) + c2); - n += 2 + dash; - - d = ParseHexByte(buf[n], buf[n + 1]); - n += 2; - e = ParseHexByte(buf[n], buf[n + 1]); - n += 2 + dash; - - f = ParseHexByte(buf[n], buf[n + 1]); - n += 2; - g = ParseHexByte(buf[n], buf[n + 1]); - n += 2; - h = ParseHexByte(buf[n], buf[n + 1]); - n += 2; - i = ParseHexByte(buf[n], buf[n + 1]); - n += 2; - j = ParseHexByte(buf[n], buf[n + 1]); - n += 2; - k = ParseHexByte(buf[n], buf[n + 1]); - - return new Guid(a, b, c, d, e, f, g, h, i, j, k); - } - - private static byte ParseHexByte(char c1, char c2) - { - try - { - byte lo = lo16[c2]; - byte hi = hi16[c1]; - - if (lo == 255 || hi == 255) - throw new FormatException(BadFormat); - - return (byte)(hi + lo); - } - catch (IndexOutOfRangeException) - { - throw new FormatException(BadFormat); - } - } - - private static readonly char[] CRLF = {'\r', '\n'}; - - public static bool TryReadLine(this StringSegment text, out StringSegment line, ref int startIndex) - { - if (startIndex >= text.Length) - { - line = TypeConstants.EmptyStringSegment; - return false; - } - - var nextLinePos = text.IndexOfAny(CRLF, startIndex); - if (nextLinePos == -1) - { - var nextLine = text.Subsegment(startIndex, text.Length - startIndex); - startIndex = text.Length; - line = nextLine; - return true; - } - else - { - var nextLine = text.Subsegment(startIndex, nextLinePos - startIndex); - - startIndex = nextLinePos + 1; - - if (text.GetChar(nextLinePos) == '\r' && text.Length > nextLinePos + 1 && text.GetChar(nextLinePos + 1) == '\n') - startIndex += 1; - - line = nextLine; - return true; - } - } - - public static bool TryReadPart(this StringSegment text, string needle, out StringSegment part, ref int startIndex) - { - if (startIndex >= text.Length) - { - part = TypeConstants.EmptyStringSegment; - return false; - } - - var nextPartPos = text.IndexOf(needle, startIndex); - if (nextPartPos == -1) - { - var nextPart = text.Subsegment(startIndex, text.Length - startIndex); - startIndex = text.Length; - part = nextPart; - return true; - } - else - { - var nextPart = text.Subsegment(startIndex, nextPartPos - startIndex); - startIndex = nextPartPos + needle.Length; - part = nextPart; - return true; - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int IndexOf(this StringSegment text, string needle) => text.IndexOf(needle, 0, text.Length); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int IndexOf(this StringSegment text, string needle, int start) => text.IndexOf(needle, start, text.Length - start); - - public static int IndexOf(this StringSegment text, string needle, int start, int count) - { - if (start < 0 || text.Offset + start > text.Buffer.Length) - throw new ArgumentOutOfRangeException(nameof(start)); - if (count < 0 || text.Offset + start + count > text.Buffer.Length) - throw new ArgumentOutOfRangeException(nameof(count)); - int num = text.Buffer.IndexOf(needle, start + text.Offset, count, StringComparison.Ordinal); - if (num != -1) - return num - text.Offset; - return num; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int LastIndexOf(this StringSegment text, char needle) => text.LastIndexOf(needle, text.Length - 1, text.Length); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int LastIndexOf(this StringSegment text, char needle, int start) => text.LastIndexOf(needle, start, start + 1); - - public static int LastIndexOf(this StringSegment text, char needle, int start, int count) - { - if (text.Length == 0) - return -1; - if (start < 0 || start >= text.Length) - throw new ArgumentOutOfRangeException(nameof(start)); - if (count < 0 || count - 1 > start) - throw new ArgumentOutOfRangeException(nameof(count)); - - int num = text.Buffer.LastIndexOf(needle, start + text.Offset, count); - if (num != -1) - return num - text.Offset; - return num; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int LastIndexOf(this StringSegment text, string needle) => text.LastIndexOf(needle, text.Length - 1, text.Length); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int LastIndexOf(this StringSegment text, string needle, int start) => text.LastIndexOf(needle, start, start + 1); - - public static int LastIndexOf(this StringSegment text, string needle, int start, int count) - { - if (text.Length == 0) - return -1; - if (start < 0 || start >= text.Length) - throw new ArgumentOutOfRangeException(nameof(start)); - if (count < 0 || count - 1 > start) - throw new ArgumentOutOfRangeException(nameof(count)); - - int num = text.Buffer.LastIndexOf(needle, start + text.Offset, count, StringComparison.Ordinal); - if (num != -1) - return num - text.Offset; - return num; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static StringSegment Advance(this StringSegment text, int to) => text.Subsegment(to, text.Length - to); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static StringSegment Subsegment(this StringSegment text, int startPos) => text.Subsegment(startPos, text.Length - startPos); - - public static StringSegment LeftPart(this StringSegment strVal, char needle) - { - if (!strVal.HasValue) return strVal; - var pos = strVal.IndexOf(needle); - return pos == -1 - ? strVal - : strVal.Subsegment(0, pos); - } - - public static StringSegment LeftPart(this StringSegment strVal, string needle) - { - if (!strVal.HasValue) return strVal; - var pos = strVal.IndexOf(needle); - return pos == -1 - ? strVal - : strVal.Subsegment(0, pos); - } - - public static StringSegment RightPart(this StringSegment strVal, char needle) - { - if (!strVal.HasValue) return strVal; - var pos = strVal.IndexOf(needle); - return pos == -1 - ? strVal - : strVal.Subsegment(pos + 1); - } - - public static StringSegment RightPart(this StringSegment strVal, string needle) - { - if (!strVal.HasValue) return strVal; - var pos = strVal.IndexOf(needle); - return pos == -1 - ? strVal - : strVal.Subsegment(pos + needle.Length); - } - - public static StringSegment LastLeftPart(this StringSegment strVal, char needle) - { - if (!strVal.HasValue) return strVal; - var pos = strVal.LastIndexOf(needle); - return pos == -1 - ? strVal - : strVal.Subsegment(0, pos); - } - - public static StringSegment LastLeftPart(this StringSegment strVal, string needle) - { - if (!strVal.HasValue) return strVal; - var pos = strVal.LastIndexOf(needle); - return pos == -1 - ? strVal - : strVal.Subsegment(0, pos); - } - - public static StringSegment LastRightPart(this StringSegment strVal, char needle) - { - if (!strVal.HasValue) return strVal; - var pos = strVal.LastIndexOf(needle); - return pos == -1 - ? strVal - : strVal.Subsegment(pos + 1); - } - - public static StringSegment LastRightPart(this StringSegment strVal, string needle) - { - if (!strVal.HasValue) return strVal; - var pos = strVal.LastIndexOf(needle); - return pos == -1 - ? strVal - : strVal.Subsegment(pos + needle.Length); - } - - public static StringSegment[] SplitOnFirst(this StringSegment strVal, char needle) - { - if (!strVal.HasValue) return TypeConstants.EmptyStringSegmentArray; - var pos = strVal.IndexOf(needle); - return pos == -1 - ? new[] { strVal } - : new[] { strVal.Subsegment(0, pos), strVal.Subsegment(pos + 1) }; - } - - public static StringSegment[] SplitOnFirst(this StringSegment strVal, string needle) - { - if (!strVal.HasValue) return TypeConstants.EmptyStringSegmentArray; - var pos = strVal.IndexOf(needle); - return pos == -1 - ? new[] { strVal } - : new[] { strVal.Subsegment(0, pos), strVal.Subsegment(pos + needle.Length) }; - } - - public static StringSegment[] SplitOnLast(this StringSegment strVal, char needle) - { - if (!strVal.HasValue) return TypeConstants.EmptyStringSegmentArray; - var pos = strVal.LastIndexOf(needle); - return pos == -1 - ? new[] { strVal } - : new[] { strVal.Subsegment(0, pos), strVal.Subsegment(pos + 1) }; - } - - public static StringSegment[] SplitOnLast(this StringSegment strVal, string needle) - { - if (!strVal.HasValue) return TypeConstants.EmptyStringSegmentArray; - var pos = strVal.LastIndexOf(needle); - return pos == -1 - ? new[] { strVal } - : new[] { strVal.Subsegment(0, pos), strVal.Subsegment(pos + needle.Length) }; - } - - public static StringSegment WithoutExtension(this StringSegment filePath) - { - if (filePath.IsNullOrEmpty()) - return TypeConstants.EmptyStringSegment; - - var extPos = filePath.LastIndexOf('.'); - if (extPos == -1) return filePath; - - var dirPos = filePath.LastIndexOfAny(PclExport.DirSeps); - return extPos > dirPos ? filePath.Subsegment(0, extPos) : filePath; - } - - public static StringSegment GetExtension(this StringSegment filePath) - { - if (filePath.IsNullOrEmpty()) - return TypeConstants.EmptyStringSegment; - - var extPos = filePath.LastIndexOf('.'); - return extPos == -1 ? TypeConstants.EmptyStringSegment : filePath.Subsegment(extPos); - } - - public static StringSegment ParentDirectory(this StringSegment filePath) - { - if (filePath.IsNullOrEmpty()) - return TypeConstants.EmptyStringSegment; - - var dirSep = filePath.IndexOf(PclExport.Instance.DirSep) != -1 - ? PclExport.Instance.DirSep - : filePath.IndexOf(PclExport.Instance.AltDirSep) != -1 - ? PclExport.Instance.AltDirSep - : (char)0; - - return dirSep == 0 ? TypeConstants.EmptyStringSegment : filePath.TrimEnd(dirSep).SplitOnLast(dirSep)[0]; - } - - public static StringSegment TrimEnd(this StringSegment value, params char[] trimChars) - { - if (trimChars == null || trimChars.Length == 0) - return value.TrimHelper(1); - return value.TrimHelper(trimChars, 1); - } - - private static StringSegment TrimHelper(this StringSegment value, int trimType) - { - int end = value.Length - 1; - int start = 0; - if (trimType != 1) - { - start = 0; - while (start < value.Length && char.IsWhiteSpace(value.GetChar(start))) - ++start; - } - if (trimType != 0) - { - end = value.Length - 1; - while (end >= start && char.IsWhiteSpace(value.GetChar(end))) - --end; - } - return value.CreateTrimmedString(start, end); - } - - private static StringSegment TrimHelper(this StringSegment value, char[] trimChars, int trimType) - { - int end = value.Length - 1; - int start = 0; - if (trimType != 1) - { - for (start = 0; start < value.Length; ++start) - { - char ch = value.GetChar(start); - int index = 0; - while (index < trimChars.Length && (int)trimChars[index] != (int)ch) - ++index; - if (index == trimChars.Length) - break; - } - } - if (trimType != 0) - { - for (end = value.Length - 1; end >= start; --end) - { - char ch = value.GetChar(end); - int index = 0; - while (index < trimChars.Length && (int)trimChars[index] != (int)ch) - ++index; - if (index == trimChars.Length) - break; - } - } - return value.CreateTrimmedString(start, end); - } - - private static StringSegment CreateTrimmedString(this StringSegment value, int start, int end) - { - int length = end - start + 1; - if (length == value.Length) - return value; - if (length == 0) - return TypeConstants.EmptyStringSegment; - return value.Subsegment(start, length); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool StartsWith(this StringSegment text, string value) => text.StartsWith(value, StringComparison.OrdinalIgnoreCase); - - public static bool StartsWith(this StringSegment text, string value, StringComparison comparisonType) - { - if (value == null) - throw new ArgumentNullException(nameof(value)); - - var textLength = value.Length; - if (!text.HasValue || text.Length < textLength) - return false; - - return string.Compare(text.Buffer, text.Offset, value, 0, textLength, comparisonType) == 0; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool EndsWith(this StringSegment text, string value) => text.EndsWith(value, StringComparison.OrdinalIgnoreCase); - - public static bool EndsWith(this StringSegment text, string value, StringComparison comparisonType) - { - if (value == null) - throw new ArgumentNullException(nameof(value)); - - var textLength = value.Length; - if (!text.HasValue || text.Length < textLength) - return false; - - return string.Compare(text.Buffer, text.Offset + text.Length - textLength, value, 0, textLength, comparisonType) == 0; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static StringSegment SafeSubsegment(this StringSegment value, int startIndex) => SafeSubsegment(value, startIndex, value.Length); - - public static StringSegment SafeSubsegment(this StringSegment value, int startIndex, int length) - { - if (IsNullOrEmpty(value)) return TypeConstants.EmptyStringSegment; - if (startIndex < 0) startIndex = 0; - if (value.Length >= startIndex + length) - return value.Subsegment(startIndex, length); - - return value.Length > startIndex ? value.Subsegment(startIndex) : TypeConstants.EmptyStringSegment; - } - - public static string SubstringWithElipsis(this StringSegment value, int startIndex, int length) - { - var str = value.SafeSubsegment(startIndex, length); - return str.Length == length - ? str.Value + "..." - : str.Value; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static List ToStringList(this IEnumerable from) - { - var to = new List(); - if (from != null) - { - foreach (var item in from) - { - to.Add(item.ToString()); - } - } - return to; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool EqualsIgnoreCase(this StringSegment value, string other) => value.Equals(other, StringComparison.OrdinalIgnoreCase); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool StartsWithIgnoreCase(this StringSegment value, string other) => value.StartsWith(other, StringComparison.OrdinalIgnoreCase); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool EndsWithIgnoreCase(this StringSegment value, string other) => value.EndsWith(other, StringComparison.OrdinalIgnoreCase); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static byte[] ToUtf8Bytes(this StringSegment value) => Encoding.UTF8.GetBytes(value.Buffer.ToCharArray(value.Offset, value.Length)); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Task WriteAsync(this Stream stream, StringSegment value, CancellationToken token = default(CancellationToken)) - { - var bytes = value.ToUtf8Bytes(); - return stream.WriteAsync(bytes, 0, bytes.Length, token); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static StringSegment SafeSubstring(this StringSegment value, int startIndex) => SafeSubstring(value, startIndex, value.Length); - - public static StringSegment SafeSubstring(this StringSegment value, int startIndex, int length) - { - if (value.IsNullOrEmpty()) return TypeConstants.EmptyStringSegment; - if (startIndex < 0) startIndex = 0; - if (value.Length >= (startIndex + length)) - return value.Subsegment(startIndex, length); - - return value.Length > startIndex ? value.Subsegment(startIndex) : TypeConstants.EmptyStringSegment; - } - - } -} diff --git a/src/ServiceStack.Text/StringSpanExtensions.cs b/src/ServiceStack.Text/StringSpanExtensions.cs new file mode 100644 index 000000000..36a200900 --- /dev/null +++ b/src/ServiceStack.Text/StringSpanExtensions.cs @@ -0,0 +1,686 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace ServiceStack.Text +{ + /// + /// Helpful extensions on ReadOnlySpan<char> + /// Previous extensions on StringSegment available from: https://gist.github.com/mythz/9825689f0db7464d1d541cb62954614c + /// + public static class StringSpanExtensions + { + /// + /// Returns null if Length == 0, string.Empty if value[0] == NonWidthWhitespace, otherwise returns value.ToString() + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string Value(this ReadOnlySpan value) => value.IsEmpty + ? null + : value.Length == 1 && value[0] == TypeConstants.NonWidthWhiteSpace + ? "" + : value.ToString(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static object Value(this object obj) => + obj is string value && value.Length == 1 && value[0] == TypeConstants.NonWidthWhiteSpace + ? "" + : obj; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsNullOrEmpty(this ReadOnlySpan value) => value.IsEmpty || (value.Length == 1 && value[0] == TypeConstants.NonWidthWhiteSpace); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsNullOrWhiteSpace(this ReadOnlySpan value) => value.IsNullOrEmpty() || value.IsWhiteSpace(); + + [Obsolete("Use value[index]")] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static char GetChar(this ReadOnlySpan value, int index) => value[index]; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string Substring(this ReadOnlySpan value, int pos) => value.Slice(pos, value.Length - pos).ToString(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string Substring(this ReadOnlySpan value, int pos, int length) => value.Slice(pos, length).ToString(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool CompareIgnoreCase(this ReadOnlySpan value, ReadOnlySpan text) => value.Equals(text, StringComparison.OrdinalIgnoreCase); + + public static ReadOnlySpan FromCsvField(this ReadOnlySpan text) + { + //TODO replace with native Replace() when exists + if (text.IsNullOrEmpty()) + return text; + + var delim = CsvConfig.ItemDelimiterString; + if (delim.Length == 1) + { + if (text[0] != delim[0]) + return text; + } + else if (!text.StartsWith(delim.AsSpan(), StringComparison.Ordinal)) + { + return text; + } + + var ret = text.Slice(CsvConfig.ItemDelimiterString.Length, text.Length - CsvConfig.EscapedItemDelimiterString.Length) + .ToString().Replace(CsvConfig.EscapedItemDelimiterString, CsvConfig.ItemDelimiterString); + + if (ret == string.Empty) + return TypeConstants.EmptyStringSpan; + + return ret.AsSpan(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool ParseBoolean(this ReadOnlySpan value) + { + //Lots of kids like to use '1', HTML checkboxes use 'on' as a soft convention + switch (value.Length) + { + case 0: + return false; + case 1: + switch (value[0]) + { + case '1': + case 't': + case 'T': + case 'y': + case 'Y': + return true; + case '0': + case 'f': + case 'F': + case 'n': + case 'N': + return false; + } + break; + case 2: + if (value[0] == 'o' && value[1] == 'n') + return true; + break; + case 3: + if (value[0] == 'o' && value[1] == 'f' && value[1] == 'f') + return false; + break; + } + + return MemoryProvider.Instance.ParseBoolean(value); + } + + public static bool TryParseBoolean(this ReadOnlySpan value, out bool result) => + MemoryProvider.Instance.TryParseBoolean(value, out result); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryParseDecimal(this ReadOnlySpan value, out decimal result) => + MemoryProvider.Instance.TryParseDecimal(value, out result); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryParseFloat(this ReadOnlySpan value, out float result) => + MemoryProvider.Instance.TryParseFloat(value, out result); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryParseDouble(this ReadOnlySpan value, out double result) => + MemoryProvider.Instance.TryParseDouble(value, out result); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static decimal ParseDecimal(this ReadOnlySpan value) => + MemoryProvider.Instance.ParseDecimal(value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static decimal ParseDecimal(this ReadOnlySpan value, bool allowThousands) => + MemoryProvider.Instance.ParseDecimal(value, allowThousands); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float ParseFloat(this ReadOnlySpan value) => + MemoryProvider.Instance.ParseFloat(value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double ParseDouble(this ReadOnlySpan value) => + MemoryProvider.Instance.ParseDouble(value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static sbyte ParseSByte(this ReadOnlySpan value) => + SignedInteger.ParseSByte(value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte ParseByte(this ReadOnlySpan value) => + UnsignedInteger.ParseByte(value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static short ParseInt16(this ReadOnlySpan value) => + SignedInteger.ParseInt16(value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ushort ParseUInt16(this ReadOnlySpan value) => + UnsignedInteger.ParseUInt16(value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int ParseInt32(this ReadOnlySpan value) => + SignedInteger.ParseInt32(value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint ParseUInt32(this ReadOnlySpan value) => + UnsignedInteger.ParseUInt32(value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static long ParseInt64(this ReadOnlySpan value) => + SignedInteger.ParseInt64(value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong ParseUInt64(this ReadOnlySpan value) => + UnsignedInteger.ParseUInt64(value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Guid ParseGuid(this ReadOnlySpan value) => + DefaultMemory.Provider.ParseGuid(value); + + public static object ParseSignedInteger(this ReadOnlySpan value) + { + var longValue = ParseInt64(value); + if (longValue >= int.MinValue && longValue <= int.MaxValue) + return (int)longValue; + return longValue; + } + + public static bool TryReadLine(this ReadOnlySpan text, out ReadOnlySpan line, ref int startIndex) + { + if (startIndex >= text.Length) + { + line = TypeConstants.NullStringSpan; + return false; + } + + text = text.Slice(startIndex); + + var nextLinePos = text.IndexOfAny('\r', '\n'); + if (nextLinePos == -1) + { + var nextLine = text.Slice(0, text.Length); + startIndex += text.Length; + line = nextLine; + return true; + } + else + { + var nextLine = text.Slice(0, nextLinePos); + + startIndex += nextLinePos + 1; + + if (text[nextLinePos] == '\r' && text.Length > nextLinePos + 1 && text[nextLinePos + 1] == '\n') + startIndex += 1; + + line = nextLine; + return true; + } + } + + public static bool TryReadPart(this ReadOnlySpan text, string needle, out ReadOnlySpan part, ref int startIndex) + { + if (startIndex >= text.Length) + { + part = TypeConstants.NullStringSpan; + return false; + } + + text = text.Slice(startIndex); + var nextPartPos = text.IndexOf(needle); + if (nextPartPos == -1) + { + var nextPart = text.Slice(0, text.Length); + startIndex += text.Length; + part = nextPart; + return true; + } + else + { + var nextPart = text.Slice(0, nextPartPos); + startIndex += nextPartPos + needle.Length; + part = nextPart; + return true; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan Advance(this ReadOnlySpan text, int to) => text.Slice(to, text.Length - to); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan AdvancePastWhitespace(this ReadOnlySpan literal) + { + var i = 0; + while (i < literal.Length && char.IsWhiteSpace(literal[i])) + i++; + + return i == 0 ? literal : literal.Slice(i < literal.Length ? i : literal.Length); + } + + public static ReadOnlySpan AdvancePastChar(this ReadOnlySpan literal, char delim) + { + var i = 0; + var c = (char) 0; + while (i < literal.Length && (c = literal[i]) != delim) + i++; + + if (c == delim) + return literal.Slice(i + 1); + + return i == 0 ? literal : literal.Slice(i < literal.Length ? i : literal.Length); + } + + [Obsolete("Use Slice()")] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan Subsegment(this ReadOnlySpan text, int startPos) => text.Slice(startPos, text.Length - startPos); + + [Obsolete("Use Slice()")] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan Subsegment(this ReadOnlySpan text, int startPos, int length) => text.Slice(startPos, length); + + public static ReadOnlySpan LeftPart(this ReadOnlySpan strVal, char needle) + { + if (strVal.IsEmpty) return strVal; + var pos = strVal.IndexOf(needle); + return pos == -1 + ? strVal + : strVal.Slice(0, pos); + } + + public static ReadOnlySpan LeftPart(this ReadOnlySpan strVal, string needle) + { + if (strVal.IsEmpty) return strVal; + var pos = strVal.IndexOf(needle); + return pos == -1 + ? strVal + : strVal.Slice(0, pos); + } + + public static ReadOnlySpan RightPart(this ReadOnlySpan strVal, char needle) + { + if (strVal.IsEmpty) return strVal; + var pos = strVal.IndexOf(needle); + return pos == -1 + ? strVal + : strVal.Slice(pos + 1); + } + + public static ReadOnlySpan RightPart(this ReadOnlySpan strVal, string needle) + { + if (strVal.IsEmpty) return strVal; + var pos = strVal.IndexOf(needle); + return pos == -1 + ? strVal + : strVal.Slice(pos + needle.Length); + } + + public static ReadOnlySpan LastLeftPart(this ReadOnlySpan strVal, char needle) + { + if (strVal.IsEmpty) return strVal; + var pos = strVal.LastIndexOf(needle); + return pos == -1 + ? strVal + : strVal.Slice(0, pos); + } + + public static ReadOnlySpan LastLeftPart(this ReadOnlySpan strVal, string needle) + { + if (strVal.IsEmpty) return strVal; + var pos = strVal.LastIndexOf(needle); + return pos == -1 + ? strVal + : strVal.Slice(0, pos); + } + + public static ReadOnlySpan LastRightPart(this ReadOnlySpan strVal, char needle) + { + if (strVal.IsEmpty) return strVal; + var pos = strVal.LastIndexOf(needle); + return pos == -1 + ? strVal + : strVal.Slice(pos + 1); + } + + public static ReadOnlySpan LastRightPart(this ReadOnlySpan strVal, string needle) + { + if (strVal.IsEmpty) return strVal; + var pos = strVal.LastIndexOf(needle); + return pos == -1 + ? strVal + : strVal.Slice(pos + needle.Length); + } + + public static void SplitOnFirst(this ReadOnlySpan strVal, char needle, out ReadOnlySpan first, out ReadOnlySpan last) + { + first = default; + last = default; + if (strVal.IsEmpty) return; + + var pos = strVal.IndexOf(needle); + if (pos == -1) + { + first = strVal; + } + else + { + first = strVal.Slice(0, pos); + last = strVal.Slice(pos + 1); + } + } + + public static void SplitOnFirst(this ReadOnlySpan strVal, string needle, out ReadOnlySpan first, out ReadOnlySpan last) + { + first = default; + last = default; + if (strVal.IsEmpty) return; + + var pos = strVal.IndexOf(needle); + if (pos == -1) + { + first = strVal; + } + else + { + first = strVal.Slice(0, pos); + last = strVal.Slice(pos + needle.Length); + } + } + + public static void SplitOnLast(this ReadOnlySpan strVal, char needle, out ReadOnlySpan first, out ReadOnlySpan last) + { + first = default; + last = default; + if (strVal.IsEmpty) return; + + var pos = strVal.LastIndexOf(needle); + if (pos == -1) + { + first = strVal; + } + else + { + first = strVal.Slice(0, pos); + last = strVal.Slice(pos + 1); + } + } + + public static void SplitOnLast(this ReadOnlySpan strVal, string needle, out ReadOnlySpan first, out ReadOnlySpan last) + { + first = default; + last = default; + if (strVal.IsEmpty) return; + + var pos = strVal.LastIndexOf(needle); + if (pos == -1) + { + first = strVal; + } + else + { + first = strVal.Slice(0, pos); + last = strVal.Slice(pos + needle.Length); + } + } + + public static ReadOnlySpan WithoutExtension(this ReadOnlySpan filePath) + { + if (filePath.IsNullOrEmpty()) + return TypeConstants.NullStringSpan; + + var extPos = filePath.LastIndexOf('.'); + if (extPos == -1) return filePath; + + var dirPos = filePath.LastIndexOfAny(PclExport.DirSeps); + return extPos > dirPos ? filePath.Slice(0, extPos) : filePath; + } + + public static ReadOnlySpan GetExtension(this ReadOnlySpan filePath) + { + if (filePath.IsNullOrEmpty()) + return TypeConstants.NullStringSpan; + + var extPos = filePath.LastIndexOf('.'); + return extPos == -1 ? TypeConstants.NullStringSpan : filePath.Slice(extPos); + } + + public static ReadOnlySpan ParentDirectory(this ReadOnlySpan filePath) + { + if (filePath.IsNullOrEmpty()) + return TypeConstants.NullStringSpan; + + var dirSep = filePath.IndexOf(PclExport.Instance.DirSep) != -1 + ? PclExport.Instance.DirSep + : filePath.IndexOf(PclExport.Instance.AltDirSep) != -1 + ? PclExport.Instance.AltDirSep + : (char)0; + + if (dirSep == 0) + return TypeConstants.NullStringSpan; + + MemoryExtensions.TrimEnd(filePath, dirSep).SplitOnLast(dirSep, out var first, out _); + return first; + } + + public static ReadOnlySpan TrimEnd(this ReadOnlySpan value, params char[] trimChars) + { + if (value.IsEmpty) return TypeConstants.NullStringSpan; + if (trimChars == null || trimChars.Length == 0) + return value.TrimHelper(1); + return value.TrimHelper(trimChars, 1); + } + + private static ReadOnlySpan TrimHelper(this ReadOnlySpan value, int trimType) + { + if (value.IsEmpty) return TypeConstants.NullStringSpan; + int end = value.Length - 1; + int start = 0; + if (trimType != 1) + { + start = 0; + while (start < value.Length && char.IsWhiteSpace(value[start])) + ++start; + } + if (trimType != 0) + { + end = value.Length - 1; + while (end >= start && char.IsWhiteSpace(value[end])) + --end; + } + return value.CreateTrimmedString(start, end); + } + + private static ReadOnlySpan TrimHelper(this ReadOnlySpan value, char[] trimChars, int trimType) + { + if (value.IsEmpty) return TypeConstants.NullStringSpan; + int end = value.Length - 1; + int start = 0; + if (trimType != 1) + { + for (start = 0; start < value.Length; ++start) + { + char ch = value[start]; + int index = 0; + while (index < trimChars.Length && (int)trimChars[index] != (int)ch) + ++index; + if (index == trimChars.Length) + break; + } + } + if (trimType != 0) + { + for (end = value.Length - 1; end >= start; --end) + { + char ch = value[end]; + int index = 0; + while (index < trimChars.Length && (int)trimChars[index] != (int)ch) + ++index; + if (index == trimChars.Length) + break; + } + } + return value.CreateTrimmedString(start, end); + } + + private static ReadOnlySpan CreateTrimmedString(this ReadOnlySpan value, int start, int end) + { + if (value.IsEmpty) return TypeConstants.NullStringSpan; + int length = end - start + 1; + if (length == value.Length) + return value; + if (length == 0) + return TypeConstants.NullStringSpan; + return value.Slice(start, length); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan SafeSlice(this ReadOnlySpan value, int startIndex) => SafeSlice(value, startIndex, value.Length); + + public static ReadOnlySpan SafeSlice(this ReadOnlySpan value, int startIndex, int length) + { + if (value.IsEmpty) return TypeConstants.NullStringSpan; + if (startIndex < 0) startIndex = 0; + if (value.Length >= startIndex + length) + return value.Slice(startIndex, length); + + return value.Length > startIndex ? value.Slice(startIndex) : TypeConstants.NullStringSpan; + } + + public static string SubstringWithEllipsis(this ReadOnlySpan value, int startIndex, int length) + { + if (value.IsEmpty) return string.Empty; + var str = value.SafeSlice(startIndex, length); + return str.Length == length + ? str.ToString() + "..." + : str.ToString(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int IndexOf(this ReadOnlySpan value, string other) => value.IndexOf(other.AsSpan()); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int IndexOf(this ReadOnlySpan value, string needle, int start) + { + var pos = value.Slice(start).IndexOf(needle.AsSpan()); + return pos == -1 ? -1 : start + pos; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int LastIndexOf(this ReadOnlySpan value, string other) => value.LastIndexOf(other.AsSpan()); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int LastIndexOf(this ReadOnlySpan value, string needle, int start) + { + var pos = value.Slice(start).LastIndexOf(needle.AsSpan()); + return pos == -1 ? -1 : start + pos; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool EqualTo(this ReadOnlySpan value, string other) => value.Equals(other.AsSpan(), StringComparison.Ordinal); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool EqualTo(this ReadOnlySpan value, ReadOnlySpan other) => value.Equals(other, StringComparison.Ordinal); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool EqualsOrdinal(this ReadOnlySpan value, string other) => value.Equals(other.AsSpan(), StringComparison.Ordinal); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool StartsWith(this ReadOnlySpan value, string other) => value.StartsWith(other.AsSpan(), StringComparison.OrdinalIgnoreCase); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool StartsWith(this ReadOnlySpan value, string other, StringComparison comparison) => value.StartsWith(other.AsSpan(), comparison); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool EndsWith(this ReadOnlySpan value, string other, StringComparison comparison) => value.EndsWith(other.AsSpan(), comparison); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool EndsWith(this ReadOnlySpan value, string other) => value.EndsWith(other.AsSpan(), StringComparison.OrdinalIgnoreCase); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool EqualsIgnoreCase(this ReadOnlySpan value, ReadOnlySpan other) => value.Equals(other, StringComparison.OrdinalIgnoreCase); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool StartsWithIgnoreCase(this ReadOnlySpan value, ReadOnlySpan other) => value.StartsWith(other, StringComparison.OrdinalIgnoreCase); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool EndsWithIgnoreCase(this ReadOnlySpan value, ReadOnlySpan other) => value.EndsWith(other, StringComparison.OrdinalIgnoreCase); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Task WriteAsync(this Stream stream, ReadOnlySpan value, CancellationToken token = default(CancellationToken)) => + MemoryProvider.Instance.WriteAsync(stream, value, token); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan SafeSubstring(this ReadOnlySpan value, int startIndex) => SafeSubstring(value, startIndex, value.Length); + + public static ReadOnlySpan SafeSubstring(this ReadOnlySpan value, int startIndex, int length) + { + if (value.IsEmpty) return TypeConstants.NullStringSpan; + if (startIndex < 0) startIndex = 0; + if (value.Length >= (startIndex + length)) + return value.Slice(startIndex, length); + + return value.Length > startIndex ? value.Slice(startIndex) : TypeConstants.NullStringSpan; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static StringBuilder Append(this StringBuilder sb, ReadOnlySpan value) => + MemoryProvider.Instance.Append(sb, value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte[] ParseBase64(this ReadOnlySpan value) => MemoryProvider.Instance.ParseBase64(value); + + public static ReadOnlyMemory ToUtf8(this ReadOnlySpan value) => + MemoryProvider.Instance.ToUtf8(value); + + public static ReadOnlyMemory FromUtf8(this ReadOnlySpan value) => + MemoryProvider.Instance.FromUtf8(value); + + public static byte[] ToUtf8Bytes(this ReadOnlySpan value) => + MemoryProvider.Instance.ToUtf8Bytes(value); + + public static string FromUtf8Bytes(this ReadOnlySpan value) => + MemoryProvider.Instance.FromUtf8Bytes(value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static List ToStringList(this IEnumerable> from) + { + var to = new List(); + if (from != null) + { + foreach (var item in from) + { + to.Add(item.ToString()); + } + } + return to; + } + + public static int CountOccurrencesOf(this ReadOnlySpan value, char needle) + { + var count = 0; + var length = value.Length; + for (var n = length - 1; n >= 0; n--) + { + if (value[n] == needle) + count++; + } + return count; + } + + public static ReadOnlySpan WithoutBom(this ReadOnlySpan value) + { + return value.Length > 0 && value[0] == 65279 + ? value.Slice(1) + : value; + } + + public static ReadOnlySpan WithoutBom(this ReadOnlySpan value) + { + return value.Length > 3 && value[0] == 0xEF && value[1] == 0xBB && value[2] == 0xBF + ? value.Slice(3) + : value; + } + + } +} diff --git a/src/ServiceStack.Text/Support/HashedStringSegment.cs b/src/ServiceStack.Text/Support/HashedStringSegment.cs deleted file mode 100644 index 8d5193524..000000000 --- a/src/ServiceStack.Text/Support/HashedStringSegment.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; - -#if NETSTANDARD2_0 -using Microsoft.Extensions.Primitives; -#endif - -namespace ServiceStack.Text.Support -{ - public class HashedStringSegment - { - public StringSegment Value { get; } - private readonly int hash; - - public HashedStringSegment(StringSegment value) - { - Value = value; - hash = ComputeHashCode(value); - } - - public HashedStringSegment(string value) : this(new StringSegment(value)) - { - } - - public override bool Equals(object obj) - { - return Value.Equals(((HashedStringSegment)obj).Value, StringComparison.OrdinalIgnoreCase); - } - - public override int GetHashCode() => hash; - - public static int ComputeHashCode(StringSegment value) - { - var length = value.Length; - if (length == 0) - return 0; - - var offset = value.Offset; - var hash = 37 * length; - - char c1 = Char.ToUpperInvariant(value.Buffer[offset]); - hash += 53 * c1; - - if (length > 1) - { - char c2 = Char.ToUpperInvariant(value.Buffer[offset + length - 1]); - hash += 37 * c2; - } - - return hash; - } - } -} \ No newline at end of file diff --git a/src/ServiceStack.Text/Support/TimeSpanConverter.cs b/src/ServiceStack.Text/Support/TimeSpanConverter.cs index 3fadcd1b1..940a9fcf8 100644 --- a/src/ServiceStack.Text/Support/TimeSpanConverter.cs +++ b/src/ServiceStack.Text/Support/TimeSpanConverter.cs @@ -6,20 +6,31 @@ namespace ServiceStack.Text.Support { public class TimeSpanConverter { + private const string MinSerializedValue = "-P10675199DT2H48M5.4775391S"; + private const string MaxSerializedValue = "P10675199DT2H48M5.4775391S"; + public static string ToXsdDuration(TimeSpan timeSpan) { + if (timeSpan == TimeSpan.MinValue) + return MinSerializedValue; + if (timeSpan == TimeSpan.MaxValue) + return MaxSerializedValue; + var sb = StringBuilderThreadStatic.Allocate(); sb.Append(timeSpan.Ticks < 0 ? "-P" : "P"); - double ticks = Math.Abs(timeSpan.Ticks); + double ticks = timeSpan.Ticks; + if (ticks < 0) + ticks = -ticks; + double totalSeconds = ticks / TimeSpan.TicksPerSecond; - int wholeSeconds = (int) totalSeconds; - int seconds = wholeSeconds; - int sec = (seconds >= 60 ? seconds % 60 : seconds); - int min = (seconds = (seconds / 60)) >= 60 ? seconds % 60 : seconds; - int hours = (seconds = (seconds / 60)) >= 24 ? seconds % 24 : seconds; - int days = seconds / 24; + long wholeSeconds = (long) totalSeconds; + long seconds = wholeSeconds; + long sec = (seconds >= 60 ? seconds % 60 : seconds); + long min = (seconds = (seconds / 60)) >= 60 ? seconds % 60 : seconds; + long hours = (seconds = (seconds / 60)) >= 24 ? seconds % 24 : seconds; + long days = seconds / 24; double remainingSecs = sec + (totalSeconds - wholeSeconds); if (days > 0) @@ -36,7 +47,7 @@ public static string ToXsdDuration(TimeSpan timeSpan) if (remainingSecs > 0) { - var secFmt = string.Format("{0:0.0000000}", remainingSecs); + var secFmt = string.Format(CultureInfo.InvariantCulture, "{0:0.0000000}", remainingSecs); secFmt = secFmt.TrimEnd('0').TrimEnd('.'); sb.Append(secFmt + "S"); } @@ -51,11 +62,16 @@ public static string ToXsdDuration(TimeSpan timeSpan) public static TimeSpan FromXsdDuration(string xsdDuration) { - int days = 0; - int hours = 0; - int minutes = 0; + if (xsdDuration == MinSerializedValue) + return TimeSpan.MinValue; + if (xsdDuration == MaxSerializedValue) + return TimeSpan.MaxValue; + + long days = 0; + long hours = 0; + long minutes = 0; decimal seconds = 0; - int sign = 1; + long sign = 1; if (xsdDuration.StartsWith("-", StringComparison.Ordinal)) { @@ -70,8 +86,7 @@ public static TimeSpan FromXsdDuration(string xsdDuration) string[] d = t[0].SplitOnFirst('D'); if (d.Length == 2) { - int day; - if (int.TryParse(d[0], out day)) + if (long.TryParse(d[0], out var day)) days = day; } if (hasTime) @@ -79,24 +94,21 @@ public static TimeSpan FromXsdDuration(string xsdDuration) string[] h = t[1].SplitOnFirst('H'); if (h.Length == 2) { - int hour; - if (int.TryParse(h[0], out hour)) + if (long.TryParse(h[0], out var hour)) hours = hour; } string[] m = h[h.Length - 1].SplitOnFirst('M'); if (m.Length == 2) { - int min; - if (int.TryParse(m[0], out min)) + if (long.TryParse(m[0], out var min)) minutes = min; } string[] s = m[m.Length - 1].SplitOnFirst('S'); if (s.Length == 2) { - decimal millis; - if (decimal.TryParse(s[0], out millis)) + if (decimal.TryParse(s[0], NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var millis)) seconds = millis; } } diff --git a/src/ServiceStack.Text/TaskUtils.cs b/src/ServiceStack.Text/TaskUtils.cs index 614d4cad8..cf5265f22 100644 --- a/src/ServiceStack.Text/TaskUtils.cs +++ b/src/ServiceStack.Text/TaskUtils.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; @@ -10,20 +11,13 @@ namespace ServiceStack { public static class TaskUtils { - public static Task FromResult(T result) - { - var taskSource = new TaskCompletionSource(); - taskSource.SetResult(result); - return taskSource.Task; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Task FromResult(T result) => Task.FromResult(result); - public static Task InTask(this T result) - { - var taskSource = new TaskCompletionSource(); - taskSource.SetResult(result); - return taskSource.Task; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Task InTask(this T result) => Task.FromResult(result); + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Task InTask(this Exception ex) { var taskSource = new TaskCompletionSource(); @@ -31,15 +25,10 @@ public static Task InTask(this Exception ex) return taskSource.Task; } - public static bool IsSuccess(this Task task) - { - return !task.IsFaulted && task.IsCompleted; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsSuccess(this Task task) => !task.IsFaulted && task.IsCompleted; - public static Task Cast(this Task task) where To : From - { - return task.Then(x => (To)x); - } + public static Task Cast(this Task task) where To : From => task.Then(x => (To)x); public static TaskScheduler SafeTaskScheduler() { @@ -144,8 +133,7 @@ static void StartNextIteration(TaskCompletionSource tcs, i++; - if (iterationTask != null) - iterationTask.ContinueWith(next, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); + iterationTask?.ContinueWith(next, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); } public static void Sleep(int timeMs) diff --git a/src/ServiceStack.Text/Tracer.cs b/src/ServiceStack.Text/Tracer.cs index d11c2d2c8..e9da684ea 100644 --- a/src/ServiceStack.Text/Tracer.cs +++ b/src/ServiceStack.Text/Tracer.cs @@ -16,12 +16,23 @@ public void WriteWarning(string warning) { } public void WriteWarning(string format, params object[] args) { } - public void WriteError(Exception ex) { } - - public void WriteError(string error) { } + public void WriteError(Exception ex) + { + if (JsConfig.ThrowOnError) + throw ex; + } - public void WriteError(string format, params object[] args) { } + public void WriteError(string error) + { + if (JsConfig.ThrowOnError) + throw new Exception(error); + } + public void WriteError(string format, params object[] args) + { + if (JsConfig.ThrowOnError) + throw new Exception(string.Format(format, args)); + } } public class ConsoleTracer : ITracer diff --git a/src/ServiceStack.Text/TranslateListWithElements.cs b/src/ServiceStack.Text/TranslateListWithElements.cs index d7049572e..b3613133d 100644 --- a/src/ServiceStack.Text/TranslateListWithElements.cs +++ b/src/ServiceStack.Text/TranslateListWithElements.cs @@ -27,8 +27,7 @@ private static Dictionary TranslateICollectionCac public static object TranslateToGenericICollectionCache(object from, Type toInstanceOfType, Type elementType) { - ConvertInstanceDelegate translateToFn; - if (TranslateICollectionCache.TryGetValue(toInstanceOfType, out translateToFn)) + if (TranslateICollectionCache.TryGetValue(toInstanceOfType, out var translateToFn)) return translateToFn(from, toInstanceOfType); var genericType = typeof(TranslateListWithElements<>).MakeGenericType(elementType); @@ -39,8 +38,9 @@ public static object TranslateToGenericICollectionCache(object from, Type toInst do { snapshot = TranslateICollectionCache; - newCache = new Dictionary(TranslateICollectionCache); - newCache[elementType] = translateToFn; + newCache = new Dictionary(TranslateICollectionCache) { + [elementType] = translateToFn + }; } while (!ReferenceEquals( Interlocked.CompareExchange(ref TranslateICollectionCache, newCache, snapshot), snapshot)); @@ -55,8 +55,7 @@ public static object TranslateToConvertibleGenericICollectionCache( object from, Type toInstanceOfType, Type fromElementType) { var typeKey = new ConvertibleTypeKey(toInstanceOfType, fromElementType); - ConvertInstanceDelegate translateToFn; - if (TranslateConvertibleICollectionCache.TryGetValue(typeKey, out translateToFn)) return translateToFn(from, toInstanceOfType); + if (TranslateConvertibleICollectionCache.TryGetValue(typeKey, out var translateToFn)) return translateToFn(from, toInstanceOfType); var toElementType = toInstanceOfType.FirstGenericType().GetGenericArguments()[0]; var genericType = typeof(TranslateListWithConvertibleElements<,>).MakeGenericType(fromElementType, toElementType); @@ -67,8 +66,9 @@ public static object TranslateToConvertibleGenericICollectionCache( do { snapshot = TranslateConvertibleICollectionCache; - newCache = new Dictionary(TranslateConvertibleICollectionCache); - newCache[typeKey] = translateToFn; + newCache = new Dictionary(TranslateConvertibleICollectionCache) { + [typeKey] = translateToFn + }; } while (!ReferenceEquals( Interlocked.CompareExchange(ref TranslateConvertibleICollectionCache, newCache, snapshot), snapshot)); @@ -102,34 +102,30 @@ public static object TryTranslateCollections(Type fromPropertyType, Type toPrope if (fromElType == null || toElType == null) return null; - if (fromElType == typeof(object) || toElType.IsAssignableFrom(fromElType)) - return TranslateToGenericICollectionCache(fromValue, toPropertyType, toElType); - - return null; + return TranslateToGenericICollectionCache(fromValue, toPropertyType, toElType); } - } public class ConvertibleTypeKey { public Type ToInstanceType { get; set; } - public Type FromElemenetType { get; set; } + public Type FromElementType { get; set; } public ConvertibleTypeKey() { } - public ConvertibleTypeKey(Type toInstanceType, Type fromElemenetType) + public ConvertibleTypeKey(Type toInstanceType, Type fromElementType) { ToInstanceType = toInstanceType; - FromElemenetType = fromElemenetType; + FromElementType = fromElementType; } public bool Equals(ConvertibleTypeKey other) { if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; - return Equals(other.ToInstanceType, ToInstanceType) && Equals(other.FromElemenetType, FromElemenetType); + return other.ToInstanceType == ToInstanceType && other.FromElementType == FromElementType; } public override bool Equals(object obj) @@ -145,7 +141,7 @@ public override int GetHashCode() unchecked { return ((ToInstanceType != null ? ToInstanceType.GetHashCode() : 0) * 397) - ^ (FromElemenetType != null ? FromElemenetType.GetHashCode() : 0); + ^ (FromElementType != null ? FromElementType.GetHashCode() : 0); } } } @@ -199,7 +195,15 @@ public static ICollection TranslateToGenericICollection( var to = (ICollection)CreateInstance(toInstanceOfType); foreach (var item in fromList) { - to.Add((T)item); + if (item is IEnumerable> dictionary) + { + var convertedItem = dictionary.FromObjectDictionary(); + to.Add(convertedItem); + } + else + { + to.Add((T)item); + } } return to; } diff --git a/src/ServiceStack.Text/TypeConfig.cs b/src/ServiceStack.Text/TypeConfig.cs index 8545d7f32..57585be3e 100644 --- a/src/ServiceStack.Text/TypeConfig.cs +++ b/src/ServiceStack.Text/TypeConfig.cs @@ -1,6 +1,8 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Reflection; +using System.Runtime.Serialization; namespace ServiceStack.Text { @@ -12,6 +14,15 @@ internal class TypeConfig internal FieldInfo[] Fields; internal Func OnDeserializing; internal bool IsUserType { get; set; } + internal Func TextCaseResolver; + internal TextCase? TextCase + { + get + { + var result = TextCaseResolver?.Invoke(); + return result is null or Text.TextCase.Default ? null : result; + } + } internal TypeConfig(Type type) { @@ -23,13 +34,12 @@ internal TypeConfig(Type type) JsConfig.AddUniqueType(Type); } } - + public static class TypeConfig { internal static TypeConfig config; - static TypeConfig Config => - config ?? (config = Create()); + static TypeConfig Config => config ??= Create(); public static PropertyInfo[] Properties { @@ -76,7 +86,9 @@ public static Func OnDeserializing static TypeConfig Create() { - config = new TypeConfig(typeof(T)); + config = new TypeConfig(typeof(T)) { + TextCaseResolver = () => JsConfig.TextCase + }; var excludedProperties = JsConfig.ExcludePropertyNames ?? TypeConstants.EmptyStringArray; @@ -87,7 +99,7 @@ static TypeConfig Create() Fields = config.Type.GetSerializableFields().ToArray(); - if (!JsConfig.HasDeserialingFn) + if (!JsConfig.HasDeserializingFn) OnDeserializing = ReflectionExtensions.GetOnDeserializing(); else config.OnDeserializing = (instance, memberName, value) => JsConfig.OnDeserializingFn((T)instance, memberName, value); diff --git a/src/ServiceStack.Text/TypeConstants.cs b/src/ServiceStack.Text/TypeConstants.cs index 88f46778c..a2d989981 100644 --- a/src/ServiceStack.Text/TypeConstants.cs +++ b/src/ServiceStack.Text/TypeConstants.cs @@ -2,11 +2,6 @@ using System.Collections.Generic; using System.Reflection; using System.Threading.Tasks; -using ServiceStack.Text; - -#if NETSTANDARD2_0 -using Microsoft.Extensions.Primitives; -#endif namespace ServiceStack { @@ -33,7 +28,15 @@ private static Task InTask(this T result) public static readonly Task EmptyTask; public static readonly object EmptyObject = new object(); - public static readonly StringSegment EmptyStringSegment = new StringSegment(null); + + public const char NonWidthWhiteSpace = (char)0x200B; //Use zero-width space marker to capture empty string + public static char[] NonWidthWhiteSpaceChars = { (char)0x200B }; + + public static ReadOnlySpan NullStringSpan => default; + public static ReadOnlySpan EmptyStringSpan => new ReadOnlySpan(NonWidthWhiteSpaceChars); + + public static ReadOnlyMemory NullStringMemory => default; + public static ReadOnlyMemory EmptyStringMemory => "".AsMemory(); public static readonly string[] EmptyStringArray = new string[0]; public static readonly long[] EmptyLongArray = new long[0]; @@ -45,7 +48,6 @@ private static Task InTask(this T result) public static readonly Type[] EmptyTypeArray = new Type[0]; public static readonly FieldInfo[] EmptyFieldInfoArray = new FieldInfo[0]; public static readonly PropertyInfo[] EmptyPropertyInfoArray = new PropertyInfo[0]; - public static readonly StringSegment[] EmptyStringSegmentArray = new StringSegment[0]; public static readonly byte[][] EmptyByteArrayArray = new byte[0][]; @@ -62,7 +64,6 @@ private static Task InTask(this T result) public static readonly List EmptyTypeList = new List(0); public static readonly List EmptyFieldInfoList = new List(0); public static readonly List EmptyPropertyInfoList = new List(0); - public static readonly List EmptyStringSegmentList = new List(0); } public static class TypeConstants diff --git a/src/ServiceStack.Text/TypeFields.cs b/src/ServiceStack.Text/TypeFields.cs index 00de10aa6..6a82b52f4 100644 --- a/src/ServiceStack.Text/TypeFields.cs +++ b/src/ServiceStack.Text/TypeFields.cs @@ -4,12 +4,6 @@ using System.Threading; using ServiceStack.Text; -using System.Linq.Expressions; - -#if NET45 || NETSTANDARD2_0 -using System.Reflection.Emit; -#endif - namespace ServiceStack { public class FieldAccessor @@ -50,8 +44,8 @@ static TypeFields() var fnRef = fi.SetExpressionRef(); Instance.FieldsMap[fi.Name] = new FieldAccessor( fi, - PclExport.Instance.CreateGetter(fi), - PclExport.Instance.CreateSetter(fi), + ReflectionOptimizer.Instance.CreateGetter(fi), + ReflectionOptimizer.Instance.CreateSetter(fi), delegate (ref object instance, object arg) { var valueInstance = (T)instance; @@ -164,270 +158,19 @@ public virtual SetMemberRefDelegate GetPublicSetterRef(string name) public static class FieldInvoker { - [Obsolete("Use CreateGetter")] - public static GetMemberDelegate GetFieldGetterFn(this FieldInfo fieldInfo) => - PclExport.Instance.CreateGetter(fieldInfo); - - [Obsolete("Use CreateSetter")] - public static SetMemberDelegate GetFieldSetterFn(this FieldInfo fieldInfo) => - PclExport.Instance.CreateSetter(fieldInfo); - public static GetMemberDelegate CreateGetter(this FieldInfo fieldInfo) => - PclExport.Instance.CreateGetter(fieldInfo); + ReflectionOptimizer.Instance.CreateGetter(fieldInfo); public static GetMemberDelegate CreateGetter(this FieldInfo fieldInfo) => - PclExport.Instance.CreateGetter(fieldInfo); + ReflectionOptimizer.Instance.CreateGetter(fieldInfo); public static SetMemberDelegate CreateSetter(this FieldInfo fieldInfo) => - PclExport.Instance.CreateSetter(fieldInfo); + ReflectionOptimizer.Instance.CreateSetter(fieldInfo); public static SetMemberDelegate CreateSetter(this FieldInfo fieldInfo) => - PclExport.Instance.CreateSetter(fieldInfo); - - public static GetMemberDelegate GetReflection(FieldInfo fieldInfo) => fieldInfo.GetValue; - public static SetMemberDelegate SetReflection(FieldInfo fieldInfo) => fieldInfo.SetValue; - - private static readonly MethodInfo setFieldMethod = - typeof(FieldInvoker).GetStaticMethod("SetField"); - - internal static void SetField(ref TValue field, TValue newValue) - { - field = newValue; - } - - public static GetMemberDelegate GetExpression(FieldInfo fieldInfo) - { - var instance = Expression.Parameter(typeof(T), "i"); - var field = typeof(T) != fieldInfo.DeclaringType - ? Expression.Field(Expression.TypeAs(instance, fieldInfo.DeclaringType), fieldInfo) - : Expression.Field(instance, fieldInfo); - var convertField = Expression.TypeAs(field, typeof(object)); - return Expression.Lambda>(convertField, instance).Compile(); - } - - public static GetMemberDelegate GetExpression(FieldInfo fieldInfo) - { - var fieldDeclaringType = fieldInfo.DeclaringType; - - var oInstanceParam = Expression.Parameter(typeof(object), "source"); - var instanceParam = GetCastOrConvertExpression(oInstanceParam, fieldDeclaringType); - - var exprCallFieldGetFn = Expression.Field(instanceParam, fieldInfo); - //var oExprCallFieldGetFn = this.GetCastOrConvertExpression(exprCallFieldGetFn, typeof(object)); - var oExprCallFieldGetFn = Expression.Convert(exprCallFieldGetFn, typeof(object)); - - var fieldGetterFn = Expression.Lambda - ( - oExprCallFieldGetFn, - oInstanceParam - ) - .Compile(); - - return fieldGetterFn; - } - - public static SetMemberDelegate SetExpression(FieldInfo fieldInfo) - { - var fieldDeclaringType = fieldInfo.DeclaringType; - - var sourceParameter = Expression.Parameter(typeof(object), "source"); - var valueParameter = Expression.Parameter(typeof(object), "value"); - - var sourceExpression = GetCastOrConvertExpression(sourceParameter, fieldDeclaringType); - - var fieldExpression = Expression.Field(sourceExpression, fieldInfo); - - var valueExpression = GetCastOrConvertExpression(valueParameter, fieldExpression.Type); - - var genericSetFieldMethodInfo = setFieldMethod.MakeGenericMethod(fieldExpression.Type); - - var setFieldMethodCallExpression = Expression.Call( - null, genericSetFieldMethodInfo, fieldExpression, valueExpression); - - var setterFn = Expression.Lambda( - setFieldMethodCallExpression, sourceParameter, valueParameter).Compile(); - - return setterFn; - } - - public static SetMemberDelegate SetExpression(FieldInfo fieldInfo) - { - var instance = Expression.Parameter(typeof(T), "i"); - var argument = Expression.Parameter(typeof(object), "a"); - - var field = typeof(T) != fieldInfo.DeclaringType - ? Expression.Field(Expression.TypeAs(instance, fieldInfo.DeclaringType), fieldInfo) - : Expression.Field(instance, fieldInfo); - - var setterCall = Expression.Assign( - field, - Expression.Convert(argument, fieldInfo.FieldType)); - - return Expression.Lambda> - ( - setterCall, instance, argument - ).Compile(); - } - - private static Expression GetCastOrConvertExpression(Expression expression, Type targetType) - { - Expression result; - var expressionType = expression.Type; - - if (targetType.IsAssignableFrom(expressionType)) - { - result = expression; - } - else - { - // Check if we can use the as operator for casting or if we must use the convert method - if (targetType.IsValueType && !targetType.IsNullableType()) - { - result = Expression.Convert(expression, targetType); - } - else - { - result = Expression.TypeAs(expression, targetType); - } - } - - return result; - } - - public static SetMemberRefDelegate SetExpressionRef(this FieldInfo fieldInfo) - { - var instance = Expression.Parameter(typeof(T).MakeByRefType(), "i"); - var argument = Expression.Parameter(typeof(object), "a"); - - var field = typeof(T) != fieldInfo.DeclaringType - ? Expression.Field(Expression.TypeAs(instance, fieldInfo.DeclaringType), fieldInfo) - : Expression.Field(instance, fieldInfo); - - var setterCall = Expression.Assign( - field, - Expression.Convert(argument, fieldInfo.FieldType)); - - return Expression.Lambda> - ( - setterCall, instance, argument - ).Compile(); - } - -#if NET45 || NETSTANDARD2_0 - public static GetMemberDelegate GetEmit(FieldInfo fieldInfo) - { - var getter = CreateDynamicGetMethod(fieldInfo); - - var gen = getter.GetILGenerator(); - - gen.Emit(OpCodes.Ldarg_0); - - gen.Emit(OpCodes.Ldfld, fieldInfo); - - if (fieldInfo.FieldType.IsValueType) - { - gen.Emit(OpCodes.Box, fieldInfo.FieldType); - } - - gen.Emit(OpCodes.Ret); - - return (GetMemberDelegate)getter.CreateDelegate(typeof(GetMemberDelegate)); - } - - public static GetMemberDelegate GetEmit(FieldInfo fieldInfo) - { - var getter = CreateDynamicGetMethod(fieldInfo); - - var gen = getter.GetILGenerator(); - - gen.Emit(OpCodes.Ldarg_0); - - if (fieldInfo.DeclaringType.IsValueType) - { - gen.Emit(OpCodes.Unbox, fieldInfo.DeclaringType); - } - else - { - gen.Emit(OpCodes.Castclass, fieldInfo.DeclaringType); - } - - gen.Emit(OpCodes.Ldfld, fieldInfo); - - if (fieldInfo.FieldType.IsValueType) - { - gen.Emit(OpCodes.Box, fieldInfo.FieldType); - } - - gen.Emit(OpCodes.Ret); - - return (GetMemberDelegate)getter.CreateDelegate(typeof(GetMemberDelegate)); - } - - static readonly Type[] DynamicGetMethodArgs = { typeof(object) }; - - internal static DynamicMethod CreateDynamicGetMethod(MemberInfo memberInfo) - { - var memberType = memberInfo is FieldInfo ? "Field" : "Property"; - var name = $"_Get{memberType}_{memberInfo.Name}_"; - var returnType = typeof(object); - - return !memberInfo.DeclaringType.IsInterface - ? new DynamicMethod(name, returnType, DynamicGetMethodArgs, memberInfo.DeclaringType, true) - : new DynamicMethod(name, returnType, DynamicGetMethodArgs, memberInfo.Module, true); - } - - internal static DynamicMethod CreateDynamicGetMethod(MemberInfo memberInfo) - { - var memberType = memberInfo is FieldInfo ? "Field" : "Property"; - var name = $"_Get{memberType}[T]_{memberInfo.Name}_"; - var returnType = typeof(object); - - return !memberInfo.DeclaringType.IsInterface - ? new DynamicMethod(name, returnType, new[] { typeof(T) }, memberInfo.DeclaringType, true) - : new DynamicMethod(name, returnType, new[] { typeof(T) }, memberInfo.Module, true); - } + ReflectionOptimizer.Instance.CreateSetter(fieldInfo); - public static SetMemberDelegate SetEmit(FieldInfo fieldInfo) - { - var setter = CreateDynamicSetMethod(fieldInfo); - - var gen = setter.GetILGenerator(); - gen.Emit(OpCodes.Ldarg_0); - - if (fieldInfo.DeclaringType.IsValueType) - { - gen.Emit(OpCodes.Unbox, fieldInfo.DeclaringType); - } - else - { - gen.Emit(OpCodes.Castclass, fieldInfo.DeclaringType); - } - - gen.Emit(OpCodes.Ldarg_1); - - gen.Emit(fieldInfo.FieldType.IsClass - ? OpCodes.Castclass - : OpCodes.Unbox_Any, - fieldInfo.FieldType); - - gen.Emit(OpCodes.Stfld, fieldInfo); - gen.Emit(OpCodes.Ret); - - return (SetMemberDelegate)setter.CreateDelegate(typeof(SetMemberDelegate)); - } - - static readonly Type[] DynamicSetMethodArgs = { typeof(object), typeof(object) }; - - internal static DynamicMethod CreateDynamicSetMethod(MemberInfo memberInfo) - { - var memberType = memberInfo is FieldInfo ? "Field" : "Property"; - var name = $"_Set{memberType}_{memberInfo.Name}_"; - var returnType = typeof(void); - - return !memberInfo.DeclaringType.IsInterface - ? new DynamicMethod(name, returnType, DynamicSetMethodArgs, memberInfo.DeclaringType, true) - : new DynamicMethod(name, returnType, DynamicSetMethodArgs, memberInfo.Module, true); - } -#endif + public static SetMemberRefDelegate SetExpressionRef(this FieldInfo fieldInfo) => + ReflectionOptimizer.Instance.CreateSetterRef(fieldInfo); } } diff --git a/src/ServiceStack.Text/TypeProperties.cs b/src/ServiceStack.Text/TypeProperties.cs index 31930bb00..1fc0fdbfd 100644 --- a/src/ServiceStack.Text/TypeProperties.cs +++ b/src/ServiceStack.Text/TypeProperties.cs @@ -4,17 +4,8 @@ using System.Threading; using ServiceStack.Text; -using System.Linq.Expressions; - -#if NET45 || NETSTANDARD2_0 -using System.Reflection.Emit; -#endif - namespace ServiceStack { - [Obsolete("Use TypeProperties.Instance")] - public static class TypeReflector { } - public class PropertyAccessor { public PropertyAccessor( @@ -48,8 +39,8 @@ static TypeProperties() { Instance.PropertyMap[pi.Name] = new PropertyAccessor( pi, - PclExport.Instance.CreateGetter(pi), - PclExport.Instance.CreateSetter(pi) + ReflectionOptimizer.Instance.CreateGetter(pi), + ReflectionOptimizer.Instance.CreateSetter(pi) ); } catch (Exception ex) @@ -71,7 +62,7 @@ public abstract class TypeProperties { static Dictionary CacheMap = new Dictionary(); - public static Type FactoryType = typeof(TypeProperties<>); + public static readonly Type FactoryType = typeof(TypeProperties<>); public static TypeProperties Get(Type type) { @@ -147,240 +138,17 @@ public SetMemberDelegate GetPublicSetter(string name) public static class PropertyInvoker { - [Obsolete("Use CreateGetter")] - public static GetMemberDelegate GetPropertyGetterFn(this PropertyInfo propertyInfo) => - PclExport.Instance.CreateGetter(propertyInfo); - - [Obsolete("Use CreateSetter")] - public static SetMemberDelegate GetPropertySetterFn(this PropertyInfo propertyInfo) => - PclExport.Instance.CreateSetter(propertyInfo); - public static GetMemberDelegate CreateGetter(this PropertyInfo propertyInfo) => - PclExport.Instance.CreateGetter(propertyInfo); + ReflectionOptimizer.Instance.CreateGetter(propertyInfo); public static GetMemberDelegate CreateGetter(this PropertyInfo propertyInfo) => - PclExport.Instance.CreateGetter(propertyInfo); + ReflectionOptimizer.Instance.CreateGetter(propertyInfo); public static SetMemberDelegate CreateSetter(this PropertyInfo propertyInfo) => - PclExport.Instance.CreateSetter(propertyInfo); + ReflectionOptimizer.Instance.CreateSetter(propertyInfo); public static SetMemberDelegate CreateSetter(this PropertyInfo propertyInfo) => - PclExport.Instance.CreateSetter(propertyInfo); - - public static GetMemberDelegate GetReflection(PropertyInfo propertyInfo) => propertyInfo.GetValue; - public static SetMemberDelegate SetReflection(PropertyInfo propertyInfo) => propertyInfo.SetValue; - - public static GetMemberDelegate GetExpression(PropertyInfo propertyInfo) - { - var expr = GetExpressionLambda(propertyInfo); - return expr.Compile(); - } - - public static Expression> GetExpressionLambda(PropertyInfo propertyInfo) - { - var instance = Expression.Parameter(typeof(T), "i"); - var property = typeof(T) != propertyInfo.DeclaringType - ? Expression.Property(Expression.TypeAs(instance, propertyInfo.DeclaringType), propertyInfo) - : Expression.Property(instance, propertyInfo); - var convertProperty = Expression.TypeAs(property, typeof(object)); - return Expression.Lambda>(convertProperty, instance); - } - - public static GetMemberDelegate GetExpression(PropertyInfo propertyInfo) - { - var lambda = GetExpressionLambda(propertyInfo); - var propertyGetFn = lambda.Compile(); - return propertyGetFn; - } - - public static Expression GetExpressionLambda(PropertyInfo propertyInfo) - { - var getMethodInfo = propertyInfo.GetGetMethod(nonPublic:true); - if (getMethodInfo == null) return null; - - var oInstanceParam = Expression.Parameter(typeof(object), "oInstanceParam"); - var instanceParam = Expression.Convert(oInstanceParam, propertyInfo.ReflectedType); //propertyInfo.DeclaringType doesn't work on Proxy types - - var exprCallPropertyGetFn = Expression.Call(instanceParam, getMethodInfo); - var oExprCallPropertyGetFn = Expression.Convert(exprCallPropertyGetFn, typeof(object)); - - return Expression.Lambda - ( - oExprCallPropertyGetFn, - oInstanceParam - ); - } - - public static SetMemberDelegate SetExpression(PropertyInfo propertyInfo) - { - try - { - var lambda = SetExpressionLambda(propertyInfo); - return lambda?.Compile(); - } - catch //fallback for Android - { - var mi = propertyInfo.GetSetMethod(nonPublic: true); - return (o, convertedValue) => - mi.Invoke(o, new[] { convertedValue }); - } - } - - public static Expression> SetExpressionLambda(PropertyInfo propertyInfo) - { - var mi = propertyInfo.GetSetMethod(nonPublic: true); - if (mi == null) return null; - - var instance = Expression.Parameter(typeof(T), "i"); - var argument = Expression.Parameter(typeof(object), "a"); - - var instanceType = typeof(T) != propertyInfo.DeclaringType - ? (Expression)Expression.TypeAs(instance, propertyInfo.DeclaringType) - : instance; - - var setterCall = Expression.Call( - instanceType, - mi, - Expression.Convert(argument, propertyInfo.PropertyType)); - - return Expression.Lambda> - ( - setterCall, instance, argument - ); - } - - public static SetMemberDelegate SetExpression(PropertyInfo propertyInfo) - { - var propertySetMethod = propertyInfo.GetSetMethod(nonPublic:true); - if (propertySetMethod == null) return null; - - try - { - var instance = Expression.Parameter(typeof(object), "i"); - var argument = Expression.Parameter(typeof(object), "a"); - - var instanceParam = Expression.Convert(instance, propertyInfo.ReflectedType); - var valueParam = Expression.Convert(argument, propertyInfo.PropertyType); - - var setterCall = Expression.Call(instanceParam, propertySetMethod, valueParam); - - return Expression.Lambda(setterCall, instance, argument).Compile(); - } - catch //fallback for Android - { - return (o, convertedValue) => - propertySetMethod.Invoke(o, new[] { convertedValue }); - } - } - -#if NET45 || NETSTANDARD2_0 - public static GetMemberDelegate GetEmit(PropertyInfo propertyInfo) - { - var getter = FieldInvoker.CreateDynamicGetMethod(propertyInfo); - - var gen = getter.GetILGenerator(); - var mi = propertyInfo.GetGetMethod(true); - - if (typeof(T).IsValueType) - { - gen.Emit(OpCodes.Ldarga_S, 0); - - if (typeof(T) != propertyInfo.DeclaringType) - { - gen.Emit(OpCodes.Unbox, propertyInfo.DeclaringType); - } - } - else - { - gen.Emit(OpCodes.Ldarg_0); - - if (typeof(T) != propertyInfo.DeclaringType) - { - gen.Emit(OpCodes.Castclass, propertyInfo.DeclaringType); - } - } - - gen.Emit(mi.IsFinal ? OpCodes.Call : OpCodes.Callvirt, mi); - - if (propertyInfo.PropertyType.IsValueType) - { - gen.Emit(OpCodes.Box, propertyInfo.PropertyType); - } - - gen.Emit(OpCodes.Isinst, typeof(object)); - - gen.Emit(OpCodes.Ret); - - return (GetMemberDelegate)getter.CreateDelegate(typeof(GetMemberDelegate)); - } - - public static GetMemberDelegate GetEmit(PropertyInfo propertyInfo) - { - var getter = FieldInvoker.CreateDynamicGetMethod(propertyInfo); - - var gen = getter.GetILGenerator(); - gen.Emit(OpCodes.Ldarg_0); - - if (propertyInfo.DeclaringType.IsValueType) - { - gen.Emit(OpCodes.Unbox, propertyInfo.DeclaringType); - } - else - { - gen.Emit(OpCodes.Castclass, propertyInfo.DeclaringType); - } - - var mi = propertyInfo.GetGetMethod(true); - gen.Emit(mi.IsFinal ? OpCodes.Call : OpCodes.Callvirt, mi); - - if (propertyInfo.PropertyType.IsValueType) - { - gen.Emit(OpCodes.Box, propertyInfo.PropertyType); - } - - gen.Emit(OpCodes.Ret); - - return (GetMemberDelegate)getter.CreateDelegate(typeof(GetMemberDelegate)); - } - - public static SetMemberDelegate SetEmit(PropertyInfo propertyInfo) - { - var mi = propertyInfo.GetSetMethod(true); - if (mi == null) - return null; - - var setter = FieldInvoker.CreateDynamicSetMethod(propertyInfo); - - var gen = setter.GetILGenerator(); - gen.Emit(OpCodes.Ldarg_0); - - if (propertyInfo.DeclaringType.IsValueType) - { - gen.Emit(OpCodes.Unbox, propertyInfo.DeclaringType); - } - else - { - gen.Emit(OpCodes.Castclass, propertyInfo.DeclaringType); - } - - gen.Emit(OpCodes.Ldarg_1); - - if (propertyInfo.PropertyType.IsValueType) - { - gen.Emit(OpCodes.Unbox_Any, propertyInfo.PropertyType); - } - else - { - gen.Emit(OpCodes.Castclass, propertyInfo.PropertyType); - } - - gen.EmitCall(mi.IsFinal ? OpCodes.Call : OpCodes.Callvirt, mi, (Type[])null); - - gen.Emit(OpCodes.Ret); - - return (SetMemberDelegate)setter.CreateDelegate(typeof(SetMemberDelegate)); - } -#endif + ReflectionOptimizer.Instance.CreateSetter(propertyInfo); } } diff --git a/src/ServiceStack.Text/TypeSerializer.cs b/src/ServiceStack.Text/TypeSerializer.cs index 4a6d1295e..a06fdcd11 100644 --- a/src/ServiceStack.Text/TypeSerializer.cs +++ b/src/ServiceStack.Text/TypeSerializer.cs @@ -13,10 +13,12 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Collections.Specialized; using System.Globalization; using System.IO; using System.Linq; using System.Text; +using System.Threading.Tasks; using ServiceStack.Text.Common; using ServiceStack.Text.Jsv; using ServiceStack.Text.Pools; @@ -33,7 +35,14 @@ static TypeSerializer() JsConfig.InitStatics(); } - public static Encoding UTF8Encoding = PclExport.Instance.GetUTF8Encoding(false); + [Obsolete("Use JsConfig.UTF8Encoding")] + public static UTF8Encoding UTF8Encoding + { + get => JsConfig.UTF8Encoding; + set => JsConfig.UTF8Encoding = value; + } + + public static Action OnSerialize { get; set; } public const string DoubleQuoteString = "\"\""; @@ -60,6 +69,12 @@ public static T DeserializeFromString(string value) return (T)JsvReader.Parse(value); } + public static T DeserializeFromSpan(ReadOnlySpan value) + { + if (value.IsEmpty) return default(T); + return (T)JsvReader.Parse(value); + } + public static T DeserializeFromReader(TextReader reader) { return DeserializeFromString(reader.ReadToEnd()); @@ -74,8 +89,15 @@ public static T DeserializeFromReader(TextReader reader) public static object DeserializeFromString(string value, Type type) { return value == null - ? null - : JsvReader.GetParseFn(type)(value); + ? null + : JsvReader.GetParseFn(type)(value); + } + + public static object DeserializeFromSpan(Type type, ReadOnlySpan value) + { + return value.IsEmpty + ? null + : JsvReader.GetParseSpanFn(type)(value); } public static object DeserializeFromReader(TextReader reader, Type type) @@ -92,9 +114,10 @@ public static string SerializeToString(T value) } if (typeof(T).IsAbstract || typeof(T).IsInterface) { + var prevState = JsState.IsWritingDynamic; JsState.IsWritingDynamic = true; var result = SerializeToString(value, value.GetType()); - JsState.IsWritingDynamic = false; + JsState.IsWritingDynamic = prevState; return result; } @@ -106,9 +129,10 @@ public static string SerializeToString(T value) public static string SerializeToString(object value, Type type) { if (value == null) return null; - if (type == typeof(string)) - return value as string; + if (value is string str) + return str.EncodeJsv(); + OnSerialize?.Invoke(value); var writer = StringWriterThreadStatic.Allocate(); JsvWriter.GetWriteFn(type)(writer, value); return StringWriterThreadStatic.ReturnAndFree(writer); @@ -117,9 +141,9 @@ public static string SerializeToString(object value, Type type) public static void SerializeToWriter(T value, TextWriter writer) { if (value == null) return; - if (typeof(T) == typeof(string)) + if (value is string str) { - writer.Write(value); + writer.Write(str.EncodeJsv()); } else if (typeof(T) == typeof(object)) { @@ -127,9 +151,10 @@ public static void SerializeToWriter(T value, TextWriter writer) } else if (typeof(T).IsAbstract || typeof(T).IsInterface) { + var prevState = JsState.IsWritingDynamic; JsState.IsWritingDynamic = false; SerializeToWriter(value, value.GetType(), writer); - JsState.IsWritingDynamic = true; + JsState.IsWritingDynamic = prevState; } else { @@ -140,12 +165,13 @@ public static void SerializeToWriter(T value, TextWriter writer) public static void SerializeToWriter(object value, Type type, TextWriter writer) { if (value == null) return; - if (type == typeof(string)) + if (value is string str) { - writer.Write(value); + writer.Write(str.EncodeJsv()); return; } + OnSerialize?.Invoke(value); JsvWriter.GetWriteFn(type)(writer, value); } @@ -158,13 +184,14 @@ public static void SerializeToStream(T value, Stream stream) } else if (typeof(T).IsAbstract || typeof(T).IsInterface) { + var prevState = JsState.IsWritingDynamic; JsState.IsWritingDynamic = false; SerializeToStream(value, value.GetType(), stream); - JsState.IsWritingDynamic = true; + JsState.IsWritingDynamic = prevState; } else { - var writer = new StreamWriter(stream, UTF8Encoding); + var writer = new StreamWriter(stream, JsConfig.UTF8Encoding); JsvWriter.WriteRootObject(writer, value); writer.Flush(); } @@ -172,7 +199,8 @@ public static void SerializeToStream(T value, Stream stream) public static void SerializeToStream(object value, Type type, Stream stream) { - var writer = new StreamWriter(stream, UTF8Encoding); + OnSerialize?.Invoke(value); + var writer = new StreamWriter(stream, JsConfig.UTF8Encoding); JsvWriter.GetWriteFn(type)(writer, value); writer.Flush(); } @@ -186,18 +214,23 @@ public static T Clone(T value) public static T DeserializeFromStream(Stream stream) { - using (var reader = new StreamReader(stream, UTF8Encoding)) - { - return DeserializeFromString(reader.ReadToEnd()); - } + return (T)MemoryProvider.Instance.Deserialize(stream, typeof(T), DeserializeFromSpan); } public static object DeserializeFromStream(Type type, Stream stream) { - using (var reader = new StreamReader(stream, UTF8Encoding)) - { - return DeserializeFromString(reader.ReadToEnd(), type); - } + return MemoryProvider.Instance.Deserialize(stream, type, DeserializeFromSpan); + } + + public static Task DeserializeFromStreamAsync(Type type, Stream stream) + { + return MemoryProvider.Instance.DeserializeAsync(stream, type, DeserializeFromSpan); + } + + public static async Task DeserializeFromStreamAsync(Stream stream) + { + var obj = await MemoryProvider.Instance.DeserializeAsync(stream, typeof(T), DeserializeFromSpan).ConfigAwait(); + return (T)obj; } /// @@ -223,11 +256,14 @@ public static Dictionary ToStringDictionary(this object obj) } if (obj is IEnumerable> kvps) + return PlatformExtensions.ToStringDictionary(kvps); + + if (obj is NameValueCollection nvc) { var to = new Dictionary(); - foreach (var kvp in kvps) + for (var i = 0; i < nvc.Count; i++) { - to[kvp.Key] = kvp.Value.ToJsv(); + to[nvc.GetKey(i)] = nvc.Get(i); } return to; } @@ -277,8 +313,7 @@ public static void Print(this long longValue) public static string SerializeAndFormat(this T instance) { - var fn = instance as Delegate; - if (fn != null) + if (instance is Delegate fn) return Dump(fn); var dtoStr = !HasCircularReferences(instance) @@ -313,8 +348,8 @@ public static bool HasCircularReferences(object value) private static bool HasCircularReferences(object value, Stack parentValues) { var type = value?.GetType(); - - if (type == null || !type.IsClass || value is string) + + if (type == null || !type.IsClass || value is string || value is Type) return false; if (parentValues == null) @@ -322,15 +357,47 @@ private static bool HasCircularReferences(object value, Stack parentValu parentValues = new Stack(); parentValues.Push(value); } + + bool CheckValue(object key) + { + if (parentValues.Contains(key)) + return true; + + parentValues.Push(key); + + if (HasCircularReferences(key, parentValues)) + return true; + + parentValues.Pop(); + return false; + } if (value is IEnumerable valueEnumerable) { foreach (var item in valueEnumerable) { - if (HasCircularReferences(item, parentValues)) + if (item == null) + continue; + + var itemType = item.GetType(); + if (itemType.IsGenericType && itemType.GetGenericTypeDefinition() == typeof(KeyValuePair<,>)) + { + var props = TypeProperties.Get(itemType); + var key = props.GetPublicGetter("Key")(item); + + if (CheckValue(key)) + return true; + + var val = props.GetPublicGetter("Value")(item); + + if (CheckValue(val)) + return true; + } + + if (CheckValue(item)) return true; } - } + } else { var props = type.GetSerializableProperties(); @@ -345,15 +412,8 @@ private static bool HasCircularReferences(object value, Stack parentValu if (pValue == null) continue; - if (parentValues.Contains(pValue)) + if (CheckValue(pValue)) return true; - - parentValues.Push(pValue); - - if (HasCircularReferences(pValue, parentValues)) - return true; - - parentValues.Pop(); } } diff --git a/src/ServiceStack.Text/XmlSerializer.cs b/src/ServiceStack.Text/XmlSerializer.cs index a73cef758..2e60c7c31 100644 --- a/src/ServiceStack.Text/XmlSerializer.cs +++ b/src/ServiceStack.Text/XmlSerializer.cs @@ -9,16 +9,19 @@ namespace ServiceStack.Text { public class XmlSerializer { - private static readonly XmlWriterSettings XWSettings = new XmlWriterSettings(); - private static readonly XmlReaderSettings XRSettings = new XmlReaderSettings(); + public static readonly XmlWriterSettings XmlWriterSettings = new XmlWriterSettings(); + public static readonly XmlReaderSettings XmlReaderSettings = new XmlReaderSettings(); public static XmlSerializer Instance = new XmlSerializer(); public XmlSerializer(bool omitXmlDeclaration = false, int maxCharsInDocument = 1024 * 1024) { - XWSettings.Encoding = PclExport.Instance.GetUTF8Encoding(false); - XWSettings.OmitXmlDeclaration = omitXmlDeclaration; - XRSettings.MaxCharactersInDocument = maxCharsInDocument; + XmlWriterSettings.Encoding = PclExport.Instance.GetUTF8Encoding(false); + XmlWriterSettings.OmitXmlDeclaration = omitXmlDeclaration; + XmlReaderSettings.MaxCharactersInDocument = maxCharsInDocument; + + //Prevent XML bombs by default: https://msdn.microsoft.com/en-us/magazine/ee335713.aspx + XmlReaderSettings.DtdProcessing = DtdProcessing.Prohibit; } private static object Deserialize(string xml, Type type) @@ -26,7 +29,7 @@ private static object Deserialize(string xml, Type type) try { var stringReader = new StringReader(xml); - using (var reader = XmlReader.Create(stringReader, XRSettings)) + using (var reader = XmlReader.Create(stringReader, XmlReaderSettings)) { var serializer = new DataContractSerializer(type); return serializer.ReadObject(reader); @@ -73,20 +76,18 @@ public static string SerializeToString(T from) { using (var ms = MemoryStreamFactory.GetStream()) { - using (var xw = XmlWriter.Create(ms, XWSettings)) + using (var xw = XmlWriter.Create(ms, XmlWriterSettings)) { var serializer = new DataContractSerializer(from.GetType()); serializer.WriteObject(xw, from); xw.Flush(); - ms.Seek(0, SeekOrigin.Begin); - var reader = new StreamReader(ms); - return reader.ReadToEnd(); + return ms.ReadToEnd(); } } } catch (Exception ex) { - throw new SerializationException(string.Format("Error serializing object of type {0}", from.GetType().FullName), ex); + throw new SerializationException($"Error serializing object of type {@from.GetType().FullName}", ex); } } @@ -94,7 +95,7 @@ public static void SerializeToWriter(T value, TextWriter writer) { try { - using (var xw = XmlWriter.Create(writer, XWSettings)) + using (var xw = XmlWriter.Create(writer, XmlWriterSettings)) { var serializer = new DataContractSerializer(value.GetType()); serializer.WriteObject(xw, value); @@ -102,14 +103,14 @@ public static void SerializeToWriter(T value, TextWriter writer) } catch (Exception ex) { - throw new SerializationException(string.Format("Error serializing object of type {0}", value.GetType().FullName), ex); + throw new SerializationException($"Error serializing object of type {value.GetType().FullName}", ex); } } public static void SerializeToStream(object obj, Stream stream) { if (obj == null) return; - using (var xw = XmlWriter.Create(stream, XWSettings)) + using (var xw = XmlWriter.Create(stream, XmlWriterSettings)) { var serializer = new DataContractSerializer(obj.GetType()); serializer.WriteObject(xw, obj); diff --git a/src/UpgradeLog.htm b/src/UpgradeLog.htm new file mode 100644 index 000000000..a5d93a65d Binary files /dev/null and b/src/UpgradeLog.htm differ diff --git a/tests/Directory.Build.props b/tests/Directory.Build.props new file mode 100644 index 000000000..7eb7717ea --- /dev/null +++ b/tests/Directory.Build.props @@ -0,0 +1,29 @@ + + + + 6.0.3 + latest + false + + + + DEBUG + + + + $(DefineConstants);NETFX;NET472 + + + + $(DefineConstants);NETCORE;NETSTANDARD2_0 + + + + $(DefineConstants);NET6_0;NET6_0_OR_GREATER + + + + $(DefineConstants);NETCORE;NETCORE_SUPPORT + + + diff --git a/tests/Northwind.Common/DataModel/Northwind.models.cs b/tests/Northwind.Common/DataModel/Northwind.models.cs index e505e51ea..17404087e 100644 --- a/tests/Northwind.Common/DataModel/Northwind.models.cs +++ b/tests/Northwind.Common/DataModel/Northwind.models.cs @@ -523,7 +523,7 @@ public override int GetHashCode() public class OrderDetail : IHasStringId, IEquatable { - public string Id { get { return this.OrderId + "/" + this.ProductId; } } + public string Id => this.OrderId + "/" + this.ProductId; [Index] [Alias("OrderID")] diff --git a/tests/Northwind.Common/Northwind.Common.csproj b/tests/Northwind.Common/Northwind.Common.csproj index 18abee877..3d8f226f4 100644 --- a/tests/Northwind.Common/Northwind.Common.csproj +++ b/tests/Northwind.Common/Northwind.Common.csproj @@ -1,7 +1,7 @@  - net45;netstandard2.0 + net472;net6.0 Northwind.Common Northwind.Common false @@ -25,30 +25,22 @@ - + + - + $(DefineConstants);NET45 - + - - + $(DefineConstants);NETSTANDARD2_0 - - - - - - - - \ No newline at end of file diff --git a/tests/ServiceStack.Text.Benchmarks/BookShelfBenchmarks.cs b/tests/ServiceStack.Text.Benchmarks/BookShelfBenchmarks.cs new file mode 100644 index 000000000..1ca72a1b3 --- /dev/null +++ b/tests/ServiceStack.Text.Benchmarks/BookShelfBenchmarks.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using BenchmarkDotNet.Attributes; +using Newtonsoft.Json; + +namespace ServiceStack.Text.Benchmarks +{ + [DataContract] + public class BookShelf + { + [DataMember] + public List Books + { + get; + set; + } + + [DataMember] + private string Secret; + + public BookShelf(string secret) + { + Secret = secret; + } + + public BookShelf() // Parameterless ctor is needed for every protocol buffer class during deserialization + { } + } + + [DataContract] + public class Book + { + [DataMember] + public string Title; + + [DataMember] + public int Id; + } + + public static class BookUtils + { + public static BookShelf Data(int nToCreate) + { + var lret = new BookShelf("private member value") + { + Books = Enumerable.Range(1, nToCreate).Select(i => new Book { Id = i, Title = $"Book {i}" }).ToList() + }; + return lret; + } + } + + public class BookShelfooksBenchmarksBase + { + protected BookShelf data; + protected MemoryStream ssStream; + protected ReadOnlyMemory ssSpan; + protected string ssJson; + + protected MemoryStream jnStream; + protected string jnJson; + + protected void Init(int count) + { + data = BookUtils.Data(10000); + + ssStream = new MemoryStream(); + ServiceStack.Text.JsonSerializer.SerializeToStream(data, ssStream); + ssJson = ssStream.ReadToEnd(); + ssSpan = ssJson.AsMemory(); + + jnStream = new MemoryStream(); + var writer = new StreamWriter(jnStream, Encoding.UTF8, 1024, leaveOpen: true); + var jsonWriter = new JsonTextWriter(writer); + var serializer = new Newtonsoft.Json.JsonSerializer(); + serializer.Serialize(jsonWriter, data); + jsonWriter.Flush(); + + jnJson = jnStream.ReadToEnd(); + $"DATA ServiceStack length = {ssJson.Length}, JSON.NET length = {jnJson.Length}".Print(); + } + } + +/* + Method | N | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated | +----------------------------- |------ |---------:|----------:|----------:|---------:|---------:|---------:|----------:| + DeserializeFromStream | 10000 | 7.069 ms | 0.0331 ms | 0.0277 ms | 226.5625 | 109.3750 | 39.0625 | 1.25 MB | + DeserializeFromStreamJsonNet | 10000 | 8.432 ms | 0.1628 ms | 0.2059 ms | 218.7500 | 109.3750 | 31.2500 | 1.25 MB | + SerializeToString | 10000 | 2.649 ms | 0.0200 ms | 0.0167 ms | 304.6875 | 156.2500 | 156.2500 | 1.21 MB | + SerializeToStringJsonNet | 10000 | 4.632 ms | 0.0664 ms | 0.0621 ms | 304.6875 | 257.8125 | 156.2500 | 1.45 MB | + + */ + [MemoryDiagnoser] + public class BookShelf10000BooksBenchmarks : BookShelfooksBenchmarksBase + { + [Params(10000)] public int N; + + [GlobalSetup] + public void Setup() + { + Init(10000); + } + + [Benchmark] + public object DeserializeFromStream() => JsonSerializer.DeserializeFromStream(ssStream); + + [Benchmark] + public object DeserializeFromStreamJsonNet() + { + jnStream.Position = 0; + var serializer = new Newtonsoft.Json.JsonSerializer(); + var sr = new StreamReader(jnStream); + var jsonTextReader = new JsonTextReader(sr); + return serializer.Deserialize(jsonTextReader); + } + +// [Benchmark] +// public Task DeserializeFromStreamAsync() => JsonSerializer.DeserializeFromStreamAsync(ssStream); + +// [Benchmark] +// public object DeserializeFromSpan() => JsonSerializer.DeserializeFromSpan(ssSpan.Span); +// +// [Benchmark] +// public object DeserializeFromString() => JsonSerializer.DeserializeFromString(ssJson); + +// [Benchmark] +// public object DeserializeFromStringJsonNet() => JsonConvert.DeserializeObject(jnJson); + + [Benchmark] + public string SerializeToString() => ServiceStack.Text.JsonSerializer.SerializeToString(data); + + [Benchmark] + public string SerializeToStringJsonNet() => Newtonsoft.Json.JsonConvert.SerializeObject(data); + } + +} \ No newline at end of file diff --git a/benchmarks/ServiceStack.Text.VersionCompareBenchmarks/JsonDeserializationBenchmarks.cs b/tests/ServiceStack.Text.Benchmarks/JsonDeserializationBenchmarks.cs similarity index 81% rename from benchmarks/ServiceStack.Text.VersionCompareBenchmarks/JsonDeserializationBenchmarks.cs rename to tests/ServiceStack.Text.Benchmarks/JsonDeserializationBenchmarks.cs index f3c2ce861..fac14a010 100644 --- a/benchmarks/ServiceStack.Text.VersionCompareBenchmarks/JsonDeserializationBenchmarks.cs +++ b/tests/ServiceStack.Text.Benchmarks/JsonDeserializationBenchmarks.cs @@ -37,16 +37,28 @@ public class JsonDeserializationBenchmarks readonly string serializedString4096 = new string('t', 4096); static string commonTypesModelJson; + static ReadOnlyMemory commonTypesModelJsonSpan; + static string stringTypeJson; + static ReadOnlyMemory stringTypeJsonSpan; static JsonDeserializationBenchmarks() { commonTypesModelJson = JsonSerializer.SerializeToString(commonTypesModel); - stringTypeJson = JsonSerializer.SerializeToString(StringType.Create()); + commonTypesModelJsonSpan = commonTypesModelJson.AsMemory(); + + stringTypeJson = JsonSerializer.SerializeToString(StringType.Create()); + stringTypeJsonSpan = stringTypeJson.AsMemory(); + } + + [Benchmark(Description = "Deserialize Json: class with builtin types (string)")] + public void DeserializeJsonCommonTypesString() + { + var result = JsonSerializer.DeserializeFromString(commonTypesModelJson); } - [Benchmark(Description = "Deserialize Json: class with builtin types")] - public void DeserializeJsonCommonTypes() + [Benchmark(Description = "Deserialize Json: class with builtin types (span)")] + public void DeserializeJsonCommonTypesSpan() { var result = JsonSerializer.DeserializeFromString(commonTypesModelJson); } diff --git a/tests/ServiceStack.Text.Benchmarks/MemoryProviderBenchmarks.cs b/tests/ServiceStack.Text.Benchmarks/MemoryProviderBenchmarks.cs new file mode 100644 index 000000000..a2a97a6a4 --- /dev/null +++ b/tests/ServiceStack.Text.Benchmarks/MemoryProviderBenchmarks.cs @@ -0,0 +1,152 @@ +using System; +using System.Globalization; +using BenchmarkDotNet.Attributes; + +namespace ServiceStack.Text.Benchmarks +{ +/* + Method | N | Mean | Error | StdDev | +-------------------- |------- |----------:|----------:|----------:| + DefaultParseBoolean | 1000 | 30.00 ns | 0.6018 ns | 0.5911 ns | + NetCoreParseBoolean | 1000 | 20.09 ns | 0.1783 ns | 0.1489 ns | + DefaulParseDecimal | 1000 | 54.57 ns | 1.3897 ns | 1.7067 ns | + NetCoreParseDecimal | 1000 | 189.92 ns | 3.3742 ns | 3.3139 ns | + DefaulParseFloat | 1000 | 117.37 ns | 0.9250 ns | 0.8652 ns | + NetCoreParseFloat | 1000 | 102.20 ns | 0.6069 ns | 0.5677 ns | + DefaulParseDouble | 1000 | 118.04 ns | 2.3821 ns | 2.3395 ns | + NetCoreParseDouble | 1000 | 101.87 ns | 1.1663 ns | 0.9739 ns | + DefaulParseInt32 | 1000 | 19.23 ns | 0.0790 ns | 0.0700 ns | + NetCoreParseInt32 | 1000 | 72.63 ns | 1.4722 ns | 1.8080 ns | + DefaulParseInt64 | 1000 | 18.35 ns | 0.1577 ns | 0.1231 ns | + NetCoreParseInt64 | 1000 | 69.79 ns | 0.5492 ns | 0.5138 ns | + DefaulParseUInt32 | 1000 | 18.66 ns | 0.1509 ns | 0.1260 ns | + NetCoreParseUInt32 | 1000 | 72.68 ns | 0.3446 ns | 0.3054 ns | + DefaultParseBoolean | 10000 | 28.35 ns | 0.1571 ns | 0.1470 ns | + NetCoreParseBoolean | 10000 | 19.39 ns | 0.0590 ns | 0.0552 ns | + DefaulParseDecimal | 10000 | 57.40 ns | 1.1499 ns | 1.8239 ns | + NetCoreParseDecimal | 10000 | 192.12 ns | 2.7811 ns | 2.6014 ns | + DefaulParseFloat | 10000 | 126.15 ns | 2.5386 ns | 4.2414 ns | + NetCoreParseFloat | 10000 | 106.11 ns | 1.6362 ns | 1.5305 ns | + DefaulParseDouble | 10000 | 122.10 ns | 1.9895 ns | 1.8610 ns | + NetCoreParseDouble | 10000 | 107.38 ns | 2.0829 ns | 1.9483 ns | + DefaulParseInt32 | 10000 | 19.97 ns | 0.3522 ns | 0.3122 ns | + NetCoreParseInt32 | 10000 | 72.41 ns | 0.8461 ns | 0.7500 ns | + DefaulParseInt64 | 10000 | 18.19 ns | 0.0536 ns | 0.0502 ns | + NetCoreParseInt64 | 10000 | 69.90 ns | 0.8358 ns | 0.6979 ns | + DefaulParseUInt32 | 10000 | 18.58 ns | 0.1056 ns | 0.0936 ns | + NetCoreParseUInt32 | 10000 | 72.05 ns | 0.3941 ns | 0.3291 ns | + + DefaultParseBoolean | 100000 | 28.40 ns | 0.1632 ns | 0.1447 ns | + NetCoreParseBoolean | 100000 | 19.38 ns | 0.1022 ns | 0.0853 ns | + + DefaulParseDecimal | 100000 | 51.66 ns | 0.2337 ns | 0.2071 ns | + NetCoreParseDecimal | 100000 | 185.71 ns | 1.0153 ns | 0.9000 ns | + + DefaulParseFloat | 100000 | 119.73 ns | 2.4091 ns | 3.0467 ns | + NetCoreParseFloat | 100000 | 103.48 ns | 0.9406 ns | 0.8798 ns | + + DefaulParseDouble | 100000 | 117.82 ns | 1.0134 ns | 0.9480 ns | + NetCoreParseDouble | 100000 | 102.38 ns | 0.3972 ns | 0.3316 ns | + + DefaulParseInt32 | 100000 | 19.41 ns | 0.2591 ns | 0.2424 ns | + NetCoreParseInt32 | 100000 | 72.14 ns | 0.7835 ns | 0.7329 ns | + + DefaulParseInt64 | 100000 | 18.33 ns | 0.1323 ns | 0.1237 ns | + NetCoreParseInt64 | 100000 | 70.20 ns | 0.5148 ns | 0.4815 ns | + + DefaulParseUInt32 | 100000 | 19.11 ns | 0.4070 ns | 0.4180 ns | + NetCoreParseUInt32 | 100000 | 74.99 ns | 1.3515 ns | 1.1980 ns | +*/ + + public class MemoryProviderBenchmarks + { + [Params(100000)] public int N; + + [Benchmark] + public bool DefaultParseBoolean() => DefaultMemory.Provider.ParseBoolean(bool.TrueString); + + [Benchmark] + public bool NetCoreParseBoolean() => NetCoreMemory.Provider.ParseBoolean(bool.TrueString); + + [Benchmark] + public decimal DefaulParseDecimal() => DefaultMemory.Provider.ParseDecimal("123456.123456"); + + [Benchmark] + public decimal NetCoreParseDecimal() => NetCoreMemory.Provider.ParseDecimal("123456.123456"); + + [Benchmark] + public float DefaulParseFloat() => DefaultMemory.Provider.ParseFloat("123456.123456"); + + [Benchmark] + public float NetCoreParseFloat() => NetCoreMemory.Provider.ParseFloat("123456.123456"); + + [Benchmark] + public double DefaulParseDouble() => DefaultMemory.Provider.ParseDouble("123456.123456"); + + [Benchmark] + public double NetCoreParseDouble() => NetCoreMemory.Provider.ParseDouble("123456.123456"); + + [Benchmark] + public int DefaulParseInt32() => DefaultMemory.Provider.ParseInt32("-123456789"); + + [Benchmark] + public int NetCoreParseInt32() => NetCoreMemory.Provider.ParseInt32("-123456789"); + + [Benchmark] + public long DefaulParseInt64() => DefaultMemory.Provider.ParseInt64("123456789"); + + [Benchmark] + public long NetCoreParseInt64() => NetCoreMemory.Provider.ParseInt64("123456789"); + + [Benchmark] + public uint DefaulParseUInt32() => DefaultMemory.Provider.ParseUInt32("123456789"); + + [Benchmark] + public uint NetCoreParseUInt32() => NetCoreMemory.Provider.ParseUInt32("123456789"); + } + + + /* + Method | N | Mean | Error | StdDev | + -------------------- |------ |----------:|----------:|----------:| + NetCoreParseDecimal | 10000 | 192.23 ns | 3.8236 ns | 4.8357 ns | + CustomParseDecimal | 10000 | 45.22 ns | 0.4465 ns | 0.3728 ns | + */ + public class MemoryDecimalBenchmarks + { + [Params(10000)] public int N; + + [Benchmark] + public decimal NetCoreParseDecimal() => decimal.Parse("123456.123456", + NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture); + + [Benchmark] + public decimal CustomParseDecimal() => DefaultMemory.Provider.ParseDecimal("123456.123456", allowThousands: true); + } + + +/* + Method | N | Mean | Error | StdDev | Median | +------------------ |------ |---------:|----------:|----------:|---------:| + NetCoreParseInt32 | 10000 | 58.28 ns | 0.5349 ns | 0.5004 ns | 58.19 ns | + NetCoreParseInt34 | 10000 | 59.95 ns | 1.2276 ns | 2.6425 ns | 58.93 ns | + CustomParseInt32 | 10000 | 12.11 ns | 0.2584 ns | 0.2417 ns | 12.05 ns | + CustomParseInt64 | 10000 | 11.28 ns | 0.0723 ns | 0.0564 ns | 11.27 ns | +*/ + public class MemoryIntegerBenchmarks + { + [Params(10000)] public int N; + + [Benchmark] + public int NetCoreParseInt32() => int.Parse("1234"); + + [Benchmark] + public long NetCoreParseInt34() => long.Parse("1234"); + + [Benchmark] + public int CustomParseInt32() => DefaultMemory.Provider.ParseInt32("1234".AsSpan()); + + [Benchmark] + public long CustomParseInt64() => DefaultMemory.Provider.ParseInt64("1234".AsSpan()); + } +} \ No newline at end of file diff --git a/benchmarks/ServiceStack.Text.VersionCompareBenchmarks/MiniProfiler/ClientTiming.cs b/tests/ServiceStack.Text.Benchmarks/MiniProfiler/ClientTiming.cs similarity index 100% rename from benchmarks/ServiceStack.Text.VersionCompareBenchmarks/MiniProfiler/ClientTiming.cs rename to tests/ServiceStack.Text.Benchmarks/MiniProfiler/ClientTiming.cs diff --git a/benchmarks/ServiceStack.Text.VersionCompareBenchmarks/MiniProfiler/ClientTimings.cs b/tests/ServiceStack.Text.Benchmarks/MiniProfiler/ClientTimings.cs similarity index 100% rename from benchmarks/ServiceStack.Text.VersionCompareBenchmarks/MiniProfiler/ClientTimings.cs rename to tests/ServiceStack.Text.Benchmarks/MiniProfiler/ClientTimings.cs diff --git a/benchmarks/ServiceStack.Text.VersionCompareBenchmarks/MiniProfiler/CustomTiming.cs b/tests/ServiceStack.Text.Benchmarks/MiniProfiler/CustomTiming.cs similarity index 100% rename from benchmarks/ServiceStack.Text.VersionCompareBenchmarks/MiniProfiler/CustomTiming.cs rename to tests/ServiceStack.Text.Benchmarks/MiniProfiler/CustomTiming.cs diff --git a/benchmarks/ServiceStack.Text.VersionCompareBenchmarks/MiniProfiler/FlowData.cs b/tests/ServiceStack.Text.Benchmarks/MiniProfiler/FlowData.cs similarity index 98% rename from benchmarks/ServiceStack.Text.VersionCompareBenchmarks/MiniProfiler/FlowData.cs rename to tests/ServiceStack.Text.Benchmarks/MiniProfiler/FlowData.cs index 2455dc292..d03b10539 100644 --- a/benchmarks/ServiceStack.Text.VersionCompareBenchmarks/MiniProfiler/FlowData.cs +++ b/tests/ServiceStack.Text.Benchmarks/MiniProfiler/FlowData.cs @@ -1,4 +1,4 @@ -#if NET45 +#if NETFX using System.Runtime.Remoting; using System.Runtime.Remoting.Messaging; #else @@ -16,7 +16,7 @@ namespace StackExchange.Profiling.Internal /// The type of data to store. public class FlowData { -#if NET45 +#if NETFX // Key specific to this type. #pragma warning disable RCS1158 // Avoid static members in generic types. private static readonly string _key = typeof(FlowData).FullName; diff --git a/benchmarks/ServiceStack.Text.VersionCompareBenchmarks/MiniProfiler/MiniProfiler.cs b/tests/ServiceStack.Text.Benchmarks/MiniProfiler/MiniProfiler.cs similarity index 99% rename from benchmarks/ServiceStack.Text.VersionCompareBenchmarks/MiniProfiler/MiniProfiler.cs rename to tests/ServiceStack.Text.Benchmarks/MiniProfiler/MiniProfiler.cs index 3d68b60c6..884dbfe47 100644 --- a/benchmarks/ServiceStack.Text.VersionCompareBenchmarks/MiniProfiler/MiniProfiler.cs +++ b/tests/ServiceStack.Text.Benchmarks/MiniProfiler/MiniProfiler.cs @@ -280,7 +280,7 @@ public IEnumerable GetTimingHierarchy() } } -#if !NETCOREAPP1_1 // TODO: Revisit in .NET Standard 2.0 +#if !NETCORE /// /// Create a DEEP clone of this MiniProfiler. /// @@ -296,7 +296,6 @@ public MiniProfiler Clone() } #endif - internal Timing StepImpl(string name, decimal? minSaveMs = null, bool? includeChildrenWithMinSave = false) { return new Timing(this, Head, name, minSaveMs, includeChildrenWithMinSave); diff --git a/benchmarks/ServiceStack.Text.VersionCompareBenchmarks/MiniProfiler/MiniProfilerExtensions.cs b/tests/ServiceStack.Text.Benchmarks/MiniProfiler/MiniProfilerExtensions.cs similarity index 100% rename from benchmarks/ServiceStack.Text.VersionCompareBenchmarks/MiniProfiler/MiniProfilerExtensions.cs rename to tests/ServiceStack.Text.Benchmarks/MiniProfiler/MiniProfilerExtensions.cs diff --git a/benchmarks/ServiceStack.Text.VersionCompareBenchmarks/MiniProfiler/StackTraceSnippet.cs b/tests/ServiceStack.Text.Benchmarks/MiniProfiler/StackTraceSnippet.cs similarity index 100% rename from benchmarks/ServiceStack.Text.VersionCompareBenchmarks/MiniProfiler/StackTraceSnippet.cs rename to tests/ServiceStack.Text.Benchmarks/MiniProfiler/StackTraceSnippet.cs diff --git a/benchmarks/ServiceStack.Text.VersionCompareBenchmarks/MiniProfiler/Timing.cs b/tests/ServiceStack.Text.Benchmarks/MiniProfiler/Timing.cs similarity index 100% rename from benchmarks/ServiceStack.Text.VersionCompareBenchmarks/MiniProfiler/Timing.cs rename to tests/ServiceStack.Text.Benchmarks/MiniProfiler/Timing.cs diff --git a/benchmarks/ServiceStack.Text.VersionCompareBenchmarks/ModelWithCommonTypes.cs b/tests/ServiceStack.Text.Benchmarks/ModelWithCommonTypes.cs similarity index 100% rename from benchmarks/ServiceStack.Text.VersionCompareBenchmarks/ModelWithCommonTypes.cs rename to tests/ServiceStack.Text.Benchmarks/ModelWithCommonTypes.cs diff --git a/benchmarks/ServiceStack.Text.VersionCompareBenchmarks/ParseBuiltinsBenchmarks.cs b/tests/ServiceStack.Text.Benchmarks/ParseBuiltinsBenchmarks.cs similarity index 100% rename from benchmarks/ServiceStack.Text.VersionCompareBenchmarks/ParseBuiltinsBenchmarks.cs rename to tests/ServiceStack.Text.Benchmarks/ParseBuiltinsBenchmarks.cs diff --git a/tests/ServiceStack.Text.Benchmarks/Program.cs b/tests/ServiceStack.Text.Benchmarks/Program.cs new file mode 100644 index 000000000..a039e7f77 --- /dev/null +++ b/tests/ServiceStack.Text.Benchmarks/Program.cs @@ -0,0 +1,70 @@ +using System; +using System.Linq; +using System.Security.Cryptography; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Attributes.Columns; +using BenchmarkDotNet.Attributes.Exporters; +using BenchmarkDotNet.Attributes.Jobs; +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Reports; +using BenchmarkDotNet.Running; +using BenchmarkDotNet.Validators; + +namespace ServiceStack.Text.Benchmarks +{ + public class Md5VsSha256 + { + private const int N = 10000; + private readonly byte[] data; + + private readonly SHA256 sha256 = SHA256.Create(); + private readonly MD5 md5 = MD5.Create(); + + public Md5VsSha256() + { + data = new byte[N]; + new Random(42).NextBytes(data); + } + + [Benchmark] + public byte[] Sha256() => sha256.ComputeHash(data); + + [Benchmark] + public byte[] Md5() => md5.ComputeHash(data); + } + + public class AllowNonOptimized : ManualConfig + { + public AllowNonOptimized() + { + Add(JitOptimizationsValidator.DontFailOnError); // ALLOW NON-OPTIMIZED DLLS + + Add(DefaultConfig.Instance.GetLoggers().ToArray()); // manual config has no loggers by default + Add(DefaultConfig.Instance.GetExporters().ToArray()); // manual config has no exporters by default + Add(DefaultConfig.Instance.GetColumnProviders().ToArray()); // manual config has no columns by default + } + } + + public class Program + { + public static void Main(string[] args) + { +#if true + Summary summary; +// summary = BenchmarkRunner.Run(); +// summary = BenchmarkRunner.Run(); +// summary = BenchmarkRunner.Run(); +// summary = BenchmarkRunner.Run(); + summary = BenchmarkRunner.Run(); +#else + var test = new BookShelf10000BooksBenchmarks(); + test.Setup(); + for (var i = 0; i < 200; i++) + { +// test.DeserializeFromString(); + test.SerializeToString(); + } +#endif + } + } +} diff --git a/tests/ServiceStack.Text.Benchmarks/ServiceStack.Text.Benchmarks.csproj b/tests/ServiceStack.Text.Benchmarks/ServiceStack.Text.Benchmarks.csproj new file mode 100644 index 000000000..32aeb35ff --- /dev/null +++ b/tests/ServiceStack.Text.Benchmarks/ServiceStack.Text.Benchmarks.csproj @@ -0,0 +1,16 @@ + + + Exe + net6.0 + + + + + + + + + + $(DefineConstants);NETSTANDARD;NETCORE;NETCORE2_1 + + \ No newline at end of file diff --git a/tests/ServiceStack.Text.Tests/AdhocModelTests.cs b/tests/ServiceStack.Text.Tests/AdhocModelTests.cs index a8b2b65bc..ca1ae00c9 100644 --- a/tests/ServiceStack.Text.Tests/AdhocModelTests.cs +++ b/tests/ServiceStack.Text.Tests/AdhocModelTests.cs @@ -363,7 +363,7 @@ public void Can_exclude_properties_scoped() Assert.That(dto.ToJsv(), Is.EqualTo("{Key:Value}")); } - using (JsConfig.With(excludePropertyReferences: new[] { "Exclude.Id" })) + using (JsConfig.With(new Config { ExcludePropertyReferences = new[] { "Exclude.Id" }})) { Assert.That(dto.ToJson(), Is.EqualTo("{\"Key\":\"Value\"}")); Assert.That(dto.ToJsv(), Is.EqualTo("{Key:Value}")); diff --git a/tests/ServiceStack.Text.Tests/AutoMappingBenchmarks.cs b/tests/ServiceStack.Text.Tests/AutoMappingBenchmarks.cs index 20c1dfb0b..d5927dca1 100644 --- a/tests/ServiceStack.Text.Tests/AutoMappingBenchmarks.cs +++ b/tests/ServiceStack.Text.Tests/AutoMappingBenchmarks.cs @@ -172,7 +172,7 @@ public void Does_Convert_BenchSource() var to = from.ConvertTo(); //warmup to = from.ConvertTo(); - using (JsConfig.With(includePublicFields: true)) + using (JsConfig.With(new Config { IncludePublicFields = true })) { to.PrintDump(); from.PrintDump(); diff --git a/tests/ServiceStack.Text.Tests/AutoMappingCustomConverterTests.cs b/tests/ServiceStack.Text.Tests/AutoMappingCustomConverterTests.cs new file mode 100644 index 000000000..6a730d415 --- /dev/null +++ b/tests/ServiceStack.Text.Tests/AutoMappingCustomConverterTests.cs @@ -0,0 +1,237 @@ +using System; +using System.Collections.Generic; +using NUnit.Framework; + +namespace ServiceStack.Text.Tests +{ + public class AutoMappingCustomConverterTests + { + public class PersonWithWrappedDateOfBirth : User + { + public WrappedDateTimeOffset DateOfBirth { get; set; } + } + + public class PersonWithDateOfBirth : User + { + public DateTimeOffset DateOfBirth { get; set; } + } + + public class WrappedDateTimeOffset + { + private readonly DateTimeOffset dateTimeOffset; + + public WrappedDateTimeOffset(DateTimeOffset dateTimeOffset) + { + this.dateTimeOffset = dateTimeOffset; + } + + public DateTimeOffset ToDateTimeOffset() + { + return dateTimeOffset; + } + } + + [Test] + public void Can_convert_prop_with_CustomTypeConverter() + { + AutoMapping.RegisterConverter((WrappedDateTimeOffset from) => from.ToDateTimeOffset()); + + var map = new Dictionary + { + { "FirstName", "Foo" }, + { "LastName", "Bar" }, + { "DateOfBirth", new WrappedDateTimeOffset( + new DateTimeOffset(1971, 3, 23, 4, 30, 0, TimeSpan.Zero)) } + }; + + var personWithDoB = map.FromObjectDictionary(); + + Assert.That(personWithDoB.FirstName, Is.EqualTo("Foo")); + Assert.That(personWithDoB.LastName, Is.EqualTo("Bar")); + Assert.That(personWithDoB.DateOfBirth, Is.Not.Null); + Assert.That(personWithDoB.DateOfBirth.Year, Is.EqualTo(1971)); + Assert.That(personWithDoB.DateOfBirth.Month, Is.EqualTo(3)); + Assert.That(personWithDoB.DateOfBirth.Day, Is.EqualTo(23)); + Assert.That(personWithDoB.DateOfBirth.Hour, Is.EqualTo(4)); + Assert.That(personWithDoB.DateOfBirth.Minute, Is.EqualTo(30)); + Assert.That(personWithDoB.DateOfBirth.Second, Is.EqualTo(0)); + + AutoMappingUtils.Reset(); + } + + [Test] + public void Can_Convert_Props_With_CustomTypeConverter() + { + AutoMapping.RegisterConverter((WrappedDateTimeOffset from) => from.ToDateTimeOffset()); + + var personWithWrappedDateOfBirth = new PersonWithWrappedDateOfBirth + { + FirstName = "Foo", + LastName = "Bar", + DateOfBirth = new WrappedDateTimeOffset( + new DateTimeOffset(1971, 3, 23, 4, 30, 0, TimeSpan.Zero)) + }; + + var personWithDoB = personWithWrappedDateOfBirth.ConvertTo(); + + Assert.That(personWithDoB.FirstName, Is.EqualTo("Foo")); + Assert.That(personWithDoB.LastName, Is.EqualTo("Bar")); + Assert.That(personWithDoB.DateOfBirth, Is.Not.Null); + Assert.That(personWithDoB.DateOfBirth.Year, Is.EqualTo(1971)); + Assert.That(personWithDoB.DateOfBirth.Month, Is.EqualTo(3)); + Assert.That(personWithDoB.DateOfBirth.Day, Is.EqualTo(23)); + Assert.That(personWithDoB.DateOfBirth.Hour, Is.EqualTo(4)); + Assert.That(personWithDoB.DateOfBirth.Minute, Is.EqualTo(30)); + Assert.That(personWithDoB.DateOfBirth.Second, Is.EqualTo(0)); + + AutoMappingUtils.Reset(); + } + + [Test] + public void Can_Convert_Anonymous_Types_With_CustomTypeConverter() + { + AutoMapping.RegisterConverter((DateTimeOffset from) => new WrappedDateTimeOffset(from)); + + var personWithDateOfBirth = new + { + FirstName = "Foo", + LastName = "Bar", + DateOfBirth = new DateTimeOffset(1971, 3, 23, 4, 30, 0, TimeSpan.Zero) + }; + + var personWithWrappedDoB = personWithDateOfBirth.ConvertTo(); + + Assert.That(personWithWrappedDoB.FirstName, Is.EqualTo("Foo")); + Assert.That(personWithWrappedDoB.LastName, Is.EqualTo("Bar")); + Assert.That(personWithWrappedDoB.DateOfBirth, Is.Not.Null); + var dto = personWithWrappedDoB.DateOfBirth.ToDateTimeOffset(); + Assert.That(dto, Is.Not.Null); + Assert.That(dto.Year, Is.EqualTo(1971)); + Assert.That(dto.Month, Is.EqualTo(3)); + Assert.That(dto.Day, Is.EqualTo(23)); + Assert.That(dto.Hour, Is.EqualTo(4)); + Assert.That(dto.Minute, Is.EqualTo(30)); + Assert.That(dto.Second, Is.EqualTo(0)); + + AutoMappingUtils.Reset(); + } + + // TODO: Work out a way we can capture and monitor the Trace log in the test, as exceptions are caught in Populate method + [Test] + public void Should_Not_Throw_Exception_When_Multiple_Same_Type_CustomTypeConverters_Found() + { + AutoMapping.RegisterConverter((DateTimeOffset from) => new WrappedDateTimeOffset(from)); + + var personWithWrappedDateOfBirth = new PersonWithWrappedDateOfBirth + { + DateOfBirth = new WrappedDateTimeOffset( + new DateTimeOffset(1971, 3, 23, 4, 30, 0, TimeSpan.Zero)) + }; + + var personWithDoB = personWithWrappedDateOfBirth.ConvertTo(); + + // Object returned but mapping failed + Assert.That(personWithDoB.FirstName, Is.Null); + Assert.That(personWithDoB.LastName, Is.Null); + Assert.That(personWithDoB.DateOfBirth, Is.EqualTo(DateTimeOffset.MinValue)); + + AutoMappingUtils.Reset(); + } + + [Test] + public void Can_Convert_POCO_collections_with_custom_Converter() + { + AutoMapping.RegisterConverter((User from) => { + var to = from.ConvertTo(skipConverters:true); // avoid infinite recursion + to.FirstName += "!"; + to.LastName += "!"; + return to; + }); + AutoMapping.RegisterConverter((Car from) => $"{from.Name} ({from.Age})"); + + var user = new User { + FirstName = "John", + LastName = "Doe", + Car = new Car { Name = "BMW X6", Age = 3 } + }; + var users = new UsersData { + Id = 1, + User = user, + UsersList = { user }, + UsersMap = { {1,user} } + }; + + var dtoUsers = users.ConvertTo(); + Assert.That(dtoUsers.Id, Is.EqualTo(users.Id)); + + void AssertUser(UserDto userDto) + { + Assert.That(userDto.FirstName, Is.EqualTo(user.FirstName + "!")); + Assert.That(userDto.LastName, Is.EqualTo(user.LastName + "!")); + Assert.That(userDto.Car, Is.EqualTo($"{user.Car.Name} ({user.Car.Age})")); + } + AssertUser(user.ConvertTo()); + AssertUser(dtoUsers.User); + AssertUser(dtoUsers.UsersList[0]); + AssertUser(dtoUsers.UsersMap[1]); + + AutoMappingUtils.Reset(); + } + + [Test] + public void Does_ignore_POCO_mappings() + { + AutoMapping.IgnoreMapping(); + + var user = new User { + FirstName = "John", + LastName = "Doe", + Car = new Car { Name = "BMW X6", Age = 3 } + }; + var users = new UsersData { + Id = 1, + User = user, + UsersList = { user }, + UsersMap = {{1,user}} + }; + + var dtoUsers = users.ConvertTo(); + Assert.That(dtoUsers.Id, Is.EqualTo(users.Id)); + + Assert.That(user.ConvertTo(), Is.Null); + Assert.That(dtoUsers.User, Is.Null); + Assert.That(dtoUsers.UsersList, Is.Empty); + Assert.That(dtoUsers.UsersMap, Is.Empty); + + AutoMappingUtils.Reset(); + } + + [Test] + public void Does_ignore_collection_mappings() + { + AutoMapping.IgnoreMapping, List>(); + AutoMapping.IgnoreMapping, Dictionary>(); + + var users = new UsersData { + Id = 1, + UsersList = new List { + new User { + FirstName = "John", + LastName = "Doe", + Car = new Car { Name = "BMW X6", Age = 3 } + } + } + }; + + var dtoUsers = users.ConvertTo(); + dtoUsers.PrintDump(); + + Assert.That(dtoUsers.Id, Is.EqualTo(users.Id)); + Assert.That(dtoUsers.UsersList, Is.Empty); + Assert.That(dtoUsers.UsersMap, Is.Empty); + + AutoMappingUtils.Reset(); + } + + } +} \ No newline at end of file diff --git a/tests/ServiceStack.Text.Tests/AutoMappingObjectDictionaryTests.cs b/tests/ServiceStack.Text.Tests/AutoMappingObjectDictionaryTests.cs index 385434a8c..34a710411 100644 --- a/tests/ServiceStack.Text.Tests/AutoMappingObjectDictionaryTests.cs +++ b/tests/ServiceStack.Text.Tests/AutoMappingObjectDictionaryTests.cs @@ -1,12 +1,26 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; using Northwind.Common.DataModel; using NUnit.Framework; +using ServiceStack.Common.Tests.Models; namespace ServiceStack.Text.Tests { [TestFixture] public class AutoMappingObjectDictionaryTests { + [Test] + public void Can_convert_Car_to_ObjectDictionary_WithMapper() + { + var dto = new DtoWithEnum { Name = "Dan", Color = Color.Blue}; + var map = dto.ToObjectDictionary((k,v) => k == nameof(Color) ? v.ToString().ToLower() : v); + + Assert.That(map["Color"], Is.EqualTo(Color.Blue.ToString().ToLower())); + Assert.That(map["Name"], Is.EqualTo(dto.Name)); + } + [Test] public void Can_convert_Car_to_ObjectDictionary() { @@ -79,6 +93,25 @@ public void Can_Convert_from_ObjectDictionary_with_Different_Types_with_camelCas Assert.That(fromDict.Car.Name, Is.EqualTo("SubCar")); } + [Test] + public void Can_Convert_from_ObjectDictionary_with_Read_Only_Dictionary() + { + var map = new Dictionary + { + { "FirstName", 1 }, + { "LastName", true }, + { "Car", new SubCar { Age = 10, Name = "SubCar", Custom = "Custom"} }, + }; + + var readOnlyMap = new ReadOnlyDictionary(map); + + var fromDict = (User)readOnlyMap.FromObjectDictionary(typeof(User)); + Assert.That(fromDict.FirstName, Is.EqualTo("1")); + Assert.That(fromDict.LastName, Is.EqualTo(bool.TrueString)); + Assert.That(fromDict.Car.Age, Is.EqualTo(10)); + Assert.That(fromDict.Car.Name, Is.EqualTo("SubCar")); + } + public class QueryCustomers : QueryDb { public string CustomerId { get; set; } @@ -106,6 +139,460 @@ public void Can_convert_from_ObjectDictionary_into_AutoQuery_DTO() Assert.That(request.Take, Is.EqualTo(5)); Assert.That(request.Meta, Is.EquivalentTo(new Dictionary {{"foo", "bar"}})); } + + public class PersonWithIdentities + { + public string Name { get; set; } + public List OtherNames { get;set; } + } + + public class OtherName + { + public string Name { get; set; } + } + + [Test] + public void Can_Convert_from_ObjectDictionary_Containing_Another_Object_Dictionary() + { + var map = new Dictionary + { + { "name", "Foo" }, + { "otherNames", new List + { + new Dictionary { { "name", "Fu" } }, + new Dictionary { { "name", "Fuey" } } + } + } + }; + + var fromDict = map.FromObjectDictionary(); + + Assert.That(fromDict.Name, Is.EqualTo("Foo")); + Assert.That(fromDict.OtherNames.Count, Is.EqualTo(2)); + Assert.That(fromDict.OtherNames.First().Name, Is.EqualTo("Fu")); + Assert.That(fromDict.OtherNames.Last().Name, Is.EqualTo("Fuey")); + + var toDict = fromDict.ToObjectDictionary(); + Assert.That(toDict["Name"], Is.EqualTo("Foo")); + Assert.That(toDict["OtherNames"], Is.EqualTo(fromDict.OtherNames)); + } + public class Employee + { + public string FirstName { get; set; } + public string LastName { get; set; } + public string DisplayName { get; set; } + } + + [Test] + public void Can_create_new_object_using_MergeIntoObjectDictionary() + { + var customer = new User { FirstName = "John", LastName = "Doe" }; + var map = customer.MergeIntoObjectDictionary(new { Initial = "Z" }); + map["DisplayName"] = map["FirstName"] + " " + map["Initial"] + " " + map["LastName"]; + var employee = map.FromObjectDictionary(); + + Assert.That(employee.DisplayName, Is.EqualTo("John Z Doe")); + } + + [Test] + public void Can_create_new_object_from_merged_objects() + { + var customer = new User { FirstName = "John", LastName = "Doe" }; + var map = MergeObjects(customer, new { Initial = "Z" }); + map["DisplayName"] = map["FirstName"] + " " + map["Initial"] + " " + map["LastName"]; + var employee = map.FromObjectDictionary(); + + Dictionary MergeObjects(params object[] sources) { + var to = new Dictionary(); + sources.Each(x => x.ToObjectDictionary().Each(entry => to[entry.Key] = entry.Value)); + return to; + } + + Assert.That(employee.DisplayName, Is.EqualTo("John Z Doe")); + } + + [Test, TestCaseSource(nameof(TestDataFromObjectDictionaryWithNullableTypes))] + public void Can_Convert_from_ObjectDictionary_with_Nullable_Properties( + Dictionary map, + ModelWithFieldsOfNullableTypes expected) + { + var actual = map.FromObjectDictionary(); + + ModelWithFieldsOfNullableTypes.AssertIsEqual(actual, expected); + } + + private static IEnumerable TestDataFromObjectDictionaryWithNullableTypes + { + get + { + var defaults = ModelWithFieldsOfNullableTypes.CreateConstant(1); + + yield return new TestCaseData( + new Dictionary + { + { "Id", defaults.Id }, + { "NId", defaults.NId }, + { "NLongId", defaults.NLongId }, + { "NGuid", defaults.NGuid }, + { "NBool", defaults.NBool }, + { "NDateTime", defaults.NDateTime }, + { "NFloat", defaults.NFloat }, + { "NDouble", defaults.NDouble }, + { "NDecimal", defaults.NDecimal }, + { "NTimeSpan", defaults.NTimeSpan } + }, + defaults).SetName("All values populated"); + + yield return new TestCaseData( + new Dictionary + { + { "Id", defaults.Id.ToString() }, + { "NId", defaults.NId.ToString() }, + { "NLongId", defaults.NLongId.ToString() }, + { "NGuid", defaults.NGuid.ToString() }, + { "NBool", defaults.NBool.ToString() }, + { "NDateTime", defaults.NDateTime?.ToString("o") }, + { "NFloat", defaults.NFloat.ToString() }, + { "NDouble", defaults.NDouble.ToString() }, + { "NDecimal", defaults.NDecimal.ToString() }, + { "NTimeSpan", defaults.NTimeSpan.ToString() } + }, + defaults).SetName("All values populated as strings"); + + yield return new TestCaseData( + new Dictionary + { + { "Id", defaults.Id }, + { "NId", null }, + { "NLongId", null }, + { "NGuid", null }, + { "NBool", null }, + { "NDateTime", null }, + { "NFloat", null }, + { "NDouble", null }, + { "NDecimal", null }, + { "NTimeSpan", null } + }, + new ModelWithFieldsOfNullableTypes + { + Id = defaults.Id + }).SetName("Nullables set to null"); + + yield return new TestCaseData( + new Dictionary + { + { "Id", defaults.Id } + }, + new ModelWithFieldsOfNullableTypes + { + Id = defaults.Id + }).SetName("Nullables unassigned"); + + yield return new TestCaseData( + new Dictionary + { + { "Id", defaults.Id }, + { "NLongId", 2 }, + { "NFloat", "3.1" }, + { "NDecimal", 4.2d }, + { "NTimeSpan", null } + }, + new ModelWithFieldsOfNullableTypes + { + Id = defaults.Id, + NLongId = 2, + NFloat = 3.1f, + NDecimal = 4.2m + }).SetName("Mixed properties"); + + yield return new TestCaseData( + new Dictionary + { + { "Id", defaults.Id }, + { "NMadeUp", 99.9 }, + { "NLongId", 2 }, + { "NFloat", "3.1" }, + { "NRandom", "RANDOM" }, + { "NDecimal", 4.2d }, + { "NTimeSpan", null }, + { "NNull", null } + }, + new ModelWithFieldsOfNullableTypes + { + Id = defaults.Id, + NLongId = 2, + NFloat = 3.1f, + NDecimal = 4.2m + }).SetName("Mixed properties with some foreign key/values"); + } + } + + [Test] + public void Can_Convert_from_ObjectDictionary_with_Nullable_Collection_Properties() + { + var map = new Dictionary + { + { "Id", 1 }, + { "Users", new[] { new User { FirstName = "Foo", LastName = "Bar", Car = new Car { Name = "Jag", Age = 25 }}}}, + { "Cars", new List { new Car { Name = "Toyota", Age = 2 }, new Car { Name = "Lexus", Age = 1 }}}, + { "Colors", null } + }; + + var actual = map.FromObjectDictionary(); + + Assert.That(actual.Id, Is.EqualTo(1)); + Assert.That(actual.Users, Is.Not.Null); + Assert.That(actual.Users.Count(), Is.EqualTo(1)); + var user = actual.Users.Single(); + Assert.That(user.FirstName, Is.EqualTo("Foo")); + Assert.That(user.LastName, Is.EqualTo("Bar")); + Assert.That(user.Car, Is.Not.Null); + Assert.That(user.Car.Name, Is.EqualTo("Jag")); + Assert.That(user.Car.Age, Is.EqualTo(25)); + Assert.That(actual.Cars, Is.Not.Null); + Assert.That(actual.Cars.Count, Is.EqualTo(2)); + var firstCar = actual.Cars.First(); + Assert.That(firstCar.Name, Is.EqualTo("Toyota")); + Assert.That(firstCar.Age, Is.EqualTo(2)); + var secondCar = actual.Cars.Last(); + Assert.That(secondCar.Name, Is.EqualTo("Lexus")); + Assert.That(secondCar.Age, Is.EqualTo(1)); + Assert.That(actual.Colors, Is.Null); + } + + public class ModelWithCollectionsOfNullableTypes + { + public int Id { get; set; } + public IEnumerable Users { get; set; } + public Car[] Cars { get; set; } + public IList Colors { get; set; } + } + + public class ModelWithTimeSpan + { + public TimeSpan Time { get; set; } + } + + public class ModelWithLong + { + public long Time { get; set; } + } + + [Test] + public void FromObjectDictionary_does_Convert_long_to_TimeSpan() + { + var time = new TimeSpan(1,1,1,1); + var map = new Dictionary { + [nameof(ModelWithTimeSpan.Time)] = time.Ticks + }; + + var dto = map.FromObjectDictionary(); + Assert.That(dto.Time, Is.EqualTo(time)); + + map = new Dictionary { + [nameof(ModelWithTimeSpan.Time)] = time + }; + + var dtoLong = map.FromObjectDictionary(); + Assert.That(dtoLong.Time, Is.EqualTo(time.Ticks)); + } + + public enum CefLogSeverity + { + Default, + Verbose, + Debug = Verbose, + Info, + Warning, + Error, + ErrorReport, + Disable = 99, + } + + public class CefSettings + { + public CefLogSeverity LogSeverity { get; set; } + } + + public class CefConfig + { + public string WindowTitle { get; set; } + public string Icon { get; set; } + public string CefPath { get; set; } + public string[] Args { get; set; } + public CefSettings CefSettings { get; set; } + public string StartUrl { get; set; } + public int? X { get; set; } + public int? Y { get; set; } + public int Width { get; set; } + public int Height { get; set; } + public bool CenterToScreen { get; set; } + public bool HideConsoleWindow { get; set; } + } + + [Test] + public void Can_use_PopulateInstance_to_populate_enum() + { + var map = new Dictionary { + ["LogSeverity"] = "Verbose" + }; + + var config = new CefConfig { CefSettings = new CefSettings { LogSeverity = CefLogSeverity.Info } }; + map.PopulateInstance(config.CefSettings); + + Assert.That(config.CefSettings.LogSeverity, Is.EqualTo(CefLogSeverity.Verbose)); + } + + [Test] + public void Can_convert_Dictionary_FromObjectDictionary() + { + var dict = new Dictionary(); + var to = dict.FromObjectDictionary>(); + Assert.That(to == dict); + } + + [Test] + public void Can_convert_inner_dictionary() + { + var map = new Dictionary + { + { "FirstName", "Foo" }, + { "LastName", "Bar" }, + { "Car", new Dictionary + { + { "Name", "Tesla" }, + { "Age", 2 } + }} + }; + + var user = map.FromObjectDictionary(); + + Assert.That(user.FirstName, Is.EqualTo("Foo")); + Assert.That(user.LastName, Is.EqualTo("Bar")); + Assert.That(user.Car, Is.Not.Null); + Assert.That(user.Car.Name, Is.EqualTo("Tesla")); + Assert.That(user.Car.Age, Is.EqualTo(2)); + } + + [Test] + public void Can_convert_inner_collection_of_dictionaries() + { + var map = new Dictionary + { + { "Name", "Tesla" }, + { "Age", "2" }, + { "Specs", new List> + { + new Dictionary + { + {"Item", "Model"}, + {"Value", "S"} + }, + new Dictionary + { + {"Item", "Engine"}, + {"Value", "Electric"} + }, + new Dictionary + { + {"Item", "Color"}, + {"Value", "Red"} + }, + new Dictionary + { + {"Item", "PowerKW"}, + {"Value", 285} + }, + new Dictionary + { + {"Item", "TorqueNm"}, + {"Value", 430} + }, + }} + }; + + var carWithSpecs = map.FromObjectDictionary(); + + Assert.That(carWithSpecs.Name, Is.EqualTo("Tesla")); + Assert.That(carWithSpecs.Age, Is.EqualTo(2)); + Assert.That(carWithSpecs.Specs.Count, Is.EqualTo(5)); + var model = carWithSpecs.Specs.SingleOrDefault(s => s.Item == "Model"); + Assert.That(model, Is.Not.Null); + Assert.That(model.Value, Is.EqualTo("S")); + var engine = carWithSpecs.Specs.SingleOrDefault(s => s.Item == "Engine"); + Assert.That(engine, Is.Not.Null); + Assert.That(engine.Value, Is.EqualTo("Electric")); + var color = carWithSpecs.Specs.SingleOrDefault(s => s.Item == "Color"); + Assert.That(color, Is.Not.Null); + Assert.That(color.Value, Is.EqualTo("Red")); + var power = carWithSpecs.Specs.SingleOrDefault(s => s.Item == "PowerKW"); + Assert.That(power, Is.Not.Null); + Assert.That(power.Value, Is.EqualTo("285")); + var torque = carWithSpecs.Specs.SingleOrDefault(s => s.Item == "TorqueNm"); + Assert.That(torque, Is.Not.Null); + Assert.That(torque.Value, Is.EqualTo("430")); + } + + [Test] + public void Can_convert_inner_array_of_dictionaries() + { + var map = new Dictionary + { + { "Name", "Tesla" }, + { "Age", "2" }, + { "Specs", new[] + { + new Dictionary + { + {"Item", "Model"}, + {"Value", "S"} + }, + new Dictionary + { + {"Item", "Engine"}, + {"Value", "Electric"} + }, + new Dictionary + { + {"Item", "Color"}, + {"Value", "Red"} + }, + new Dictionary + { + {"Item", "PowerKW"}, + {"Value", 285} + }, + new Dictionary + { + {"Item", "TorqueNm"}, + {"Value", 430} + }, + }} + }; + + var carWithSpecs = map.FromObjectDictionary(); + + Assert.That(carWithSpecs.Name, Is.EqualTo("Tesla")); + Assert.That(carWithSpecs.Age, Is.EqualTo(2)); + Assert.That(carWithSpecs.Specs.Count, Is.EqualTo(5)); + var model = carWithSpecs.Specs.SingleOrDefault(s => s.Item == "Model"); + Assert.That(model, Is.Not.Null); + Assert.That(model.Value, Is.EqualTo("S")); + var engine = carWithSpecs.Specs.SingleOrDefault(s => s.Item == "Engine"); + Assert.That(engine, Is.Not.Null); + Assert.That(engine.Value, Is.EqualTo("Electric")); + var color = carWithSpecs.Specs.SingleOrDefault(s => s.Item == "Color"); + Assert.That(color, Is.Not.Null); + Assert.That(color.Value, Is.EqualTo("Red")); + var power = carWithSpecs.Specs.SingleOrDefault(s => s.Item == "PowerKW"); + Assert.That(power, Is.Not.Null); + Assert.That(power.Value, Is.EqualTo("285")); + var torque = carWithSpecs.Specs.SingleOrDefault(s => s.Item == "TorqueNm"); + Assert.That(torque, Is.Not.Null); + Assert.That(torque.Value, Is.EqualTo("430")); + } } + + } \ No newline at end of file diff --git a/tests/ServiceStack.Text.Tests/AutoMappingPopulatorTests.cs b/tests/ServiceStack.Text.Tests/AutoMappingPopulatorTests.cs new file mode 100644 index 000000000..54aca58dd --- /dev/null +++ b/tests/ServiceStack.Text.Tests/AutoMappingPopulatorTests.cs @@ -0,0 +1,73 @@ +using NUnit.Framework; + +namespace ServiceStack.Text.Tests +{ + public class AutoMappingPopulatorTests + { + public class UserData + { + public string FirstName { get; set; } + public string LastName { get; set; } + public Car Car { get; set; } + } + + private static UserData CreateUser() => + new UserData { + FirstName = "John", + LastName = "Doe", + Car = new Car {Name = "BMW X6", Age = 3} + }; + + [Test] + public void Does_call_populator_for_PopulateWith() + { + AutoMapping.RegisterPopulator((UserDto target, UserData source) => + target.LastName += "?!"); + + var user = CreateUser(); + var dtoUser = new UserDto().PopulateWith(user); + + Assert.That(dtoUser.FirstName, Is.EqualTo(user.FirstName)); + Assert.That(dtoUser.LastName, Is.EqualTo(user.LastName + "?!")); + } + + [Test] + public void Does_call_populator_for_PopulateWithNonDefaultValues() + { + AutoMapping.RegisterPopulator((UserDto target, UserData source) => + target.LastName += "?!"); + + var user = CreateUser(); + var dtoUser = new UserDto().PopulateWithNonDefaultValues(user); + + Assert.That(dtoUser.FirstName, Is.EqualTo(user.FirstName)); + Assert.That(dtoUser.LastName, Is.EqualTo(user.LastName + "?!")); + } + + [Test] + public void Does_call_populator_for_PopulateFromPropertiesWithoutAttribute() + { + AutoMapping.RegisterPopulator((UserDto target, UserData source) => + target.LastName += "?!"); + + var user = CreateUser(); + var dtoUser = new UserDto().PopulateFromPropertiesWithoutAttribute(user, typeof(IgnoreAttribute)); + + Assert.That(dtoUser.FirstName, Is.EqualTo(user.FirstName)); + Assert.That(dtoUser.LastName, Is.EqualTo(user.LastName + "?!")); + } + + [Test] + public void Does_call_populator_for_ConvertTo() + { + AutoMapping.RegisterPopulator((UserDto target, UserData source) => + target.LastName += "?!"); + + var user = CreateUser(); + var dtoUser = user.ConvertTo(); + + Assert.That(dtoUser.FirstName, Is.EqualTo(user.FirstName)); + Assert.That(dtoUser.LastName, Is.EqualTo(user.LastName + "?!")); + } + } +} \ No newline at end of file diff --git a/tests/ServiceStack.Text.Tests/AutoMappingTests.cs b/tests/ServiceStack.Text.Tests/AutoMappingTests.cs index 45901159c..9701c7478 100644 --- a/tests/ServiceStack.Text.Tests/AutoMappingTests.cs +++ b/tests/ServiceStack.Text.Tests/AutoMappingTests.cs @@ -4,10 +4,13 @@ using System.Linq; using System.Linq.Expressions; using System.Runtime.Serialization; +using System.Xml.Linq; #if !NETCORE using System.Web.Script.Serialization; #endif using NUnit.Framework; +using ServiceStack.Common.Tests.Models; +using ServiceStack.DataAnnotations; using ServiceStack.Text.Tests.DynamicModels; using ServiceStack.Web; @@ -42,6 +45,17 @@ public class SubCar : Car public string Custom { get; set; } } + public class CarWithSpecs : Car + { + public List Specs { get; set; } + } + + public class CarSpec + { + public string Item { get; set; } + public string Value { get; set; } + } + public class UserDto { public string FirstName { get; set; } @@ -49,6 +63,23 @@ public class UserDto public string Car { get; set; } } + public class UsersData + { + public int Id { get; set; } + + public User User { get; set; } + public List UsersList { get; set; } = new List(); + public Dictionary UsersMap { get; set; } = new Dictionary(); + } + public class UsersDto + { + public int Id { get; set; } + + public UserDto User { get; set; } + public List UsersList { get; set; } = new List(); + public Dictionary UsersMap { get; set; } = new Dictionary(); + } + public enum Color { Red, @@ -100,6 +131,12 @@ public class NullableConversionDto public decimal? Amount { get; set; } } + public class DtoWithEnum + { + public string Name { get; set; } + public Color Color { get; set; } + } + public class NullableEnumConversion { public Color Color { get; set; } @@ -168,18 +205,44 @@ public class ModelWithIgnoredFields } public class ReadOnlyAttribute : AttributeBase { } + + [EnumAsInt] + public enum MyEnumAsInt + { + ZeroValue = 0, + OneValue = 1, + DefaultValue = 2, + }; + + class MyEnumAsIntSource + { + public MyEnumAsInt MyEnumAsInt { get; set; } = MyEnumAsInt.DefaultValue; + } + + class MyEnumAsIntTarget + { + public MyEnumAsInt MyEnumAsInt { get; set; } = MyEnumAsInt.DefaultValue; + } [TestFixture] public class AutoMappingTests { + [Test] + public void Can_convert_Default_Enum_Values() + { + var from = new MyEnumAsIntSource { MyEnumAsInt = MyEnumAsInt.ZeroValue }; + var to = from.ConvertTo(); + + Assert.That(to.MyEnumAsInt, Is.EqualTo(from.MyEnumAsInt)); + } + [Test] public void Does_populate() { - var user = new User() - { + var user = new User { FirstName = "Demis", LastName = "Bellot", - Car = new Car() { Name = "BMW X6", Age = 3 } + Car = new Car { Name = "BMW X6", Age = 3 } }; var userDto = new UserDto().PopulateWith(user); @@ -536,7 +599,7 @@ public void Does_convert_models_with_collections() Assert.That(MatchesUsers(array, from.Collection.ConvertTo())); Assert.That(MatchesUsers(array, from.Collection.ConvertTo>())); - var hashset = from.Collection.ToHashSet(); + var hashset = from.Collection.ToSet(); Assert.That(MatchesUsers(hashset, from.Collection.ConvertTo>())); Assert.That(MatchesUsers(hashset, from.Collection.ConvertTo>())); Assert.That(MatchesUsers(hashset, from.Collection.ConvertTo())); @@ -687,6 +750,175 @@ public void Can_create_Dictionary_default_value() var obj = (Dictionary)AutoMappingUtils.CreateDefaultValue(typeof(Dictionary), new Dictionary()); Assert.That(obj, Is.Not.Null); } + + [Test] + public void Can_get_generic_types_from_Dictionary_or_KVPs() + { + typeof(Dictionary).GetKeyValuePairsTypes(out var keyType, out var valueType); + Assert.That(keyType, Is.EqualTo(typeof(string))); + Assert.That(valueType, Is.EqualTo(typeof(object))); + + typeof(IEnumerable>).GetKeyValuePairsTypes(out keyType, out valueType); + Assert.That(keyType, Is.EqualTo(typeof(string))); + Assert.That(valueType, Is.EqualTo(typeof(object))); + + typeof(List>).GetKeyValuePairsTypes(out keyType, out valueType); + Assert.That(keyType, Is.EqualTo(typeof(string))); + Assert.That(valueType, Is.EqualTo(typeof(object))); + } + + [Test] + public void Can_convert_between_different_types_of_Dictionaries_and_KVP_values() + { + var genericMap = new Dictionary { + { "a", 1 } + }; + var stringMap = new Dictionary { + {"a", "1"} + }; + var intMap = new Dictionary { + {"a", 1} + }; + var kvps = new List> { + new KeyValuePair("a", 1) + }; + var stringKvps = new List> { + new KeyValuePair("a", "1") + }; + var objDict = new ObjectDictionary { + { "a", 1 } + }; + + var genericMapStringValue = new Dictionary { + { "a", "1" } + }; + var objDictStringValue = new ObjectDictionary { + { "a", "1" } + }; + var kvpsStringValue = new List> { + new KeyValuePair("a", "1") + }; + + Assert.That(genericMap.ConvertTo>(), Is.EquivalentTo(genericMap)); + Assert.That(genericMap.ConvertTo>(), Is.EquivalentTo(stringMap)); + Assert.That(genericMap.ConvertTo>(), Is.EquivalentTo(intMap)); + Assert.That(genericMap.ConvertTo>>(), Is.EquivalentTo(kvps)); + Assert.That(genericMap.ConvertTo>>(), Is.EquivalentTo(stringKvps)); + Assert.That(genericMap.ConvertTo(), Is.EquivalentTo(objDict)); + + Assert.That(stringMap.ConvertTo>(), Is.EquivalentTo(genericMapStringValue)); + Assert.That(stringMap.ConvertTo>(), Is.EquivalentTo(stringMap)); + Assert.That(stringMap.ConvertTo>(), Is.EquivalentTo(intMap)); + Assert.That(stringMap.ConvertTo>>(), Is.EquivalentTo(kvpsStringValue)); + Assert.That(stringMap.ConvertTo>>(), Is.EquivalentTo(stringKvps)); + Assert.That(stringMap.ConvertTo(), Is.EquivalentTo(objDictStringValue)); + + Assert.That(intMap.ConvertTo>(), Is.EquivalentTo(genericMap)); + Assert.That(intMap.ConvertTo>(), Is.EquivalentTo(stringMap)); + Assert.That(intMap.ConvertTo>(), Is.EquivalentTo(intMap)); + Assert.That(intMap.ConvertTo>>(), Is.EquivalentTo(kvps)); + Assert.That(intMap.ConvertTo>>(), Is.EquivalentTo(stringKvps)); + Assert.That(intMap.ConvertTo(), Is.EquivalentTo(objDict)); + + Assert.That(kvps.ConvertTo>(), Is.EquivalentTo(genericMap)); + Assert.That(kvps.ConvertTo>(), Is.EquivalentTo(stringMap)); + Assert.That(kvps.ConvertTo>(), Is.EquivalentTo(intMap)); + Assert.That(kvps.ConvertTo>>(), Is.EquivalentTo(kvps)); + Assert.That(kvps.ConvertTo>>(), Is.EquivalentTo(stringKvps)); + Assert.That(kvps.ConvertTo(), Is.EquivalentTo(objDict)); + + Assert.That(stringKvps.ConvertTo>(), Is.EquivalentTo(genericMapStringValue)); + Assert.That(stringKvps.ConvertTo>(), Is.EquivalentTo(stringMap)); + Assert.That(stringKvps.ConvertTo>(), Is.EquivalentTo(intMap)); + Assert.That(stringKvps.ConvertTo>>(), Is.EquivalentTo(kvpsStringValue)); + Assert.That(stringKvps.ConvertTo>>(), Is.EquivalentTo(stringKvps)); + Assert.That(stringKvps.ConvertTo(), Is.EquivalentTo(objDictStringValue)); + } + + [Test] + public void Can_convert_between_different_types_of_Dictionaries_and_KVP_keys() + { + var genericMap = new Dictionary { + { 1, "a" } + }; + var stringMap = new Dictionary { + {"1","a"} + }; + var intMap = new Dictionary { + {1,"a"} + }; + var kvps = new List> { + new KeyValuePair(1, "a") + }; + var stringKvps = new List> { + new KeyValuePair("1","a") + }; + + var genericMapStringValue = new Dictionary { + { "1","a" } + }; + var kvpsStringValue = new List> { + new KeyValuePair("1","a") + }; + + Assert.That(genericMap.ConvertTo>(), Is.EquivalentTo(genericMap)); + Assert.That(genericMap.ConvertTo>(), Is.EquivalentTo(stringMap)); + Assert.That(genericMap.ConvertTo>(), Is.EquivalentTo(intMap)); + Assert.That(genericMap.ConvertTo>>(), Is.EquivalentTo(kvps)); + Assert.That(genericMap.ConvertTo>>(), Is.EquivalentTo(stringKvps)); + + Assert.That(stringMap.ConvertTo>(), Is.EquivalentTo(genericMapStringValue)); + Assert.That(stringMap.ConvertTo>(), Is.EquivalentTo(stringMap)); + Assert.That(stringMap.ConvertTo>(), Is.EquivalentTo(intMap)); + Assert.That(stringMap.ConvertTo>>(), Is.EquivalentTo(kvpsStringValue)); + Assert.That(stringMap.ConvertTo>>(), Is.EquivalentTo(stringKvps)); + + Assert.That(intMap.ConvertTo>(), Is.EquivalentTo(genericMap)); + Assert.That(intMap.ConvertTo>(), Is.EquivalentTo(stringMap)); + Assert.That(intMap.ConvertTo>(), Is.EquivalentTo(intMap)); + Assert.That(intMap.ConvertTo>>(), Is.EquivalentTo(kvps)); + Assert.That(intMap.ConvertTo>>(), Is.EquivalentTo(stringKvps)); + + Assert.That(kvps.ConvertTo>(), Is.EquivalentTo(genericMap)); + Assert.That(kvps.ConvertTo>(), Is.EquivalentTo(stringMap)); + Assert.That(kvps.ConvertTo>(), Is.EquivalentTo(intMap)); + Assert.That(kvps.ConvertTo>>(), Is.EquivalentTo(kvps)); + Assert.That(kvps.ConvertTo>>(), Is.EquivalentTo(stringKvps)); + + Assert.That(stringKvps.ConvertTo>(), Is.EquivalentTo(genericMapStringValue)); + Assert.That(stringKvps.ConvertTo>(), Is.EquivalentTo(stringMap)); + Assert.That(stringKvps.ConvertTo>(), Is.EquivalentTo(intMap)); + Assert.That(stringKvps.ConvertTo>>(), Is.EquivalentTo(kvpsStringValue)); + Assert.That(stringKvps.ConvertTo>>(), Is.EquivalentTo(stringKvps)); + } + + class HasObject + { + public object Value { get; set; } + } + + [Test] + public void Can_convert_dictionary_to_HasObject() + { + var objDict = new ObjectDictionary { + ["value"] = new ObjectDictionary { + ["a"] = 1 + } + }; + + var to = objDict.FromObjectDictionary(); + Assert.That(to.Value, Is.EqualTo(objDict["value"])); + + var kvps = new ObjectDictionary { + ["value"] = new List> { + new KeyValuePair("a",1) + } + }; + + to = objDict.FromObjectDictionary(); + Assert.That(to.Value, Is.EqualTo(objDict["value"])); + } + } public enum ClassWithEnumType @@ -758,5 +990,613 @@ public void Can_create_DTO_with_Stream() Assert.That(requestObj, Is.Not.Null); } + + public class ModelWithUri + { + public Uri Uri { get; set; } + } + public class ModelWithUriString + { + public string Uri { get; set; } + } + + [Test] + public void Does_map_Uri() + { + var dto = new ModelWithUri { Uri = new Uri("http://a.com") }; + + var to = new ModelWithUri().PopulateWithNonDefaultValues(dto); + Assert.That(to.Uri, Is.EqualTo(dto.Uri)); + + var toString = new ModelWithUriString().PopulateWithNonDefaultValues(dto); + Assert.That(toString.Uri, Is.EqualTo(dto.Uri.AbsoluteUri)); + } + + public class ViewModel + { + public string Public { get; set; } + + [IgnoreOnSelect] + public string Private { get; set; } + } + + [Test] + public void Does_convert_same_model_without_Attributes() + { + var vm = new ViewModel().PopulateFromPropertiesWithoutAttribute( + new ViewModel { Public = "Public", Private = "Private" }, + typeof(IgnoreOnSelectAttribute)); + + Assert.That(vm.Public, Is.EqualTo("Public")); + Assert.That(vm.Private, Is.Null); + } + + [Test] + public void Can_use_ToObjectDictionary_to_Remove_Property_with_Attribute() + { + var vm = new ViewModel {Public = "Public", Private = "Private"}; + var map = vm.ToObjectDictionary(); + vm.GetType().GetProperties() + .Where(x => x.HasAttribute()) + .Each(x => map.Remove(x.Name)); + + vm = map.FromObjectDictionary(); + Assert.That(vm.Public, Is.EqualTo("Public")); + Assert.That(vm.Private, Is.Null); + } + + class DictionaryTest + { + public string A { get; set; } + public int B { get; set; } + public bool C { get; set; } + public double D { get; set; } + } + + [Test] + public void Can_convert_anonymous_object_to_ObjectDictionary() + { + var newObj = new { A = "a", B = 1, C = true, D = 2.0 }; + var type = new DictionaryTest { A = "a", B = 1, C = true, D = 2.0 }; + var expected = new Dictionary { + ["A"] = "a", + ["B"] = 1, + ["C"] = true, + ["D"] = 2.0 + }; + + var to = newObj.ToObjectDictionary(); + Assert.That(to, Is.EquivalentTo(expected)); + to = type.ToObjectDictionary(); + Assert.That(to, Is.EquivalentTo(expected)); + to = expected.ToObjectDictionary(); + Assert.That(to, Is.EquivalentTo(expected)); + } + + [Test] + public void Can_convert_string_to_collection() + { + Assert.That("a,b,c".ConvertTo(), Is.EquivalentTo(new[]{ "a", "b", "c" })); + Assert.That("1,2,3".ConvertTo(), Is.EquivalentTo(new[]{ 1, 2, 3 })); + } + + [Test] + public void Translate_Between_Models_of_different_types_and_nullables() + { + var fromObj = ModelWithFieldsOfDifferentTypes.CreateConstant(1); + + var toObj = fromObj.ConvertTo(); + + Console.WriteLine(toObj.Dump()); + + ModelWithFieldsOfDifferentTypesAsNullables.AssertIsEqual(fromObj, toObj); + } + + [Test] + public void Translate_Between_Models_of_nullables_and_different_types() + { + var fromObj = ModelWithFieldsOfDifferentTypesAsNullables.CreateConstant(1); + + var toObj = fromObj.ConvertTo(); + + Console.WriteLine(toObj.Dump()); + + ModelWithFieldsOfDifferentTypesAsNullables.AssertIsEqual(toObj, fromObj); + } + + [Test] + public void Can_convert_to_array_to_string() + { + Assert.That(new []{ new ViewModel { Public = "A"} }.ConvertTo(), Is.Not.Empty); + Assert.That(new []{ DayOfWeek.Monday }.ConvertTo(), Is.Not.Empty); + } + + [Test] + public void To_works_with_ValueTypes() + { + Assert.That(1.ToString().ConvertTo(), Is.EqualTo(1)); + } + + [Test] + public void To_on_null_or_empty_string_returns_default_value_supplied() + { + const string nullString = null; + Assert.That("".ConvertTo(1), Is.EqualTo(1)); + Assert.That("".ConvertTo(default(int)), Is.EqualTo(default(int))); + Assert.That(nullString.ConvertTo(1), Is.EqualTo(1)); + } + + [Test] + public void To_ValueType_on_null_or_empty_string_returns_default_value() + { + Assert.That("".ConvertTo(), Is.EqualTo(default(int))); + } + + struct A + { + public int Id { get; } + public A(int id) => Id = id; + public static implicit operator B(A from) => new B(from.Id); + } + + struct B + { + public int Id { get; } + public B(int id) => Id = id; + public static implicit operator A(B from) => new A(from.Id); + } + + [Test] + public void Can_convert_between_structs_with_implicit_casts() + { + var a = new A(1); + var b = a; + Assert.That(b.Id, Is.EqualTo(a.Id)); + + b = a.ConvertTo(); + Assert.That(b.Id, Is.EqualTo(a.Id)); + } + + [Test] + public void Can_convert_from_class_implicit_Casts() + { + var to = "test".ConvertTo(); + Assert.That(to.LocalName, Is.EqualTo("test")); + } + + struct C + { + public int Id { get; } + public C(int id) => Id = id; + public static explicit operator C(D from) => new C(from.Id); + } + + struct D + { + public int Id { get; } + public D(int id) => Id = id; + public static explicit operator D(C from) => new D(from.Id); + } + + [Test] + public void Can_convert_between_structs_with_explicit_casts() + { + var c = new C(1); + var d = (D)c; + Assert.That(d.Id, Is.EqualTo(c.Id)); + + d = c.ConvertTo(); + Assert.That(d.Id, Is.EqualTo(c.Id)); + } + + [Test] + public void Can_Convert_from_ObjectDictionary_Containing_Another_Object_Dictionary() + { + var map = new Dictionary + { + { "name", "Foo" }, + { "otherNames", new List + { + new Dictionary { { "name", "Fu" } }, + new Dictionary { { "name", "Fuey" } } + } + } + }; + + var fromDict = map.ConvertTo(); + + Assert.That(fromDict.Name, Is.EqualTo("Foo")); + Assert.That(fromDict.OtherNames.Count, Is.EqualTo(2)); + Assert.That(fromDict.OtherNames.First().Name, Is.EqualTo("Fu")); + Assert.That(fromDict.OtherNames.Last().Name, Is.EqualTo("Fuey")); + + var toDict = fromDict.ConvertTo>(); + Assert.That(toDict["Name"], Is.EqualTo("Foo")); + Assert.That(toDict["OtherNames"], Is.EqualTo(fromDict.OtherNames)); + + var toObjDict = fromDict.ConvertTo(); + Assert.That(toObjDict["Name"], Is.EqualTo("Foo")); + Assert.That(toObjDict["OtherNames"], Is.EqualTo(fromDict.OtherNames)); + } + + class Person + { + public int Id { get; set; } + public string Name { get; set; } + + protected bool Equals(Person other) => Id == other.Id && string.Equals(Name, other.Name); + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + return obj.GetType() == this.GetType() && Equals((Person) obj); + } + + public override int GetHashCode() + { + unchecked + { + return (Id * 397) ^ (Name != null ? Name.GetHashCode() : 0); + } + } + } + + [Test] + public void Can_convert_between_Poco_and_ObjectDictionary() + { + var person = new Person { Id = 1, Name = "foo" }; + + Assert.That(person.ConvertTo>(), Is.EqualTo(new Dictionary { + {"Id", 1}, + {"Name", "foo"}, + })); + Assert.That(new Dictionary { + {"Id", 1}, + {"Name", "foo"}, + }.ConvertTo(), Is.EqualTo(person)); + + Assert.That(person.ConvertTo(), Is.EqualTo(new ObjectDictionary { + {"Id", 1}, + {"Name", "foo"}, + })); + Assert.That(new ObjectDictionary { + {"Id", 1}, + {"Name", "foo"}, + }.ConvertTo(), Is.EqualTo(person)); + + Assert.That(person.ConvertTo>(), Is.EqualTo(new Dictionary { + {"Id", "1"}, + {"Name", "foo"}, + })); + + Assert.That(new Dictionary { + {"Id", "1"}, + {"Name", "foo"}, + }.ConvertTo(), Is.EqualTo(person)); + + Assert.That(person.ConvertTo(), Is.EqualTo(new StringDictionary { + {"Id", "1"}, + {"Name", "foo"}, + })); + Assert.That(new StringDictionary { + {"Id", "1"}, + {"Name", "foo"}, + }.ConvertTo(), Is.EqualTo(person)); + } + + [Test] + public void Can_ToObjectDictionary_KVPs() + { + var objKvp = new KeyValuePair("A", 1); + var strKvp = new KeyValuePair("A", "1"); + var intKvp = new KeyValuePair("A", 1); + + Assert.That(objKvp.ToObjectDictionary(), Is.EquivalentTo(new Dictionary { {"Key", "A" }, { "Value", 1 } } )); + Assert.That(strKvp.ToObjectDictionary(), Is.EquivalentTo(new Dictionary { {"Key", "A" }, { "Value", "1"} } )); + Assert.That(intKvp.ToObjectDictionary(), Is.EquivalentTo(new Dictionary { {"Key", "A" }, { "Value", 1 } } )); + + var objKvp2 = new KeyValuePair("B", 2); + var strKvp2 = new KeyValuePair("B", "2"); + var intKvp2 = new KeyValuePair("B", 2); + + Assert.That(new[] { objKvp, objKvp2 }.ToObjectDictionary(), Is.EquivalentTo(new Dictionary { {"A", 1}, {"B", 2} })); + Assert.That(new[] { strKvp, strKvp2 }.ToObjectDictionary(), Is.EquivalentTo(new Dictionary { {"A", "1"}, {"B", "2"} })); + Assert.That(new[] { intKvp, intKvp2 }.ToObjectDictionary(), Is.EquivalentTo(new Dictionary { {"A", 1}, {"B", 2} })); + } + + [Test] + public void Can_convert_KVPs_to_ObjectDictionary() + { + var objKvp = new KeyValuePair("A", 1); + var strKvp = new KeyValuePair("A", "1"); + var intKvp = new KeyValuePair("A", 1); + + Assert.That(objKvp.ConvertTo>(), Is.EquivalentTo(new Dictionary { {"Key", "A" }, { "Value", 1 } } )); + Assert.That(strKvp.ConvertTo>(), Is.EquivalentTo(new Dictionary { {"Key", "A" }, { "Value", "1"} } )); + Assert.That(intKvp.ConvertTo>(), Is.EquivalentTo(new Dictionary { {"Key", "A" }, { "Value", 1 } } )); + + var objKvp2 = new KeyValuePair("B", 2); + var strKvp2 = new KeyValuePair("B", "2"); + var intKvp2 = new KeyValuePair("B", 2); + + Assert.That(new[] { objKvp, objKvp2 }.ConvertTo>(), Is.EquivalentTo(new Dictionary { {"A", 1}, {"B", 2} })); + Assert.That(new[] { strKvp, strKvp2 }.ConvertTo>(), Is.EquivalentTo(new Dictionary { {"A", "1"}, {"B", "2"} })); + Assert.That(new[] { intKvp, intKvp2 }.ConvertTo>(), Is.EquivalentTo(new Dictionary { {"A", 1}, {"B", 2} })); + } + + [Test] + public void Can_convert_between_KVP_Objects_and_IEnumerable() + { + var kvps = new List> { + new KeyValuePair("A", 1), + new KeyValuePair("B", 2), + new KeyValuePair("C", 3), + }; + + Assert.That(kvps.ConvertTo>(), Is.EqualTo(kvps)); + Assert.That(kvps.ConvertTo>(), Is.EqualTo(kvps)); + Assert.That(kvps.ConvertTo(), Is.EqualTo(kvps)); + + List listObjs = kvps.Cast().ToList(); + Assert.That(listObjs.ConvertTo>>(), Is.EquivalentTo(listObjs)); + object[] arrayObjs = kvps.Cast().ToArray(); + Assert.That(arrayObjs.ConvertTo>>(), Is.EquivalentTo(arrayObjs)); + } + + [Test] + public void Can_convert_between_KVP_Strings_and_IEnumerable() + { + var kvps = new List> { + new KeyValuePair("A", "1"), + new KeyValuePair("B", "2"), + new KeyValuePair("C", "3"), + }; + + Assert.That(kvps.ConvertTo>(), Is.EqualTo(kvps)); + Assert.That(kvps.ConvertTo>(), Is.EqualTo(kvps)); + Assert.That(kvps.ConvertTo(), Is.EqualTo(kvps)); + + List listObjs = kvps.Cast().ToList(); + Assert.That(listObjs.ConvertTo>>(), Is.EquivalentTo(listObjs)); + object[] arrayObjs = kvps.Cast().ToArray(); + Assert.That(arrayObjs.ConvertTo>>(), Is.EquivalentTo(arrayObjs)); + } + + [Test] + public void Can_convert_between_KVP_ints_and_IEnumerable() + { + var kvps = new List> { + new KeyValuePair("A", 1), + new KeyValuePair("B", 2), + new KeyValuePair("C", 3), + }; + + Assert.That(kvps.ConvertTo>(), Is.EqualTo(kvps)); + Assert.That(kvps.ConvertTo>(), Is.EqualTo(kvps)); + Assert.That(kvps.ConvertTo(), Is.EqualTo(kvps)); + + List listObjs = kvps.Cast().ToList(); + Assert.That(listObjs.ConvertTo>>(), Is.EquivalentTo(listObjs)); + object[] arrayObjs = kvps.Cast().ToArray(); + Assert.That(arrayObjs.ConvertTo>>(), Is.EquivalentTo(arrayObjs)); + } + + public enum TestEnum { A, B, C } + + [Test] + public void Can_convert_between_list_of_value_types() + { + var enumArrays = new[]{ TestEnum.A, TestEnum.B, TestEnum.C }; + var strArrays = new List {"A","B","C"}; + Assert.That(enumArrays.ConvertTo>(), Is.EquivalentTo(enumArrays.ToList())); + Assert.That(enumArrays.ConvertTo>(), Is.EquivalentTo(strArrays)); + + var strNums = new List {"1","2","3"}; + var intNums = new List {1,2,3}; + Assert.That(strNums.ConvertTo>(), Is.EquivalentTo(intNums)); + Assert.That(intNums.ConvertTo>(), Is.EquivalentTo(strNums)); + } + + public class TestClassA + { + public IList IListStrings { get; set; } + public ArrayOfString ListStrings { get; set; } + public IList ListEnums { get; set; } + } + public class TestClassB + { + public ArrayOfString IListStrings { get; set; } + public IList ListStrings { get; set; } + public ArrayOfString ListEnums { get; set; } + } + public class TestClassC + { + public IList ListStrings { get; protected set; } + } + + [Test] + public void Can_translate_generic_lists() + { + + var values = new[] { "A", "B", "C" }; + var testA = new TestClassA + { + IListStrings = new List(values), + ListStrings = new ArrayOfString(values), + ListEnums = new List { TestEnum.A, TestEnum.B, TestEnum.C }, + }; + + var fromTestA = testA.ConvertTo(); + + AssertAreEqual(testA, fromTestA); + + var userFileTypeValues = testA.ListEnums.Map(x => x.ToString()); + var testB = new TestClassB + { + IListStrings = new ArrayOfString(values), + ListStrings = new List(values), + ListEnums = new ArrayOfString(userFileTypeValues), + }; + + var fromTestB = testB.ConvertTo(); + AssertAreEqual(fromTestB, testB); + } + + private static void AssertAreEqual(TestClassA testA, TestClassB testB) + { + Assert.That(testA, Is.Not.Null); + Assert.That(testB, Is.Not.Null); + + Assert.That(testA.IListStrings, Is.Not.Null); + Assert.That(testB.IListStrings, Is.Not.Null); + Assert.That(testA.IListStrings, + Is.EquivalentTo(new List(testB.IListStrings))); + + Assert.That(testA.ListStrings, Is.Not.Null); + Assert.That(testB.ListStrings, Is.Not.Null); + Assert.That(testA.ListStrings, Is.EquivalentTo(testB.ListStrings)); + + Assert.That(testA.ListEnums, Is.Not.Null); + Assert.That(testB.ListEnums, Is.Not.Null); + Assert.That(testA.ListEnums, + Is.EquivalentTo(testB.ListEnums.ConvertAll(x => x.ToEnum()))); + } + + class Company + { + public string Name { get; set; } + public Address Address { get; set; } + } + + class Address + { + public string City { get; set; } + public string PostCode { get; set; } + } + + [Test] + public void Can_register_converters_to_PopulateWithNonDefaultValues() + { + Company Create() + { + return new Company { + Name = "Compnay1", + Address = new Address { + City = "NY", + PostCode = "1234", + } + }; + } + + var defaults = Create(); + var original = Create(); + + original.PopulateWithNonDefaultValues(new Company { + Address = new Address { + PostCode = "4321" + } + }); + + Assert.That(original.Name, Is.EqualTo(defaults.Name)); + Assert.That(original.Address.City, Is.Null); + Assert.That(original.Address.PostCode, Is.EqualTo("4321")); + + original = Create(); + + var updated = new Company { + Address = new Address { + PostCode = "4321" + } + }; + original.Address.PopulateWithNonDefaultValues(updated.Address); + updated.Address = null; + original.PopulateWithNonDefaultValues(updated); + + Assert.That(original.Name, Is.EqualTo(defaults.Name)); + Assert.That(original.Address.City, Is.EqualTo(defaults.Address.City)); + Assert.That(original.Address.PostCode, Is.EqualTo("4321")); + } + + public class NavItem : IMeta + { + public string Label { get; set; } + public string Path { get; set; } + + /// + /// Whether Active class should only be added when paths are exact match + /// otherwise checks if ActivePath starts with Path + /// + public bool Exact { get; set; } + + public string Id { get; set; } // Emit id="{Id}" + public string Class { get; set; } // Override class="{Class}" + + public List Children { get; set; } = new List(); + + public Dictionary Meta { get; set; } = new Dictionary(); + } + + [Test] + public void Does_convert_nested_collections() + { + var to = new[] { + new Dictionary { + ["id"] = "A1", + ["children"] = new List + { + new Dictionary + { + ["id"] = "B1", + }, + new Dictionary + { + ["id"] = "B2", + ["children"] = new List { + new Dictionary + { + ["id"] = "C1", + }, + } + }, + } + } + }; + + var navItems = to.ConvertTo>(); + Assert.That(navItems[0].Id, Is.EqualTo("A1")); + Assert.That(navItems[0].Children[0].Id, Is.EqualTo("B1")); + Assert.That(navItems[0].Children[1].Id, Is.EqualTo("B2")); + Assert.That(navItems[0].Children[1].Children[0].Id, Is.EqualTo("C1")); + } + + public class TestMethod + { + public void MyMethod() + { + MyProp = nameof(MyProp); + } + public string MyProp { get; set; } + } + + public class TestMethod2 + { + public void MyMethod(string myProp) + { + MyProp = nameof(MyMethod); + } + public string MyProp { get; set; } + } + + [Test] + public void Does_not_try_to_map_methods() + { + var from = new TestMethod(); + var to = new TestMethod2(); + from.MyProp = "Test"; + to.PopulateFromPropertiesWithoutAttribute(from, typeof(IgnoreDataMemberAttribute)); + Assert.That(from.MyProp, Is.EqualTo(to.MyProp)); + } + } } diff --git a/tests/ServiceStack.Text.Tests/BasicStringSerializerTests.cs b/tests/ServiceStack.Text.Tests/BasicStringSerializerTests.cs index f4d16a851..a88c1ecd2 100644 --- a/tests/ServiceStack.Text.Tests/BasicStringSerializerTests.cs +++ b/tests/ServiceStack.Text.Tests/BasicStringSerializerTests.cs @@ -61,7 +61,7 @@ public void Can_convert_comma_delimited_string_to_List_String() [Test] public void Null_or_Empty_string_returns_null() { - var convertedJsvValues = TypeSerializer.DeserializeFromString>(null); + var convertedJsvValues = TypeSerializer.DeserializeFromString>((string)null); Assert.That(convertedJsvValues, Is.EqualTo(null)); convertedJsvValues = TypeSerializer.DeserializeFromString>(string.Empty); @@ -78,7 +78,7 @@ public void Empty_list_string_returns_empty_List() [Test] public void Null_or_Empty_string_returns_null_Map() { - var convertedStringValues = TypeSerializer.DeserializeFromString>(null); + var convertedStringValues = TypeSerializer.DeserializeFromString>((string)null); Assert.That(convertedStringValues, Is.EqualTo(null)); convertedStringValues = TypeSerializer.DeserializeFromString>(string.Empty); @@ -133,7 +133,7 @@ public void Can_convert_to_nullable_enum() [Test] public void Can_convert_to_nullable_enum_with_null_value() { - var enumValue = TypeSerializer.DeserializeFromString(null); + var enumValue = TypeSerializer.DeserializeFromString((string)null); Assert.That(enumValue, Is.Null); } @@ -391,7 +391,6 @@ public T Serialize(T model) return fromJsonModel; } -#if !IOS public class TestClass { [Required] @@ -571,7 +570,7 @@ public void Can_convert_Field_Map_or_List_with_single_invalid_char() { foreach (var invalidChar in allCharsUsed) { - var singleInvalidChar = string.Format("a {0} b", invalidChar); + var singleInvalidChar = $"a {invalidChar} b"; var instance = new ModelWithMapAndList { @@ -616,6 +615,32 @@ public void Can_convert_List_Guid() Assert.That(toModel, Is.EquivalentTo(model)); } -#endif + + [Test] + public void Can_deserialize_int_with_leading_zeros() + { + Assert.That("01".FromJson(), Is.EqualTo(1)); + Assert.That("01".FromJson(), Is.EqualTo(1)); + Assert.That("01".FromJson(), Is.EqualTo(1)); + } + + public class EmptyCollections + { + public string[] Strings { get; set; } + public int[] Ints { get; set; } + public List IntList { get; set; } + } + + [Test] + public void Can_deserialize_empty_array() + { + Assert.That("[]".FromJson(), Is.EquivalentTo(new string[0])); + Assert.That("[]".FromJson(), Is.EquivalentTo(new int[0])); + Assert.That("[]".FromJson>(), Is.EquivalentTo(new List())); + + Assert.That("{\"Strings\":[]}".FromJson().Strings, Is.EquivalentTo(new string[0])); + Assert.That("{\"Ints\":[]}".FromJson().Ints, Is.EquivalentTo(new int[0])); + Assert.That("{\"IntList\":[]}".FromJson().IntList, Is.EquivalentTo(new List())); + } } } \ No newline at end of file diff --git a/tests/ServiceStack.Text.Tests/Benchmarks/JsonSerializationBenchmarks.cs b/tests/ServiceStack.Text.Tests/Benchmarks/JsonSerializationBenchmarks.cs index 47499d35e..44e56c12e 100644 --- a/tests/ServiceStack.Text.Tests/Benchmarks/JsonSerializationBenchmarks.cs +++ b/tests/ServiceStack.Text.Tests/Benchmarks/JsonSerializationBenchmarks.cs @@ -91,7 +91,7 @@ public void SerializeJsonCommonTypesToStream() public void SerializeJsonStringToStreamUsingDirectStreamWriter() { stream.Position = 0; - var writer = new DirectStreamWriter(stream, JsonSerializer.UTF8Encoding); + var writer = new DirectStreamWriter(stream, JsConfig.UTF8Encoding); JsonWriter.WriteRootObject(writer, serializedString); writer.Flush(); } @@ -100,7 +100,7 @@ public void SerializeJsonStringToStreamUsingDirectStreamWriter() public void SerializeJsonString256ToStreamUsingDirectStreamWriter() { stream.Position = 0; - var writer = new DirectStreamWriter(stream, JsonSerializer.UTF8Encoding); + var writer = new DirectStreamWriter(stream, JsConfig.UTF8Encoding); JsonWriter.WriteRootObject(writer, serializedString256); writer.Flush(); } @@ -109,7 +109,7 @@ public void SerializeJsonString256ToStreamUsingDirectStreamWriter() public void SerializeJsonString512ToStreamUsingDirectStreamWriter() { stream.Position = 0; - var writer = new DirectStreamWriter(stream, JsonSerializer.UTF8Encoding); + var writer = new DirectStreamWriter(stream, JsConfig.UTF8Encoding); JsonWriter.WriteRootObject(writer, serializedString512); writer.Flush(); } @@ -118,7 +118,7 @@ public void SerializeJsonString512ToStreamUsingDirectStreamWriter() public void SerializeJsonString4096ToStreamUsingDirectStreamWriter() { stream.Position = 0; - var writer = new DirectStreamWriter(stream, JsonSerializer.UTF8Encoding); + var writer = new DirectStreamWriter(stream, JsConfig.UTF8Encoding); JsonWriter.WriteRootObject(writer, serializedString4096); writer.Flush(); } diff --git a/tests/ServiceStack.Text.Tests/CsvSerializerTests.cs b/tests/ServiceStack.Text.Tests/CsvSerializerTests.cs index 50189c407..0ac783e0a 100644 --- a/tests/ServiceStack.Text.Tests/CsvSerializerTests.cs +++ b/tests/ServiceStack.Text.Tests/CsvSerializerTests.cs @@ -1,7 +1,9 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Globalization; using System.IO; +using System.Linq; using Northwind.Common.DataModel; using NUnit.Framework; using ServiceStack.Text.Tests.Support; @@ -9,7 +11,7 @@ namespace ServiceStack.Text.Tests { [TestFixture] -#if NETCORE_SUPPORT +#if NETCORE [Ignore("Fix Northwind.dll")] #endif public class CsvSerializerTests @@ -114,7 +116,7 @@ public void Does_parse_fields() } [Test] - public void Does_parse_fileds_with_unmatchedJsMark() + public void Does_parse_fields_with_unmatchedJsMark() { Assert.That(CsvReader.ParseFields("{A,B"), Is.EqualTo(new[] { "{A", "B" })); Assert.That(CsvReader.ParseFields("{A},B"), Is.EqualTo(new[] { "{A}", "B" })); @@ -219,7 +221,7 @@ public void Can_Deserialize_into_String_Dictionary() Assert.That(map["Id"], Is.EqualTo(movie.Id.ToString())); Assert.That(map["ImdbId"], Is.EqualTo(movie.ImdbId)); Assert.That(map["Title"], Is.EqualTo(movie.Title)); - Assert.That(map["Rating"], Is.EqualTo(movie.Rating.ToString())); + Assert.That(map["Rating"], Is.EqualTo(movie.Rating.ToString(CultureInfo.InvariantCulture))); Assert.That(map["Director"], Is.EqualTo(movie.Director)); Assert.That(map["ReleaseDate"], Is.EqualTo(movie.ReleaseDate.ToJsv())); Assert.That(map["TagLine"], Is.EqualTo(movie.TagLine)); @@ -252,7 +254,7 @@ public void Can_deserialize_into_String_List() Assert.That(first[0], Is.EqualTo(movie.Id.ToString())); Assert.That(first[1], Is.EqualTo(movie.ImdbId)); Assert.That(first[2], Is.EqualTo(movie.Title)); - Assert.That(first[3], Is.EqualTo(movie.Rating.ToString())); + Assert.That(first[3], Is.EqualTo(movie.Rating.ToString(CultureInfo.InvariantCulture))); Assert.That(first[4], Is.EqualTo(movie.Director)); Assert.That(first[5], Is.EqualTo(movie.ReleaseDate.ToJsv())); Assert.That(first[6], Is.EqualTo(movie.TagLine)); @@ -375,6 +377,66 @@ public void Can_serialize_single_StringDictionary_or_StringKvps() Assert.That(kvps.ToCsv().NormalizeNewLines(), Is.EqualTo("Id,CustomerId\n1,ALFKI").Or.EqualTo("CustomerId,Id\nALFKI,1")); } + [Test] + public void Can_serialize_fields_with_double_quotes() + { + var person = new Person { Id = 1, Name = "\"Mr. Lee\"" }; + Assert.That(person.ToCsv().NormalizeNewLines(), Is.EqualTo("Id,Name\n1,\"\"\"Mr. Lee\"\"\"")); + var fromCsv = person.ToCsv().FromCsv(); + Assert.That(fromCsv, Is.EqualTo(person)); + + person = new Person { Id = 1, Name = "\"Anon\" Review" }; + Assert.That(person.ToCsv().NormalizeNewLines(), Is.EqualTo("Id,Name\n1,\"\"\"Anon\"\" Review\"")); + fromCsv = person.ToCsv().FromCsv(); + Assert.That(fromCsv, Is.EqualTo(person)); + } + + public Order Clone(Order o) => new Order { + Id = o.Id, + CustomerId = o.CustomerId, + EmployeeId = o.EmployeeId, + OrderDate = o.OrderDate, + RequiredDate = o.RequiredDate, + ShippedDate = o.ShippedDate, + ShipVia = o.ShipVia, + Freight = o.Freight, + ShipName = o.ShipName, + ShipAddress = o.ShipAddress, + ShipCity = o.ShipCity, + ShipRegion = o.ShipRegion, + ShipPostalCode = o.ShipPostalCode, + ShipCountry = o.ShipCountry, + }; + + [Test] + public void Can_only_serialize_NonDefaultValues() + { + using var scope = JsConfig.With(new Config { + ExcludeDefaultValues = true + }); + + var orders = NorthwindData.Orders.Take(5).Map(Clone); + orders.ForEach(x => { + //non-default min values + x.RequiredDate = DateTime.MinValue; + x.ShipVia = 0; + + //default values + x.ShippedDate = null; + x.EmployeeId = default; + x.Freight = default; + x.ShipPostalCode = null; + x.ShipCountry = null; + }); + + var csv = orders.ToCsv(); + // csv.Print(); + var headers = csv.LeftPart('\r'); + headers.Print(); + Assert.That(headers, Is.EquivalentTo( + "Id,CustomerId,OrderDate,RequiredDate,ShipVia,ShipName,ShipAddress,ShipCity,ShipRegion")); + } + [Test] public void serialize_Category() { diff --git a/tests/ServiceStack.Text.Tests/CsvStreamTests.cs b/tests/ServiceStack.Text.Tests/CsvStreamTests.cs index 414fcbab6..770f094db 100644 --- a/tests/ServiceStack.Text.Tests/CsvStreamTests.cs +++ b/tests/ServiceStack.Text.Tests/CsvStreamTests.cs @@ -19,7 +19,7 @@ public void TearDown() } [Test] -#if NETCORE_SUPPORT +#if NETCORE [Ignore("Fix Northwind.dll")] #endif public void Can_create_csv_from_Customers() @@ -31,7 +31,7 @@ public void Can_create_csv_from_Customers() } [Test] -#if NETCORE_SUPPORT +#if NETCORE [Ignore("Fix Northwind.dll")] #endif public void Can_create_csv_from_Customers_pipe_separator() @@ -44,7 +44,7 @@ public void Can_create_csv_from_Customers_pipe_separator() } [Test] -#if NETCORE_SUPPORT +#if NETCORE [Ignore("Fix Northwind.dll")] #endif public void Can_create_csv_from_Customers_pipe_delimiter() @@ -57,7 +57,7 @@ public void Can_create_csv_from_Customers_pipe_delimiter() } [Test] -#if NETCORE_SUPPORT +#if NETCORE [Ignore("Fix Northwind.dll")] #endif public void Can_create_csv_from_Customers_pipe_row_separator() @@ -70,7 +70,7 @@ public void Can_create_csv_from_Customers_pipe_row_separator() } [Test] -#if NETCORE_SUPPORT +#if NETCORE [Ignore("Fix Northwind.dll")] #endif public void Can_create_csv_from_Categories() @@ -88,7 +88,7 @@ public void Can_create_csv_from_Categories() } [Test] -#if NETCORE_SUPPORT +#if NETCORE [Ignore("Fix Northwind.dll")] #endif public void Can_create_csv_from_Categories_pipe_separator() @@ -107,7 +107,7 @@ public void Can_create_csv_from_Categories_pipe_separator() } [Test] -#if NETCORE_SUPPORT +#if NETCORE [Ignore("Fix Northwind.dll")] #endif public void Can_create_csv_from_Categories_pipe_delimiter() @@ -126,7 +126,7 @@ public void Can_create_csv_from_Categories_pipe_delimiter() } [Test] -#if NETCORE_SUPPORT +#if NETCORE [Ignore("Fix Northwind.dll")] #endif public void Can_create_csv_from_Categories_long_delimiter() diff --git a/tests/ServiceStack.Text.Tests/CsvTests/ObjectSerializerTests.cs b/tests/ServiceStack.Text.Tests/CsvTests/ObjectSerializerTests.cs index aed93e269..b0920e8e6 100644 --- a/tests/ServiceStack.Text.Tests/CsvTests/ObjectSerializerTests.cs +++ b/tests/ServiceStack.Text.Tests/CsvTests/ObjectSerializerTests.cs @@ -8,6 +8,56 @@ namespace ServiceStack.Text.Tests.CsvTests [TestFixture] public class ObjectSerializerTests { + [Test] + public void MidnightAndNoonTestSerialization() + { + JsConfig.Reset(); + JsConfig.SerializeFn = null; + JsConfig.Reset(); + + JsConfig.AlwaysUseUtc = true; + JsConfig.AssumeUtc = true; + // Set the format for DatTimeFormatting explicitly using DateTimeSerializer.XsdDateTimeFormat because it is ISO8601 fractional seconds + JsConfig.DateTimeFormat = DateTimeSerializer.XsdDateTimeFormat; + + var midnight = new DateTime(2018, 1, 1, 0, 0, 0, DateTimeKind.Utc); + var noon = midnight.AddHours(12); + var dotnetValues = new + { + Midnight = midnight.ToString("o"), + Noon = noon.ToString("o") + }; + var data = new object[] { + new POCO { DateTime = midnight }, + new POCO { DateTime = noon } + }; + var csv = CsvSerializer.SerializeToCsv(data); + // Reset back to defaults + JsConfig.Reset(); + JsConfig.SerializeFn = null; + JsConfig.Reset(); + + Console.WriteLine(csv); + + const string endLineChars = "\r\n"; + Assert.AreEqual($"DateTime{endLineChars}" + + $"{dotnetValues.Midnight}{endLineChars}" + + $"{dotnetValues.Noon}{endLineChars}", csv); + + // Now don't use custom DateTimeFormat + JsConfig.AlwaysUseUtc = true; + JsConfig.AssumeUtc = true; + csv = CsvSerializer.SerializeToCsv(data); + Console.WriteLine(csv); + Assert.AreEqual($"DateTime{endLineChars}" + + $"2018-01-01{endLineChars}" + + $"2018-01-01T12:00:00Z{endLineChars}", csv); + + JsConfig.Reset(); + JsConfig.SerializeFn = null; + JsConfig.Reset(); + } + [Test] public void IEnumerableObjectSerialization() { @@ -141,6 +191,40 @@ public void Can_serialize_text_with_unmatched_list_or_map_chars() Assert.That(des[0].Prop4, Is.EqualTo(src[0].Prop4)); Assert.That(des[0].Prop5, Is.EqualTo(src[0].Prop5)); } + + [Test] + public void Can_serialize_csv_and_deserialize_List_string() + { + var list = new List + { + "one", + "two", + "three" + }; + + var flatList = list.ToCsv(); + + var originalList = flatList.FromCsv>(); + + Assert.That(originalList.Count, Is.EqualTo(3)); + } + + [Test] + public void Can_serialize_csv_and_deserialize_List_primitives() + { + var list = new List + { + 1, + 2, + 3 + }; + + var flatList = list.ToCsv(); + + var originalList = flatList.FromCsv>(); + + Assert.That(originalList.Count, Is.EqualTo(3)); + } } public class POCO diff --git a/tests/ServiceStack.Text.Tests/CustomStructTests.cs b/tests/ServiceStack.Text.Tests/CustomStructTests.cs index 7c3758dfa..d4b978170 100644 --- a/tests/ServiceStack.Text.Tests/CustomStructTests.cs +++ b/tests/ServiceStack.Text.Tests/CustomStructTests.cs @@ -49,16 +49,8 @@ public static UserStat Parse(string userStatString) return userStat; } - public override string ToString() - { - return string.Format("{0}:{1}:{2}:{3}:{4}:{5}", - this.UserId.ToString("n"), - TimesRecommended, - TimesPurchased, - TimesRecommended, - TimesPreviewed, - GetWeightedValue()); - } + public override string ToString() => + $"{this.UserId:n}:{TimesRecommended}:{TimesPurchased}:{TimesRecommended}:{TimesPreviewed}:{GetWeightedValue()}"; } [TestFixture] diff --git a/tests/ServiceStack.Text.Tests/CyclicalDependencyTests.cs b/tests/ServiceStack.Text.Tests/CyclicalDependencyTests.cs index 05a5fd45c..f76d48815 100644 --- a/tests/ServiceStack.Text.Tests/CyclicalDependencyTests.cs +++ b/tests/ServiceStack.Text.Tests/CyclicalDependencyTests.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; +using System.Reflection; using System.Runtime.Serialization; using NUnit.Framework; @@ -163,7 +164,7 @@ class person [Test] public void Can_limit_cyclical_dependencies() { - using (JsConfig.With(maxDepth: 4)) + using (JsConfig.With(new Config { MaxDepth = 4 })) { var p = new person(); p.teacher = new person { name = "sam", teacher = p }; @@ -213,5 +214,35 @@ public void Ignore_Cyclical_dependencies() JsConfig.OnDeserializedFn = null; JsConfig.Reset(); } + + public class ReflectionType + { + public string Name { get; set; } = "A"; + public Type Type { get; set; } + public MethodInfo MethodInfo { get; set; } + public PropertyInfo PropertyInfo { get; set; } + public FieldInfo FieldInfo; + public MemberInfo MemberInfo { get; set; } + + public void Method() {} + } + + [Test] + public void Can_serialize_POCO_with_Type() + { + var dto = new ReflectionType { + Type = typeof(ReflectionType), + MethodInfo = typeof(ReflectionType).GetMethod(nameof(ReflectionType.Method)), + PropertyInfo = typeof(ReflectionType).GetProperty(nameof(ReflectionType.PropertyInfo)), + FieldInfo = typeof(ReflectionType).GetPublicFields().FirstOrDefault(), + MemberInfo = typeof(ReflectionType).GetMembers().FirstOrDefault(), + }; + + dto.Name.Print(); + dto.ToJson().Print(); + dto.ToJsv().Print(); + dto.PrintDump(); + } + } } diff --git a/tests/ServiceStack.Text.Tests/DataContractTests.cs b/tests/ServiceStack.Text.Tests/DataContractTests.cs index 16846fd4f..c4f689049 100644 --- a/tests/ServiceStack.Text.Tests/DataContractTests.cs +++ b/tests/ServiceStack.Text.Tests/DataContractTests.cs @@ -258,20 +258,6 @@ public void Json_Serialize_Should_Respects_DataContract_Field_Name_With_Bytes_De Assert.IsTrue (t.Bytes.AreEqual (new byte[] { 0x61, 0x62 })); } -#if !NETCORE_SUPPORT - [Test] - public void Can_get_weak_DataMember() - { - var dto = new ClassOne(); - var dataMemberAttr = dto.GetType().GetProperty("Id").GetWeakDataMember(); - Assert.That(dataMemberAttr.Name, Is.Null); - - dataMemberAttr = dto.GetType().GetProperty("List").GetWeakDataMember(); - Assert.That(dataMemberAttr.Name, Is.EqualTo("listClassTwo")); - Assert.That(dataMemberAttr.Order, Is.EqualTo(1)); - } -#endif - [DataContract(Name = "my-class", Namespace = "http://schemas.ns.com")] public class MyClass { @@ -279,19 +265,6 @@ public class MyClass public string Title { get; set; } } -#if !NETCORE_SUPPORT - [Test] - public void Can_get_weak_DataContract() - { - var mc = new MyClass { Title = "Some random title" }; - - var attr = mc.GetType().GetWeakDataContract(); - - Assert.That(attr.Name, Is.EqualTo("my-class")); - Assert.That(attr.Namespace, Is.EqualTo("http://schemas.ns.com")); - } -#endif - [Test] public void Does_use_DataMember_Name() { @@ -329,6 +302,10 @@ public void Does_use_alias_and_is_not_optin_without_DataContract_Attribute() { var dto = new AliasWithoutDataContract { Id = 1, Name = "foo" }; Assert.That(dto.ToJson(), Is.EqualTo("{\"Id\":1,\"alias\":\"foo\"}")); + + Assert.That(dto.ToJsv(), Is.EqualTo("{Id:1,alias:foo}")); + + Assert.That(dto.ToCsv(), Is.EqualTo("Id,alias\r\n1,foo\r\n")); } } diff --git a/tests/ServiceStack.Text.Tests/DateTimeOffsetAndTimeSpanTests.cs b/tests/ServiceStack.Text.Tests/DateTimeOffsetAndTimeSpanTests.cs index b1be37929..b168d5e91 100644 --- a/tests/ServiceStack.Text.Tests/DateTimeOffsetAndTimeSpanTests.cs +++ b/tests/ServiceStack.Text.Tests/DateTimeOffsetAndTimeSpanTests.cs @@ -1,6 +1,6 @@ using System; using NUnit.Framework; -#if !NETCORE_SUPPORT +#if !NETCORE using ServiceStack.Serialization; #endif @@ -9,7 +9,7 @@ namespace ServiceStack.Text.Tests [TestFixture] public class DateTimeOffsetAndTimeSpanTests : TestBase { -#if !IOS && !NETCORE_SUPPORT +#if !IOS && !NETCORE [OneTimeSetUp] public void TestFixtureSetUp() { @@ -64,7 +64,7 @@ public void Can_serialize_TimeSpan_field() [Test] public void Can_serialize_TimeSpan_field_with_StandardTimeSpanFormat() { - using (JsConfig.With(timeSpanHandler:TimeSpanHandler.StandardFormat)) + using (JsConfig.With(new Config { TimeSpanHandler = TimeSpanHandler.StandardFormat })) { var period = TimeSpan.FromSeconds(70); @@ -77,7 +77,7 @@ public void Can_serialize_TimeSpan_field_with_StandardTimeSpanFormat() [Test] public void Can_serialize_NullableTimeSpan_field_with_StandardTimeSpanFormat() { - using (JsConfig.With(timeSpanHandler: TimeSpanHandler.StandardFormat)) + using (JsConfig.With(new Config { TimeSpanHandler = TimeSpanHandler.StandardFormat })) { var period = TimeSpan.FromSeconds(70); @@ -90,7 +90,7 @@ public void Can_serialize_NullableTimeSpan_field_with_StandardTimeSpanFormat() [Test] public void Can_serialize_NullTimeSpan_field_with_StandardTimeSpanFormat() { - using (JsConfig.With(timeSpanHandler: TimeSpanHandler.StandardFormat)) + using (JsConfig.With(new Config { TimeSpanHandler = TimeSpanHandler.StandardFormat })) { var model = new NullableSampleModel { Id = 1 }; var json = JsonSerializer.SerializeToString(model); diff --git a/tests/ServiceStack.Text.Tests/DictionaryTests.cs b/tests/ServiceStack.Text.Tests/DictionaryTests.cs index 3b88a14a7..400d44f51 100644 --- a/tests/ServiceStack.Text.Tests/DictionaryTests.cs +++ b/tests/ServiceStack.Text.Tests/DictionaryTests.cs @@ -1,7 +1,9 @@ using System; using System.Collections.Generic; using System.Collections.Specialized; +using System.Globalization; using System.Text.RegularExpressions; +using System.Threading; using NUnit.Framework; using ServiceStack.Text.Tests.DynamicModels.DataModel; @@ -248,7 +250,7 @@ public void Test_ServiceStack_Text_JsonSerializer_Array_Value_Deserializes_Corre } [Test] - public void deserizes_to_decimal_by_default() + public void Deserializes_to_decimal_by_default() { JsConfig.TryToParsePrimitiveTypeValues = true; @@ -277,7 +279,7 @@ public NumericType(decimal min, decimal max, Type type) } [Test] - public void deserizes_signed_bytes_into_to_best_fit_numeric() + public void Deserializes_signed_bytes_into_to_best_fit_numeric() { JsConfig.TryToParsePrimitiveTypeValues = true; JsConfig.TryToParseNumericType = true; @@ -290,8 +292,9 @@ public void deserizes_signed_bytes_into_to_best_fit_numeric() Assert.That(deserializedDict["max"], Is.EqualTo(sbyte.MaxValue)); } +#if NETFX [Test] - public void deserizes_floats_into_to_best_fit_floating_point() + public void Deserializes_floats_into_to_best_fit_floating_point() { JsConfig.TryToParsePrimitiveTypeValues = true; JsConfig.TryToParseNumericType = true; @@ -299,7 +302,8 @@ public void deserizes_floats_into_to_best_fit_floating_point() float floatValue = 1.1f; //TODO find a number that doesn't suck which throws in float.Parse() but not double.Parse() - double doubleValue = double.MaxValue - Math.Pow(2, 1000); + double Offset = Math.Pow(2, 1000); + double doubleValue = double.MaxValue - Offset; var intValue = int.MaxValue; var longValue = long.MaxValue; @@ -309,17 +313,17 @@ public void deserizes_floats_into_to_best_fit_floating_point() var toFloatValue = float.Parse(floatValue.ToString()); Assert.AreEqual(toFloatValue, floatValue, 1); var toDoubleValue = double.Parse(doubleValue.ToString()); - Assert.AreEqual(toDoubleValue, doubleValue, Math.Pow(2, 1000)); + Assert.AreEqual(toDoubleValue, doubleValue, Offset); var json = "{{\"float\":{0},\"double\":{1},\"int\":{2},\"long\":{3}}}" - .Fmt(floatValue, doubleValue, intValue, longValue); + .Fmt(CultureInfo.InvariantCulture, floatValue, doubleValue, intValue, longValue); var map = JsonSerializer.DeserializeFromString>(json); Assert.That(map["float"], Is.TypeOf()); Assert.That(map["float"], Is.EqualTo(floatValue)); - + Assert.That(map["double"], Is.TypeOf()); - Assert.AreEqual((double)map["double"], doubleValue, Math.Pow(2, 1000)); + Assert.AreEqual((double)map["double"], doubleValue, Offset); Assert.That(map["int"], Is.TypeOf()); Assert.That(map["int"], Is.EqualTo(intValue)); @@ -329,9 +333,10 @@ public void deserizes_floats_into_to_best_fit_floating_point() JsConfig.Reset(); } +#endif [Test] - public void deserizes_signed_types_into_to_best_fit_numeric() + public void Deserializes_signed_types_into_to_best_fit_numeric() { var unsignedTypes = new[] { @@ -363,7 +368,7 @@ public void deserizes_signed_types_into_to_best_fit_numeric() } [Test] - public void deserizes_unsigned_types_into_to_best_fit_numeric() + public void Deserializes_unsigned_types_into_to_best_fit_numeric() { var unsignedTypes = new[] { @@ -527,7 +532,7 @@ public void Can_serialize_string_string_Dictionary_with_UTF8() public void Can_Deserialize_Object_To_Dictionary() { const string json = "{\"Id\":1}"; - var d = json.To>(); + var d = json.ConvertTo>(); Assert.That(d.ContainsKey("Id")); Assert.That(d["Id"], Is.EqualTo("1")); } diff --git a/tests/ServiceStack.Text.Tests/DynamicNumberTests.cs b/tests/ServiceStack.Text.Tests/DynamicNumberTests.cs index e63b86466..0bf524e75 100644 --- a/tests/ServiceStack.Text.Tests/DynamicNumberTests.cs +++ b/tests/ServiceStack.Text.Tests/DynamicNumberTests.cs @@ -1,4 +1,6 @@ -using NUnit.Framework; +using System.Globalization; +using System.Threading; +using NUnit.Framework; namespace ServiceStack.Text.Tests { @@ -77,7 +79,7 @@ public void Does_convert_string_to_appropriate_popular_type() Assert.That(DynamicNumber.TryParse("1", out o) && o is int i && i == 1); Assert.That(DynamicNumber.TryParse(int.MaxValue.ToString(), out o) && o is int imax && imax == int.MaxValue); Assert.That(DynamicNumber.TryParse((int.MaxValue + (long)1).ToString(), out o) && o is long l && l == int.MaxValue + (long)1); - Assert.That(DynamicNumber.TryParse((long.MaxValue + (double)1).ToString(), out o) && o is double d ? d : 0, Is.EqualTo(long.MaxValue + (double)1).Within(10000)); + Assert.That(DynamicNumber.TryParse((long.MaxValue + (double)1).ToString(CultureInfo.InvariantCulture), out o) && o is double d ? d : 0, Is.EqualTo(long.MaxValue + (double)1).Within(10000)); Assert.That(DynamicNumber.TryParse("1.1", out o) && o is double d2 && d2 == 1.1); } @@ -193,5 +195,22 @@ public void TryParseIntoBestFit_tests() JsConfig.TryParseIntoBestFit = false; } + [Test] + public void Can_get_Nullable_number() + { + int? null1 = 1; + var number = DynamicNumber.GetNumber(null1.GetType()); + Assert.That(number.ToString(null1), Is.EqualTo("1")); + } + + [Test] + public void Can_text_for_numbers() + { + object x = null; + Assert.That(DynamicNumber.TryParse("(", out x), Is.False); + Assert.That(DynamicNumber.TryParse("-", out x), Is.False); + } + + } } \ No newline at end of file diff --git a/tests/ServiceStack.Text.Tests/DynamicObjectTests.cs b/tests/ServiceStack.Text.Tests/DynamicObjectTests.cs index 7dbbdcf52..20ea8e9fa 100644 --- a/tests/ServiceStack.Text.Tests/DynamicObjectTests.cs +++ b/tests/ServiceStack.Text.Tests/DynamicObjectTests.cs @@ -319,25 +319,51 @@ public void Does_deserialize_int_objects() JsConfig.Reset(); } - string SerializeObject(object value) + string SerializeObject(object value) => new TypeWithObjects { Value = value }.ToJson(); + + private void SerializeObjectTypes() { - return new TypeWithObjects { Value = value }.ToJson(); + Assert.That(SerializeObject((string) "a"), Is.EqualTo("{\"Value\":\"a\"}")); + Assert.That(SerializeObject((byte) 1), Is.EqualTo("{\"Value\":1}")); + Assert.That(SerializeObject((sbyte) 1), Is.EqualTo("{\"Value\":1}")); + Assert.That(SerializeObject((short) 1), Is.EqualTo("{\"Value\":1}")); + Assert.That(SerializeObject((ushort) 1), Is.EqualTo("{\"Value\":1}")); + Assert.That(SerializeObject((int) 1), Is.EqualTo("{\"Value\":1}")); + Assert.That(SerializeObject((uint) 1), Is.EqualTo("{\"Value\":1}")); + Assert.That(SerializeObject((long) 1), Is.EqualTo("{\"Value\":1}")); + Assert.That(SerializeObject((ulong) 1), Is.EqualTo("{\"Value\":1}")); + Assert.That(SerializeObject((float) 1.1), Is.EqualTo("{\"Value\":1.1}")); + Assert.That(SerializeObject((double) 1.1), Is.EqualTo("{\"Value\":1.1}")); + Assert.That(SerializeObject((decimal) 1.1), Is.EqualTo("{\"Value\":1.1}")); } + object DeserializeObject(string json) => json.FromJson().Value; + [Test] public void Does_serialize_number_object_types() { - Assert.That(SerializeObject((byte)1), Is.EqualTo("{\"Value\":1}")); - Assert.That(SerializeObject((sbyte)1), Is.EqualTo("{\"Value\":1}")); - Assert.That(SerializeObject((short)1), Is.EqualTo("{\"Value\":1}")); - Assert.That(SerializeObject((ushort)1), Is.EqualTo("{\"Value\":1}")); - Assert.That(SerializeObject((int)1), Is.EqualTo("{\"Value\":1}")); - Assert.That(SerializeObject((uint)1), Is.EqualTo("{\"Value\":1}")); - Assert.That(SerializeObject((long)1), Is.EqualTo("{\"Value\":1}")); - Assert.That(SerializeObject((ulong)1), Is.EqualTo("{\"Value\":1}")); - Assert.That(SerializeObject((float)1.1), Is.EqualTo("{\"Value\":1.1}")); - Assert.That(SerializeObject((double)1.1), Is.EqualTo("{\"Value\":1.1}")); - Assert.That(SerializeObject((decimal)1.1), Is.EqualTo("{\"Value\":1.1}")); + SerializeObjectTypes(); + + Assert.That(DeserializeObject("{\"Value\":\"a\"}"), Is.EqualTo((string) "a")); + Assert.That(DeserializeObject("{\"Value\":1}"), Is.EqualTo("1")); + Assert.That(DeserializeObject("{\"Value\":1.1}"), Is.EqualTo("1.1")); + Assert.That(DeserializeObject("{\"Value\":\"a\nb\"}"), Is.EqualTo("a\nb")); + } + + [Test] + public void Does_serialize_number_object_types_with_JS_utils() + { + JS.Configure(); + + SerializeObjectTypes(); + + Assert.That(DeserializeObject("{\"Value\":\"a\"}"), Is.EqualTo("a")); + Assert.That(DeserializeObject("{\"Value\":1}"), Is.EqualTo(1)); + Assert.That(DeserializeObject("{\"Value\":1.1}"), Is.EqualTo((double)1.1)); + Assert.That(DeserializeObject("{\"Value\":\"a\nb\"}"), Is.EqualTo("a\nb")); + + JS.UnConfigure(); } + } } \ No newline at end of file diff --git a/tests/ServiceStack.Text.Tests/EnumTests.cs b/tests/ServiceStack.Text.Tests/EnumTests.cs index bf93f0d60..bfe4b1705 100644 --- a/tests/ServiceStack.Text.Tests/EnumTests.cs +++ b/tests/ServiceStack.Text.Tests/EnumTests.cs @@ -36,7 +36,7 @@ public class ClassWithEnums public EnumWithFlags? NullableFlagsEnum { get; set; } public EnumWithoutFlags? NullableNoFlagsEnum { get; set; } } - + [Test] public void Can_correctly_serialize_enums() { @@ -152,7 +152,7 @@ public void Can_serialize_different_enum_styles() Assert.That("DoubleWord".FromJson(), Is.EqualTo(EnumStyles.DoubleWord)); Assert.That("Underscore_Words".FromJson(), Is.EqualTo(EnumStyles.Underscore_Words)); - using (JsConfig.With(emitLowercaseUnderscoreNames: true)) + using (JsConfig.With(new Config { TextCase = TextCase.SnakeCase })) { Assert.That("Double_Word".FromJson(), Is.EqualTo(EnumStyles.DoubleWord)); Assert.That("Underscore_Words".FromJson(), Is.EqualTo(EnumStyles.Underscore_Words)); @@ -169,7 +169,7 @@ public class NullableEnum [Test] public void Can_deserialize_null_Nullable_Enum() { - JsConfig.ThrowOnDeserializationError = true; + JsConfig.ThrowOnError = true; string json = @"{""myEnum"":null}"; var o = json.FromJson(); Assert.That(o.MyEnum, Is.Null); @@ -177,6 +177,140 @@ public void Can_deserialize_null_Nullable_Enum() JsConfig.Reset(); } + [Test] + public void Does_write_EnumValues_when_ExcludeDefaultValues() + { + using (JsConfig.With(new Config { ExcludeDefaultValues = true })) + { + Assert.That(new ClassWithEnums + { + NoFlagsEnum = EnumWithoutFlags.One + }.ToJson(), Is.EqualTo("{\"FlagsEnum\":0,\"NoFlagsEnum\":\"One\"}")); + + Assert.That(new ClassWithEnums + { + NoFlagsEnum = EnumWithoutFlags.Zero + }.ToJson(), Is.EqualTo("{\"FlagsEnum\":0,\"NoFlagsEnum\":\"Zero\"}")); + } + + using (JsConfig.With(new Config { ExcludeDefaultValues = true, IncludeDefaultEnums = false })) + { + Assert.That(new ClassWithEnums + { + NoFlagsEnum = EnumWithoutFlags.One + }.ToJson(), Is.EqualTo("{\"NoFlagsEnum\":\"One\"}")); + + Assert.That(new ClassWithEnums + { + NoFlagsEnum = EnumWithoutFlags.Zero + }.ToJson(), Is.EqualTo("{}")); + } + } + + [DataContract] + public enum Day + { + [EnumMember(Value = "MON")] + Monday, + [EnumMember(Value = "TUE")] + Tuesday, + [EnumMember(Value = "WED")] + Wednesday, + [EnumMember(Value = "THU")] + Thursday, + [EnumMember(Value = "FRI")] + Friday, + [EnumMember(Value = "SAT")] + Saturday, + [EnumMember(Value = "SUN")] + Sunday, + } + + class EnumMemberDto + { + public Day Day { get; set; } + + public Day? NDay { get; set; } + } + + [Test] + public void Does_serialize_EnumMember_Value() + { + var dto = new EnumMemberDto { Day = Day.Sunday }; + + var json = dto.ToJson(); + Assert.That(json, Is.EqualTo("{\"Day\":\"SUN\"}")); + var fromDto = json.FromJson(); + Assert.That(fromDto.Day, Is.EqualTo(Day.Sunday)); + + var jsv = dto.ToJsv(); + Assert.That(jsv, Is.EqualTo("{Day:SUN}")); + fromDto = jsv.FromJsv(); + Assert.That(fromDto.Day, Is.EqualTo(Day.Sunday)); + + var csv = dto.ToCsv(); + Assert.That(csv.NormalizeNewLines(), Is.EqualTo("Day,NDay\nSUN,\n".NormalizeNewLines())); + } + + [Test] + public void Does_serialize_EnumMember_enum() + { + Assert.That(Day.Sunday.ToJson(), Is.EqualTo("\"SUN\"")); + Assert.That(Day.Sunday.ToJsv(), Is.EqualTo("SUN")); + Assert.That(Day.Sunday.ToCsv(), Is.EqualTo("SUN")); + + Assert.That(((Day?)Day.Sunday).ToJson(), Is.EqualTo("\"SUN\"")); + Assert.That(((Day?)Day.Sunday).ToJsv(), Is.EqualTo("SUN")); + Assert.That(((Day?)Day.Sunday).ToCsv(), Is.EqualTo("SUN")); + } + + [Test] + public void Can_deserialize_EnumMember_with_int_value() + { + var fromDto = "{\"Day\":1}".FromJson(); + Assert.That(fromDto.Day, Is.EqualTo(Day.Tuesday)); + } + + public class GetDayOfWeekAsInt + { + public DayOfWeek DayOfWeek { get; set; } + } + + [Test] + public void Can_override_TreatEnumAsInteger() + { + JsConfig.Init(new Config + { + TreatEnumAsInteger = false, + }); + + using (JsConfig.With(new Config + { + TreatEnumAsInteger = true + })) + { + Assert.That(new GetDayOfWeekAsInt { DayOfWeek = DayOfWeek.Tuesday }.ToJson(), Is.EqualTo("{\"DayOfWeek\":2}")); + Assert.That("{\"DayOfWeek\":2}".FromJson().DayOfWeek, Is.EqualTo(DayOfWeek.Tuesday)); + } + + Assert.That(new GetDayOfWeekAsInt { DayOfWeek = DayOfWeek.Tuesday }.ToJson(), Is.EqualTo("{\"DayOfWeek\":\"Tuesday\"}")); + Assert.That("{\"DayOfWeek\":\"Tuesday\"}".FromJson().DayOfWeek, Is.EqualTo(DayOfWeek.Tuesday)); + + JsConfig.Reset(); + } + + public class FeatureDto + { + public LicenseFeature Feature { get; set; } + } + + [Test] + public void Can_deserialize_Flag_Enum_with_multiple_same_values() + { + var key = "{\"Feature\":\"Premium\"}".FromJson(); + Assert.That(key.Feature, Is.EqualTo(LicenseFeature.Premium)); + } + } } diff --git a/tests/ServiceStack.Text.Tests/ExpandoTests.cs b/tests/ServiceStack.Text.Tests/ExpandoTests.cs index e34b332e7..41544e8d5 100644 --- a/tests/ServiceStack.Text.Tests/ExpandoTests.cs +++ b/tests/ServiceStack.Text.Tests/ExpandoTests.cs @@ -8,7 +8,6 @@ namespace ServiceStack.Text.Tests [TestFixture] public class ExpandoTests : TestBase { - [Test] public void Can_serialize_one_level_expando() { @@ -177,7 +176,7 @@ public void Can_Deserialize_Object_To_ExpandoObject() { JsConfig.TryToParsePrimitiveTypeValues = true; const string json = "{\"Id\":1}"; - dynamic d = json.To(); + dynamic d = json.ConvertTo(); Assert.That(d.Id, Is.Not.Null); Assert.That(d.Id, Is.EqualTo(1)); } diff --git a/tests/ServiceStack.Text.Tests/GlobPathTests.cs b/tests/ServiceStack.Text.Tests/GlobPathTests.cs index 20a416627..60302676f 100644 --- a/tests/ServiceStack.Text.Tests/GlobPathTests.cs +++ b/tests/ServiceStack.Text.Tests/GlobPathTests.cs @@ -47,6 +47,7 @@ public void Does_validate_GlobPaths() Assert.That("dir/a/b/c/d/e/file.json".GlobPath("dir/**/*.json")); Assert.That("/jspm_packages/npm/zone.js@0.6.26.json".GlobPath("jspm_packages/**/*.json")); + Assert.That("/.well-known/acme-challenge/XzF9VXFuw4UMBVdiX2jDj2vykjrvEsQR8AZ8kJiaBdk".GlobPath(".well-known/**/*")); } } } \ No newline at end of file diff --git a/tests/ServiceStack.Text.Tests/HttpUtilsMockTests.cs b/tests/ServiceStack.Text.Tests/HttpUtilsMockTests.cs index 88093707f..9870126e4 100644 --- a/tests/ServiceStack.Text.Tests/HttpUtilsMockTests.cs +++ b/tests/ServiceStack.Text.Tests/HttpUtilsMockTests.cs @@ -1,4 +1,5 @@ -// Copyright (c) ServiceStack, Inc. All Rights Reserved. +#if !NET6_0_OR_GREATER +// Copyright (c) ServiceStack, Inc. All Rights Reserved. // License: https://raw.github.com/ServiceStack/ServiceStack/master/license.txt using System.Collections.Generic; @@ -217,3 +218,4 @@ public async Task Can_Mock_BytesFn_Api_responses_Async() } } +#endif diff --git a/tests/ServiceStack.Text.Tests/HttpUtilsTests.cs b/tests/ServiceStack.Text.Tests/HttpUtilsTests.cs index e36ceeb07..bf8e243fe 100644 --- a/tests/ServiceStack.Text.Tests/HttpUtilsTests.cs +++ b/tests/ServiceStack.Text.Tests/HttpUtilsTests.cs @@ -15,6 +15,14 @@ public void Can_AddQueryParam() Assert.That("http://example.com?s=rf&f=1".AddQueryParam("f", "2"), Is.EqualTo("http://example.com?s=rf&f=1&f=2")); Assert.That("http://example.com?".AddQueryParam("f", "1"), Is.EqualTo("http://example.com?f=1")); Assert.That("http://example.com?f=1&".AddQueryParam("f", "2"), Is.EqualTo("http://example.com?f=1&f=2")); + Assert.That("http://example.com?ab=0".AddQueryParam("a", "1"), Is.EqualTo("http://example.com?ab=0&a=1")); + + Assert.That("".AddQueryParam("a", ""), Is.EqualTo("?a=")); + Assert.That("".AddQueryParam("a", null), Is.EqualTo("")); + Assert.That("/".AddQueryParam("a", null), Is.EqualTo("/")); + Assert.That("/".AddQueryParam("a", ""), Is.EqualTo("/?a=")); + Assert.That("/".AddQueryParam("a", "b"), Is.EqualTo("/?a=b")); + Assert.That((null as string).AddQueryParam("a", "b"), Is.EqualTo("?a=b")); } [Test] @@ -25,6 +33,7 @@ public void Can_SetQueryParam() Assert.That("http://example.com?f=1".SetQueryParam("f", "2"), Is.EqualTo("http://example.com?f=2")); Assert.That("http://example.com?s=0&f=1&s=1".SetQueryParam("f", "2"), Is.EqualTo("http://example.com?s=0&f=2&s=1")); Assert.That("http://example.com?s=rf&f=1".SetQueryParam("f", "2"), Is.EqualTo("http://example.com?s=rf&f=2")); + Assert.That("http://example.com?ab=0".SetQueryParam("a", "1"), Is.EqualTo("http://example.com?ab=0&a=1")); } [Test] @@ -35,6 +44,14 @@ public void Can_AddHashParam() Assert.That("http://example.com#f=1".AddHashParam("f", "2"), Is.EqualTo("http://example.com#f=1/f=2")); Assert.That("http://example.com#s=0/f=1/s=1".AddHashParam("f", "2"), Is.EqualTo("http://example.com#s=0/f=1/s=1/f=2")); Assert.That("http://example.com#s=rf/f=1".AddHashParam("f", "2"), Is.EqualTo("http://example.com#s=rf/f=1/f=2")); + Assert.That("http://example.com#ab=0".AddHashParam("a", "1"), Is.EqualTo("http://example.com#ab=0/a=1")); + + Assert.That("".AddHashParam("a", ""), Is.EqualTo("#a=")); + Assert.That("".AddHashParam("a", null), Is.EqualTo("")); + Assert.That("/".AddHashParam("a", null), Is.EqualTo("/")); + Assert.That("/".AddHashParam("a", ""), Is.EqualTo("/#a=")); + Assert.That("/".AddHashParam("a", "b"), Is.EqualTo("/#a=b")); + Assert.That((null as string).AddHashParam("a", "b"), Is.EqualTo("#a=b")); } [Test] @@ -45,6 +62,16 @@ public void Can_SetHashParam() Assert.That("http://example.com#f=1".SetHashParam("f", "2"), Is.EqualTo("http://example.com#f=2")); Assert.That("http://example.com#s=0/f=1/s=1".SetHashParam("f", "2"), Is.EqualTo("http://example.com#s=0/f=2/s=1")); Assert.That("http://example.com#s=rf/f=1".SetHashParam("f", "2"), Is.EqualTo("http://example.com#s=rf/f=2")); + Assert.That("http://example.com#ab=0".SetHashParam("a", "1"), Is.EqualTo("http://example.com#ab=0/a=1")); + } + + [Test] + public void Can_get_MimeType_file_extension() + { + Assert.That(MimeTypes.GetExtension(MimeTypes.Html), Is.EqualTo(".html")); + Assert.That(MimeTypes.GetExtension(MimeTypes.HtmlUtf8), Is.EqualTo(".html")); + Assert.That(MimeTypes.GetExtension(MimeTypes.ImagePng), Is.EqualTo(".png")); + Assert.That(MimeTypes.GetExtension(MimeTypes.ImageSvg), Is.EqualTo(".svg")); } } } \ No newline at end of file diff --git a/tests/ServiceStack.Text.Tests/InterfaceTests.cs b/tests/ServiceStack.Text.Tests/InterfaceTests.cs index be0a06c95..53e152ca5 100644 --- a/tests/ServiceStack.Text.Tests/InterfaceTests.cs +++ b/tests/ServiceStack.Text.Tests/InterfaceTests.cs @@ -120,7 +120,7 @@ public UserSession() public Dictionary ProviderOAuthAccess { get; set; } } -#if !NETCORE_SUPPORT +#if !NETCORE [Test] public void Can_Serialize_User_OAuthSession_map() { diff --git a/tests/ServiceStack.Text.Tests/InvalidJsonTests.cs b/tests/ServiceStack.Text.Tests/InvalidJsonTests.cs new file mode 100644 index 000000000..e282c0831 --- /dev/null +++ b/tests/ServiceStack.Text.Tests/InvalidJsonTests.cs @@ -0,0 +1,26 @@ +using NUnit.Framework; + +namespace ServiceStack.Text.Tests +{ + public class InvalidJsonTests + { + public class StoreContact + { + public string Name { get; set; } + public string Company { get; set; } + public int Age { get; set; } + } + + [Test] + public void Does_not_throw_in_empty_integer() + { + var json = "{\"Name\":\"\",\"Age\":\"\",\"Company\":\"\"}"; + + var dto = json.FromJson(); + + Assert.That(dto.Name, Is.EqualTo("")); + Assert.That(dto.Company, Is.EqualTo("")); + Assert.That(dto.Age, Is.EqualTo(0)); + } + } +} \ No newline at end of file diff --git a/tests/ServiceStack.Text.Tests/Issues/CircularReferenceIssues.cs b/tests/ServiceStack.Text.Tests/Issues/CircularReferenceIssues.cs new file mode 100644 index 000000000..654cc77ba --- /dev/null +++ b/tests/ServiceStack.Text.Tests/Issues/CircularReferenceIssues.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using NUnit.Framework; + +namespace ServiceStack.Text.Tests.Issues +{ + public class CircularMap : Collection> { } + + public class CircularDictionary : Dictionary { } + + public class CircularReferenceIssues + { + CircularMap CreateCircularMap() + { + var to = new CircularMap(); + to.Add(new KeyValuePair("X", to)); + return to; + } + + CircularDictionary CreateCircularDictionary() + { + var to = new CircularDictionary(); + to["X"] = to; + return to; + } + + [Test] + public void Does_detect_circular_references_of_CircularMap() + { + Assert.That(TypeSerializer.HasCircularReferences(CreateCircularMap())); + } + + [Test] + public void Does_not_report_CircularReferences_of_Built_In_Types() + { + Assert.That(TypeSerializer.HasCircularReferences(new DateTime()), Is.False); + Assert.That(TypeSerializer.HasCircularReferences(new TimeSpan()), Is.False); + Assert.That(TypeSerializer.HasCircularReferences(Guid.NewGuid()), Is.False); + } + + [Test] + public void CircularMap_does_stop_at_MaxLimit() + { + JsConfig.MaxDepth = 5; + + var o = CreateCircularMap(); + var json = o.ToJson(); + + Assert.That(json.CountOccurrencesOf('X'), Is.EqualTo(5)); + + JsConfig.Reset(); + } + + [Test] + public void Does_detect_circular_references_of_CircularDictionary() + { + Assert.That(TypeSerializer.HasCircularReferences(CreateCircularDictionary())); + } + + [Test] + public void CircularDictionary_does_stop_at_MaxLimit() + { + JsConfig.MaxDepth = 5; + + var o = CreateCircularDictionary(); + var json = o.ToJson(); + json.Print(); + + Assert.That(json.CountOccurrencesOf('X'), Is.EqualTo(5).Within(1)); + + JsConfig.Reset(); + } + } +} \ No newline at end of file diff --git a/tests/ServiceStack.Text.Tests/Issues/CompositeKeyIssue.cs b/tests/ServiceStack.Text.Tests/Issues/CompositeKeyIssue.cs new file mode 100644 index 000000000..daae1a98b --- /dev/null +++ b/tests/ServiceStack.Text.Tests/Issues/CompositeKeyIssue.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using NUnit.Framework; + +namespace ServiceStack.Text.Tests.Issues +{ + public class Container + { + public IDictionary Data { get; set; } = new Dictionary(); + } + + public struct CompositeKey + { + public string Name { get; set; } + public bool Value { get; set; } + public CompositeKey(string name, bool value) + { + Name = name; + Value = value; + } + + public CompositeKey(string jsonKey) + { + Name = jsonKey.LeftPart(':'); + Value = jsonKey.RightPart(':').ConvertTo(); + } + + public bool Equals(CompositeKey other) => + Name == other.Name && Value == other.Value; + + public override bool Equals(object obj) => + obj is CompositeKey other && Equals(other); + + public override int GetHashCode() + { + unchecked + { + return ((Name != null ? Name.GetHashCode() : 0) * 397) + ^ Value.GetHashCode(); + } + } + + public override string ToString() => $"{Name}:{Value}"; + } + + + public class CompositeKeyIssue + { + [Test] + public void Can_serialize_CompositeKey() + { + var dto = new Container + { + Data = new Dictionary + { + { new CompositeKey("abc", false), new[] { "1","2","3"} }, + { new CompositeKey("bdf", true), new[] { "b","c","d"} }, + { new CompositeKey("ceg", false), new[] { "4","5","6"} }, + } + }; + + var serialized = JsonSerializer.SerializeToString(dto); + var fromJson = JsonSerializer.DeserializeFromString>(serialized); + + Assert.That(fromJson.Data.Count, Is.EqualTo(dto.Data.Count)); + Assert.That(fromJson.Data, Is.EqualTo(dto.Data)); + } + } +} \ No newline at end of file diff --git a/tests/ServiceStack.Text.Tests/Issues/EscapedCharIssues.cs b/tests/ServiceStack.Text.Tests/Issues/EscapedCharIssues.cs new file mode 100644 index 000000000..f57911b0c --- /dev/null +++ b/tests/ServiceStack.Text.Tests/Issues/EscapedCharIssues.cs @@ -0,0 +1,27 @@ +using NUnit.Framework; + +namespace ServiceStack.Text.Tests.Issues +{ + public class EscapedCharIssues + { + public class MyChar + { + public char Char { get; set; } + } + + + [Test] + public void Does_unescaped_unicode_char() + { + var dto = new MyChar(); + + var json = dto.ToJson(); + + Assert.That(json, Is.EqualTo("{\"Char\":\"\\u0000\"}")); + + var fromDto = json.FromJson(); + + Assert.That(fromDto.Char, Is.EqualTo(dto.Char)); + } + } +} \ No newline at end of file diff --git a/tests/ServiceStack.Text.Tests/Issues/InvalidParsingIssues.cs b/tests/ServiceStack.Text.Tests/Issues/InvalidParsingIssues.cs new file mode 100644 index 000000000..57970109c --- /dev/null +++ b/tests/ServiceStack.Text.Tests/Issues/InvalidParsingIssues.cs @@ -0,0 +1,16 @@ +using NUnit.Framework; + +namespace ServiceStack.Text.Tests.Issues +{ + public class InvalidParsingIssues + { + [Test] + public void Parsing_string_into_string_array_splits_on_spaces() + { + var str = "string with a bunch of words"; + var result = str.FromJson(); + + Assert.That(result, Is.EquivalentTo(str.Split(' '))); + } + } +} \ No newline at end of file diff --git a/tests/ServiceStack.Text.Tests/Issues/JsConfigIssues.cs b/tests/ServiceStack.Text.Tests/Issues/JsConfigIssues.cs index 6add141cf..929030ba0 100644 --- a/tests/ServiceStack.Text.Tests/Issues/JsConfigIssues.cs +++ b/tests/ServiceStack.Text.Tests/Issues/JsConfigIssues.cs @@ -22,6 +22,11 @@ public CustomFormatType(int value) { _value = value; } + + public override string ToString() + { + return _value.ToString(); + } } class Dto @@ -44,6 +49,18 @@ public void CallReset_AfterSerializingOnce_WithCustomSerializationForProperty_Do TestRoundTripValue(dto); } + + [Test] + public void CallReset_AfterSerializingOnce_WithCustomSerializationForProperty_MustClearCustomSerialization() + { + var dto = new Dto { CustomFormatTypeProperty = new CustomFormatType(12345) }; + JsConfig.DeSerializeFn = str => + new CustomFormatType(int.Parse(str)); + var json = dto.ToJson(); + JsConfig.Reset(); + var fromJson = json.FromJson(); + Assert.That(fromJson.CustomFormatTypeProperty.Value, Is.EqualTo(0)); + } private static void ConfigureCustomFormatType() { diff --git a/tests/ServiceStack.Text.Tests/Issues/NullableIssues.cs b/tests/ServiceStack.Text.Tests/Issues/NullableIssues.cs new file mode 100644 index 000000000..1f378ce02 --- /dev/null +++ b/tests/ServiceStack.Text.Tests/Issues/NullableIssues.cs @@ -0,0 +1,54 @@ +using NUnit.Framework; + +namespace ServiceStack.Text.Tests.Issues +{ + public class NullableIssues + { + public class NBoolTest + { + public bool? IsOk {get; set;} + + protected bool Equals(NBoolTest other) => IsOk == other.IsOk; + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((NBoolTest) obj); + } + public override int GetHashCode() => IsOk.GetHashCode(); + } + + [Test] + public void Does_deserialize_nullable_bools() + { + Assert.That("{\"IsOk\": true}".FromJson(), Is.EqualTo(new NBoolTest { IsOk = true })); + Assert.That("{\"IsOk\": false}".FromJson(), Is.EqualTo(new NBoolTest { IsOk = false })); + Assert.That("{\"IsOk\": \"true\"}".FromJson(), Is.EqualTo(new NBoolTest { IsOk = true })); + Assert.That("{\"IsOk\": \"false\"}".FromJson(), Is.EqualTo(new NBoolTest { IsOk = false })); + Assert.That("{\"IsOk\": \"True\"}".FromJson(), Is.EqualTo(new NBoolTest { IsOk = true })); + Assert.That("{\"IsOk\": \"False\"}".FromJson(), Is.EqualTo(new NBoolTest { IsOk = false })); + + Assert.That("{\"IsOk\": null}".FromJson(), Is.EqualTo(new NBoolTest { IsOk = null })); + } + + [Test] + public void Does_deserialize_nullable_bools_conventions() + { + Assert.That("{\"IsOk\": \"t\"}".FromJson(), Is.EqualTo(new NBoolTest { IsOk = true })); + Assert.That("{\"IsOk\": \"f\"}".FromJson(), Is.EqualTo(new NBoolTest { IsOk = false })); + Assert.That("{\"IsOk\": \"Y\"}".FromJson(), Is.EqualTo(new NBoolTest { IsOk = true })); + Assert.That("{\"IsOk\": \"N\"}".FromJson(), Is.EqualTo(new NBoolTest { IsOk = false })); + Assert.That("{\"IsOk\": \"on\"}".FromJson(), Is.EqualTo(new NBoolTest { IsOk = true })); + Assert.That("{\"IsOk\": \"off\"}".FromJson(), Is.EqualTo(new NBoolTest { IsOk = false })); + } + + [Test] + public void Deserialize_nullable_bools_results_in_error() + { + Assert.That("{\"IsOk\": \"tt\"}".FromJson(), Is.EqualTo(new NBoolTest { IsOk = null })); + Assert.That("{\"IsOk\": \"fu\"}".FromJson(), Is.EqualTo(new NBoolTest { IsOk = null })); + Assert.That("{\"IsOk\": \"eee\"}".FromJson(), Is.EqualTo(new NBoolTest { IsOk = null })); + } + } +} \ No newline at end of file diff --git a/tests/ServiceStack.Text.Tests/Issues/SerializationPrecisionIssues.cs b/tests/ServiceStack.Text.Tests/Issues/SerializationPrecisionIssues.cs new file mode 100644 index 000000000..cdafc371c --- /dev/null +++ b/tests/ServiceStack.Text.Tests/Issues/SerializationPrecisionIssues.cs @@ -0,0 +1,62 @@ +using System; +using NUnit.Framework; + +namespace ServiceStack.Text.Tests.Issues +{ + public class SerializationPrecisionIssues + { + public class TimeSpanWrapper + { + public TimeSpan TimeSpan { get; set; } + } + + [Test] + public void Can_convert_min_TimeSpan() + { + var dto = new TimeSpanWrapper { + TimeSpan = TimeSpan.MinValue, + }; + var json = JsonSerializer.SerializeToString(dto, typeof(TimeSpanWrapper)); + var fromJson = JsonSerializer.DeserializeFromString(json); + Assert.That(fromJson.TimeSpan, Is.EqualTo(dto.TimeSpan)); + } + + [Test] + public void Can_convert_max_TimeSpan() + { + var dto = new TimeSpanWrapper { + TimeSpan = TimeSpan.MaxValue, + }; + var json = JsonSerializer.SerializeToString(dto, typeof(TimeSpanWrapper)); + var fromJson = JsonSerializer.DeserializeFromString(json); + Assert.That(fromJson.TimeSpan, Is.EqualTo(dto.TimeSpan)); + } + + class DateTimeOffsetWrapper + { + public DateTimeOffset DateTimeOffset { get; set; } + } + + [Test] + public void Can_convert_min_DateTimeOffset() + { + var dto = new DateTimeOffsetWrapper { + DateTimeOffset = DateTimeOffset.MinValue, + }; + var json = JsonSerializer.SerializeToString(dto, typeof(DateTimeOffsetWrapper)); + var fromJson = JsonSerializer.DeserializeFromString(json); + Assert.That(fromJson.DateTimeOffset, Is.EqualTo(dto.DateTimeOffset)); + } + + [Test] + public void Can_convert_max_DateTimeOffset() + { + var dto = new DateTimeOffsetWrapper { + DateTimeOffset = DateTimeOffset.MaxValue, + }; + var json = JsonSerializer.SerializeToString(dto, typeof(DateTimeOffsetWrapper)); + var fromJson = JsonSerializer.DeserializeFromString(json); + Assert.That(fromJson.DateTimeOffset, Is.EqualTo(dto.DateTimeOffset)); + } + } +} \ No newline at end of file diff --git a/tests/ServiceStack.Text.Tests/Issues/WhitespaceIssues.cs b/tests/ServiceStack.Text.Tests/Issues/WhitespaceIssues.cs new file mode 100644 index 000000000..fd8f76daf --- /dev/null +++ b/tests/ServiceStack.Text.Tests/Issues/WhitespaceIssues.cs @@ -0,0 +1,67 @@ +using System.Collections.Generic; +using System.IO; +using Northwind.Common.DataModel; +using NUnit.Framework; +using ServiceStack.Text.Json; + +namespace ServiceStack.Text.Tests.Issues +{ + public class WhitespaceIssues + { + [Test] + public void Does_deserialize_JsonObject_empty_string() + { + var json = "{\"Name\":\"\"}"; + var obj = json.FromJson(); + var name = obj.Get("Name"); + Assert.That(name, Is.EqualTo("")); + } + + [Test] + public void Does_deserialize_empty_string_to_object() + { + var json = "\"\""; + var obj = json.FromJson(); + Assert.That(obj, Is.EqualTo("")); + } + + public class ObjectEmptyStringTest + { + public object Name { get; set; } + } + + [Test] + public void Does_serialize_property_Empty_String() + { + JS.Configure(); + var dto = new ObjectEmptyStringTest { Name = "" }; + var json = dto.ToJson(); + + var fromJson = json.FromJson(); + Assert.That(fromJson.Name, Is.EqualTo(dto.Name)); + + var utf8 = json.ToUtf8Bytes(); + var ms = new MemoryStream(utf8); + var fromMs = JsonSerializer.DeserializeFromStream(ms); + Assert.That(fromMs.Name, Is.EqualTo(dto.Name)); + JS.UnConfigure(); + } + + [Test] + public void Does_serialize_object_dictionary() + { + JsConfig.ConvertObjectTypesIntoStringDictionary = true; + + var value = "{Number:10330,CountryKey:DE,FederalStateKey:\"\",Salutation:,City:''}"; + var map = (Dictionary) value.FromJsv(); + + Assert.That(map["Number"], Is.EqualTo("10330")); + Assert.That(map["CountryKey"], Is.EqualTo("DE")); + Assert.That(map["FederalStateKey"], Is.EqualTo("")); + Assert.That(map["Salutation"], Is.EqualTo("")); + Assert.That(map["City"], Is.EqualTo("''")); + + JsConfig.ConvertObjectTypesIntoStringDictionary = false; + } + } +} \ No newline at end of file diff --git a/tests/ServiceStack.Text.Tests/JsConfigTests.cs b/tests/ServiceStack.Text.Tests/JsConfigTests.cs index d1f10f347..38c298e73 100644 --- a/tests/ServiceStack.Text.Tests/JsConfigTests.cs +++ b/tests/ServiceStack.Text.Tests/JsConfigTests.cs @@ -38,8 +38,8 @@ public class JsConfigTests [OneTimeSetUp] public void TestFixtureSetUp() { - JsConfig.EmitLowercaseUnderscoreNames = true; - JsConfig.EmitLowercaseUnderscoreNames = false; + JsConfig.TextCase = TextCase.SnakeCase; + JsConfig.TextCase = TextCase.PascalCase; } [OneTimeTearDown] @@ -58,7 +58,7 @@ public void Does_use_specific_configuration() [Test] public void Can_override_default_configuration() { - using (JsConfig.With(emitLowercaseUnderscoreNames: false)) + using (JsConfig.With(new Config { TextCase = TextCase.PascalCase })) { Assert.That(new Foo { FooBar = "value" }.ToJson(), Is.EqualTo("{\"FooBar\":\"value\"}")); } @@ -66,28 +66,37 @@ public void Can_override_default_configuration() } [TestFixture] - public class SerializEmitLowerCaseUnderscoreNamesTests + public class SerializeEmitLowerCaseUnderscoreNamesTests { [Test] public void TestJsonDataWithJsConfigScope() { - using (JsConfig.With(emitLowercaseUnderscoreNames: true, - propertyConvention: PropertyConvention.Lenient)) + using (JsConfig.With(new Config { TextCase = TextCase.SnakeCase, PropertyConvention = PropertyConvention.Lenient})) AssertObjectJson(); } + [Test] + public void TestJsonDataWithJsConfigScope_ext() + { + Assert.That(CreateObject().ToJson(config => config.TextCase = TextCase.SnakeCase), + Is.EqualTo("{\"id\":1,\"root_id\":100,\"display_name\":\"Test object\"}")); + Assert.That(CreateObject().ToJson(config => config.TextCase = TextCase.CamelCase), + Is.EqualTo("{\"id\":1,\"rootId\":100,\"displayName\":\"Test object\"}")); + Assert.That(CreateObject().ToJson(config => config.TextCase = TextCase.PascalCase), + Is.EqualTo("{\"Id\":1,\"RootId\":100,\"DisplayName\":\"Test object\"}")); + } + [Test] public void TestCloneObjectWithJsConfigScope() { - using (JsConfig.With(emitLowercaseUnderscoreNames: true, - propertyConvention: PropertyConvention.Lenient)) + using (JsConfig.With(new Config { TextCase = TextCase.SnakeCase, PropertyConvention = PropertyConvention.Lenient})) AssertObject(); } [Test] public void TestJsonDataWithJsConfigGlobal() { - JsConfig.EmitLowercaseUnderscoreNames = true; + JsConfig.TextCase = TextCase.SnakeCase; JsConfig.PropertyConvention = PropertyConvention.Lenient; AssertObjectJson(); @@ -98,7 +107,7 @@ public void TestJsonDataWithJsConfigGlobal() [Test] public void TestCloneObjectWithJsConfigGlobal() { - JsConfig.EmitLowercaseUnderscoreNames = true; + JsConfig.TextCase = TextCase.SnakeCase; JsConfig.PropertyConvention = PropertyConvention.Lenient; AssertObject(); @@ -109,7 +118,7 @@ public void TestCloneObjectWithJsConfigGlobal() [Test] public void TestJsonDataWithJsConfigLocal() { - JsConfig.EmitLowercaseUnderscoreNames = true; + JsConfig.TextCase = TextCase.SnakeCase; JsConfig.PropertyConvention = PropertyConvention.Lenient; AssertObjectJson(); @@ -120,9 +129,8 @@ public void TestJsonDataWithJsConfigLocal() [Test] public void TestCloneObjectWithJsConfigLocal() { - JsConfig.EmitLowercaseUnderscoreNames = false; - JsConfig.EmitLowercaseUnderscoreNames = true; - JsConfig.PropertyConvention = PropertyConvention.Lenient; + JsConfig.TextCase = TextCase.Default; + JsConfig.TextCase = TextCase.SnakeCase; AssertObject(); @@ -132,8 +140,8 @@ public void TestCloneObjectWithJsConfigLocal() [Test] public void TestCloneObjectWithoutLowercaseThroughJsConfigLocal() { - JsConfig.EmitLowercaseUnderscoreNames = true; - JsConfig.EmitLowercaseUnderscoreNames = false; + JsConfig.TextCase = TextCase.SnakeCase; + JsConfig.TextCase = TextCase.Default; JsConfig.PropertyConvention = PropertyConvention.Lenient; AssertObject(); @@ -203,6 +211,19 @@ class TestObject } private const string AssertMessageFormat = "Cannot find correct property value ({0})"; + + [Test] + public void Can_indent_in_scoped_json() + { + var obj = new TestObject { Id = 1 }; + using (JsConfig.With(new Config { Indent = true })) + { + var scopedJson = obj.ToJson(); + Assert.That(scopedJson.NormalizeNewLines(), Is.EqualTo("{\n \"Id\": 1,\n \"RootId\": 0\n}")); + } + var json = obj.ToJson(); + Assert.That(json, Is.EqualTo("{\"Id\":1,\"RootId\":0}")); + } } [TestFixture] @@ -211,37 +232,88 @@ public class JsConfigCreateTests [Test] public void Does_create_scope_from_string() { - var scope = JsConfig.CreateScope("EmitCamelCaseNames,emitlowercaseunderscorenames,IncludeNullValues:false,ExcludeDefaultValues:0,IncludeDefaultEnums:1"); - Assert.That(scope.EmitCamelCaseNames.Value); - Assert.That(scope.EmitLowercaseUnderscoreNames.Value); - Assert.That(!scope.IncludeNullValues.Value); - Assert.That(!scope.ExcludeDefaultValues.Value); - Assert.That(scope.IncludeDefaultEnums.Value); + var scope = JsConfig.CreateScope("emitlowercaseunderscorenames,IncludeNullValues:false,ExcludeDefaultValues:0,IncludeDefaultEnums:1,indent"); + Assert.That(scope.TextCase, Is.EqualTo(TextCase.SnakeCase)); + Assert.That(!scope.IncludeNullValues); + Assert.That(!scope.ExcludeDefaultValues); + Assert.That(scope.IncludeDefaultEnums); + Assert.That(scope.Indent); scope.Dispose(); - scope = JsConfig.CreateScope("DateHandler:ISO8601,timespanhandler:durationformat,PropertyConvention:strict"); + scope = JsConfig.CreateScope("DateHandler:ISO8601,timespanhandler:durationformat,PropertyConvention:strict,TextCase:CamelCase"); Assert.That(scope.DateHandler, Is.EqualTo(DateHandler.ISO8601)); Assert.That(scope.TimeSpanHandler, Is.EqualTo(TimeSpanHandler.DurationFormat)); Assert.That(scope.PropertyConvention, Is.EqualTo(PropertyConvention.Strict)); + Assert.That(scope.TextCase, Is.EqualTo(TextCase.CamelCase)); scope.Dispose(); } [Test] public void Does_create_scope_from_string_using_CamelCaseHumps() { - var scope = JsConfig.CreateScope("eccn,elun,inv:false,edv:0,ide:1"); - Assert.That(scope.EmitCamelCaseNames.Value); - Assert.That(scope.EmitLowercaseUnderscoreNames.Value); - Assert.That(!scope.IncludeNullValues.Value); - Assert.That(!scope.ExcludeDefaultValues.Value); - Assert.That(scope.IncludeDefaultEnums.Value); + var scope = JsConfig.CreateScope("eccn,inv:false,edv:0,ide:1,pp"); + Assert.That(scope.TextCase, Is.EqualTo(TextCase.CamelCase)); + Assert.That(!scope.IncludeNullValues); + Assert.That(!scope.ExcludeDefaultValues); + Assert.That(scope.IncludeDefaultEnums); + Assert.That(scope.Indent); scope.Dispose(); - scope = JsConfig.CreateScope("dh:ISO8601,tsh:df,pc:strict"); + scope = JsConfig.CreateScope("dh:ISO8601,tsh:df,pc:strict,tc:cc"); Assert.That(scope.DateHandler, Is.EqualTo(DateHandler.ISO8601)); Assert.That(scope.TimeSpanHandler, Is.EqualTo(TimeSpanHandler.DurationFormat)); Assert.That(scope.PropertyConvention, Is.EqualTo(PropertyConvention.Strict)); + Assert.That(scope.TextCase, Is.EqualTo(TextCase.CamelCase)); scope.Dispose(); } } + + public class JsConfigInitTests + { + [TearDown] public void TearDown() => JsConfig.Reset(); + + [Test] + public void Allows_setting_config_before_Init() + { + JsConfig.MaxDepth = 1; + JsConfig.Init(new Config { + DateHandler = DateHandler.UnixTime + }); + } + + [Test] + public void Does_not_allow_setting_JsConfig_after_Init() + { + JsConfig.Init(new Config { + DateHandler = DateHandler.UnixTime + }); + + Assert.Throws(() => JsConfig.MaxDepth = 1000); + } + + [Test] + public void Does_not_allow_setting_multiple_inits_in_StrictMode() + { + JsConfig.Init(); + JsConfig.Init(new Config { MaxDepth = 1 }); + + Env.StrictMode = true; + + Assert.Throws(JsConfig.Init); + } + + [Test] + public void Does_combine_global_configs_in_multiple_inits() + { + JsConfig.Init(new Config { MaxDepth = 1 }); + JsConfig.Init(new Config { DateHandler = DateHandler.UnixTime }); + + Assert.That(JsConfig.MaxDepth, Is.EqualTo(1)); + Assert.That(JsConfig.DateHandler, Is.EqualTo(DateHandler.UnixTime)); + + var newConfig = new Config(); + Assert.That(newConfig.MaxDepth, Is.EqualTo(1)); + Assert.That(newConfig.DateHandler, Is.EqualTo(DateHandler.UnixTime)); + } + } } \ No newline at end of file diff --git a/tests/ServiceStack.Text.Tests/JsonObjectTests.cs b/tests/ServiceStack.Text.Tests/JsonObjectTests.cs index a53d476b9..667781dfd 100644 --- a/tests/ServiceStack.Text.Tests/JsonObjectTests.cs +++ b/tests/ServiceStack.Text.Tests/JsonObjectTests.cs @@ -1,5 +1,8 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.IO; using System.Linq; +using System.Text; using NUnit.Framework; namespace ServiceStack.Text.Tests @@ -89,6 +92,45 @@ public void Does_encode_large_strings() System.Diagnostics.Debug.WriteLine(copy1["test"]); } } + + public interface IFoo + { + string Property { get; } + } + + public class FooA : IFoo + { + public string Property { get; set; } = "A"; + } + + [Test] + public void Can_consistently_serialize_stream() + { + var item = new FooA(); + var result1 = SerializeToStream1(item); + var result2 = SerializeToStream2(item); + var result3 = SerializeToStream1(item); + var result4 = SerializeToStream2(item); + + Assert.That(result1, Is.EqualTo(result2)); + Assert.That(result3, Is.EqualTo(result4)); + } + + // Serialize using TypeSerializer.SerializeToStream(T, Stream) + public static string SerializeToStream1(IFoo item) + { + using var stream = new MemoryStream(); + TypeSerializer.SerializeToStream(item, stream); + return Encoding.UTF8.GetString(stream.GetBuffer(), 0, (int)stream.Length); + } + + // Serialize using TypeSerializer.SerializeToStream(object, Type, Stream) + public static string SerializeToStream2(IFoo item) + { + using var stream = new MemoryStream(); + TypeSerializer.SerializeToStream(item, item.GetType(), stream); + return Encoding.UTF8.GetString(stream.GetBuffer(), 0, (int)stream.Length); + } [Test] public void Can_parse_Twitter_response() @@ -309,5 +351,171 @@ public void Can_deserialize_array_objects_in_Map() new TestJArray { Id = 2, Name = "Role 2" }, })); } + + [Test] + public void Can_deserialice_string_list() + { + var obj = new JsonObject { + ["null"] = null, + ["item"] = "foo", + ["list"] = new List { "foo", "bar", "qux" }.ToJson() + }; + + var nullList = obj["null"].FromJson>(); + var itemList = obj["item"].FromJson>(); + var listList = obj["list"].FromJson>(); + + Assert.That(nullList, Is.Null); + Assert.That(itemList, Is.EquivalentTo(new[]{ "foo" })); + Assert.That(listList, Is.EquivalentTo(new[]{ "foo", "bar", "qux" })); + } + + [Test] + public void Can_deserialize_Inherited_JSON_Object() + { + var jsonValue = "{\"test\":[\"Test1\",\"Test Two\"]}"; + + var jsonObject = JsonSerializer.DeserializeFromString(jsonValue); + var inheritedJsonObject = JsonSerializer.DeserializeFromString(jsonValue); + + string testString = jsonObject.Child("test"); + string inheritedTestString = inheritedJsonObject.Child("test"); + + Assert.AreEqual(testString, inheritedTestString); + + var serializedJsonObject = JsonSerializer.SerializeToString(jsonObject); + var serializedInheritedJsonObject = JsonSerializer.SerializeToString(inheritedJsonObject); + + Assert.AreEqual(serializedJsonObject, serializedInheritedJsonObject); + } + + public class InheritedJsonObject : JsonObject { } + + [Test] + public void Does_escape_string_values() + { + var json = JsonObject.Parse("{\"text\":\"line\nbreak\"}"); + Assert.That(json["text"], Is.EqualTo("line\nbreak")); + + json = JsonObject.Parse("{\"a\":{\"text\":\"line\nbreak\"}}"); + var a = json.Object("a"); + Assert.That(a["text"], Is.EqualTo("line\nbreak")); + } + + public class JsonObjectWrapper + { + public JsonObject Prop { get; set; } + } + + [Test] + public void Does_escape_strings_in_JsonObject_DTO() + { + var dto = "{\"Prop\":{\"text\":\"line\nbreak\"}}".FromJson(); + Assert.That(dto.Prop["text"], Is.EqualTo("line\nbreak")); + + Assert.That(dto.ToJson(), Is.EqualTo("{\"Prop\":{\"text\":\"line\\nbreak\"}}")); + + dto = "{\"Prop\":{\"a\":{\"text\":\"line\nbreak\"}}}".FromJson(); + var a = dto.Prop.Object("a"); + Assert.That(a["text"], Is.EqualTo("line\nbreak")); + + // + //Assert.That(dto.ToJson(), Is.EqualTo("{\"Prop\":{\"a\":{\"text\":\"line\\nbreak\"}}}")); + } + + public class StringDictionaryWrapper + { + public Dictionary Prop { get; set; } + } + + public class NestedStringDictionaryWrapper + { + public Dictionary> Prop { get; set; } + } + + [Test] + public void Does_serialize_StringDictionaryWrapper_line_breaks() + { + var prop = new Dictionary { + ["text"] = "line\nbreak" + }; + + Assert.That(prop.ToJson(), Is.EqualTo("{\"text\":\"line\\nbreak\"}")); + + var dto = new StringDictionaryWrapper { Prop = prop }; + Assert.That(dto.ToJson(), Is.EqualTo("{\"Prop\":{\"text\":\"line\\nbreak\"}}")); + + var nested = new NestedStringDictionaryWrapper { Prop = new Dictionary> { + ["a"] = prop + } + }; + Assert.That(nested.ToJson(), Is.EqualTo("{\"Prop\":{\"a\":{\"text\":\"line\\nbreak\"}}}")); + } + + public class ObjectDictionaryWrapper + { + public object Prop { get; set; } + } + + [Test] + public void Does_serialize_ObjectDictionaryWrapper_line_breaks() + { + var prop = new Dictionary { + ["text"] = "line\nbreak" + }; + + var dto = new ObjectDictionaryWrapper { Prop = prop }; + Assert.That(dto.ToJson(), Is.EqualTo("{\"Prop\":{\"text\":\"line\\nbreak\"}}")); + + var nested = new ObjectDictionaryWrapper { Prop = new Dictionary> { + ["a"] = prop + } + }; + Assert.That(nested.ToJson(), Is.EqualTo("{\"Prop\":{\"a\":{\"text\":\"line\\nbreak\"}}}")); + } + + [Test] + public void Enumerating_JsonObject_returns_same_unescaped_value_as_indexer() + { + var obj = JsonObject.Parse(@"{""a"":""b\\c""}"); + Assert.That(obj["a"], Is.EqualTo("b\\c")); + + foreach (var entry in obj) + { + if (entry.Key == "a") + { + Assert.That(entry.Value, Is.EqualTo("b\\c")); + } + } + + var asEnumerable = (IEnumerable>)obj; + foreach (var entry in asEnumerable) + { + if (entry.Key == "a") + { + Assert.That(entry.Value, Is.EqualTo("b\\c")); + } + } + + var asIDict = (IDictionary)obj; + foreach (var entry in asIDict) + { + if (entry.Key == "a") + { + Assert.That(entry.Value, Is.EqualTo("b\\c")); + } + } + + // Warning: can't override concrete Dictionary enumerator + // var asDict = (Dictionary)obj; + // foreach (var entry in asDict) + // { + // if (entry.Key == "a") + // { + // Assert.That(entry.Value, Is.EqualTo("b\\c")); + // } + // } + } + } } \ No newline at end of file diff --git a/tests/ServiceStack.Text.Tests/JsonTests/AnonymousDeserializationTests.cs b/tests/ServiceStack.Text.Tests/JsonTests/AnonymousDeserializationTests.cs index 91a114e9e..71638f632 100644 --- a/tests/ServiceStack.Text.Tests/JsonTests/AnonymousDeserializationTests.cs +++ b/tests/ServiceStack.Text.Tests/JsonTests/AnonymousDeserializationTests.cs @@ -53,7 +53,7 @@ public void Deserialize_dynamic_json() string dob = dyn.DateOfBirth; "DynamicJson: {0}, {1}, {2}".Print(id, name, dob); - using (JsConfig.With(convertObjectTypesIntoStringDictionary: true)) + using (JsConfig.With(new Config { ConvertObjectTypesIntoStringDictionary = true })) { "Object Dictionary".Print(); var map = (Dictionary)json.FromJson(); @@ -77,5 +77,18 @@ public void Deserialize_dynamic_json_with_inner_obj_and_array() var phone2 = dyn.obj.phones[1].number; Assert.AreEqual(phone2, "39967"); } + + [Test] + public void Deserialize_dynamic_json_with_keys_starting_with_object_literal() + { + using (JsConfig.With(new Config {ConvertObjectTypesIntoStringDictionary = true})) + { + var json = @"{""prop1"": ""value1"", ""prop2"": ""{tag} value2"", ""prop3"": { ""A"" : 1 } }"; + var obj = json.FromJson>(); + Assert.That(obj["prop1"], Is.EqualTo("value1")); + Assert.That(obj["prop2"], Is.EqualTo("{tag} value2")); + Assert.That(obj["prop3"], Is.EqualTo(new Dictionary { ["A"] = "1" })); + } + } } } \ No newline at end of file diff --git a/tests/ServiceStack.Text.Tests/JsonTests/BasicJsonTests.cs b/tests/ServiceStack.Text.Tests/JsonTests/BasicJsonTests.cs index b13ad889a..25a787f36 100644 --- a/tests/ServiceStack.Text.Tests/JsonTests/BasicJsonTests.cs +++ b/tests/ServiceStack.Text.Tests/JsonTests/BasicJsonTests.cs @@ -25,6 +25,7 @@ public class JsonPrimitives public bool Boolean { get; set; } public DateTime DateTime { get; set; } public string NullString { get; set; } + public string EmptyString { get; set; } public static JsonPrimitives Create(int i) { @@ -53,11 +54,6 @@ public class NullableValueTypes [SetUp] public void Setup() { -#if IOS - JsConfig.Reset(); - JsConfig.RegisterTypeForAot(); - JsConfig.RegisterTypeForAot(); -#endif } [TearDown] @@ -135,6 +131,12 @@ public void Can_parse_mixed_enumarable_nulls() Is.EqualTo(new string[] { "abc", null, "cde", null })); } + [Test] + public void Can_parse_mixed_enumarable_empty_strings() + { + Assert.That(JsonSerializer.DeserializeFromString>("[\"abc\",\"\",\"cde\",\"\"]"), + Is.EqualTo(new string[] { "abc", "", "cde", "" })); + } [Test] public void Can_handle_json_primitives() @@ -149,11 +151,12 @@ public void Can_handle_json_primitives() [Test] public void Can_parse_json_with_nulls() { - const string json = "{\"Int\":1,\"NullString\":null}"; + const string json = "{\"Int\":1,\"NullString\":null,\"EmptyString\":\"\"}"; var value = JsonSerializer.DeserializeFromString(json); Assert.That(value.Int, Is.EqualTo(1)); Assert.That(value.NullString, Is.Null); + Assert.That(value.EmptyString, Is.EqualTo("")); } [Test] @@ -579,7 +582,7 @@ public void Can_include_null_values_for_adhoc_types() JsConfig.RawSerializeFn = obj => { - using (JsConfig.With(includeNullValues: true)) + using (JsConfig.With(new Config { IncludeNullValues = true })) return obj.ToJson(); }; @@ -593,7 +596,7 @@ public void Can_run_FromJson_within_RawDeserializeFn() { JsConfig.RawDeserializeFn = json => { - using (JsConfig.With(includeNullValues: true)) + using (JsConfig.With(new Config { IncludeNullValues = true })) return json.FromJson(); }; @@ -605,7 +608,7 @@ public void Can_run_FromJson_within_RawDeserializeFn() [Test] public void Does_include_null_values_in_lists() { - using (JsConfig.With(includeNullValues: true)) + using (JsConfig.With(new Config { IncludeNullValues = true })) { var dto = new List { @@ -623,5 +626,50 @@ public void Does_include_null_values_in_lists() Assert.That(fromJson.Count, Is.EqualTo(dto.Count)); } } + + [Test] + public void Can_deserialize_int_with_null_values() + { + var json = "{\"id\":null,\"name\":null}"; + var dto = json.FromJson(); + + Assert.That(dto.Id, Is.EqualTo(default(int))); + Assert.That(dto.Name, Is.Null); + } + + public partial class ThrowValidation + { + public virtual int Age { get; set; } + public virtual string Required { get; set; } + public virtual string Email { get; set; } + } + + [Test] + public void Can_deserialize_ThrowValidation_with_null_values() + { + var json = "{\"version\":null,\"age\":null,\"required\":null,\"email\":\"invalidemail\"}"; + var dto = json.FromJson(); + + Assert.That(dto.Age, Is.EqualTo(default(int))); + Assert.That(dto.Required, Is.Null); + Assert.That(dto.Email, Is.EqualTo("invalidemail")); + } + + class TestTrim + { + public string Description { get; set; } + } + + [Test] + public void Can_deserialize_custom_string_deserializer() + { + JsConfig.DeSerializeFn = str => str.Trim(); + var json = "{\"description\":null}"; + var dto = json.FromJson(); + Assert.That(dto.Description, Is.Null); + JsConfig.DeSerializeFn = null; + } + + } } diff --git a/tests/ServiceStack.Text.Tests/JsonTests/BasicPropertiesTests.cs b/tests/ServiceStack.Text.Tests/JsonTests/BasicPropertiesTests.cs index 28162605e..7ad25b38a 100644 --- a/tests/ServiceStack.Text.Tests/JsonTests/BasicPropertiesTests.cs +++ b/tests/ServiceStack.Text.Tests/JsonTests/BasicPropertiesTests.cs @@ -185,7 +185,7 @@ public class ModelWithHashSet [Test] public void Can_deserialize_null_Nested_HashSet() { - JsConfig.ThrowOnDeserializationError = true; + JsConfig.ThrowOnError = true; string json = @"{""set"":null}"; var o = json.FromJson(); Assert.That(o.Set, Is.Null); diff --git a/tests/ServiceStack.Text.Tests/JsonTests/CamelCaseTests.cs b/tests/ServiceStack.Text.Tests/JsonTests/CamelCaseTests.cs index 104274b3f..244cbb71a 100644 --- a/tests/ServiceStack.Text.Tests/JsonTests/CamelCaseTests.cs +++ b/tests/ServiceStack.Text.Tests/JsonTests/CamelCaseTests.cs @@ -12,7 +12,7 @@ public class CamelCaseTests : TestBase [SetUp] public void SetUp() { - JsConfig.EmitCamelCaseNames = true; + JsConfig.TextCase = TextCase.CamelCase; } [TearDown] diff --git a/tests/ServiceStack.Text.Tests/JsonTests/ContractByInterfaceTests.cs b/tests/ServiceStack.Text.Tests/JsonTests/ContractByInterfaceTests.cs index bfc287a07..6cfb168f8 100644 --- a/tests/ServiceStack.Text.Tests/JsonTests/ContractByInterfaceTests.cs +++ b/tests/ServiceStack.Text.Tests/JsonTests/ContractByInterfaceTests.cs @@ -13,7 +13,7 @@ public class ContractByInterfaceTests [Test] public void Prefer_interfaces_should_work_on_top_level_object_using_extension_method() { - using (JsConfig.With(preferInterfaces:true)) + using (JsConfig.With(new Config { PreferInterfaces = true })) { var json = new Concrete("boo", 1).ToJson(); @@ -24,7 +24,7 @@ public void Prefer_interfaces_should_work_on_top_level_object_using_extension_me [Test] public void Should_be_able_to_serialise_based_on_an_interface() { - using (JsConfig.With(preferInterfaces: true)) + using (JsConfig.With(new Config { PreferInterfaces = true })) { IContract myConcrete = new Concrete("boo", 1); var json = JsonSerializer.SerializeToString(myConcrete, typeof(IContract)); @@ -37,7 +37,7 @@ public void Should_be_able_to_serialise_based_on_an_interface() [Test] public void Should_not_use_interface_type_if_concrete_specified() { - using (JsConfig.With(preferInterfaces: false)) + using (JsConfig.With(new Config { PreferInterfaces = false })) { IContract myConcrete = new Concrete("boo", 1); var json = JsonSerializer.SerializeToString(myConcrete, typeof(IContract)); @@ -50,7 +50,7 @@ public void Should_not_use_interface_type_if_concrete_specified() [Test] public void Should_be_able_to_deserialise_based_on_an_interface_with_no_concrete() { - using (JsConfig.With(preferInterfaces: true)) + using (JsConfig.With(new Config { PreferInterfaces = true })) { var json = new Concrete("boo", 42).ToJson(); diff --git a/tests/ServiceStack.Text.Tests/JsonTests/CustomSerializerTests.cs b/tests/ServiceStack.Text.Tests/JsonTests/CustomSerializerTests.cs index 5bc166e32..c0086f00a 100644 --- a/tests/ServiceStack.Text.Tests/JsonTests/CustomSerializerTests.cs +++ b/tests/ServiceStack.Text.Tests/JsonTests/CustomSerializerTests.cs @@ -4,6 +4,7 @@ using System.Linq; using NUnit.Framework; using System.Runtime.Serialization; +using System.Threading; namespace ServiceStack.Text.Tests.JsonTests { @@ -184,7 +185,7 @@ public DtoV1() [Test] public void Can_detect_dto_with_no_Version() { - using (JsConfig.With(modelFactory: type => + using (JsConfig.With(new Config { ModelFactory = type => { if (typeof(IHasVersion).IsAssignableFrom(type)) { @@ -196,7 +197,7 @@ public void Can_detect_dto_with_no_Version() }; } return type.CreateInstance; - })) + }})) { var dto = new Dto { Name = "Foo" }; var fromDto = dto.ToJson().FromJson(); @@ -225,7 +226,7 @@ public void Can_deserialize_json_with_underscores() Assert.That(dto.ErrorCode, Is.Null); - using (JsConfig.With(propertyConvention: PropertyConvention.Lenient)) + using (JsConfig.With(new Config { PropertyConvention = PropertyConvention.Lenient })) { dto = json.FromJson(); @@ -276,7 +277,7 @@ public void Can_serialize_custom_ints() var dto = new ModelInt { Int = 0 }; - using (JsConfig.With(includeNullValues: true)) + using (JsConfig.With(new Config { IncludeNullValues = true })) { Assert.That(dto.ToJson(), Is.EqualTo("{\"Int\":-1}")); } @@ -315,37 +316,35 @@ public FormatAttribute(string format) public class DcStatus { [Format("{0:0.0} V")] - public Double Voltage { get; set; } + public double Voltage { get; set; } + [Format("{0:0.000} A")] - public Double Current { get; set; } + public double Current { get; set; } + [Format("{0:0} W")] - public Double Power - { - get { return Voltage * Current; } - } + public double Power => Voltage * Current; public string ToJson() { return new Dictionary { - { "Voltage", "{0:0.0} V".Fmt(Voltage) }, - { "Current", "{0:0.000} A".Fmt(Current) }, - { "Power", "{0:0} W".Fmt(Power) }, + { "Voltage", string.Format(CultureInfo.InvariantCulture, "{0:0.0} V", Voltage)}, + { "Current", string.Format(CultureInfo.InvariantCulture, "{0:0.000} A", Current)}, // Use $"{Current:0.000} A" if you don't care about culture + { "Power", $"{Power:0} W"}, }.ToJson(); } } - public class DcStatus2 + public class DcStatusRawFn { [Format("{0:0.0} V")] - public Double Voltage { get; set; } + public double Voltage { get; set; } + [Format("{0:0.000} A")] - public Double Current { get; set; } + public double Current { get; set; } + [Format("{0:0} W")] - public Double Power - { - get { return Voltage*Current; } - } + public double Power => Voltage * Current; } [Test] @@ -354,13 +353,13 @@ public void Can_deserialize_using_CustomFormat() var test = new DcStatus { Voltage = 10, Current = 1.2 }; Assert.That(test.ToJson(), Is.EqualTo("{\"Voltage\":\"10.0 V\",\"Current\":\"1.200 A\",\"Power\":\"12 W\"}")); - JsConfig.RawSerializeFn = o => new Dictionary { - { "Voltage", "{0:0.0} V".Fmt(o.Voltage) }, - { "Current", "{0:0.000} A".Fmt(o.Current) }, - { "Power", "{0:0} W".Fmt(o.Power) }, + JsConfig.RawSerializeFn = o => new Dictionary { + { "Voltage", string.Format(CultureInfo.InvariantCulture, "{0:0.0} V", o.Voltage)}, + { "Current", string.Format(CultureInfo.InvariantCulture, "{0:0.000} A", o.Current)}, + { "Power", $"{o.Power:0} W"}, }.ToJson(); - var test2 = new DcStatus2 { Voltage = 10, Current = 1.2 }; + var test2 = new DcStatusRawFn { Voltage = 10, Current = 1.2 }; Assert.That(test2.ToJson(), Is.EqualTo("{\"Voltage\":\"10.0 V\",\"Current\":\"1.200 A\",\"Power\":\"12 W\"}")); JsConfig.Reset(); diff --git a/tests/ServiceStack.Text.Tests/JsonTests/DictionaryDeserializationTests.cs b/tests/ServiceStack.Text.Tests/JsonTests/DictionaryDeserializationTests.cs index 598179b9d..7cd3df292 100644 --- a/tests/ServiceStack.Text.Tests/JsonTests/DictionaryDeserializationTests.cs +++ b/tests/ServiceStack.Text.Tests/JsonTests/DictionaryDeserializationTests.cs @@ -12,18 +12,13 @@ public void CanDeserializeDictionaryOfComplexTypes() { JsConfig.ConvertObjectTypesIntoStringDictionary = true; - var dict = new Dictionary(); - - dict["ChildDict"] = new Dictionary - { - {"age", 12}, - {"name", "mike"} + var dict = new Dictionary { + ["ChildDict"] = new Dictionary {{"age", 12}, {"name", "mike"}}, + ["ChildIntList"] = new List {1, 2, 3}, + ["ChildStringList"] = new List {"a", "b", "c"}, + ["ChildObjectList"] = new List {1, "cat", new Dictionary {{"s", "s"}, {"n", 1}}} }; - dict["ChildIntList"] = new List {1, 2, 3}; - dict["ChildStringList"] = new List {"a", "b", "c"}; - dict["ChildObjectList"] = new List {1, "cat", new Dictionary {{"s", "s"}, {"n", 1}}}; - var serialized = JsonSerializer.SerializeToString(dict); var deserialized = JsonSerializer.DeserializeFromString>(serialized); diff --git a/tests/ServiceStack.Text.Tests/JsonTests/InvalidJsonTests.cs b/tests/ServiceStack.Text.Tests/JsonTests/InvalidJsonTests.cs index e782a87f6..d4fa6470a 100644 --- a/tests/ServiceStack.Text.Tests/JsonTests/InvalidJsonTests.cs +++ b/tests/ServiceStack.Text.Tests/JsonTests/InvalidJsonTests.cs @@ -28,7 +28,7 @@ public void Does_parse_invalid_JSON() [Test] public void Does_throw_on_invalid_JSON() { - JsConfig.ThrowOnDeserializationError = true; + JsConfig.ThrowOnError = true; var json = "[{\"ExtId\":\"2\",\"Name\":\"VIP sj�lland\",\"Mobiles\":[\"4533333333\",\"4544444444\"]"; diff --git a/tests/ServiceStack.Text.Tests/JsonTests/JsonArrayObjectTests.cs b/tests/ServiceStack.Text.Tests/JsonTests/JsonArrayObjectTests.cs index 5d867c715..30d554e3e 100644 --- a/tests/ServiceStack.Text.Tests/JsonTests/JsonArrayObjectTests.cs +++ b/tests/ServiceStack.Text.Tests/JsonTests/JsonArrayObjectTests.cs @@ -114,9 +114,10 @@ public void Can_parse_custom_AuthResponse() dto.PrintDump(); - using (JsConfig.With( - emitLowercaseUnderscoreNames: true, - propertyConvention: PropertyConvention.Lenient)) + using (JsConfig.With(new Config { + TextCase = TextCase.SnakeCase, + PropertyConvention = PropertyConvention.Lenient + })) { var response = json.FromJson(); response.PrintDump(); diff --git a/tests/ServiceStack.Text.Tests/JsonTests/JsonDateTimeTests.cs b/tests/ServiceStack.Text.Tests/JsonTests/JsonDateTimeTests.cs index 7a0980368..444dec72a 100644 --- a/tests/ServiceStack.Text.Tests/JsonTests/JsonDateTimeTests.cs +++ b/tests/ServiceStack.Text.Tests/JsonTests/JsonDateTimeTests.cs @@ -2,6 +2,7 @@ using System.Linq; using NUnit.Framework; using ServiceStack.Text.Common; +using ServiceStack.Text.Jsv; namespace ServiceStack.Text.Tests.JsonTests { @@ -309,6 +310,27 @@ public void When_using_ISO8601_and_serializing_as_Utc_It_should_deserialize_as_U Assert.AreEqual(initialDate, deserializedDate); } + [Test] + public void ISO8601_assumeUtc_serialize_datetime_is_the_same() + { + JsConfig.AssumeUtc = true; + JsConfig.DateHandler = DateHandler.ISO8601; + var initialDate = new DateTime(2012, 7, 25, 16, 17, 00, DateTimeKind.Unspecified); + var writers = new + { + jsv = new System.IO.StringWriter(new System.Text.StringBuilder()), + json = new System.IO.StringWriter(new System.Text.StringBuilder()) + }; + new JsvTypeSerializer().WriteDateTime(writers.jsv, initialDate); + new Json.JsonTypeSerializer().WriteDateTime(writers.json, initialDate); + var results = new + { + jsv = DateTime.SpecifyKind(DateTime.Parse(writers.jsv.ToString()), DateTimeKind.Utc), + json = DateTime.SpecifyKind(DateTime.Parse(writers.json.ToString().Replace("\"", "")), DateTimeKind.Utc) + }; + Assert.AreEqual(results.jsv, results.json); + } + [Test] public void Can_serialize_json_date_iso8601_utc() { @@ -395,7 +417,7 @@ public void Can_deserialize_json_date_iso8601_with_skipDateTimeConversion_true() Assert.AreEqual(deserilizedResult.Date, testObject.Date); Assert.AreEqual(DateTimeKind.Utc, deserilizedResult.Date.Kind); - using (JsConfig.With(skipDateTimeConversion: false)) + using (JsConfig.With(new Config { SkipDateTimeConversion = false })) { Assert.AreEqual(DateTimeKind.Local, JsonSerializer.DeserializeFromString(JsonSerializer.SerializeToString(testObject)).Date.Kind); } @@ -409,11 +431,11 @@ public void Can_deserialize_json_date_iso8601_with_skipDateTimeConversion_true() Assert.AreEqual(deserilizedResult.Date, testObject.Date); Assert.AreEqual(DateTimeKind.Utc, deserilizedResult.Date.Kind); - using (JsConfig.With(skipDateTimeConversion: false)) + using (JsConfig.With(new Config { SkipDateTimeConversion = false })) { Assert.AreEqual(DateTimeKind.Local, JsonSerializer.DeserializeFromString(JsonSerializer.SerializeToString(testObject)).Date.Kind); } - using (JsConfig.With(alwaysUseUtc: true, skipDateTimeConversion: false)) //It will work now + using (JsConfig.With(new Config { AlwaysUseUtc = true, SkipDateTimeConversion = false })) { Assert.AreEqual(DateTimeKind.Utc, JsonSerializer.DeserializeFromString(JsonSerializer.SerializeToString(testObject)).Date.Kind); } @@ -428,11 +450,11 @@ public void Can_deserialize_json_date_iso8601_with_skipDateTimeConversion_true() Assert.AreEqual(deserilizedResult.Date, testObject.Date); Assert.AreEqual(DateTimeKind.Local, deserilizedResult.Date.Kind); - using (JsConfig.With(alwaysUseUtc: true)) + using (JsConfig.With(new Config { AlwaysUseUtc = true })) { - Assert.AreEqual(DateTimeKind.Local, JsonSerializer.DeserializeFromString(JsonSerializer.SerializeToString(testObject)).Date.Kind); + Assert.AreEqual(DateTimeKind.Utc, JsonSerializer.DeserializeFromString(JsonSerializer.SerializeToString(testObject)).Date.Kind); } - using (JsConfig.With(alwaysUseUtc: true, skipDateTimeConversion: false)) + using (JsConfig.With(new Config { AlwaysUseUtc = true, SkipDateTimeConversion = false })) { Assert.AreEqual(DateTimeKind.Utc, JsonSerializer.DeserializeFromString(JsonSerializer.SerializeToString(testObject)).Date.Kind); } @@ -447,16 +469,16 @@ public void Can_deserialize_json_date_iso8601_with_skipDateTimeConversion_true() Assert.AreEqual(deserilizedResult.Date, testObject.Date); Assert.AreEqual(DateTimeKind.Unspecified, deserilizedResult.Date.Kind); - using (JsConfig.With(alwaysUseUtc: true)) + using (JsConfig.With(new Config { AlwaysUseUtc = true })) { Assert.AreEqual(DateTimeKind.Unspecified, JsonSerializer.DeserializeFromString(JsonSerializer.SerializeToString(testObject)).Date.Kind); } - using (JsConfig.With(alwaysUseUtc: true, skipDateTimeConversion: false)) + using (JsConfig.With(new Config { AlwaysUseUtc = true, SkipDateTimeConversion = false })) { Assert.AreEqual(DateTimeKind.Utc, JsonSerializer.DeserializeFromString(JsonSerializer.SerializeToString(testObject)).Date.Kind); } - using (JsConfig.With(skipDateTimeConversion: false)) + using (JsConfig.With(new Config { SkipDateTimeConversion = false })) { serilizedResult = JsonSerializer.SerializeToString(testObject); deserilizedResult = JsonSerializer.DeserializeFromString(serilizedResult); diff --git a/tests/ServiceStack.Text.Tests/JsonTests/JsonObjectTests.cs b/tests/ServiceStack.Text.Tests/JsonTests/JsonObjectTests.cs index 0d64ba7dd..8d0bb3343 100644 --- a/tests/ServiceStack.Text.Tests/JsonTests/JsonObjectTests.cs +++ b/tests/ServiceStack.Text.Tests/JsonTests/JsonObjectTests.cs @@ -1,4 +1,8 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Runtime.Serialization; +using System.Threading; using NUnit.Framework; namespace ServiceStack.Text.Tests.JsonTests @@ -26,9 +30,61 @@ public void Can_parse_empty_object_with_mixed_whitespaces() Assert.That(JsonObject.Parse("{ \n\t \n\r}"), Is.Empty); } + + public class JsonObjectResponse + { + public JsonObject result { get; set; } + } + + [Test] + public void Can_serialize_null_JsonObject_response() + { + JsConfig.ThrowOnError = true; + var json = "{\"result\":null}"; + var dto = json.FromJson(); + Assert.That(dto.result, Is.Null); + JsConfig.ThrowOnError = false; + } + [Test] public void Can_Serialize_numbers() { + var culture = new CultureInfo("en-US"); + Thread.CurrentThread.CurrentCulture = culture; + Thread.CurrentThread.CurrentUICulture = culture; + + string notNumber = "{\"field\":\"00001\"}"; + Assert.That(JsonObject.Parse(notNumber).ToJson(), Is.EqualTo(notNumber)); + + string num1 = "{\"field\":0}"; + Assert.That(JsonObject.Parse(num1).ToJson(), Is.EqualTo(num1)); + + string num2 = "{\"field\":0.5}"; + Assert.That(JsonObject.Parse(num2).ToJson(), Is.EqualTo(num2)); + + string num3 = "{\"field\":.5}"; + Assert.That(JsonObject.Parse(num3).ToJson(), Is.EqualTo(num3)); + + string num4 = "{\"field\":12312}"; + Assert.That(JsonObject.Parse(num4).ToJson(), Is.EqualTo(num4)); + + string num5 = "{\"field\":12312.1231}"; + Assert.That(JsonObject.Parse(num5).ToJson(), Is.EqualTo(num5)); + + string num6 = "{\"field\":1435252569117}"; + Assert.That(JsonObject.Parse(num6).ToJson(), Is.EqualTo(num6)); + + string num7 = "{\"field\":1435052569117}"; + Assert.That(JsonObject.Parse(num7).ToJson(), Is.EqualTo(num7)); + } + + [Test] + public void Can_Serialize_numbers_DifferentCulture() + { + var culture = new CultureInfo("sl-SI"); + Thread.CurrentThread.CurrentCulture = culture; + Thread.CurrentThread.CurrentUICulture = culture; + string notNumber = "{\"field\":\"00001\"}"; Assert.That(JsonObject.Parse(notNumber).ToJson(), Is.EqualTo(notNumber)); @@ -387,5 +443,144 @@ public class ElementActionDto // There can be more nested objects in here } + + public class CreateEvent + { + public EventContent Event { get; set; } + } + + public class EventContent + { + public JsonObject EventPayLoad { get; set; } + public object EventObject { get; set; } + } + + public class EventPayLoadPosition + { + public double? Heading { get; set; } + + public double? Accuracy { get; set; } + + public double? Speed { get; set; } + } + + [Test] + public void Can_deserialize_custom_JsonObject_payload() + { + var json = "{\"Event\":{\"EventPayload\":{\"Heading\":1.1}}}"; + var dto = json.FromJson(); + + Assert.That(dto.Event.EventPayLoad.Get("Heading"), Is.EqualTo("1.1")); + + var payload = dto.Event.EventPayLoad.ConvertTo(); + Assert.That(payload.Heading, Is.EqualTo(1.1)); + } + + [Test] + public void Can_deserialize_custom_object_payload() + { + JS.Configure(); + + var json = "{\"Event\":{\"EventObject\":{\"Heading\":1.1}}}"; + var dto = json.FromJson(); + + var obj = (Dictionary)dto.Event.EventObject; + + var payload = obj.FromObjectDictionary(); + + Assert.That(payload.Heading, Is.EqualTo(1.1)); + + JS.UnConfigure(); + } + + [Test] + public void Can_deserialize_custom_JsonObject_with_incorrect_payload() + { + var json = "{\"Event\":{\"EventPayload\":{\"Heading\":24.687999725341797.0}}}"; + var dto = json.FromJson(); + + try + { + var payload = dto.Event.EventPayLoad.ConvertTo(); + Assert.Fail("Should throw"); + } + catch (FormatException) {} + } + + class HasObjectDictionary + { + public Dictionary Properties { get; set; } + } + + [Test] + public void Can_deserialize_unknown_ObjectDictionary() + { + JS.Configure(); + + Assert.That("{\"Properties\":{\"a\":1}}".FromJson().Properties["a"], Is.EqualTo(1)); + Assert.That("{\"Properties\":{\"a\":\"1\"}}".FromJson().Properties["a"], Is.EqualTo("1")); + Assert.That("{\"Properties\":{\"a\":[\"1\",\"2\"]}}".FromJson().Properties["a"], Is.EquivalentTo(new[]{"1","2"})); + Assert.That("{\"Properties\":{\"a\":[1,2]}}".FromJson().Properties["a"], Is.EquivalentTo(new[]{1,2})); + + JS.UnConfigure(); + } + + class HasObjectList + { + public List Properties { get; set; } + } + + [Test] + public void Can_deserialize_unknown_ObjectList() + { + JS.Configure(); + + Assert.That("{\"Properties\":[\"1\"]}".FromJson().Properties[0], Is.EqualTo("1")); + Assert.That("{\"Properties\":[1]}".FromJson().Properties[0], Is.EqualTo(1)); + Assert.That("{\"Properties\":[[\"1\",\"2\"]]}".FromJson().Properties[0], Is.EquivalentTo(new[]{"1","2"})); + Assert.That("{\"Properties\":[[1,2]]}".FromJson().Properties[0], Is.EquivalentTo(new[]{1,2})); + Assert.That("{\"Properties\":[{\"a\":1}]".FromJson().Properties[0], Is.EquivalentTo(new Dictionary { + ["a"] = 1 + })); + + JS.UnConfigure(); + } + + [DataContract] + public class BrowseProtocolTemplateResponseLight + { + [DataMember(Name = "ProtocolTemplateId")] + public Guid Oid { get; set; } + + [DataMember] + public string Name { get; set; } + + [DataMember] + public string Title { get; set; } + + [DataMember] + public string Description { get; set; } + + [DataMember] + public List ProtocolBusinessObjects { get; set; } + + [DataMember] + public ResponseStatus ResponseStatus { get; set; } + } + + [Test] + public void Can_deserialize_JSV_List_object_when_ObjectDeserializer_is_configured() + { + JS.Configure(); + + var json = @"{ ProtocolTemplateId:d7f0aa3afd834e90aaa9a97b7efd0c03,Name: Rendezvényszervezés,Title: Test,Description: Rendezvényszervezés,ProtocolBusinessObjects:[[{ Oid: 3c229e70345f11e9bf726dd67bf32ebd,ObjectType: AwaitObject,Flags: 18,Name: AwaitObject1,Title: AwaitObject1,SecondaryTitle: AwaitObject1,Description: "",OwnerType: 1,Owner: { UserId: 2,DisplayName: System},DeadlineType: 2,DeadlineOffsetDays: 1,Priority: 2,PriorityToData: False,DeadlineToData: False,CompletedDateToData: False,OwnerToData: False,Visible: False,ProtocolTemplateId: d7f0aa3afd834e90aaa9a97b7efd0c03,ProtocolTemplateGroupId: e44dfc5da20942fe8532d6446ef0b76c,CreatedBy: { UserId: 4,DisplayName: Wiszt Máté},CreatedDateTime: 2019 - 03 - 21T15: 05:41.0929542 + 01:00}]]}"; + + var ret = json.FromJsv(); + + Assert.That(ret.ProtocolBusinessObjects.Count, Is.GreaterThan(0)); + + JS.UnConfigure(); + } + } } diff --git a/tests/ServiceStack.Text.Tests/JsonTests/LowercaseUnderscoreTests.cs b/tests/ServiceStack.Text.Tests/JsonTests/LowercaseUnderscoreTests.cs index bffbc2f4c..b0035f46f 100644 --- a/tests/ServiceStack.Text.Tests/JsonTests/LowercaseUnderscoreTests.cs +++ b/tests/ServiceStack.Text.Tests/JsonTests/LowercaseUnderscoreTests.cs @@ -12,7 +12,7 @@ public class LowercaseUnderscoreTests : TestBase [SetUp] public void SetUp() { - JsConfig.EmitLowercaseUnderscoreNames = true; + JsConfig.TextCase = TextCase.SnakeCase; } [TearDown] @@ -51,6 +51,15 @@ class Person public int Id { get; set; } [DataMember] public string Name { get; set; } + + [DataMember(Name = "sur_name")] + public string LastName { get; set; } + + [DataMember(Name = "current_age")] + public int? CurrentAge { get; set; } + + [DataMember(Name = "birth_day")] + public DateTime? BirthDay { get; set; } } class WithUnderscore @@ -78,13 +87,43 @@ public void Can_override_name() var person = new Person { Id = 123, - Name = "Abc" + Name = "Abc", + LastName = "Xyz" }; - Assert.That(TypeSerializer.SerializeToString(person), Is.EqualTo("{MyID:123,name:Abc}")); - Assert.That(JsonSerializer.SerializeToString(person), Is.EqualTo("{\"MyID\":123,\"name\":\"Abc\"}")); + Assert.That(TypeSerializer.SerializeToString(person), Is.EqualTo("{MyID:123,name:Abc,sur_name:Xyz}")); + Assert.That(JsonSerializer.SerializeToString(person), Is.EqualTo("{\"MyID\":123,\"name\":\"Abc\",\"sur_name\":\"Xyz\"}")); } + + [Test] + public void Can_override_name_and_deserialize_with_lenient_scope() + { + var person = new Person + { + Id = 123, + Name = "Abc", + LastName = "Xyz", + BirthDay = new DateTime(2000,1,2,12,0,0), + CurrentAge = 19 + }; + + using (JsConfig.With(new Config { + TextCase = TextCase.SnakeCase, + PropertyConvention = PropertyConvention.Lenient })) + { + var test = new List {person}; + var personSerialized = test.ToJson(); + var personFromString = personSerialized.FromJson>(); - + var fromJson = personFromString[0]; + Assert.That(person.Id, Is.EqualTo(fromJson.Id)); + Assert.That(person.Name, Is.EqualTo(fromJson.Name)); + Assert.That(person.LastName, Is.EqualTo(fromJson.LastName)); + Assert.That(person.BirthDay.Value, Is.EqualTo(fromJson.BirthDay.Value)); + Assert.That(person.CurrentAge.Value, Is.EqualTo(fromJson.CurrentAge.Value)); + } + } + + class WithUnderscoreSeveralDigits { public int? user_id_00_11 { get; set; } diff --git a/tests/ServiceStack.Text.Tests/JsonTests/OnDeserializationErrorTests.cs b/tests/ServiceStack.Text.Tests/JsonTests/OnDeserializationErrorTests.cs index 855f5f58d..6c576c852 100644 --- a/tests/ServiceStack.Text.Tests/JsonTests/OnDeserializationErrorTests.cs +++ b/tests/ServiceStack.Text.Tests/JsonTests/OnDeserializationErrorTests.cs @@ -57,11 +57,14 @@ public void TestReset() } [Test] - public void Invokes_callback_deserialization_of_array_with_missing_comma() + public void StrictMode_throws_Exception_on_array_with_missing_comma() { + Env.StrictMode = true; string json = @"{""Values"": [ { ""Val"": ""a""} { ""Val"": ""b""}] }"; + + Assert.Throws(() => json.FromJson()); - AssertThatInvalidJsonInvokesExpectedCallback(json, "Values", @"[ { ""Val"": ""a""} { ""Val"": ""b""}]", typeof(TestChildDto[]), null); + Env.StrictMode = false; } [Test] diff --git a/tests/ServiceStack.Text.Tests/JsonTests/PolymorphicListTests.cs b/tests/ServiceStack.Text.Tests/JsonTests/PolymorphicListTests.cs index 618f53796..14235662b 100644 --- a/tests/ServiceStack.Text.Tests/JsonTests/PolymorphicListTests.cs +++ b/tests/ServiceStack.Text.Tests/JsonTests/PolymorphicListTests.cs @@ -242,12 +242,8 @@ public void Can_deserialise_polymorphic_list_serialized_by_datacontractjsonseria using (var stream = new MemoryStream()) { dataContractJsonSerializer.WriteObject(stream, originalList); - stream.Position = 0; - using (var reader = new StreamReader(stream)) - { - var json = reader.ReadToEnd(); - deserializedList = JsonSerializer.DeserializeFromString>(json); - } + var json = stream.ReadToEnd(); + deserializedList = JsonSerializer.DeserializeFromString>(json); } Assert.That(deserializedList.Count, Is.EqualTo(originalList.Count)); @@ -331,7 +327,7 @@ public void Can_deserialise_an_entity_containing_a_polymorphic_property_serializ { var regex = new Regex(@"^(?[^:]+):#(?.*)$"); var match = regex.Match(value); - var typeName = string.Format("{0}.{1}", match.Groups["namespace"].Value, match.Groups["type"].Value.Replace(".", "+")); + var typeName = $"{match.Groups["namespace"].Value}.{match.Groups["type"].Value.Replace(".", "+")}"; return AssemblyUtils.FindType(typeName); }; @@ -354,12 +350,8 @@ public void Can_deserialise_an_entity_containing_a_polymorphic_property_serializ using (var stream = new MemoryStream()) { dataContractJsonSerializer.WriteObject(stream, originalPets); - stream.Position = 0; - using (var reader = new StreamReader(stream)) - { - var json = reader.ReadToEnd(); - deserializedPets = JsonSerializer.DeserializeFromString(json); - } + var json = stream.ReadToEnd(); + deserializedPets = JsonSerializer.DeserializeFromString(json); } Assert.That(deserializedPets.Cat.GetType(), Is.EqualTo(originalPets.Cat.GetType())); @@ -376,7 +368,7 @@ public void Can_deserialise_an_entity_containing_a_polymorphic_property_serializ #endif [Test] - public void Can_deserialise_an_entity_containing_a_polymorphic_property_serialized_by_newtonsoft() + public void Can_deserialize_an_entity_containing_a_polymorphic_property_serialized_by_newtonsoft() { var json = "{\"$type\":\"" @@ -404,7 +396,7 @@ public void Can_deserialise_an_entity_containing_a_polymorphic_property_serializ } [Test] - public void Can_deserialise_polymorphic_list_serialized_by_newtonsoft() + public void Can_deserialize_polymorphic_list_serialized_by_newtonsoft() { var json = "[{\"$type\":\"" diff --git a/tests/ServiceStack.Text.Tests/JsonTests/PublicFieldTest.cs b/tests/ServiceStack.Text.Tests/JsonTests/PublicFieldTest.cs index 86cf2058f..1e6864525 100644 --- a/tests/ServiceStack.Text.Tests/JsonTests/PublicFieldTest.cs +++ b/tests/ServiceStack.Text.Tests/JsonTests/PublicFieldTest.cs @@ -23,13 +23,13 @@ public class PublicFieldTest : TestBase [Test] public void Public_readonly_fields_can_be_deserialized() { - using (JsConfig.BeginScope()) + using (var config = JsConfig.BeginScope()) { - JsConfig.IncludePublicFields = true; + config.IncludePublicFields = true; var instance = new TypeWithPublicFields("Hello"); - var deserilized = instance.ToJson(); + var deserialized = instance.ToJson(); - var copy = deserilized.FromJson(); + var copy = deserialized.FromJson(); Assert.That(copy.Text, Is.EqualTo(instance.Text)); } diff --git a/tests/ServiceStack.Text.Tests/JsonTests/RouteValueDictionaryTests.cs b/tests/ServiceStack.Text.Tests/JsonTests/RouteValueDictionaryTests.cs index da921a1e1..89fc75227 100644 --- a/tests/ServiceStack.Text.Tests/JsonTests/RouteValueDictionaryTests.cs +++ b/tests/ServiceStack.Text.Tests/JsonTests/RouteValueDictionaryTests.cs @@ -1,4 +1,4 @@ -#if !NETCORE_SUPPORT +#if !NETCORE using System; using System.Runtime.Serialization; using System.Web.Routing; diff --git a/tests/ServiceStack.Text.Tests/JsonTests/ThrowOnDeserializeErrorTest.cs b/tests/ServiceStack.Text.Tests/JsonTests/ThrowOnDeserializeErrorTest.cs index 69d6cf945..e417d9594 100644 --- a/tests/ServiceStack.Text.Tests/JsonTests/ThrowOnDeserializeErrorTest.cs +++ b/tests/ServiceStack.Text.Tests/JsonTests/ThrowOnDeserializeErrorTest.cs @@ -13,7 +13,7 @@ public class ThrowOnDeserializeErrorTest public void Throws_on_protected_setter() { JsConfig.Reset(); - JsConfig.ThrowOnDeserializationError = true; + JsConfig.ThrowOnError = true; string json = @"{""idBadProt"":""abc"", ""idGood"":""2"" }"; Assert.Throws(typeof(SerializationException), () => JsonSerializer.DeserializeFromString(json, typeof(TestDto)), "Failed to set property 'idBadProt' with 'abc'"); @@ -23,7 +23,7 @@ public void Throws_on_protected_setter() public void Throws_on_incorrect_type() { JsConfig.Reset(); - JsConfig.ThrowOnDeserializationError = true; + JsConfig.ThrowOnError = true; string json = @"{""idBad"":""abc"", ""idGood"":""2"" }"; Assert.Throws(typeof(SerializationException), () => JsonSerializer.DeserializeFromString(json, typeof(TestDto)), "Failed to set property 'idBad' with 'abc'"); @@ -33,7 +33,7 @@ public void Throws_on_incorrect_type() public void Throws_on_incorrect_type_with_data_set() { JsConfig.Reset(); - JsConfig.ThrowOnDeserializationError = true; + JsConfig.ThrowOnError = true; try { @@ -54,7 +54,7 @@ public void Throws_on_incorrect_type_with_data_set() public void TestDoesNotThrow() { JsConfig.Reset(); - JsConfig.ThrowOnDeserializationError = false; + JsConfig.ThrowOnError = false; string json = @"{""idBad"":""abc"", ""idGood"":""2"" }"; JsonSerializer.DeserializeFromString(json, typeof(TestDto)); } @@ -63,11 +63,11 @@ public void TestDoesNotThrow() public void TestReset() { JsConfig.Reset(); - Assert.IsFalse(JsConfig.ThrowOnDeserializationError); - JsConfig.ThrowOnDeserializationError = true; - Assert.IsTrue(JsConfig.ThrowOnDeserializationError); + Assert.IsFalse(JsConfig.ThrowOnError); + JsConfig.ThrowOnError = true; + Assert.IsTrue(JsConfig.ThrowOnError); JsConfig.Reset(); - Assert.IsFalse(JsConfig.ThrowOnDeserializationError); + Assert.IsFalse(JsConfig.ThrowOnError); } [DataContract] diff --git a/tests/ServiceStack.Text.Tests/JsvTests/JsvBasicDataTests.cs b/tests/ServiceStack.Text.Tests/JsvTests/JsvBasicDataTests.cs index ae955b02b..293599587 100644 --- a/tests/ServiceStack.Text.Tests/JsvTests/JsvBasicDataTests.cs +++ b/tests/ServiceStack.Text.Tests/JsvTests/JsvBasicDataTests.cs @@ -102,5 +102,14 @@ public void Can_serialize_ModelWithNullableFloatTypes_From_String() fromJsv = jsv.FromJsv(); Assert.That(fromJsv, Is.EqualTo(dto)); } + + [Test] + public void Does_encode_object_string_values_with_escaped_chars() + { + var url = "https://url.com"; + Assert.That(url.ToJsv(), Is.EqualTo("\"https://url.com\"")); + Assert.That(((object)url).ToJsv(), Is.EqualTo("\"https://url.com\"")); + } + } } \ No newline at end of file diff --git a/tests/ServiceStack.Text.Tests/JsvTests/TypeSerializerToStringDictionaryTests.cs b/tests/ServiceStack.Text.Tests/JsvTests/TypeSerializerToStringDictionaryTests.cs index 49250da2e..a1bf22ec0 100644 --- a/tests/ServiceStack.Text.Tests/JsvTests/TypeSerializerToStringDictionaryTests.cs +++ b/tests/ServiceStack.Text.Tests/JsvTests/TypeSerializerToStringDictionaryTests.cs @@ -8,7 +8,7 @@ namespace ServiceStack.Text.Tests.JsvTests { [TestFixture] -#if NETCORE_SUPPORT +#if NETCORE [Ignore("Fix Northwind.dll")] #endif public class TypeSerializerToStringDictionaryTests diff --git a/tests/ServiceStack.Text.Tests/LicensingTests.cs b/tests/ServiceStack.Text.Tests/LicensingTests.cs index 9e66ec3ee..ef6347dc2 100644 --- a/tests/ServiceStack.Text.Tests/LicensingTests.cs +++ b/tests/ServiceStack.Text.Tests/LicensingTests.cs @@ -4,6 +4,7 @@ using System; using System.Collections; using System.IO; +using System.IO.Compression; using System.Reflection; using NUnit.Framework; @@ -44,6 +45,7 @@ public class LicensingTests [SetUp] public void SetUp() { + LicenseUtils.RemoveLicense(); } @@ -245,5 +247,52 @@ public void Doesnt_override_DateTime_config() Assert.That(result, Is.EqualTo(fixedDate)); } + + public class OldLicenseKey + { + public string Ref { get; set; } + public string Name { get; set; } + public LicenseType Type { get; set; } + public string Hash { get; set; } + public DateTime Expiry { get; set; } + } + + [Test] + public void Does_deserialize_LicenseKey() + { + var key = new LicenseKey { + Name = "The Name", + Ref = "1000", + Type = LicenseType.Business, + Expiry = new DateTime(2001,01,01), + Meta = (long)(LicenseMeta.Subscription | LicenseMeta.Cores), + }; + + var jsv = key.ToJsv(); + Assert.That(jsv, Does.Contain($"eta:" + (int)key.Meta)); + jsv.Print(); + + var fromKey = jsv.FromJsv(); + + Assert.That(fromKey.Name, Is.EqualTo(key.Name)); + Assert.That(fromKey.Ref, Is.EqualTo(key.Ref)); + Assert.That(fromKey.Type, Is.EqualTo(key.Type)); + Assert.That(fromKey.Expiry, Is.EqualTo(key.Expiry)); + Assert.That(fromKey.Meta, Is.EqualTo(key.Meta)); + + var oldKey = jsv.FromJsv(); + Assert.That(oldKey.Name, Is.EqualTo(key.Name)); + Assert.That(oldKey.Ref, Is.EqualTo(key.Ref)); + Assert.That(oldKey.Type, Is.EqualTo(key.Type)); + Assert.That(oldKey.Expiry, Is.EqualTo(key.Expiry)); + + var oldJsv = oldKey.ToJsv(); + fromKey = oldJsv.FromJsv(); + Assert.That(fromKey.Name, Is.EqualTo(key.Name)); + Assert.That(fromKey.Ref, Is.EqualTo(key.Ref)); + Assert.That(fromKey.Type, Is.EqualTo(key.Type)); + Assert.That(fromKey.Expiry, Is.EqualTo(key.Expiry)); + Assert.That(fromKey.Meta, Is.EqualTo(0)); + } } } \ No newline at end of file diff --git a/tests/ServiceStack.Text.Tests/MessagingTests.cs b/tests/ServiceStack.Text.Tests/MessagingTests.cs index b0038e984..8b00a6096 100644 --- a/tests/ServiceStack.Text.Tests/MessagingTests.cs +++ b/tests/ServiceStack.Text.Tests/MessagingTests.cs @@ -46,7 +46,7 @@ public void Can_serialize_object_IMessage_into_typed_Message() Assert.That(typedMessage.GetBody().Value, Is.EqualTo(dto.Value)); } -#if !NETCORE_SUPPORT +#if !NETCORE [Test] public void Can_serialize_IMessage_ToBytes_into_typed_Message() { diff --git a/tests/ServiceStack.Text.Tests/NUnitSetupFixture.cs b/tests/ServiceStack.Text.Tests/NUnitSetupFixture.cs new file mode 100644 index 000000000..8c65298f2 --- /dev/null +++ b/tests/ServiceStack.Text.Tests/NUnitSetupFixture.cs @@ -0,0 +1,16 @@ +//using NUnit.Framework; +// +//namespace ServiceStack.Text.Tests +//{ +// [SetUpFixture] +// public class NUnitSetupFixture +// { +// [OneTimeSetUp] +// public void Setup() +// { +//#if NETCORE +// ServiceStack.Memory.NetCoreMemory.Configure(); +//#endif +// } +// } +//} \ No newline at end of file diff --git a/tests/ServiceStack.Text.Tests/Net6TypeTests.cs b/tests/ServiceStack.Text.Tests/Net6TypeTests.cs new file mode 100644 index 000000000..c9187b43f --- /dev/null +++ b/tests/ServiceStack.Text.Tests/Net6TypeTests.cs @@ -0,0 +1,206 @@ +using System; +using NUnit.Framework; + +#if NET6_0 +namespace ServiceStack.Text.Tests; + +public class DateOnlyDto +{ + public DateOnly Date { get; set; } + + protected bool Equals(DateOnlyDto other) => Date.Equals(other.Date); + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((DateOnlyDto)obj); + } + public override int GetHashCode() => Date.GetHashCode(); +} + +public class TimeOnlyDto +{ + public TimeOnly Time { get; set; } + + protected bool Equals(TimeOnlyDto other) => Time.Equals(other.Time); + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((TimeOnlyDto)obj); + } + public override int GetHashCode() => Time.GetHashCode(); +} + +public class NullableDateOnlyDto +{ + public DateOnly? Date { get; set; } + + protected bool Equals(NullableDateOnlyDto other) => Date.Equals(other.Date); + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((NullableDateOnlyDto)obj); + } + public override int GetHashCode() => (Date ?? default).GetHashCode(); +} + +public class NullableTimeOnlyDto +{ + public TimeOnly? Time { get; set; } + + protected bool Equals(NullableTimeOnlyDto other) => Time.Equals(other.Time); + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((NullableTimeOnlyDto)obj); + } + public override int GetHashCode() => (Time ?? default).GetHashCode(); +} + +public class Net6TypeTests +{ + [Test] + public void Can_json_serialize_DateOnly() + { + var date = new DateOnly(2001, 1, 13); + var json = date.ToJson(); + Assert.That(json, Is.EqualTo("\"2001-01-13\"")); + + var fromJson = json.FromJson(); + Assert.That(fromJson, Is.EqualTo(date)); + + var dto = new DateOnlyDto { Date = date }; + json = dto.ToJson(); + Assert.That(json, Is.EqualTo("{\"Date\":\"2001-01-13\"}")); + var fromJsonDto = json.FromJson(); + Assert.That(fromJsonDto, Is.EqualTo(dto)); + + var nullableDto = new NullableDateOnlyDto { Date = date }; + json = nullableDto.ToJson(); + Assert.That(json, Is.EqualTo("{\"Date\":\"2001-01-13\"}")); + var fromJsonNullableDto = json.FromJson(); + Assert.That(fromJsonNullableDto, Is.EqualTo(nullableDto)); + } + + [Test] + public void Can_jsv_serialize_DateOnly() + { + var date = new DateOnly(2001, 1, 13); + var json = date.ToJsv(); + Assert.That(json, Is.EqualTo("2001-01-13")); + + var fromJson = json.FromJsv(); + Assert.That(fromJson, Is.EqualTo(date)); + + var dto = new DateOnlyDto { Date = date }; + json = dto.ToJsv(); + Assert.That(json, Is.EqualTo("{Date:2001-01-13}")); + var fromJsonDto = json.FromJsv(); + Assert.That(fromJsonDto, Is.EqualTo(dto)); + + var nullableDto = new NullableDateOnlyDto { Date = date }; + json = nullableDto.ToJsv(); + Assert.That(json, Is.EqualTo("{Date:2001-01-13}")); + var fromJsonNullableDto = json.FromJsv(); + Assert.That(fromJsonNullableDto, Is.EqualTo(nullableDto)); + } + + [Test] + public void Can_json_serialize_DateOnly_UnixTime() + { + using (JsConfig.With(new Config { DateHandler = DateHandler.UnixTime })) + { + var date = new DateOnly(2001, 1, 13); + var json = date.ToJson(); + Assert.That(json, Is.EqualTo("979344000")); + + var fromJson = json.FromJson(); + Assert.That(fromJson, Is.EqualTo(date)); + + var dto = new DateOnlyDto { Date = date }; + json = dto.ToJson(); + Assert.That(json, Is.EqualTo("{\"Date\":979344000}")); + + var nullableDto = new NullableDateOnlyDto { Date = date }; + json = nullableDto.ToJson(); + Assert.That(json, Is.EqualTo("{\"Date\":979344000}")); + } + } + + [Test] + public void Can_jsv_serialize_DateOnly_UnixTime() + { + using (JsConfig.With(new Config { DateHandler = DateHandler.UnixTime })) + { + var date = new DateOnly(2001, 1, 13); + var json = date.ToJsv(); + Assert.That(json, Is.EqualTo("979344000")); + + var fromJson = json.FromJsv(); + Assert.That(fromJson, Is.EqualTo(date)); + + var dto = new DateOnlyDto { Date = date }; + json = dto.ToJsv(); + Assert.That(json, Is.EqualTo("{Date:979344000}")); + + var nullableDto = new NullableDateOnlyDto { Date = date }; + json = nullableDto.ToJsv(); + Assert.That(json, Is.EqualTo("{Date:979344000}")); + } + } + + [Test] + public void Can_json_serialize_TimeOnly() + { + var time = new TimeOnly(13, 13, 13); + var json = time.ToJson(); + Assert.That(json, Is.EqualTo("\"PT13H13M13S\"")); + + var fromJson = json.FromJson(); + Assert.That(fromJson, Is.EqualTo(time)); + + var dto = new TimeOnlyDto { Time = time }; + json = dto.ToJson(); + Assert.That(json, Is.EqualTo("{\"Time\":\"PT13H13M13S\"}")); + var fromJsonDto = json.FromJson(); + Assert.That(fromJsonDto, Is.EqualTo(dto)); + + var nullableDto = new NullableTimeOnlyDto { Time = time }; + json = nullableDto.ToJson(); + Assert.That(json, Is.EqualTo("{\"Time\":\"PT13H13M13S\"}")); + var fromJsonNullableDto = json.FromJson(); + Assert.That(fromJsonNullableDto, Is.EqualTo(nullableDto)); + } + + [Test] + public void Can_jsv_serialize_TimeOnly() + { + var time = new TimeOnly(13, 13, 13); + var json = time.ToJsv(); + Assert.That(json, Is.EqualTo("PT13H13M13S")); + + var fromJson = json.FromJsv(); + Assert.That(fromJson, Is.EqualTo(time)); + + var dto = new TimeOnlyDto { Time = time }; + json = dto.ToJsv(); + Assert.That(json, Is.EqualTo("{Time:PT13H13M13S}")); + var fromJsonDto = json.FromJsv(); + Assert.That(fromJsonDto, Is.EqualTo(dto)); + + var nullableDto = new NullableTimeOnlyDto { Time = time }; + json = nullableDto.ToJsv(); + Assert.That(json, Is.EqualTo("{Time:PT13H13M13S}")); + var fromJsonNullableDto = json.FromJsv(); + Assert.That(fromJsonNullableDto, Is.EqualTo(nullableDto)); + } + +} +#endif \ No newline at end of file diff --git a/tests/ServiceStack.Text.Tests/QueryStringSerializerTests.cs b/tests/ServiceStack.Text.Tests/QueryStringSerializerTests.cs index 6f8c6054f..b16c7b767 100644 --- a/tests/ServiceStack.Text.Tests/QueryStringSerializerTests.cs +++ b/tests/ServiceStack.Text.Tests/QueryStringSerializerTests.cs @@ -4,7 +4,7 @@ using System.IO; using System.Web; using NUnit.Framework; -#if !NETCORE_SUPPORT +#if !NETCORE using ServiceStack.Host; using ServiceStack.Testing; #endif @@ -48,6 +48,17 @@ public void Can_Serialize_Unicode_Query_String() Is.EqualTo("A=%e5%b4%91%e2%a8%b9%e5%a0%a1%ea%81%80%e1%a2%96%e3%a4%b9%c3%ac%e3%ad%a1%ec%a4%aa%e9%8a%ac")); } + [Test] + public void Does_serialize_Poco_and_string_dictionary_with_encoded_data() + { + var msg = "Field with comma, to demo. "; + Assert.That(QueryStringSerializer.SerializeToString(new D { A = msg }), + Is.EqualTo("A=Field+with+comma,+to+demo.+")); + + Assert.That(QueryStringSerializer.SerializeToString(new D { A = msg }.ToStringDictionary()), + Is.EqualTo("A=Field+with+comma,+to+demo.+")); + } + class Empty { } [Test] @@ -128,9 +139,11 @@ public void Can_serialize_quoted_strings() Assert.That(QueryStringSerializer.SerializeToString(new B { Property = "\"quoted content, and with a comma\"" }), Is.EqualTo("Property=%22quoted+content,+and+with+a+comma%22")); } -#if !NETCORE_SUPPORT +#if !NETCORE private T StringToPoco(string str) { + var envKey = Environment.GetEnvironmentVariable("SERVICESTACK_LICENSE"); + if (!string.IsNullOrEmpty(envKey)) Licensing.RegisterLicense(envKey); using (new BasicAppHost().Init()) { NameValueCollection queryString = HttpUtility.ParseQueryString(str); @@ -164,7 +177,7 @@ public void Can_serialize_with_comma_in_property_in_list() Assert.That(QueryStringSerializer.SerializeToString(testPocos), Is.EqualTo("ListOfA={ListOfB:[{Property:%22Doe,+John%22,Property2:Doe,Property3:John}]}")); } -#if !NETCORE_SUPPORT +#if !NETCORE [Test] public void Can_deserialize_with_comma_in_property_in_list_from_QueryStringSerializer() { diff --git a/tests/ServiceStack.Text.Tests/QueryStringWriterTests.cs b/tests/ServiceStack.Text.Tests/QueryStringWriterTests.cs index 63233de26..d4e7abb0d 100644 --- a/tests/ServiceStack.Text.Tests/QueryStringWriterTests.cs +++ b/tests/ServiceStack.Text.Tests/QueryStringWriterTests.cs @@ -49,7 +49,7 @@ public void Can_write_dictionary_to_QueryString() queryString.Print(); Assert.That(queryString, - Is.EqualTo("Id=tt0110912&Title=Pulp+Fiction&Rating=8.9&Director=Quentin+Tarantino&ReleaseDate=1994-10-24&TagLine=Girls+like+me+don%27t+make+invitations+like+this+to+just+anyone%21&Genres=%22Crime,Drama,Thriller%22")); + Is.EqualTo("Id=tt0110912&Title=Pulp+Fiction&Rating=8.9&Director=Quentin+Tarantino&ReleaseDate=1994-10-24&TagLine=Girls+like+me+don%27t+make+invitations+like+this+to+just+anyone%21&Genres=Crime,Drama,Thriller")); } [Test] diff --git a/tests/ServiceStack.Text.Tests/ReflectionExtensionTests.cs b/tests/ServiceStack.Text.Tests/ReflectionExtensionTests.cs index b366a1ef8..ca8c7e780 100644 --- a/tests/ServiceStack.Text.Tests/ReflectionExtensionTests.cs +++ b/tests/ServiceStack.Text.Tests/ReflectionExtensionTests.cs @@ -4,6 +4,7 @@ using System.Text; using NUnit.Framework; using ServiceStack; +using ServiceStack.DataAnnotations; namespace ServiceStack.Text.Tests { @@ -130,9 +131,65 @@ public void Does_GetCollectionType() Assert.That(new[] { "" }.Select(x => new TestModel()).GetType().GetCollectionType(), Is.EqualTo(typeof(TestModel))); } + [EnumAsChar] + public enum CharEnum : int + { + Value1 = 'A', + Value2 = 'B', + Value3 = 'C', + Value4 = 'D' + } + + [Test] + public void Can_use_HasAttributeCached() + { + Assert.That(typeof(CharEnum).HasAttributeCached()); + Assert.That(typeof(CharEnum).HasAttribute()); + } + + [Test] + public void Can_use_lazy_HasAttributeOf_APIs() + { + var props = typeof(DeclarativeValidationTest).GetPublicProperties(); + Assert.That(props.Length, Is.EqualTo(3)); + + var locationsProp = props.FirstOrDefault(x => + x.PropertyType != typeof(string) && x.PropertyType.GetTypeWithGenericInterfaceOf(typeof(IEnumerable<>)) != null); + + Assert.That(locationsProp.Name, Is.EqualTo(nameof(DeclarativeValidationTest.Locations))); + + var genericDef = locationsProp.PropertyType.GetTypeWithGenericInterfaceOf(typeof(IEnumerable<>)); + var elementType = genericDef.GetGenericArguments()[0]; + var elementProps = elementType.GetPublicProperties(); + var hasAnyChildValidators = elementProps + .Any(elProp => elProp.HasAttributeOf()); + Assert.That(hasAnyChildValidators); + } } public class GenericType { } public class GenericType { } public class GenericType { } + + public class Location + { + public string Name { get; set; } + [ValidateMaximumLength(20)] + public string Value { get; set; } + } + + public class DeclarativeValidationTest : IReturn + { + [ValidateNotEmpty] + [ValidateMaximumLength(20)] + public string Site { get; set; } + public List Locations { get; set; } // **** here's the example + public List NoValidators { get; set; } + } + + public class NoValidators + { + public string Name { get; set; } + public string Value { get; set; } + } } diff --git a/tests/ServiceStack.Text.Tests/ReportedIssues.cs b/tests/ServiceStack.Text.Tests/ReportedIssues.cs index e0abb3a9a..586071bae 100644 --- a/tests/ServiceStack.Text.Tests/ReportedIssues.cs +++ b/tests/ServiceStack.Text.Tests/ReportedIssues.cs @@ -330,6 +330,68 @@ public void Deserialize_Correctly_When_Last_Item_Is_Null_in_Int_array_prop() var deserialized = TypeSerializer.DeserializeFromString(serialized); Assert.That(deserialized.IntArrayProp, Is.EqualTo(type.IntArrayProp)); } + + + [Test] + public void Null_Reference_Exception_On_Inherited_Field_With_No_Setter() + { + string testString = null; + InheritedFieldErrorTest parentClass = new InheritedFieldErrorTest(), parentClassResult = null; + InheritedFieldErrorTestChild childClass = new InheritedFieldErrorTestChild(), childClassResult = null; + Assert.DoesNotThrow(() => { testString = JsonSerializer.SerializeToString(parentClass); }); + Assert.IsNotNull(testString); + Assert.IsNotEmpty(testString); + Assert.DoesNotThrow(() => { parentClassResult = JsonSerializer.DeserializeFromString(testString); }); + Assert.IsNotNull(parentClassResult); + Assert.DoesNotThrow(() => { testString = JsonSerializer.SerializeToString(childClass); }); + Assert.IsNotNull(testString); + Assert.IsNotEmpty(testString); + Assert.DoesNotThrow(() => { childClassResult = JsonSerializer.DeserializeFromString(testString); }); + Assert.IsNotNull(childClassResult); + } + + [System.Runtime.Serialization.DataContract] + public class InheritedFieldErrorTest + { + [System.Runtime.Serialization.DataMember] + protected bool test = false; + } + + [System.Runtime.Serialization.DataContract] + public class InheritedFieldErrorTestChild : InheritedFieldErrorTest + { + } + + [Test] + public void Null_Reference_Exception_On_Inherited_Property_With_No_Setter() + { + string testString = null; + InheritedPropertyErrorTest parentClass = new InheritedPropertyErrorTest(), parentClassResult = null; + InheritedPropertyErrorTestChild childClass = new InheritedPropertyErrorTestChild(), childClassResult = null; + Assert.DoesNotThrow(() => { testString = JsonSerializer.SerializeToString(parentClass); }); + Assert.IsNotNull(testString); + Assert.IsNotEmpty(testString); + Assert.DoesNotThrow(() => { parentClassResult = JsonSerializer.DeserializeFromString(testString); }); + Assert.IsNotNull(parentClassResult); + Assert.DoesNotThrow(() => { testString = JsonSerializer.SerializeToString(childClass); }); + Assert.IsNotNull(testString); + Assert.IsNotEmpty(testString); + Assert.DoesNotThrow(() => { childClassResult = JsonSerializer.DeserializeFromString(testString); }); + Assert.IsNotNull(childClassResult); + } + + + [System.Runtime.Serialization.DataContract] + public class InheritedPropertyErrorTest + { + [System.Runtime.Serialization.DataMember] + protected bool Test { get; } + } + + [System.Runtime.Serialization.DataContract] + public class InheritedPropertyErrorTestChild : InheritedPropertyErrorTest + { + } } public class TestMappedList diff --git a/tests/ServiceStack.Text.Tests/RuntimeSerializtionTests.cs b/tests/ServiceStack.Text.Tests/RuntimeSerializationTests.cs similarity index 51% rename from tests/ServiceStack.Text.Tests/RuntimeSerializtionTests.cs rename to tests/ServiceStack.Text.Tests/RuntimeSerializationTests.cs index 57ad898d4..4b5dd3ff5 100644 --- a/tests/ServiceStack.Text.Tests/RuntimeSerializtionTests.cs +++ b/tests/ServiceStack.Text.Tests/RuntimeSerializationTests.cs @@ -5,6 +5,8 @@ using NUnit.Framework; using ServiceStack.Auth; using ServiceStack.Messaging; +using ServiceStack.Templates; +using ServiceStack.Text.Json; namespace ServiceStack.Text.Tests { @@ -15,6 +17,16 @@ public class RuntimeObject public class AType {} + public class JsonType + { + public int Int { get; set; } + public string String { get; set; } + public bool Bool { get; set; } + public string Null { get; set; } + public List List { get; set; } + public Dictionary Dictionary { get; set; } + } + [DataContract] public class DtoType { } @@ -28,7 +40,7 @@ public class MetaType : IMeta public class RequestDto : IReturn {} -#if NET45 +#if NETFX [Serializable] public class SerialiazableType { } #endif @@ -45,7 +57,7 @@ public class RuntimeObjects public object[] Objects { get; set; } } - public class RuntimeSerializtionTests + public class RuntimeSerializationTests { string CreateJson(Type type) => CreateJson(type.AssemblyQualifiedName); string CreateJson(string typeInfo) => "{\"Object\":{\"__type\":\"" + typeInfo + "\"}}"; @@ -215,5 +227,119 @@ public void Does_allow_Unknown_Type_in_RequestLogEntry() var fromJson = json.FromJson(); Assert.That(fromJson.RequestDto.GetType(), Is.EqualTo(typeof(AType))); } + + [Test] + public void Can_deserialize_object_with_unknown_JSON_into_object_type() + { + using (JsConfig.With(new Config { ExcludeTypeInfo = true })) + { + JS.Configure(); + + var dto = new RuntimeObject + { + Object = new JsonType + { + Int = 1, + String = "foo", + Bool = true, + List = new List { new JsonType{Int = 1, String = "foo", Bool = true} }, + Dictionary = new Dictionary {{ "key", new JsonType{Int = 1, String = "foo", Bool = true} }}, + } + }; + + var json = dto.ToJson(); + Assert.That(json, Is.EqualTo(@"{""Object"":{""Int"":1,""String"":""foo"",""Bool"":true," + + @"""List"":[{""Int"":1,""String"":""foo"",""Bool"":true}],""Dictionary"":{""key"":{""Int"":1,""String"":""foo"",""Bool"":true}}}}")); + + // into object + var fromJson = json.FromJson(); + var jsonObj = (Dictionary)fromJson; + var jsonType = (Dictionary)jsonObj["Object"]; + Assert.That(jsonType["Int"], Is.EqualTo(1)); + Assert.That(jsonType["String"], Is.EqualTo("foo")); + Assert.That(jsonType["Bool"], Is.EqualTo(true)); + var jsonList = (List)jsonType["List"]; + Assert.That(((Dictionary)jsonList[0])["Int"], Is.EqualTo(1)); + var jsonDict = (Dictionary)jsonType["Dictionary"]; + Assert.That(((Dictionary)jsonDict["key"])["Int"], Is.EqualTo(1)); + + // into DTO with Object property + var dtoFromJson = json.FromJson(); + jsonType = (Dictionary) dtoFromJson.Object; + Assert.That(jsonType["Int"], Is.EqualTo(1)); + Assert.That(jsonType["String"], Is.EqualTo("foo")); + Assert.That(jsonType["Bool"], Is.EqualTo(true)); + jsonList = (List)jsonType["List"]; + Assert.That(((Dictionary)jsonList[0])["Int"], Is.EqualTo(1)); + jsonDict = (Dictionary)jsonType["Dictionary"]; + Assert.That(((Dictionary)jsonDict["key"])["Int"], Is.EqualTo(1)); + + JS.UnConfigure(); + } + } + + [Test] + public void Can_serialize_JS_literal_into_DTO() + { + JS.Configure(); + + var js = @"{""Object"":{ Int:1,String:'foo',Bool:true,List:[{Int:1,String:`foo`,Bool:true}],Dictionary:{key:{Int:1,String:""foo"",Bool:true}}}}"; + + // into DTO with Object property + var dtoFromJson = js.FromJson(); + var jsonType = (Dictionary)dtoFromJson.Object; + Assert.That(jsonType["Int"], Is.EqualTo(1)); + Assert.That(jsonType["Int"], Is.EqualTo(1)); + Assert.That(jsonType["String"], Is.EqualTo("foo")); + Assert.That(jsonType["Bool"], Is.EqualTo(true)); + var jsonList = (List)jsonType["List"]; + Assert.That(((Dictionary)jsonList[0])["Int"], Is.EqualTo(1)); + var jsonDict = (Dictionary)jsonType["Dictionary"]; + Assert.That(((Dictionary)jsonDict["key"])["Int"], Is.EqualTo(1)); + + JS.UnConfigure(); + } + + [Test] + public void ServiceStack_AllowRuntimeType() + { + // Initialize static delegate to allow all types to be deserialized with the type attribute + JsConfig.AllowRuntimeType = _ => true; + JsConfig.TypeAttr = "$type"; + var example = new Example { Property = new MyProperty { Value = "Hello serializer" } }; + + var serialized = JsonSerializer.SerializeToString(example); + var deserialized = JsonSerializer.DeserializeFromString(serialized); + Assert.IsNotNull(deserialized?.Property); + + // Now the same process with a config scope that has a TypeAttr that differs from the global TypeAttr value + using var scope = JsConfig.With(new Config { TypeAttr = "_type" }); + serialized = JsonSerializer.SerializeToString(example); + deserialized = JsonSerializer.DeserializeFromString(serialized); + Assert.IsNotNull(deserialized?.Property); + + JsConfig.Reset(); + } + + private class Example + { + public IProperty Property { get; set; } + } + private interface IProperty + { + } + private class MyProperty : IProperty + { + public string Value { get; set; } + } + + [Test] + public void Can_deserialize_object_into_string_dictionary() + { + var json = "{\"__type\":\"System.Collections.Generic.KeyValuePair`2[[System.String, System.Private.CoreLib],[System.String, System.Private.CoreLib]], System.Private.CoreLib\",\"Key\":\"A\",\"Value\":\"B\"}"; + var dto = json.FromJson>(); + Assert.That(dto.Key, Is.EqualTo("A")); + Assert.That(dto.Value, Is.EqualTo("B")); + } } } \ No newline at end of file diff --git a/tests/ServiceStack.Text.Tests/ServiceStack.Text.Tests.csproj b/tests/ServiceStack.Text.Tests/ServiceStack.Text.Tests.csproj index 7520339f0..d57516471 100644 --- a/tests/ServiceStack.Text.Tests/ServiceStack.Text.Tests.csproj +++ b/tests/ServiceStack.Text.Tests/ServiceStack.Text.Tests.csproj @@ -1,6 +1,6 @@  - net45;netcoreapp2.0 + net472;net6.0 portable ServiceStack.Text.Tests Library @@ -13,24 +13,34 @@ false false false + latest + 1591 - Full + Full + - - - - - + + + + + + + + + + + + - - $(DefineConstants);NET45 + + $(DefineConstants);NETFX - + @@ -39,41 +49,15 @@ - - - - - + - - - Exe - $(DefineConstants);NUNITLITE + + $(DefineConstants);NETCORE;NETSTANDARD2_0 - - - - - $(DefineConstants);NETCORE_SUPPORT;NETCORE;NETSTANDARD2_0 - - - - + - - - - - - - - - diff --git a/tests/ServiceStack.Text.Tests/Shared/ModelWithFieldsOfDifferentTypes.cs b/tests/ServiceStack.Text.Tests/Shared/ModelWithFieldsOfDifferentTypes.cs index 49eb083a2..1b0448181 100644 --- a/tests/ServiceStack.Text.Tests/Shared/ModelWithFieldsOfDifferentTypes.cs +++ b/tests/ServiceStack.Text.Tests/Shared/ModelWithFieldsOfDifferentTypes.cs @@ -165,24 +165,8 @@ public static void AssertIsEqual(ModelWithFieldsOfDifferentTypes actual, ModelWi Assert.That(actual.Guid, Is.EqualTo(expected.Guid)); Assert.That(actual.LongId, Is.EqualTo(expected.LongId)); Assert.That(actual.Bool, Is.EqualTo(expected.Bool)); - try - { - Assert.That(actual.DateTime, Is.EqualTo(expected.DateTime)); - } - catch (Exception ex) - { - Log.Error("Trouble with DateTime precisions, trying Assert again with rounding to seconds", ex); - Assert.That(actual.DateTime.RoundToSecond(), Is.EqualTo(expected.DateTime.RoundToSecond())); - } - try - { - Assert.That(actual.Double, Is.EqualTo(expected.Double)); - } - catch (Exception ex) - { - Log.Error("Trouble with double precisions, trying Assert again with rounding to 10 decimals", ex); - Assert.That(Math.Round(actual.Double, 10), Is.EqualTo(Math.Round(actual.Double, 10))); - } + Assert.That(actual.DateTime, Is.EqualTo(expected.DateTime).Within(TimeSpan.FromSeconds(1))); + Assert.That(actual.Double, Is.EqualTo(expected.Double).Within(0.1)); } } } \ No newline at end of file diff --git a/tests/ServiceStack.Text.Tests/Shared/ModelWithFieldsOfNullableTypes.cs b/tests/ServiceStack.Text.Tests/Shared/ModelWithFieldsOfNullableTypes.cs index 24a2acf4b..b3947e8ba 100644 --- a/tests/ServiceStack.Text.Tests/Shared/ModelWithFieldsOfNullableTypes.cs +++ b/tests/ServiceStack.Text.Tests/Shared/ModelWithFieldsOfNullableTypes.cs @@ -77,36 +77,20 @@ public static void AssertIsEqual(ModelWithFieldsOfNullableTypes actual, ModelWit Assert.That(actual.NBool, Is.EqualTo(expected.NBool)); Assert.That(actual.NTimeSpan, Is.EqualTo(expected.NTimeSpan)); - try + if (actual.NDateTime.HasValue || expected.NDateTime.HasValue) { - Assert.That(actual.NDateTime, Is.EqualTo(expected.NDateTime)); - } - catch (Exception ex) - { - Log.Error("Trouble with DateTime precisions, trying Assert again with rounding to seconds", ex); - Assert.That(actual.NDateTime.Value.ToUniversalTime().RoundToSecond(), Is.EqualTo(expected.NDateTime.Value.ToUniversalTime().RoundToSecond())); + Assert.That(actual.NDateTime, Is.EqualTo(expected.NDateTime).Within(TimeSpan.FromSeconds(1))); } - try - { - Assert.That(actual.NFloat, Is.EqualTo(expected.NFloat)); - } - catch (Exception ex) + if (actual.NFloat.HasValue || expected.NFloat.HasValue) { - Log.Error("Trouble with float precisions, trying Assert again with rounding to 10 decimals", ex); - Assert.That(Math.Round(actual.NFloat.Value, 10), Is.EqualTo(Math.Round(actual.NFloat.Value, 10))); + Assert.That(actual.NFloat.Value, Is.EqualTo(expected.NFloat.Value).Within(0.1)); } - try + if (actual.NDouble.HasValue || expected.NDouble.HasValue) { - Assert.That(actual.NDouble, Is.EqualTo(expected.NDouble)); + Assert.That(actual.NDouble.Value, Is.EqualTo(expected.NDouble.Value).Within(0.1)); } - catch (Exception ex) - { - Log.Error("Trouble with double precisions, trying Assert again with rounding to 10 decimals", ex); - Assert.That(Math.Round(actual.NDouble.Value, 10), Is.EqualTo(Math.Round(actual.NDouble.Value, 10))); - } - } } } \ No newline at end of file diff --git a/tests/ServiceStack.Text.Tests/SpanMemoryTests.cs b/tests/ServiceStack.Text.Tests/SpanMemoryTests.cs new file mode 100644 index 000000000..298c3f0bc --- /dev/null +++ b/tests/ServiceStack.Text.Tests/SpanMemoryTests.cs @@ -0,0 +1,142 @@ +using System; +using System.Threading.Tasks; +using NUnit.Framework; +using ServiceStack.Text.Pools; + +namespace ServiceStack.Text.Tests +{ + public class SpanMemoryTests + { + [Test] + public void Can_use_Memory() + { + ReadOnlyMemory a = "foo bar".AsMemory(); + + var foo = a.Slice(0, 3).ToArray(); + + Assert.That(foo, Is.EqualTo("foo".ToCharArray())); + } + + [Test] + public void Can_not_detect_null_empty_string_memory() + { + var n = ((string) null).AsMemory(); + var e = "".AsMemory(); + + Assert.That(!n.Equals(e)); //null + "" memory are not equal + + Assert.That(n.Equals(((string) null).AsMemory())); + Assert.That(e.Equals("".AsMemory())); + + Assert.That(n.Equals(default(ReadOnlyMemory))); + Assert.That(!e.Equals(default(ReadOnlyMemory))); + + Assert.That(n.IsEmpty); + Assert.That(e.IsEmpty); + } + + [Test] + public void Can_read_lines_with_TryReadLine_using_Memory() + { + var str = "A\nB\r\nC\rD\r\n"; + var expected = new[] {"A", "B", "C", "D"}; + + var i = 0; + var buf = str.AsMemory(); + var pos = 0; + while (buf.TryReadLine(out ReadOnlyMemory line, ref pos)) + { + Assert.That(line.ToString(), Is.EqualTo(expected[i++])); + } + + Assert.That(pos, Is.EqualTo(buf.Length)); + Assert.That(i, Is.EqualTo(expected.Length)); + } + + [Test] + public void Can_read_parts_with_TryReadPart_using_Memory() + { + var str = "A.BB.CCC.DD DD"; + var expected = new[] {"A", "BB", "CCC", "DD DD"}; + + var i = 0; + var buf = str.AsMemory(); + var pos = 0; + while (buf.TryReadPart(".".AsMemory(), out ReadOnlyMemory part, ref pos)) + { + Assert.That(part.ToString(), Is.EqualTo(expected[i++])); + } + + Assert.That(pos, Is.EqualTo(buf.Length)); + Assert.That(i, Is.EqualTo(expected.Length)); + + str = "A||BB||CCC||DD DD"; + + i = 0; + buf = str.AsMemory(); + pos = 0; + while (buf.TryReadPart("||".AsMemory(), out ReadOnlyMemory part, ref pos)) + { + Assert.That(part.ToString(), Is.EqualTo(expected[i++])); + } + + Assert.That(pos, Is.EqualTo(buf.Length)); + Assert.That(i, Is.EqualTo(expected.Length)); + } + + [Test] + public void Can_SplitOnFirst_using_Memory() + { + "a:b:c".AsMemory().SplitOnFirst(':', out var first, out var last); + Assert.That(first.ToString(), Is.EqualTo("a")); + Assert.That(last.ToString(), Is.EqualTo("b:c")); + + "a::b::c".AsMemory().SplitOnFirst("::".AsMemory(), out first, out last); + Assert.That(first.ToString(), Is.EqualTo("a")); + Assert.That(last.ToString(), Is.EqualTo("b::c")); + } + + [Test] + public void Can_SplitOnLast_using_Span() + { + "a:b:c".AsMemory().SplitOnLast(':', out var first, out var last); + Assert.That(first.ToString(), Is.EqualTo("a:b")); + Assert.That(last.ToString(), Is.EqualTo("c")); + + "a::b::c".AsMemory().SplitOnLast("::".AsMemory(), out first, out last); + Assert.That(first.ToString(), Is.EqualTo("a::b")); + Assert.That(last.ToString(), Is.EqualTo("c")); + } + + [Test] + public void Can_ToUtf8_and_FromUtf8_using_Memory() + { + foreach (var test in Utf8Case.Source) + { + ReadOnlyMemory bytes = test.expectedString.AsMemory().ToUtf8(); + Assert.That(bytes.Length, Is.EqualTo(test.count)); + Assert.That(bytes.ToArray(), Is.EquivalentTo(test.expectedBytes)); + + ReadOnlyMemory chars = bytes.FromUtf8(); + Assert.That(chars.Length, Is.EqualTo(test.expectedString.Length) + .Or.EqualTo(test.expectedString.WithoutBom().Length)); + Assert.That(chars.ToString(), Is.EqualTo(test.expectedString) + .Or.EqualTo(test.expectedString.WithoutBom())); + } + } + + [Test] + public async Task Can_deserialize_from_MemoryStream_using_Memory() + { + var from = new Person { Id = 1, Name = "FooBA\u0400R" }; + var json = from.ToJson(); + + var ms = MemoryStreamFactory.GetStream(json.ToUtf8Bytes()); + + var to = (Person)await MemoryProvider.Instance.DeserializeAsync(ms, typeof(Person), JsonSerializer.DeserializeFromSpan); + + Assert.That(to, Is.EqualTo(from)); + } + + } +} \ No newline at end of file diff --git a/tests/ServiceStack.Text.Tests/SpanTests.cs b/tests/ServiceStack.Text.Tests/SpanTests.cs new file mode 100644 index 000000000..a5393552e --- /dev/null +++ b/tests/ServiceStack.Text.Tests/SpanTests.cs @@ -0,0 +1,269 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using NUnit.Framework; +using ServiceStack.Text.Pools; + +namespace ServiceStack.Text.Tests +{ + public class SpanTests + { + [Test] + public void Can_use_Span() + { + ReadOnlySpan a = "foo bar".AsSpan(); + + var foo = a.Slice(0,3).ToArray(); + + Assert.That(foo, Is.EqualTo("foo".ToCharArray())); + } + + [Test] + public void Can_not_detect_null_empty_string_spans() + { + var n = ((string)null).AsSpan(); + var e = "".AsSpan(); + + Assert.That(n.SequenceEqual(e)); //null + "" spans are considered equal + } + + [Test] + public void Can_read_lines_with_TryReadLine_using_Span() + { + var str = "A\nB\r\nC\rD\r\n"; + var expected = new[] {"A", "B", "C", "D"}; + + var i = 0; + var buf = str.AsSpan(); + var pos = 0; + while (buf.TryReadLine(out var line, ref pos)) + { + Assert.That(line.ToString(), Is.EqualTo(expected[i++])); + } + + Assert.That(pos, Is.EqualTo(buf.Length)); + Assert.That(i, Is.EqualTo(expected.Length)); + } + + [Test] + public void Can_read_parts_with_TryReadPart_using_Span() + { + var str = "A.BB.CCC.DD DD"; + var expected = new[] { "A", "BB", "CCC", "DD DD" }; + + var i = 0; + var buf = str.AsSpan(); + var pos = 0; + while (buf.TryReadPart(".", out ReadOnlySpan part, ref pos)) + { + Assert.That(part.ToString(), Is.EqualTo(expected[i++])); + } + + Assert.That(pos, Is.EqualTo(buf.Length)); + Assert.That(i, Is.EqualTo(expected.Length)); + + str = "A||BB||CCC||DD DD"; + + i = 0; + buf = str.AsSpan(); + pos = 0; + while (buf.TryReadPart("||", out ReadOnlySpan part, ref pos)) + { + Assert.That(part.ToString(), Is.EqualTo(expected[i++])); + } + + Assert.That(pos, Is.EqualTo(buf.Length)); + Assert.That(i, Is.EqualTo(expected.Length)); + } + + [Test] + public void Can_SplitOnFirst_using_Span() + { + "a:b:c".AsSpan().SplitOnFirst(':', out var first, out var last); + Assert.That(first.ToString(), Is.EqualTo("a")); + Assert.That(last.ToString(), Is.EqualTo("b:c")); + + "a::b::c".AsSpan().SplitOnFirst("::", out first, out last); + Assert.That(first.ToString(), Is.EqualTo("a")); + Assert.That(last.ToString(), Is.EqualTo("b::c")); + } + + [Test] + public void Can_SplitOnLast_using_Span() + { + "a:b:c".AsSpan().SplitOnLast(':', out var first, out var last); + Assert.That(first.ToString(), Is.EqualTo("a:b")); + Assert.That(last.ToString(), Is.EqualTo("c")); + + "a::b::c".AsSpan().SplitOnLast("::", out first, out last); + Assert.That(first.ToString(), Is.EqualTo("a::b")); + Assert.That(last.ToString(), Is.EqualTo("c")); + } + + [Test] + public void Can_ToUtf8_and_FromUtf8_using_Span() + { + foreach (var test in Utf8Case.Source) + { + ReadOnlyMemory bytes = test.expectedString.AsSpan().ToUtf8(); + Assert.That(bytes.Length, Is.EqualTo(test.count)); + Assert.That(bytes.ToArray(), Is.EquivalentTo(test.expectedBytes)); + + ReadOnlyMemory chars = bytes.FromUtf8(); + Assert.That(chars.Length, Is.EqualTo(test.expectedString.Length) + .Or.EqualTo(test.expectedString.WithoutBom().Length)); + Assert.That(chars.ToString(), Is.EqualTo(test.expectedString) + .Or.EqualTo(test.expectedString.WithoutBom())); + } + } + + [Test] + public void Can_ToUtf8_and_FromUtf8_in_place_using_Span() + { + foreach (var test in Utf8Case.Source) + { + var chars = test.expectedString.AsSpan(); + Memory buffer = BufferPool.GetBuffer(MemoryProvider.Instance.GetUtf8ByteCount(chars)); + var bytesWritten = MemoryProvider.Instance.ToUtf8(chars, buffer.Span); + var bytes = buffer.Slice(0, bytesWritten); + + Assert.That(bytes.Length, Is.EqualTo(test.count)); + Assert.That(bytes.ToArray(), Is.EquivalentTo(test.expectedBytes)); + + Memory charBuff = CharPool.GetBuffer(MemoryProvider.Instance.GetUtf8CharCount(bytes.Span)); + var charsWritten = MemoryProvider.Instance.FromUtf8(bytes.Span, charBuff.Span); + chars = charBuff.Slice(0, charsWritten).Span; + + Assert.That(chars.Length, Is.EqualTo(test.expectedString.Length) + .Or.EqualTo(test.expectedString.WithoutBom().Length)); + Assert.That(chars.ToString(), Is.EqualTo(test.expectedString) + .Or.EqualTo(test.expectedString.WithoutBom())); + } + } + + [Test] + public async Task Can_deserialize_from_MemoryStream_using_Memory() + { + var from = new Person { Id = 1, Name = "FooBA\u0400R" }; + var json = from.ToJson(); + + var ms = MemoryStreamFactory.GetStream(json.ToUtf8Bytes()); + + var to = (Person)await MemoryProvider.Instance.DeserializeAsync(ms, typeof(Person), JsonSerializer.DeserializeFromSpan); + + Assert.That(to, Is.EqualTo(from)); + } + + [Test] + public void Can_deserialize_JSON_with_UTF8_BOM() + { + var from = new Person { Id = 1, Name = "Foo" }; + var json = from.ToJson(); + var jsonBytes = json.ToUtf8Bytes(); + + var bytes = new List(new byte[] { 0xEF, 0xBB, 0xBF }); + bytes.AddRange(jsonBytes); + + var mergedBytes = bytes.ToArray(); + + var jsonWithBOM = mergedBytes.FromUtf8Bytes(); + + var fromJsonWithBOM = jsonWithBOM.FromJson(); + + Assert.That(fromJsonWithBOM, Is.EqualTo(from)); + } + } + + public class Utf8Case + { + //https://github.com/dotnet/corefx/blob/master/src/System.Text.Encoding/tests/UTF8Encoding/UTF8EncodingDecode.cs + public static readonly Utf8Case[] Source = { + new Utf8Case(new byte[] {70, 111, 111, 66, 65, 208, 128, 82}, 0, 8, "FooBA\u0400R"), + new Utf8Case(new byte[] { 195, 128, 110, 105, 109, 97, 204, 128, 108 }, 0, 9, "\u00C0nima\u0300l"), + new Utf8Case(new byte[] { 84, 101, 115, 116, 240, 144, 181, 181, 84, 101, 115, 116 }, 0, 12, "Test\uD803\uDD75Test"), + new Utf8Case(new byte[] { 0, 84, 101, 10, 115, 116, 0, 9, 0, 84, 15, 101, 115, 116, 0 }, 0, 15, "\0Te\nst\0\t\0T\u000Fest\0"), + new Utf8Case(new byte[] { 240, 144, 181, 181, 240, 144, 181, 181, 240, 144, 181, 181 }, 0, 12, "\uD803\uDD75\uD803\uDD75\uD803\uDD75"), + new Utf8Case(new byte[] { 196, 176 }, 0, 2, "\u0130"), + new Utf8Case(new byte[] { 0x61, 0xCC, 0x8A }, 0, 3, "\u0061\u030A"), + new Utf8Case(new byte[] { 0xC2, 0xA4, 0xC3, 0x90, 0x61, 0x52, 0x7C, 0x7B, 0x41, 0x6E, 0x47, 0x65, 0xC2, 0xA3, 0xC2, 0xA4 }, 0, 16, "\u00A4\u00D0aR|{AnGe\u00A3\u00A4"), + new Utf8Case(new byte[] { 0x00, 0x7F }, 0, 2, "\u0000\u007F"), + new Utf8Case(new byte[] { 0x00, 0x7F, 0x00, 0x7F, 0x00, 0x7F, 0x00, 0x7F, 0x00, 0x7F, 0x00, 0x7F, 0x00, 0x7F }, 0, 14, "\u0000\u007F\u0000\u007F\u0000\u007F\u0000\u007F\u0000\u007F\u0000\u007F\u0000\u007F"), + new Utf8Case(new byte[] { 0xC2, 0x80, 0xDF, 0xBF }, 0, 4, "\u0080\u07FF"), + + // Long ASCII strings + new Utf8Case(new byte[] { 84, 101, 115, 116, 83, 116, 114, 105, 110, 103 }, 0, 10, "TestString"), + new Utf8Case(new byte[] { 84, 101, 115, 116, 84, 101, 115, 116 }, 0, 8, "TestTest"), + + // Control codes + new Utf8Case(new byte[] { 0x1F, 0x10, 0x00, 0x09 }, 0, 4, "\u001F\u0010\u0000\u0009"), + new Utf8Case(new byte[] { 0x1F, 0x00, 0x10, 0x09 }, 0, 4, "\u001F\u0000\u0010\u0009"), + new Utf8Case(new byte[] { 0x00, 0x1F, 0x10, 0x09 }, 0, 4, "\u0000\u001F\u0010\u0009"), + + // BOM + new Utf8Case(new byte[] { 0xEF, 0xBB, 0xBF, 0x41 }, 0, 4, "\uFEFF\u0041"), + + // U+FDD0 - U+FDEF + new Utf8Case(new byte[] { 0xEF, 0xB7, 0x90, 0xEF, 0xB7, 0xAF }, 0, 6, "\uFDD0\uFDEF"), + + // 2 byte encoding + new Utf8Case(new byte[] { 0xC3, 0xA1 }, 0, 2, "\u00E1"), + new Utf8Case(new byte[] { 0xC3, 0x85 }, 0, 2, "\u00C5"), + + // 3 byte encoding + new Utf8Case(new byte[] { 0xE8, 0x80, 0x80 }, 0, 3, "\u8000"), + new Utf8Case(new byte[] { 0xE2, 0x84, 0xAB }, 0, 3, "\u212B"), + + // Surrogate pairs + new Utf8Case(new byte[] { 240, 144, 128, 128 }, 0, 4, "\uD800\uDC00"), + new Utf8Case(new byte[] { 97, 240, 144, 128, 128, 98 }, 0, 6, "a\uD800\uDC00b"), + + // High BMP non-chars + new Utf8Case(new byte[] { 239, 191, 189 }, 0, 3, "\uFFFD"), + + // Empty strings + new Utf8Case(new byte[0], 0, 0, string.Empty), + }; + + public byte[] expectedBytes; + public int index; + public int count; + public string expectedString; + + public Utf8Case(byte[] expectedBytes, int index, int count, string expectedString) + { + this.expectedBytes = expectedBytes; + this.index = index; + this.count = count; + this.expectedString = expectedString; + } + } + + public class Person + { + public int Id { get; set; } + public string Name { get; set; } + + protected bool Equals(Person other) + { + return Id == other.Id && string.Equals(Name, other.Name); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((Person) obj); + } + + public override int GetHashCode() + { + unchecked + { + return (Id * 397) ^ (Name != null ? Name.GetHashCode() : 0); + } + } + } + +} \ No newline at end of file diff --git a/tests/ServiceStack.Text.Tests/SpecialTypesTests.cs b/tests/ServiceStack.Text.Tests/SpecialTypesTests.cs index 0a7a06a67..06c083d4b 100644 --- a/tests/ServiceStack.Text.Tests/SpecialTypesTests.cs +++ b/tests/ServiceStack.Text.Tests/SpecialTypesTests.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.IO; +using System.Threading.Tasks; using NUnit.Framework; using ServiceStack.Web; @@ -122,5 +123,13 @@ public void Does_not_serialize_Streams() var dto = new RawRequest { Id = 1, RequestStream = new MemoryStream() }; Serialize(dto, includeXml: false); } + + [Test] + public void Serializing_Tasks_throws_NotSupportedException() + { + Assert.Throws(() => Task.FromResult(1).ToJson()); + Assert.Throws(() => Task.FromResult(1).ToJsv()); + } + } } \ No newline at end of file diff --git a/tests/ServiceStack.Text.Tests/StreamTests.cs b/tests/ServiceStack.Text.Tests/StreamTests.cs index fff1acc82..006e0c138 100644 --- a/tests/ServiceStack.Text.Tests/StreamTests.cs +++ b/tests/ServiceStack.Text.Tests/StreamTests.cs @@ -22,7 +22,7 @@ public void Does_escape_string_when_serializing_to_TextWriter() using (var sr = new StreamReader(ms)) { ms.Position = 0; - var ssJson = sr.ReadToEnd(); + var ssJson = ms.ReadToEnd(); Assert.That(ssJson, Is.EqualTo(json)); ms.Position = 0; @@ -42,7 +42,7 @@ public void Does_escape_string_when_serializing_to_Stream() using (var ms = new MemoryStream()) { JsonSerializer.SerializeToStream(expected, ms); - var ssJson = ms.ToArray().FromUtf8Bytes(); + var ssJson = ms.ReadToEnd(); Assert.That(ssJson, Is.EqualTo(json)); @@ -52,5 +52,15 @@ public void Does_escape_string_when_serializing_to_Stream() Assert.That(ssString, Is.EqualTo(expected)); } } + + [Test] + public void Can_create_MD5_hashes_from_Stream() + { + var md5Hash = "35f184b0e35d7f5629e79cb4bc802893"; + var utf8Bytes = nameof(Can_create_MD5_hashes_from_Stream).ToUtf8Bytes(); + var ms = new MemoryStream(utf8Bytes); + Assert.That(utf8Bytes.ToMd5Hash(), Is.EqualTo(md5Hash)); + Assert.That(ms.ToMd5Bytes().ToHex(), Is.EqualTo(md5Hash)); + } } } \ No newline at end of file diff --git a/tests/ServiceStack.Text.Tests/StringConverterUtilsTests.cs b/tests/ServiceStack.Text.Tests/StringConverterUtilsTests.cs index 0432a86c6..593cf79d8 100644 --- a/tests/ServiceStack.Text.Tests/StringConverterUtilsTests.cs +++ b/tests/ServiceStack.Text.Tests/StringConverterUtilsTests.cs @@ -28,7 +28,7 @@ public static StringEnumerable ParseJsv(string value) { return new StringEnumerable { - Items = value.To>() + Items = value.ConvertTo>() }; } @@ -38,7 +38,7 @@ public static StringEnumerable ParseJsv(string value) public void Create_super_list_type_of_int_from_string() { var textValue = "1,2,3"; - var convertedValue = textValue.Split(',').ToList().ConvertAll(x => Convert.ToInt32(x)); + var convertedValue = textValue.Split(',').ToList().ConvertAll(Convert.ToInt32); var result = TypeSerializer.DeserializeFromString(textValue); Assert.That(result, Is.EquivalentTo(convertedValue)); } diff --git a/tests/ServiceStack.Text.Tests/StringExtensionsTests.cs b/tests/ServiceStack.Text.Tests/StringExtensionsTests.cs index 6981e2227..b6fce3311 100644 --- a/tests/ServiceStack.Text.Tests/StringExtensionsTests.cs +++ b/tests/ServiceStack.Text.Tests/StringExtensionsTests.cs @@ -1,6 +1,7 @@ using System; using System.IO; using System.Linq; +using System.Threading.Tasks; using NUnit.Framework; namespace ServiceStack.Text.Tests @@ -237,6 +238,7 @@ public void Can_ToCamelCase_String() Assert.That("lllUlllUlll".ToCamelCase(), Is.EqualTo("lllUlllUlll")); Assert.That("".ToCamelCase(), Is.EqualTo("")); Assert.That(((string)null).ToCamelCase(), Is.EqualTo((string)null)); + Assert.That("__type".ToCamelCase(), Is.EqualTo("__type")); } [Test] @@ -300,7 +302,7 @@ public void Can_ParseKeyValueText() Assert.That("a".ParseKeyValueText().Count, Is.EqualTo(1)); Assert.That("a".ParseKeyValueText()["a"], Is.Null); Assert.That("a ".ParseKeyValueText().Count, Is.EqualTo(1)); - Assert.That("a ".ParseKeyValueText()["a"], Is.EqualTo("")); + Assert.That("a ".ParseKeyValueText()["a"], Is.Null); Assert.That("a b".ParseKeyValueText()["a"], Is.EqualTo("b")); Assert.That("a b c".ParseKeyValueText()["a"], Is.EqualTo("b c")); Assert.That("a b c ".ParseKeyValueText()["a"], Is.EqualTo("b c")); @@ -343,6 +345,68 @@ public void Can_convert_ToPascalCase() Assert.That("aa_bb".ToPascalCase(), Is.EqualTo("AaBb")); Assert.That("Aa_Bb".ToPascalCase(), Is.EqualTo("AaBb")); Assert.That("AA_BB".ToPascalCase(), Is.EqualTo("AaBb")); + Assert.That("__type".ToPascalCase(), Is.EqualTo("Type")); + } + + [Test] + public void Does_ContainsAny_Return_CaseInsensitive_Matches() + { + var testMatches = new string[] { "abc" }; + var input = "ABC"; + + Assert.That(input.ContainsAny(testMatches, StringComparison.OrdinalIgnoreCase)); + } + + [Test] + public void Does_ReplaceAll_from_Start() + { + Assert.That("/images".Replace("/",""), Is.EqualTo("images")); + } + + [Test] + public void Does_ReplaceAll_Avoid_Infinite_Loops() + { + var input = "image"; + var output = input; + + output = input.Replace("image", "images"); + + Assert.That(output, Is.EqualTo("images")); + } + + [TestCase("", ExpectedResult = "/")] + [TestCase("/", ExpectedResult = "/")] + [TestCase("?p1=asdf", ExpectedResult = "/?p1=asdf")] + [TestCase("/page", ExpectedResult = "/page/")] + [TestCase("/page/", ExpectedResult = "/page/")] + [TestCase("/page?p1=asdf", ExpectedResult = "/page/?p1=asdf")] + [TestCase("/page?p1=asdf&p2=asdf", ExpectedResult = "/page/?p1=asdf&p2=asdf")] + [TestCase("/page/?p1=asdf&p2=asdf", ExpectedResult = "/page/?p1=asdf&p2=asdf")] + + [TestCase("#here", ExpectedResult = "/#here")] + [TestCase("?p1=asdf#here", ExpectedResult = "/?p1=asdf#here")] + [TestCase("/page#here", ExpectedResult = "/page/#here")] + [TestCase("/page/#here", ExpectedResult = "/page/#here")] + [TestCase("/page?p1=asdf#here", ExpectedResult = "/page/?p1=asdf#here")] + [TestCase("/page?p1=asdf&p2=asdf#here", ExpectedResult = "/page/?p1=asdf&p2=asdf#here")] + [TestCase("/page/?p1=asdf&p2=asdf#here", ExpectedResult = "/page/?p1=asdf&p2=asdf#here")] + + [TestCase("domain.com", ExpectedResult = "domain.com/")] + [TestCase("domain.com/", ExpectedResult = "domain.com/")] + [TestCase("domain.com?p1=asdf", ExpectedResult = "domain.com/?p1=asdf")] + [TestCase("domain.com/page?p1=asdf", ExpectedResult = "domain.com/page/?p1=asdf")] + [TestCase("domain.com/page?p1=asdf&p2=asdf", ExpectedResult = "domain.com/page/?p1=asdf&p2=asdf")] + [TestCase("domain.com/page/?p1=asdf&p2=asdf", ExpectedResult = "domain.com/page/?p1=asdf&p2=asdf")] + + [TestCase("domain.com#here", ExpectedResult = "domain.com/#here")] + [TestCase("domain.com/#here", ExpectedResult = "domain.com/#here")] + [TestCase("domain.com?p1=asdf#here", ExpectedResult = "domain.com/?p1=asdf#here")] + [TestCase("domain.com/page?p1=asdf#here", ExpectedResult = "domain.com/page/?p1=asdf#here")] + [TestCase("domain.com/page?p1=asdf&p2=asdf#here", ExpectedResult = "domain.com/page/?p1=asdf&p2=asdf#here")] + [TestCase("domain.com/page/?p1=asdf&p2=asdf#here", ExpectedResult = "domain.com/page/?p1=asdf&p2=asdf#here")] + public string Does_UrlWithTrailingSlash(string url) + { + return url.UrlWithTrailingSlash(); } } } diff --git a/tests/ServiceStack.Text.Tests/StringSegmentExtensionsTests.cs b/tests/ServiceStack.Text.Tests/StringSegmentExtensionsTests.cs index 1d9e1d570..ef0341ab5 100644 --- a/tests/ServiceStack.Text.Tests/StringSegmentExtensionsTests.cs +++ b/tests/ServiceStack.Text.Tests/StringSegmentExtensionsTests.cs @@ -6,98 +6,122 @@ namespace ServiceStack.Text.Tests { [TestFixture] - public class StringSegmentExtensionsTests + public class StringSpanExtensionsTests { [Test] public void Can_SplitOnFirst_char_needle() { - var parts = "user:pass@w:rd".ToStringSegment().SplitOnFirst(':'); - Assert.That(parts[0], Is.EqualTo("user")); - Assert.That(parts[1], Is.EqualTo("pass@w:rd")); + "user:pass@w:rd".AsSpan().SplitOnFirst(':', out var first, out var last); + Assert.That(first.EqualTo("user")); + Assert.That(last.EqualTo("pass@w:rd")); } [Test] public void Can_LeftPart_and_LeftPart_char_needle() { - var str = "user:pass@w:rd".ToStringSegment(); - Assert.That(str.LeftPart(':'), Is.EqualTo("user")); - Assert.That(str.SplitOnFirst(':')[0], Is.EqualTo("user")); - Assert.That(str.RightPart(':'), Is.EqualTo("pass@w:rd")); - Assert.That(str.SplitOnFirst(':').Last(), Is.EqualTo("pass@w:rd")); - - Assert.That(str.LeftPart('|'), Is.EqualTo("user:pass@w:rd")); - Assert.That(str.SplitOnFirst('|')[0], Is.EqualTo("user:pass@w:rd")); - Assert.That(str.RightPart('|'), Is.EqualTo("user:pass@w:rd")); - Assert.That(str.SplitOnFirst('|').Last(), Is.EqualTo("user:pass@w:rd")); + var str = "user:pass@w:rd".AsSpan(); + Assert.That(str.LeftPart(':').EqualTo("user")); + + str.SplitOnFirst(':', out var first, out var last); + Assert.That(first.EqualTo("user")); + Assert.That(str.RightPart(':').EqualTo("pass@w:rd")); + + str.SplitOnFirst(':', out first, out last); + Assert.That(last.EqualTo("pass@w:rd")); + + Assert.That(str.LeftPart('|').EqualTo("user:pass@w:rd")); + + str.SplitOnFirst('|', out first, out last); + Assert.That(first.EqualTo("user:pass@w:rd")); + + Assert.That(str.RightPart('|').EqualTo("user:pass@w:rd")); + Assert.That(last.IsEmpty); } [Test] public void Can_SplitOnFirst_string_needle() { - var parts = "user:pass@w:rd".ToStringSegment().SplitOnFirst(":"); - Assert.That(parts[0], Is.EqualTo("user")); - Assert.That(parts[1], Is.EqualTo("pass@w:rd")); + "user:pass@w:rd".AsSpan().SplitOnFirst(":", out var first, out var last); + Assert.That(first.EqualTo("user")); + Assert.That(last.EqualTo("pass@w:rd")); } [Test] public void Can_LeftPart_and_RightPart_string_needle() { - var str = "user::pass@w:rd".ToStringSegment(); - Assert.That(str.LeftPart("::"), Is.EqualTo("user")); - Assert.That(str.SplitOnFirst("::")[0], Is.EqualTo("user")); - Assert.That(str.RightPart("::"), Is.EqualTo("pass@w:rd")); - Assert.That(str.SplitOnFirst("::").Last(), Is.EqualTo("pass@w:rd")); - - Assert.That(str.LeftPart("||"), Is.EqualTo("user::pass@w:rd")); - Assert.That(str.SplitOnFirst("||")[0], Is.EqualTo("user::pass@w:rd")); - Assert.That(str.RightPart("||"), Is.EqualTo("user::pass@w:rd")); - Assert.That(str.SplitOnFirst("||").Last(), Is.EqualTo("user::pass@w:rd")); + var str = "user::pass@w:rd".AsSpan(); + + Assert.That(str.LeftPart("::").EqualTo("user")); + + str.SplitOnFirst("::", out var first, out var last); + Assert.That(first.EqualTo("user")); + + Assert.That(str.RightPart("::").EqualTo("pass@w:rd")); + Assert.That(last.EqualTo("pass@w:rd")); + + Assert.That(str.LeftPart("||").EqualTo("user::pass@w:rd")); + + str.SplitOnFirst("||", out first, out last); + Assert.That(first.EqualTo("user::pass@w:rd")); + + Assert.That(str.RightPart("||").EqualTo("user::pass@w:rd")); + Assert.That(last.IsEmpty); } [Test] public void Can_SplitOnLast_char_needle() { - var parts = "user:name:pass@word".ToStringSegment().SplitOnLast(':'); - Assert.That(parts[0], Is.EqualTo("user:name")); - Assert.That(parts[1], Is.EqualTo("pass@word")); + "user:name:pass@word".AsSpan().SplitOnLast(':', out var first, out var last); + Assert.That(first.EqualTo("user:name")); + Assert.That(last.EqualTo("pass@word")); } [Test] public void Can_LastLeftPart_and_LastRightPart_char_needle() { - var str = "user:name:pass@word".ToStringSegment(); - Assert.That(str.LastLeftPart(':'), Is.EqualTo("user:name")); - Assert.That(str.SplitOnLast(':')[0], Is.EqualTo("user:name")); - Assert.That(str.LastRightPart(':'), Is.EqualTo("pass@word")); - Assert.That(str.SplitOnLast(':').Last(), Is.EqualTo("pass@word")); - - Assert.That(str.LastLeftPart('|'), Is.EqualTo("user:name:pass@word")); - Assert.That(str.SplitOnLast('|')[0], Is.EqualTo("user:name:pass@word")); - Assert.That(str.LastRightPart('|'), Is.EqualTo("user:name:pass@word")); - Assert.That(str.SplitOnLast('|').Last(), Is.EqualTo("user:name:pass@word")); + var str = "user:name:pass@word".AsSpan(); + Assert.That(str.LastLeftPart(':').EqualTo("user:name")); + + str.SplitOnLast(':', out var first, out var last); + Assert.That(first.EqualTo("user:name")); + + Assert.That(str.LastRightPart(':').EqualTo("pass@word")); + + Assert.That(last.EqualTo("pass@word")); + + Assert.That(str.LastLeftPart('|').EqualTo("user:name:pass@word")); + + str.SplitOnLast('|', out first, out last); + Assert.That(first.EqualTo("user:name:pass@word")); + Assert.That(str.LastRightPart('|').EqualTo("user:name:pass@word")); + Assert.That(last.IsEmpty); } [Test] public void Can_SplitOnLast_string_needle() { - var parts = "user:name:pass@word".ToStringSegment().SplitOnLast(":"); - Assert.That(parts[0], Is.EqualTo("user:name")); - Assert.That(parts[1], Is.EqualTo("pass@word")); + "user:name:pass@word".AsSpan().SplitOnLast(":", out var first, out var last); + Assert.That(first.EqualTo("user:name")); + Assert.That(last.EqualTo("pass@word")); } [Test] public void Can_LastLeftPart_and_LastRightPart_string_needle() { - var str = "user::name::pass@word".ToStringSegment(); - Assert.That(str.LastLeftPart("::"), Is.EqualTo("user::name")); - Assert.That(str.SplitOnLast("::")[0], Is.EqualTo("user::name")); - Assert.That(str.LastRightPart("::"), Is.EqualTo("pass@word")); - Assert.That(str.SplitOnLast("::").Last(), Is.EqualTo("pass@word")); - - Assert.That(str.LastLeftPart("||"), Is.EqualTo("user::name::pass@word")); - Assert.That(str.SplitOnLast("||")[0], Is.EqualTo("user::name::pass@word")); - Assert.That(str.LastRightPart("||"), Is.EqualTo("user::name::pass@word")); - Assert.That(str.SplitOnLast("||").Last(), Is.EqualTo("user::name::pass@word")); + var str = "user::name::pass@word".AsSpan(); + Assert.That(str.LastLeftPart("::").EqualTo("user::name")); + + str.SplitOnLast("::", out var first, out var last); + Assert.That(first.EqualTo("user::name")); + Assert.That(str.LastRightPart("::").EqualTo("pass@word")); + Assert.That(last.EqualTo("pass@word")); + + Assert.That(str.LastLeftPart("||").EqualTo("user::name::pass@word")); + + str.SplitOnLast("||", out first, out last); + Assert.That(first.EqualTo("user::name::pass@word")); + Assert.That(str.LastRightPart("||").EqualTo("user::name::pass@word")); + Assert.That(last.IsEmpty); } private static readonly char DirSep = Path.DirectorySeparatorChar; @@ -107,39 +131,39 @@ public void Can_LastLeftPart_and_LastRightPart_string_needle() public void Does_get_ParentDirectory() { var dirSep = DirSep; - var filePath = $"path{dirSep}to{dirSep}file".ToStringSegment(); - Assert.That(filePath.ParentDirectory(), Is.EqualTo("path{0}to".FormatWith(dirSep))); - Assert.That(filePath.ParentDirectory().ParentDirectory(), Is.EqualTo("path".FormatWith(dirSep))); - Assert.That(filePath.ParentDirectory().ParentDirectory().ParentDirectory(), Is.EqualTo(TypeConstants.EmptyStringSegment)); - - var filePathWithExt = $"path{dirSep}to{dirSep}file/".ToStringSegment(); - Assert.That(filePathWithExt.ParentDirectory(), Is.EqualTo($"path{dirSep}to")); - Assert.That(filePathWithExt.ParentDirectory().ParentDirectory(), Is.EqualTo("path")); - Assert.That(filePathWithExt.ParentDirectory().ParentDirectory().ParentDirectory(), Is.EqualTo(TypeConstants.EmptyStringSegment)); + var filePath = $"path{dirSep}to{dirSep}file".AsSpan(); + Assert.That(filePath.ParentDirectory().EqualTo("path{0}to".FormatWith(dirSep))); + Assert.That(filePath.ParentDirectory().ParentDirectory().EqualTo("path".FormatWith(dirSep))); + Assert.That(filePath.ParentDirectory().ParentDirectory().ParentDirectory().IsEmpty); + + var filePathWithExt = $"path{dirSep}to{dirSep}file/".AsSpan(); + Assert.That(filePathWithExt.ParentDirectory().EqualTo($"path{dirSep}to")); + Assert.That(filePathWithExt.ParentDirectory().ParentDirectory().EqualTo("path")); + Assert.That(filePathWithExt.ParentDirectory().ParentDirectory().ParentDirectory().IsEmpty); } [Test] public void Does_get_ParentDirectory_of_AltDirectorySeperator() { var dirSep = AltDirSep; - var filePath = $"path{dirSep}to{dirSep}file".ToStringSegment(); - Assert.That(filePath.ParentDirectory(), Is.EqualTo($"path{dirSep}to")); - Assert.That(filePath.ParentDirectory().ParentDirectory(), Is.EqualTo("path")); - Assert.That(filePath.ParentDirectory().ParentDirectory().ParentDirectory(), Is.EqualTo(TypeConstants.EmptyStringSegment)); - - var filePathWithExt = $"path{dirSep}to{dirSep}file{dirSep}".ToStringSegment(); - Assert.That(filePathWithExt.ParentDirectory(), Is.EqualTo($"path{dirSep}to")); - Assert.That(filePathWithExt.ParentDirectory().ParentDirectory(), Is.EqualTo("path")); - Assert.That(filePathWithExt.ParentDirectory().ParentDirectory().ParentDirectory(), Is.EqualTo(TypeConstants.EmptyStringSegment)); + var filePath = $"path{dirSep}to{dirSep}file".AsSpan(); + Assert.That(filePath.ParentDirectory().EqualTo($"path{dirSep}to")); + Assert.That(filePath.ParentDirectory().ParentDirectory().EqualTo("path")); + Assert.That(filePath.ParentDirectory().ParentDirectory().ParentDirectory().IsEmpty); + + var filePathWithExt = $"path{dirSep}to{dirSep}file{dirSep}".AsSpan(); + Assert.That(filePathWithExt.ParentDirectory().EqualTo($"path{dirSep}to")); + Assert.That(filePathWithExt.ParentDirectory().ParentDirectory().EqualTo("path")); + Assert.That(filePathWithExt.ParentDirectory().ParentDirectory().ParentDirectory().IsEmpty); } [Test] public void Does_not_alter_filepath_without_extension() { - var path = "path/dir.with.dot/to/file".ToStringSegment(); - Assert.That(path.WithoutExtension(), Is.EqualTo(path)); + var path = "path/dir.with.dot/to/file".AsSpan(); + Assert.That(path.WithoutExtension().EqualTo(path)); - Assert.That("path/to/file.ext".ToStringSegment().WithoutExtension(), Is.EqualTo("path/to/file")); + Assert.That("path/to/file.ext".AsSpan().WithoutExtension().EqualTo("path/to/file")); } //[TestCase(null, null)] @@ -151,8 +175,8 @@ public void Does_not_alter_filepath_without_extension() [TestCase("/:=#%$@{a.b}.c", ".c")] public void Does_get_Path_extension(string actual, string expected) { - Assert.That(actual.ToStringSegment().GetExtension(), Is.EqualTo(Path.GetExtension(actual))); - Assert.That(actual.ToStringSegment().GetExtension(), Is.EqualTo(expected)); + Assert.That(actual.AsSpan().GetExtension().EqualTo(Path.GetExtension(actual))); + Assert.That(actual.AsSpan().GetExtension().EqualTo(expected)); } //TODO: impl if needed @@ -164,7 +188,7 @@ public void Does_get_Path_extension(string actual, string expected) //[TestCase("text with /* and + + - - - - - - - - - - - - - + + + + \ No newline at end of file